diff options
52 files changed, 3715 insertions, 304 deletions
diff --git a/.travis.yml b/.travis.yml index 0dd5020..79377c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ addons: - libattr1-dev - libbrlapi-dev - libcap-ng-dev - - libgcc-6-dev + - libgcc-4.8-dev - libgnutls-dev - libgtk-3-dev - libiscsi-dev @@ -294,7 +294,7 @@ else DOCS= endif -SUBDIR_MAKEFLAGS=BUILD_DIR=$(BUILD_DIR) +SUBDIR_MAKEFLAGS=$(if $(V),,--no-print-directory --quiet) BUILD_DIR=$(BUILD_DIR) SUBDIR_DEVICES_MAK=$(patsubst %, %/config-devices.mak, $(TARGET_DIRS)) SUBDIR_DEVICES_MAK_DEP=$(patsubst %, %-config-devices.mak.d, $(TARGET_DIRS)) @@ -958,4 +958,5 @@ ifdef QEMU_GA_MSI_ENABLED endif @echo '' endif - @echo ' $(MAKE) V=0|1 [targets] 0 => quiet build (default), 1 => verbose build' + @echo ' $(MAKE) [targets] (quiet build, default)' + @echo ' $(MAKE) V=1 [targets] (verbose build)' @@ -471,10 +471,8 @@ for opt do --cpu=*) cpu="$optarg" ;; --extra-cflags=*) QEMU_CFLAGS="$QEMU_CFLAGS $optarg" - EXTRA_CFLAGS="$optarg" ;; --extra-cxxflags=*) QEMU_CXXFLAGS="$QEMU_CXXFLAGS $optarg" - EXTRA_CXXFLAGS="$optarg" ;; --extra-ldflags=*) LDFLAGS="$LDFLAGS $optarg" EXTRA_LDFLAGS="$optarg" @@ -1424,7 +1422,6 @@ case "$cpu" in esac QEMU_CFLAGS="$CPU_CFLAGS $QEMU_CFLAGS" -EXTRA_CFLAGS="$CPU_CFLAGS $EXTRA_CFLAGS" # For user-mode emulation the host arch has to be one we explicitly # support, even if we're using TCI. @@ -5309,7 +5306,15 @@ fi ########################################## # checks for sanitizers -write_c_skeleton +# we could use a simple skeleton for flags checks, but this also +# detect the static linking issue of ubsan, see also: +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84285 +cat > $TMPC << EOF +#include <stdint.h> +int main(void) { + return INT32_MIN / -1; +} +EOF have_asan=no have_ubsan=no @@ -5877,9 +5882,6 @@ if test "$mingw32" = "no" ; then echo "qemu_localstatedir=$local_statedir" >> $config_host_mak fi echo "qemu_helperdir=$libexecdir" >> $config_host_mak -echo "extra_cflags=$EXTRA_CFLAGS" >> $config_host_mak -echo "extra_cxxflags=$EXTRA_CXXFLAGS" >> $config_host_mak -echo "extra_ldflags=$EXTRA_LDFLAGS" >> $config_host_mak echo "qemu_localedir=$qemu_localedir" >> $config_host_mak echo "libs_softmmu=$libs_softmmu" >> $config_host_mak echo "GIT=$git" >> $config_host_mak diff --git a/default-configs/pci.mak b/default-configs/pci.mak index 49a0f28..35e7596 100644 --- a/default-configs/pci.mak +++ b/default-configs/pci.mak @@ -31,6 +31,9 @@ CONFIG_ESP_PCI=y CONFIG_SERIAL=y CONFIG_SERIAL_ISA=y CONFIG_SERIAL_PCI=y +CONFIG_CAN_BUS=y +CONFIG_CAN_SJA1000=y +CONFIG_CAN_PCI=y CONFIG_IPACK=y CONFIG_WDT_IB6300ESB=y CONFIG_PCI_TESTDEV=y diff --git a/docs/can.txt b/docs/can.txt new file mode 100644 index 0000000..a357105 --- /dev/null +++ b/docs/can.txt @@ -0,0 +1,107 @@ +QEMU CAN bus emulation support +============================== + +The CAN bus emulation provides mechanism to connect multiple +emulated CAN controller chips together by one or multiple CAN busses +(the controller device "canbus" parameter). The individual busses +can be connected to host system CAN API (at this time only Linux +SocketCAN is supported). + +The concept of busses is generic and different CAN controllers +can be implemented for it but at this time only SJA1000 chip +controller is implemented. + +The PCI addon card hardware has been selected as the first CAN +interface to implement because such device can be easily connected +to systems with different CPU architectures (x86, PowerPC, ARM, etc.). + +The project has been initially started in frame of RTEMS GSoC 2013 +slot by Jin Yang under our mentoring The initial idea was to provide generic +CAN subsystem for RTEMS. But lack of common environment for code and RTEMS +testing lead to goal change to provide environment which provides complete +emulated environment for testing and RTEMS GSoC slot has been donated +to work on CAN hardware emulation on QEMU. + +Examples how to use CAN emulation +================================= + +When QEMU with CAN PCI support is compiled then one of the next +CAN boards can be selected + + (1) CAN bus Kvaser PCI CAN-S (single SJA1000 channel) boad. QEMU startup options + -object can-bus,id=canbus0 + -device kvaser_pci,canbus=canbus0 + Add "can-host-socketcan" object to connect device to host system CAN bus + -object can-host-socketcan,id=canhost0,if=can0,canbus=canbus0 + + (2) CAN bus PCM-3680I PCI (dual SJA1000 channel) emulation + -object can-bus,id=canbus0 + -device pcm3680_pci,canbus0=canbus0,canbus1=canbus0 + + another example: + -object can-bus,id=canbus0 + -object can-bus,id=canbus1 + -device pcm3680_pci,canbus0=canbus0,canbus1=canbus1 + + (3) CAN bus MIOe-3680 PCI (dual SJA1000 channel) emulation + -device mioe3680_pci,canbus0=canbus0 + + +The ''kvaser_pci'' board/device model is compatible with and has been tested with +''kvaser_pci'' driver included in mainline Linux kernel. +The tested setup was Linux 4.9 kernel on the host and guest side. +Example for qemu-system-x86_64: + + qemu-system-x86_64 -enable-kvm -kernel /boot/vmlinuz-4.9.0-4-amd64 \ + -initrd ramdisk.cpio \ + -virtfs local,path=shareddir,security_model=none,mount_tag=shareddir \ + -object can-bus,id=canbus0 \ + -object can-host-socketcan,id=canhost0,if=can0,canbus=canbus0 \ + -device kvaser_pci,canbus=canbus0 \ + -nographic -append "console=ttyS0" + +Example for qemu-system-arm: + + qemu-system-arm -cpu arm1176 -m 256 -M versatilepb \ + -kernel kernel-qemu-arm1176-versatilepb \ + -hda rpi-wheezy-overlay \ + -append "console=ttyAMA0 root=/dev/sda2 ro init=/sbin/init-overlay" \ + -nographic \ + -virtfs local,path=shareddir,security_model=none,mount_tag=shareddir \ + -object can-bus,id=canbus0 \ + -object can-host-socketcan,id=canhost0,if=can0,canbus=canbus0 \ + -device kvaser_pci,canbus=canbus0,host=can0 \ + +The CAN interface of the host system has to be configured for proper +bitrate and set up. Configuration is not propagated from emulated +devices through bus to the physical host device. Example configuration +for 1 Mbit/s + + ip link set can0 type can bitrate 1000000 + ip link set can0 up + +Virtual (host local only) can interface can be used on the host +side instead of physical interface + + ip link add dev can0 type vcan + +The CAN interface on the host side can be used to analyze CAN +traffic with "candump" command which is included in "can-utils". + + candump can0 + +Links to other resources +======================== + + (1) Repository with development branch can-pci at Czech Technical University + https://gitlab.fel.cvut.cz/canbus/qemu-canbus + (2) GitHub repository with can-pci and our other changes included + https://gitlab.fel.cvut.cz/canbus/qemu-canbus + (3) RTEMS page describing project + https://devel.rtems.org/wiki/Developer/Simulators/QEMU/CANEmulation + (4) RTLWS 2015 article about the projevt and its use with CANopen emulation + http://rtime.felk.cvut.cz/publications/public/rtlws2015-qemu-can.pdf + Slides + http://rtime.felk.cvut.cz/publications/public/rtlws2015-qemu-can-slides.pdf + (5) Linux SocketCAN utilities + https://github.com/linux-can/can-utils/
\ No newline at end of file diff --git a/hw/arm/bcm2835_peripherals.c b/hw/arm/bcm2835_peripherals.c index 12e0dd1..13b6397 100644 --- a/hw/arm/bcm2835_peripherals.c +++ b/hw/arm/bcm2835_peripherals.c @@ -19,7 +19,7 @@ #define BCM2835_VC_PERI_BASE 0x7e000000 /* Capabilities for SD controller: no DMA, high-speed, default clocks etc. */ -#define BCM2835_SDHC_CAPAREG 0x52034b4 +#define BCM2835_SDHC_CAPAREG 0x52134b4 static void bcm2835_peripherals_init(Object *obj) { @@ -254,14 +254,19 @@ static void bcm2835_peripherals_realize(DeviceState *dev, Error **errp) memory_region_add_subregion(&s->peri_mr, RNG_OFFSET, sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->rng), 0)); - /* Extended Mass Media Controller */ - object_property_set_int(OBJECT(&s->sdhci), BCM2835_SDHC_CAPAREG, "capareg", - &err); - if (err) { - error_propagate(errp, err); - return; - } - + /* Extended Mass Media Controller + * + * Compatible with: + * - SD Host Controller Specification Version 3.0 Draft 1.0 + * - SDIO Specification Version 3.0 + * - MMC Specification Version 4.4 + * + * For the exact details please refer to the Arasan documentation: + * SD3.0_Host_AHB_eMMC4.4_Usersguide_ver5.9_jan11_10.pdf + */ + object_property_set_uint(OBJECT(&s->sdhci), 3, "sd-spec-version", &err); + object_property_set_uint(OBJECT(&s->sdhci), BCM2835_SDHC_CAPAREG, "capareg", + &err); object_property_set_bool(OBJECT(&s->sdhci), true, "pending-insert-quirk", &err); if (err) { diff --git a/hw/arm/exynos4210.c b/hw/arm/exynos4210.c index e8e1d81..06f9d1f 100644 --- a/hw/arm/exynos4210.c +++ b/hw/arm/exynos4210.c @@ -377,8 +377,20 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem) BlockBackend *blk; DriveInfo *di; + /* Compatible with: + * - SD Host Controller Specification Version 2.0 + * - SDIO Specification Version 2.0 + * - MMC Specification Version 4.3 + * - SDMA + * - ADMA2 + * + * As this part of the Exynos4210 is not publically available, + * we used the "HS-MMC Controller S3C2416X RISC Microprocessor" + * public datasheet which is very similar (implementing + * MMC Specification Version 4.0 being the only difference noted) + */ dev = qdev_create(NULL, TYPE_SYSBUS_SDHCI); - qdev_prop_set_uint32(dev, "capareg", EXYNOS4210_SDHCI_CAPABILITIES); + qdev_prop_set_uint64(dev, "capareg", EXYNOS4210_SDHCI_CAPABILITIES); qdev_init_nofail(dev); busdev = SYS_BUS_DEVICE(dev); diff --git a/hw/arm/fsl-imx6.c b/hw/arm/fsl-imx6.c index e6559a8..b6ac72d 100644 --- a/hw/arm/fsl-imx6.c +++ b/hw/arm/fsl-imx6.c @@ -27,6 +27,8 @@ #include "chardev/char.h" #include "qemu/error-report.h" +#define IMX6_ESDHC_CAPABILITIES 0x057834b4 + #define NAME_SIZE 20 static void fsl_imx6_init(Object *obj) @@ -348,6 +350,11 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) { FSL_IMX6_uSDHC4_ADDR, FSL_IMX6_uSDHC4_IRQ }, }; + /* UHS-I SDIO3.0 SDR104 1.8V ADMA */ + object_property_set_uint(OBJECT(&s->esdhc[i]), 3, "sd-spec-version", + &err); + object_property_set_uint(OBJECT(&s->esdhc[i]), IMX6_ESDHC_CAPABILITIES, + "capareg", &err); object_property_set_bool(OBJECT(&s->esdhc[i]), true, "realized", &err); if (err) { error_propagate(errp, err); diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c index 1836a4e..0f76333 100644 --- a/hw/arm/xilinx_zynq.c +++ b/hw/arm/xilinx_zynq.c @@ -61,6 +61,8 @@ static const int dma_irqs[8] = { #define SLCR_XILINX_UNLOCK_KEY 0xdf0d #define SLCR_XILINX_LOCK_KEY 0x767b +#define ZYNQ_SDHCI_CAPABILITIES 0x69ec0080 /* Datasheet: UG585 (v1.12.1) */ + #define ARMV7_IMM16(x) (extract32((x), 0, 12) | \ extract32((x), 12, 4) << 16) @@ -165,10 +167,8 @@ static void zynq_init(MachineState *machine) MemoryRegion *address_space_mem = get_system_memory(); MemoryRegion *ext_ram = g_new(MemoryRegion, 1); MemoryRegion *ocm_ram = g_new(MemoryRegion, 1); - DeviceState *dev, *carddev; + DeviceState *dev; SysBusDevice *busdev; - DriveInfo *di; - BlockBackend *blk; qemu_irq pic[64]; int n; @@ -247,27 +247,32 @@ static void zynq_init(MachineState *machine) gem_init(&nd_table[0], 0xE000B000, pic[54-IRQ_OFFSET]); gem_init(&nd_table[1], 0xE000C000, pic[77-IRQ_OFFSET]); - dev = qdev_create(NULL, TYPE_SYSBUS_SDHCI); - qdev_init_nofail(dev); - sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xE0100000); - sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[56-IRQ_OFFSET]); - - di = drive_get_next(IF_SD); - blk = di ? blk_by_legacy_dinfo(di) : NULL; - carddev = qdev_create(qdev_get_child_bus(dev, "sd-bus"), TYPE_SD_CARD); - qdev_prop_set_drive(carddev, "drive", blk, &error_fatal); - object_property_set_bool(OBJECT(carddev), true, "realized", &error_fatal); - - dev = qdev_create(NULL, TYPE_SYSBUS_SDHCI); - qdev_init_nofail(dev); - sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xE0101000); - sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[79-IRQ_OFFSET]); - - di = drive_get_next(IF_SD); - blk = di ? blk_by_legacy_dinfo(di) : NULL; - carddev = qdev_create(qdev_get_child_bus(dev, "sd-bus"), TYPE_SD_CARD); - qdev_prop_set_drive(carddev, "drive", blk, &error_fatal); - object_property_set_bool(OBJECT(carddev), true, "realized", &error_fatal); + for (n = 0; n < 2; n++) { + int hci_irq = n ? 79 : 56; + hwaddr hci_addr = n ? 0xE0101000 : 0xE0100000; + DriveInfo *di; + BlockBackend *blk; + DeviceState *carddev; + + /* Compatible with: + * - SD Host Controller Specification Version 2.0 Part A2 + * - SDIO Specification Version 2.0 + * - MMC Specification Version 3.31 + */ + dev = qdev_create(NULL, TYPE_SYSBUS_SDHCI); + qdev_prop_set_uint8(dev, "sd-spec-version", 2); + qdev_prop_set_uint64(dev, "capareg", ZYNQ_SDHCI_CAPABILITIES); + qdev_init_nofail(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, hci_addr); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[hci_irq - IRQ_OFFSET]); + + di = drive_get_next(IF_SD); + blk = di ? blk_by_legacy_dinfo(di) : NULL; + carddev = qdev_create(qdev_get_child_bus(dev, "sd-bus"), TYPE_SD_CARD); + qdev_prop_set_drive(carddev, "drive", blk, &error_fatal); + object_property_set_bool(OBJECT(carddev), true, "realized", + &error_fatal); + } dev = qdev_create(NULL, TYPE_ZYNQ_XADC); qdev_init_nofail(dev); diff --git a/hw/arm/xlnx-zynqmp.c b/hw/arm/xlnx-zynqmp.c index ca398c4..4b93a3a 100644 --- a/hw/arm/xlnx-zynqmp.c +++ b/hw/arm/xlnx-zynqmp.c @@ -53,6 +53,8 @@ #define IPI_ADDR 0xFF300000 #define IPI_IRQ 64 +#define SDHCI_CAPABILITIES 0x280737ec6481 /* Datasheet: UG1085 (v1.7) */ + static const uint64_t gem_addr[XLNX_ZYNQMP_NUM_GEMS] = { 0xFF0B0000, 0xFF0C0000, 0xFF0D0000, 0xFF0E0000, }; @@ -387,22 +389,28 @@ static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp) sysbus_connect_irq(SYS_BUS_DEVICE(&s->sata), 0, gic_spi[SATA_INTR]); for (i = 0; i < XLNX_ZYNQMP_NUM_SDHCI; i++) { - char *bus_name; - - object_property_set_bool(OBJECT(&s->sdhci[i]), true, - "realized", &err); + char *bus_name = g_strdup_printf("sd-bus%d", i); + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->sdhci[i]); + Object *sdhci = OBJECT(&s->sdhci[i]); + + /* Compatible with: + * - SD Host Controller Specification Version 3.00 + * - SDIO Specification Version 3.0 + * - eMMC Specification Version 4.51 + */ + object_property_set_uint(sdhci, 3, "sd-spec-version", &err); + object_property_set_uint(sdhci, SDHCI_CAPABILITIES, "capareg", &err); + object_property_set_uint(sdhci, UHS_I, "uhs", &err); + object_property_set_bool(sdhci, true, "realized", &err); if (err) { error_propagate(errp, err); return; } - sysbus_mmio_map(SYS_BUS_DEVICE(&s->sdhci[i]), 0, - sdhci_addr[i]); - sysbus_connect_irq(SYS_BUS_DEVICE(&s->sdhci[i]), 0, - gic_spi[sdhci_intr[i]]); + sysbus_mmio_map(sbd, 0, sdhci_addr[i]); + sysbus_connect_irq(sbd, 0, gic_spi[sdhci_intr[i]]); + /* Alias controller SD bus to the SoC itself */ - bus_name = g_strdup_printf("sd-bus%d", i); - object_property_add_alias(OBJECT(s), bus_name, - OBJECT(&s->sdhci[i]), "sd-bus", + object_property_add_alias(OBJECT(s), bus_name, sdhci, "sd-bus", &error_abort); g_free(bus_name); } diff --git a/hw/display/cg3.c b/hw/display/cg3.c index cafd9f4..6fff485 100644 --- a/hw/display/cg3.c +++ b/hw/display/cg3.c @@ -108,7 +108,6 @@ static void cg3_update_display(void *opaque) data = (uint32_t *)surface_data(surface); if (!s->full_update) { - memory_region_sync_dirty_bitmap(&s->vram_mem); snap = memory_region_snapshot_and_clear_dirty(&s->vram_mem, 0x0, memory_region_size(&s->vram_mem), DIRTY_MEMORY_VGA); diff --git a/hw/display/exynos4210_fimd.c b/hw/display/exynos4210_fimd.c index 86e37e9..f011ea5 100644 --- a/hw/display/exynos4210_fimd.c +++ b/hw/display/exynos4210_fimd.c @@ -1289,7 +1289,6 @@ static void exynos4210_fimd_update(void *opaque) scrn_width = w->virtpage_width; /* Total width of virtual screen page in bytes */ inc_size = scrn_width + w->virtpage_offsize; - memory_region_sync_dirty_bitmap(w->mem_section.mr); host_fb_addr = w->host_fb_addr; fb_line_addr = w->mem_section.offset_within_region; snap = memory_region_snapshot_and_clear_dirty(w->mem_section.mr, diff --git a/hw/display/framebuffer.c b/hw/display/framebuffer.c index d7310d2..36e3db1 100644 --- a/hw/display/framebuffer.c +++ b/hw/display/framebuffer.c @@ -83,7 +83,6 @@ void framebuffer_update_display( if (!mem) { return; } - memory_region_sync_dirty_bitmap(mem); addr = mem_section->offset_within_region; src = memory_region_get_ram_ptr(mem) + addr; diff --git a/hw/display/g364fb.c b/hw/display/g364fb.c index 86557d1..819f8be 100644 --- a/hw/display/g364fb.c +++ b/hw/display/g364fb.c @@ -62,15 +62,15 @@ typedef struct G364State { #define G364_PAGE_SIZE 4096 -static inline int check_dirty(G364State *s, ram_addr_t page) +static inline int check_dirty(G364State *s, DirtyBitmapSnapshot *snap, ram_addr_t page) { - return memory_region_test_and_clear_dirty(&s->mem_vram, page, G364_PAGE_SIZE, - DIRTY_MEMORY_VGA); + return memory_region_snapshot_get_dirty(&s->mem_vram, snap, page, G364_PAGE_SIZE); } static void g364fb_draw_graphic8(G364State *s) { DisplaySurface *surface = qemu_console_surface(s->con); + DirtyBitmapSnapshot *snap; int i, w; uint8_t *vram; uint8_t *data_display, *dd; @@ -122,8 +122,10 @@ static void g364fb_draw_graphic8(G364State *s) vram = s->vram + s->top_of_screen; /* XXX: out of range in vram? */ data_display = dd = surface_data(surface); + snap = memory_region_snapshot_and_clear_dirty(&s->mem_vram, 0, s->vram_size, + DIRTY_MEMORY_VGA); while (y < s->height) { - if (check_dirty(s, page)) { + if (check_dirty(s, snap, page)) { if (y < ymin) ymin = ymax = y; if (x < xmin) @@ -244,7 +246,6 @@ static void g364fb_update_display(void *opaque) qemu_console_resize(s->con, s->width, s->height); } - memory_region_sync_dirty_bitmap(&s->mem_vram); if (s->ctla & CTLA_FORCE_BLANK) { g364fb_draw_blank(s); } else if (s->depth == 8) { diff --git a/hw/display/sm501.c b/hw/display/sm501.c index 134cbed..f4bb33c 100644 --- a/hw/display/sm501.c +++ b/hw/display/sm501.c @@ -1508,7 +1508,6 @@ static void sm501_update_display(void *opaque) } /* draw each line according to conditions */ - memory_region_sync_dirty_bitmap(&s->local_mem_region); snap = memory_region_snapshot_and_clear_dirty(&s->local_mem_region, offset, width * height * src_bpp, DIRTY_MEMORY_VGA); for (y = 0, offset = 0; y < height; y++, offset += width * src_bpp) { diff --git a/hw/display/tcx.c b/hw/display/tcx.c index daa93e0..b2786ee 100644 --- a/hw/display/tcx.c +++ b/hw/display/tcx.c @@ -236,7 +236,6 @@ static void tcx_update_display(void *opaque) dd = surface_stride(surface); ds = 1024; - memory_region_sync_dirty_bitmap(&ts->vram_mem); snap = memory_region_snapshot_and_clear_dirty(&ts->vram_mem, 0x0, memory_region_size(&ts->vram_mem), DIRTY_MEMORY_VGA); @@ -292,7 +291,6 @@ static void tcx24_update_display(void *opaque) dd = surface_stride(surface); ds = 1024; - memory_region_sync_dirty_bitmap(&ts->vram_mem); snap = memory_region_snapshot_and_clear_dirty(&ts->vram_mem, 0x0, memory_region_size(&ts->vram_mem), DIRTY_MEMORY_VGA); diff --git a/hw/display/vga.c b/hw/display/vga.c index 6e78a4e..28f298b 100644 --- a/hw/display/vga.c +++ b/hw/display/vga.c @@ -1444,11 +1444,6 @@ static bool vga_scanline_invalidated(VGACommonState *s, int y) return s->invalidated_y_table[y >> 5] & (1 << (y & 0x1f)); } -void vga_sync_dirty_bitmap(VGACommonState *s) -{ - memory_region_sync_dirty_bitmap(&s->vram); -} - void vga_dirty_log_start(VGACommonState *s) { memory_region_set_log(&s->vram, true, DIRTY_MEMORY_VGA); @@ -1638,7 +1633,6 @@ static void vga_draw_graphic(VGACommonState *s, int full_update) y1 = 0; if (!full_update) { - vga_sync_dirty_bitmap(s); if (s->line_compare < height) { /* split screen mode */ region_start = 0; diff --git a/hw/net/Makefile.objs b/hw/net/Makefile.objs index 4171af0..ab22968 100644 --- a/hw/net/Makefile.objs +++ b/hw/net/Makefile.objs @@ -46,3 +46,5 @@ common-obj-$(CONFIG_ROCKER) += rocker/rocker.o rocker/rocker_fp.o \ rocker/rocker_desc.o rocker/rocker_world.o \ rocker/rocker_of_dpa.o obj-$(call lnot,$(CONFIG_ROCKER)) += rocker/qmp-norocker.o + +common-obj-$(CONFIG_CAN_BUS) += can/ diff --git a/hw/net/can/Makefile.objs b/hw/net/can/Makefile.objs new file mode 100644 index 0000000..9f0c4ee --- /dev/null +++ b/hw/net/can/Makefile.objs @@ -0,0 +1,4 @@ +common-obj-$(CONFIG_CAN_SJA1000) += can_sja1000.o +common-obj-$(CONFIG_CAN_PCI) += can_kvaser_pci.o +common-obj-$(CONFIG_CAN_PCI) += can_pcm3680_pci.o +common-obj-$(CONFIG_CAN_PCI) += can_mioe3680_pci.o diff --git a/hw/net/can/can_kvaser_pci.c b/hw/net/can/can_kvaser_pci.c new file mode 100644 index 0000000..5f82f43 --- /dev/null +++ b/hw/net/can/can_kvaser_pci.c @@ -0,0 +1,319 @@ +/* + * Kvaser PCI CAN device (SJA1000 based) emulation + * + * Copyright (c) 2013-2014 Jin Yang + * Copyright (c) 2014-2018 Pavel Pisa + * + * Partially based on educational PCIexpress APOHW hardware + * emulator used fro class A0B36APO at CTU FEE course by + * Rostislav Lisovy and Pavel Pisa + * + * Initial development supported by Google GSoC 2013 from RTEMS project slot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF 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/osdep.h" +#include "qemu/event_notifier.h" +#include "qemu/thread.h" +#include "qemu/sockets.h" +#include "qapi/error.h" +#include "chardev/char.h" +#include "hw/hw.h" +#include "hw/pci/pci.h" +#include "net/can_emu.h" + +#include "can_sja1000.h" + +#define TYPE_CAN_PCI_DEV "kvaser_pci" + +#define KVASER_PCI_DEV(obj) \ + OBJECT_CHECK(KvaserPCIState, (obj), TYPE_CAN_PCI_DEV) + +#ifndef KVASER_PCI_VENDOR_ID1 +#define KVASER_PCI_VENDOR_ID1 0x10e8 /* the PCI device and vendor IDs */ +#endif + +#ifndef KVASER_PCI_DEVICE_ID1 +#define KVASER_PCI_DEVICE_ID1 0x8406 +#endif + +#define KVASER_PCI_S5920_RANGE 0x80 +#define KVASER_PCI_SJA_RANGE 0x80 +#define KVASER_PCI_XILINX_RANGE 0x8 + +#define KVASER_PCI_BYTES_PER_SJA 0x20 + +#define S5920_OMB 0x0C +#define S5920_IMB 0x1C +#define S5920_MBEF 0x34 +#define S5920_INTCSR 0x38 +#define S5920_RCR 0x3C +#define S5920_PTCR 0x60 + +#define S5920_INTCSR_ADDON_INTENABLE_M 0x2000 +#define S5920_INTCSR_INTERRUPT_ASSERTED_M 0x800000 + +#define KVASER_PCI_XILINX_VERINT 7 /* Lower nibble simulate interrupts, + high nibble version number. */ + +#define KVASER_PCI_XILINX_VERSION_NUMBER 13 + +typedef struct KvaserPCIState { + /*< private >*/ + PCIDevice dev; + /*< public >*/ + MemoryRegion s5920_io; + MemoryRegion sja_io; + MemoryRegion xilinx_io; + + CanSJA1000State sja_state; + qemu_irq irq; + + uint32_t s5920_intcsr; + uint32_t s5920_irqstate; + + CanBusState *canbus; +} KvaserPCIState; + +static void kvaser_pci_irq_handler(void *opaque, int irq_num, int level) +{ + KvaserPCIState *d = (KvaserPCIState *)opaque; + + d->s5920_irqstate = level; + if (d->s5920_intcsr & S5920_INTCSR_ADDON_INTENABLE_M) { + pci_set_irq(&d->dev, level); + } +} + +static void kvaser_pci_reset(DeviceState *dev) +{ + KvaserPCIState *d = KVASER_PCI_DEV(dev); + CanSJA1000State *s = &d->sja_state; + + can_sja_hardware_reset(s); +} + +static uint64_t kvaser_pci_s5920_io_read(void *opaque, hwaddr addr, + unsigned size) +{ + KvaserPCIState *d = opaque; + uint64_t val; + + switch (addr) { + case S5920_INTCSR: + val = d->s5920_intcsr; + val &= ~S5920_INTCSR_INTERRUPT_ASSERTED_M; + if (d->s5920_irqstate) { + val |= S5920_INTCSR_INTERRUPT_ASSERTED_M; + } + return val; + } + return 0; +} + +static void kvaser_pci_s5920_io_write(void *opaque, hwaddr addr, uint64_t data, + unsigned size) +{ + KvaserPCIState *d = opaque; + + switch (addr) { + case S5920_INTCSR: + if (d->s5920_irqstate && + ((d->s5920_intcsr ^ data) & S5920_INTCSR_ADDON_INTENABLE_M)) { + pci_set_irq(&d->dev, !!(data & S5920_INTCSR_ADDON_INTENABLE_M)); + } + d->s5920_intcsr = data; + break; + } +} + +static uint64_t kvaser_pci_sja_io_read(void *opaque, hwaddr addr, unsigned size) +{ + KvaserPCIState *d = opaque; + CanSJA1000State *s = &d->sja_state; + + if (addr >= KVASER_PCI_BYTES_PER_SJA) { + return 0; + } + + return can_sja_mem_read(s, addr, size); +} + +static void kvaser_pci_sja_io_write(void *opaque, hwaddr addr, uint64_t data, + unsigned size) +{ + KvaserPCIState *d = opaque; + CanSJA1000State *s = &d->sja_state; + + if (addr >= KVASER_PCI_BYTES_PER_SJA) { + return; + } + + can_sja_mem_write(s, addr, data, size); +} + +static uint64_t kvaser_pci_xilinx_io_read(void *opaque, hwaddr addr, + unsigned size) +{ + switch (addr) { + case KVASER_PCI_XILINX_VERINT: + return (KVASER_PCI_XILINX_VERSION_NUMBER << 4) | 0; + } + + return 0; +} + +static void kvaser_pci_xilinx_io_write(void *opaque, hwaddr addr, uint64_t data, + unsigned size) +{ + +} + +static const MemoryRegionOps kvaser_pci_s5920_io_ops = { + .read = kvaser_pci_s5920_io_read, + .write = kvaser_pci_s5920_io_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps kvaser_pci_sja_io_ops = { + .read = kvaser_pci_sja_io_read, + .write = kvaser_pci_sja_io_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .max_access_size = 1, + }, +}; + +static const MemoryRegionOps kvaser_pci_xilinx_io_ops = { + .read = kvaser_pci_xilinx_io_read, + .write = kvaser_pci_xilinx_io_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .max_access_size = 1, + }, +}; + +static void kvaser_pci_realize(PCIDevice *pci_dev, Error **errp) +{ + KvaserPCIState *d = KVASER_PCI_DEV(pci_dev); + CanSJA1000State *s = &d->sja_state; + uint8_t *pci_conf; + + pci_conf = pci_dev->config; + pci_conf[PCI_INTERRUPT_PIN] = 0x01; /* interrupt pin A */ + + d->irq = qemu_allocate_irq(kvaser_pci_irq_handler, d, 0); + + can_sja_init(s, d->irq); + + if (can_sja_connect_to_bus(s, d->canbus) < 0) { + error_setg(errp, "can_sja_connect_to_bus failed"); + return; + } + + memory_region_init_io(&d->s5920_io, OBJECT(d), &kvaser_pci_s5920_io_ops, + d, "kvaser_pci-s5920", KVASER_PCI_S5920_RANGE); + memory_region_init_io(&d->sja_io, OBJECT(d), &kvaser_pci_sja_io_ops, + d, "kvaser_pci-sja", KVASER_PCI_SJA_RANGE); + memory_region_init_io(&d->xilinx_io, OBJECT(d), &kvaser_pci_xilinx_io_ops, + d, "kvaser_pci-xilinx", KVASER_PCI_XILINX_RANGE); + + pci_register_bar(&d->dev, /*BAR*/ 0, PCI_BASE_ADDRESS_SPACE_IO, + &d->s5920_io); + pci_register_bar(&d->dev, /*BAR*/ 1, PCI_BASE_ADDRESS_SPACE_IO, + &d->sja_io); + pci_register_bar(&d->dev, /*BAR*/ 2, PCI_BASE_ADDRESS_SPACE_IO, + &d->xilinx_io); +} + +static void kvaser_pci_exit(PCIDevice *pci_dev) +{ + KvaserPCIState *d = KVASER_PCI_DEV(pci_dev); + CanSJA1000State *s = &d->sja_state; + + can_sja_disconnect(s); + + qemu_free_irq(d->irq); +} + +static const VMStateDescription vmstate_kvaser_pci = { + .name = "kvaser_pci", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(dev, KvaserPCIState), + /* Load this before sja_state. */ + VMSTATE_UINT32(s5920_intcsr, KvaserPCIState), + VMSTATE_STRUCT(sja_state, KvaserPCIState, 0, vmstate_can_sja, + CanSJA1000State), + VMSTATE_END_OF_LIST() + } +}; + +static void kvaser_pci_instance_init(Object *obj) +{ + KvaserPCIState *d = KVASER_PCI_DEV(obj); + + object_property_add_link(obj, "canbus", TYPE_CAN_BUS, + (Object **)&d->canbus, + qdev_prop_allow_set_link_before_realize, + 0, &error_abort); +} + +static void kvaser_pci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->realize = kvaser_pci_realize; + k->exit = kvaser_pci_exit; + k->vendor_id = KVASER_PCI_VENDOR_ID1; + k->device_id = KVASER_PCI_DEVICE_ID1; + k->revision = 0x00; + k->class_id = 0x00ff00; + dc->desc = "Kvaser PCICANx"; + dc->vmsd = &vmstate_kvaser_pci; + dc->reset = kvaser_pci_reset; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static const TypeInfo kvaser_pci_info = { + .name = TYPE_CAN_PCI_DEV, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(KvaserPCIState), + .class_init = kvaser_pci_class_init, + .instance_init = kvaser_pci_instance_init, + .interfaces = (InterfaceInfo[]) { + { INTERFACE_CONVENTIONAL_PCI_DEVICE }, + { }, + }, +}; + +static void kvaser_pci_register_types(void) +{ + type_register_static(&kvaser_pci_info); +} + +type_init(kvaser_pci_register_types) diff --git a/hw/net/can/can_mioe3680_pci.c b/hw/net/can/can_mioe3680_pci.c new file mode 100644 index 0000000..fd20b88 --- /dev/null +++ b/hw/net/can/can_mioe3680_pci.c @@ -0,0 +1,262 @@ +/* + * MIOe-3680 PCI CAN device (SJA1000 based) emulation + * + * Copyright (c) 2016 Deniz Eren (deniz.eren@icloud.com) + * + * Based on Kvaser PCI CAN device (SJA1000 based) emulation implemented by + * Jin Yang and Pavel Pisa + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF 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/osdep.h" +#include "qemu/event_notifier.h" +#include "qemu/thread.h" +#include "qemu/sockets.h" +#include "qapi/error.h" +#include "chardev/char.h" +#include "hw/hw.h" +#include "hw/pci/pci.h" +#include "net/can_emu.h" + +#include "can_sja1000.h" + +#define TYPE_CAN_PCI_DEV "mioe3680_pci" + +#define MIOe3680_PCI_DEV(obj) \ + OBJECT_CHECK(Mioe3680PCIState, (obj), TYPE_CAN_PCI_DEV) + +/* the PCI device and vendor IDs */ +#ifndef MIOe3680_PCI_VENDOR_ID1 +#define MIOe3680_PCI_VENDOR_ID1 0x13fe +#endif + +#ifndef MIOe3680_PCI_DEVICE_ID1 +#define MIOe3680_PCI_DEVICE_ID1 0xc302 +#endif + +#define MIOe3680_PCI_SJA_COUNT 2 +#define MIOe3680_PCI_SJA_RANGE 0x400 + +#define MIOe3680_PCI_BYTES_PER_SJA 0x80 + +typedef struct Mioe3680PCIState { + /*< private >*/ + PCIDevice dev; + /*< public >*/ + MemoryRegion sja_io[MIOe3680_PCI_SJA_COUNT]; + + CanSJA1000State sja_state[MIOe3680_PCI_SJA_COUNT]; + qemu_irq irq; + + char *model; /* The model that support, only SJA1000 now. */ + CanBusState *canbus[MIOe3680_PCI_SJA_COUNT]; +} Mioe3680PCIState; + +static void mioe3680_pci_reset(DeviceState *dev) +{ + Mioe3680PCIState *d = MIOe3680_PCI_DEV(dev); + int i; + + for (i = 0 ; i < MIOe3680_PCI_SJA_COUNT; i++) { + can_sja_hardware_reset(&d->sja_state[i]); + } +} + +static uint64_t mioe3680_pci_sja1_io_read(void *opaque, hwaddr addr, + unsigned size) +{ + Mioe3680PCIState *d = opaque; + CanSJA1000State *s = &d->sja_state[0]; + + if (addr >= MIOe3680_PCI_BYTES_PER_SJA) { + return 0; + } + + return can_sja_mem_read(s, addr >> 2, size); +} + +static void mioe3680_pci_sja1_io_write(void *opaque, hwaddr addr, uint64_t data, + unsigned size) +{ + Mioe3680PCIState *d = opaque; + CanSJA1000State *s = &d->sja_state[0]; + + if (addr >= MIOe3680_PCI_BYTES_PER_SJA) { + return; + } + + can_sja_mem_write(s, addr >> 2, data, size); +} + +static uint64_t mioe3680_pci_sja2_io_read(void *opaque, hwaddr addr, + unsigned size) +{ + Mioe3680PCIState *d = opaque; + CanSJA1000State *s = &d->sja_state[1]; + + if (addr >= MIOe3680_PCI_BYTES_PER_SJA) { + return 0; + } + + return can_sja_mem_read(s, addr >> 2, size); +} + +static void mioe3680_pci_sja2_io_write(void *opaque, hwaddr addr, uint64_t data, + unsigned size) +{ + Mioe3680PCIState *d = opaque; + CanSJA1000State *s = &d->sja_state[1]; + + if (addr >= MIOe3680_PCI_BYTES_PER_SJA) { + return; + } + + can_sja_mem_write(s, addr >> 2, data, size); +} + +static const MemoryRegionOps mioe3680_pci_sja1_io_ops = { + .read = mioe3680_pci_sja1_io_read, + .write = mioe3680_pci_sja1_io_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .max_access_size = 1, + }, +}; + +static const MemoryRegionOps mioe3680_pci_sja2_io_ops = { + .read = mioe3680_pci_sja2_io_read, + .write = mioe3680_pci_sja2_io_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .max_access_size = 1, + }, +}; + +static void mioe3680_pci_realize(PCIDevice *pci_dev, Error **errp) +{ + Mioe3680PCIState *d = MIOe3680_PCI_DEV(pci_dev); + uint8_t *pci_conf; + int i; + + pci_conf = pci_dev->config; + pci_conf[PCI_INTERRUPT_PIN] = 0x01; /* interrupt pin A */ + + d->irq = pci_allocate_irq(&d->dev); + + for (i = 0 ; i < MIOe3680_PCI_SJA_COUNT; i++) { + can_sja_init(&d->sja_state[i], d->irq); + } + + for (i = 0 ; i < MIOe3680_PCI_SJA_COUNT; i++) { + if (can_sja_connect_to_bus(&d->sja_state[i], d->canbus[i]) < 0) { + error_setg(errp, "can_sja_connect_to_bus failed"); + return; + } + } + + memory_region_init_io(&d->sja_io[0], OBJECT(d), &mioe3680_pci_sja1_io_ops, + d, "mioe3680_pci-sja1", MIOe3680_PCI_SJA_RANGE); + memory_region_init_io(&d->sja_io[1], OBJECT(d), &mioe3680_pci_sja2_io_ops, + d, "mioe3680_pci-sja2", MIOe3680_PCI_SJA_RANGE); + + for (i = 0 ; i < MIOe3680_PCI_SJA_COUNT; i++) { + pci_register_bar(&d->dev, /*BAR*/ i, PCI_BASE_ADDRESS_SPACE_IO, + &d->sja_io[i]); + } +} + +static void mioe3680_pci_exit(PCIDevice *pci_dev) +{ + Mioe3680PCIState *d = MIOe3680_PCI_DEV(pci_dev); + int i; + + for (i = 0 ; i < MIOe3680_PCI_SJA_COUNT; i++) { + can_sja_disconnect(&d->sja_state[i]); + } + + qemu_free_irq(d->irq); +} + +static const VMStateDescription vmstate_mioe3680_pci = { + .name = "mioe3680_pci", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(dev, Mioe3680PCIState), + VMSTATE_STRUCT(sja_state[0], Mioe3680PCIState, 0, vmstate_can_sja, + CanSJA1000State), + VMSTATE_STRUCT(sja_state[1], Mioe3680PCIState, 0, vmstate_can_sja, + CanSJA1000State), + VMSTATE_END_OF_LIST() + } +}; + +static void mioe3680_pci_instance_init(Object *obj) +{ + Mioe3680PCIState *d = MIOe3680_PCI_DEV(obj); + + object_property_add_link(obj, "canbus0", TYPE_CAN_BUS, + (Object **)&d->canbus[0], + qdev_prop_allow_set_link_before_realize, + 0, &error_abort); + object_property_add_link(obj, "canbus1", TYPE_CAN_BUS, + (Object **)&d->canbus[1], + qdev_prop_allow_set_link_before_realize, + 0, &error_abort); +} + +static void mioe3680_pci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->realize = mioe3680_pci_realize; + k->exit = mioe3680_pci_exit; + k->vendor_id = MIOe3680_PCI_VENDOR_ID1; + k->device_id = MIOe3680_PCI_DEVICE_ID1; + k->revision = 0x00; + k->class_id = 0x000c09; + k->subsystem_vendor_id = MIOe3680_PCI_VENDOR_ID1; + k->subsystem_id = MIOe3680_PCI_DEVICE_ID1; + dc->desc = "Mioe3680 PCICANx"; + dc->vmsd = &vmstate_mioe3680_pci; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + dc->reset = mioe3680_pci_reset; +} + +static const TypeInfo mioe3680_pci_info = { + .name = TYPE_CAN_PCI_DEV, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(Mioe3680PCIState), + .class_init = mioe3680_pci_class_init, + .instance_init = mioe3680_pci_instance_init, + .interfaces = (InterfaceInfo[]) { + { INTERFACE_CONVENTIONAL_PCI_DEVICE }, + { }, + }, +}; + +static void mioe3680_pci_register_types(void) +{ + type_register_static(&mioe3680_pci_info); +} + +type_init(mioe3680_pci_register_types) diff --git a/hw/net/can/can_pcm3680_pci.c b/hw/net/can/can_pcm3680_pci.c new file mode 100644 index 0000000..23f7ff4 --- /dev/null +++ b/hw/net/can/can_pcm3680_pci.c @@ -0,0 +1,263 @@ +/* + * PCM-3680i PCI CAN device (SJA1000 based) emulation + * + * Copyright (c) 2016 Deniz Eren (deniz.eren@icloud.com) + * + * Based on Kvaser PCI CAN device (SJA1000 based) emulation implemented by + * Jin Yang and Pavel Pisa + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF 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/osdep.h" +#include "qemu/event_notifier.h" +#include "qemu/thread.h" +#include "qemu/sockets.h" +#include "qapi/error.h" +#include "chardev/char.h" +#include "hw/hw.h" +#include "hw/pci/pci.h" +#include "net/can_emu.h" + +#include "can_sja1000.h" + +#define TYPE_CAN_PCI_DEV "pcm3680_pci" + +#define PCM3680i_PCI_DEV(obj) \ + OBJECT_CHECK(Pcm3680iPCIState, (obj), TYPE_CAN_PCI_DEV) + +/* the PCI device and vendor IDs */ +#ifndef PCM3680i_PCI_VENDOR_ID1 +#define PCM3680i_PCI_VENDOR_ID1 0x13fe +#endif + +#ifndef PCM3680i_PCI_DEVICE_ID1 +#define PCM3680i_PCI_DEVICE_ID1 0xc002 +#endif + +#define PCM3680i_PCI_SJA_COUNT 2 +#define PCM3680i_PCI_SJA_RANGE 0x100 + +#define PCM3680i_PCI_BYTES_PER_SJA 0x20 + +typedef struct Pcm3680iPCIState { + /*< private >*/ + PCIDevice dev; + /*< public >*/ + MemoryRegion sja_io[PCM3680i_PCI_SJA_COUNT]; + + CanSJA1000State sja_state[PCM3680i_PCI_SJA_COUNT]; + qemu_irq irq; + + char *model; /* The model that support, only SJA1000 now. */ + CanBusState *canbus[PCM3680i_PCI_SJA_COUNT]; +} Pcm3680iPCIState; + +static void pcm3680i_pci_reset(DeviceState *dev) +{ + Pcm3680iPCIState *d = PCM3680i_PCI_DEV(dev); + int i; + + for (i = 0; i < PCM3680i_PCI_SJA_COUNT; i++) { + can_sja_hardware_reset(&d->sja_state[i]); + } +} + +static uint64_t pcm3680i_pci_sja1_io_read(void *opaque, hwaddr addr, + unsigned size) +{ + Pcm3680iPCIState *d = opaque; + CanSJA1000State *s = &d->sja_state[0]; + + if (addr >= PCM3680i_PCI_BYTES_PER_SJA) { + return 0; + } + + return can_sja_mem_read(s, addr, size); +} + +static void pcm3680i_pci_sja1_io_write(void *opaque, hwaddr addr, + uint64_t data, unsigned size) +{ + Pcm3680iPCIState *d = opaque; + CanSJA1000State *s = &d->sja_state[0]; + + if (addr >= PCM3680i_PCI_BYTES_PER_SJA) { + return; + } + + can_sja_mem_write(s, addr, data, size); +} + +static uint64_t pcm3680i_pci_sja2_io_read(void *opaque, hwaddr addr, + unsigned size) +{ + Pcm3680iPCIState *d = opaque; + CanSJA1000State *s = &d->sja_state[1]; + + if (addr >= PCM3680i_PCI_BYTES_PER_SJA) { + return 0; + } + + return can_sja_mem_read(s, addr, size); +} + +static void pcm3680i_pci_sja2_io_write(void *opaque, hwaddr addr, uint64_t data, + unsigned size) +{ + Pcm3680iPCIState *d = opaque; + CanSJA1000State *s = &d->sja_state[1]; + + if (addr >= PCM3680i_PCI_BYTES_PER_SJA) { + return; + } + + can_sja_mem_write(s, addr, data, size); +} + +static const MemoryRegionOps pcm3680i_pci_sja1_io_ops = { + .read = pcm3680i_pci_sja1_io_read, + .write = pcm3680i_pci_sja1_io_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .max_access_size = 1, + }, +}; + +static const MemoryRegionOps pcm3680i_pci_sja2_io_ops = { + .read = pcm3680i_pci_sja2_io_read, + .write = pcm3680i_pci_sja2_io_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .max_access_size = 1, + }, +}; + +static void pcm3680i_pci_realize(PCIDevice *pci_dev, Error **errp) +{ + Pcm3680iPCIState *d = PCM3680i_PCI_DEV(pci_dev); + uint8_t *pci_conf; + int i; + + pci_conf = pci_dev->config; + pci_conf[PCI_INTERRUPT_PIN] = 0x01; /* interrupt pin A */ + + d->irq = pci_allocate_irq(&d->dev); + + for (i = 0; i < PCM3680i_PCI_SJA_COUNT; i++) { + can_sja_init(&d->sja_state[i], d->irq); + } + + for (i = 0; i < PCM3680i_PCI_SJA_COUNT; i++) { + if (can_sja_connect_to_bus(&d->sja_state[i], d->canbus[i]) < 0) { + error_setg(errp, "can_sja_connect_to_bus failed"); + return; + } + } + + memory_region_init_io(&d->sja_io[0], OBJECT(d), &pcm3680i_pci_sja1_io_ops, + d, "pcm3680i_pci-sja1", PCM3680i_PCI_SJA_RANGE); + + memory_region_init_io(&d->sja_io[1], OBJECT(d), &pcm3680i_pci_sja2_io_ops, + d, "pcm3680i_pci-sja2", PCM3680i_PCI_SJA_RANGE); + + for (i = 0; i < PCM3680i_PCI_SJA_COUNT; i++) { + pci_register_bar(&d->dev, /*BAR*/ i, PCI_BASE_ADDRESS_SPACE_IO, + &d->sja_io[i]); + } +} + +static void pcm3680i_pci_exit(PCIDevice *pci_dev) +{ + Pcm3680iPCIState *d = PCM3680i_PCI_DEV(pci_dev); + int i; + + for (i = 0; i < PCM3680i_PCI_SJA_COUNT; i++) { + can_sja_disconnect(&d->sja_state[i]); + } + + qemu_free_irq(d->irq); +} + +static const VMStateDescription vmstate_pcm3680i_pci = { + .name = "pcm3680i_pci", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(dev, Pcm3680iPCIState), + VMSTATE_STRUCT(sja_state[0], Pcm3680iPCIState, 0, + vmstate_can_sja, CanSJA1000State), + VMSTATE_STRUCT(sja_state[1], Pcm3680iPCIState, 0, + vmstate_can_sja, CanSJA1000State), + VMSTATE_END_OF_LIST() + } +}; + +static void pcm3680i_pci_instance_init(Object *obj) +{ + Pcm3680iPCIState *d = PCM3680i_PCI_DEV(obj); + + object_property_add_link(obj, "canbus0", TYPE_CAN_BUS, + (Object **)&d->canbus[0], + qdev_prop_allow_set_link_before_realize, + 0, &error_abort); + object_property_add_link(obj, "canbus1", TYPE_CAN_BUS, + (Object **)&d->canbus[1], + qdev_prop_allow_set_link_before_realize, + 0, &error_abort); +} + +static void pcm3680i_pci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->realize = pcm3680i_pci_realize; + k->exit = pcm3680i_pci_exit; + k->vendor_id = PCM3680i_PCI_VENDOR_ID1; + k->device_id = PCM3680i_PCI_DEVICE_ID1; + k->revision = 0x00; + k->class_id = 0x000c09; + k->subsystem_vendor_id = PCM3680i_PCI_VENDOR_ID1; + k->subsystem_id = PCM3680i_PCI_DEVICE_ID1; + dc->desc = "Pcm3680i PCICANx"; + dc->vmsd = &vmstate_pcm3680i_pci; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + dc->reset = pcm3680i_pci_reset; +} + +static const TypeInfo pcm3680i_pci_info = { + .name = TYPE_CAN_PCI_DEV, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(Pcm3680iPCIState), + .class_init = pcm3680i_pci_class_init, + .instance_init = pcm3680i_pci_instance_init, + .interfaces = (InterfaceInfo[]) { + { INTERFACE_CONVENTIONAL_PCI_DEVICE }, + { }, + }, +}; + +static void pcm3680i_pci_register_types(void) +{ + type_register_static(&pcm3680i_pci_info); +} + +type_init(pcm3680i_pci_register_types) diff --git a/hw/net/can/can_sja1000.c b/hw/net/can/can_sja1000.c new file mode 100644 index 0000000..6293233 --- /dev/null +++ b/hw/net/can/can_sja1000.c @@ -0,0 +1,953 @@ +/* + * CAN device - SJA1000 chip emulation for QEMU + * + * Copyright (c) 2013-2014 Jin Yang + * Copyright (c) 2014-2018 Pavel Pisa + * + * Initial development supported by Google GSoC 2013 from RTEMS project slot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF 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/osdep.h" +#include "qemu/log.h" +#include "chardev/char.h" +#include "hw/hw.h" +#include "net/can_emu.h" + +#include "can_sja1000.h" + +#ifndef DEBUG_FILTER +#define DEBUG_FILTER 0 +#endif /*DEBUG_FILTER*/ + +#ifndef DEBUG_CAN +#define DEBUG_CAN 0 +#endif /*DEBUG_CAN*/ + +#define DPRINTF(fmt, ...) \ + do { \ + if (DEBUG_CAN) { \ + qemu_log("[cansja]: " fmt , ## __VA_ARGS__); \ + } \ + } while (0) + +static void can_sja_software_reset(CanSJA1000State *s) +{ + s->mode &= ~0x31; + s->mode |= 0x01; + s->status_pel &= ~0x37; + s->status_pel |= 0x34; + + s->rxbuf_start = 0x00; + s->rxmsg_cnt = 0x00; + s->rx_cnt = 0x00; +} + +void can_sja_hardware_reset(CanSJA1000State *s) +{ + /* Reset by hardware, p10 */ + s->mode = 0x01; + s->status_pel = 0x3c; + s->interrupt_pel = 0x00; + s->clock = 0x00; + s->rxbuf_start = 0x00; + s->rxmsg_cnt = 0x00; + s->rx_cnt = 0x00; + + s->control = 0x01; + s->status_bas = 0x0c; + s->interrupt_bas = 0x00; + + qemu_irq_lower(s->irq); +} + +static +void can_sja_single_filter(struct qemu_can_filter *filter, + const uint8_t *acr, const uint8_t *amr, int extended) +{ + if (extended) { + filter->can_id = (uint32_t)acr[0] << 21; + filter->can_id |= (uint32_t)acr[1] << 13; + filter->can_id |= (uint32_t)acr[2] << 5; + filter->can_id |= (uint32_t)acr[3] >> 3; + if (acr[3] & 4) { + filter->can_id |= QEMU_CAN_RTR_FLAG; + } + + filter->can_mask = (uint32_t)amr[0] << 21; + filter->can_mask |= (uint32_t)amr[1] << 13; + filter->can_mask |= (uint32_t)amr[2] << 5; + filter->can_mask |= (uint32_t)amr[3] >> 3; + filter->can_mask = ~filter->can_mask & QEMU_CAN_EFF_MASK; + if (!(amr[3] & 4)) { + filter->can_mask |= QEMU_CAN_RTR_FLAG; + } + } else { + filter->can_id = (uint32_t)acr[0] << 3; + filter->can_id |= (uint32_t)acr[1] >> 5; + if (acr[1] & 0x10) { + filter->can_id |= QEMU_CAN_RTR_FLAG; + } + + filter->can_mask = (uint32_t)amr[0] << 3; + filter->can_mask |= (uint32_t)amr[1] << 5; + filter->can_mask = ~filter->can_mask & QEMU_CAN_SFF_MASK; + if (!(amr[1] & 0x10)) { + filter->can_mask |= QEMU_CAN_RTR_FLAG; + } + } +} + +static +void can_sja_dual_filter(struct qemu_can_filter *filter, + const uint8_t *acr, const uint8_t *amr, int extended) +{ + if (extended) { + filter->can_id = (uint32_t)acr[0] << 21; + filter->can_id |= (uint32_t)acr[1] << 13; + + filter->can_mask = (uint32_t)amr[0] << 21; + filter->can_mask |= (uint32_t)amr[1] << 13; + filter->can_mask = ~filter->can_mask & QEMU_CAN_EFF_MASK & ~0x1fff; + } else { + filter->can_id = (uint32_t)acr[0] << 3; + filter->can_id |= (uint32_t)acr[1] >> 5; + if (acr[1] & 0x10) { + filter->can_id |= QEMU_CAN_RTR_FLAG; + } + + filter->can_mask = (uint32_t)amr[0] << 3; + filter->can_mask |= (uint32_t)amr[1] >> 5; + filter->can_mask = ~filter->can_mask & QEMU_CAN_SFF_MASK; + if (!(amr[1] & 0x10)) { + filter->can_mask |= QEMU_CAN_RTR_FLAG; + } + } +} + +/* Details in DS-p22, what we need to do here is to test the data. */ +static +int can_sja_accept_filter(CanSJA1000State *s, + const qemu_can_frame *frame) +{ + + struct qemu_can_filter filter; + + if (s->clock & 0x80) { /* PeliCAN Mode */ + if (s->mode & (1 << 3)) { /* Single mode. */ + if (frame->can_id & QEMU_CAN_EFF_FLAG) { /* EFF */ + can_sja_single_filter(&filter, + s->code_mask + 0, s->code_mask + 4, 1); + + if (!can_bus_filter_match(&filter, frame->can_id)) { + return 0; + } + } else { /* SFF */ + can_sja_single_filter(&filter, + s->code_mask + 0, s->code_mask + 4, 0); + + if (!can_bus_filter_match(&filter, frame->can_id)) { + return 0; + } + + if (frame->can_id & QEMU_CAN_RTR_FLAG) { /* RTR */ + return 1; + } + + if (frame->can_dlc == 0) { + return 1; + } + + if ((frame->data[0] & ~(s->code_mask[6])) != + (s->code_mask[2] & ~(s->code_mask[6]))) { + return 0; + } + + if (frame->can_dlc < 2) { + return 1; + } + + if ((frame->data[1] & ~(s->code_mask[7])) == + (s->code_mask[3] & ~(s->code_mask[7]))) { + return 1; + } + + return 0; + } + } else { /* Dual mode */ + if (frame->can_id & QEMU_CAN_EFF_FLAG) { /* EFF */ + can_sja_dual_filter(&filter, + s->code_mask + 0, s->code_mask + 4, 1); + + if (can_bus_filter_match(&filter, frame->can_id)) { + return 1; + } + + can_sja_dual_filter(&filter, + s->code_mask + 2, s->code_mask + 6, 1); + + if (can_bus_filter_match(&filter, frame->can_id)) { + return 1; + } + + return 0; + } else { + can_sja_dual_filter(&filter, + s->code_mask + 0, s->code_mask + 4, 0); + + if (can_bus_filter_match(&filter, frame->can_id)) { + uint8_t expect; + uint8_t mask; + expect = s->code_mask[1] << 4; + expect |= s->code_mask[3] & 0x0f; + + mask = s->code_mask[5] << 4; + mask |= s->code_mask[7] & 0x0f; + mask = ~mask & 0xff; + + if ((frame->data[0] & mask) == + (expect & mask)) { + return 1; + } + } + + can_sja_dual_filter(&filter, + s->code_mask + 2, s->code_mask + 6, 0); + + if (can_bus_filter_match(&filter, frame->can_id)) { + return 1; + } + + return 0; + } + } + } + + return 1; +} + +static void can_display_msg(const char *prefix, const qemu_can_frame *msg) +{ + int i; + + qemu_log_lock(); + qemu_log("%s%03X [%01d] %s %s", + prefix, + msg->can_id & QEMU_CAN_EFF_MASK, + msg->can_dlc, + msg->can_id & QEMU_CAN_EFF_FLAG ? "EFF" : "SFF", + msg->can_id & QEMU_CAN_RTR_FLAG ? "RTR" : "DAT"); + + for (i = 0; i < msg->can_dlc; i++) { + qemu_log(" %02X", msg->data[i]); + } + qemu_log("\n"); + qemu_log_flush(); + qemu_log_unlock(); +} + +static void buff2frame_pel(const uint8_t *buff, qemu_can_frame *frame) +{ + uint8_t i; + + frame->can_id = 0; + if (buff[0] & 0x40) { /* RTR */ + frame->can_id = QEMU_CAN_RTR_FLAG; + } + frame->can_dlc = buff[0] & 0x0f; + + if (buff[0] & 0x80) { /* Extended */ + frame->can_id |= QEMU_CAN_EFF_FLAG; + frame->can_id |= buff[1] << 21; /* ID.28~ID.21 */ + frame->can_id |= buff[2] << 13; /* ID.20~ID.13 */ + frame->can_id |= buff[3] << 5; + frame->can_id |= buff[4] >> 3; + for (i = 0; i < frame->can_dlc; i++) { + frame->data[i] = buff[5 + i]; + } + for (; i < 8; i++) { + frame->data[i] = 0; + } + } else { + frame->can_id |= buff[1] << 3; + frame->can_id |= buff[2] >> 5; + for (i = 0; i < frame->can_dlc; i++) { + frame->data[i] = buff[3 + i]; + } + for (; i < 8; i++) { + frame->data[i] = 0; + } + } +} + + +static void buff2frame_bas(const uint8_t *buff, qemu_can_frame *frame) +{ + uint8_t i; + + frame->can_id = ((buff[0] << 3) & (0xff << 3)) + ((buff[1] >> 5) & 0x07); + if (buff[1] & 0x10) { /* RTR */ + frame->can_id = QEMU_CAN_RTR_FLAG; + } + frame->can_dlc = buff[1] & 0x0f; + + for (i = 0; i < frame->can_dlc; i++) { + frame->data[i] = buff[2 + i]; + } + for (; i < 8; i++) { + frame->data[i] = 0; + } +} + + +static int frame2buff_pel(const qemu_can_frame *frame, uint8_t *buff) +{ + int i; + + if (frame->can_id & QEMU_CAN_ERR_FLAG) { /* error frame, NOT support now. */ + return -1; + } + + buff[0] = 0x0f & frame->can_dlc; /* DLC */ + if (frame->can_id & QEMU_CAN_RTR_FLAG) { /* RTR */ + buff[0] |= (1 << 6); + } + if (frame->can_id & QEMU_CAN_EFF_FLAG) { /* EFF */ + buff[0] |= (1 << 7); + buff[1] = extract32(frame->can_id, 21, 8); /* ID.28~ID.21 */ + buff[2] = extract32(frame->can_id, 13, 8); /* ID.20~ID.13 */ + buff[3] = extract32(frame->can_id, 5, 8); /* ID.12~ID.05 */ + buff[4] = extract32(frame->can_id, 0, 5) << 3; /* ID.04~ID.00,xxx */ + for (i = 0; i < frame->can_dlc; i++) { + buff[5 + i] = frame->data[i]; + } + return frame->can_dlc + 5; + } else { /* SFF */ + buff[1] = extract32(frame->can_id, 3, 8); /* ID.10~ID.03 */ + buff[2] = extract32(frame->can_id, 0, 3) << 5; /* ID.02~ID.00,xxxxx */ + for (i = 0; i < frame->can_dlc; i++) { + buff[3 + i] = frame->data[i]; + } + + return frame->can_dlc + 3; + } + + return -1; +} + +static int frame2buff_bas(const qemu_can_frame *frame, uint8_t *buff) +{ + int i; + + /* + * EFF, no support for BasicMode + * No use for Error frames now, + * they could be used in future to update SJA1000 error state + */ + if ((frame->can_id & QEMU_CAN_EFF_FLAG) || + (frame->can_id & QEMU_CAN_ERR_FLAG)) { + return -1; + } + + buff[0] = extract32(frame->can_id, 3, 8); /* ID.10~ID.03 */ + buff[1] = extract32(frame->can_id, 0, 3) << 5; /* ID.02~ID.00,xxxxx */ + if (frame->can_id & QEMU_CAN_RTR_FLAG) { /* RTR */ + buff[1] |= (1 << 4); + } + buff[1] |= frame->can_dlc & 0x0f; + for (i = 0; i < frame->can_dlc; i++) { + buff[2 + i] = frame->data[i]; + } + + return frame->can_dlc + 2; +} + +static void can_sja_update_pel_irq(CanSJA1000State *s) +{ + if (s->interrupt_en & s->interrupt_pel) { + qemu_irq_raise(s->irq); + } else { + qemu_irq_lower(s->irq); + } +} + +static void can_sja_update_bas_irq(CanSJA1000State *s) +{ + if ((s->control >> 1) & s->interrupt_bas) { + qemu_irq_raise(s->irq); + } else { + qemu_irq_lower(s->irq); + } +} + +void can_sja_mem_write(CanSJA1000State *s, hwaddr addr, uint64_t val, + unsigned size) +{ + qemu_can_frame frame; + uint32_t tmp; + uint8_t tmp8, count; + + + DPRINTF("write 0x%02llx addr 0x%02x\n", + (unsigned long long)val, (unsigned int)addr); + + if (addr > CAN_SJA_MEM_SIZE) { + return ; + } + + if (s->clock & 0x80) { /* PeliCAN Mode */ + switch (addr) { + case SJA_MOD: /* Mode register */ + s->mode = 0x1f & val; + if ((s->mode & 0x01) && ((val & 0x01) == 0)) { + /* Go to operation mode from reset mode. */ + if (s->mode & (1 << 3)) { /* Single mode. */ + /* For EFF */ + can_sja_single_filter(&s->filter[0], + s->code_mask + 0, s->code_mask + 4, 1); + + /* For SFF */ + can_sja_single_filter(&s->filter[1], + s->code_mask + 0, s->code_mask + 4, 0); + + can_bus_client_set_filters(&s->bus_client, s->filter, 2); + } else { /* Dual mode */ + /* For EFF */ + can_sja_dual_filter(&s->filter[0], + s->code_mask + 0, s->code_mask + 4, 1); + + can_sja_dual_filter(&s->filter[1], + s->code_mask + 2, s->code_mask + 6, 1); + + /* For SFF */ + can_sja_dual_filter(&s->filter[2], + s->code_mask + 0, s->code_mask + 4, 0); + + can_sja_dual_filter(&s->filter[3], + s->code_mask + 2, s->code_mask + 6, 0); + + can_bus_client_set_filters(&s->bus_client, s->filter, 4); + } + + s->rxmsg_cnt = 0; + s->rx_cnt = 0; + } + break; + + case SJA_CMR: /* Command register. */ + if (0x01 & val) { /* Send transmission request. */ + buff2frame_pel(s->tx_buff, &frame); + if (DEBUG_FILTER) { + can_display_msg("[cansja]: Tx request " , &frame); + } + + /* + * Clear transmission complete status, + * and Transmit Buffer Status. + * write to the backends. + */ + s->status_pel &= ~(3 << 2); + + can_bus_client_send(&s->bus_client, &frame, 1); + + /* + * Set transmission complete status + * and Transmit Buffer Status. + */ + s->status_pel |= (3 << 2); + + /* Clear transmit status. */ + s->status_pel &= ~(1 << 5); + s->interrupt_pel |= 0x02; + can_sja_update_pel_irq(s); + } + if (0x04 & val) { /* Release Receive Buffer */ + if (s->rxmsg_cnt <= 0) { + break; + } + + tmp8 = s->rx_buff[s->rxbuf_start]; count = 0; + if (tmp8 & (1 << 7)) { /* EFF */ + count += 2; + } + count += 3; + if (!(tmp8 & (1 << 6))) { /* DATA */ + count += (tmp8 & 0x0f); + } + + if (DEBUG_FILTER) { + qemu_log("[cansja]: message released from " + "Rx FIFO cnt=%d, count=%d\n", s->rx_cnt, count); + } + + s->rxbuf_start += count; + s->rxbuf_start %= SJA_RCV_BUF_LEN; + + s->rx_cnt -= count; + s->rxmsg_cnt--; + if (s->rxmsg_cnt == 0) { + s->status_pel &= ~(1 << 0); + s->interrupt_pel &= ~(1 << 0); + can_sja_update_pel_irq(s); + } + } + if (0x08 & val) { /* Clear data overrun */ + s->status_pel &= ~(1 << 1); + s->interrupt_pel &= ~(1 << 3); + can_sja_update_pel_irq(s); + } + break; + case SJA_SR: /* Status register */ + case SJA_IR: /* Interrupt register */ + break; /* Do nothing */ + case SJA_IER: /* Interrupt enable register */ + s->interrupt_en = val; + break; + case 16: /* RX frame information addr16-28. */ + s->status_pel |= (1 << 5); /* Set transmit status. */ + case 17 ... 28: + if (s->mode & 0x01) { /* Reset mode */ + if (addr < 24) { + s->code_mask[addr - 16] = val; + } + } else { /* Operation mode */ + s->tx_buff[addr - 16] = val; /* Store to TX buffer directly. */ + } + break; + case SJA_CDR: + s->clock = val; + break; + } + } else { /* Basic Mode */ + switch (addr) { + case SJA_BCAN_CTR: /* Control register, addr 0 */ + if ((s->control & 0x01) && ((val & 0x01) == 0)) { + /* Go to operation mode from reset mode. */ + s->filter[0].can_id = (s->code << 3) & (0xff << 3); + tmp = (~(s->mask << 3)) & (0xff << 3); + tmp |= QEMU_CAN_EFF_FLAG; /* Only Basic CAN Frame. */ + s->filter[0].can_mask = tmp; + can_bus_client_set_filters(&s->bus_client, s->filter, 1); + + s->rxmsg_cnt = 0; + s->rx_cnt = 0; + } else if (!(s->control & 0x01) && !(val & 0x01)) { + can_sja_software_reset(s); + } + + s->control = 0x1f & val; + break; + case SJA_BCAN_CMR: /* Command register, addr 1 */ + if (0x01 & val) { /* Send transmission request. */ + buff2frame_bas(s->tx_buff, &frame); + if (DEBUG_FILTER) { + can_display_msg("[cansja]: Tx request " , &frame); + } + + /* + * Clear transmission complete status, + * and Transmit Buffer Status. + */ + s->status_bas &= ~(3 << 2); + + /* write to the backends. */ + can_bus_client_send(&s->bus_client, &frame, 1); + + /* + * Set transmission complete status, + * and Transmit Buffer Status. + */ + s->status_bas |= (3 << 2); + + /* Clear transmit status. */ + s->status_bas &= ~(1 << 5); + s->interrupt_bas |= 0x02; + can_sja_update_bas_irq(s); + } + if (0x04 & val) { /* Release Receive Buffer */ + if (s->rxmsg_cnt <= 0) { + break; + } + + tmp8 = s->rx_buff[(s->rxbuf_start + 1) % SJA_RCV_BUF_LEN]; + count = 2 + (tmp8 & 0x0f); + + if (DEBUG_FILTER) { + qemu_log("[cansja]: message released from " + "Rx FIFO cnt=%d, count=%d\n", s->rx_cnt, count); + } + + s->rxbuf_start += count; + s->rxbuf_start %= SJA_RCV_BUF_LEN; + s->rx_cnt -= count; + s->rxmsg_cnt--; + + if (s->rxmsg_cnt == 0) { + s->status_bas &= ~(1 << 0); + s->interrupt_bas &= ~(1 << 0); + can_sja_update_bas_irq(s); + } + } + if (0x08 & val) { /* Clear data overrun */ + s->status_bas &= ~(1 << 1); + s->interrupt_bas &= ~(1 << 3); + can_sja_update_bas_irq(s); + } + break; + case 4: + s->code = val; + break; + case 5: + s->mask = val; + break; + case 10: + s->status_bas |= (1 << 5); /* Set transmit status. */ + case 11 ... 19: + if ((s->control & 0x01) == 0) { /* Operation mode */ + s->tx_buff[addr - 10] = val; /* Store to TX buffer directly. */ + } + break; + case SJA_CDR: + s->clock = val; + break; + } + } +} + +uint64_t can_sja_mem_read(CanSJA1000State *s, hwaddr addr, unsigned size) +{ + uint64_t temp = 0; + + DPRINTF("read addr 0x%02x ...\n", (unsigned int)addr); + + if (addr > CAN_SJA_MEM_SIZE) { + return 0; + } + + if (s->clock & 0x80) { /* PeliCAN Mode */ + switch (addr) { + case SJA_MOD: /* Mode register, addr 0 */ + temp = s->mode; + break; + case SJA_CMR: /* Command register, addr 1 */ + temp = 0x00; /* Command register, cannot be read. */ + break; + case SJA_SR: /* Status register, addr 2 */ + temp = s->status_pel; + break; + case SJA_IR: /* Interrupt register, addr 3 */ + temp = s->interrupt_pel; + s->interrupt_pel = 0; + if (s->rxmsg_cnt) { + s->interrupt_pel |= (1 << 0); /* Receive interrupt. */ + } + can_sja_update_pel_irq(s); + break; + case SJA_IER: /* Interrupt enable register, addr 4 */ + temp = s->interrupt_en; + break; + case 5: /* Reserved */ + case 6: /* Bus timing 0, hardware related, not support now. */ + case 7: /* Bus timing 1, hardware related, not support now. */ + case 8: /* + * Output control register, hardware related, + * not supported for now. + */ + case 9: /* Test. */ + case 10 ... 15: /* Reserved */ + temp = 0x00; + break; + + case 16 ... 28: + if (s->mode & 0x01) { /* Reset mode */ + if (addr < 24) { + temp = s->code_mask[addr - 16]; + } else { + temp = 0x00; + } + } else { /* Operation mode */ + temp = s->rx_buff[(s->rxbuf_start + addr - 16) % + SJA_RCV_BUF_LEN]; + } + break; + case SJA_CDR: + temp = s->clock; + break; + default: + temp = 0xff; + } + } else { /* Basic Mode */ + switch (addr) { + case SJA_BCAN_CTR: /* Control register, addr 0 */ + temp = s->control; + break; + case SJA_BCAN_SR: /* Status register, addr 2 */ + temp = s->status_bas; + break; + case SJA_BCAN_IR: /* Interrupt register, addr 3 */ + temp = s->interrupt_bas; + s->interrupt_bas = 0; + if (s->rxmsg_cnt) { + s->interrupt_bas |= (1 << 0); /* Receive interrupt. */ + } + can_sja_update_bas_irq(s); + break; + case 4: + temp = s->code; + break; + case 5: + temp = s->mask; + break; + case 20 ... 29: + temp = s->rx_buff[(s->rxbuf_start + addr - 20) % SJA_RCV_BUF_LEN]; + break; + case 31: + temp = s->clock; + break; + default: + temp = 0xff; + break; + } + } + DPRINTF("read addr 0x%02x, %d bytes, content 0x%02lx\n", + (int)addr, size, (long unsigned int)temp); + + return temp; +} + +int can_sja_can_receive(CanBusClientState *client) +{ + CanSJA1000State *s = container_of(client, CanSJA1000State, bus_client); + + if (s->clock & 0x80) { /* PeliCAN Mode */ + if (s->mode & 0x01) { /* reset mode. */ + return 0; + } + } else { /* BasicCAN mode */ + if (s->control & 0x01) { + return 0; + } + } + + return 1; /* always return 1, when operation mode */ +} + +ssize_t can_sja_receive(CanBusClientState *client, const qemu_can_frame *frames, + size_t frames_cnt) +{ + CanSJA1000State *s = container_of(client, CanSJA1000State, bus_client); + static uint8_t rcv[SJA_MSG_MAX_LEN]; + int i; + int ret = -1; + const qemu_can_frame *frame = frames; + + if (frames_cnt <= 0) { + return 0; + } + if (DEBUG_FILTER) { + can_display_msg("[cansja]: receive ", frame); + } + + if (s->clock & 0x80) { /* PeliCAN Mode */ + + /* the CAN controller is receiving a message */ + s->status_pel |= (1 << 4); + + if (can_sja_accept_filter(s, frame) == 0) { + s->status_pel &= ~(1 << 4); + if (DEBUG_FILTER) { + qemu_log("[cansja]: filter rejects message\n"); + } + return ret; + } + + ret = frame2buff_pel(frame, rcv); + if (ret < 0) { + s->status_pel &= ~(1 << 4); + if (DEBUG_FILTER) { + qemu_log("[cansja]: message store failed\n"); + } + return ret; /* maybe not support now. */ + } + + if (s->rx_cnt + ret > SJA_RCV_BUF_LEN) { /* Data overrun. */ + s->status_pel |= (1 << 1); /* Overrun status */ + s->interrupt_pel |= (1 << 3); + s->status_pel &= ~(1 << 4); + if (DEBUG_FILTER) { + qemu_log("[cansja]: receive FIFO overrun\n"); + } + can_sja_update_pel_irq(s); + return ret; + } + s->rx_cnt += ret; + s->rxmsg_cnt++; + if (DEBUG_FILTER) { + qemu_log("[cansja]: message stored in receive FIFO\n"); + } + + for (i = 0; i < ret; i++) { + s->rx_buff[(s->rx_ptr++) % SJA_RCV_BUF_LEN] = rcv[i]; + } + s->rx_ptr %= SJA_RCV_BUF_LEN; /* update the pointer. */ + + s->status_pel |= 0x01; /* Set the Receive Buffer Status. DS-p23 */ + s->interrupt_pel |= 0x01; + s->status_pel &= ~(1 << 4); + s->status_pel |= (1 << 0); + can_sja_update_pel_irq(s); + } else { /* BasicCAN mode */ + + /* the CAN controller is receiving a message */ + s->status_bas |= (1 << 4); + + ret = frame2buff_bas(frame, rcv); + if (ret < 0) { + s->status_bas &= ~(1 << 4); + if (DEBUG_FILTER) { + qemu_log("[cansja]: message store failed\n"); + } + return ret; /* maybe not support now. */ + } + + if (s->rx_cnt + ret > SJA_RCV_BUF_LEN) { /* Data overrun. */ + s->status_bas |= (1 << 1); /* Overrun status */ + s->status_bas &= ~(1 << 4); + s->interrupt_bas |= (1 << 3); + can_sja_update_bas_irq(s); + if (DEBUG_FILTER) { + qemu_log("[cansja]: receive FIFO overrun\n"); + } + return ret; + } + s->rx_cnt += ret; + s->rxmsg_cnt++; + + if (DEBUG_FILTER) { + qemu_log("[cansja]: message stored\n"); + } + + for (i = 0; i < ret; i++) { + s->rx_buff[(s->rx_ptr++) % SJA_RCV_BUF_LEN] = rcv[i]; + } + s->rx_ptr %= SJA_RCV_BUF_LEN; /* update the pointer. */ + + s->status_bas |= 0x01; /* Set the Receive Buffer Status. DS-p15 */ + s->status_bas &= ~(1 << 4); + s->interrupt_bas |= (1 << 0); + can_sja_update_bas_irq(s); + } + return 1; +} + +static CanBusClientInfo can_sja_bus_client_info = { + .can_receive = can_sja_can_receive, + .receive = can_sja_receive, +}; + + +int can_sja_connect_to_bus(CanSJA1000State *s, CanBusState *bus) +{ + s->bus_client.info = &can_sja_bus_client_info; + + if (can_bus_insert_client(bus, &s->bus_client) < 0) { + return -1; + } + + return 0; +} + +void can_sja_disconnect(CanSJA1000State *s) +{ + can_bus_remove_client(&s->bus_client); +} + +int can_sja_init(CanSJA1000State *s, qemu_irq irq) +{ + s->irq = irq; + + qemu_irq_lower(s->irq); + + can_sja_hardware_reset(s); + + return 0; +} + +const VMStateDescription vmstate_qemu_can_filter = { + .name = "qemu_can_filter", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(can_id, qemu_can_filter), + VMSTATE_UINT32(can_mask, qemu_can_filter), + VMSTATE_END_OF_LIST() + } +}; + +static int can_sja_post_load(void *opaque, int version_id) +{ + CanSJA1000State *s = opaque; + if (s->clock & 0x80) { /* PeliCAN Mode */ + can_sja_update_pel_irq(s); + } else { + can_sja_update_bas_irq(s); + } + return 0; +} + +/* VMState is needed for live migration of QEMU images */ +const VMStateDescription vmstate_can_sja = { + .name = "can_sja", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = can_sja_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT8(mode, CanSJA1000State), + + VMSTATE_UINT8(status_pel, CanSJA1000State), + VMSTATE_UINT8(interrupt_pel, CanSJA1000State), + VMSTATE_UINT8(interrupt_en, CanSJA1000State), + VMSTATE_UINT8(rxmsg_cnt, CanSJA1000State), + VMSTATE_UINT8(rxbuf_start, CanSJA1000State), + VMSTATE_UINT8(clock, CanSJA1000State), + + VMSTATE_BUFFER(code_mask, CanSJA1000State), + VMSTATE_BUFFER(tx_buff, CanSJA1000State), + + VMSTATE_BUFFER(rx_buff, CanSJA1000State), + + VMSTATE_UINT32(rx_ptr, CanSJA1000State), + VMSTATE_UINT32(rx_cnt, CanSJA1000State), + + VMSTATE_UINT8(control, CanSJA1000State), + + VMSTATE_UINT8(status_bas, CanSJA1000State), + VMSTATE_UINT8(interrupt_bas, CanSJA1000State), + VMSTATE_UINT8(code, CanSJA1000State), + VMSTATE_UINT8(mask, CanSJA1000State), + + VMSTATE_STRUCT_ARRAY(filter, CanSJA1000State, 4, 0, + vmstate_qemu_can_filter, qemu_can_filter), + + + VMSTATE_END_OF_LIST() + } +}; diff --git a/hw/net/can/can_sja1000.h b/hw/net/can/can_sja1000.h new file mode 100644 index 0000000..4731cbb --- /dev/null +++ b/hw/net/can/can_sja1000.h @@ -0,0 +1,146 @@ +/* + * CAN device - SJA1000 chip emulation for QEMU + * + * Copyright (c) 2013-2014 Jin Yang + * Copyright (c) 2014-2018 Pavel Pisa + * + * Initial development supported by Google GSoC 2013 from RTEMS project slot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef HW_CAN_SJA1000_H +#define HW_CAN_SJA1000_H + +#include "net/can_emu.h" + +#define CAN_SJA_MEM_SIZE 128 + +/* The max size for a message buffer, EFF and DLC=8, DS-p39 */ +#define SJA_MSG_MAX_LEN 13 +/* The receive buffer size. */ +#define SJA_RCV_BUF_LEN 64 + +typedef struct CanSJA1000State { + /* PeliCAN state and registers sorted by address */ + uint8_t mode; /* 0 .. Mode register, DS-p26 */ + /* 1 .. Command register */ + uint8_t status_pel; /* 2 .. Status register, p15 */ + uint8_t interrupt_pel; /* 3 .. Interrupt register */ + uint8_t interrupt_en; /* 4 .. Interrupt Enable register */ + uint8_t rxmsg_cnt; /* 29 .. RX message counter. DS-p49 */ + uint8_t rxbuf_start; /* 30 .. RX buffer start address, DS-p49 */ + uint8_t clock; /* 31 .. Clock Divider register, DS-p55 */ + + uint8_t code_mask[8]; /* 16~23 */ + uint8_t tx_buff[13]; /* 96~108 .. transmit buffer */ + /* 10~19 .. transmit buffer for BasicCAN */ + + uint8_t rx_buff[SJA_RCV_BUF_LEN]; /* 32~95 .. 64bytes Rx FIFO */ + uint32_t rx_ptr; /* Count by bytes. */ + uint32_t rx_cnt; /* Count by bytes. */ + + /* PeliCAN state and registers sorted by address */ + uint8_t control; /* 0 .. Control register */ + /* 1 .. Command register */ + uint8_t status_bas; /* 2 .. Status register */ + uint8_t interrupt_bas; /* 3 .. Interrupt register */ + uint8_t code; /* 4 .. Acceptance code register */ + uint8_t mask; /* 5 .. Acceptance mask register */ + + qemu_can_filter filter[4]; + + qemu_irq irq; + CanBusClientState bus_client; +} CanSJA1000State; + +/* PeliCAN mode */ +enum SJA1000_PeliCAN_regs { + SJA_MOD = 0x00, /* Mode control register */ + SJA_CMR = 0x01, /* Command register */ + SJA_SR = 0x02, /* Status register */ + SJA_IR = 0x03, /* Interrupt register */ + SJA_IER = 0x04, /* Interrupt Enable */ + SJA_BTR0 = 0x06, /* Bus Timing register 0 */ + SJA_BTR1 = 0x07, /* Bus Timing register 1 */ + SJA_OCR = 0x08, /* Output Control register */ + SJA_ALC = 0x0b, /* Arbitration Lost Capture */ + SJA_ECC = 0x0c, /* Error Code Capture */ + SJA_EWLR = 0x0d, /* Error Warning Limit */ + SJA_RXERR = 0x0e, /* RX Error Counter */ + SJA_TXERR0 = 0x0e, /* TX Error Counter */ + SJA_TXERR1 = 0x0f, + SJA_RMC = 0x1d, /* Rx Message Counter + * number of messages in RX FIFO + */ + SJA_RBSA = 0x1e, /* Rx Buffer Start Addr + * address of current message + */ + SJA_FRM = 0x10, /* Transmit Buffer + * write: Receive Buffer + * read: Frame Information + */ +/* + * ID bytes (11 bits in 0 and 1 for standard message or + * 16 bits in 0,1 and 13 bits in 2,3 for extended message) + * The most significant bit of ID is placed in MSB + * position of ID0 register. + */ + SJA_ID0 = 0x11, /* ID for standard and extended frames */ + SJA_ID1 = 0x12, + SJA_ID2 = 0x13, /* ID cont. for extended frames */ + SJA_ID3 = 0x14, + + SJA_DATS = 0x13, /* Data start standard frame */ + SJA_DATE = 0x15, /* Data start extended frame */ + SJA_ACR0 = 0x10, /* Acceptance Code (4 bytes) in RESET mode */ + SJA_AMR0 = 0x14, /* Acceptance Mask (4 bytes) in RESET mode */ + SJA_PeliCAN_AC_LEN = 4, /* 4 bytes */ + SJA_CDR = 0x1f /* Clock Divider */ +}; + + +/* BasicCAN mode */ +enum SJA1000_BasicCAN_regs { + SJA_BCAN_CTR = 0x00, /* Control register */ + SJA_BCAN_CMR = 0x01, /* Command register */ + SJA_BCAN_SR = 0x02, /* Status register */ + SJA_BCAN_IR = 0x03 /* Interrupt register */ +}; + +void can_sja_hardware_reset(CanSJA1000State *s); + +void can_sja_mem_write(CanSJA1000State *s, hwaddr addr, uint64_t val, + unsigned size); + +uint64_t can_sja_mem_read(CanSJA1000State *s, hwaddr addr, unsigned size); + +int can_sja_connect_to_bus(CanSJA1000State *s, CanBusState *bus); + +void can_sja_disconnect(CanSJA1000State *s); + +int can_sja_init(CanSJA1000State *s, qemu_irq irq); + +int can_sja_can_receive(CanBusClientState *client); + +ssize_t can_sja_receive(CanBusClientState *client, + const qemu_can_frame *frames, size_t frames_cnt); + +extern const VMStateDescription vmstate_can_sja; + +#endif diff --git a/hw/sd/core.c b/hw/sd/core.c index 295dc44..3c6eae6 100644 --- a/hw/sd/core.c +++ b/hw/sd/core.c @@ -23,6 +23,12 @@ #include "hw/qdev-core.h" #include "sysemu/block-backend.h" #include "hw/sd/sd.h" +#include "trace.h" + +static inline const char *sdbus_name(SDBus *sdbus) +{ + return sdbus->qbus.name; +} static SDState *get_card(SDBus *sdbus) { @@ -35,10 +41,58 @@ static SDState *get_card(SDBus *sdbus) return SD_CARD(kid->child); } +uint8_t sdbus_get_dat_lines(SDBus *sdbus) +{ + SDState *slave = get_card(sdbus); + uint8_t dat_lines = 0b1111; /* 4 bit bus width */ + + if (slave) { + SDCardClass *sc = SD_CARD_GET_CLASS(slave); + + if (sc->get_dat_lines) { + dat_lines = sc->get_dat_lines(slave); + } + } + trace_sdbus_get_dat_lines(sdbus_name(sdbus), dat_lines); + + return dat_lines; +} + +bool sdbus_get_cmd_line(SDBus *sdbus) +{ + SDState *slave = get_card(sdbus); + bool cmd_line = true; + + if (slave) { + SDCardClass *sc = SD_CARD_GET_CLASS(slave); + + if (sc->get_cmd_line) { + cmd_line = sc->get_cmd_line(slave); + } + } + trace_sdbus_get_cmd_line(sdbus_name(sdbus), cmd_line); + + return cmd_line; +} + +void sdbus_set_voltage(SDBus *sdbus, uint16_t millivolts) +{ + SDState *card = get_card(sdbus); + + trace_sdbus_set_voltage(sdbus_name(sdbus), millivolts); + if (card) { + SDCardClass *sc = SD_CARD_GET_CLASS(card); + + assert(sc->set_voltage); + sc->set_voltage(card, millivolts); + } +} + int sdbus_do_command(SDBus *sdbus, SDRequest *req, uint8_t *response) { SDState *card = get_card(sdbus); + trace_sdbus_command(sdbus_name(sdbus), req->cmd, req->arg, req->crc); if (card) { SDCardClass *sc = SD_CARD_GET_CLASS(card); @@ -52,6 +106,7 @@ void sdbus_write_data(SDBus *sdbus, uint8_t value) { SDState *card = get_card(sdbus); + trace_sdbus_write(sdbus_name(sdbus), value); if (card) { SDCardClass *sc = SD_CARD_GET_CLASS(card); @@ -62,14 +117,16 @@ void sdbus_write_data(SDBus *sdbus, uint8_t value) uint8_t sdbus_read_data(SDBus *sdbus) { SDState *card = get_card(sdbus); + uint8_t value = 0; if (card) { SDCardClass *sc = SD_CARD_GET_CLASS(card); - return sc->read_data(card); + value = sc->read_data(card); } + trace_sdbus_read(sdbus_name(sdbus), value); - return 0; + return value; } bool sdbus_data_ready(SDBus *sdbus) @@ -126,8 +126,32 @@ struct SDState { BlockBackend *blk; bool enable; + uint8_t dat_lines; + bool cmd_line; }; +static uint8_t sd_get_dat_lines(SDState *sd) +{ + return sd->enable ? sd->dat_lines : 0; +} + +static bool sd_get_cmd_line(SDState *sd) +{ + return sd->enable ? sd->cmd_line : false; +} + +static void sd_set_voltage(SDState *sd, uint16_t millivolts) +{ + switch (millivolts) { + case 3001 ... 3600: /* SD_VOLTAGE_3_3V */ + case 2001 ... 3000: /* SD_VOLTAGE_3_0V */ + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "SD card voltage not supported: %.3fV", + millivolts / 1000.f); + } +} + static void sd_set_mode(SDState *sd) { switch (sd->state) { @@ -445,6 +469,8 @@ static void sd_reset(DeviceState *dev) sd->blk_len = 0x200; sd->pwd_len = 0; sd->expecting_acmd = false; + sd->dat_lines = 0xf; + sd->cmd_line = true; sd->multi_blk_cnt = 0; } @@ -1926,6 +1952,9 @@ static void sd_class_init(ObjectClass *klass, void *data) dc->reset = sd_reset; dc->bus_type = TYPE_SD_BUS; + sc->set_voltage = sd_set_voltage; + sc->get_dat_lines = sd_get_dat_lines; + sc->get_cmd_line = sd_get_cmd_line; sc->do_command = sd_do_command; sc->write_data = sd_write_data; sc->read_data = sd_read_data; diff --git a/hw/sd/sdhci-internal.h b/hw/sd/sdhci-internal.h index 0991acd..756ef3f 100644 --- a/hw/sd/sdhci-internal.h +++ b/hw/sd/sdhci-internal.h @@ -24,6 +24,8 @@ #ifndef SDHCI_INTERNAL_H #define SDHCI_INTERNAL_H +#include "hw/registerfields.h" + /* R/W SDMA System Address register 0x0 */ #define SDHC_SYSAD 0x00 @@ -41,6 +43,7 @@ #define SDHC_TRNS_DMA 0x0001 #define SDHC_TRNS_BLK_CNT_EN 0x0002 #define SDHC_TRNS_ACMD12 0x0004 +#define SDHC_TRNS_ACMD23 0x0008 /* since v3 */ #define SDHC_TRNS_READ 0x0010 #define SDHC_TRNS_MULTI 0x0020 #define SDHC_TRNMOD_MASK 0x0037 @@ -79,15 +82,19 @@ #define SDHC_CARD_PRESENT 0x00010000 #define SDHC_CARD_DETECT 0x00040000 #define SDHC_WRITE_PROTECT 0x00080000 +FIELD(SDHC_PRNSTS, DAT_LVL, 20, 4); +FIELD(SDHC_PRNSTS, CMD_LVL, 24, 1); #define TRANSFERRING_DATA(x) \ ((x) & (SDHC_DOING_READ | SDHC_DOING_WRITE)) /* R/W Host control Register 0x0 */ #define SDHC_HOSTCTL 0x28 #define SDHC_CTRL_LED 0x01 +#define SDHC_CTRL_DATATRANSFERWIDTH 0x02 /* SD mode only */ +#define SDHC_CTRL_HIGH_SPEED 0x04 #define SDHC_CTRL_DMA_CHECK_MASK 0x18 #define SDHC_CTRL_SDMA 0x00 -#define SDHC_CTRL_ADMA1_32 0x08 +#define SDHC_CTRL_ADMA1_32 0x08 /* NOT ALLOWED since v2 */ #define SDHC_CTRL_ADMA2_32 0x10 #define SDHC_CTRL_ADMA2_64 0x18 #define SDHC_DMA_TYPE(x) ((x) & SDHC_CTRL_DMA_CHECK_MASK) @@ -96,10 +103,10 @@ #define SDHC_CTRL_CDTEST_INS 0x40 #define SDHC_CTRL_CDTEST_EN 0x80 - /* R/W Power Control Register 0x0 */ #define SDHC_PWRCON 0x29 #define SDHC_POWER_ON (1 << 0) +FIELD(SDHC_PWRCON, BUS_VOLTAGE, 1, 3); /* R/W Block Gap Control Register 0x0 */ #define SDHC_BLKGAP 0x2A @@ -122,6 +129,7 @@ /* R/W Timeout Control Register 0x0 */ #define SDHC_TIMEOUTCON 0x2E +FIELD(SDHC_TIMEOUTCON, COUNTER, 0, 4); /* R/W Software Reset Register 0x0 */ #define SDHC_SWRST 0x2F @@ -178,17 +186,62 @@ /* ROC Auto CMD12 error status register 0x0 */ #define SDHC_ACMD12ERRSTS 0x3C +FIELD(SDHC_ACMD12ERRSTS, TIMEOUT_ERR, 1, 1); +FIELD(SDHC_ACMD12ERRSTS, CRC_ERR, 2, 1); +FIELD(SDHC_ACMD12ERRSTS, INDEX_ERR, 4, 1); + +/* Host Control Register 2 (since v3) */ +#define SDHC_HOSTCTL2 0x3E +FIELD(SDHC_HOSTCTL2, UHS_MODE_SEL, 0, 3); +FIELD(SDHC_HOSTCTL2, V18_ENA, 3, 1); /* UHS-I only */ +FIELD(SDHC_HOSTCTL2, DRIVER_STRENGTH, 4, 2); /* UHS-I only */ +FIELD(SDHC_HOSTCTL2, EXECUTE_TUNING, 6, 1); /* UHS-I only */ +FIELD(SDHC_HOSTCTL2, SAMPLING_CLKSEL, 7, 1); /* UHS-I only */ +FIELD(SDHC_HOSTCTL2, UHS_II_ENA, 8, 1); /* since v4 */ +FIELD(SDHC_HOSTCTL2, ADMA2_LENGTH, 10, 1); /* since v4 */ +FIELD(SDHC_HOSTCTL2, CMD23_ENA, 11, 1); /* since v4 */ +FIELD(SDHC_HOSTCTL2, VERSION4, 12, 1); /* since v4 */ +FIELD(SDHC_HOSTCTL2, ASYNC_INT, 14, 1); +FIELD(SDHC_HOSTCTL2, PRESET_ENA, 15, 1); /* HWInit Capabilities Register 0x05E80080 */ #define SDHC_CAPAB 0x40 -#define SDHC_CAN_DO_DMA 0x00400000 -#define SDHC_CAN_DO_ADMA2 0x00080000 -#define SDHC_CAN_DO_ADMA1 0x00100000 -#define SDHC_64_BIT_BUS_SUPPORT (1 << 28) -#define SDHC_CAPAB_BLOCKSIZE(x) (((x) >> 16) & 0x3) +FIELD(SDHC_CAPAB, TOCLKFREQ, 0, 6); +FIELD(SDHC_CAPAB, TOUNIT, 7, 1); +FIELD(SDHC_CAPAB, BASECLKFREQ, 8, 8); +FIELD(SDHC_CAPAB, MAXBLOCKLENGTH, 16, 2); +FIELD(SDHC_CAPAB, EMBEDDED_8BIT, 18, 1); /* since v3 */ +FIELD(SDHC_CAPAB, ADMA2, 19, 1); /* since v2 */ +FIELD(SDHC_CAPAB, ADMA1, 20, 1); /* v1 only? */ +FIELD(SDHC_CAPAB, HIGHSPEED, 21, 1); +FIELD(SDHC_CAPAB, SDMA, 22, 1); +FIELD(SDHC_CAPAB, SUSPRESUME, 23, 1); +FIELD(SDHC_CAPAB, V33, 24, 1); +FIELD(SDHC_CAPAB, V30, 25, 1); +FIELD(SDHC_CAPAB, V18, 26, 1); +FIELD(SDHC_CAPAB, BUS64BIT_V4, 27, 1); /* since v4.10 */ +FIELD(SDHC_CAPAB, BUS64BIT, 28, 1); /* since v2 */ +FIELD(SDHC_CAPAB, ASYNC_INT, 29, 1); /* since v3 */ +FIELD(SDHC_CAPAB, SLOT_TYPE, 30, 2); /* since v3 */ +FIELD(SDHC_CAPAB, BUS_SPEED, 32, 3); /* since v3 */ +FIELD(SDHC_CAPAB, UHS_II, 35, 8); /* since v4.20 */ +FIELD(SDHC_CAPAB, DRIVER_STRENGTH, 36, 3); /* since v3 */ +FIELD(SDHC_CAPAB, DRIVER_TYPE_A, 36, 1); /* since v3 */ +FIELD(SDHC_CAPAB, DRIVER_TYPE_C, 37, 1); /* since v3 */ +FIELD(SDHC_CAPAB, DRIVER_TYPE_D, 38, 1); /* since v3 */ +FIELD(SDHC_CAPAB, TIMER_RETUNING, 40, 4); /* since v3 */ +FIELD(SDHC_CAPAB, SDR50_TUNING, 45, 1); /* since v3 */ +FIELD(SDHC_CAPAB, RETUNING_MODE, 46, 2); /* since v3 */ +FIELD(SDHC_CAPAB, CLOCK_MULT, 48, 8); /* since v3 */ +FIELD(SDHC_CAPAB, ADMA3, 59, 1); /* since v4.20 */ +FIELD(SDHC_CAPAB, V18_VDD2, 60, 1); /* since v4.20 */ /* HWInit Maximum Current Capabilities Register 0x0 */ #define SDHC_MAXCURR 0x48 +FIELD(SDHC_MAXCURR, V33_VDD1, 0, 8); +FIELD(SDHC_MAXCURR, V30_VDD1, 8, 8); +FIELD(SDHC_MAXCURR, V18_VDD1, 16, 8); +FIELD(SDHC_MAXCURR, V18_VDD2, 32, 8); /* since v4.20 */ /* W Force Event Auto CMD12 Error Interrupt Register 0x0000 */ #define SDHC_FEAER 0x50 @@ -216,9 +269,9 @@ /* Slot interrupt status */ #define SDHC_SLOT_INT_STATUS 0xFC -/* HWInit Host Controller Version Register 0x0401 */ +/* HWInit Host Controller Version Register */ #define SDHC_HCVER 0xFE -#define SD_HOST_SPECv2_VERS 0x2401 +#define SDHC_HCVER_VENDOR 0x24 #define SDHC_REGISTERS_MAP_SIZE 0x100 #define SDHC_INSERTION_DELAY (NANOSECONDS_PER_SECOND) diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index ee95e78..97b4a47 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -23,6 +23,7 @@ */ #include "qemu/osdep.h" +#include "qemu/error-report.h" #include "qapi/error.h" #include "hw/hw.h" #include "sysemu/block-backend.h" @@ -33,72 +34,194 @@ #include "hw/sd/sdhci.h" #include "sdhci-internal.h" #include "qemu/log.h" +#include "qemu/cutils.h" #include "trace.h" #define TYPE_SDHCI_BUS "sdhci-bus" #define SDHCI_BUS(obj) OBJECT_CHECK(SDBus, (obj), TYPE_SDHCI_BUS) +#define MASKED_WRITE(reg, mask, val) (reg = (reg & (mask)) | (val)) + /* Default SD/MMC host controller features information, which will be * presented in CAPABILITIES register of generic SD host controller at reset. - * If not stated otherwise: - * 0 - not supported, 1 - supported, other - prohibited. + * + * support: + * - 3.3v and 1.8v voltages + * - SDMA/ADMA1/ADMA2 + * - high-speed + * max host controller R/W buffers size: 512B + * max clock frequency for SDclock: 52 MHz + * timeout clock frequency: 52 MHz + * + * does not support: + * - 3.0v voltage + * - 64-bit system bus + * - suspend/resume */ -#define SDHC_CAPAB_64BITBUS 0ul /* 64-bit System Bus Support */ -#define SDHC_CAPAB_18V 1ul /* Voltage support 1.8v */ -#define SDHC_CAPAB_30V 0ul /* Voltage support 3.0v */ -#define SDHC_CAPAB_33V 1ul /* Voltage support 3.3v */ -#define SDHC_CAPAB_SUSPRESUME 0ul /* Suspend/resume support */ -#define SDHC_CAPAB_SDMA 1ul /* SDMA support */ -#define SDHC_CAPAB_HIGHSPEED 1ul /* High speed support */ -#define SDHC_CAPAB_ADMA1 1ul /* ADMA1 support */ -#define SDHC_CAPAB_ADMA2 1ul /* ADMA2 support */ -/* Maximum host controller R/W buffers size - * Possible values: 512, 1024, 2048 bytes */ -#define SDHC_CAPAB_MAXBLOCKLENGTH 512ul -/* Maximum clock frequency for SDclock in MHz - * value in range 10-63 MHz, 0 - not defined */ -#define SDHC_CAPAB_BASECLKFREQ 52ul -#define SDHC_CAPAB_TOUNIT 1ul /* Timeout clock unit 0 - kHz, 1 - MHz */ -/* Timeout clock frequency 1-63, 0 - not defined */ -#define SDHC_CAPAB_TOCLKFREQ 52ul - -/* Now check all parameters and calculate CAPABILITIES REGISTER value */ -#if SDHC_CAPAB_64BITBUS > 1 || SDHC_CAPAB_18V > 1 || SDHC_CAPAB_30V > 1 || \ - SDHC_CAPAB_33V > 1 || SDHC_CAPAB_SUSPRESUME > 1 || SDHC_CAPAB_SDMA > 1 || \ - SDHC_CAPAB_HIGHSPEED > 1 || SDHC_CAPAB_ADMA2 > 1 || SDHC_CAPAB_ADMA1 > 1 ||\ - SDHC_CAPAB_TOUNIT > 1 -#error Capabilities features can have value 0 or 1 only! -#endif - -#if SDHC_CAPAB_MAXBLOCKLENGTH == 512 -#define MAX_BLOCK_LENGTH 0ul -#elif SDHC_CAPAB_MAXBLOCKLENGTH == 1024 -#define MAX_BLOCK_LENGTH 1ul -#elif SDHC_CAPAB_MAXBLOCKLENGTH == 2048 -#define MAX_BLOCK_LENGTH 2ul -#else -#error Max host controller block size can have value 512, 1024 or 2048 only! -#endif - -#if (SDHC_CAPAB_BASECLKFREQ > 0 && SDHC_CAPAB_BASECLKFREQ < 10) || \ - SDHC_CAPAB_BASECLKFREQ > 63 -#error SDclock frequency can have value in range 0, 10-63 only! -#endif - -#if SDHC_CAPAB_TOCLKFREQ > 63 -#error Timeout clock frequency can have value in range 0-63 only! -#endif - -#define SDHC_CAPAB_REG_DEFAULT \ - ((SDHC_CAPAB_64BITBUS << 28) | (SDHC_CAPAB_18V << 26) | \ - (SDHC_CAPAB_30V << 25) | (SDHC_CAPAB_33V << 24) | \ - (SDHC_CAPAB_SUSPRESUME << 23) | (SDHC_CAPAB_SDMA << 22) | \ - (SDHC_CAPAB_HIGHSPEED << 21) | (SDHC_CAPAB_ADMA1 << 20) | \ - (SDHC_CAPAB_ADMA2 << 19) | (MAX_BLOCK_LENGTH << 16) | \ - (SDHC_CAPAB_BASECLKFREQ << 8) | (SDHC_CAPAB_TOUNIT << 7) | \ - (SDHC_CAPAB_TOCLKFREQ)) +#define SDHC_CAPAB_REG_DEFAULT 0x057834b4 -#define MASKED_WRITE(reg, mask, val) (reg = (reg & (mask)) | (val)) +static inline unsigned int sdhci_get_fifolen(SDHCIState *s) +{ + return 1 << (9 + FIELD_EX32(s->capareg, SDHC_CAPAB, MAXBLOCKLENGTH)); +} + +/* return true on error */ +static bool sdhci_check_capab_freq_range(SDHCIState *s, const char *desc, + uint8_t freq, Error **errp) +{ + if (s->sd_spec_version >= 3) { + return false; + } + switch (freq) { + case 0: + case 10 ... 63: + break; + default: + error_setg(errp, "SD %s clock frequency can have value" + "in range 0-63 only", desc); + return true; + } + return false; +} + +static void sdhci_check_capareg(SDHCIState *s, Error **errp) +{ + uint64_t msk = s->capareg; + uint32_t val; + bool y; + + switch (s->sd_spec_version) { + case 4: + val = FIELD_EX64(s->capareg, SDHC_CAPAB, BUS64BIT_V4); + trace_sdhci_capareg("64-bit system bus (v4)", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, BUS64BIT_V4, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, UHS_II); + trace_sdhci_capareg("UHS-II", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, UHS_II, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, ADMA3); + trace_sdhci_capareg("ADMA3", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, ADMA3, 0); + + /* fallthrough */ + case 3: + val = FIELD_EX64(s->capareg, SDHC_CAPAB, ASYNC_INT); + trace_sdhci_capareg("async interrupt", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, ASYNC_INT, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, SLOT_TYPE); + if (val) { + error_setg(errp, "slot-type not supported"); + return; + } + trace_sdhci_capareg("slot type", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, SLOT_TYPE, 0); + + if (val != 2) { + val = FIELD_EX64(s->capareg, SDHC_CAPAB, EMBEDDED_8BIT); + trace_sdhci_capareg("8-bit bus", val); + } + msk = FIELD_DP64(msk, SDHC_CAPAB, EMBEDDED_8BIT, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, BUS_SPEED); + trace_sdhci_capareg("bus speed mask", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, BUS_SPEED, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, DRIVER_STRENGTH); + trace_sdhci_capareg("driver strength mask", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, DRIVER_STRENGTH, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, TIMER_RETUNING); + trace_sdhci_capareg("timer re-tuning", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, TIMER_RETUNING, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, SDR50_TUNING); + trace_sdhci_capareg("use SDR50 tuning", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, SDR50_TUNING, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, RETUNING_MODE); + trace_sdhci_capareg("re-tuning mode", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, RETUNING_MODE, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, CLOCK_MULT); + trace_sdhci_capareg("clock multiplier", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, CLOCK_MULT, 0); + + /* fallthrough */ + case 2: /* default version */ + val = FIELD_EX64(s->capareg, SDHC_CAPAB, ADMA2); + trace_sdhci_capareg("ADMA2", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, ADMA2, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, ADMA1); + trace_sdhci_capareg("ADMA1", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, ADMA1, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, BUS64BIT); + trace_sdhci_capareg("64-bit system bus (v3)", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, BUS64BIT, 0); + + /* fallthrough */ + case 1: + y = FIELD_EX64(s->capareg, SDHC_CAPAB, TOUNIT); + msk = FIELD_DP64(msk, SDHC_CAPAB, TOUNIT, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, TOCLKFREQ); + trace_sdhci_capareg(y ? "timeout (MHz)" : "Timeout (KHz)", val); + if (sdhci_check_capab_freq_range(s, "timeout", val, errp)) { + return; + } + msk = FIELD_DP64(msk, SDHC_CAPAB, TOCLKFREQ, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, BASECLKFREQ); + trace_sdhci_capareg(y ? "base (MHz)" : "Base (KHz)", val); + if (sdhci_check_capab_freq_range(s, "base", val, errp)) { + return; + } + msk = FIELD_DP64(msk, SDHC_CAPAB, BASECLKFREQ, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, MAXBLOCKLENGTH); + if (val >= 3) { + error_setg(errp, "block size can be 512, 1024 or 2048 only"); + return; + } + trace_sdhci_capareg("max block length", sdhci_get_fifolen(s)); + msk = FIELD_DP64(msk, SDHC_CAPAB, MAXBLOCKLENGTH, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, HIGHSPEED); + trace_sdhci_capareg("high speed", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, HIGHSPEED, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, SDMA); + trace_sdhci_capareg("SDMA", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, SDMA, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, SUSPRESUME); + trace_sdhci_capareg("suspend/resume", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, SUSPRESUME, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, V33); + trace_sdhci_capareg("3.3v", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, V33, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, V30); + trace_sdhci_capareg("3.0v", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, V30, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, V18); + trace_sdhci_capareg("1.8v", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, V18, 0); + break; + + default: + error_setg(errp, "Unsupported spec version: %u", s->sd_spec_version); + } + if (msk) { + qemu_log_mask(LOG_UNIMP, + "SDHCI: unknown CAPAB mask: 0x%016" PRIx64 "\n", msk); + } +} static uint8_t sdhci_slotint(SDHCIState *s) { @@ -173,7 +296,8 @@ static void sdhci_reset(SDHCIState *s) timer_del(s->insert_timer); timer_del(s->transfer_timer); - /* Set all registers to 0. Capabilities registers are not cleared + + /* Set all registers to 0. Capabilities/Version registers are not cleared * and assumed to always preserve their value, given to them during * initialization */ memset(&s->sdmasysad, 0, (uintptr_t)&s->capareg - (uintptr_t)&s->sdmasysad); @@ -292,19 +416,35 @@ static void sdhci_end_transfer(SDHCIState *s) /* * Programmed i/o data transfer */ +#define BLOCK_SIZE_MASK (4 * K_BYTE - 1) /* Fill host controller's read buffer with BLKSIZE bytes of data from card */ static void sdhci_read_block_from_card(SDHCIState *s) { int index = 0; + uint8_t data; + const uint16_t blk_size = s->blksize & BLOCK_SIZE_MASK; if ((s->trnmod & SDHC_TRNS_MULTI) && (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) { return; } - for (index = 0; index < (s->blksize & 0x0fff); index++) { - s->fifo_buffer[index] = sdbus_read_data(&s->sdbus); + for (index = 0; index < blk_size; index++) { + data = sdbus_read_data(&s->sdbus); + if (!FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, EXECUTE_TUNING)) { + /* Device is not in tunning */ + s->fifo_buffer[index] = data; + } + } + + if (FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, EXECUTE_TUNING)) { + /* Device is in tunning */ + s->hostctl2 &= ~R_SDHC_HOSTCTL2_EXECUTE_TUNING_MASK; + s->hostctl2 |= R_SDHC_HOSTCTL2_SAMPLING_CLKSEL_MASK; + s->prnsts &= ~(SDHC_DAT_LINE_ACTIVE | SDHC_DOING_READ | + SDHC_DATA_INHIBIT); + goto read_done; } /* New data now available for READ through Buffer Port Register */ @@ -329,6 +469,7 @@ static void sdhci_read_block_from_card(SDHCIState *s) } } +read_done: sdhci_update_irq(s); } @@ -348,7 +489,7 @@ static uint32_t sdhci_read_dataport(SDHCIState *s, unsigned size) value |= s->fifo_buffer[s->data_count] << i * 8; s->data_count++; /* check if we've read all valid data (blksize bytes) from buffer */ - if ((s->data_count) >= (s->blksize & 0x0fff)) { + if ((s->data_count) >= (s->blksize & BLOCK_SIZE_MASK)) { trace_sdhci_read_dataport(s->data_count); s->prnsts &= ~SDHC_DATA_AVAILABLE; /* no more data in a buffer */ s->data_count = 0; /* next buff read must start at position [0] */ @@ -395,7 +536,7 @@ static void sdhci_write_block_to_card(SDHCIState *s) } } - for (index = 0; index < (s->blksize & 0x0fff); index++) { + for (index = 0; index < (s->blksize & BLOCK_SIZE_MASK); index++) { sdbus_write_data(&s->sdbus, s->fifo_buffer[index]); } @@ -440,7 +581,7 @@ static void sdhci_write_dataport(SDHCIState *s, uint32_t value, unsigned size) s->fifo_buffer[s->data_count] = value & 0xFF; s->data_count++; value >>= 8; - if (s->data_count >= (s->blksize & 0x0fff)) { + if (s->data_count >= (s->blksize & BLOCK_SIZE_MASK)) { trace_sdhci_write_dataport(s->data_count); s->data_count = 0; s->prnsts &= ~SDHC_SPACE_AVAILABLE; @@ -460,8 +601,8 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) { bool page_aligned = false; unsigned int n, begin; - const uint16_t block_size = s->blksize & 0x0fff; - uint32_t boundary_chk = 1 << (((s->blksize & 0xf000) >> 12) + 12); + const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK; + uint32_t boundary_chk = 1 << (((s->blksize & ~BLOCK_SIZE_MASK) >> 12) + 12); uint32_t boundary_count = boundary_chk - (s->sdmasysad % boundary_chk); if (!(s->trnmod & SDHC_TRNS_BLK_CNT_EN) || !s->blkcnt) { @@ -550,7 +691,7 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) static void sdhci_sdma_transfer_single_block(SDHCIState *s) { int n; - uint32_t datacnt = s->blksize & 0x0fff; + uint32_t datacnt = s->blksize & BLOCK_SIZE_MASK; if (s->trnmod & SDHC_TRNS_READ) { for (n = 0; n < datacnt; n++) { @@ -580,7 +721,7 @@ static void get_adma_description(SDHCIState *s, ADMADescr *dscr) uint32_t adma1 = 0; uint64_t adma2 = 0; hwaddr entry_addr = (hwaddr)s->admasysaddr; - switch (SDHC_DMA_TYPE(s->hostctl)) { + switch (SDHC_DMA_TYPE(s->hostctl1)) { case SDHC_CTRL_ADMA2_32: dma_memory_read(s->dma_as, entry_addr, (uint8_t *)&adma2, sizeof(adma2)); @@ -614,8 +755,8 @@ static void get_adma_description(SDHCIState *s, ADMADescr *dscr) dscr->length = le16_to_cpu(dscr->length); dma_memory_read(s->dma_as, entry_addr + 4, (uint8_t *)(&dscr->addr), 8); - dscr->attr = le64_to_cpu(dscr->attr); - dscr->attr &= 0xfffffff8; + dscr->addr = le64_to_cpu(dscr->addr); + dscr->attr &= (uint8_t) ~0xC0; dscr->incr = 12; break; } @@ -626,7 +767,7 @@ static void get_adma_description(SDHCIState *s, ADMADescr *dscr) static void sdhci_do_adma(SDHCIState *s) { unsigned int n, begin, length; - const uint16_t block_size = s->blksize & 0x0fff; + const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK; ADMADescr dscr = {}; int i; @@ -769,7 +910,7 @@ static void sdhci_data_transfer(void *opaque) SDHCIState *s = (SDHCIState *)opaque; if (s->trnmod & SDHC_TRNS_DMA) { - switch (SDHC_DMA_TYPE(s->hostctl)) { + switch (SDHC_DMA_TYPE(s->hostctl1)) { case SDHC_CTRL_SDMA: if ((s->blkcnt == 1) || !(s->trnmod & SDHC_TRNS_MULTI)) { sdhci_sdma_transfer_single_block(s); @@ -779,7 +920,7 @@ static void sdhci_data_transfer(void *opaque) break; case SDHC_CTRL_ADMA1_32: - if (!(s->capareg & SDHC_CAN_DO_ADMA1)) { + if (!(s->capareg & R_SDHC_CAPAB_ADMA1_MASK)) { trace_sdhci_error("ADMA1 not supported"); break; } @@ -787,7 +928,7 @@ static void sdhci_data_transfer(void *opaque) sdhci_do_adma(s); break; case SDHC_CTRL_ADMA2_32: - if (!(s->capareg & SDHC_CAN_DO_ADMA2)) { + if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK)) { trace_sdhci_error("ADMA2 not supported"); break; } @@ -795,8 +936,8 @@ static void sdhci_data_transfer(void *opaque) sdhci_do_adma(s); break; case SDHC_CTRL_ADMA2_64: - if (!(s->capareg & SDHC_CAN_DO_ADMA2) || - !(s->capareg & SDHC_64_BIT_BUS_SUPPORT)) { + if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK) || + !(s->capareg & R_SDHC_CAPAB_BUS64BIT_MASK)) { trace_sdhci_error("64 bit ADMA not supported"); break; } @@ -876,9 +1017,13 @@ static uint64_t sdhci_read(void *opaque, hwaddr offset, unsigned size) break; case SDHC_PRNSTS: ret = s->prnsts; + ret = FIELD_DP32(ret, SDHC_PRNSTS, DAT_LVL, + sdbus_get_dat_lines(&s->sdbus)); + ret = FIELD_DP32(ret, SDHC_PRNSTS, CMD_LVL, + sdbus_get_cmd_line(&s->sdbus)); break; case SDHC_HOSTCTL: - ret = s->hostctl | (s->pwrcon << 8) | (s->blkgap << 16) | + ret = s->hostctl1 | (s->pwrcon << 8) | (s->blkgap << 16) | (s->wakcon << 24); break; case SDHC_CLKCON: @@ -894,7 +1039,7 @@ static uint64_t sdhci_read(void *opaque, hwaddr offset, unsigned size) ret = s->norintsigen | (s->errintsigen << 16); break; case SDHC_ACMD12ERRSTS: - ret = s->acmd12errsts; + ret = s->acmd12errsts | (s->hostctl2 << 16); break; case SDHC_CAPAB: ret = (uint32_t)s->capareg; @@ -918,7 +1063,7 @@ static uint64_t sdhci_read(void *opaque, hwaddr offset, unsigned size) ret = (uint32_t)(s->admasysaddr >> 32); break; case SDHC_SLOT_INT_STATUS: - ret = (SD_HOST_SPECv2_VERS << 16) | sdhci_slotint(s); + ret = (s->version << 16) | sdhci_slotint(s); break; default: qemu_log_mask(LOG_UNIMP, "SDHC rd_%ub @0x%02" HWADDR_PRIx " " @@ -996,7 +1141,7 @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) MASKED_WRITE(s->sdmasysad, mask, value); /* Writing to last byte of sdmasysad might trigger transfer */ if (!(mask & 0xFF000000) && TRANSFERRING_DATA(s->prnsts) && s->blkcnt && - s->blksize && SDHC_DMA_TYPE(s->hostctl) == SDHC_CTRL_SDMA) { + s->blksize && SDHC_DMA_TYPE(s->hostctl1) == SDHC_CTRL_SDMA) { if (s->trnmod & SDHC_TRNS_MULTI) { sdhci_sdma_transfer_multi_blocks(s); } else { @@ -1026,7 +1171,7 @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) case SDHC_TRNMOD: /* DMA can be enabled only if it is supported as indicated by * capabilities register */ - if (!(s->capareg & SDHC_CAN_DO_DMA)) { + if (!(s->capareg & R_SDHC_CAPAB_SDMA_MASK)) { value &= ~SDHC_TRNS_DMA; } MASKED_WRITE(s->trnmod, mask, value & SDHC_TRNMOD_MASK); @@ -1048,7 +1193,7 @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) if (!(mask & 0xFF0000)) { sdhci_blkgap_write(s, value >> 16); } - MASKED_WRITE(s->hostctl, mask, value); + MASKED_WRITE(s->hostctl1, mask, value); MASKED_WRITE(s->pwrcon, mask >> 8, value >> 8); MASKED_WRITE(s->wakcon, mask >> 24, value >> 24); if (!(s->prnsts & SDHC_CARD_PRESENT) || ((s->pwrcon >> 1) & 0x7) < 5 || @@ -1128,7 +1273,16 @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) sdhci_update_irq(s); break; case SDHC_ACMD12ERRSTS: - MASKED_WRITE(s->acmd12errsts, mask, value); + MASKED_WRITE(s->acmd12errsts, mask, value & UINT16_MAX); + if (s->uhs_mode >= UHS_I) { + MASKED_WRITE(s->hostctl2, mask >> 16, value >> 16); + + if (FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, V18_ENA)) { + sdbus_set_voltage(&s->sdbus, SD_VOLTAGE_1_8V); + } else { + sdbus_set_voltage(&s->sdbus, SD_VOLTAGE_3_3V); + } + } break; case SDHC_CAPAB: @@ -1159,26 +1313,34 @@ static const MemoryRegionOps sdhci_mmio_ops = { .endianness = DEVICE_LITTLE_ENDIAN, }; -static inline unsigned int sdhci_get_fifolen(SDHCIState *s) +static void sdhci_init_readonly_registers(SDHCIState *s, Error **errp) { - switch (SDHC_CAPAB_BLOCKSIZE(s->capareg)) { - case 0: - return 512; - case 1: - return 1024; - case 2: - return 2048; + Error *local_err = NULL; + + switch (s->sd_spec_version) { + case 2 ... 3: + break; default: - hw_error("SDHC: unsupported value for maximum block size\n"); - return 0; + error_setg(errp, "Only Spec v2/v3 are supported"); + return; + } + s->version = (SDHC_HCVER_VENDOR << 8) | (s->sd_spec_version - 1); + + sdhci_check_capareg(s, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; } } /* --- qdev common --- */ #define DEFINE_SDHCI_COMMON_PROPERTIES(_state) \ - /* Capabilities registers provide information on supported features - * of this specific host controller implementation */ \ + DEFINE_PROP_UINT8("sd-spec-version", _state, sd_spec_version, 2), \ + DEFINE_PROP_UINT8("uhs", _state, uhs_mode, UHS_NOT_SUPPORTED), \ + \ + /* Capabilities registers provide information on supported + * features of this specific host controller implementation */ \ DEFINE_PROP_UINT64("capareg", _state, capareg, SDHC_CAPAB_REG_DEFAULT), \ DEFINE_PROP_UINT64("maxcurr", _state, maxcurr, 0) @@ -1206,6 +1368,13 @@ static void sdhci_uninitfn(SDHCIState *s) static void sdhci_common_realize(SDHCIState *s, Error **errp) { + Error *local_err = NULL; + + sdhci_init_readonly_registers(s, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } s->buf_maxsz = sdhci_get_fifolen(s); s->fifo_buffer = g_malloc0(s->buf_maxsz); @@ -1255,7 +1424,7 @@ const VMStateDescription sdhci_vmstate = { VMSTATE_UINT16(cmdreg, SDHCIState), VMSTATE_UINT32_ARRAY(rspreg, SDHCIState, 4), VMSTATE_UINT32(prnsts, SDHCIState), - VMSTATE_UINT8(hostctl, SDHCIState), + VMSTATE_UINT8(hostctl1, SDHCIState), VMSTATE_UINT8(pwrcon, SDHCIState), VMSTATE_UINT8(blkgap, SDHCIState), VMSTATE_UINT8(wakcon, SDHCIState), @@ -1302,10 +1471,12 @@ static Property sdhci_pci_properties[] = { static void sdhci_pci_realize(PCIDevice *dev, Error **errp) { SDHCIState *s = PCI_SDHCI(dev); + Error *local_err = NULL; sdhci_initfn(s); sdhci_common_realize(s, errp); - if (errp && *errp) { + if (local_err) { + error_propagate(errp, local_err); return; } @@ -1383,9 +1554,11 @@ static void sdhci_sysbus_realize(DeviceState *dev, Error ** errp) { SDHCIState *s = SYSBUS_SDHCI(dev); SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Error *local_err = NULL; sdhci_common_realize(s, errp); - if (errp && *errp) { + if (local_err) { + error_propagate(errp, local_err); return; } @@ -1457,7 +1630,7 @@ static uint64_t usdhc_read(void *opaque, hwaddr offset, unsigned size) { SDHCIState *s = SYSBUS_SDHCI(opaque); uint32_t ret; - uint16_t hostctl; + uint16_t hostctl1; switch (offset) { default: @@ -1469,17 +1642,17 @@ static uint64_t usdhc_read(void *opaque, hwaddr offset, unsigned size) * manipulation code see comments in a similar part of * usdhc_write() */ - hostctl = SDHC_DMA_TYPE(s->hostctl) << (8 - 3); + hostctl1 = SDHC_DMA_TYPE(s->hostctl1) << (8 - 3); - if (s->hostctl & SDHC_CTRL_8BITBUS) { - hostctl |= ESDHC_CTRL_8BITBUS; + if (s->hostctl1 & SDHC_CTRL_8BITBUS) { + hostctl1 |= ESDHC_CTRL_8BITBUS; } - if (s->hostctl & SDHC_CTRL_4BITBUS) { - hostctl |= ESDHC_CTRL_4BITBUS; + if (s->hostctl1 & SDHC_CTRL_4BITBUS) { + hostctl1 |= ESDHC_CTRL_4BITBUS; } - ret = hostctl; + ret = hostctl1; ret |= (uint32_t)s->blkgap << 16; ret |= (uint32_t)s->wakcon << 24; @@ -1503,7 +1676,7 @@ static void usdhc_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) { SDHCIState *s = SYSBUS_SDHCI(opaque); - uint8_t hostctl; + uint8_t hostctl1; uint32_t value = (uint32_t)val; switch (offset) { @@ -1566,25 +1739,25 @@ usdhc_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) /* * First, save bits 7 6 and 0 since they are identical */ - hostctl = value & (SDHC_CTRL_LED | - SDHC_CTRL_CDTEST_INS | - SDHC_CTRL_CDTEST_EN); + hostctl1 = value & (SDHC_CTRL_LED | + SDHC_CTRL_CDTEST_INS | + SDHC_CTRL_CDTEST_EN); /* * Second, split "Data Transfer Width" from bits 2 and 1 in to * bits 5 and 1 */ if (value & ESDHC_CTRL_8BITBUS) { - hostctl |= SDHC_CTRL_8BITBUS; + hostctl1 |= SDHC_CTRL_8BITBUS; } if (value & ESDHC_CTRL_4BITBUS) { - hostctl |= ESDHC_CTRL_4BITBUS; + hostctl1 |= ESDHC_CTRL_4BITBUS; } /* * Third, move DMA select from bits 9 and 8 to bits 4 and 3 */ - hostctl |= SDHC_DMA_TYPE(value >> (8 - 3)); + hostctl1 |= SDHC_DMA_TYPE(value >> (8 - 3)); /* * Now place the corrected value into low 16-bit of the value @@ -1595,7 +1768,7 @@ usdhc_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) * kernel */ value &= ~UINT16_MAX; - value |= hostctl; + value |= hostctl1; value |= (uint16_t)s->pwrcon << 8; sdhci_write(opaque, offset, value, size); diff --git a/hw/sd/trace-events b/hw/sd/trace-events index 0a12115..0f8536d 100644 --- a/hw/sd/trace-events +++ b/hw/sd/trace-events @@ -1,5 +1,13 @@ # See docs/devel/tracing.txt for syntax documentation. +# hw/sd/core.c +sdbus_command(const char *bus_name, uint8_t cmd, uint32_t arg, uint8_t crc) "@%s CMD%02d arg 0x%08x crc 0x%02x" +sdbus_read(const char *bus_name, uint8_t value) "@%s value 0x%02x" +sdbus_write(const char *bus_name, uint8_t value) "@%s value 0x%02x" +sdbus_set_voltage(const char *bus_name, uint16_t millivolts) "@%s %u (mV)" +sdbus_get_dat_lines(const char *bus_name, uint8_t dat_lines) "@%s dat_lines: %u" +sdbus_get_cmd_line(const char *bus_name, bool cmd_line) "@%s cmd_line: %u" + # hw/sd/sdhci.c sdhci_set_inserted(const char *level) "card state changed: %s" sdhci_send_command(uint8_t cmd, uint32_t arg) "CMD%02u ARG[0x%08x]" @@ -13,6 +21,7 @@ sdhci_adma_transfer_completed(void) "" sdhci_access(const char *access, unsigned int size, uint64_t offset, const char *dir, uint64_t val, uint64_t val2) "%s%u: addr[0x%04" PRIx64 "] %s 0x%08" PRIx64 " (%" PRIu64 ")" sdhci_read_dataport(uint16_t data_count) "all %u bytes of data have been read from input buffer" sdhci_write_dataport(uint16_t data_count) "write buffer filled with %u bytes of data" +sdhci_capareg(const char *desc, uint16_t val) "%s: %u" # hw/sd/milkymist-memcard.c milkymist_memcard_memory_read(uint32_t addr, uint32_t value) "addr 0x%08x value 0x%08x" diff --git a/include/exec/memory.h b/include/exec/memory.h index 783ef64..fff9b1d 100644 --- a/include/exec/memory.h +++ b/include/exec/memory.h @@ -1091,32 +1091,14 @@ void memory_region_set_dirty(MemoryRegion *mr, hwaddr addr, hwaddr size); /** - * memory_region_test_and_clear_dirty: Check whether a range of bytes is dirty - * for a specified client. It clears them. - * - * Checks whether a range of bytes has been written to since the last - * call to memory_region_reset_dirty() with the same @client. Dirty logging - * must be enabled. - * - * @mr: the memory region being queried. - * @addr: the address (relative to the start of the region) being queried. - * @size: the size of the range being queried. - * @client: the user of the logging information; %DIRTY_MEMORY_MIGRATION or - * %DIRTY_MEMORY_VGA. - */ -bool memory_region_test_and_clear_dirty(MemoryRegion *mr, hwaddr addr, - hwaddr size, unsigned client); - -/** * memory_region_snapshot_and_clear_dirty: Get a snapshot of the dirty * bitmap and clear it. * * Creates a snapshot of the dirty bitmap, clears the dirty bitmap and * returns the snapshot. The snapshot can then be used to query dirty - * status, using memory_region_snapshot_get_dirty. Unlike - * memory_region_test_and_clear_dirty this allows to query the same - * page multiple times, which is especially useful for display updates - * where the scanlines often are not page aligned. + * status, using memory_region_snapshot_get_dirty. Snapshotting allows + * querying the same page multiple times, which is especially useful for + * display updates where the scanlines often are not page aligned. * * The dirty bitmap region which gets copyed into the snapshot (and * cleared afterwards) can be larger than requested. The boundaries @@ -1154,17 +1136,6 @@ bool memory_region_snapshot_get_dirty(MemoryRegion *mr, hwaddr addr, hwaddr size); /** - * memory_region_sync_dirty_bitmap: Synchronize a region's dirty bitmap with - * any external TLBs (e.g. kvm) - * - * Flushes dirty information from accelerators such as kvm and vhost-net - * and makes it available to users of the memory API. - * - * @mr: the region being flushed. - */ -void memory_region_sync_dirty_bitmap(MemoryRegion *mr); - -/** * memory_region_reset_dirty: Mark a range of pages as clean, for a specified * client. * diff --git a/include/hw/sd/sd.h b/include/hw/sd/sd.h index 96caefe..bf1eb07 100644 --- a/include/hw/sd/sd.h +++ b/include/hw/sd/sd.h @@ -56,6 +56,20 @@ #define OCR_CCS_BITN 30 typedef enum { + SD_VOLTAGE_0_4V = 400, /* currently not supported */ + SD_VOLTAGE_1_8V = 1800, + SD_VOLTAGE_3_0V = 3000, + SD_VOLTAGE_3_3V = 3300, +} sd_voltage_mv_t; + +typedef enum { + UHS_NOT_SUPPORTED = 0, + UHS_I = 1, + UHS_II = 2, /* currently not supported */ + UHS_III = 3, /* currently not supported */ +} sd_uhs_mode_t; + +typedef enum { sd_none = -1, sd_bc = 0, /* broadcast -- no response */ sd_bcr, /* broadcast with response */ @@ -88,6 +102,9 @@ typedef struct { void (*write_data)(SDState *sd, uint8_t value); uint8_t (*read_data)(SDState *sd); bool (*data_ready)(SDState *sd); + void (*set_voltage)(SDState *sd, uint16_t millivolts); + uint8_t (*get_dat_lines)(SDState *sd); + bool (*get_cmd_line)(SDState *sd); void (*enable)(SDState *sd, bool enable); bool (*get_inserted)(SDState *sd); bool (*get_readonly)(SDState *sd); @@ -134,6 +151,9 @@ void sd_enable(SDState *sd, bool enable); /* Functions to be used by qdevified callers (working via * an SDBus rather than directly with SDState) */ +void sdbus_set_voltage(SDBus *sdbus, uint16_t millivolts); +uint8_t sdbus_get_dat_lines(SDBus *sdbus); +bool sdbus_get_cmd_line(SDBus *sdbus); int sdbus_do_command(SDBus *sd, SDRequest *req, uint8_t *response); void sdbus_write_data(SDBus *sd, uint8_t value); uint8_t sdbus_read_data(SDBus *sd); diff --git a/include/hw/sd/sdhci.h b/include/hw/sd/sdhci.h index f8d1ba3..f321767 100644 --- a/include/hw/sd/sdhci.h +++ b/include/hw/sd/sdhci.h @@ -59,7 +59,7 @@ typedef struct SDHCIState { uint16_t cmdreg; /* Command Register */ uint32_t rspreg[4]; /* Response Registers 0-3 */ uint32_t prnsts; /* Present State Register */ - uint8_t hostctl; /* Host Control Register */ + uint8_t hostctl1; /* Host Control Register */ uint8_t pwrcon; /* Power control Register */ uint8_t blkgap; /* Block Gap Control Register */ uint8_t wakcon; /* WakeUp Control Register */ @@ -73,11 +73,13 @@ typedef struct SDHCIState { uint16_t norintsigen; /* Normal Interrupt Signal Enable Register */ uint16_t errintsigen; /* Error Interrupt Signal Enable Register */ uint16_t acmd12errsts; /* Auto CMD12 error status register */ + uint16_t hostctl2; /* Host Control 2 */ uint64_t admasysaddr; /* ADMA System Address Register */ /* Read-only registers */ uint64_t capareg; /* Capabilities Register */ uint64_t maxcurr; /* Maximum Current Capabilities Register */ + uint16_t version; /* Host Controller Version Register */ uint8_t *fifo_buffer; /* SD host i/o FIFO buffer */ uint32_t buf_maxsz; @@ -93,6 +95,8 @@ typedef struct SDHCIState { /* Configurable properties */ bool pending_insert_quirk; /* Quirk for Raspberry Pi card insert int */ uint32_t quirks; + uint8_t sd_spec_version; + uint8_t uhs_mode; } SDHCIState; /* diff --git a/include/net/can_emu.h b/include/net/can_emu.h new file mode 100644 index 0000000..1da4d01 --- /dev/null +++ b/include/net/can_emu.h @@ -0,0 +1,123 @@ +/* + * CAN common CAN bus emulation support + * + * Copyright (c) 2013-2014 Jin Yang + * Copyright (c) 2014-2018 Pavel Pisa + * + * Initial development supported by Google GSoC 2013 from RTEMS project slot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION 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 NET_CAN_EMU_H +#define NET_CAN_EMU_H + +#include "qom/object.h" + +/* NOTE: the following two structures is copied from <linux/can.h>. */ + +/* + * Controller Area Network Identifier structure + * + * bit 0-28 : CAN identifier (11/29 bit) + * bit 29 : error frame flag (0 = data frame, 1 = error frame) + * bit 30 : remote transmission request flag (1 = rtr frame) + * bit 31 : frame format flag (0 = standard 11 bit, 1 = extended 29 bit) + */ +typedef uint32_t qemu_canid_t; + +typedef struct qemu_can_frame { + qemu_canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */ + uint8_t can_dlc; /* data length code: 0 .. 8 */ + uint8_t data[8] QEMU_ALIGNED(8); +} qemu_can_frame; + +/* Keep defines for QEMU separate from Linux ones for now */ + +#define QEMU_CAN_EFF_FLAG 0x80000000U /* EFF/SFF is set in the MSB */ +#define QEMU_CAN_RTR_FLAG 0x40000000U /* remote transmission request */ +#define QEMU_CAN_ERR_FLAG 0x20000000U /* error message frame */ + +#define QEMU_CAN_SFF_MASK 0x000007FFU /* standard frame format (SFF) */ +#define QEMU_CAN_EFF_MASK 0x1FFFFFFFU /* extended frame format (EFF) */ + +/** + * struct qemu_can_filter - CAN ID based filter in can_register(). + * @can_id: relevant bits of CAN ID which are not masked out. + * @can_mask: CAN mask (see description) + * + * Description: + * A filter matches, when + * + * <received_can_id> & mask == can_id & mask + * + * The filter can be inverted (QEMU_CAN_INV_FILTER bit set in can_id) or it can + * filter for error message frames (QEMU_CAN_ERR_FLAG bit set in mask). + */ +typedef struct qemu_can_filter { + qemu_canid_t can_id; + qemu_canid_t can_mask; +} qemu_can_filter; + +/* QEMU_CAN_INV_FILTER can be set in qemu_can_filter.can_id */ +#define QEMU_CAN_INV_FILTER 0x20000000U + +typedef struct CanBusClientState CanBusClientState; +typedef struct CanBusState CanBusState; + +typedef struct CanBusClientInfo { + int (*can_receive)(CanBusClientState *); + ssize_t (*receive)(CanBusClientState *, + const struct qemu_can_frame *frames, size_t frames_cnt); +} CanBusClientInfo; + +struct CanBusClientState { + CanBusClientInfo *info; + CanBusState *bus; + int link_down; + QTAILQ_ENTRY(CanBusClientState) next; + CanBusClientState *peer; + char *model; + char *name; + void (*destructor)(CanBusClientState *); +}; + +#define TYPE_CAN_BUS "can-bus" +#define CAN_BUS_CLASS(klass) \ + OBJECT_CLASS_CHECK(CanBusClass, (klass), TYPE_CAN_BUS) +#define CAN_BUS_GET_CLASS(obj) \ + OBJECT_GET_CLASS(CanBusClass, (obj), TYPE_CAN_BUS) +#define CAN_BUS(obj) \ + OBJECT_CHECK(CanBusState, (obj), TYPE_CAN_BUS) + +int can_bus_filter_match(struct qemu_can_filter *filter, qemu_canid_t can_id); + +int can_bus_insert_client(CanBusState *bus, CanBusClientState *client); + +int can_bus_remove_client(CanBusClientState *client); + +ssize_t can_bus_client_send(CanBusClientState *, + const struct qemu_can_frame *frames, + size_t frames_cnt); + +int can_bus_client_set_filters(CanBusClientState *, + const struct qemu_can_filter *filters, + size_t filters_cnt); + +#endif diff --git a/include/net/can_host.h b/include/net/can_host.h new file mode 100644 index 0000000..d796767 --- /dev/null +++ b/include/net/can_host.h @@ -0,0 +1,55 @@ +/* + * CAN common CAN bus emulation support + * + * Copyright (c) 2013-2014 Jin Yang + * Copyright (c) 2014-2018 Pavel Pisa + * + * Initial development supported by Google GSoC 2013 from RTEMS project slot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION 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 NET_CAN_HOST_H +#define NET_CAN_HOST_H + +#include "net/can_emu.h" + +#define TYPE_CAN_HOST "can-host" +#define CAN_HOST_CLASS(klass) \ + OBJECT_CLASS_CHECK(CanHostClass, (klass), TYPE_CAN_HOST) +#define CAN_HOST_GET_CLASS(obj) \ + OBJECT_GET_CLASS(CanHostClass, (obj), TYPE_CAN_HOST) +#define CAN_HOST(obj) \ + OBJECT_CHECK(CanHostState, (obj), TYPE_CAN_HOST) + +typedef struct CanHostState { + ObjectClass oc; + + CanBusState *bus; + CanBusClientState bus_client; +} CanHostState; + +typedef struct CanHostClass { + ObjectClass oc; + + void (*connect)(CanHostState *ch, Error **errp); + void (*disconnect)(CanHostState *ch); +} CanHostClass; + +#endif diff --git a/include/sysemu/hax.h b/include/sysemu/hax.h index f252399..1f6c461 100644 --- a/include/sysemu/hax.h +++ b/include/sysemu/hax.h @@ -27,7 +27,7 @@ int hax_sync_vcpus(void); int hax_init_vcpu(CPUState *cpu); int hax_smp_cpu_exec(CPUState *cpu); -int hax_populate_ram(uint64_t va, uint32_t size); +int hax_populate_ram(uint64_t va, uint64_t size); void hax_cpu_synchronize_state(CPUState *cpu); void hax_cpu_synchronize_post_reset(CPUState *cpu); @@ -1971,33 +1971,7 @@ void memory_region_set_dirty(MemoryRegion *mr, hwaddr addr, memory_region_get_dirty_log_mask(mr)); } -bool memory_region_test_and_clear_dirty(MemoryRegion *mr, hwaddr addr, - hwaddr size, unsigned client) -{ - assert(mr->ram_block); - return cpu_physical_memory_test_and_clear_dirty( - memory_region_get_ram_addr(mr) + addr, size, client); -} - -DirtyBitmapSnapshot *memory_region_snapshot_and_clear_dirty(MemoryRegion *mr, - hwaddr addr, - hwaddr size, - unsigned client) -{ - assert(mr->ram_block); - return cpu_physical_memory_snapshot_and_clear_dirty( - memory_region_get_ram_addr(mr) + addr, size, client); -} - -bool memory_region_snapshot_get_dirty(MemoryRegion *mr, DirtyBitmapSnapshot *snap, - hwaddr addr, hwaddr size) -{ - assert(mr->ram_block); - return cpu_physical_memory_snapshot_get_dirty(snap, - memory_region_get_ram_addr(mr) + addr, size); -} - -void memory_region_sync_dirty_bitmap(MemoryRegion *mr) +static void memory_region_sync_dirty_bitmap(MemoryRegion *mr) { MemoryListener *listener; AddressSpace *as; @@ -2016,7 +1990,7 @@ void memory_region_sync_dirty_bitmap(MemoryRegion *mr) as = listener->address_space; view = address_space_get_flatview(as); FOR_EACH_FLAT_RANGE(fr, view) { - if (fr->mr == mr) { + if (fr->dirty_log_mask && (!mr || fr->mr == mr)) { MemoryRegionSection mrs = section_from_flat_range(fr, view); listener->log_sync(listener, &mrs); } @@ -2025,6 +1999,25 @@ void memory_region_sync_dirty_bitmap(MemoryRegion *mr) } } +DirtyBitmapSnapshot *memory_region_snapshot_and_clear_dirty(MemoryRegion *mr, + hwaddr addr, + hwaddr size, + unsigned client) +{ + assert(mr->ram_block); + memory_region_sync_dirty_bitmap(mr); + return cpu_physical_memory_snapshot_and_clear_dirty( + memory_region_get_ram_addr(mr) + addr, size, client); +} + +bool memory_region_snapshot_get_dirty(MemoryRegion *mr, DirtyBitmapSnapshot *snap, + hwaddr addr, hwaddr size) +{ + assert(mr->ram_block); + return cpu_physical_memory_snapshot_get_dirty(snap, + memory_region_get_ram_addr(mr) + addr, size); +} + void memory_region_set_readonly(MemoryRegion *mr, bool readonly) { if (mr->readonly != readonly) { @@ -2513,26 +2506,7 @@ bool memory_region_present(MemoryRegion *container, hwaddr addr) void memory_global_dirty_log_sync(void) { - MemoryListener *listener; - AddressSpace *as; - FlatView *view; - FlatRange *fr; - - QTAILQ_FOREACH(listener, &memory_listeners, link) { - if (!listener->log_sync) { - continue; - } - as = listener->address_space; - view = address_space_get_flatview(as); - FOR_EACH_FLAT_RANGE(fr, view) { - if (fr->dirty_log_mask) { - MemoryRegionSection mrs = section_from_flat_range(fr, view); - - listener->log_sync(listener, &mrs); - } - } - flatview_unref(view); - } + memory_region_sync_dirty_bitmap(NULL); } static VMChangeStateEntry *vmstate_change; diff --git a/net/Makefile.objs b/net/Makefile.objs index 64adf32..b2bf88a 100644 --- a/net/Makefile.objs +++ b/net/Makefile.objs @@ -23,3 +23,5 @@ common-obj-$(CONFIG_POSIX) += tap.o $(tap-obj-y) common-obj-$(CONFIG_WIN32) += tap-win32.o vde.o-libs = $(VDE_LIBS) + +common-obj-$(CONFIG_CAN_BUS) += can/ diff --git a/net/can/Makefile.objs b/net/can/Makefile.objs new file mode 100644 index 0000000..9f35dc5 --- /dev/null +++ b/net/can/Makefile.objs @@ -0,0 +1,2 @@ +common-obj-y += can_core.o can_host.o +common-obj-$(CONFIG_LINUX) += can_socketcan.o diff --git a/net/can/can_core.c b/net/can/can_core.c new file mode 100644 index 0000000..2a83cad --- /dev/null +++ b/net/can/can_core.c @@ -0,0 +1,138 @@ +/* + * CAN common CAN bus emulation support + * + * Copyright (c) 2013-2014 Jin Yang + * Copyright (c) 2014-2018 Pavel Pisa + * + * Initial development supported by Google GSoC 2013 from RTEMS project slot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF 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/osdep.h" +#include "chardev/char.h" +#include "qemu/sockets.h" +#include "qapi/error.h" +#include "net/can_emu.h" +#include "qom/object_interfaces.h" + +struct CanBusState { + Object object; + + QTAILQ_HEAD(, CanBusClientState) clients; +}; + +static void can_bus_instance_init(Object *object) +{ + CanBusState *bus = (CanBusState *)object; + + QTAILQ_INIT(&bus->clients); +} + +int can_bus_insert_client(CanBusState *bus, CanBusClientState *client) +{ + client->bus = bus; + QTAILQ_INSERT_TAIL(&bus->clients, client, next); + return 0; +} + +int can_bus_remove_client(CanBusClientState *client) +{ + CanBusState *bus = client->bus; + if (bus == NULL) { + return 0; + } + + QTAILQ_REMOVE(&bus->clients, client, next); + client->bus = NULL; + return 1; +} + +ssize_t can_bus_client_send(CanBusClientState *client, + const struct qemu_can_frame *frames, size_t frames_cnt) +{ + int ret = 0; + CanBusState *bus = client->bus; + CanBusClientState *peer; + if (bus == NULL) { + return -1; + } + + QTAILQ_FOREACH(peer, &bus->clients, next) { + if (peer->info->can_receive(peer)) { + if (peer == client) { + /* No loopback support for now */ + continue; + } + if (peer->info->receive(peer, frames, frames_cnt) > 0) { + ret = 1; + } + } + } + + return ret; +} + +int can_bus_filter_match(struct qemu_can_filter *filter, qemu_canid_t can_id) +{ + int m; + if (((can_id | filter->can_mask) & QEMU_CAN_ERR_FLAG)) { + return (filter->can_mask & QEMU_CAN_ERR_FLAG) != 0; + } + m = (can_id & filter->can_mask) == (filter->can_id & filter->can_mask); + return filter->can_id & QEMU_CAN_INV_FILTER ? !m : m; +} + +int can_bus_client_set_filters(CanBusClientState *client, + const struct qemu_can_filter *filters, size_t filters_cnt) +{ + return 0; +} + + +static bool can_bus_can_be_deleted(UserCreatable *uc) +{ + return false; +} + +static void can_bus_class_init(ObjectClass *klass, + void *class_data G_GNUC_UNUSED) +{ + UserCreatableClass *uc_klass = USER_CREATABLE_CLASS(klass); + + uc_klass->can_be_deleted = can_bus_can_be_deleted; +} + +static const TypeInfo can_bus_info = { + .parent = TYPE_OBJECT, + .name = TYPE_CAN_BUS, + .instance_size = sizeof(CanBusState), + .instance_init = can_bus_instance_init, + .class_init = can_bus_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_USER_CREATABLE }, + { } + } +}; + +static void can_bus_register_types(void) +{ + type_register_static(&can_bus_info); +} + +type_init(can_bus_register_types); diff --git a/net/can/can_host.c b/net/can/can_host.c new file mode 100644 index 0000000..c3d2652 --- /dev/null +++ b/net/can/can_host.c @@ -0,0 +1,112 @@ +/* + * CAN generic CAN host connection support + * + * Copyright (c) 2013-2014 Jin Yang + * Copyright (c) 2014-2018 Pavel Pisa + * + * Initial development supported by Google GSoC 2013 from RTEMS project slot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF 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/osdep.h" +#include "chardev/char.h" +#include "qemu/sockets.h" +#include "qapi/error.h" +#include "qom/object_interfaces.h" +#include "net/can_emu.h" +#include "net/can_host.h" + +struct CanBusState { + Object object; + + QTAILQ_HEAD(, CanBusClientState) clients; +}; + +static void can_host_disconnect(CanHostState *ch) +{ + CanHostClass *chc = CAN_HOST_GET_CLASS(ch); + + can_bus_remove_client(&ch->bus_client); + chc->disconnect(ch); +} + +static void can_host_connect(CanHostState *ch, Error **errp) +{ + CanHostClass *chc = CAN_HOST_GET_CLASS(ch); + Error *local_err = NULL; + + chc->connect(ch, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + can_bus_insert_client(ch->bus, &ch->bus_client); +} + +static void can_host_unparent(Object *obj) +{ + can_host_disconnect(CAN_HOST(obj)); +} + +static void can_host_complete(UserCreatable *uc, Error **errp) +{ + can_host_connect(CAN_HOST(uc), errp); +} + +static void can_host_instance_init(Object *obj) +{ + CanHostState *ch = CAN_HOST(obj); + + object_property_add_link(obj, "canbus", TYPE_CAN_BUS, + (Object **)&ch->bus, + object_property_allow_set_link, + OBJ_PROP_LINK_UNREF_ON_RELEASE, + &error_abort); +} + +static void can_host_class_init(ObjectClass *klass, + void *class_data G_GNUC_UNUSED) +{ + UserCreatableClass *uc_klass = USER_CREATABLE_CLASS(klass); + + klass->unparent = can_host_unparent; + uc_klass->complete = can_host_complete; +} + +static const TypeInfo can_host_info = { + .parent = TYPE_OBJECT, + .name = TYPE_CAN_HOST, + .instance_size = sizeof(CanHostState), + .class_size = sizeof(CanHostClass), + .abstract = true, + .instance_init = can_host_instance_init, + .class_init = can_host_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_USER_CREATABLE }, + { } + } +}; + +static void can_host_register_types(void) +{ + type_register_static(&can_host_info); +} + +type_init(can_host_register_types); diff --git a/net/can/can_socketcan.c b/net/can/can_socketcan.c new file mode 100644 index 0000000..39865e2 --- /dev/null +++ b/net/can/can_socketcan.c @@ -0,0 +1,286 @@ +/* + * CAN c support to connect to the Linux host SocketCAN interfaces + * + * Copyright (c) 2013-2014 Jin Yang + * Copyright (c) 2014-2018 Pavel Pisa + * + * Initial development supported by Google GSoC 2013 from RTEMS project slot + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF 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/osdep.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "chardev/char.h" +#include "qemu/sockets.h" +#include "qemu/error-report.h" +#include "net/can_emu.h" +#include "net/can_host.h" + +#include <sys/ioctl.h> +#include <net/if.h> +#include <linux/can.h> +#include <linux/can/raw.h> + +#ifndef DEBUG_CAN +#define DEBUG_CAN 0 +#endif /*DEBUG_CAN*/ + +#define TYPE_CAN_HOST_SOCKETCAN "can-host-socketcan" +#define CAN_HOST_SOCKETCAN(obj) \ + OBJECT_CHECK(CanHostSocketCAN, (obj), TYPE_CAN_HOST_SOCKETCAN) + +#define CAN_READ_BUF_LEN 5 +typedef struct CanHostSocketCAN { + CanHostState parent; + char *ifname; + + qemu_can_filter *rfilter; + int rfilter_num; + can_err_mask_t err_mask; + + qemu_can_frame buf[CAN_READ_BUF_LEN]; + int bufcnt; + int bufptr; + + int fd; +} CanHostSocketCAN; + +/* Check that QEMU and Linux kernel flags encoding and structure matches */ +QEMU_BUILD_BUG_ON(QEMU_CAN_EFF_FLAG != CAN_EFF_FLAG); +QEMU_BUILD_BUG_ON(QEMU_CAN_RTR_FLAG != CAN_RTR_FLAG); +QEMU_BUILD_BUG_ON(QEMU_CAN_ERR_FLAG != CAN_ERR_FLAG); +QEMU_BUILD_BUG_ON(QEMU_CAN_INV_FILTER != CAN_INV_FILTER); +QEMU_BUILD_BUG_ON(offsetof(qemu_can_frame, data) + != offsetof(struct can_frame, data)); + +static void can_host_socketcan_display_msg(struct qemu_can_frame *msg) +{ + int i; + + qemu_log_lock(); + qemu_log("[cansocketcan]: %03X [%01d] %s %s", + msg->can_id & QEMU_CAN_EFF_MASK, + msg->can_dlc, + msg->can_id & QEMU_CAN_EFF_FLAG ? "EFF" : "SFF", + msg->can_id & QEMU_CAN_RTR_FLAG ? "RTR" : "DAT"); + + for (i = 0; i < msg->can_dlc; i++) { + qemu_log(" %02X", msg->data[i]); + } + qemu_log("\n"); + qemu_log_flush(); + qemu_log_unlock(); +} + +static void can_host_socketcan_read(void *opaque) +{ + CanHostSocketCAN *c = opaque; + CanHostState *ch = CAN_HOST(c); + + /* CAN_READ_BUF_LEN for multiple messages syscall is possible for future */ + c->bufcnt = read(c->fd, c->buf, sizeof(qemu_can_frame)); + if (c->bufcnt < 0) { + warn_report("CAN bus host read failed (%s)", strerror(errno)); + return; + } + + can_bus_client_send(&ch->bus_client, c->buf, 1); + + if (DEBUG_CAN) { + can_host_socketcan_display_msg(c->buf); + } +} + +static int can_host_socketcan_can_receive(CanBusClientState *client) +{ + return 1; +} + +static ssize_t can_host_socketcan_receive(CanBusClientState *client, + const qemu_can_frame *frames, size_t frames_cnt) +{ + CanHostState *ch = container_of(client, CanHostState, bus_client); + CanHostSocketCAN *c = CAN_HOST_SOCKETCAN(ch); + + size_t len = sizeof(qemu_can_frame); + int res; + + if (c->fd < 0) { + return -1; + } + + res = write(c->fd, frames, len); + + if (!res) { + warn_report("[cansocketcan]: write message to host returns zero"); + return -1; + } + + if (res != len) { + if (res < 0) { + warn_report("[cansocketcan]: write to host failed (%s)", + strerror(errno)); + } else { + warn_report("[cansocketcan]: write to host truncated"); + } + return -1; + } + + return 1; +} + +static void can_host_socketcan_disconnect(CanHostState *ch) +{ + CanHostSocketCAN *c = CAN_HOST_SOCKETCAN(ch); + + if (c->fd >= 0) { + qemu_set_fd_handler(c->fd, NULL, NULL, c); + close(c->fd); + c->fd = -1; + } + + g_free(c->rfilter); + c->rfilter = NULL; + c->rfilter_num = 0; +} + +static CanBusClientInfo can_host_socketcan_bus_client_info = { + .can_receive = can_host_socketcan_can_receive, + .receive = can_host_socketcan_receive, +}; + +static void can_host_socketcan_connect(CanHostState *ch, Error **errp) +{ + CanHostSocketCAN *c = CAN_HOST_SOCKETCAN(ch); + int s; /* can raw socket */ + struct sockaddr_can addr; + struct ifreq ifr; + + /* open socket */ + s = qemu_socket(PF_CAN, SOCK_RAW, CAN_RAW); + if (s < 0) { + error_setg_errno(errp, errno, "failed to create CAN_RAW socket"); + return; + } + + addr.can_family = AF_CAN; + memset(&ifr.ifr_name, 0, sizeof(ifr.ifr_name)); + strcpy(ifr.ifr_name, c->ifname); + if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) { + error_setg_errno(errp, errno, + "SocketCAN host interface %s not available", c->ifname); + goto fail; + } + addr.can_ifindex = ifr.ifr_ifindex; + + c->err_mask = 0xffffffff; /* Receive error frame. */ + setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, + &c->err_mask, sizeof(c->err_mask)); + + c->rfilter_num = 1; + c->rfilter = g_new(struct qemu_can_filter, c->rfilter_num); + + /* Receive all data frame. If |= CAN_INV_FILTER no data. */ + c->rfilter[0].can_id = 0; + c->rfilter[0].can_mask = 0; + c->rfilter[0].can_mask &= ~CAN_ERR_FLAG; + + setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, c->rfilter, + c->rfilter_num * sizeof(struct qemu_can_filter)); + + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + error_setg_errno(errp, errno, "failed to bind to host interface %s", + c->ifname); + goto fail; + } + + c->fd = s; + ch->bus_client.info = &can_host_socketcan_bus_client_info; + qemu_set_fd_handler(c->fd, can_host_socketcan_read, NULL, c); + return; + +fail: + close(s); + g_free(c->rfilter); + c->rfilter = NULL; + c->rfilter_num = 0; +} + +static char *can_host_socketcan_get_if(Object *obj, Error **errp) +{ + CanHostSocketCAN *c = CAN_HOST_SOCKETCAN(obj); + + return g_strdup(c->ifname); +} + +static void can_host_socketcan_set_if(Object *obj, const char *value, Error **errp) +{ + CanHostSocketCAN *c = CAN_HOST_SOCKETCAN(obj); + struct ifreq ifr; + + if (strlen(value) >= sizeof(ifr.ifr_name)) { + error_setg(errp, "CAN interface name longer than %zd characters", + sizeof(ifr.ifr_name) - 1); + return; + } + + if (c->fd != -1) { + error_setg(errp, "CAN interface already connected"); + return; + } + + g_free(c->ifname); + c->ifname = g_strdup(value); +} + +static void can_host_socketcan_instance_init(Object *obj) +{ + CanHostSocketCAN *c = CAN_HOST_SOCKETCAN(obj); + + c->fd = -1; +} + +static void can_host_socketcan_class_init(ObjectClass *klass, + void *class_data G_GNUC_UNUSED) +{ + CanHostClass *chc = CAN_HOST_CLASS(klass); + + object_class_property_add_str(klass, "if", + can_host_socketcan_get_if, + can_host_socketcan_set_if, + &error_abort); + chc->connect = can_host_socketcan_connect; + chc->disconnect = can_host_socketcan_disconnect; +} + +static const TypeInfo can_host_socketcan_info = { + .parent = TYPE_CAN_HOST, + .name = TYPE_CAN_HOST_SOCKETCAN, + .instance_size = sizeof(CanHostSocketCAN), + .instance_init = can_host_socketcan_instance_init, + .class_init = can_host_socketcan_class_init, +}; + +static void can_host_register_types(void) +{ + type_register_static(&can_host_socketcan_info); +} + +type_init(can_host_register_types); @@ -131,8 +131,6 @@ modules: # If called with only a single argument, will print nothing in quiet mode. quiet-command = $(if $(V),$1,$(if $(2),@printf " %-7s %s\n" $2 $3 && $1, @$1)) -MAKEFLAGS += $(if $(V),,--no-print-directory --quiet) - # cc-option # Usage: CFLAGS+=$(call cc-option, -falign-functions=0, -malign-functions=0) diff --git a/target/i386/hax-all.c b/target/i386/hax-all.c index bc9a12c..cad7531 100644 --- a/target/i386/hax-all.c +++ b/target/i386/hax-all.c @@ -103,6 +103,8 @@ static int hax_get_capability(struct hax_state *hax) return -ENOTSUP; } + hax->supports_64bit_ramblock = !!(cap->winfo & HAX_CAP_64BIT_RAMBLOCK); + if (cap->wstatus & HAX_CAP_MEMQUOTA) { if (cap->mem_quota < hax->mem_quota) { fprintf(stderr, "The VM memory needed exceeds the driver limit.\n"); diff --git a/target/i386/hax-darwin.c b/target/i386/hax-darwin.c index ee94174..acdde47 100644 --- a/target/i386/hax-darwin.c +++ b/target/i386/hax-darwin.c @@ -28,21 +28,36 @@ hax_fd hax_mod_open(void) return fd; } -int hax_populate_ram(uint64_t va, uint32_t size) +int hax_populate_ram(uint64_t va, uint64_t size) { int ret; - struct hax_alloc_ram_info info; if (!hax_global.vm || !hax_global.vm->fd) { fprintf(stderr, "Allocate memory before vm create?\n"); return -EINVAL; } - info.size = size; - info.va = va; - ret = ioctl(hax_global.vm->fd, HAX_VM_IOCTL_ALLOC_RAM, &info); + if (hax_global.supports_64bit_ramblock) { + struct hax_ramblock_info ramblock = { + .start_va = va, + .size = size, + .reserved = 0 + }; + + ret = ioctl(hax_global.vm->fd, HAX_VM_IOCTL_ADD_RAMBLOCK, &ramblock); + } else { + struct hax_alloc_ram_info info = { + .size = (uint32_t)size, + .pad = 0, + .va = va + }; + + ret = ioctl(hax_global.vm->fd, HAX_VM_IOCTL_ALLOC_RAM, &info); + } if (ret < 0) { - fprintf(stderr, "Failed to allocate %x memory\n", size); + fprintf(stderr, "Failed to register RAM block: ret=%d, va=0x%" PRIx64 + ", size=0x%" PRIx64 ", method=%s\n", ret, va, size, + hax_global.supports_64bit_ramblock ? "new" : "legacy"); return ret; } return 0; diff --git a/target/i386/hax-darwin.h b/target/i386/hax-darwin.h index fb8e25a..51af0e8 100644 --- a/target/i386/hax-darwin.h +++ b/target/i386/hax-darwin.h @@ -44,6 +44,7 @@ static inline void hax_close_fd(hax_fd fd) #define HAX_VM_IOCTL_SET_RAM _IOWR(0, 0x82, struct hax_set_ram_info) #define HAX_VM_IOCTL_VCPU_DESTROY _IOW(0, 0x83, uint32_t) #define HAX_VM_IOCTL_NOTIFY_QEMU_VERSION _IOW(0, 0x84, struct hax_qemu_version) +#define HAX_VM_IOCTL_ADD_RAMBLOCK _IOW(0, 0x85, struct hax_ramblock_info) #define HAX_VCPU_IOCTL_RUN _IO(0, 0xc0) #define HAX_VCPU_IOCTL_SET_MSRS _IOWR(0, 0xc1, struct hax_msr_data) diff --git a/target/i386/hax-i386.h b/target/i386/hax-i386.h index 8ffe91f..6abc156 100644 --- a/target/i386/hax-i386.h +++ b/target/i386/hax-i386.h @@ -37,6 +37,7 @@ struct hax_state { uint32_t version; struct hax_vm *vm; uint64_t mem_quota; + bool supports_64bit_ramblock; }; #define HAX_MAX_VCPU 0x10 diff --git a/target/i386/hax-interface.h b/target/i386/hax-interface.h index d141308..93d5fcb 100644 --- a/target/i386/hax-interface.h +++ b/target/i386/hax-interface.h @@ -308,6 +308,13 @@ struct hax_alloc_ram_info { uint32_t pad; uint64_t va; } __attribute__ ((__packed__)); + +struct hax_ramblock_info { + uint64_t start_va; + uint64_t size; + uint64_t reserved; +} __attribute__ ((__packed__)); + #define HAX_RAM_INFO_ROM 0x01 /* Read-Only */ #define HAX_RAM_INFO_INVALID 0x80 /* Unmapped, usually used for MMIO */ struct hax_set_ram_info { @@ -327,6 +334,7 @@ struct hax_set_ram_info { #define HAX_CAP_MEMQUOTA 0x2 #define HAX_CAP_UG 0x4 +#define HAX_CAP_64BIT_RAMBLOCK 0x8 struct hax_capabilityinfo { /* bit 0: 1 - working diff --git a/target/i386/hax-mem.c b/target/i386/hax-mem.c index 27a0d21..f46e855 100644 --- a/target/i386/hax-mem.c +++ b/target/i386/hax-mem.c @@ -174,6 +174,7 @@ static void hax_process_section(MemoryRegionSection *section, uint8_t flags) ram_addr_t size = int128_get64(section->size); unsigned int delta; uint64_t host_va; + uint32_t max_mapping_size; /* We only care about RAM and ROM regions */ if (!memory_region_is_ram(mr)) { @@ -206,10 +207,23 @@ static void hax_process_section(MemoryRegionSection *section, uint8_t flags) flags |= HAX_RAM_INFO_ROM; } - /* the kernel module interface uses 32-bit sizes (but we could split...) */ - g_assert(size <= UINT32_MAX); - - hax_update_mapping(start_pa, size, host_va, flags); + /* + * The kernel module interface uses 32-bit sizes: + * https://github.com/intel/haxm/blob/master/API.md#hax_vm_ioctl_set_ram + * + * If the mapping size is longer than 32 bits, we can't process it in one + * call into the kernel. Instead, we split the mapping into smaller ones, + * and call hax_update_mapping() on each. + */ + max_mapping_size = UINT32_MAX & qemu_real_host_page_mask; + while (size > max_mapping_size) { + hax_update_mapping(start_pa, max_mapping_size, host_va, flags); + start_pa += max_mapping_size; + size -= max_mapping_size; + host_va += max_mapping_size; + } + /* Now size <= max_mapping_size */ + hax_update_mapping(start_pa, (uint32_t)size, host_va, flags); } static void hax_region_add(MemoryListener *listener, @@ -283,12 +297,16 @@ static MemoryListener hax_memory_listener = { static void hax_ram_block_added(RAMBlockNotifier *n, void *host, size_t size) { /* - * In HAX, QEMU allocates the virtual address, and HAX kernel - * populates the memory with physical memory. Currently we have no - * paging, so user should make sure enough free memory in advance. + * We must register each RAM block with the HAXM kernel module, or + * hax_set_ram() will fail for any mapping into the RAM block: + * https://github.com/intel/haxm/blob/master/API.md#hax_vm_ioctl_alloc_ram + * + * Old versions of the HAXM kernel module (< 6.2.0) used to preallocate all + * host physical pages for the RAM block as part of this registration + * process, hence the name hax_populate_ram(). */ if (hax_populate_ram((uint64_t)(uintptr_t)host, size) < 0) { - fprintf(stderr, "HAX failed to populate RAM"); + fprintf(stderr, "HAX failed to populate RAM\n"); abort(); } } diff --git a/target/i386/hax-windows.c b/target/i386/hax-windows.c index 15a180b..b1ac737 100644 --- a/target/i386/hax-windows.c +++ b/target/i386/hax-windows.c @@ -58,10 +58,9 @@ static int hax_open_device(hax_fd *fd) return fd; } -int hax_populate_ram(uint64_t va, uint32_t size) +int hax_populate_ram(uint64_t va, uint64_t size) { int ret; - struct hax_alloc_ram_info info; HANDLE hDeviceVM; DWORD dSize = 0; @@ -70,18 +69,35 @@ int hax_populate_ram(uint64_t va, uint32_t size) return -EINVAL; } - info.size = size; - info.va = va; - hDeviceVM = hax_global.vm->fd; - - ret = DeviceIoControl(hDeviceVM, - HAX_VM_IOCTL_ALLOC_RAM, - &info, sizeof(info), NULL, 0, &dSize, - (LPOVERLAPPED) NULL); + if (hax_global.supports_64bit_ramblock) { + struct hax_ramblock_info ramblock = { + .start_va = va, + .size = size, + .reserved = 0 + }; + + ret = DeviceIoControl(hDeviceVM, + HAX_VM_IOCTL_ADD_RAMBLOCK, + &ramblock, sizeof(ramblock), NULL, 0, &dSize, + (LPOVERLAPPED) NULL); + } else { + struct hax_alloc_ram_info info = { + .size = (uint32_t) size, + .pad = 0, + .va = va + }; + + ret = DeviceIoControl(hDeviceVM, + HAX_VM_IOCTL_ALLOC_RAM, + &info, sizeof(info), NULL, 0, &dSize, + (LPOVERLAPPED) NULL); + } if (!ret) { - fprintf(stderr, "Failed to allocate %x memory\n", size); + fprintf(stderr, "Failed to register RAM block: va=0x%" PRIx64 + ", size=0x%" PRIx64 ", method=%s\n", va, size, + hax_global.supports_64bit_ramblock ? "new" : "legacy"); return ret; } diff --git a/target/i386/hax-windows.h b/target/i386/hax-windows.h index 20e2f85..12cbd81 100644 --- a/target/i386/hax-windows.h +++ b/target/i386/hax-windows.h @@ -57,6 +57,8 @@ static inline int hax_invalid_fd(hax_fd fd) METHOD_BUFFERED, FILE_ANY_ACCESS) #define HAX_VM_IOCTL_VCPU_DESTROY CTL_CODE(HAX_DEVICE_TYPE, 0x905, \ METHOD_BUFFERED, FILE_ANY_ACCESS) +#define HAX_VM_IOCTL_ADD_RAMBLOCK CTL_CODE(HAX_DEVICE_TYPE, 0x913, \ + METHOD_BUFFERED, FILE_ANY_ACCESS) #define HAX_VCPU_IOCTL_RUN CTL_CODE(HAX_DEVICE_TYPE, 0x906, \ METHOD_BUFFERED, FILE_ANY_ACCESS) diff --git a/tests/Makefile.include b/tests/Makefile.include index f41da23..278c13a 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -294,6 +294,7 @@ check-qtest-i386-y += tests/migration-test$(EXESUF) check-qtest-i386-y += tests/test-x86-cpuid-compat$(EXESUF) check-qtest-i386-y += tests/numa-test$(EXESUF) check-qtest-x86_64-y += $(check-qtest-i386-y) +check-qtest-x86_64-y += tests/sdhci-test$(EXESUF) gcov-files-i386-y += i386-softmmu/hw/timer/mc146818rtc.c gcov-files-x86_64-y = $(subst i386-softmmu/,x86_64-softmmu/,$(gcov-files-i386-y)) @@ -367,8 +368,10 @@ gcov-files-arm-y += arm-softmmu/hw/block/virtio-blk.c check-qtest-arm-y += tests/test-arm-mptimer$(EXESUF) gcov-files-arm-y += hw/timer/arm_mptimer.c check-qtest-arm-y += tests/boot-serial-test$(EXESUF) +check-qtest-arm-y += tests/sdhci-test$(EXESUF) check-qtest-aarch64-y = tests/numa-test$(EXESUF) +check-qtest-aarch64-y += tests/sdhci-test$(EXESUF) check-qtest-microblazeel-y = $(check-qtest-microblaze-y) @@ -822,6 +825,7 @@ tests/test-arm-mptimer$(EXESUF): tests/test-arm-mptimer.o tests/test-qapi-util$(EXESUF): tests/test-qapi-util.o $(test-util-obj-y) tests/numa-test$(EXESUF): tests/numa-test.o tests/vmgenid-test$(EXESUF): tests/vmgenid-test.o tests/boot-sector.o tests/acpi-utils.o +tests/sdhci-test$(EXESUF): tests/sdhci-test.o $(libqos-pc-obj-y) tests/migration/stress$(EXESUF): tests/migration/stress.o $(call quiet-command, $(LINKPROG) -static -O3 $(PTHREAD_LIB) -o $@ $< ,"LINK","$(TARGET_DIR)$@") diff --git a/tests/sdhci-test.c b/tests/sdhci-test.c new file mode 100644 index 0000000..493023f --- /dev/null +++ b/tests/sdhci-test.c @@ -0,0 +1,250 @@ +/* + * QTest testcase for SDHCI controllers + * + * Written by Philippe Mathieu-Daudé <f4bug@amsat.org> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "qemu/osdep.h" +#include "hw/registerfields.h" +#include "libqtest.h" +#include "libqos/pci-pc.h" +#include "hw/pci/pci.h" + +#define SDHC_CAPAB 0x40 +FIELD(SDHC_CAPAB, BASECLKFREQ, 8, 8); /* since v2 */ +FIELD(SDHC_CAPAB, SDMA, 22, 1); +FIELD(SDHC_CAPAB, SDR, 32, 3); /* since v3 */ +FIELD(SDHC_CAPAB, DRIVER, 36, 3); /* since v3 */ +#define SDHC_HCVER 0xFE + +static const struct sdhci_t { + const char *arch, *machine; + struct { + uintptr_t addr; + uint8_t version; + uint8_t baseclock; + struct { + bool sdma; + uint64_t reg; + } capab; + } sdhci; + struct { + uint16_t vendor_id, device_id; + } pci; +} models[] = { + /* PC via PCI */ + { "x86_64", "pc", + {-1, 2, 0, {1, 0x057834b4} }, + .pci = { PCI_VENDOR_ID_REDHAT, PCI_DEVICE_ID_REDHAT_SDHCI } }, + + /* Exynos4210 */ + { "arm", "smdkc210", + {0x12510000, 2, 0, {1, 0x5e80080} } }, + + /* i.MX 6 */ + { "arm", "sabrelite", + {0x02190000, 3, 0, {1, 0x057834b4} } }, + + /* BCM2835 */ + { "arm", "raspi2", + {0x3f300000, 3, 52, {0, 0x052134b4} } }, + + /* Zynq-7000 */ + { "arm", "xilinx-zynq-a9", /* Datasheet: UG585 (v1.12.1) */ + {0xe0100000, 2, 0, {1, 0x69ec0080} } }, + + /* ZynqMP */ + { "aarch64", "xlnx-zcu102", /* Datasheet: UG1085 (v1.7) */ + {0xff160000, 3, 0, {1, 0x280737ec6481} } }, + +}; + +typedef struct QSDHCI { + struct { + QPCIBus *bus; + QPCIDevice *dev; + } pci; + union { + QPCIBar mem_bar; + uint64_t addr; + }; +} QSDHCI; + +static uint16_t sdhci_readw(QSDHCI *s, uint32_t reg) +{ + uint16_t val; + + if (s->pci.dev) { + val = qpci_io_readw(s->pci.dev, s->mem_bar, reg); + } else { + val = qtest_readw(global_qtest, s->addr + reg); + } + + return val; +} + +static uint64_t sdhci_readq(QSDHCI *s, uint32_t reg) +{ + uint64_t val; + + if (s->pci.dev) { + val = qpci_io_readq(s->pci.dev, s->mem_bar, reg); + } else { + val = qtest_readq(global_qtest, s->addr + reg); + } + + return val; +} + +static void sdhci_writeq(QSDHCI *s, uint32_t reg, uint64_t val) +{ + if (s->pci.dev) { + qpci_io_writeq(s->pci.dev, s->mem_bar, reg, val); + } else { + qtest_writeq(global_qtest, s->addr + reg, val); + } +} + +static void check_specs_version(QSDHCI *s, uint8_t version) +{ + uint32_t v; + + v = sdhci_readw(s, SDHC_HCVER); + v &= 0xff; + v += 1; + g_assert_cmpuint(v, ==, version); +} + +static void check_capab_capareg(QSDHCI *s, uint64_t expec_capab) +{ + uint64_t capab; + + capab = sdhci_readq(s, SDHC_CAPAB); + g_assert_cmphex(capab, ==, expec_capab); +} + +static void check_capab_readonly(QSDHCI *s) +{ + const uint64_t vrand = 0x123456789abcdef; + uint64_t capab0, capab1; + + capab0 = sdhci_readq(s, SDHC_CAPAB); + g_assert_cmpuint(capab0, !=, vrand); + + sdhci_writeq(s, SDHC_CAPAB, vrand); + capab1 = sdhci_readq(s, SDHC_CAPAB); + g_assert_cmpuint(capab1, !=, vrand); + g_assert_cmpuint(capab1, ==, capab0); +} + +static void check_capab_baseclock(QSDHCI *s, uint8_t expec_freq) +{ + uint64_t capab, capab_freq; + + if (!expec_freq) { + return; + } + capab = sdhci_readq(s, SDHC_CAPAB); + capab_freq = FIELD_EX64(capab, SDHC_CAPAB, BASECLKFREQ); + g_assert_cmpuint(capab_freq, ==, expec_freq); +} + +static void check_capab_sdma(QSDHCI *s, bool supported) +{ + uint64_t capab, capab_sdma; + + capab = sdhci_readq(s, SDHC_CAPAB); + capab_sdma = FIELD_EX64(capab, SDHC_CAPAB, SDMA); + g_assert_cmpuint(capab_sdma, ==, supported); +} + +static void check_capab_v3(QSDHCI *s, uint8_t version) +{ + uint64_t capab, capab_v3; + + if (version < 3) { + /* before v3 those fields are RESERVED */ + capab = sdhci_readq(s, SDHC_CAPAB); + capab_v3 = FIELD_EX64(capab, SDHC_CAPAB, SDR); + g_assert_cmpuint(capab_v3, ==, 0); + capab_v3 = FIELD_EX64(capab, SDHC_CAPAB, DRIVER); + g_assert_cmpuint(capab_v3, ==, 0); + } +} + +static QSDHCI *machine_start(const struct sdhci_t *test) +{ + QSDHCI *s = g_new0(QSDHCI, 1); + + if (test->pci.vendor_id) { + /* PCI */ + uint16_t vendor_id, device_id; + uint64_t barsize; + + global_qtest = qtest_startf("-machine %s -device sdhci-pci", + test->machine); + + s->pci.bus = qpci_init_pc(NULL); + + /* Find PCI device and verify it's the right one */ + s->pci.dev = qpci_device_find(s->pci.bus, QPCI_DEVFN(4, 0)); + g_assert_nonnull(s->pci.dev); + vendor_id = qpci_config_readw(s->pci.dev, PCI_VENDOR_ID); + device_id = qpci_config_readw(s->pci.dev, PCI_DEVICE_ID); + g_assert(vendor_id == test->pci.vendor_id); + g_assert(device_id == test->pci.device_id); + s->mem_bar = qpci_iomap(s->pci.dev, 0, &barsize); + qpci_device_enable(s->pci.dev); + } else { + /* SysBus */ + global_qtest = qtest_startf("-machine %s", test->machine); + s->addr = test->sdhci.addr; + } + + return s; +} + +static void machine_stop(QSDHCI *s) +{ + g_free(s->pci.dev); + qtest_quit(global_qtest); +} + +static void test_machine(const void *data) +{ + const struct sdhci_t *test = data; + QSDHCI *s; + + s = machine_start(test); + + check_specs_version(s, test->sdhci.version); + check_capab_capareg(s, test->sdhci.capab.reg); + check_capab_readonly(s); + check_capab_v3(s, test->sdhci.version); + check_capab_sdma(s, test->sdhci.capab.sdma); + check_capab_baseclock(s, test->sdhci.baseclock); + + machine_stop(s); +} + +int main(int argc, char *argv[]) +{ + const char *arch = qtest_get_arch(); + char *name; + int i; + + g_test_init(&argc, &argv, NULL); + for (i = 0; i < ARRAY_SIZE(models); i++) { + if (strcmp(arch, models[i].arch)) { + continue; + } + name = g_strdup_printf("sdhci/%s", models[i].machine); + qtest_add_data_func(name, &models[i], test_machine); + g_free(name); + } + + return g_test_run(); +} |