diff options
61 files changed, 3651 insertions, 832 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index a334a25e5d..b7c4130388 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -556,7 +556,7 @@ F: hw/misc/arm_sysctl.c Xilinx Zynq M: Edgar E. Iglesias <edgar.iglesias@gmail.com> -M: Alistair Francis <alistair.francis@xilinx.com> +M: Alistair Francis <alistair@alistair23.me> L: qemu-arm@nongnu.org S: Maintained F: hw/*/xilinx_* @@ -566,7 +566,7 @@ F: include/hw/misc/zynq* X: hw/ssi/xilinx_* Xilinx ZynqMP -M: Alistair Francis <alistair.francis@xilinx.com> +M: Alistair Francis <alistair@alistair23.me> M: Edgar E. Iglesias <edgar.iglesias@gmail.com> L: qemu-arm@nongnu.org S: Maintained @@ -1075,7 +1075,7 @@ T: git git://github.com/bonzini/qemu.git scsi-next SSI M: Peter Crosthwaite <crosthwaite.peter@gmail.com> -M: Alistair Francis <alistair.francis@xilinx.com> +M: Alistair Francis <alistair@alistair23.me> S: Maintained F: hw/ssi/* F: hw/block/m25p80.c @@ -1084,7 +1084,7 @@ X: hw/ssi/xilinx_* F: tests/m25p80-test.c Xilinx SPI -M: Alistair Francis <alistair.francis@xilinx.com> +M: Alistair Francis <alistair@alistair23.me> M: Peter Crosthwaite <crosthwaite.peter@gmail.com> S: Maintained F: hw/ssi/xilinx_* @@ -1254,7 +1254,7 @@ S: Maintained F: hw/net/eepro100.c Generic Loader -M: Alistair Francis <alistair.francis@xilinx.com> +M: Alistair Francis <alistair@alistair23.me> S: Maintained F: hw/core/generic-loader.c F: include/hw/core/generic-loader.h @@ -1600,7 +1600,7 @@ F: tests/qmp-test.c T: git git://repo.or.cz/qemu/armbru.git qapi-next Register API -M: Alistair Francis <alistair.francis@xilinx.com> +M: Alistair Francis <alistair@alistair23.me> S: Maintained F: hw/core/register.c F: include/hw/register.h @@ -1639,6 +1639,7 @@ F: include/hw/acpi/tpm.h F: include/sysemu/tpm* F: qapi/tpm.json F: backends/tpm.c +F: tests/*tpm* T: git git://github.com/stefanberger/qemu-tpm.git tpm-next Checkpatch diff --git a/block/nvme.c b/block/nvme.c index 10bffbbf2f..75078022f6 100644 --- a/block/nvme.c +++ b/block/nvme.c @@ -645,7 +645,7 @@ static int nvme_init(BlockDriverState *bs, const char *device, int namespace, aio_set_event_notifier(bdrv_get_aio_context(bs), &s->irq_notifier, false, nvme_handle_event, nvme_poll_cb); - nvme_identify(bs, namespace, errp); + nvme_identify(bs, namespace, &local_err); if (local_err) { error_propagate(errp, local_err); ret = -EIO; @@ -666,8 +666,12 @@ fail_queue: nvme_free_queue_pair(bs, s->queues[0]); fail: g_free(s->queues); - qemu_vfio_pci_unmap_bar(s->vfio, 0, (void *)s->regs, 0, NVME_BAR_SIZE); - qemu_vfio_close(s->vfio); + if (s->regs) { + qemu_vfio_pci_unmap_bar(s->vfio, 0, (void *)s->regs, 0, NVME_BAR_SIZE); + } + if (s->vfio) { + qemu_vfio_close(s->vfio); + } event_notifier_cleanup(&s->irq_notifier); return ret; } diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak index ca34cf4462..54f855d072 100644 --- a/default-configs/arm-softmmu.mak +++ b/default-configs/arm-softmmu.mak @@ -21,6 +21,8 @@ CONFIG_STELLARIS_INPUT=y CONFIG_STELLARIS_ENET=y CONFIG_SSD0303=y CONFIG_SSD0323=y +CONFIG_DDC=y +CONFIG_SII9022=y CONFIG_ADS7846=y CONFIG_MAX111X=y CONFIG_SSI=y diff --git a/hw/arm/vexpress.c b/hw/arm/vexpress.c index dc5928ae1a..9fad79177a 100644 --- a/hw/arm/vexpress.c +++ b/hw/arm/vexpress.c @@ -29,6 +29,7 @@ #include "hw/arm/arm.h" #include "hw/arm/primecell.h" #include "hw/devices.h" +#include "hw/i2c/i2c.h" #include "net/net.h" #include "sysemu/sysemu.h" #include "hw/boards.h" @@ -537,6 +538,7 @@ static void vexpress_common_init(MachineState *machine) uint32_t sys_id; DriveInfo *dinfo; pflash_t *pflash0; + I2CBus *i2c; ram_addr_t vram_size, sram_size; MemoryRegion *sysmem = get_system_memory(); MemoryRegion *vram = g_new(MemoryRegion, 1); @@ -628,7 +630,9 @@ static void vexpress_common_init(MachineState *machine) sysbus_create_simple("sp804", map[VE_TIMER01], pic[2]); sysbus_create_simple("sp804", map[VE_TIMER23], pic[3]); - /* VE_SERIALDVI: not modelled */ + dev = sysbus_create_simple("versatile_i2c", map[VE_SERIALDVI], NULL); + i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c"); + i2c_create_slave(i2c, "sii9022", 0x39); sysbus_create_simple("pl031", map[VE_RTC], pic[4]); /* RTC */ diff --git a/hw/core/loader.c b/hw/core/loader.c index 91669d65aa..c08f130461 100644 --- a/hw/core/loader.c +++ b/hw/core/loader.c @@ -1104,20 +1104,22 @@ int rom_check_and_register_reset(void) if (rom->fw_file) { continue; } - if ((addr > rom->addr) && (as == rom->as)) { - fprintf(stderr, "rom: requested regions overlap " - "(rom %s. free=0x" TARGET_FMT_plx - ", addr=0x" TARGET_FMT_plx ")\n", - rom->name, addr, rom->addr); - return -1; + if (!rom->mr) { + if ((addr > rom->addr) && (as == rom->as)) { + fprintf(stderr, "rom: requested regions overlap " + "(rom %s. free=0x" TARGET_FMT_plx + ", addr=0x" TARGET_FMT_plx ")\n", + rom->name, addr, rom->addr); + return -1; + } + addr = rom->addr; + addr += rom->romsize; + as = rom->as; } - addr = rom->addr; - addr += rom->romsize; section = memory_region_find(rom->mr ? rom->mr : get_system_memory(), rom->addr, 1); rom->isrom = int128_nz(section.size) && memory_region_is_rom(section.mr); memory_region_unref(section.mr); - as = rom->as; } qemu_register_reset(rom_reset, NULL); roms_loaded = 1; diff --git a/hw/core/register.c b/hw/core/register.c index 900294b9c4..0741a1af32 100644 --- a/hw/core/register.c +++ b/hw/core/register.c @@ -159,13 +159,21 @@ uint64_t register_read(RegisterInfo *reg, uint64_t re, const char* prefix, void register_reset(RegisterInfo *reg) { + const RegisterAccessInfo *ac; + g_assert(reg); if (!reg->data || !reg->access) { return; } + ac = reg->access; + register_write_val(reg, reg->access->reset); + + if (ac->post_write) { + ac->post_write(reg, reg->access->reset); + } } void register_init(RegisterInfo *reg) diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs index d3a4cb396e..3c7c75b94d 100644 --- a/hw/display/Makefile.objs +++ b/hw/display/Makefile.objs @@ -3,6 +3,7 @@ common-obj-$(CONFIG_VGA_CIRRUS) += cirrus_vga.o common-obj-$(CONFIG_G364FB) += g364fb.o common-obj-$(CONFIG_JAZZ_LED) += jazz_led.o common-obj-$(CONFIG_PL110) += pl110.o +common-obj-$(CONFIG_SII9022) += sii9022.o common-obj-$(CONFIG_SSD0303) += ssd0303.o common-obj-$(CONFIG_SSD0323) += ssd0323.o common-obj-$(CONFIG_XEN) += xenfb.o diff --git a/hw/display/sii9022.c b/hw/display/sii9022.c new file mode 100644 index 0000000000..eaf11a6e7b --- /dev/null +++ b/hw/display/sii9022.c @@ -0,0 +1,191 @@ +/* + * Silicon Image SiI9022 + * + * This is a pretty hollow emulation: all we do is acknowledge that we + * exist (chip ID) and confirm that we get switched over into DDC mode + * so the emulated host can proceed to read out EDID data. All subsequent + * set-up of connectors etc will be acknowledged and ignored. + * + * Copyright (C) 2018 Linus Walleij + * + * 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 "qemu-common.h" +#include "hw/i2c/i2c.h" +#include "hw/i2c/i2c-ddc.h" +#include "trace.h" + +#define SII9022_SYS_CTRL_DATA 0x1a +#define SII9022_SYS_CTRL_PWR_DWN 0x10 +#define SII9022_SYS_CTRL_AV_MUTE 0x08 +#define SII9022_SYS_CTRL_DDC_BUS_REQ 0x04 +#define SII9022_SYS_CTRL_DDC_BUS_GRTD 0x02 +#define SII9022_SYS_CTRL_OUTPUT_MODE 0x01 +#define SII9022_SYS_CTRL_OUTPUT_HDMI 1 +#define SII9022_SYS_CTRL_OUTPUT_DVI 0 +#define SII9022_REG_CHIPID 0x1b +#define SII9022_INT_ENABLE 0x3c +#define SII9022_INT_STATUS 0x3d +#define SII9022_INT_STATUS_HOTPLUG 0x01; +#define SII9022_INT_STATUS_PLUGGED 0x04; + +#define TYPE_SII9022 "sii9022" +#define SII9022(obj) OBJECT_CHECK(sii9022_state, (obj), TYPE_SII9022) + +typedef struct sii9022_state { + I2CSlave parent_obj; + uint8_t ptr; + bool addr_byte; + bool ddc_req; + bool ddc_skip_finish; + bool ddc; +} sii9022_state; + +static const VMStateDescription vmstate_sii9022 = { + .name = "sii9022", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_I2C_SLAVE(parent_obj, sii9022_state), + VMSTATE_UINT8(ptr, sii9022_state), + VMSTATE_BOOL(addr_byte, sii9022_state), + VMSTATE_BOOL(ddc_req, sii9022_state), + VMSTATE_BOOL(ddc_skip_finish, sii9022_state), + VMSTATE_BOOL(ddc, sii9022_state), + VMSTATE_END_OF_LIST() + } +}; + +static int sii9022_event(I2CSlave *i2c, enum i2c_event event) +{ + sii9022_state *s = SII9022(i2c); + + switch (event) { + case I2C_START_SEND: + s->addr_byte = true; + break; + case I2C_START_RECV: + break; + case I2C_FINISH: + break; + case I2C_NACK: + break; + } + + return 0; +} + +static int sii9022_rx(I2CSlave *i2c) +{ + sii9022_state *s = SII9022(i2c); + uint8_t res = 0x00; + + switch (s->ptr) { + case SII9022_SYS_CTRL_DATA: + if (s->ddc_req) { + /* Acknowledge DDC bus request */ + res = SII9022_SYS_CTRL_DDC_BUS_GRTD | SII9022_SYS_CTRL_DDC_BUS_REQ; + } + break; + case SII9022_REG_CHIPID: + res = 0xb0; + break; + case SII9022_INT_STATUS: + /* Something is cold-plugged in, no interrupts */ + res = SII9022_INT_STATUS_PLUGGED; + break; + default: + break; + } + + trace_sii9022_read_reg(s->ptr, res); + s->ptr++; + + return res; +} + +static int sii9022_tx(I2CSlave *i2c, uint8_t data) +{ + sii9022_state *s = SII9022(i2c); + + if (s->addr_byte) { + s->ptr = data; + s->addr_byte = false; + return 0; + } + + switch (s->ptr) { + case SII9022_SYS_CTRL_DATA: + if (data & SII9022_SYS_CTRL_DDC_BUS_REQ) { + s->ddc_req = true; + if (data & SII9022_SYS_CTRL_DDC_BUS_GRTD) { + s->ddc = true; + /* Skip this finish since we just switched to DDC */ + s->ddc_skip_finish = true; + trace_sii9022_switch_mode("DDC"); + } + } else { + s->ddc_req = false; + s->ddc = false; + trace_sii9022_switch_mode("normal"); + } + break; + default: + break; + } + + trace_sii9022_write_reg(s->ptr, data); + s->ptr++; + + return 0; +} + +static void sii9022_reset(DeviceState *dev) +{ + sii9022_state *s = SII9022(dev); + + s->ptr = 0; + s->addr_byte = false; + s->ddc_req = false; + s->ddc_skip_finish = false; + s->ddc = false; +} + +static void sii9022_realize(DeviceState *dev, Error **errp) +{ + I2CBus *bus; + + bus = I2C_BUS(qdev_get_parent_bus(dev)); + i2c_create_slave(bus, TYPE_I2CDDC, 0x50); +} + +static void sii9022_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); + + k->event = sii9022_event; + k->recv = sii9022_rx; + k->send = sii9022_tx; + dc->reset = sii9022_reset; + dc->realize = sii9022_realize; + dc->vmsd = &vmstate_sii9022; +} + +static const TypeInfo sii9022_info = { + .name = TYPE_SII9022, + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(sii9022_state), + .class_init = sii9022_class_init, +}; + +static void sii9022_register_types(void) +{ + type_register_static(&sii9022_info); +} + +type_init(sii9022_register_types) diff --git a/hw/display/trace-events b/hw/display/trace-events index da498c1def..5a48c6cb6a 100644 --- a/hw/display/trace-events +++ b/hw/display/trace-events @@ -132,3 +132,8 @@ vga_cirrus_read_io(uint32_t addr, uint32_t val) "addr 0x%x, val 0x%x" vga_cirrus_write_io(uint32_t addr, uint32_t val) "addr 0x%x, val 0x%x" vga_cirrus_read_blt(uint32_t offset, uint32_t val) "offset 0x%x, val 0x%x" vga_cirrus_write_blt(uint32_t offset, uint32_t val) "offset 0x%x, val 0x%x" + +# hw/display/sii9022.c +sii9022_read_reg(uint8_t addr, uint8_t val) "addr 0x%02x, val 0x%02x" +sii9022_write_reg(uint8_t addr, uint8_t val) "addr 0x%02x, val 0x%02x" +sii9022_switch_mode(const char *mode) "mode: %s" diff --git a/hw/display/virtio-gpu-3d.c b/hw/display/virtio-gpu-3d.c index 7db84efe89..3558f38fe8 100644 --- a/hw/display/virtio-gpu-3d.c +++ b/hw/display/virtio-gpu-3d.c @@ -362,6 +362,11 @@ static void virgl_cmd_get_capset_info(VirtIOGPU *g, virgl_renderer_get_cap_set(resp.capset_id, &resp.capset_max_version, &resp.capset_max_size); + } else if (info.capset_index == 1) { + resp.capset_id = VIRTIO_GPU_CAPSET_VIRGL2; + virgl_renderer_get_cap_set(resp.capset_id, + &resp.capset_max_version, + &resp.capset_max_size); } else { resp.capset_max_version = 0; resp.capset_max_size = 0; @@ -635,4 +640,14 @@ int virtio_gpu_virgl_init(VirtIOGPU *g) return 0; } +int virtio_gpu_virgl_get_num_capsets(VirtIOGPU *g) +{ + uint32_t capset2_max_ver, capset2_max_size; + virgl_renderer_get_cap_set(VIRTIO_GPU_CAPSET_VIRGL2, + &capset2_max_ver, + &capset2_max_size); + + return capset2_max_ver ? 2 : 1; +} + #endif /* CONFIG_VIRGL */ diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c index 6658f6c6a6..2dd3c3481a 100644 --- a/hw/display/virtio-gpu.c +++ b/hw/display/virtio-gpu.c @@ -1215,7 +1215,12 @@ static void virtio_gpu_device_realize(DeviceState *qdev, Error **errp) /* use larger control queue in 3d mode */ g->ctrl_vq = virtio_add_queue(vdev, 256, virtio_gpu_handle_ctrl_cb); g->cursor_vq = virtio_add_queue(vdev, 16, virtio_gpu_handle_cursor_cb); - g->virtio_config.num_capsets = 1; + +#if defined(CONFIG_VIRGL) + g->virtio_config.num_capsets = virtio_gpu_virgl_get_num_capsets(g); +#else + g->virtio_config.num_capsets = 0; +#endif } else { g->ctrl_vq = virtio_add_queue(vdev, 64, virtio_gpu_handle_ctrl_cb); g->cursor_vq = virtio_add_queue(vdev, 16, virtio_gpu_handle_cursor_cb); diff --git a/hw/i2c/core.c b/hw/i2c/core.c index 59068f157e..cfccefca3d 100644 --- a/hw/i2c/core.c +++ b/hw/i2c/core.c @@ -10,31 +10,13 @@ #include "qemu/osdep.h" #include "hw/i2c/i2c.h" -typedef struct I2CNode I2CNode; - -struct I2CNode { - I2CSlave *elt; - QLIST_ENTRY(I2CNode) next; -}; - #define I2C_BROADCAST 0x00 -struct I2CBus -{ - BusState qbus; - QLIST_HEAD(, I2CNode) current_devs; - uint8_t saved_address; - bool broadcast; -}; - static Property i2c_props[] = { DEFINE_PROP_UINT8("address", struct I2CSlave, address, 0), DEFINE_PROP_END_OF_LIST(), }; -#define TYPE_I2C_BUS "i2c-bus" -#define I2C_BUS(obj) OBJECT_CHECK(I2CBus, (obj), TYPE_I2C_BUS) - static const TypeInfo i2c_bus_info = { .name = TYPE_I2C_BUS, .parent = TYPE_BUS, diff --git a/hw/i2c/i2c-ddc.c b/hw/i2c/i2c-ddc.c index 199dac9e41..bec0c91e2d 100644 --- a/hw/i2c/i2c-ddc.c +++ b/hw/i2c/i2c-ddc.c @@ -259,12 +259,12 @@ static int i2c_ddc_tx(I2CSlave *i2c, uint8_t data) s->reg = data; s->firstbyte = false; DPRINTF("[EDID] Written new pointer: %u\n", data); - return 1; + return 0; } /* Ignore all writes */ s->reg++; - return 1; + return 0; } static void i2c_ddc_init(Object *obj) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index 8af36ca3d4..426f971311 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -223,7 +223,7 @@ static void xilinx_spips_update_cs(XilinxSPIPS *s, int field) { int i; - for (i = 0; i < s->num_cs; i++) { + for (i = 0; i < s->num_cs * s->num_busses; i++) { bool old_state = s->cs_lines_state[i]; bool new_state = field & (1 << i); @@ -234,7 +234,7 @@ static void xilinx_spips_update_cs(XilinxSPIPS *s, int field) } qemu_set_irq(s->cs_lines[i], !new_state); } - if (!(field & ((1 << s->num_cs) - 1))) { + if (!(field & ((1 << (s->num_cs * s->num_busses)) - 1))) { s->snoop_state = SNOOP_CHECKING; s->cmd_dummies = 0; s->link_state = 1; @@ -248,7 +248,40 @@ static void xlnx_zynqmp_qspips_update_cs_lines(XlnxZynqMPQSPIPS *s) { if (s->regs[R_GQSPI_GF_SNAPSHOT]) { int field = ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, CHIP_SELECT); - xilinx_spips_update_cs(XILINX_SPIPS(s), field); + bool upper_cs_sel = field & (1 << 1); + bool lower_cs_sel = field & 1; + bool bus0_enabled; + bool bus1_enabled; + uint8_t buses; + int cs = 0; + + buses = ARRAY_FIELD_EX32(s->regs, GQSPI_GF_SNAPSHOT, DATA_BUS_SELECT); + bus0_enabled = buses & 1; + bus1_enabled = buses & (1 << 1); + + if (bus0_enabled && bus1_enabled) { + if (lower_cs_sel) { + cs |= 1; + } + if (upper_cs_sel) { + cs |= 1 << 3; + } + } else if (bus0_enabled) { + if (lower_cs_sel) { + cs |= 1; + } + if (upper_cs_sel) { + cs |= 1 << 1; + } + } else if (bus1_enabled) { + if (lower_cs_sel) { + cs |= 1 << 2; + } + if (upper_cs_sel) { + cs |= 1 << 3; + } + } + xilinx_spips_update_cs(XILINX_SPIPS(s), cs); } } @@ -260,7 +293,7 @@ static void xilinx_spips_update_cs_lines(XilinxSPIPS *s) if (num_effective_busses(s) == 2) { /* Single bit chip-select for qspi */ field &= 0x1; - field |= field << 1; + field |= field << 3; /* Dual stack U-Page */ } else if (s->regs[R_LQSPI_CFG] & LQSPI_CFG_TWO_MEM && s->regs[R_LQSPI_STS] & LQSPI_CFG_U_PAGE) { @@ -544,7 +577,7 @@ static int xilinx_spips_num_dummies(XilinxQSPIPS *qs, uint8_t command) return 2; case QIOR: case QIOR_4: - return 5; + return 4; default: return -1; } diff --git a/hw/tpm/tpm_tis.c b/hw/tpm/tpm_tis.c index f81168a7e3..834eef75fa 100644 --- a/hw/tpm/tpm_tis.c +++ b/hw/tpm/tpm_tis.c @@ -92,107 +92,6 @@ typedef struct TPMState { } \ } while (0) -/* tis registers */ -#define TPM_TIS_REG_ACCESS 0x00 -#define TPM_TIS_REG_INT_ENABLE 0x08 -#define TPM_TIS_REG_INT_VECTOR 0x0c -#define TPM_TIS_REG_INT_STATUS 0x10 -#define TPM_TIS_REG_INTF_CAPABILITY 0x14 -#define TPM_TIS_REG_STS 0x18 -#define TPM_TIS_REG_DATA_FIFO 0x24 -#define TPM_TIS_REG_INTERFACE_ID 0x30 -#define TPM_TIS_REG_DATA_XFIFO 0x80 -#define TPM_TIS_REG_DATA_XFIFO_END 0xbc -#define TPM_TIS_REG_DID_VID 0xf00 -#define TPM_TIS_REG_RID 0xf04 - -/* vendor-specific registers */ -#define TPM_TIS_REG_DEBUG 0xf90 - -#define TPM_TIS_STS_TPM_FAMILY_MASK (0x3 << 26)/* TPM 2.0 */ -#define TPM_TIS_STS_TPM_FAMILY1_2 (0 << 26) /* TPM 2.0 */ -#define TPM_TIS_STS_TPM_FAMILY2_0 (1 << 26) /* TPM 2.0 */ -#define TPM_TIS_STS_RESET_ESTABLISHMENT_BIT (1 << 25) /* TPM 2.0 */ -#define TPM_TIS_STS_COMMAND_CANCEL (1 << 24) /* TPM 2.0 */ - -#define TPM_TIS_STS_VALID (1 << 7) -#define TPM_TIS_STS_COMMAND_READY (1 << 6) -#define TPM_TIS_STS_TPM_GO (1 << 5) -#define TPM_TIS_STS_DATA_AVAILABLE (1 << 4) -#define TPM_TIS_STS_EXPECT (1 << 3) -#define TPM_TIS_STS_SELFTEST_DONE (1 << 2) -#define TPM_TIS_STS_RESPONSE_RETRY (1 << 1) - -#define TPM_TIS_BURST_COUNT_SHIFT 8 -#define TPM_TIS_BURST_COUNT(X) \ - ((X) << TPM_TIS_BURST_COUNT_SHIFT) - -#define TPM_TIS_ACCESS_TPM_REG_VALID_STS (1 << 7) -#define TPM_TIS_ACCESS_ACTIVE_LOCALITY (1 << 5) -#define TPM_TIS_ACCESS_BEEN_SEIZED (1 << 4) -#define TPM_TIS_ACCESS_SEIZE (1 << 3) -#define TPM_TIS_ACCESS_PENDING_REQUEST (1 << 2) -#define TPM_TIS_ACCESS_REQUEST_USE (1 << 1) -#define TPM_TIS_ACCESS_TPM_ESTABLISHMENT (1 << 0) - -#define TPM_TIS_INT_ENABLED (1 << 31) -#define TPM_TIS_INT_DATA_AVAILABLE (1 << 0) -#define TPM_TIS_INT_STS_VALID (1 << 1) -#define TPM_TIS_INT_LOCALITY_CHANGED (1 << 2) -#define TPM_TIS_INT_COMMAND_READY (1 << 7) - -#define TPM_TIS_INT_POLARITY_MASK (3 << 3) -#define TPM_TIS_INT_POLARITY_LOW_LEVEL (1 << 3) - -#define TPM_TIS_INTERRUPTS_SUPPORTED (TPM_TIS_INT_LOCALITY_CHANGED | \ - TPM_TIS_INT_DATA_AVAILABLE | \ - TPM_TIS_INT_STS_VALID | \ - TPM_TIS_INT_COMMAND_READY) - -#define TPM_TIS_CAP_INTERFACE_VERSION1_3 (2 << 28) -#define TPM_TIS_CAP_INTERFACE_VERSION1_3_FOR_TPM2_0 (3 << 28) -#define TPM_TIS_CAP_DATA_TRANSFER_64B (3 << 9) -#define TPM_TIS_CAP_DATA_TRANSFER_LEGACY (0 << 9) -#define TPM_TIS_CAP_BURST_COUNT_DYNAMIC (0 << 8) -#define TPM_TIS_CAP_INTERRUPT_LOW_LEVEL (1 << 4) /* support is mandatory */ -#define TPM_TIS_CAPABILITIES_SUPPORTED1_3 \ - (TPM_TIS_CAP_INTERRUPT_LOW_LEVEL | \ - TPM_TIS_CAP_BURST_COUNT_DYNAMIC | \ - TPM_TIS_CAP_DATA_TRANSFER_64B | \ - TPM_TIS_CAP_INTERFACE_VERSION1_3 | \ - TPM_TIS_INTERRUPTS_SUPPORTED) - -#define TPM_TIS_CAPABILITIES_SUPPORTED2_0 \ - (TPM_TIS_CAP_INTERRUPT_LOW_LEVEL | \ - TPM_TIS_CAP_BURST_COUNT_DYNAMIC | \ - TPM_TIS_CAP_DATA_TRANSFER_64B | \ - TPM_TIS_CAP_INTERFACE_VERSION1_3_FOR_TPM2_0 | \ - TPM_TIS_INTERRUPTS_SUPPORTED) - -#define TPM_TIS_IFACE_ID_INTERFACE_TIS1_3 (0xf) /* TPM 2.0 */ -#define TPM_TIS_IFACE_ID_INTERFACE_FIFO (0x0) /* TPM 2.0 */ -#define TPM_TIS_IFACE_ID_INTERFACE_VER_FIFO (0 << 4) /* TPM 2.0 */ -#define TPM_TIS_IFACE_ID_CAP_5_LOCALITIES (1 << 8) /* TPM 2.0 */ -#define TPM_TIS_IFACE_ID_CAP_TIS_SUPPORTED (1 << 13) /* TPM 2.0 */ -#define TPM_TIS_IFACE_ID_INT_SEL_LOCK (1 << 19) /* TPM 2.0 */ - -#define TPM_TIS_IFACE_ID_SUPPORTED_FLAGS1_3 \ - (TPM_TIS_IFACE_ID_INTERFACE_TIS1_3 | \ - (~0u << 4)/* all of it is don't care */) - -/* if backend was a TPM 2.0: */ -#define TPM_TIS_IFACE_ID_SUPPORTED_FLAGS2_0 \ - (TPM_TIS_IFACE_ID_INTERFACE_FIFO | \ - TPM_TIS_IFACE_ID_INTERFACE_VER_FIFO | \ - TPM_TIS_IFACE_ID_CAP_5_LOCALITIES | \ - TPM_TIS_IFACE_ID_CAP_TIS_SUPPORTED) - -#define TPM_TIS_TPM_DID 0x0001 -#define TPM_TIS_TPM_VID PCI_VENDOR_ID_IBM -#define TPM_TIS_TPM_RID 0x0001 - -#define TPM_TIS_NO_DATA_BYTE 0xff - /* local prototypes */ static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr, diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c index 94c2e94f10..6ecf70a79b 100644 --- a/hw/usb/dev-mtp.c +++ b/hw/usb/dev-mtp.c @@ -46,6 +46,9 @@ enum mtp_code { CMD_GET_OBJECT_HANDLES = 0x1007, CMD_GET_OBJECT_INFO = 0x1008, CMD_GET_OBJECT = 0x1009, + CMD_DELETE_OBJECT = 0x100b, + CMD_SEND_OBJECT_INFO = 0x100c, + CMD_SEND_OBJECT = 0x100d, CMD_GET_PARTIAL_OBJECT = 0x101b, CMD_GET_OBJECT_PROPS_SUPPORTED = 0x9801, CMD_GET_OBJECT_PROP_DESC = 0x9802, @@ -62,7 +65,13 @@ enum mtp_code { RES_INVALID_STORAGE_ID = 0x2008, RES_INVALID_OBJECT_HANDLE = 0x2009, RES_INVALID_OBJECT_FORMAT_CODE = 0x200b, + RES_STORE_FULL = 0x200c, + RES_STORE_READ_ONLY = 0x200e, + RES_PARTIAL_DELETE = 0x2012, + RES_STORE_NOT_AVAILABLE = 0x2013, RES_SPEC_BY_FORMAT_UNSUPPORTED = 0x2014, + RES_INVALID_OBJECTINFO = 0x2015, + RES_DESTINATION_UNSUPPORTED = 0x2020, RES_INVALID_PARENT_OBJECT = 0x201a, RES_INVALID_PARAMETER = 0x201d, RES_SESSION_ALREADY_OPEN = 0x201e, @@ -172,6 +181,7 @@ struct MTPState { MTPControl *result; uint32_t session; uint32_t next_handle; + bool readonly; QTAILQ_HEAD(, MTPObject) objects; #ifdef CONFIG_INOTIFY1 @@ -179,8 +189,44 @@ struct MTPState { int inotifyfd; QTAILQ_HEAD(events, MTPMonEntry) events; #endif + /* Responder is expecting a write operation */ + bool write_pending; + struct { + uint32_t parent_handle; + uint16_t format; + uint32_t size; + char *filename; + } dataset; }; +/* + * ObjectInfo dataset received from initiator + * Fields we don't care about are ignored + */ +typedef struct { + uint32_t storage_id; /*unused*/ + uint16_t format; + uint16_t protection_status; /*unused*/ + uint32_t size; + uint16_t thumb_format; /*unused*/ + uint32_t thumb_comp_sz; /*unused*/ + uint32_t thumb_pix_width; /*unused*/ + uint32_t thumb_pix_height; /*unused*/ + uint32_t image_pix_width; /*unused*/ + uint32_t image_pix_height; /*unused*/ + uint32_t image_bit_depth; /*unused*/ + uint32_t parent; /*unused*/ + uint16_t assoc_type; + uint32_t assoc_desc; + uint32_t seq_no; /*unused*/ + uint8_t length; /*part of filename field*/ + uint16_t filename[0]; + char date_created[0]; /*unused*/ + char date_modified[0]; /*unused*/ + char keywords[0]; /*unused*/ + /* string and other data follows */ +} QEMU_PACKED ObjectInfo; + #define TYPE_USB_MTP "usb-mtp" #define USB_MTP(obj) OBJECT_CHECK(MTPState, (obj), TYPE_USB_MTP) @@ -422,7 +468,6 @@ static MTPObject *usb_mtp_add_child(MTPState *s, MTPObject *o, return child; } -#ifdef CONFIG_INOTIFY1 static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent, char *name, int len) { @@ -437,6 +482,7 @@ static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent, return NULL; } +#ifdef CONFIG_INOTIFY1 static MTPObject *usb_mtp_object_lookup_wd(MTPState *s, int wd) { MTPObject *iter; @@ -540,9 +586,8 @@ static void inotify_watchfn(void *arg) break; case IN_IGNORED: - o = usb_mtp_object_lookup_name(parent, event->name, event->len); - trace_usb_mtp_inotify_event(s->dev.addr, o->path, - event->mask, "Obj ignored"); + trace_usb_mtp_inotify_event(s->dev.addr, parent->path, + event->mask, "Obj parent dir ignored"); break; default: @@ -765,7 +810,8 @@ static void usb_mtp_add_time(MTPData *data, time_t time) /* ----------------------------------------------------------------------- */ static void usb_mtp_queue_result(MTPState *s, uint16_t code, uint32_t trans, - int argc, uint32_t arg0, uint32_t arg1) + int argc, uint32_t arg0, uint32_t arg1, + uint32_t arg2) { MTPControl *c = g_new0(MTPControl, 1); @@ -778,6 +824,9 @@ static void usb_mtp_queue_result(MTPState *s, uint16_t code, uint32_t trans, if (argc > 1) { c->argv[1] = arg1; } + if (argc > 2) { + c->argv[2] = arg2; + } assert(s->result == NULL); s->result = c; @@ -796,6 +845,9 @@ static MTPData *usb_mtp_get_device_info(MTPState *s, MTPControl *c) CMD_GET_NUM_OBJECTS, CMD_GET_OBJECT_HANDLES, CMD_GET_OBJECT_INFO, + CMD_DELETE_OBJECT, + CMD_SEND_OBJECT_INFO, + CMD_SEND_OBJECT, CMD_GET_OBJECT, CMD_GET_PARTIAL_OBJECT, CMD_GET_OBJECT_PROPS_SUPPORTED, @@ -1110,16 +1162,126 @@ static MTPData *usb_mtp_get_object_prop_value(MTPState *s, MTPControl *c, return d; } +/* Return correct return code for a delete event */ +enum { + ALL_DELETE, + PARTIAL_DELETE, + READ_ONLY, +}; + +/* Assumes that children, if any, have been already freed */ +static void usb_mtp_object_free_one(MTPState *s, MTPObject *o) +{ +#ifndef CONFIG_INOTIFY1 + assert(o->nchildren == 0); + QTAILQ_REMOVE(&s->objects, o, next); + g_free(o->name); + g_free(o->path); + g_free(o); +#endif +} + +static int usb_mtp_deletefn(MTPState *s, MTPObject *o, uint32_t trans) +{ + MTPObject *iter, *iter2; + bool partial_delete = false; + bool success = false; + + /* + * TODO: Add support for Protection Status + */ + + QLIST_FOREACH(iter, &o->children, list) { + if (iter->format == FMT_ASSOCIATION) { + QLIST_FOREACH(iter2, &iter->children, list) { + usb_mtp_deletefn(s, iter2, trans); + } + } + } + + if (o->format == FMT_UNDEFINED_OBJECT) { + if (remove(o->path)) { + partial_delete = true; + } else { + usb_mtp_object_free_one(s, o); + success = true; + } + } + + if (o->format == FMT_ASSOCIATION) { + if (rmdir(o->path)) { + partial_delete = true; + } else { + usb_mtp_object_free_one(s, o); + success = true; + } + } + + if (success && partial_delete) { + return PARTIAL_DELETE; + } + if (!success && partial_delete) { + return READ_ONLY; + } + return ALL_DELETE; +} + +static void usb_mtp_object_delete(MTPState *s, uint32_t handle, + uint32_t format_code, uint32_t trans) +{ + MTPObject *o; + int ret; + + /* Return error if store is read-only */ + if (!FLAG_SET(s, MTP_FLAG_WRITABLE)) { + usb_mtp_queue_result(s, RES_STORE_READ_ONLY, + trans, 0, 0, 0, 0); + return; + } + + if (format_code != 0) { + usb_mtp_queue_result(s, RES_SPEC_BY_FORMAT_UNSUPPORTED, + trans, 0, 0, 0, 0); + return; + } + + if (handle == 0xFFFFFFF) { + o = QTAILQ_FIRST(&s->objects); + } else { + o = usb_mtp_object_lookup(s, handle); + } + if (o == NULL) { + usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, + trans, 0, 0, 0, 0); + return; + } + + ret = usb_mtp_deletefn(s, o, trans); + if (ret == PARTIAL_DELETE) { + usb_mtp_queue_result(s, RES_PARTIAL_DELETE, + trans, 0, 0, 0, 0); + return; + } else if (ret == READ_ONLY) { + usb_mtp_queue_result(s, RES_STORE_READ_ONLY, trans, + 0, 0, 0, 0); + return; + } else { + usb_mtp_queue_result(s, RES_OK, trans, + 0, 0, 0, 0); + return; + } +} + static void usb_mtp_command(MTPState *s, MTPControl *c) { MTPData *data_in = NULL; - MTPObject *o; + MTPObject *o = NULL; uint32_t nres = 0, res0 = 0; /* sanity checks */ if (c->code >= CMD_CLOSE_SESSION && s->session == 0) { usb_mtp_queue_result(s, RES_SESSION_NOT_OPEN, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } @@ -1131,12 +1293,12 @@ static void usb_mtp_command(MTPState *s, MTPControl *c) case CMD_OPEN_SESSION: if (s->session) { usb_mtp_queue_result(s, RES_SESSION_ALREADY_OPEN, - c->trans, 1, s->session, 0); + c->trans, 1, s->session, 0, 0); return; } if (c->argv[0] == 0) { usb_mtp_queue_result(s, RES_INVALID_PARAMETER, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } trace_usb_mtp_op_open_session(s->dev.addr); @@ -1165,7 +1327,7 @@ static void usb_mtp_command(MTPState *s, MTPControl *c) if (c->argv[0] != QEMU_STORAGE_ID && c->argv[0] != 0xffffffff) { usb_mtp_queue_result(s, RES_INVALID_STORAGE_ID, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } data_in = usb_mtp_get_storage_info(s, c); @@ -1175,12 +1337,12 @@ static void usb_mtp_command(MTPState *s, MTPControl *c) if (c->argv[0] != QEMU_STORAGE_ID && c->argv[0] != 0xffffffff) { usb_mtp_queue_result(s, RES_INVALID_STORAGE_ID, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } if (c->argv[1] != 0x00000000) { usb_mtp_queue_result(s, RES_SPEC_BY_FORMAT_UNSUPPORTED, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } if (c->argv[2] == 0x00000000 || @@ -1191,12 +1353,12 @@ static void usb_mtp_command(MTPState *s, MTPControl *c) } if (o == NULL) { usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } if (o->format != FMT_ASSOCIATION) { usb_mtp_queue_result(s, RES_INVALID_PARENT_OBJECT, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } usb_mtp_object_readdir(s, o); @@ -1212,7 +1374,7 @@ static void usb_mtp_command(MTPState *s, MTPControl *c) o = usb_mtp_object_lookup(s, c->argv[0]); if (o == NULL) { usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } data_in = usb_mtp_get_object_info(s, c, o); @@ -1221,47 +1383,98 @@ static void usb_mtp_command(MTPState *s, MTPControl *c) o = usb_mtp_object_lookup(s, c->argv[0]); if (o == NULL) { usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } if (o->format == FMT_ASSOCIATION) { usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } data_in = usb_mtp_get_object(s, c, o); if (data_in == NULL) { usb_mtp_queue_result(s, RES_GENERAL_ERROR, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } break; + case CMD_DELETE_OBJECT: + usb_mtp_object_delete(s, c->argv[0], c->argv[1], c->trans); + return; case CMD_GET_PARTIAL_OBJECT: o = usb_mtp_object_lookup(s, c->argv[0]); if (o == NULL) { usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } if (o->format == FMT_ASSOCIATION) { usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } data_in = usb_mtp_get_partial_object(s, c, o); if (data_in == NULL) { usb_mtp_queue_result(s, RES_GENERAL_ERROR, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } nres = 1; res0 = data_in->length; break; + case CMD_SEND_OBJECT_INFO: + /* Return error if store is read-only */ + if (!FLAG_SET(s, MTP_FLAG_WRITABLE)) { + usb_mtp_queue_result(s, RES_STORE_READ_ONLY, + c->trans, 0, 0, 0, 0); + } else if (c->argv[0] && (c->argv[0] != QEMU_STORAGE_ID)) { + /* First parameter points to storage id or is 0 */ + usb_mtp_queue_result(s, RES_STORE_NOT_AVAILABLE, c->trans, + 0, 0, 0, 0); + } else if (c->argv[1] && !c->argv[0]) { + /* If second parameter is specified, first must also be specified */ + usb_mtp_queue_result(s, RES_DESTINATION_UNSUPPORTED, c->trans, + 0, 0, 0, 0); + } else { + uint32_t handle = c->argv[1]; + if (handle == 0xFFFFFFFF || handle == 0) { + /* root object */ + o = QTAILQ_FIRST(&s->objects); + } else { + o = usb_mtp_object_lookup(s, handle); + } + if (o == NULL) { + usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, c->trans, + 0, 0, 0, 0); + } + if (o->format != FMT_ASSOCIATION) { + usb_mtp_queue_result(s, RES_INVALID_PARENT_OBJECT, c->trans, + 0, 0, 0, 0); + } + } + if (o) { + s->dataset.parent_handle = o->handle; + } + s->data_out = usb_mtp_data_alloc(c); + return; + case CMD_SEND_OBJECT: + if (!FLAG_SET(s, MTP_FLAG_WRITABLE)) { + usb_mtp_queue_result(s, RES_STORE_READ_ONLY, + c->trans, 0, 0, 0, 0); + return; + } + if (!s->write_pending) { + usb_mtp_queue_result(s, RES_INVALID_OBJECTINFO, + c->trans, 0, 0, 0, 0); + return; + } + s->data_out = usb_mtp_data_alloc(c); + return; case CMD_GET_OBJECT_PROPS_SUPPORTED: if (c->argv[0] != FMT_UNDEFINED_OBJECT && c->argv[0] != FMT_ASSOCIATION) { usb_mtp_queue_result(s, RES_INVALID_OBJECT_FORMAT_CODE, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } data_in = usb_mtp_get_object_props_supported(s, c); @@ -1270,13 +1483,13 @@ static void usb_mtp_command(MTPState *s, MTPControl *c) if (c->argv[1] != FMT_UNDEFINED_OBJECT && c->argv[1] != FMT_ASSOCIATION) { usb_mtp_queue_result(s, RES_INVALID_OBJECT_FORMAT_CODE, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } data_in = usb_mtp_get_object_prop_desc(s, c); if (data_in == NULL) { usb_mtp_queue_result(s, RES_INVALID_OBJECT_PROP_CODE, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } break; @@ -1284,20 +1497,20 @@ static void usb_mtp_command(MTPState *s, MTPControl *c) o = usb_mtp_object_lookup(s, c->argv[0]); if (o == NULL) { usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } data_in = usb_mtp_get_object_prop_value(s, c, o); if (data_in == NULL) { usb_mtp_queue_result(s, RES_INVALID_OBJECT_PROP_CODE, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } break; default: trace_usb_mtp_op_unknown(s->dev.addr, c->code); usb_mtp_queue_result(s, RES_OPERATION_NOT_SUPPORTED, - c->trans, 0, 0, 0); + c->trans, 0, 0, 0, 0); return; } @@ -1306,7 +1519,7 @@ static void usb_mtp_command(MTPState *s, MTPControl *c) assert(s->data_in == NULL); s->data_in = data_in; } - usb_mtp_queue_result(s, RES_OK, c->trans, nres, res0, 0); + usb_mtp_queue_result(s, RES_OK, c->trans, nres, res0, 0, 0); } /* ----------------------------------------------------------------------- */ @@ -1351,12 +1564,191 @@ static void usb_mtp_cancel_packet(USBDevice *dev, USBPacket *p) fprintf(stderr, "%s\n", __func__); } +static void utf16_to_str(uint8_t len, uint16_t *arr, char *name) +{ + int count; + wchar_t *wstr = g_new0(wchar_t, len); + + for (count = 0; count < len; count++) { + wstr[count] = (wchar_t)arr[count]; + } + + wcstombs(name, wstr, len); + g_free(wstr); +} + +static void usb_mtp_write_data(MTPState *s) +{ + MTPData *d = s->data_out; + MTPObject *parent = + usb_mtp_object_lookup(s, s->dataset.parent_handle); + char *path = NULL; + int rc = -1; + mode_t mask = 0644; + + assert(d != NULL); + + if (parent == NULL || !s->write_pending) { + usb_mtp_queue_result(s, RES_INVALID_OBJECTINFO, d->trans, + 0, 0, 0, 0); + return; + } + + if (s->dataset.filename) { + path = g_strdup_printf("%s/%s", parent->path, s->dataset.filename); + if (s->dataset.format == FMT_ASSOCIATION) { + d->fd = mkdir(path, mask); + goto free; + } + if (s->dataset.size < d->length) { + usb_mtp_queue_result(s, RES_STORE_FULL, d->trans, + 0, 0, 0, 0); + goto done; + } + d->fd = open(path, O_CREAT | O_WRONLY, mask); + if (d->fd == -1) { + usb_mtp_queue_result(s, RES_STORE_FULL, d->trans, + 0, 0, 0, 0); + goto done; + } + + /* + * Return success if initiator sent 0 sized data + */ + if (!s->dataset.size) { + goto success; + } + + rc = write(d->fd, d->data, s->dataset.size); + if (rc == -1) { + usb_mtp_queue_result(s, RES_STORE_FULL, d->trans, + 0, 0, 0, 0); + goto done; + } + if (rc != s->dataset.size) { + usb_mtp_queue_result(s, RES_INCOMPLETE_TRANSFER, d->trans, + 0, 0, 0, 0); + goto done; + } + } + +success: + usb_mtp_queue_result(s, RES_OK, d->trans, + 0, 0, 0, 0); + +done: + /* + * The write dataset is kept around and freed only + * on success or if another write request comes in + */ + if (d->fd != -1) { + close(d->fd); + } +free: + g_free(s->dataset.filename); + g_free(path); + s->write_pending = false; +} + +static void usb_mtp_write_metadata(MTPState *s) +{ + MTPData *d = s->data_out; + ObjectInfo *dataset = (ObjectInfo *)d->data; + char *filename = g_new0(char, dataset->length); + MTPObject *o; + MTPObject *p = usb_mtp_object_lookup(s, s->dataset.parent_handle); + uint32_t next_handle = s->next_handle; + + assert(!s->write_pending); + + utf16_to_str(dataset->length, dataset->filename, filename); + + o = usb_mtp_object_lookup_name(p, filename, dataset->length); + if (o != NULL) { + next_handle = o->handle; + } + + s->dataset.filename = filename; + s->dataset.format = dataset->format; + s->dataset.size = dataset->size; + s->dataset.filename = filename; + s->write_pending = true; + + if (s->dataset.format == FMT_ASSOCIATION) { + usb_mtp_write_data(s); + /* next_handle will be allocated to the newly created dir */ + if (d->fd == -1) { + usb_mtp_queue_result(s, RES_STORE_FULL, d->trans, + 0, 0, 0, 0); + return; + } + d->fd = -1; + } + + usb_mtp_queue_result(s, RES_OK, d->trans, 3, QEMU_STORAGE_ID, + s->dataset.parent_handle, next_handle); +} + +static void usb_mtp_get_data(MTPState *s, mtp_container *container, + USBPacket *p) +{ + MTPData *d = s->data_out; + uint64_t dlen; + uint32_t data_len = p->iov.size; + + if (d->first) { + /* Total length of incoming data */ + d->length = cpu_to_le32(container->length) - sizeof(mtp_container); + /* Length of data in this packet */ + data_len -= sizeof(mtp_container); + usb_mtp_realloc(d, d->length); + d->offset = 0; + d->first = false; + } + + if (d->length - d->offset > data_len) { + dlen = data_len; + } else { + dlen = d->length - d->offset; + } + + switch (d->code) { + case CMD_SEND_OBJECT_INFO: + usb_packet_copy(p, d->data + d->offset, dlen); + d->offset += dlen; + if (d->offset == d->length) { + /* The operation might have already failed */ + if (!s->result) { + usb_mtp_write_metadata(s); + } + usb_mtp_data_free(s->data_out); + s->data_out = NULL; + return; + } + break; + case CMD_SEND_OBJECT: + usb_packet_copy(p, d->data + d->offset, dlen); + d->offset += dlen; + if (d->offset == d->length) { + usb_mtp_write_data(s); + usb_mtp_data_free(s->data_out); + s->data_out = NULL; + return; + } + break; + default: + p->status = USB_RET_STALL; + return; + } +} + static void usb_mtp_handle_data(USBDevice *dev, USBPacket *p) { MTPState *s = USB_MTP(dev); MTPControl cmd; mtp_container container; uint32_t params[5]; + uint16_t container_type; int i, rc; switch (p->ep->nr) { @@ -1446,8 +1838,13 @@ static void usb_mtp_handle_data(USBDevice *dev, USBPacket *p) p->status = USB_RET_STALL; return; } - usb_packet_copy(p, &container, sizeof(container)); - switch (le16_to_cpu(container.type)) { + if (s->data_out && !s->data_out->first) { + container_type = TYPE_DATA; + } else { + usb_packet_copy(p, &container, sizeof(container)); + container_type = le16_to_cpu(container.type); + } + switch (container_type) { case TYPE_COMMAND: if (s->data_in || s->data_out || s->result) { trace_usb_mtp_stall(s->dev.addr, "transaction inflight"); @@ -1478,6 +1875,15 @@ static void usb_mtp_handle_data(USBDevice *dev, USBPacket *p) (cmd.argc > 4) ? cmd.argv[4] : 0); usb_mtp_command(s, &cmd); break; + case TYPE_DATA: + /* One of the previous transfers has already errored but the + * responder is still sending data associated with it + */ + if (s->result != NULL) { + return; + } + usb_mtp_get_data(s, &container, p); + break; default: /* not needed as long as the mtp device is read-only */ p->status = USB_RET_STALL; @@ -1542,6 +1948,10 @@ static void usb_mtp_realize(USBDevice *dev, Error **errp) return; } s->desc = strrchr(s->root, '/'); + /* Mark store as RW */ + if (!s->readonly) { + s->flags |= (1 << MTP_FLAG_WRITABLE); + } if (s->desc && s->desc[0]) { s->desc = g_strdup(s->desc + 1); } else { @@ -1564,6 +1974,7 @@ static const VMStateDescription vmstate_usb_mtp = { static Property mtp_properties[] = { DEFINE_PROP_STRING("x-root", MTPState, root), DEFINE_PROP_STRING("desc", MTPState, desc), + DEFINE_PROP_BOOL("readonly", MTPState, readonly, true), DEFINE_PROP_END_OF_LIST(), }; diff --git a/include/elf.h b/include/elf.h index e8a515ce3d..943ee21171 100644 --- a/include/elf.h +++ b/include/elf.h @@ -33,6 +33,9 @@ typedef int64_t Elf64_Sxword; /* Flags in the e_flags field of the header */ /* MIPS architecture level. */ +#define EF_MIPS_ARCH 0xf0000000 + +/* Legal values for MIPS architecture level. */ #define EF_MIPS_ARCH_1 0x00000000 /* -mips1 code. */ #define EF_MIPS_ARCH_2 0x10000000 /* -mips2 code. */ #define EF_MIPS_ARCH_3 0x20000000 /* -mips3 code. */ @@ -40,6 +43,10 @@ typedef int64_t Elf64_Sxword; #define EF_MIPS_ARCH_5 0x40000000 /* -mips5 code. */ #define EF_MIPS_ARCH_32 0x50000000 /* MIPS32 code. */ #define EF_MIPS_ARCH_64 0x60000000 /* MIPS64 code. */ +#define EF_MIPS_ARCH_32R2 0x70000000 /* MIPS32r2 code. */ +#define EF_MIPS_ARCH_64R2 0x80000000 /* MIPS64r2 code. */ +#define EF_MIPS_ARCH_32R6 0x90000000 /* MIPS32r6 code. */ +#define EF_MIPS_ARCH_64R6 0xa0000000 /* MIPS64r6 code. */ /* The ABI of a file. */ #define EF_MIPS_ABI_O32 0x00001000 /* O32 ABI. */ @@ -537,6 +544,34 @@ typedef struct { #define HWCAP_S390_HIGH_GPRS 512 #define HWCAP_S390_TE 1024 +/* M68K specific definitions. */ +/* We use the top 24 bits to encode information about the + architecture variant. */ +#define EF_M68K_CPU32 0x00810000 +#define EF_M68K_M68000 0x01000000 +#define EF_M68K_CFV4E 0x00008000 +#define EF_M68K_FIDO 0x02000000 +#define EF_M68K_ARCH_MASK \ + (EF_M68K_M68000 | EF_M68K_CPU32 | EF_M68K_CFV4E | EF_M68K_FIDO) + +/* We use the bottom 8 bits to encode information about the + coldfire variant. If we use any of these bits, the top 24 bits are + either 0 or EF_M68K_CFV4E. */ +#define EF_M68K_CF_ISA_MASK 0x0F /* Which ISA */ +#define EF_M68K_CF_ISA_A_NODIV 0x01 /* ISA A except for div */ +#define EF_M68K_CF_ISA_A 0x02 +#define EF_M68K_CF_ISA_A_PLUS 0x03 +#define EF_M68K_CF_ISA_B_NOUSP 0x04 /* ISA_B except for USP */ +#define EF_M68K_CF_ISA_B 0x05 +#define EF_M68K_CF_ISA_C 0x06 +#define EF_M68K_CF_ISA_C_NODIV 0x07 /* ISA C except for div */ +#define EF_M68K_CF_MAC_MASK 0x30 +#define EF_M68K_CF_MAC 0x10 /* MAC */ +#define EF_M68K_CF_EMAC 0x20 /* EMAC */ +#define EF_M68K_CF_EMAC_B 0x30 /* EMAC_B */ +#define EF_M68K_CF_FLOAT 0x40 /* Has float insns */ +#define EF_M68K_CF_MASK 0xFF + /* * 68k ELF relocation types */ diff --git a/include/exec/helper-head.h b/include/exec/helper-head.h index e1fd08f2ba..15b6a68de3 100644 --- a/include/exec/helper-head.h +++ b/include/exec/helper-head.h @@ -26,6 +26,7 @@ #define dh_alias_int i32 #define dh_alias_i64 i64 #define dh_alias_s64 i64 +#define dh_alias_f16 i32 #define dh_alias_f32 i32 #define dh_alias_f64 i64 #define dh_alias_ptr ptr @@ -38,6 +39,7 @@ #define dh_ctype_int int #define dh_ctype_i64 uint64_t #define dh_ctype_s64 int64_t +#define dh_ctype_f16 float16 #define dh_ctype_f32 float32 #define dh_ctype_f64 float64 #define dh_ctype_ptr void * @@ -94,6 +96,7 @@ #define dh_is_signed_s32 1 #define dh_is_signed_i64 0 #define dh_is_signed_s64 1 +#define dh_is_signed_f16 0 #define dh_is_signed_f32 0 #define dh_is_signed_f64 0 #define dh_is_signed_tl 0 diff --git a/include/fpu/softfloat.h b/include/fpu/softfloat.h index 9b7b5e34e2..27876e711c 100644 --- a/include/fpu/softfloat.h +++ b/include/fpu/softfloat.h @@ -306,8 +306,11 @@ static inline float16 float16_set_sign(float16 a, int sign) } #define float16_zero make_float16(0) -#define float16_one make_float16(0x3c00) #define float16_half make_float16(0x3800) +#define float16_one make_float16(0x3c00) +#define float16_one_point_five make_float16(0x3e00) +#define float16_two make_float16(0x4000) +#define float16_three make_float16(0x4200) #define float16_infinity make_float16(0x7c00) /*---------------------------------------------------------------------------- @@ -415,11 +418,13 @@ static inline float32 float32_set_sign(float32 a, int sign) } #define float32_zero make_float32(0) -#define float32_one make_float32(0x3f800000) #define float32_half make_float32(0x3f000000) +#define float32_one make_float32(0x3f800000) +#define float32_one_point_five make_float32(0x3fc00000) +#define float32_two make_float32(0x40000000) +#define float32_three make_float32(0x40400000) #define float32_infinity make_float32(0x7f800000) - /*---------------------------------------------------------------------------- | The pattern for a default generated single-precision NaN. *----------------------------------------------------------------------------*/ @@ -526,9 +531,12 @@ static inline float64 float64_set_sign(float64 a, int sign) } #define float64_zero make_float64(0) +#define float64_half make_float64(0x3fe0000000000000LL) #define float64_one make_float64(0x3ff0000000000000LL) +#define float64_one_point_five make_float64(0x3FF8000000000000ULL) +#define float64_two make_float64(0x4000000000000000ULL) +#define float64_three make_float64(0x4008000000000000ULL) #define float64_ln2 make_float64(0x3fe62e42fefa39efLL) -#define float64_half make_float64(0x3fe0000000000000LL) #define float64_infinity make_float64(0x7ff0000000000000LL) /*---------------------------------------------------------------------------- diff --git a/include/hw/acpi/tpm.h b/include/hw/acpi/tpm.h index 96fd3a92f7..46ac4dc581 100644 --- a/include/hw/acpi/tpm.h +++ b/include/hw/acpi/tpm.h @@ -23,6 +23,111 @@ #define TPM_TIS_IRQ 5 +#define TPM_TIS_NUM_LOCALITIES 5 /* per spec */ +#define TPM_TIS_LOCALITY_SHIFT 12 + +/* tis registers */ +#define TPM_TIS_REG_ACCESS 0x00 +#define TPM_TIS_REG_INT_ENABLE 0x08 +#define TPM_TIS_REG_INT_VECTOR 0x0c +#define TPM_TIS_REG_INT_STATUS 0x10 +#define TPM_TIS_REG_INTF_CAPABILITY 0x14 +#define TPM_TIS_REG_STS 0x18 +#define TPM_TIS_REG_DATA_FIFO 0x24 +#define TPM_TIS_REG_INTERFACE_ID 0x30 +#define TPM_TIS_REG_DATA_XFIFO 0x80 +#define TPM_TIS_REG_DATA_XFIFO_END 0xbc +#define TPM_TIS_REG_DID_VID 0xf00 +#define TPM_TIS_REG_RID 0xf04 + +/* vendor-specific registers */ +#define TPM_TIS_REG_DEBUG 0xf90 + +#define TPM_TIS_STS_TPM_FAMILY_MASK (0x3 << 26)/* TPM 2.0 */ +#define TPM_TIS_STS_TPM_FAMILY1_2 (0 << 26) /* TPM 2.0 */ +#define TPM_TIS_STS_TPM_FAMILY2_0 (1 << 26) /* TPM 2.0 */ +#define TPM_TIS_STS_RESET_ESTABLISHMENT_BIT (1 << 25) /* TPM 2.0 */ +#define TPM_TIS_STS_COMMAND_CANCEL (1 << 24) /* TPM 2.0 */ + +#define TPM_TIS_STS_VALID (1 << 7) +#define TPM_TIS_STS_COMMAND_READY (1 << 6) +#define TPM_TIS_STS_TPM_GO (1 << 5) +#define TPM_TIS_STS_DATA_AVAILABLE (1 << 4) +#define TPM_TIS_STS_EXPECT (1 << 3) +#define TPM_TIS_STS_SELFTEST_DONE (1 << 2) +#define TPM_TIS_STS_RESPONSE_RETRY (1 << 1) + +#define TPM_TIS_BURST_COUNT_SHIFT 8 +#define TPM_TIS_BURST_COUNT(X) \ + ((X) << TPM_TIS_BURST_COUNT_SHIFT) + +#define TPM_TIS_ACCESS_TPM_REG_VALID_STS (1 << 7) +#define TPM_TIS_ACCESS_ACTIVE_LOCALITY (1 << 5) +#define TPM_TIS_ACCESS_BEEN_SEIZED (1 << 4) +#define TPM_TIS_ACCESS_SEIZE (1 << 3) +#define TPM_TIS_ACCESS_PENDING_REQUEST (1 << 2) +#define TPM_TIS_ACCESS_REQUEST_USE (1 << 1) +#define TPM_TIS_ACCESS_TPM_ESTABLISHMENT (1 << 0) + +#define TPM_TIS_INT_ENABLED (1 << 31) +#define TPM_TIS_INT_DATA_AVAILABLE (1 << 0) +#define TPM_TIS_INT_STS_VALID (1 << 1) +#define TPM_TIS_INT_LOCALITY_CHANGED (1 << 2) +#define TPM_TIS_INT_COMMAND_READY (1 << 7) + +#define TPM_TIS_INT_POLARITY_MASK (3 << 3) +#define TPM_TIS_INT_POLARITY_LOW_LEVEL (1 << 3) + +#define TPM_TIS_INTERRUPTS_SUPPORTED (TPM_TIS_INT_LOCALITY_CHANGED | \ + TPM_TIS_INT_DATA_AVAILABLE | \ + TPM_TIS_INT_STS_VALID | \ + TPM_TIS_INT_COMMAND_READY) + +#define TPM_TIS_CAP_INTERFACE_VERSION1_3 (2 << 28) +#define TPM_TIS_CAP_INTERFACE_VERSION1_3_FOR_TPM2_0 (3 << 28) +#define TPM_TIS_CAP_DATA_TRANSFER_64B (3 << 9) +#define TPM_TIS_CAP_DATA_TRANSFER_LEGACY (0 << 9) +#define TPM_TIS_CAP_BURST_COUNT_DYNAMIC (0 << 8) +#define TPM_TIS_CAP_INTERRUPT_LOW_LEVEL (1 << 4) /* support is mandatory */ +#define TPM_TIS_CAPABILITIES_SUPPORTED1_3 \ + (TPM_TIS_CAP_INTERRUPT_LOW_LEVEL | \ + TPM_TIS_CAP_BURST_COUNT_DYNAMIC | \ + TPM_TIS_CAP_DATA_TRANSFER_64B | \ + TPM_TIS_CAP_INTERFACE_VERSION1_3 | \ + TPM_TIS_INTERRUPTS_SUPPORTED) + +#define TPM_TIS_CAPABILITIES_SUPPORTED2_0 \ + (TPM_TIS_CAP_INTERRUPT_LOW_LEVEL | \ + TPM_TIS_CAP_BURST_COUNT_DYNAMIC | \ + TPM_TIS_CAP_DATA_TRANSFER_64B | \ + TPM_TIS_CAP_INTERFACE_VERSION1_3_FOR_TPM2_0 | \ + TPM_TIS_INTERRUPTS_SUPPORTED) + +#define TPM_TIS_IFACE_ID_INTERFACE_TIS1_3 (0xf) /* TPM 2.0 */ +#define TPM_TIS_IFACE_ID_INTERFACE_FIFO (0x0) /* TPM 2.0 */ +#define TPM_TIS_IFACE_ID_INTERFACE_VER_FIFO (0 << 4) /* TPM 2.0 */ +#define TPM_TIS_IFACE_ID_CAP_5_LOCALITIES (1 << 8) /* TPM 2.0 */ +#define TPM_TIS_IFACE_ID_CAP_TIS_SUPPORTED (1 << 13) /* TPM 2.0 */ +#define TPM_TIS_IFACE_ID_INT_SEL_LOCK (1 << 19) /* TPM 2.0 */ + +#define TPM_TIS_IFACE_ID_SUPPORTED_FLAGS1_3 \ + (TPM_TIS_IFACE_ID_INTERFACE_TIS1_3 | \ + (~0u << 4)/* all of it is don't care */) + +/* if backend was a TPM 2.0: */ +#define TPM_TIS_IFACE_ID_SUPPORTED_FLAGS2_0 \ + (TPM_TIS_IFACE_ID_INTERFACE_FIFO | \ + TPM_TIS_IFACE_ID_INTERFACE_VER_FIFO | \ + TPM_TIS_IFACE_ID_CAP_5_LOCALITIES | \ + TPM_TIS_IFACE_ID_CAP_TIS_SUPPORTED) + +#define TPM_TIS_TPM_DID 0x0001 +#define TPM_TIS_TPM_VID PCI_VENDOR_ID_IBM +#define TPM_TIS_TPM_RID 0x0001 + +#define TPM_TIS_NO_DATA_BYTE 0xff + + REG32(CRB_LOC_STATE, 0x00) FIELD(CRB_LOC_STATE, tpmEstablished, 0, 1) FIELD(CRB_LOC_STATE, locAssigned, 1, 1) diff --git a/include/hw/i2c/i2c.h b/include/hw/i2c/i2c.h index 24e95d0155..d727379b48 100644 --- a/include/hw/i2c/i2c.h +++ b/include/hw/i2c/i2c.h @@ -25,8 +25,7 @@ typedef struct I2CSlave I2CSlave; #define I2C_SLAVE_GET_CLASS(obj) \ OBJECT_GET_CLASS(I2CSlaveClass, (obj), TYPE_I2C_SLAVE) -typedef struct I2CSlaveClass -{ +typedef struct I2CSlaveClass { DeviceClass parent_class; /* Callbacks provided by the device. */ @@ -50,14 +49,30 @@ typedef struct I2CSlaveClass int (*event)(I2CSlave *s, enum i2c_event event); } I2CSlaveClass; -struct I2CSlave -{ +struct I2CSlave { DeviceState qdev; /* Remaining fields for internal use by the I2C code. */ uint8_t address; }; +#define TYPE_I2C_BUS "i2c-bus" +#define I2C_BUS(obj) OBJECT_CHECK(I2CBus, (obj), TYPE_I2C_BUS) + +typedef struct I2CNode I2CNode; + +struct I2CNode { + I2CSlave *elt; + QLIST_ENTRY(I2CNode) next; +}; + +struct I2CBus { + BusState qbus; + QLIST_HEAD(, I2CNode) current_devs; + uint8_t saved_address; + bool broadcast; +}; + I2CBus *i2c_init_bus(DeviceState *parent, const char *name); void i2c_set_slave_address(I2CSlave *dev, uint8_t address); int i2c_bus_busy(I2CBus *bus); diff --git a/include/hw/register.h b/include/hw/register.h index de2414e6b4..5796584588 100644 --- a/include/hw/register.h +++ b/include/hw/register.h @@ -34,7 +34,7 @@ typedef struct RegisterInfoArray RegisterInfoArray; * immediately before the actual write. The returned value is what is written, * giving the handler a chance to modify the written value. * @post_write: Post write callback. Passed the written value. Most write side - * effects should be implemented here. + * effects should be implemented here. This is called during device reset. * * @post_read: Post read callback. Passes the value that is about to be returned * for a read. The return value from this function is what is ultimately read, @@ -135,8 +135,8 @@ uint64_t register_read(RegisterInfo *reg, uint64_t re, const char* prefix, bool debug); /** - * reset a register - * @reg: register to reset + * Resets a register. This will also call the post_write hook if it exists. + * @reg: The register to reset. */ void register_reset(RegisterInfo *reg); diff --git a/include/hw/virtio/virtio-gpu.h b/include/hw/virtio/virtio-gpu.h index 83f474ffc3..22ac3c2d0e 100644 --- a/include/hw/virtio/virtio-gpu.h +++ b/include/hw/virtio/virtio-gpu.h @@ -171,5 +171,5 @@ void virtio_gpu_virgl_fence_poll(VirtIOGPU *g); void virtio_gpu_virgl_reset(VirtIOGPU *g); void virtio_gpu_gl_block(void *opaque, bool block); int virtio_gpu_virgl_init(VirtIOGPU *g); - +int virtio_gpu_virgl_get_num_capsets(VirtIOGPU *g); #endif diff --git a/include/standard-headers/linux/virtio_gpu.h b/include/standard-headers/linux/virtio_gpu.h index c1c8f0751d..52a830dcf8 100644 --- a/include/standard-headers/linux/virtio_gpu.h +++ b/include/standard-headers/linux/virtio_gpu.h @@ -260,6 +260,7 @@ struct virtio_gpu_cmd_submit { }; #define VIRTIO_GPU_CAPSET_VIRGL 1 +#define VIRTIO_GPU_CAPSET_VIRGL2 2 /* VIRTIO_GPU_CMD_GET_CAPSET_INFO */ struct virtio_gpu_get_capset_info { diff --git a/linux-user/aarch64/target_elf.h b/linux-user/aarch64/target_elf.h new file mode 100644 index 0000000000..a7eb962fba --- /dev/null +++ b/linux-user/aarch64/target_elf.h @@ -0,0 +1,14 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef AARCH64_TARGET_ELF_H +#define AARCH64_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + return "any"; +} +#endif diff --git a/linux-user/alpha/target_elf.h b/linux-user/alpha/target_elf.h new file mode 100644 index 0000000000..344e9f4d39 --- /dev/null +++ b/linux-user/alpha/target_elf.h @@ -0,0 +1,14 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef ALPHA_TARGET_ELF_H +#define ALPHA_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + return "any"; +} +#endif diff --git a/linux-user/arm/target_elf.h b/linux-user/arm/target_elf.h new file mode 100644 index 0000000000..58ff6a0986 --- /dev/null +++ b/linux-user/arm/target_elf.h @@ -0,0 +1,14 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef ARM_TARGET_ELF_H +#define ARM_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + return "any"; +} +#endif diff --git a/linux-user/cris/target_elf.h b/linux-user/cris/target_elf.h new file mode 100644 index 0000000000..99eb4ec704 --- /dev/null +++ b/linux-user/cris/target_elf.h @@ -0,0 +1,14 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef CRIS_TARGET_ELF_H +#define CRIS_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + return "any"; +} +#endif diff --git a/linux-user/elfload.c b/linux-user/elfload.c index 8bb9a2c3e8..6689089cd2 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -551,6 +551,8 @@ static uint32_t get_elf_hwcap(void) GET_FEATURE(ARM_FEATURE_V8_SM3, ARM_HWCAP_A64_SM3); GET_FEATURE(ARM_FEATURE_V8_SM4, ARM_HWCAP_A64_SM4); GET_FEATURE(ARM_FEATURE_V8_SHA512, ARM_HWCAP_A64_SHA512); + GET_FEATURE(ARM_FEATURE_V8_FP16, + ARM_HWCAP_A64_FPHP | ARM_HWCAP_A64_ASIMDHP); #undef GET_FEATURE return hwcaps; @@ -2396,6 +2398,41 @@ give_up: g_free(syms); } +uint32_t get_elf_eflags(int fd) +{ + struct elfhdr ehdr; + off_t offset; + int ret; + + /* Read ELF header */ + offset = lseek(fd, 0, SEEK_SET); + if (offset == (off_t) -1) { + return 0; + } + ret = read(fd, &ehdr, sizeof(ehdr)); + if (ret < sizeof(ehdr)) { + return 0; + } + offset = lseek(fd, offset, SEEK_SET); + if (offset == (off_t) -1) { + return 0; + } + + /* Check ELF signature */ + if (!elf_check_ident(&ehdr)) { + return 0; + } + + /* check header */ + bswap_ehdr(&ehdr); + if (!elf_check_ehdr(&ehdr)) { + return 0; + } + + /* return architecture id */ + return ehdr.e_flags; +} + int load_elf_binary(struct linux_binprm *bprm, struct image_info *info) { struct image_info interp_info; diff --git a/linux-user/hppa/target_elf.h b/linux-user/hppa/target_elf.h new file mode 100644 index 0000000000..82b4e9535e --- /dev/null +++ b/linux-user/hppa/target_elf.h @@ -0,0 +1,14 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef HPPA_TARGET_ELF_H +#define HPPA_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + return "any"; +} +#endif diff --git a/linux-user/i386/target_elf.h b/linux-user/i386/target_elf.h new file mode 100644 index 0000000000..1c6142e7da --- /dev/null +++ b/linux-user/i386/target_elf.h @@ -0,0 +1,14 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef I386_TARGET_ELF_H +#define I386_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + return "qemu32"; +} +#endif diff --git a/linux-user/m68k/target_elf.h b/linux-user/m68k/target_elf.h new file mode 100644 index 0000000000..998fe0fe2f --- /dev/null +++ b/linux-user/m68k/target_elf.h @@ -0,0 +1,20 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef M68K_TARGET_ELF_H +#define M68K_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + if (eflags == 0 || (eflags & EF_M68K_M68000)) { + /* 680x0 */ + return "m68040"; + } + + /* Coldfire */ + return "any"; +} +#endif diff --git a/linux-user/main.c b/linux-user/main.c index fd7900628b..bbeb78fb89 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -35,6 +35,7 @@ #include "elf.h" #include "exec/log.h" #include "trace/control.h" +#include "target_elf.h" char *exec_path; @@ -4343,46 +4344,17 @@ int main(int argc, char **argv, char **envp) init_qemu_uname_release(); + execfd = qemu_getauxval(AT_EXECFD); + if (execfd == 0) { + execfd = open(filename, O_RDONLY); + if (execfd < 0) { + printf("Error while loading %s: %s\n", filename, strerror(errno)); + _exit(EXIT_FAILURE); + } + } + if (cpu_model == NULL) { -#if defined(TARGET_I386) -#ifdef TARGET_X86_64 - cpu_model = "qemu64"; -#else - cpu_model = "qemu32"; -#endif -#elif defined(TARGET_ARM) - cpu_model = "any"; -#elif defined(TARGET_UNICORE32) - cpu_model = "any"; -#elif defined(TARGET_M68K) - cpu_model = "any"; -#elif defined(TARGET_SPARC) -#ifdef TARGET_SPARC64 - cpu_model = "TI UltraSparc II"; -#else - cpu_model = "Fujitsu MB86904"; -#endif -#elif defined(TARGET_MIPS) -#if defined(TARGET_ABI_MIPSN32) || defined(TARGET_ABI_MIPSN64) - cpu_model = "5KEf"; -#else - cpu_model = "24Kf"; -#endif -#elif defined TARGET_OPENRISC - cpu_model = "or1200"; -#elif defined(TARGET_PPC) -# ifdef TARGET_PPC64 - cpu_model = "POWER8"; -# else - cpu_model = "750"; -# endif -#elif defined TARGET_SH4 - cpu_model = "sh7785"; -#elif defined TARGET_S390X - cpu_model = "qemu"; -#else - cpu_model = "any"; -#endif + cpu_model = cpu_get_model(get_elf_eflags(execfd)); } tcg_exec_init(0); /* NOTE: we need to init the CPU at this stage to get @@ -4475,15 +4447,6 @@ int main(int argc, char **argv, char **envp) cpu->opaque = ts; task_settid(ts); - execfd = qemu_getauxval(AT_EXECFD); - if (execfd == 0) { - execfd = open(filename, O_RDONLY); - if (execfd < 0) { - printf("Error while loading %s: %s\n", filename, strerror(errno)); - _exit(EXIT_FAILURE); - } - } - ret = loader_exec(execfd, filename, target_argv, target_environ, regs, info, &bprm); if (ret != 0) { diff --git a/linux-user/microblaze/target_elf.h b/linux-user/microblaze/target_elf.h new file mode 100644 index 0000000000..8a8f1debff --- /dev/null +++ b/linux-user/microblaze/target_elf.h @@ -0,0 +1,14 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef MICROBLAZE_TARGET_ELF_H +#define MICROBLAZE_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + return "any"; +} +#endif diff --git a/linux-user/mips/target_elf.h b/linux-user/mips/target_elf.h new file mode 100644 index 0000000000..fa5d30bf99 --- /dev/null +++ b/linux-user/mips/target_elf.h @@ -0,0 +1,17 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef MIPS_TARGET_ELF_H +#define MIPS_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + if ((eflags & EF_MIPS_ARCH) == EF_MIPS_ARCH_32R6) { + return "mips32r6-generic"; + } + return "24Kf"; +} +#endif diff --git a/linux-user/mips64/target_elf.h b/linux-user/mips64/target_elf.h new file mode 100644 index 0000000000..ec55d8542a --- /dev/null +++ b/linux-user/mips64/target_elf.h @@ -0,0 +1,17 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef MIPS64_TARGET_ELF_H +#define MIPS64_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + if ((eflags & EF_MIPS_ARCH) == EF_MIPS_ARCH_64R6) { + return "I6400"; + } + return "5KEf"; +} +#endif diff --git a/linux-user/nios2/target_elf.h b/linux-user/nios2/target_elf.h new file mode 100644 index 0000000000..801e20afaf --- /dev/null +++ b/linux-user/nios2/target_elf.h @@ -0,0 +1,14 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef NIOS2_TARGET_ELF_H +#define NIOS2_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + return "any"; +} +#endif diff --git a/linux-user/openrisc/target_elf.h b/linux-user/openrisc/target_elf.h new file mode 100644 index 0000000000..40ceb025c9 --- /dev/null +++ b/linux-user/openrisc/target_elf.h @@ -0,0 +1,14 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef OPENRISC_TARGET_ELF_H +#define OPENRISC_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + return "or1200"; +} +#endif diff --git a/linux-user/ppc/target_elf.h b/linux-user/ppc/target_elf.h new file mode 100644 index 0000000000..576a5b9959 --- /dev/null +++ b/linux-user/ppc/target_elf.h @@ -0,0 +1,18 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef PPC_TARGET_ELF_H +#define PPC_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ +#ifdef TARGET_PPC64 + return "POWER8"; +#else + return "750"; +#endif +} +#endif diff --git a/linux-user/qemu.h b/linux-user/qemu.h index bc4bf35036..f4b4ca72ad 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -186,6 +186,7 @@ int loader_exec(int fdexec, const char *filename, char **argv, char **envp, struct target_pt_regs * regs, struct image_info *infop, struct linux_binprm *); +uint32_t get_elf_eflags(int fd); int load_elf_binary(struct linux_binprm *bprm, struct image_info *info); int load_flt_binary(struct linux_binprm *bprm, struct image_info *info); diff --git a/linux-user/s390x/target_elf.h b/linux-user/s390x/target_elf.h new file mode 100644 index 0000000000..8114b59c1d --- /dev/null +++ b/linux-user/s390x/target_elf.h @@ -0,0 +1,14 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef S390X_TARGET_ELF_H +#define S390X_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + return "qemu"; +} +#endif diff --git a/linux-user/sh4/target_elf.h b/linux-user/sh4/target_elf.h new file mode 100644 index 0000000000..f485e0cef2 --- /dev/null +++ b/linux-user/sh4/target_elf.h @@ -0,0 +1,14 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef SH4_TARGET_ELF_H +#define SH4_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + return "sh7785"; +} +#endif diff --git a/linux-user/sparc/target_elf.h b/linux-user/sparc/target_elf.h new file mode 100644 index 0000000000..a510ceb612 --- /dev/null +++ b/linux-user/sparc/target_elf.h @@ -0,0 +1,18 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef SPARC_TARGET_ELF_H +#define SPARC_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ +#ifdef TARGET_SPARC64 + return "TI UltraSparc II"; +#else + return "Fujitsu MB86904"; +#endif +} +#endif diff --git a/linux-user/sparc64/target_elf.h b/linux-user/sparc64/target_elf.h new file mode 100644 index 0000000000..d6e388f1cf --- /dev/null +++ b/linux-user/sparc64/target_elf.h @@ -0,0 +1,14 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef SPARC64_TARGET_ELF_H +#define SPARC64_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + return "TI UltraSparc II"; +} +#endif diff --git a/linux-user/tilegx/target_elf.h b/linux-user/tilegx/target_elf.h new file mode 100644 index 0000000000..7197bb0005 --- /dev/null +++ b/linux-user/tilegx/target_elf.h @@ -0,0 +1,14 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef TILEGX_TARGET_ELF_H +#define TILEGX_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + return "any"; +} +#endif diff --git a/linux-user/unicore32/target_elf.h b/linux-user/unicore32/target_elf.h new file mode 100644 index 0000000000..e2bfcb2ca3 --- /dev/null +++ b/linux-user/unicore32/target_elf.h @@ -0,0 +1,14 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef UNICORE32_TARGET_ELF_H +#define UNICORE32_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + return "any"; +} +#endif diff --git a/linux-user/x86_64/target_elf.h b/linux-user/x86_64/target_elf.h new file mode 100644 index 0000000000..7b76a90de8 --- /dev/null +++ b/linux-user/x86_64/target_elf.h @@ -0,0 +1,14 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or (at your option) any + * later version. See the COPYING file in the top-level directory. + */ + +#ifndef X86_64_TARGET_ELF_H +#define X86_64_TARGET_ELF_H +static inline const char *cpu_get_model(uint32_t eflags) +{ + return "qemu64"; +} +#endif diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 8c839faa8f..2b9740878b 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -168,6 +168,7 @@ typedef struct { * Qn = regs[n].d[1]:regs[n].d[0] * Dn = regs[n].d[0] * Sn = regs[n].d[0] bits 31..0 + * Hn = regs[n].d[0] bits 15..0 * * This corresponds to the architecturally defined mapping between * the two execution states, and means we do not need to explicitly @@ -537,19 +538,29 @@ typedef struct CPUARMState { /* scratch space when Tn are not sufficient. */ uint32_t scratch[8]; - /* fp_status is the "normal" fp status. standard_fp_status retains - * values corresponding to the ARM "Standard FPSCR Value", ie - * default-NaN, flush-to-zero, round-to-nearest and is used by - * any operations (generally Neon) which the architecture defines - * as controlled by the standard FPSCR value rather than the FPSCR. + /* There are a number of distinct float control structures: + * + * fp_status: is the "normal" fp status. + * fp_status_fp16: used for half-precision calculations + * standard_fp_status : the ARM "Standard FPSCR Value" + * + * Half-precision operations are governed by a separate + * flush-to-zero control bit in FPSCR:FZ16. We pass a separate + * status structure to control this. + * + * The "Standard FPSCR", ie default-NaN, flush-to-zero, + * round-to-nearest and is used by any operations (generally + * Neon) which the architecture defines as controlled by the + * standard FPSCR value rather than the FPSCR. * * To avoid having to transfer exception bits around, we simply * say that the FPSCR cumulative exception flags are the logical - * OR of the flags in the two fp statuses. This relies on the + * OR of the flags in the three fp statuses. This relies on the * only thing which needs to read the exception flags being * an explicit FPSCR read. */ float_status fp_status; + float_status fp_status_f16; float_status standard_fp_status; /* ZCR_EL[1-3] */ @@ -1189,12 +1200,20 @@ static inline void xpsr_write(CPUARMState *env, uint32_t val, uint32_t mask) uint32_t vfp_get_fpscr(CPUARMState *env); void vfp_set_fpscr(CPUARMState *env, uint32_t val); -/* For A64 the FPSCR is split into two logically distinct registers, +/* FPCR, Floating Point Control Register + * FPSR, Floating Poiht Status Register + * + * For A64 the FPSCR is split into two logically distinct registers, * FPCR and FPSR. However since they still use non-overlapping bits * we store the underlying state in fpscr and just mask on read/write. */ #define FPSR_MASK 0xf800009f #define FPCR_MASK 0x07f79f00 + +#define FPCR_FZ16 (1 << 19) /* ARMv8.2+, FP16 flush-to-zero */ +#define FPCR_FZ (1 << 24) /* Flush-to-zero enable bit */ +#define FPCR_DN (1 << 25) /* Default NaN enable bit */ + static inline uint32_t vfp_get_fpsr(CPUARMState *env) { return vfp_get_fpscr(env) & FPSR_MASK; @@ -1408,6 +1427,7 @@ enum arm_features { ARM_FEATURE_V8_SHA3, /* implements SHA3 part of v8 Crypto Extensions */ ARM_FEATURE_V8_SM3, /* implements SM3 part of v8 Crypto Extensions */ ARM_FEATURE_V8_SM4, /* implements SM4 part of v8 Crypto Extensions */ + ARM_FEATURE_V8_FP16, /* implements v8.2 half-precision float */ }; static inline int arm_feature(CPUARMState *env, int feature) diff --git a/target/arm/cpu64.c b/target/arm/cpu64.c index 1c330adc28..9743bdc8c3 100644 --- a/target/arm/cpu64.c +++ b/target/arm/cpu64.c @@ -230,6 +230,7 @@ static void aarch64_any_initfn(Object *obj) set_feature(&cpu->env, ARM_FEATURE_V8_SM4); set_feature(&cpu->env, ARM_FEATURE_V8_PMULL); set_feature(&cpu->env, ARM_FEATURE_CRC); + set_feature(&cpu->env, ARM_FEATURE_V8_FP16); cpu->ctr = 0x80038003; /* 32 byte I and D cacheline size, VIPT icache */ cpu->dcz_blocksize = 7; /* 512 bytes */ } diff --git a/target/arm/helper-a64.c b/target/arm/helper-a64.c index 10e08bdc1f..afb25ad20c 100644 --- a/target/arm/helper-a64.c +++ b/target/arm/helper-a64.c @@ -192,6 +192,10 @@ uint64_t HELPER(neon_cgt_f64)(float64 a, float64 b, void *fpstp) * versions, these do a fully fused multiply-add or * multiply-add-and-halve. */ +#define float16_two make_float16(0x4000) +#define float16_three make_float16(0x4200) +#define float16_one_point_five make_float16(0x3e00) + #define float32_two make_float32(0x40000000) #define float32_three make_float32(0x40400000) #define float32_one_point_five make_float32(0x3fc00000) @@ -200,6 +204,21 @@ uint64_t HELPER(neon_cgt_f64)(float64 a, float64 b, void *fpstp) #define float64_three make_float64(0x4008000000000000ULL) #define float64_one_point_five make_float64(0x3FF8000000000000ULL) +float16 HELPER(recpsf_f16)(float16 a, float16 b, void *fpstp) +{ + float_status *fpst = fpstp; + + a = float16_squash_input_denormal(a, fpst); + b = float16_squash_input_denormal(b, fpst); + + a = float16_chs(a); + if ((float16_is_infinity(a) && float16_is_zero(b)) || + (float16_is_infinity(b) && float16_is_zero(a))) { + return float16_two; + } + return float16_muladd(a, b, float16_two, 0, fpst); +} + float32 HELPER(recpsf_f32)(float32 a, float32 b, void *fpstp) { float_status *fpst = fpstp; @@ -230,6 +249,21 @@ float64 HELPER(recpsf_f64)(float64 a, float64 b, void *fpstp) return float64_muladd(a, b, float64_two, 0, fpst); } +float16 HELPER(rsqrtsf_f16)(float16 a, float16 b, void *fpstp) +{ + float_status *fpst = fpstp; + + a = float16_squash_input_denormal(a, fpst); + b = float16_squash_input_denormal(b, fpst); + + a = float16_chs(a); + if ((float16_is_infinity(a) && float16_is_zero(b)) || + (float16_is_infinity(b) && float16_is_zero(a))) { + return float16_one_point_five; + } + return float16_muladd(a, b, float16_three, float_muladd_halve_result, fpst); +} + float32 HELPER(rsqrtsf_f32)(float32 a, float32 b, void *fpstp) { float_status *fpst = fpstp; @@ -322,6 +356,35 @@ uint64_t HELPER(neon_addlp_u16)(uint64_t a) } /* Floating-point reciprocal exponent - see FPRecpX in ARM ARM */ +float16 HELPER(frecpx_f16)(float16 a, void *fpstp) +{ + float_status *fpst = fpstp; + uint16_t val16, sbit; + int16_t exp; + + if (float16_is_any_nan(a)) { + float16 nan = a; + if (float16_is_signaling_nan(a, fpst)) { + float_raise(float_flag_invalid, fpst); + nan = float16_maybe_silence_nan(a, fpst); + } + if (fpst->default_nan_mode) { + nan = float16_default_nan(fpst); + } + return nan; + } + + val16 = float16_val(a); + sbit = 0x8000 & val16; + exp = extract32(val16, 10, 5); + + if (exp == 0) { + return make_float16(deposit32(sbit, 10, 5, 0x1e)); + } else { + return make_float16(deposit32(sbit, 10, 5, ~exp)); + } +} + float32 HELPER(frecpx_f32)(float32 a, void *fpstp) { float_status *fpst = fpstp; @@ -572,3 +635,209 @@ uint64_t HELPER(paired_cmpxchg64_be_parallel)(CPUARMState *env, uint64_t addr, { return do_paired_cmpxchg64_be(env, addr, new_lo, new_hi, true, GETPC()); } + +/* + * AdvSIMD half-precision + */ + +#define ADVSIMD_HELPER(name, suffix) HELPER(glue(glue(advsimd_, name), suffix)) + +#define ADVSIMD_HALFOP(name) \ +float16 ADVSIMD_HELPER(name, h)(float16 a, float16 b, void *fpstp) \ +{ \ + float_status *fpst = fpstp; \ + return float16_ ## name(a, b, fpst); \ +} + +ADVSIMD_HALFOP(add) +ADVSIMD_HALFOP(sub) +ADVSIMD_HALFOP(mul) +ADVSIMD_HALFOP(div) +ADVSIMD_HALFOP(min) +ADVSIMD_HALFOP(max) +ADVSIMD_HALFOP(minnum) +ADVSIMD_HALFOP(maxnum) + +#define ADVSIMD_TWOHALFOP(name) \ +uint32_t ADVSIMD_HELPER(name, 2h)(uint32_t two_a, uint32_t two_b, void *fpstp) \ +{ \ + float16 a1, a2, b1, b2; \ + uint32_t r1, r2; \ + float_status *fpst = fpstp; \ + a1 = extract32(two_a, 0, 16); \ + a2 = extract32(two_a, 16, 16); \ + b1 = extract32(two_b, 0, 16); \ + b2 = extract32(two_b, 16, 16); \ + r1 = float16_ ## name(a1, b1, fpst); \ + r2 = float16_ ## name(a2, b2, fpst); \ + return deposit32(r1, 16, 16, r2); \ +} + +ADVSIMD_TWOHALFOP(add) +ADVSIMD_TWOHALFOP(sub) +ADVSIMD_TWOHALFOP(mul) +ADVSIMD_TWOHALFOP(div) +ADVSIMD_TWOHALFOP(min) +ADVSIMD_TWOHALFOP(max) +ADVSIMD_TWOHALFOP(minnum) +ADVSIMD_TWOHALFOP(maxnum) + +/* Data processing - scalar floating-point and advanced SIMD */ +static float16 float16_mulx(float16 a, float16 b, void *fpstp) +{ + float_status *fpst = fpstp; + + a = float16_squash_input_denormal(a, fpst); + b = float16_squash_input_denormal(b, fpst); + + if ((float16_is_zero(a) && float16_is_infinity(b)) || + (float16_is_infinity(a) && float16_is_zero(b))) { + /* 2.0 with the sign bit set to sign(A) XOR sign(B) */ + return make_float16((1U << 14) | + ((float16_val(a) ^ float16_val(b)) & (1U << 15))); + } + return float16_mul(a, b, fpst); +} + +ADVSIMD_HALFOP(mulx) +ADVSIMD_TWOHALFOP(mulx) + +/* fused multiply-accumulate */ +float16 HELPER(advsimd_muladdh)(float16 a, float16 b, float16 c, void *fpstp) +{ + float_status *fpst = fpstp; + return float16_muladd(a, b, c, 0, fpst); +} + +uint32_t HELPER(advsimd_muladd2h)(uint32_t two_a, uint32_t two_b, + uint32_t two_c, void *fpstp) +{ + float_status *fpst = fpstp; + float16 a1, a2, b1, b2, c1, c2; + uint32_t r1, r2; + a1 = extract32(two_a, 0, 16); + a2 = extract32(two_a, 16, 16); + b1 = extract32(two_b, 0, 16); + b2 = extract32(two_b, 16, 16); + c1 = extract32(two_c, 0, 16); + c2 = extract32(two_c, 16, 16); + r1 = float16_muladd(a1, b1, c1, 0, fpst); + r2 = float16_muladd(a2, b2, c2, 0, fpst); + return deposit32(r1, 16, 16, r2); +} + +/* + * Floating point comparisons produce an integer result. Softfloat + * routines return float_relation types which we convert to the 0/-1 + * Neon requires. + */ + +#define ADVSIMD_CMPRES(test) (test) ? 0xffff : 0 + +uint32_t HELPER(advsimd_ceq_f16)(float16 a, float16 b, void *fpstp) +{ + float_status *fpst = fpstp; + int compare = float16_compare_quiet(a, b, fpst); + return ADVSIMD_CMPRES(compare == float_relation_equal); +} + +uint32_t HELPER(advsimd_cge_f16)(float16 a, float16 b, void *fpstp) +{ + float_status *fpst = fpstp; + int compare = float16_compare(a, b, fpst); + return ADVSIMD_CMPRES(compare == float_relation_greater || + compare == float_relation_equal); +} + +uint32_t HELPER(advsimd_cgt_f16)(float16 a, float16 b, void *fpstp) +{ + float_status *fpst = fpstp; + int compare = float16_compare(a, b, fpst); + return ADVSIMD_CMPRES(compare == float_relation_greater); +} + +uint32_t HELPER(advsimd_acge_f16)(float16 a, float16 b, void *fpstp) +{ + float_status *fpst = fpstp; + float16 f0 = float16_abs(a); + float16 f1 = float16_abs(b); + int compare = float16_compare(f0, f1, fpst); + return ADVSIMD_CMPRES(compare == float_relation_greater || + compare == float_relation_equal); +} + +uint32_t HELPER(advsimd_acgt_f16)(float16 a, float16 b, void *fpstp) +{ + float_status *fpst = fpstp; + float16 f0 = float16_abs(a); + float16 f1 = float16_abs(b); + int compare = float16_compare(f0, f1, fpst); + return ADVSIMD_CMPRES(compare == float_relation_greater); +} + +/* round to integral */ +float16 HELPER(advsimd_rinth_exact)(float16 x, void *fp_status) +{ + return float16_round_to_int(x, fp_status); +} + +float16 HELPER(advsimd_rinth)(float16 x, void *fp_status) +{ + int old_flags = get_float_exception_flags(fp_status), new_flags; + float16 ret; + + ret = float16_round_to_int(x, fp_status); + + /* Suppress any inexact exceptions the conversion produced */ + if (!(old_flags & float_flag_inexact)) { + new_flags = get_float_exception_flags(fp_status); + set_float_exception_flags(new_flags & ~float_flag_inexact, fp_status); + } + + return ret; +} + +/* + * Half-precision floating point conversion functions + * + * There are a multitude of conversion functions with various + * different rounding modes. This is dealt with by the calling code + * setting the mode appropriately before calling the helper. + */ + +uint32_t HELPER(advsimd_f16tosinth)(float16 a, void *fpstp) +{ + float_status *fpst = fpstp; + + /* Invalid if we are passed a NaN */ + if (float16_is_any_nan(a)) { + float_raise(float_flag_invalid, fpst); + return 0; + } + return float16_to_int16(a, fpst); +} + +uint32_t HELPER(advsimd_f16touinth)(float16 a, void *fpstp) +{ + float_status *fpst = fpstp; + + /* Invalid if we are passed a NaN */ + if (float16_is_any_nan(a)) { + float_raise(float_flag_invalid, fpst); + return 0; + } + return float16_to_uint16(a, fpst); +} + +/* + * Square Root and Reciprocal square root + */ + +float16 HELPER(sqrt_f16)(float16 a, void *fpstp) +{ + float_status *s = fpstp; + + return float16_sqrt(a, s); +} + + diff --git a/target/arm/helper-a64.h b/target/arm/helper-a64.h index 85d86741db..ef4ddfe9d8 100644 --- a/target/arm/helper-a64.h +++ b/target/arm/helper-a64.h @@ -29,8 +29,10 @@ DEF_HELPER_FLAGS_3(vfp_mulxd, TCG_CALL_NO_RWG, f64, f64, f64, ptr) DEF_HELPER_FLAGS_3(neon_ceq_f64, TCG_CALL_NO_RWG, i64, i64, i64, ptr) DEF_HELPER_FLAGS_3(neon_cge_f64, TCG_CALL_NO_RWG, i64, i64, i64, ptr) DEF_HELPER_FLAGS_3(neon_cgt_f64, TCG_CALL_NO_RWG, i64, i64, i64, ptr) +DEF_HELPER_FLAGS_3(recpsf_f16, TCG_CALL_NO_RWG, f16, f16, f16, ptr) DEF_HELPER_FLAGS_3(recpsf_f32, TCG_CALL_NO_RWG, f32, f32, f32, ptr) DEF_HELPER_FLAGS_3(recpsf_f64, TCG_CALL_NO_RWG, f64, f64, f64, ptr) +DEF_HELPER_FLAGS_3(rsqrtsf_f16, TCG_CALL_NO_RWG, f16, f16, f16, ptr) DEF_HELPER_FLAGS_3(rsqrtsf_f32, TCG_CALL_NO_RWG, f32, f32, f32, ptr) DEF_HELPER_FLAGS_3(rsqrtsf_f64, TCG_CALL_NO_RWG, f64, f64, f64, ptr) DEF_HELPER_FLAGS_1(neon_addlp_s8, TCG_CALL_NO_RWG_SE, i64, i64) @@ -39,6 +41,7 @@ DEF_HELPER_FLAGS_1(neon_addlp_s16, TCG_CALL_NO_RWG_SE, i64, i64) DEF_HELPER_FLAGS_1(neon_addlp_u16, TCG_CALL_NO_RWG_SE, i64, i64) DEF_HELPER_FLAGS_2(frecpx_f64, TCG_CALL_NO_RWG, f64, f64, ptr) DEF_HELPER_FLAGS_2(frecpx_f32, TCG_CALL_NO_RWG, f32, f32, ptr) +DEF_HELPER_FLAGS_2(frecpx_f16, TCG_CALL_NO_RWG, f16, f16, ptr) DEF_HELPER_FLAGS_2(fcvtx_f64_to_f32, TCG_CALL_NO_RWG, f32, f64, env) DEF_HELPER_FLAGS_3(crc32_64, TCG_CALL_NO_RWG_SE, i64, i64, i64, i32) DEF_HELPER_FLAGS_3(crc32c_64, TCG_CALL_NO_RWG_SE, i64, i64, i64, i32) @@ -48,3 +51,33 @@ DEF_HELPER_FLAGS_4(paired_cmpxchg64_le_parallel, TCG_CALL_NO_WG, DEF_HELPER_FLAGS_4(paired_cmpxchg64_be, TCG_CALL_NO_WG, i64, env, i64, i64, i64) DEF_HELPER_FLAGS_4(paired_cmpxchg64_be_parallel, TCG_CALL_NO_WG, i64, env, i64, i64, i64) +DEF_HELPER_FLAGS_3(advsimd_maxh, TCG_CALL_NO_RWG, f16, f16, f16, ptr) +DEF_HELPER_FLAGS_3(advsimd_minh, TCG_CALL_NO_RWG, f16, f16, f16, ptr) +DEF_HELPER_FLAGS_3(advsimd_maxnumh, TCG_CALL_NO_RWG, f16, f16, f16, ptr) +DEF_HELPER_FLAGS_3(advsimd_minnumh, TCG_CALL_NO_RWG, f16, f16, f16, ptr) +DEF_HELPER_3(advsimd_addh, f16, f16, f16, ptr) +DEF_HELPER_3(advsimd_subh, f16, f16, f16, ptr) +DEF_HELPER_3(advsimd_mulh, f16, f16, f16, ptr) +DEF_HELPER_3(advsimd_divh, f16, f16, f16, ptr) +DEF_HELPER_3(advsimd_ceq_f16, i32, f16, f16, ptr) +DEF_HELPER_3(advsimd_cge_f16, i32, f16, f16, ptr) +DEF_HELPER_3(advsimd_cgt_f16, i32, f16, f16, ptr) +DEF_HELPER_3(advsimd_acge_f16, i32, f16, f16, ptr) +DEF_HELPER_3(advsimd_acgt_f16, i32, f16, f16, ptr) +DEF_HELPER_3(advsimd_mulxh, f16, f16, f16, ptr) +DEF_HELPER_4(advsimd_muladdh, f16, f16, f16, f16, ptr) +DEF_HELPER_3(advsimd_add2h, i32, i32, i32, ptr) +DEF_HELPER_3(advsimd_sub2h, i32, i32, i32, ptr) +DEF_HELPER_3(advsimd_mul2h, i32, i32, i32, ptr) +DEF_HELPER_3(advsimd_div2h, i32, i32, i32, ptr) +DEF_HELPER_3(advsimd_max2h, i32, i32, i32, ptr) +DEF_HELPER_3(advsimd_min2h, i32, i32, i32, ptr) +DEF_HELPER_3(advsimd_maxnum2h, i32, i32, i32, ptr) +DEF_HELPER_3(advsimd_minnum2h, i32, i32, i32, ptr) +DEF_HELPER_3(advsimd_mulx2h, i32, i32, i32, ptr) +DEF_HELPER_4(advsimd_muladd2h, i32, i32, i32, i32, ptr) +DEF_HELPER_2(advsimd_rinth_exact, f16, f16, ptr) +DEF_HELPER_2(advsimd_rinth, f16, f16, ptr) +DEF_HELPER_2(advsimd_f16tosinth, i32, f16, ptr) +DEF_HELPER_2(advsimd_f16touinth, i32, f16, ptr) +DEF_HELPER_2(sqrt_f16, f16, f16, ptr) diff --git a/target/arm/helper.c b/target/arm/helper.c index c5bc69b961..c82f63d440 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -11103,6 +11103,7 @@ uint32_t HELPER(vfp_get_fpscr)(CPUARMState *env) | (env->vfp.vec_stride << 20); i = get_float_exception_flags(&env->vfp.fp_status); i |= get_float_exception_flags(&env->vfp.standard_fp_status); + i |= get_float_exception_flags(&env->vfp.fp_status_f16); fpscr |= vfp_exceptbits_from_host(i); return fpscr; } @@ -11160,16 +11161,31 @@ void HELPER(vfp_set_fpscr)(CPUARMState *env, uint32_t val) break; } set_float_rounding_mode(i, &env->vfp.fp_status); + set_float_rounding_mode(i, &env->vfp.fp_status_f16); } - if (changed & (1 << 24)) { - set_flush_to_zero((val & (1 << 24)) != 0, &env->vfp.fp_status); - set_flush_inputs_to_zero((val & (1 << 24)) != 0, &env->vfp.fp_status); + if (changed & FPCR_FZ16) { + bool ftz_enabled = val & FPCR_FZ16; + set_flush_to_zero(ftz_enabled, &env->vfp.fp_status_f16); + set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status_f16); + } + if (changed & FPCR_FZ) { + bool ftz_enabled = val & FPCR_FZ; + set_flush_to_zero(ftz_enabled, &env->vfp.fp_status); + set_flush_inputs_to_zero(ftz_enabled, &env->vfp.fp_status); + } + if (changed & FPCR_DN) { + bool dnan_enabled = val & FPCR_DN; + set_default_nan_mode(dnan_enabled, &env->vfp.fp_status); + set_default_nan_mode(dnan_enabled, &env->vfp.fp_status_f16); } - if (changed & (1 << 25)) - set_default_nan_mode((val & (1 << 25)) != 0, &env->vfp.fp_status); + /* The exception flags are ORed together when we read fpscr so we + * only need to preserve the current state in one of our + * float_status values. + */ i = vfp_exceptbits_to_host(val); set_float_exception_flags(i, &env->vfp.fp_status); + set_float_exception_flags(0, &env->vfp.fp_status_f16); set_float_exception_flags(0, &env->vfp.standard_fp_status); } @@ -11286,8 +11302,10 @@ CONV_ITOF(vfp_##name##to##p, fsz, sign) \ CONV_FTOI(vfp_to##name##p, fsz, sign, ) \ CONV_FTOI(vfp_to##name##z##p, fsz, sign, _round_to_zero) +FLOAT_CONVS(si, h, 16, ) FLOAT_CONVS(si, s, 32, ) FLOAT_CONVS(si, d, 64, ) +FLOAT_CONVS(ui, h, 16, u) FLOAT_CONVS(ui, s, 32, u) FLOAT_CONVS(ui, d, 64, u) @@ -11370,6 +11388,8 @@ VFP_CONV_FIX_A64(sq, s, 32, 64, int64) VFP_CONV_FIX(uh, s, 32, 32, uint16) VFP_CONV_FIX(ul, s, 32, 32, uint32) VFP_CONV_FIX_A64(uq, s, 32, 64, uint64) +VFP_CONV_FIX_A64(sl, h, 16, 32, int32) +VFP_CONV_FIX_A64(ul, h, 16, 32, uint32) #undef VFP_CONV_FIX #undef VFP_CONV_FIX_FLOAT #undef VFP_CONV_FLOAT_FIX_ROUND @@ -11377,9 +11397,9 @@ VFP_CONV_FIX_A64(uq, s, 32, 64, uint64) /* Set the current fp rounding mode and return the old one. * The argument is a softfloat float_round_ value. */ -uint32_t HELPER(set_rmode)(uint32_t rmode, CPUARMState *env) +uint32_t HELPER(set_rmode)(uint32_t rmode, void *fpstp) { - float_status *fp_status = &env->vfp.fp_status; + float_status *fp_status = fpstp; uint32_t prev_rmode = get_float_rounding_mode(fp_status); set_float_rounding_mode(rmode, fp_status); @@ -11503,80 +11523,75 @@ float32 HELPER(rsqrts_f32)(float32 a, float32 b, CPUARMState *env) * int->float conversions at run-time. */ #define float64_256 make_float64(0x4070000000000000LL) #define float64_512 make_float64(0x4080000000000000LL) +#define float16_maxnorm make_float16(0x7bff) #define float32_maxnorm make_float32(0x7f7fffff) #define float64_maxnorm make_float64(0x7fefffffffffffffLL) /* Reciprocal functions * * The algorithm that must be used to calculate the estimate - * is specified by the ARM ARM, see FPRecipEstimate() + * is specified by the ARM ARM, see FPRecipEstimate()/RecipEstimate */ -static float64 recip_estimate(float64 a, float_status *real_fp_status) -{ - /* These calculations mustn't set any fp exception flags, - * so we use a local copy of the fp_status. - */ - float_status dummy_status = *real_fp_status; - float_status *s = &dummy_status; - /* q = (int)(a * 512.0) */ - float64 q = float64_mul(float64_512, a, s); - int64_t q_int = float64_to_int64_round_to_zero(q, s); - - /* r = 1.0 / (((double)q + 0.5) / 512.0) */ - q = int64_to_float64(q_int, s); - q = float64_add(q, float64_half, s); - q = float64_div(q, float64_512, s); - q = float64_div(float64_one, q, s); - - /* s = (int)(256.0 * r + 0.5) */ - q = float64_mul(q, float64_256, s); - q = float64_add(q, float64_half, s); - q_int = float64_to_int64_round_to_zero(q, s); +/* See RecipEstimate() + * + * input is a 9 bit fixed point number + * input range 256 .. 511 for a number from 0.5 <= x < 1.0. + * result range 256 .. 511 for a number from 1.0 to 511/256. + */ - /* return (double)s / 256.0 */ - return float64_div(int64_to_float64(q_int, s), float64_256, s); +static int recip_estimate(int input) +{ + int a, b, r; + assert(256 <= input && input < 512); + a = (input * 2) + 1; + b = (1 << 19) / a; + r = (b + 1) >> 1; + assert(256 <= r && r < 512); + return r; } -/* Common wrapper to call recip_estimate */ -static float64 call_recip_estimate(float64 num, int off, float_status *fpst) +/* + * Common wrapper to call recip_estimate + * + * The parameters are exponent and 64 bit fraction (without implicit + * bit) where the binary point is nominally at bit 52. Returns a + * float64 which can then be rounded to the appropriate size by the + * callee. + */ + +static uint64_t call_recip_estimate(int *exp, int exp_off, uint64_t frac) { - uint64_t val64 = float64_val(num); - uint64_t frac = extract64(val64, 0, 52); - int64_t exp = extract64(val64, 52, 11); - uint64_t sbit; - float64 scaled, estimate; + uint32_t scaled, estimate; + uint64_t result_frac; + int result_exp; - /* Generate the scaled number for the estimate function */ - if (exp == 0) { + /* Handle sub-normals */ + if (*exp == 0) { if (extract64(frac, 51, 1) == 0) { - exp = -1; - frac = extract64(frac, 0, 50) << 2; + *exp = -1; + frac <<= 2; } else { - frac = extract64(frac, 0, 51) << 1; + frac <<= 1; } } - /* scaled = '0' : '01111111110' : fraction<51:44> : Zeros(44); */ - scaled = make_float64((0x3feULL << 52) - | extract64(frac, 44, 8) << 44); - - estimate = recip_estimate(scaled, fpst); - - /* Build new result */ - val64 = float64_val(estimate); - sbit = 0x8000000000000000ULL & val64; - exp = off - exp; - frac = extract64(val64, 0, 52); + /* scaled = UInt('1':fraction<51:44>) */ + scaled = deposit32(1 << 8, 0, 8, extract64(frac, 44, 8)); + estimate = recip_estimate(scaled); - if (exp == 0) { - frac = 1ULL << 51 | extract64(frac, 1, 51); - } else if (exp == -1) { - frac = 1ULL << 50 | extract64(frac, 2, 50); - exp = 0; + result_exp = exp_off - *exp; + result_frac = deposit64(0, 44, 8, estimate); + if (result_exp == 0) { + result_frac = deposit64(result_frac >> 1, 51, 1, 1); + } else if (result_exp == -1) { + result_frac = deposit64(result_frac >> 2, 50, 2, 1); + result_exp = 0; } - return make_float64(sbit | (exp << 52) | frac); + *exp = result_exp; + + return result_frac; } static bool round_to_inf(float_status *fpst, bool sign_bit) @@ -11595,18 +11610,63 @@ static bool round_to_inf(float_status *fpst, bool sign_bit) g_assert_not_reached(); } +float16 HELPER(recpe_f16)(float16 input, void *fpstp) +{ + float_status *fpst = fpstp; + float16 f16 = float16_squash_input_denormal(input, fpst); + uint32_t f16_val = float16_val(f16); + uint32_t f16_sign = float16_is_neg(f16); + int f16_exp = extract32(f16_val, 10, 5); + uint32_t f16_frac = extract32(f16_val, 0, 10); + uint64_t f64_frac; + + if (float16_is_any_nan(f16)) { + float16 nan = f16; + if (float16_is_signaling_nan(f16, fpst)) { + float_raise(float_flag_invalid, fpst); + nan = float16_maybe_silence_nan(f16, fpst); + } + if (fpst->default_nan_mode) { + nan = float16_default_nan(fpst); + } + return nan; + } else if (float16_is_infinity(f16)) { + return float16_set_sign(float16_zero, float16_is_neg(f16)); + } else if (float16_is_zero(f16)) { + float_raise(float_flag_divbyzero, fpst); + return float16_set_sign(float16_infinity, float16_is_neg(f16)); + } else if (float16_abs(f16) < (1 << 8)) { + /* Abs(value) < 2.0^-16 */ + float_raise(float_flag_overflow | float_flag_inexact, fpst); + if (round_to_inf(fpst, f16_sign)) { + return float16_set_sign(float16_infinity, f16_sign); + } else { + return float16_set_sign(float16_maxnorm, f16_sign); + } + } else if (f16_exp >= 29 && fpst->flush_to_zero) { + float_raise(float_flag_underflow, fpst); + return float16_set_sign(float16_zero, float16_is_neg(f16)); + } + + f64_frac = call_recip_estimate(&f16_exp, 29, + ((uint64_t) f16_frac) << (52 - 10)); + + /* result = sign : result_exp<4:0> : fraction<51:42> */ + f16_val = deposit32(0, 15, 1, f16_sign); + f16_val = deposit32(f16_val, 10, 5, f16_exp); + f16_val = deposit32(f16_val, 0, 10, extract64(f64_frac, 52 - 10, 10)); + return make_float16(f16_val); +} + float32 HELPER(recpe_f32)(float32 input, void *fpstp) { float_status *fpst = fpstp; float32 f32 = float32_squash_input_denormal(input, fpst); uint32_t f32_val = float32_val(f32); - uint32_t f32_sbit = 0x80000000ULL & f32_val; - int32_t f32_exp = extract32(f32_val, 23, 8); + bool f32_sign = float32_is_neg(f32); + int f32_exp = extract32(f32_val, 23, 8); uint32_t f32_frac = extract32(f32_val, 0, 23); - float64 f64, r64; - uint64_t r64_val; - int64_t r64_exp; - uint64_t r64_frac; + uint64_t f64_frac; if (float32_is_any_nan(f32)) { float32 nan = f32; @@ -11623,30 +11683,27 @@ float32 HELPER(recpe_f32)(float32 input, void *fpstp) } else if (float32_is_zero(f32)) { float_raise(float_flag_divbyzero, fpst); return float32_set_sign(float32_infinity, float32_is_neg(f32)); - } else if ((f32_val & ~(1ULL << 31)) < (1ULL << 21)) { + } else if (float32_abs(f32) < (1ULL << 21)) { /* Abs(value) < 2.0^-128 */ float_raise(float_flag_overflow | float_flag_inexact, fpst); - if (round_to_inf(fpst, f32_sbit)) { - return float32_set_sign(float32_infinity, float32_is_neg(f32)); + if (round_to_inf(fpst, f32_sign)) { + return float32_set_sign(float32_infinity, f32_sign); } else { - return float32_set_sign(float32_maxnorm, float32_is_neg(f32)); + return float32_set_sign(float32_maxnorm, f32_sign); } } else if (f32_exp >= 253 && fpst->flush_to_zero) { float_raise(float_flag_underflow, fpst); return float32_set_sign(float32_zero, float32_is_neg(f32)); } + f64_frac = call_recip_estimate(&f32_exp, 253, + ((uint64_t) f32_frac) << (52 - 23)); - f64 = make_float64(((int64_t)(f32_exp) << 52) | (int64_t)(f32_frac) << 29); - r64 = call_recip_estimate(f64, 253, fpst); - r64_val = float64_val(r64); - r64_exp = extract64(r64_val, 52, 11); - r64_frac = extract64(r64_val, 0, 52); - - /* result = sign : result_exp<7:0> : fraction<51:29>; */ - return make_float32(f32_sbit | - (r64_exp & 0xff) << 23 | - extract64(r64_frac, 29, 24)); + /* result = sign : result_exp<7:0> : fraction<51:29> */ + f32_val = deposit32(0, 31, 1, f32_sign); + f32_val = deposit32(f32_val, 23, 8, f32_exp); + f32_val = deposit32(f32_val, 0, 23, extract64(f64_frac, 52 - 23, 23)); + return make_float32(f32_val); } float64 HELPER(recpe_f64)(float64 input, void *fpstp) @@ -11654,12 +11711,9 @@ float64 HELPER(recpe_f64)(float64 input, void *fpstp) float_status *fpst = fpstp; float64 f64 = float64_squash_input_denormal(input, fpst); uint64_t f64_val = float64_val(f64); - uint64_t f64_sbit = 0x8000000000000000ULL & f64_val; - int64_t f64_exp = extract64(f64_val, 52, 11); - float64 r64; - uint64_t r64_val; - int64_t r64_exp; - uint64_t r64_frac; + bool f64_sign = float64_is_neg(f64); + int f64_exp = extract64(f64_val, 52, 11); + uint64_t f64_frac = extract64(f64_val, 0, 52); /* Deal with any special cases */ if (float64_is_any_nan(f64)) { @@ -11680,80 +11734,119 @@ float64 HELPER(recpe_f64)(float64 input, void *fpstp) } else if ((f64_val & ~(1ULL << 63)) < (1ULL << 50)) { /* Abs(value) < 2.0^-1024 */ float_raise(float_flag_overflow | float_flag_inexact, fpst); - if (round_to_inf(fpst, f64_sbit)) { - return float64_set_sign(float64_infinity, float64_is_neg(f64)); + if (round_to_inf(fpst, f64_sign)) { + return float64_set_sign(float64_infinity, f64_sign); } else { - return float64_set_sign(float64_maxnorm, float64_is_neg(f64)); + return float64_set_sign(float64_maxnorm, f64_sign); } } else if (f64_exp >= 2045 && fpst->flush_to_zero) { float_raise(float_flag_underflow, fpst); return float64_set_sign(float64_zero, float64_is_neg(f64)); } - r64 = call_recip_estimate(f64, 2045, fpst); - r64_val = float64_val(r64); - r64_exp = extract64(r64_val, 52, 11); - r64_frac = extract64(r64_val, 0, 52); + f64_frac = call_recip_estimate(&f64_exp, 2045, f64_frac); - /* result = sign : result_exp<10:0> : fraction<51:0> */ - return make_float64(f64_sbit | - ((r64_exp & 0x7ff) << 52) | - r64_frac); + /* result = sign : result_exp<10:0> : fraction<51:0>; */ + f64_val = deposit64(0, 63, 1, f64_sign); + f64_val = deposit64(f64_val, 52, 11, f64_exp); + f64_val = deposit64(f64_val, 0, 52, f64_frac); + return make_float64(f64_val); } /* The algorithm that must be used to calculate the estimate * is specified by the ARM ARM. */ -static float64 recip_sqrt_estimate(float64 a, float_status *real_fp_status) -{ - /* These calculations mustn't set any fp exception flags, - * so we use a local copy of the fp_status. - */ - float_status dummy_status = *real_fp_status; - float_status *s = &dummy_status; - float64 q; - int64_t q_int; - - if (float64_lt(a, float64_half, s)) { - /* range 0.25 <= a < 0.5 */ - - /* a in units of 1/512 rounded down */ - /* q0 = (int)(a * 512.0); */ - q = float64_mul(float64_512, a, s); - q_int = float64_to_int64_round_to_zero(q, s); - - /* reciprocal root r */ - /* r = 1.0 / sqrt(((double)q0 + 0.5) / 512.0); */ - q = int64_to_float64(q_int, s); - q = float64_add(q, float64_half, s); - q = float64_div(q, float64_512, s); - q = float64_sqrt(q, s); - q = float64_div(float64_one, q, s); + +static int do_recip_sqrt_estimate(int a) +{ + int b, estimate; + + assert(128 <= a && a < 512); + if (a < 256) { + a = a * 2 + 1; + } else { + a = (a >> 1) << 1; + a = (a + 1) * 2; + } + b = 512; + while (a * (b + 1) * (b + 1) < (1 << 28)) { + b += 1; + } + estimate = (b + 1) / 2; + assert(256 <= estimate && estimate < 512); + + return estimate; +} + + +static uint64_t recip_sqrt_estimate(int *exp , int exp_off, uint64_t frac) +{ + int estimate; + uint32_t scaled; + + if (*exp == 0) { + while (extract64(frac, 51, 1) == 0) { + frac = frac << 1; + *exp -= 1; + } + frac = extract64(frac, 0, 51) << 1; + } + + if (*exp & 1) { + /* scaled = UInt('01':fraction<51:45>) */ + scaled = deposit32(1 << 7, 0, 7, extract64(frac, 45, 7)); } else { - /* range 0.5 <= a < 1.0 */ + /* scaled = UInt('1':fraction<51:44>) */ + scaled = deposit32(1 << 8, 0, 8, extract64(frac, 44, 8)); + } + estimate = do_recip_sqrt_estimate(scaled); + + *exp = (exp_off - *exp) / 2; + return extract64(estimate, 0, 8) << 44; +} - /* a in units of 1/256 rounded down */ - /* q1 = (int)(a * 256.0); */ - q = float64_mul(float64_256, a, s); - int64_t q_int = float64_to_int64_round_to_zero(q, s); +float16 HELPER(rsqrte_f16)(float16 input, void *fpstp) +{ + float_status *s = fpstp; + float16 f16 = float16_squash_input_denormal(input, s); + uint16_t val = float16_val(f16); + bool f16_sign = float16_is_neg(f16); + int f16_exp = extract32(val, 10, 5); + uint16_t f16_frac = extract32(val, 0, 10); + uint64_t f64_frac; - /* reciprocal root r */ - /* r = 1.0 /sqrt(((double)q1 + 0.5) / 256); */ - q = int64_to_float64(q_int, s); - q = float64_add(q, float64_half, s); - q = float64_div(q, float64_256, s); - q = float64_sqrt(q, s); - q = float64_div(float64_one, q, s); + if (float16_is_any_nan(f16)) { + float16 nan = f16; + if (float16_is_signaling_nan(f16, s)) { + float_raise(float_flag_invalid, s); + nan = float16_maybe_silence_nan(f16, s); + } + if (s->default_nan_mode) { + nan = float16_default_nan(s); + } + return nan; + } else if (float16_is_zero(f16)) { + float_raise(float_flag_divbyzero, s); + return float16_set_sign(float16_infinity, f16_sign); + } else if (f16_sign) { + float_raise(float_flag_invalid, s); + return float16_default_nan(s); + } else if (float16_is_infinity(f16)) { + return float16_zero; } - /* r in units of 1/256 rounded to nearest */ - /* s = (int)(256.0 * r + 0.5); */ - q = float64_mul(q, float64_256,s ); - q = float64_add(q, float64_half, s); - q_int = float64_to_int64_round_to_zero(q, s); + /* Scale and normalize to a double-precision value between 0.25 and 1.0, + * preserving the parity of the exponent. */ + + f64_frac = ((uint64_t) f16_frac) << (52 - 10); + + f64_frac = recip_sqrt_estimate(&f16_exp, 44, f64_frac); - /* return (double)s / 256.0;*/ - return float64_div(int64_to_float64(q_int, s), float64_256, s); + /* result = sign : result_exp<4:0> : estimate<7:0> : Zeros(2) */ + val = deposit32(0, 15, 1, f16_sign); + val = deposit32(val, 10, 5, f16_exp); + val = deposit32(val, 2, 8, extract64(f64_frac, 52 - 8, 8)); + return make_float16(val); } float32 HELPER(rsqrte_f32)(float32 input, void *fpstp) @@ -11761,13 +11854,10 @@ float32 HELPER(rsqrte_f32)(float32 input, void *fpstp) float_status *s = fpstp; float32 f32 = float32_squash_input_denormal(input, s); uint32_t val = float32_val(f32); - uint32_t f32_sbit = 0x80000000 & val; - int32_t f32_exp = extract32(val, 23, 8); + uint32_t f32_sign = float32_is_neg(f32); + int f32_exp = extract32(val, 23, 8); uint32_t f32_frac = extract32(val, 0, 23); uint64_t f64_frac; - uint64_t val64; - int result_exp; - float64 f64; if (float32_is_any_nan(f32)) { float32 nan = f32; @@ -11793,32 +11883,13 @@ float32 HELPER(rsqrte_f32)(float32 input, void *fpstp) * preserving the parity of the exponent. */ f64_frac = ((uint64_t) f32_frac) << 29; - if (f32_exp == 0) { - while (extract64(f64_frac, 51, 1) == 0) { - f64_frac = f64_frac << 1; - f32_exp = f32_exp-1; - } - f64_frac = extract64(f64_frac, 0, 51) << 1; - } - - if (extract64(f32_exp, 0, 1) == 0) { - f64 = make_float64(((uint64_t) f32_sbit) << 32 - | (0x3feULL << 52) - | f64_frac); - } else { - f64 = make_float64(((uint64_t) f32_sbit) << 32 - | (0x3fdULL << 52) - | f64_frac); - } - - result_exp = (380 - f32_exp) / 2; - f64 = recip_sqrt_estimate(f64, s); + f64_frac = recip_sqrt_estimate(&f32_exp, 380, f64_frac); - val64 = float64_val(f64); - - val = ((result_exp & 0xff) << 23) - | ((val64 >> 29) & 0x7fffff); + /* result = sign : result_exp<4:0> : estimate<7:0> : Zeros(15) */ + val = deposit32(0, 31, 1, f32_sign); + val = deposit32(val, 23, 8, f32_exp); + val = deposit32(val, 15, 8, extract64(f64_frac, 52 - 8, 8)); return make_float32(val); } @@ -11827,11 +11898,9 @@ float64 HELPER(rsqrte_f64)(float64 input, void *fpstp) float_status *s = fpstp; float64 f64 = float64_squash_input_denormal(input, s); uint64_t val = float64_val(f64); - uint64_t f64_sbit = 0x8000000000000000ULL & val; - int64_t f64_exp = extract64(val, 52, 11); + bool f64_sign = float64_is_neg(f64); + int f64_exp = extract64(val, 52, 11); uint64_t f64_frac = extract64(val, 0, 52); - int64_t result_exp; - uint64_t result_frac; if (float64_is_any_nan(f64)) { float64 nan = f64; @@ -11853,75 +11922,41 @@ float64 HELPER(rsqrte_f64)(float64 input, void *fpstp) return float64_zero; } - /* Scale and normalize to a double-precision value between 0.25 and 1.0, - * preserving the parity of the exponent. */ - - if (f64_exp == 0) { - while (extract64(f64_frac, 51, 1) == 0) { - f64_frac = f64_frac << 1; - f64_exp = f64_exp - 1; - } - f64_frac = extract64(f64_frac, 0, 51) << 1; - } - - if (extract64(f64_exp, 0, 1) == 0) { - f64 = make_float64(f64_sbit - | (0x3feULL << 52) - | f64_frac); - } else { - f64 = make_float64(f64_sbit - | (0x3fdULL << 52) - | f64_frac); - } - - result_exp = (3068 - f64_exp) / 2; - - f64 = recip_sqrt_estimate(f64, s); + f64_frac = recip_sqrt_estimate(&f64_exp, 3068, f64_frac); - result_frac = extract64(float64_val(f64), 0, 52); - - return make_float64(f64_sbit | - ((result_exp & 0x7ff) << 52) | - result_frac); + /* result = sign : result_exp<4:0> : estimate<7:0> : Zeros(44) */ + val = deposit64(0, 61, 1, f64_sign); + val = deposit64(val, 52, 11, f64_exp); + val = deposit64(val, 44, 8, extract64(f64_frac, 52 - 8, 8)); + return make_float64(val); } uint32_t HELPER(recpe_u32)(uint32_t a, void *fpstp) { - float_status *s = fpstp; - float64 f64; + /* float_status *s = fpstp; */ + int input, estimate; if ((a & 0x80000000) == 0) { return 0xffffffff; } - f64 = make_float64((0x3feULL << 52) - | ((int64_t)(a & 0x7fffffff) << 21)); - - f64 = recip_estimate(f64, s); + input = extract32(a, 23, 9); + estimate = recip_estimate(input); - return 0x80000000 | ((float64_val(f64) >> 21) & 0x7fffffff); + return deposit32(0, (32 - 9), 9, estimate); } uint32_t HELPER(rsqrte_u32)(uint32_t a, void *fpstp) { - float_status *fpst = fpstp; - float64 f64; + int estimate; if ((a & 0xc0000000) == 0) { return 0xffffffff; } - if (a & 0x80000000) { - f64 = make_float64((0x3feULL << 52) - | ((uint64_t)(a & 0x7fffffff) << 21)); - } else { /* bits 31-30 == '01' */ - f64 = make_float64((0x3fdULL << 52) - | ((uint64_t)(a & 0x3fffffff) << 22)); - } - - f64 = recip_sqrt_estimate(f64, fpst); + estimate = do_recip_sqrt_estimate(extract32(a, 23, 9)); - return 0x80000000 | ((float64_val(f64) >> 21) & 0x7fffffff); + return deposit32(0, 23, 9, estimate); } /* VFPv4 fused multiply-accumulate */ diff --git a/target/arm/helper.h b/target/arm/helper.h index 6383d7d09e..6dd8504ec3 100644 --- a/target/arm/helper.h +++ b/target/arm/helper.h @@ -120,17 +120,23 @@ DEF_HELPER_3(vfp_cmped, void, f64, f64, env) DEF_HELPER_2(vfp_fcvtds, f64, f32, env) DEF_HELPER_2(vfp_fcvtsd, f32, f64, env) +DEF_HELPER_2(vfp_uitoh, f16, i32, ptr) DEF_HELPER_2(vfp_uitos, f32, i32, ptr) DEF_HELPER_2(vfp_uitod, f64, i32, ptr) +DEF_HELPER_2(vfp_sitoh, f16, i32, ptr) DEF_HELPER_2(vfp_sitos, f32, i32, ptr) DEF_HELPER_2(vfp_sitod, f64, i32, ptr) +DEF_HELPER_2(vfp_touih, i32, f16, ptr) DEF_HELPER_2(vfp_touis, i32, f32, ptr) DEF_HELPER_2(vfp_touid, i32, f64, ptr) +DEF_HELPER_2(vfp_touizh, i32, f16, ptr) DEF_HELPER_2(vfp_touizs, i32, f32, ptr) DEF_HELPER_2(vfp_touizd, i32, f64, ptr) +DEF_HELPER_2(vfp_tosih, i32, f16, ptr) DEF_HELPER_2(vfp_tosis, i32, f32, ptr) DEF_HELPER_2(vfp_tosid, i32, f64, ptr) +DEF_HELPER_2(vfp_tosizh, i32, f16, ptr) DEF_HELPER_2(vfp_tosizs, i32, f32, ptr) DEF_HELPER_2(vfp_tosizd, i32, f64, ptr) @@ -142,6 +148,8 @@ DEF_HELPER_3(vfp_toshd_round_to_zero, i64, f64, i32, ptr) DEF_HELPER_3(vfp_tosld_round_to_zero, i64, f64, i32, ptr) DEF_HELPER_3(vfp_touhd_round_to_zero, i64, f64, i32, ptr) DEF_HELPER_3(vfp_tould_round_to_zero, i64, f64, i32, ptr) +DEF_HELPER_3(vfp_toulh, i32, f16, i32, ptr) +DEF_HELPER_3(vfp_toslh, i32, f16, i32, ptr) DEF_HELPER_3(vfp_toshs, i32, f32, i32, ptr) DEF_HELPER_3(vfp_tosls, i32, f32, i32, ptr) DEF_HELPER_3(vfp_tosqs, i64, f32, i32, ptr) @@ -166,8 +174,10 @@ DEF_HELPER_3(vfp_sqtod, f64, i64, i32, ptr) DEF_HELPER_3(vfp_uhtod, f64, i64, i32, ptr) DEF_HELPER_3(vfp_ultod, f64, i64, i32, ptr) DEF_HELPER_3(vfp_uqtod, f64, i64, i32, ptr) +DEF_HELPER_3(vfp_sltoh, f16, i32, i32, ptr) +DEF_HELPER_3(vfp_ultoh, f16, i32, i32, ptr) -DEF_HELPER_FLAGS_2(set_rmode, TCG_CALL_NO_RWG, i32, i32, env) +DEF_HELPER_FLAGS_2(set_rmode, TCG_CALL_NO_RWG, i32, i32, ptr) DEF_HELPER_FLAGS_2(set_neon_rmode, TCG_CALL_NO_RWG, i32, i32, env) DEF_HELPER_2(vfp_fcvt_f16_to_f32, f32, i32, env) @@ -182,8 +192,10 @@ DEF_HELPER_4(vfp_muladds, f32, f32, f32, f32, ptr) DEF_HELPER_3(recps_f32, f32, f32, f32, env) DEF_HELPER_3(rsqrts_f32, f32, f32, f32, env) +DEF_HELPER_FLAGS_2(recpe_f16, TCG_CALL_NO_RWG, f16, f16, ptr) DEF_HELPER_FLAGS_2(recpe_f32, TCG_CALL_NO_RWG, f32, f32, ptr) DEF_HELPER_FLAGS_2(recpe_f64, TCG_CALL_NO_RWG, f64, f64, ptr) +DEF_HELPER_FLAGS_2(rsqrte_f16, TCG_CALL_NO_RWG, f16, f16, ptr) DEF_HELPER_FLAGS_2(rsqrte_f32, TCG_CALL_NO_RWG, f32, f32, ptr) DEF_HELPER_FLAGS_2(rsqrte_f64, TCG_CALL_NO_RWG, f64, f64, ptr) DEF_HELPER_2(recpe_u32, i32, i32, ptr) diff --git a/target/arm/translate-a64.c b/target/arm/translate-a64.c index 1c88539d62..32811dc8b0 100644 --- a/target/arm/translate-a64.c +++ b/target/arm/translate-a64.c @@ -637,16 +637,21 @@ static void write_fp_sreg(DisasContext *s, int reg, TCGv_i32 v) tcg_temp_free_i64(tmp); } -static TCGv_ptr get_fpstatus_ptr(void) +static TCGv_ptr get_fpstatus_ptr(bool is_f16) { TCGv_ptr statusptr = tcg_temp_new_ptr(); int offset; - /* In A64 all instructions (both FP and Neon) use the FPCR; - * there is no equivalent of the A32 Neon "standard FPSCR value" - * and all operations use vfp.fp_status. + /* In A64 all instructions (both FP and Neon) use the FPCR; there + * is no equivalent of the A32 Neon "standard FPSCR value". + * However half-precision operations operate under a different + * FZ16 flag and use vfp.fp_status_f16 instead of vfp.fp_status. */ - offset = offsetof(CPUARMState, vfp.fp_status); + if (is_f16) { + offset = offsetof(CPUARMState, vfp.fp_status_f16); + } else { + offset = offsetof(CPUARMState, vfp.fp_status); + } tcg_gen_addi_ptr(statusptr, cpu_env, offset); return statusptr; } @@ -4423,7 +4428,7 @@ static void handle_fp_compare(DisasContext *s, bool is_double, bool cmp_with_zero, bool signal_all_nans) { TCGv_i64 tcg_flags = tcg_temp_new_i64(); - TCGv_ptr fpst = get_fpstatus_ptr(); + TCGv_ptr fpst = get_fpstatus_ptr(false); if (is_double) { TCGv_i64 tcg_vn, tcg_vm; @@ -4591,6 +4596,65 @@ static void disas_fp_csel(DisasContext *s, uint32_t insn) tcg_temp_free_i64(t_true); } +/* Floating-point data-processing (1 source) - half precision */ +static void handle_fp_1src_half(DisasContext *s, int opcode, int rd, int rn) +{ + TCGv_ptr fpst = NULL; + TCGv_i32 tcg_op = tcg_temp_new_i32(); + TCGv_i32 tcg_res = tcg_temp_new_i32(); + + read_vec_element_i32(s, tcg_op, rn, 0, MO_16); + + switch (opcode) { + case 0x0: /* FMOV */ + tcg_gen_mov_i32(tcg_res, tcg_op); + break; + case 0x1: /* FABS */ + tcg_gen_andi_i32(tcg_res, tcg_op, 0x7fff); + break; + case 0x2: /* FNEG */ + tcg_gen_xori_i32(tcg_res, tcg_op, 0x8000); + break; + case 0x3: /* FSQRT */ + gen_helper_sqrt_f16(tcg_res, tcg_op, cpu_env); + break; + case 0x8: /* FRINTN */ + case 0x9: /* FRINTP */ + case 0xa: /* FRINTM */ + case 0xb: /* FRINTZ */ + case 0xc: /* FRINTA */ + { + TCGv_i32 tcg_rmode = tcg_const_i32(arm_rmode_to_sf(opcode & 7)); + fpst = get_fpstatus_ptr(true); + + gen_helper_set_rmode(tcg_rmode, tcg_rmode, fpst); + gen_helper_advsimd_rinth(tcg_res, tcg_op, fpst); + + gen_helper_set_rmode(tcg_rmode, tcg_rmode, fpst); + tcg_temp_free_i32(tcg_rmode); + break; + } + case 0xe: /* FRINTX */ + fpst = get_fpstatus_ptr(true); + gen_helper_advsimd_rinth_exact(tcg_res, tcg_op, fpst); + break; + case 0xf: /* FRINTI */ + fpst = get_fpstatus_ptr(true); + gen_helper_advsimd_rinth(tcg_res, tcg_op, fpst); + break; + default: + abort(); + } + + write_fp_sreg(s, rd, tcg_res); + + if (fpst) { + tcg_temp_free_ptr(fpst); + } + tcg_temp_free_i32(tcg_op); + tcg_temp_free_i32(tcg_res); +} + /* Floating-point data-processing (1 source) - single precision */ static void handle_fp_1src_single(DisasContext *s, int opcode, int rd, int rn) { @@ -4598,7 +4662,7 @@ static void handle_fp_1src_single(DisasContext *s, int opcode, int rd, int rn) TCGv_i32 tcg_op; TCGv_i32 tcg_res; - fpst = get_fpstatus_ptr(); + fpst = get_fpstatus_ptr(false); tcg_op = read_fp_sreg(s, rn); tcg_res = tcg_temp_new_i32(); @@ -4623,10 +4687,10 @@ static void handle_fp_1src_single(DisasContext *s, int opcode, int rd, int rn) { TCGv_i32 tcg_rmode = tcg_const_i32(arm_rmode_to_sf(opcode & 7)); - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, fpst); gen_helper_rints(tcg_res, tcg_op, fpst); - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, fpst); tcg_temp_free_i32(tcg_rmode); break; } @@ -4660,7 +4724,7 @@ static void handle_fp_1src_double(DisasContext *s, int opcode, int rd, int rn) return; } - fpst = get_fpstatus_ptr(); + fpst = get_fpstatus_ptr(false); tcg_op = read_fp_dreg(s, rn); tcg_res = tcg_temp_new_i64(); @@ -4682,10 +4746,10 @@ static void handle_fp_1src_double(DisasContext *s, int opcode, int rd, int rn) { TCGv_i32 tcg_rmode = tcg_const_i32(arm_rmode_to_sf(opcode & 7)); - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, fpst); gen_helper_rintd(tcg_res, tcg_op, fpst); - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, fpst); tcg_temp_free_i32(tcg_rmode); break; } @@ -4820,6 +4884,18 @@ static void disas_fp_1src(DisasContext *s, uint32_t insn) handle_fp_1src_double(s, opcode, rd, rn); break; + case 3: + if (!arm_dc_feature(s, ARM_FEATURE_V8_FP16)) { + unallocated_encoding(s); + return; + } + + if (!fp_access_check(s)) { + return; + } + + handle_fp_1src_half(s, opcode, rd, rn); + break; default: unallocated_encoding(s); } @@ -4840,7 +4916,7 @@ static void handle_fp_2src_single(DisasContext *s, int opcode, TCGv_ptr fpst; tcg_res = tcg_temp_new_i32(); - fpst = get_fpstatus_ptr(); + fpst = get_fpstatus_ptr(false); tcg_op1 = read_fp_sreg(s, rn); tcg_op2 = read_fp_sreg(s, rm); @@ -4893,7 +4969,7 @@ static void handle_fp_2src_double(DisasContext *s, int opcode, TCGv_ptr fpst; tcg_res = tcg_temp_new_i64(); - fpst = get_fpstatus_ptr(); + fpst = get_fpstatus_ptr(false); tcg_op1 = read_fp_dreg(s, rn); tcg_op2 = read_fp_dreg(s, rm); @@ -4979,7 +5055,7 @@ static void handle_fp_3src_single(DisasContext *s, bool o0, bool o1, { TCGv_i32 tcg_op1, tcg_op2, tcg_op3; TCGv_i32 tcg_res = tcg_temp_new_i32(); - TCGv_ptr fpst = get_fpstatus_ptr(); + TCGv_ptr fpst = get_fpstatus_ptr(false); tcg_op1 = read_fp_sreg(s, rn); tcg_op2 = read_fp_sreg(s, rm); @@ -5017,7 +5093,7 @@ static void handle_fp_3src_double(DisasContext *s, bool o0, bool o1, { TCGv_i64 tcg_op1, tcg_op2, tcg_op3; TCGv_i64 tcg_res = tcg_temp_new_i64(); - TCGv_ptr fpst = get_fpstatus_ptr(); + TCGv_ptr fpst = get_fpstatus_ptr(false); tcg_op1 = read_fp_dreg(s, rn); tcg_op2 = read_fp_dreg(s, rm); @@ -5158,7 +5234,7 @@ static void handle_fpfpcvt(DisasContext *s, int rd, int rn, int opcode, TCGv_ptr tcg_fpstatus; TCGv_i32 tcg_shift; - tcg_fpstatus = get_fpstatus_ptr(); + tcg_fpstatus = get_fpstatus_ptr(false); tcg_shift = tcg_const_i32(64 - scale); @@ -5212,7 +5288,7 @@ static void handle_fpfpcvt(DisasContext *s, int rd, int rn, int opcode, tcg_rmode = tcg_const_i32(arm_rmode_to_sf(rmode)); - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, tcg_fpstatus); if (is_double) { TCGv_i64 tcg_double = read_fp_dreg(s, rn); @@ -5259,7 +5335,7 @@ static void handle_fpfpcvt(DisasContext *s, int rd, int rn, int opcode, tcg_temp_free_i32(tcg_single); } - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, tcg_fpstatus); tcg_temp_free_i32(tcg_rmode); if (!sf) { @@ -5736,26 +5812,75 @@ static void disas_simd_zip_trn(DisasContext *s, uint32_t insn) tcg_temp_free_i64(tcg_resh); } -static void do_minmaxop(DisasContext *s, TCGv_i32 tcg_elt1, TCGv_i32 tcg_elt2, - int opc, bool is_min, TCGv_ptr fpst) -{ - /* Helper function for disas_simd_across_lanes: do a single precision - * min/max operation on the specified two inputs, - * and return the result in tcg_elt1. - */ - if (opc == 0xc) { - if (is_min) { - gen_helper_vfp_minnums(tcg_elt1, tcg_elt1, tcg_elt2, fpst); - } else { - gen_helper_vfp_maxnums(tcg_elt1, tcg_elt1, tcg_elt2, fpst); - } +/* + * do_reduction_op helper + * + * This mirrors the Reduce() pseudocode in the ARM ARM. It is + * important for correct NaN propagation that we do these + * operations in exactly the order specified by the pseudocode. + * + * This is a recursive function, TCG temps should be freed by the + * calling function once it is done with the values. + */ +static TCGv_i32 do_reduction_op(DisasContext *s, int fpopcode, int rn, + int esize, int size, int vmap, TCGv_ptr fpst) +{ + if (esize == size) { + int element; + TCGMemOp msize = esize == 16 ? MO_16 : MO_32; + TCGv_i32 tcg_elem; + + /* We should have one register left here */ + assert(ctpop8(vmap) == 1); + element = ctz32(vmap); + assert(element < 8); + + tcg_elem = tcg_temp_new_i32(); + read_vec_element_i32(s, tcg_elem, rn, element, msize); + return tcg_elem; } else { - assert(opc == 0xf); - if (is_min) { - gen_helper_vfp_mins(tcg_elt1, tcg_elt1, tcg_elt2, fpst); - } else { - gen_helper_vfp_maxs(tcg_elt1, tcg_elt1, tcg_elt2, fpst); + int bits = size / 2; + int shift = ctpop8(vmap) / 2; + int vmap_lo = (vmap >> shift) & vmap; + int vmap_hi = (vmap & ~vmap_lo); + TCGv_i32 tcg_hi, tcg_lo, tcg_res; + + tcg_hi = do_reduction_op(s, fpopcode, rn, esize, bits, vmap_hi, fpst); + tcg_lo = do_reduction_op(s, fpopcode, rn, esize, bits, vmap_lo, fpst); + tcg_res = tcg_temp_new_i32(); + + switch (fpopcode) { + case 0x0c: /* fmaxnmv half-precision */ + gen_helper_advsimd_maxnumh(tcg_res, tcg_lo, tcg_hi, fpst); + break; + case 0x0f: /* fmaxv half-precision */ + gen_helper_advsimd_maxh(tcg_res, tcg_lo, tcg_hi, fpst); + break; + case 0x1c: /* fminnmv half-precision */ + gen_helper_advsimd_minnumh(tcg_res, tcg_lo, tcg_hi, fpst); + break; + case 0x1f: /* fminv half-precision */ + gen_helper_advsimd_minh(tcg_res, tcg_lo, tcg_hi, fpst); + break; + case 0x2c: /* fmaxnmv */ + gen_helper_vfp_maxnums(tcg_res, tcg_lo, tcg_hi, fpst); + break; + case 0x2f: /* fmaxv */ + gen_helper_vfp_maxs(tcg_res, tcg_lo, tcg_hi, fpst); + break; + case 0x3c: /* fminnmv */ + gen_helper_vfp_minnums(tcg_res, tcg_lo, tcg_hi, fpst); + break; + case 0x3f: /* fminv */ + gen_helper_vfp_mins(tcg_res, tcg_lo, tcg_hi, fpst); + break; + default: + g_assert_not_reached(); } + + tcg_temp_free_i32(tcg_hi); + tcg_temp_free_i32(tcg_lo); + return tcg_res; } } @@ -5797,16 +5922,21 @@ static void disas_simd_across_lanes(DisasContext *s, uint32_t insn) break; case 0xc: /* FMAXNMV, FMINNMV */ case 0xf: /* FMAXV, FMINV */ - if (!is_u || !is_q || extract32(size, 0, 1)) { - unallocated_encoding(s); - return; - } - /* Bit 1 of size field encodes min vs max, and actual size is always - * 32 bits: adjust the size variable so following code can rely on it + /* Bit 1 of size field encodes min vs max and the actual size + * depends on the encoding of the U bit. If not set (and FP16 + * enabled) then we do half-precision float instead of single + * precision. */ is_min = extract32(size, 1, 1); is_fp = true; - size = 2; + if (!is_u && arm_dc_feature(s, ARM_FEATURE_V8_FP16)) { + size = 1; + } else if (!is_u || !is_q || extract32(size, 0, 1)) { + unallocated_encoding(s); + return; + } else { + size = 2; + } break; default: unallocated_encoding(s); @@ -5863,38 +5993,18 @@ static void disas_simd_across_lanes(DisasContext *s, uint32_t insn) } } else { - /* Floating point ops which work on 32 bit (single) intermediates. + /* Floating point vector reduction ops which work across 32 + * bit (single) or 16 bit (half-precision) intermediates. * Note that correct NaN propagation requires that we do these * operations in exactly the order specified by the pseudocode. */ - TCGv_i32 tcg_elt1 = tcg_temp_new_i32(); - TCGv_i32 tcg_elt2 = tcg_temp_new_i32(); - TCGv_i32 tcg_elt3 = tcg_temp_new_i32(); - TCGv_ptr fpst = get_fpstatus_ptr(); - - assert(esize == 32); - assert(elements == 4); - - read_vec_element(s, tcg_elt, rn, 0, MO_32); - tcg_gen_extrl_i64_i32(tcg_elt1, tcg_elt); - read_vec_element(s, tcg_elt, rn, 1, MO_32); - tcg_gen_extrl_i64_i32(tcg_elt2, tcg_elt); - - do_minmaxop(s, tcg_elt1, tcg_elt2, opcode, is_min, fpst); - - read_vec_element(s, tcg_elt, rn, 2, MO_32); - tcg_gen_extrl_i64_i32(tcg_elt2, tcg_elt); - read_vec_element(s, tcg_elt, rn, 3, MO_32); - tcg_gen_extrl_i64_i32(tcg_elt3, tcg_elt); - - do_minmaxop(s, tcg_elt2, tcg_elt3, opcode, is_min, fpst); - - do_minmaxop(s, tcg_elt1, tcg_elt2, opcode, is_min, fpst); - - tcg_gen_extu_i32_i64(tcg_res, tcg_elt1); - tcg_temp_free_i32(tcg_elt1); - tcg_temp_free_i32(tcg_elt2); - tcg_temp_free_i32(tcg_elt3); + TCGv_ptr fpst = get_fpstatus_ptr(size == MO_16); + int fpopcode = opcode | is_min << 4 | is_u << 5; + int vmap = (1 << elements) - 1; + TCGv_i32 tcg_res32 = do_reduction_op(s, fpopcode, rn, esize, + (is_q ? 128 : 64), vmap, fpst); + tcg_gen_extu_i32_i64(tcg_res, tcg_res32); + tcg_temp_free_i32(tcg_res32); tcg_temp_free_ptr(fpst); } @@ -6195,6 +6305,8 @@ static void disas_simd_copy(DisasContext *s, uint32_t insn) * MVNI - move inverted (shifted) imm into register * ORR - bitwise OR of (shifted) imm with register * BIC - bitwise clear of (shifted) imm with register + * With ARMv8.2 we also have: + * FMOV half-precision */ static void disas_simd_mod_imm(DisasContext *s, uint32_t insn) { @@ -6209,8 +6321,11 @@ static void disas_simd_mod_imm(DisasContext *s, uint32_t insn) uint64_t imm = 0; if (o2 != 0 || ((cmode == 0xf) && is_neg && !is_q)) { - unallocated_encoding(s); - return; + /* Check for FMOV (vector, immediate) - half-precision */ + if (!(arm_dc_feature(s, ARM_FEATURE_V8_FP16) && o2 && cmode == 0xf)) { + unallocated_encoding(s); + return; + } } if (!fp_access_check(s)) { @@ -6268,19 +6383,29 @@ static void disas_simd_mod_imm(DisasContext *s, uint32_t insn) imm |= 0x4000000000000000ULL; } } else { - imm = (abcdefgh & 0x3f) << 19; - if (abcdefgh & 0x80) { - imm |= 0x80000000; - } - if (abcdefgh & 0x40) { - imm |= 0x3e000000; + if (o2) { + /* FMOV (vector, immediate) - half-precision */ + imm = vfp_expand_imm(MO_16, abcdefgh); + /* now duplicate across the lanes */ + imm = bitfield_replicate(imm, 16); } else { - imm |= 0x40000000; + imm = (abcdefgh & 0x3f) << 19; + if (abcdefgh & 0x80) { + imm |= 0x80000000; + } + if (abcdefgh & 0x40) { + imm |= 0x3e000000; + } else { + imm |= 0x40000000; + } + imm |= (imm << 32); } - imm |= (imm << 32); } } break; + default: + fprintf(stderr, "%s: cmode_3_1: %x\n", __func__, cmode_3_1); + g_assert_not_reached(); } if (cmode_3_1 != 7 && is_neg) { @@ -6362,24 +6487,30 @@ static void disas_simd_scalar_pairwise(DisasContext *s, uint32_t insn) case 0xf: /* FMAXP */ case 0x2c: /* FMINNMP */ case 0x2f: /* FMINP */ - /* FP op, size[0] is 32 or 64 bit */ + /* FP op, size[0] is 32 or 64 bit*/ if (!u) { - unallocated_encoding(s); - return; + if (!arm_dc_feature(s, ARM_FEATURE_V8_FP16)) { + unallocated_encoding(s); + return; + } else { + size = MO_16; + } + } else { + size = extract32(size, 0, 1) ? MO_64 : MO_32; } + if (!fp_access_check(s)) { return; } - size = extract32(size, 0, 1) ? 3 : 2; - fpst = get_fpstatus_ptr(); + fpst = get_fpstatus_ptr(size == MO_16); break; default: unallocated_encoding(s); return; } - if (size == 3) { + if (size == MO_64) { TCGv_i64 tcg_op1 = tcg_temp_new_i64(); TCGv_i64 tcg_op2 = tcg_temp_new_i64(); TCGv_i64 tcg_res = tcg_temp_new_i64(); @@ -6420,27 +6551,49 @@ static void disas_simd_scalar_pairwise(DisasContext *s, uint32_t insn) TCGv_i32 tcg_op2 = tcg_temp_new_i32(); TCGv_i32 tcg_res = tcg_temp_new_i32(); - read_vec_element_i32(s, tcg_op1, rn, 0, MO_32); - read_vec_element_i32(s, tcg_op2, rn, 1, MO_32); + read_vec_element_i32(s, tcg_op1, rn, 0, size); + read_vec_element_i32(s, tcg_op2, rn, 1, size); - switch (opcode) { - case 0xc: /* FMAXNMP */ - gen_helper_vfp_maxnums(tcg_res, tcg_op1, tcg_op2, fpst); - break; - case 0xd: /* FADDP */ - gen_helper_vfp_adds(tcg_res, tcg_op1, tcg_op2, fpst); - break; - case 0xf: /* FMAXP */ - gen_helper_vfp_maxs(tcg_res, tcg_op1, tcg_op2, fpst); - break; - case 0x2c: /* FMINNMP */ - gen_helper_vfp_minnums(tcg_res, tcg_op1, tcg_op2, fpst); - break; - case 0x2f: /* FMINP */ - gen_helper_vfp_mins(tcg_res, tcg_op1, tcg_op2, fpst); - break; - default: - g_assert_not_reached(); + if (size == MO_16) { + switch (opcode) { + case 0xc: /* FMAXNMP */ + gen_helper_advsimd_maxnumh(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0xd: /* FADDP */ + gen_helper_advsimd_addh(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0xf: /* FMAXP */ + gen_helper_advsimd_maxh(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x2c: /* FMINNMP */ + gen_helper_advsimd_minnumh(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x2f: /* FMINP */ + gen_helper_advsimd_minh(tcg_res, tcg_op1, tcg_op2, fpst); + break; + default: + g_assert_not_reached(); + } + } else { + switch (opcode) { + case 0xc: /* FMAXNMP */ + gen_helper_vfp_maxnums(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0xd: /* FADDP */ + gen_helper_vfp_adds(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0xf: /* FMAXP */ + gen_helper_vfp_maxs(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x2c: /* FMINNMP */ + gen_helper_vfp_minnums(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x2f: /* FMINP */ + gen_helper_vfp_mins(tcg_res, tcg_op1, tcg_op2, fpst); + break; + default: + g_assert_not_reached(); + } } write_fp_sreg(s, rd, tcg_res); @@ -6863,23 +7016,28 @@ static void handle_simd_intfp_conv(DisasContext *s, int rd, int rn, int elements, int is_signed, int fracbits, int size) { - bool is_double = size == 3 ? true : false; - TCGv_ptr tcg_fpst = get_fpstatus_ptr(); - TCGv_i32 tcg_shift = tcg_const_i32(fracbits); - TCGv_i64 tcg_int = tcg_temp_new_i64(); + TCGv_ptr tcg_fpst = get_fpstatus_ptr(size == MO_16); + TCGv_i32 tcg_shift = NULL; + TCGMemOp mop = size | (is_signed ? MO_SIGN : 0); int pass; - for (pass = 0; pass < elements; pass++) { - read_vec_element(s, tcg_int, rn, pass, mop); + if (fracbits || size == MO_64) { + tcg_shift = tcg_const_i32(fracbits); + } + + if (size == MO_64) { + TCGv_i64 tcg_int64 = tcg_temp_new_i64(); + TCGv_i64 tcg_double = tcg_temp_new_i64(); + + for (pass = 0; pass < elements; pass++) { + read_vec_element(s, tcg_int64, rn, pass, mop); - if (is_double) { - TCGv_i64 tcg_double = tcg_temp_new_i64(); if (is_signed) { - gen_helper_vfp_sqtod(tcg_double, tcg_int, + gen_helper_vfp_sqtod(tcg_double, tcg_int64, tcg_shift, tcg_fpst); } else { - gen_helper_vfp_uqtod(tcg_double, tcg_int, + gen_helper_vfp_uqtod(tcg_double, tcg_int64, tcg_shift, tcg_fpst); } if (elements == 1) { @@ -6887,28 +7045,72 @@ static void handle_simd_intfp_conv(DisasContext *s, int rd, int rn, } else { write_vec_element(s, tcg_double, rd, pass, MO_64); } - tcg_temp_free_i64(tcg_double); - } else { - TCGv_i32 tcg_single = tcg_temp_new_i32(); - if (is_signed) { - gen_helper_vfp_sqtos(tcg_single, tcg_int, - tcg_shift, tcg_fpst); - } else { - gen_helper_vfp_uqtos(tcg_single, tcg_int, - tcg_shift, tcg_fpst); + } + + tcg_temp_free_i64(tcg_int64); + tcg_temp_free_i64(tcg_double); + + } else { + TCGv_i32 tcg_int32 = tcg_temp_new_i32(); + TCGv_i32 tcg_float = tcg_temp_new_i32(); + + for (pass = 0; pass < elements; pass++) { + read_vec_element_i32(s, tcg_int32, rn, pass, mop); + + switch (size) { + case MO_32: + if (fracbits) { + if (is_signed) { + gen_helper_vfp_sltos(tcg_float, tcg_int32, + tcg_shift, tcg_fpst); + } else { + gen_helper_vfp_ultos(tcg_float, tcg_int32, + tcg_shift, tcg_fpst); + } + } else { + if (is_signed) { + gen_helper_vfp_sitos(tcg_float, tcg_int32, tcg_fpst); + } else { + gen_helper_vfp_uitos(tcg_float, tcg_int32, tcg_fpst); + } + } + break; + case MO_16: + if (fracbits) { + if (is_signed) { + gen_helper_vfp_sltoh(tcg_float, tcg_int32, + tcg_shift, tcg_fpst); + } else { + gen_helper_vfp_ultoh(tcg_float, tcg_int32, + tcg_shift, tcg_fpst); + } + } else { + if (is_signed) { + gen_helper_vfp_sitoh(tcg_float, tcg_int32, tcg_fpst); + } else { + gen_helper_vfp_uitoh(tcg_float, tcg_int32, tcg_fpst); + } + } + break; + default: + g_assert_not_reached(); } + if (elements == 1) { - write_fp_sreg(s, rd, tcg_single); + write_fp_sreg(s, rd, tcg_float); } else { - write_vec_element_i32(s, tcg_single, rd, pass, MO_32); + write_vec_element_i32(s, tcg_float, rd, pass, size); } - tcg_temp_free_i32(tcg_single); } + + tcg_temp_free_i32(tcg_int32); + tcg_temp_free_i32(tcg_float); } - tcg_temp_free_i64(tcg_int); tcg_temp_free_ptr(tcg_fpst); - tcg_temp_free_i32(tcg_shift); + if (tcg_shift) { + tcg_temp_free_i32(tcg_shift); + } clear_vec_high(s, elements << size == 16, rd); } @@ -6979,8 +7181,8 @@ static void handle_simd_shift_fpint_conv(DisasContext *s, bool is_scalar, assert(!(is_scalar && is_q)); tcg_rmode = tcg_const_i32(arm_rmode_to_sf(FPROUNDING_ZERO)); - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); - tcg_fpstatus = get_fpstatus_ptr(); + tcg_fpstatus = get_fpstatus_ptr(false); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, tcg_fpstatus); tcg_shift = tcg_const_i32(fracbits); if (is_double) { @@ -7024,7 +7226,7 @@ static void handle_simd_shift_fpint_conv(DisasContext *s, bool is_scalar, tcg_temp_free_ptr(tcg_fpstatus); tcg_temp_free_i32(tcg_shift); - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, tcg_fpstatus); tcg_temp_free_i32(tcg_rmode); } @@ -7239,8 +7441,7 @@ static void handle_3same_64(DisasContext *s, int opcode, bool u, /* Handle 64x64->64 opcodes which are shared between the scalar * and vector 3-same groups. We cover every opcode where size == 3 * is valid in either the three-reg-same (integer, not pairwise) - * or scalar-three-reg-same groups. (Some opcodes are not yet - * implemented.) + * or scalar-three-reg-same groups. */ TCGCond cond; @@ -7326,7 +7527,7 @@ static void handle_3same_float(DisasContext *s, int size, int elements, int fpopcode, int rd, int rn, int rm) { int pass; - TCGv_ptr fpst = get_fpstatus_ptr(); + TCGv_ptr fpst = get_fpstatus_ptr(false); for (pass = 0; pass < elements; pass++) { if (size) { @@ -7672,6 +7873,104 @@ static void disas_simd_scalar_three_reg_same(DisasContext *s, uint32_t insn) tcg_temp_free_i64(tcg_rd); } +/* AdvSIMD scalar three same FP16 + * 31 30 29 28 24 23 22 21 20 16 15 14 13 11 10 9 5 4 0 + * +-----+---+-----------+---+-----+------+-----+--------+---+----+----+ + * | 0 1 | U | 1 1 1 1 0 | a | 1 0 | Rm | 0 0 | opcode | 1 | Rn | Rd | + * +-----+---+-----------+---+-----+------+-----+--------+---+----+----+ + * v: 0101 1110 0100 0000 0000 0100 0000 0000 => 5e400400 + * m: 1101 1111 0110 0000 1100 0100 0000 0000 => df60c400 + */ +static void disas_simd_scalar_three_reg_same_fp16(DisasContext *s, + uint32_t insn) +{ + int rd = extract32(insn, 0, 5); + int rn = extract32(insn, 5, 5); + int opcode = extract32(insn, 11, 3); + int rm = extract32(insn, 16, 5); + bool u = extract32(insn, 29, 1); + bool a = extract32(insn, 23, 1); + int fpopcode = opcode | (a << 3) | (u << 4); + TCGv_ptr fpst; + TCGv_i32 tcg_op1; + TCGv_i32 tcg_op2; + TCGv_i32 tcg_res; + + switch (fpopcode) { + case 0x03: /* FMULX */ + case 0x04: /* FCMEQ (reg) */ + case 0x07: /* FRECPS */ + case 0x0f: /* FRSQRTS */ + case 0x14: /* FCMGE (reg) */ + case 0x15: /* FACGE */ + case 0x1a: /* FABD */ + case 0x1c: /* FCMGT (reg) */ + case 0x1d: /* FACGT */ + break; + default: + unallocated_encoding(s); + return; + } + + if (!arm_dc_feature(s, ARM_FEATURE_V8_FP16)) { + unallocated_encoding(s); + } + + if (!fp_access_check(s)) { + return; + } + + fpst = get_fpstatus_ptr(true); + + tcg_op1 = tcg_temp_new_i32(); + tcg_op2 = tcg_temp_new_i32(); + tcg_res = tcg_temp_new_i32(); + + read_vec_element_i32(s, tcg_op1, rn, 0, MO_16); + read_vec_element_i32(s, tcg_op2, rm, 0, MO_16); + + switch (fpopcode) { + case 0x03: /* FMULX */ + gen_helper_advsimd_mulxh(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x04: /* FCMEQ (reg) */ + gen_helper_advsimd_ceq_f16(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x07: /* FRECPS */ + gen_helper_recpsf_f16(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x0f: /* FRSQRTS */ + gen_helper_rsqrtsf_f16(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x14: /* FCMGE (reg) */ + gen_helper_advsimd_cge_f16(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x15: /* FACGE */ + gen_helper_advsimd_acge_f16(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x1a: /* FABD */ + gen_helper_advsimd_subh(tcg_res, tcg_op1, tcg_op2, fpst); + tcg_gen_andi_i32(tcg_res, tcg_res, 0x7fff); + break; + case 0x1c: /* FCMGT (reg) */ + gen_helper_advsimd_cgt_f16(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x1d: /* FACGT */ + gen_helper_advsimd_acgt_f16(tcg_res, tcg_op1, tcg_op2, fpst); + break; + default: + g_assert_not_reached(); + } + + write_fp_sreg(s, rd, tcg_res); + + + tcg_temp_free_i32(tcg_res); + tcg_temp_free_i32(tcg_op1); + tcg_temp_free_i32(tcg_op2); + tcg_temp_free_ptr(fpst); +} + static void handle_2misc_64(DisasContext *s, int opcode, bool u, TCGv_i64 tcg_rd, TCGv_i64 tcg_rn, TCGv_i32 tcg_rmode, TCGv_ptr tcg_fpstatus) @@ -7783,14 +8082,14 @@ static void handle_2misc_fcmp_zero(DisasContext *s, int opcode, bool is_scalar, bool is_u, bool is_q, int size, int rn, int rd) { - bool is_double = (size == 3); + bool is_double = (size == MO_64); TCGv_ptr fpst; if (!fp_access_check(s)) { return; } - fpst = get_fpstatus_ptr(); + fpst = get_fpstatus_ptr(size == MO_16); if (is_double) { TCGv_i64 tcg_op = tcg_temp_new_i64(); @@ -7842,34 +8141,57 @@ static void handle_2misc_fcmp_zero(DisasContext *s, int opcode, bool swap = false; int pass, maxpasses; - switch (opcode) { - case 0x2e: /* FCMLT (zero) */ - swap = true; - /* fall through */ - case 0x2c: /* FCMGT (zero) */ - genfn = gen_helper_neon_cgt_f32; - break; - case 0x2d: /* FCMEQ (zero) */ - genfn = gen_helper_neon_ceq_f32; - break; - case 0x6d: /* FCMLE (zero) */ - swap = true; - /* fall through */ - case 0x6c: /* FCMGE (zero) */ - genfn = gen_helper_neon_cge_f32; - break; - default: - g_assert_not_reached(); + if (size == MO_16) { + switch (opcode) { + case 0x2e: /* FCMLT (zero) */ + swap = true; + /* fall through */ + case 0x2c: /* FCMGT (zero) */ + genfn = gen_helper_advsimd_cgt_f16; + break; + case 0x2d: /* FCMEQ (zero) */ + genfn = gen_helper_advsimd_ceq_f16; + break; + case 0x6d: /* FCMLE (zero) */ + swap = true; + /* fall through */ + case 0x6c: /* FCMGE (zero) */ + genfn = gen_helper_advsimd_cge_f16; + break; + default: + g_assert_not_reached(); + } + } else { + switch (opcode) { + case 0x2e: /* FCMLT (zero) */ + swap = true; + /* fall through */ + case 0x2c: /* FCMGT (zero) */ + genfn = gen_helper_neon_cgt_f32; + break; + case 0x2d: /* FCMEQ (zero) */ + genfn = gen_helper_neon_ceq_f32; + break; + case 0x6d: /* FCMLE (zero) */ + swap = true; + /* fall through */ + case 0x6c: /* FCMGE (zero) */ + genfn = gen_helper_neon_cge_f32; + break; + default: + g_assert_not_reached(); + } } if (is_scalar) { maxpasses = 1; } else { - maxpasses = is_q ? 4 : 2; + int vector_size = 8 << is_q; + maxpasses = vector_size >> size; } for (pass = 0; pass < maxpasses; pass++) { - read_vec_element_i32(s, tcg_op, rn, pass, MO_32); + read_vec_element_i32(s, tcg_op, rn, pass, size); if (swap) { genfn(tcg_res, tcg_zero, tcg_op, fpst); } else { @@ -7878,7 +8200,7 @@ static void handle_2misc_fcmp_zero(DisasContext *s, int opcode, if (is_scalar) { write_fp_sreg(s, rd, tcg_res); } else { - write_vec_element_i32(s, tcg_res, rd, pass, MO_32); + write_vec_element_i32(s, tcg_res, rd, pass, size); } } tcg_temp_free_i32(tcg_res); @@ -7897,7 +8219,7 @@ static void handle_2misc_reciprocal(DisasContext *s, int opcode, int size, int rn, int rd) { bool is_double = (size == 3); - TCGv_ptr fpst = get_fpstatus_ptr(); + TCGv_ptr fpst = get_fpstatus_ptr(false); if (is_double) { TCGv_i64 tcg_op = tcg_temp_new_i64(); @@ -8295,8 +8617,8 @@ static void disas_simd_scalar_two_reg_misc(DisasContext *s, uint32_t insn) if (is_fcvt) { tcg_rmode = tcg_const_i32(arm_rmode_to_sf(rmode)); - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); - tcg_fpstatus = get_fpstatus_ptr(); + tcg_fpstatus = get_fpstatus_ptr(false); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, tcg_fpstatus); } else { tcg_rmode = NULL; tcg_fpstatus = NULL; @@ -8361,7 +8683,7 @@ static void disas_simd_scalar_two_reg_misc(DisasContext *s, uint32_t insn) } if (is_fcvt) { - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, tcg_fpstatus); tcg_temp_free_i32(tcg_rmode); tcg_temp_free_ptr(tcg_fpstatus); } @@ -9516,7 +9838,7 @@ static void handle_simd_3same_pair(DisasContext *s, int is_q, int u, int opcode, /* Floating point operations need fpst */ if (opcode >= 0x58) { - fpst = get_fpstatus_ptr(); + fpst = get_fpstatus_ptr(false); } else { fpst = NULL; } @@ -10190,6 +10512,200 @@ static void disas_simd_three_reg_same(DisasContext *s, uint32_t insn) } } +/* + * Advanced SIMD three same (ARMv8.2 FP16 variants) + * + * 31 30 29 28 24 23 22 21 20 16 15 14 13 11 10 9 5 4 0 + * +---+---+---+-----------+---------+------+-----+--------+---+------+------+ + * | 0 | Q | U | 0 1 1 1 0 | a | 1 0 | Rm | 0 0 | opcode | 1 | Rn | Rd | + * +---+---+---+-----------+---------+------+-----+--------+---+------+------+ + * + * This includes FMULX, FCMEQ (register), FRECPS, FRSQRTS, FCMGE + * (register), FACGE, FABD, FCMGT (register) and FACGT. + * + */ +static void disas_simd_three_reg_same_fp16(DisasContext *s, uint32_t insn) +{ + int opcode, fpopcode; + int is_q, u, a, rm, rn, rd; + int datasize, elements; + int pass; + TCGv_ptr fpst; + bool pairwise = false; + + if (!arm_dc_feature(s, ARM_FEATURE_V8_FP16)) { + unallocated_encoding(s); + return; + } + + if (!fp_access_check(s)) { + return; + } + + /* For these floating point ops, the U, a and opcode bits + * together indicate the operation. + */ + opcode = extract32(insn, 11, 3); + u = extract32(insn, 29, 1); + a = extract32(insn, 23, 1); + is_q = extract32(insn, 30, 1); + rm = extract32(insn, 16, 5); + rn = extract32(insn, 5, 5); + rd = extract32(insn, 0, 5); + + fpopcode = opcode | (a << 3) | (u << 4); + datasize = is_q ? 128 : 64; + elements = datasize / 16; + + switch (fpopcode) { + case 0x10: /* FMAXNMP */ + case 0x12: /* FADDP */ + case 0x16: /* FMAXP */ + case 0x18: /* FMINNMP */ + case 0x1e: /* FMINP */ + pairwise = true; + break; + } + + fpst = get_fpstatus_ptr(true); + + if (pairwise) { + int maxpass = is_q ? 8 : 4; + TCGv_i32 tcg_op1 = tcg_temp_new_i32(); + TCGv_i32 tcg_op2 = tcg_temp_new_i32(); + TCGv_i32 tcg_res[8]; + + for (pass = 0; pass < maxpass; pass++) { + int passreg = pass < (maxpass / 2) ? rn : rm; + int passelt = (pass << 1) & (maxpass - 1); + + read_vec_element_i32(s, tcg_op1, passreg, passelt, MO_16); + read_vec_element_i32(s, tcg_op2, passreg, passelt + 1, MO_16); + tcg_res[pass] = tcg_temp_new_i32(); + + switch (fpopcode) { + case 0x10: /* FMAXNMP */ + gen_helper_advsimd_maxnumh(tcg_res[pass], tcg_op1, tcg_op2, + fpst); + break; + case 0x12: /* FADDP */ + gen_helper_advsimd_addh(tcg_res[pass], tcg_op1, tcg_op2, fpst); + break; + case 0x16: /* FMAXP */ + gen_helper_advsimd_maxh(tcg_res[pass], tcg_op1, tcg_op2, fpst); + break; + case 0x18: /* FMINNMP */ + gen_helper_advsimd_minnumh(tcg_res[pass], tcg_op1, tcg_op2, + fpst); + break; + case 0x1e: /* FMINP */ + gen_helper_advsimd_minh(tcg_res[pass], tcg_op1, tcg_op2, fpst); + break; + default: + g_assert_not_reached(); + } + } + + for (pass = 0; pass < maxpass; pass++) { + write_vec_element_i32(s, tcg_res[pass], rd, pass, MO_16); + tcg_temp_free_i32(tcg_res[pass]); + } + + tcg_temp_free_i32(tcg_op1); + tcg_temp_free_i32(tcg_op2); + + } else { + for (pass = 0; pass < elements; pass++) { + TCGv_i32 tcg_op1 = tcg_temp_new_i32(); + TCGv_i32 tcg_op2 = tcg_temp_new_i32(); + TCGv_i32 tcg_res = tcg_temp_new_i32(); + + read_vec_element_i32(s, tcg_op1, rn, pass, MO_16); + read_vec_element_i32(s, tcg_op2, rm, pass, MO_16); + + switch (fpopcode) { + case 0x0: /* FMAXNM */ + gen_helper_advsimd_maxnumh(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x1: /* FMLA */ + read_vec_element_i32(s, tcg_res, rd, pass, MO_16); + gen_helper_advsimd_muladdh(tcg_res, tcg_op1, tcg_op2, tcg_res, + fpst); + break; + case 0x2: /* FADD */ + gen_helper_advsimd_addh(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x3: /* FMULX */ + gen_helper_advsimd_mulxh(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x4: /* FCMEQ */ + gen_helper_advsimd_ceq_f16(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x6: /* FMAX */ + gen_helper_advsimd_maxh(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x7: /* FRECPS */ + gen_helper_recpsf_f16(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x8: /* FMINNM */ + gen_helper_advsimd_minnumh(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x9: /* FMLS */ + /* As usual for ARM, separate negation for fused multiply-add */ + tcg_gen_xori_i32(tcg_op1, tcg_op1, 0x8000); + read_vec_element_i32(s, tcg_res, rd, pass, MO_16); + gen_helper_advsimd_muladdh(tcg_res, tcg_op1, tcg_op2, tcg_res, + fpst); + break; + case 0xa: /* FSUB */ + gen_helper_advsimd_subh(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0xe: /* FMIN */ + gen_helper_advsimd_minh(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0xf: /* FRSQRTS */ + gen_helper_rsqrtsf_f16(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x13: /* FMUL */ + gen_helper_advsimd_mulh(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x14: /* FCMGE */ + gen_helper_advsimd_cge_f16(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x15: /* FACGE */ + gen_helper_advsimd_acge_f16(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x17: /* FDIV */ + gen_helper_advsimd_divh(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x1a: /* FABD */ + gen_helper_advsimd_subh(tcg_res, tcg_op1, tcg_op2, fpst); + tcg_gen_andi_i32(tcg_res, tcg_res, 0x7fff); + break; + case 0x1c: /* FCMGT */ + gen_helper_advsimd_cgt_f16(tcg_res, tcg_op1, tcg_op2, fpst); + break; + case 0x1d: /* FACGT */ + gen_helper_advsimd_acgt_f16(tcg_res, tcg_op1, tcg_op2, fpst); + break; + default: + fprintf(stderr, "%s: insn %#04x, fpop %#2x @ %#" PRIx64 "\n", + __func__, insn, fpopcode, s->pc); + g_assert_not_reached(); + } + + write_vec_element_i32(s, tcg_res, rd, pass, MO_16); + tcg_temp_free_i32(tcg_res); + tcg_temp_free_i32(tcg_op1); + tcg_temp_free_i32(tcg_op2); + } + } + + tcg_temp_free_ptr(fpst); + + clear_vec_high(s, is_q, rd); +} + static void handle_2misc_widening(DisasContext *s, int opcode, bool is_q, int size, int rn, int rd) { @@ -10675,14 +11191,14 @@ static void disas_simd_two_reg_misc(DisasContext *s, uint32_t insn) return; } - if (need_fpstatus) { - tcg_fpstatus = get_fpstatus_ptr(); + if (need_fpstatus || need_rmode) { + tcg_fpstatus = get_fpstatus_ptr(false); } else { tcg_fpstatus = NULL; } if (need_rmode) { tcg_rmode = tcg_const_i32(arm_rmode_to_sf(rmode)); - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, tcg_fpstatus); } else { tcg_rmode = NULL; } @@ -10924,7 +11440,7 @@ static void disas_simd_two_reg_misc(DisasContext *s, uint32_t insn) clear_vec_high(s, is_q, rd); if (need_rmode) { - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, tcg_fpstatus); tcg_temp_free_i32(tcg_rmode); } if (need_fpstatus) { @@ -10932,6 +11448,307 @@ static void disas_simd_two_reg_misc(DisasContext *s, uint32_t insn) } } +/* AdvSIMD [scalar] two register miscellaneous (FP16) + * + * 31 30 29 28 27 24 23 22 21 17 16 12 11 10 9 5 4 0 + * +---+---+---+---+---------+---+-------------+--------+-----+------+------+ + * | 0 | Q | U | S | 1 1 1 0 | a | 1 1 1 1 0 0 | opcode | 1 0 | Rn | Rd | + * +---+---+---+---+---------+---+-------------+--------+-----+------+------+ + * mask: 1000 1111 0111 1110 0000 1100 0000 0000 0x8f7e 0c00 + * val: 0000 1110 0111 1000 0000 1000 0000 0000 0x0e78 0800 + * + * This actually covers two groups where scalar access is governed by + * bit 28. A bunch of the instructions (float to integral) only exist + * in the vector form and are un-allocated for the scalar decode. Also + * in the scalar decode Q is always 1. + */ +static void disas_simd_two_reg_misc_fp16(DisasContext *s, uint32_t insn) +{ + int fpop, opcode, a, u; + int rn, rd; + bool is_q; + bool is_scalar; + bool only_in_vector = false; + + int pass; + TCGv_i32 tcg_rmode = NULL; + TCGv_ptr tcg_fpstatus = NULL; + bool need_rmode = false; + bool need_fpst = true; + int rmode; + + if (!arm_dc_feature(s, ARM_FEATURE_V8_FP16)) { + unallocated_encoding(s); + return; + } + + rd = extract32(insn, 0, 5); + rn = extract32(insn, 5, 5); + + a = extract32(insn, 23, 1); + u = extract32(insn, 29, 1); + is_scalar = extract32(insn, 28, 1); + is_q = extract32(insn, 30, 1); + + opcode = extract32(insn, 12, 5); + fpop = deposit32(opcode, 5, 1, a); + fpop = deposit32(fpop, 6, 1, u); + + rd = extract32(insn, 0, 5); + rn = extract32(insn, 5, 5); + + switch (fpop) { + case 0x1d: /* SCVTF */ + case 0x5d: /* UCVTF */ + { + int elements; + + if (is_scalar) { + elements = 1; + } else { + elements = (is_q ? 8 : 4); + } + + if (!fp_access_check(s)) { + return; + } + handle_simd_intfp_conv(s, rd, rn, elements, !u, 0, MO_16); + return; + } + break; + case 0x2c: /* FCMGT (zero) */ + case 0x2d: /* FCMEQ (zero) */ + case 0x2e: /* FCMLT (zero) */ + case 0x6c: /* FCMGE (zero) */ + case 0x6d: /* FCMLE (zero) */ + handle_2misc_fcmp_zero(s, fpop, is_scalar, 0, is_q, MO_16, rn, rd); + return; + case 0x3d: /* FRECPE */ + case 0x3f: /* FRECPX */ + break; + case 0x18: /* FRINTN */ + need_rmode = true; + only_in_vector = true; + rmode = FPROUNDING_TIEEVEN; + break; + case 0x19: /* FRINTM */ + need_rmode = true; + only_in_vector = true; + rmode = FPROUNDING_NEGINF; + break; + case 0x38: /* FRINTP */ + need_rmode = true; + only_in_vector = true; + rmode = FPROUNDING_POSINF; + break; + case 0x39: /* FRINTZ */ + need_rmode = true; + only_in_vector = true; + rmode = FPROUNDING_ZERO; + break; + case 0x58: /* FRINTA */ + need_rmode = true; + only_in_vector = true; + rmode = FPROUNDING_TIEAWAY; + break; + case 0x59: /* FRINTX */ + case 0x79: /* FRINTI */ + only_in_vector = true; + /* current rounding mode */ + break; + case 0x1a: /* FCVTNS */ + need_rmode = true; + rmode = FPROUNDING_TIEEVEN; + break; + case 0x1b: /* FCVTMS */ + need_rmode = true; + rmode = FPROUNDING_NEGINF; + break; + case 0x1c: /* FCVTAS */ + need_rmode = true; + rmode = FPROUNDING_TIEAWAY; + break; + case 0x3a: /* FCVTPS */ + need_rmode = true; + rmode = FPROUNDING_POSINF; + break; + case 0x3b: /* FCVTZS */ + need_rmode = true; + rmode = FPROUNDING_ZERO; + break; + case 0x5a: /* FCVTNU */ + need_rmode = true; + rmode = FPROUNDING_TIEEVEN; + break; + case 0x5b: /* FCVTMU */ + need_rmode = true; + rmode = FPROUNDING_NEGINF; + break; + case 0x5c: /* FCVTAU */ + need_rmode = true; + rmode = FPROUNDING_TIEAWAY; + break; + case 0x7a: /* FCVTPU */ + need_rmode = true; + rmode = FPROUNDING_POSINF; + break; + case 0x7b: /* FCVTZU */ + need_rmode = true; + rmode = FPROUNDING_ZERO; + break; + case 0x2f: /* FABS */ + case 0x6f: /* FNEG */ + need_fpst = false; + break; + case 0x7d: /* FRSQRTE */ + case 0x7f: /* FSQRT (vector) */ + break; + default: + fprintf(stderr, "%s: insn %#04x fpop %#2x\n", __func__, insn, fpop); + g_assert_not_reached(); + } + + + /* Check additional constraints for the scalar encoding */ + if (is_scalar) { + if (!is_q) { + unallocated_encoding(s); + return; + } + /* FRINTxx is only in the vector form */ + if (only_in_vector) { + unallocated_encoding(s); + return; + } + } + + if (!fp_access_check(s)) { + return; + } + + if (need_rmode || need_fpst) { + tcg_fpstatus = get_fpstatus_ptr(true); + } + + if (need_rmode) { + tcg_rmode = tcg_const_i32(arm_rmode_to_sf(rmode)); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, tcg_fpstatus); + } + + if (is_scalar) { + TCGv_i32 tcg_op = tcg_temp_new_i32(); + TCGv_i32 tcg_res = tcg_temp_new_i32(); + + read_vec_element_i32(s, tcg_op, rn, 0, MO_16); + + switch (fpop) { + case 0x1a: /* FCVTNS */ + case 0x1b: /* FCVTMS */ + case 0x1c: /* FCVTAS */ + case 0x3a: /* FCVTPS */ + case 0x3b: /* FCVTZS */ + gen_helper_advsimd_f16tosinth(tcg_res, tcg_op, tcg_fpstatus); + break; + case 0x3d: /* FRECPE */ + gen_helper_recpe_f16(tcg_res, tcg_op, tcg_fpstatus); + break; + case 0x3f: /* FRECPX */ + gen_helper_frecpx_f16(tcg_res, tcg_op, tcg_fpstatus); + break; + case 0x5a: /* FCVTNU */ + case 0x5b: /* FCVTMU */ + case 0x5c: /* FCVTAU */ + case 0x7a: /* FCVTPU */ + case 0x7b: /* FCVTZU */ + gen_helper_advsimd_f16touinth(tcg_res, tcg_op, tcg_fpstatus); + break; + case 0x6f: /* FNEG */ + tcg_gen_xori_i32(tcg_res, tcg_op, 0x8000); + break; + case 0x7d: /* FRSQRTE */ + gen_helper_rsqrte_f16(tcg_res, tcg_op, tcg_fpstatus); + break; + default: + g_assert_not_reached(); + } + + /* limit any sign extension going on */ + tcg_gen_andi_i32(tcg_res, tcg_res, 0xffff); + write_fp_sreg(s, rd, tcg_res); + + tcg_temp_free_i32(tcg_res); + tcg_temp_free_i32(tcg_op); + } else { + for (pass = 0; pass < (is_q ? 8 : 4); pass++) { + TCGv_i32 tcg_op = tcg_temp_new_i32(); + TCGv_i32 tcg_res = tcg_temp_new_i32(); + + read_vec_element_i32(s, tcg_op, rn, pass, MO_16); + + switch (fpop) { + case 0x1a: /* FCVTNS */ + case 0x1b: /* FCVTMS */ + case 0x1c: /* FCVTAS */ + case 0x3a: /* FCVTPS */ + case 0x3b: /* FCVTZS */ + gen_helper_advsimd_f16tosinth(tcg_res, tcg_op, tcg_fpstatus); + break; + case 0x3d: /* FRECPE */ + gen_helper_recpe_f16(tcg_res, tcg_op, tcg_fpstatus); + break; + case 0x5a: /* FCVTNU */ + case 0x5b: /* FCVTMU */ + case 0x5c: /* FCVTAU */ + case 0x7a: /* FCVTPU */ + case 0x7b: /* FCVTZU */ + gen_helper_advsimd_f16touinth(tcg_res, tcg_op, tcg_fpstatus); + break; + case 0x18: /* FRINTN */ + case 0x19: /* FRINTM */ + case 0x38: /* FRINTP */ + case 0x39: /* FRINTZ */ + case 0x58: /* FRINTA */ + case 0x79: /* FRINTI */ + gen_helper_advsimd_rinth(tcg_res, tcg_op, tcg_fpstatus); + break; + case 0x59: /* FRINTX */ + gen_helper_advsimd_rinth_exact(tcg_res, tcg_op, tcg_fpstatus); + break; + case 0x2f: /* FABS */ + tcg_gen_andi_i32(tcg_res, tcg_op, 0x7fff); + break; + case 0x6f: /* FNEG */ + tcg_gen_xori_i32(tcg_res, tcg_op, 0x8000); + break; + case 0x7d: /* FRSQRTE */ + gen_helper_rsqrte_f16(tcg_res, tcg_op, tcg_fpstatus); + break; + case 0x7f: /* FSQRT */ + gen_helper_sqrt_f16(tcg_res, tcg_op, tcg_fpstatus); + break; + default: + g_assert_not_reached(); + } + + write_vec_element_i32(s, tcg_res, rd, pass, MO_16); + + tcg_temp_free_i32(tcg_res); + tcg_temp_free_i32(tcg_op); + } + + clear_vec_high(s, is_q, rd); + } + + if (tcg_rmode) { + gen_helper_set_rmode(tcg_rmode, tcg_rmode, tcg_fpstatus); + tcg_temp_free_i32(tcg_rmode); + } + + if (tcg_fpstatus) { + tcg_temp_free_ptr(tcg_fpstatus); + } +} + /* AdvSIMD scalar x indexed element * 31 30 29 28 24 23 22 21 20 19 16 15 12 11 10 9 5 4 0 * +-----+---+-----------+------+---+---+------+-----+---+---+------+------+ @@ -10966,6 +11783,7 @@ static void disas_simd_indexed(DisasContext *s, uint32_t insn) int rd = extract32(insn, 0, 5); bool is_long = false; bool is_fp = false; + bool is_fp16 = false; int index; TCGv_ptr fpst; @@ -11012,7 +11830,7 @@ static void disas_simd_indexed(DisasContext *s, uint32_t insn) } /* fall through */ case 0x9: /* FMUL, FMULX */ - if (!extract32(size, 1, 1)) { + if (size == 1) { unallocated_encoding(s); return; } @@ -11024,18 +11842,34 @@ static void disas_simd_indexed(DisasContext *s, uint32_t insn) } if (is_fp) { - /* low bit of size indicates single/double */ - size = extract32(size, 0, 1) ? 3 : 2; - if (size == 2) { + /* convert insn encoded size to TCGMemOp size */ + switch (size) { + case 2: /* single precision */ + size = MO_32; index = h << 1 | l; - } else { + rm |= (m << 4); + break; + case 3: /* double precision */ + size = MO_64; if (l || !is_q) { unallocated_encoding(s); return; } index = h; + rm |= (m << 4); + break; + case 0: /* half precision */ + size = MO_16; + index = h << 2 | l << 1 | m; + is_fp16 = true; + if (arm_dc_feature(s, ARM_FEATURE_V8_FP16)) { + break; + } + /* fallthru */ + default: /* unallocated */ + unallocated_encoding(s); + return; } - rm |= (m << 4); } else { switch (size) { case 1: @@ -11056,7 +11890,7 @@ static void disas_simd_indexed(DisasContext *s, uint32_t insn) } if (is_fp) { - fpst = get_fpstatus_ptr(); + fpst = get_fpstatus_ptr(is_fp16); } else { fpst = NULL; } @@ -11158,18 +11992,67 @@ static void disas_simd_indexed(DisasContext *s, uint32_t insn) break; } case 0x5: /* FMLS */ - /* As usual for ARM, separate negation for fused multiply-add */ - gen_helper_vfp_negs(tcg_op, tcg_op); - /* fall through */ case 0x1: /* FMLA */ - read_vec_element_i32(s, tcg_res, rd, pass, MO_32); - gen_helper_vfp_muladds(tcg_res, tcg_op, tcg_idx, tcg_res, fpst); + read_vec_element_i32(s, tcg_res, rd, pass, + is_scalar ? size : MO_32); + switch (size) { + case 1: + if (opcode == 0x5) { + /* As usual for ARM, separate negation for fused + * multiply-add */ + tcg_gen_xori_i32(tcg_op, tcg_op, 0x80008000); + } + if (is_scalar) { + gen_helper_advsimd_muladdh(tcg_res, tcg_op, tcg_idx, + tcg_res, fpst); + } else { + gen_helper_advsimd_muladd2h(tcg_res, tcg_op, tcg_idx, + tcg_res, fpst); + } + break; + case 2: + if (opcode == 0x5) { + /* As usual for ARM, separate negation for + * fused multiply-add */ + tcg_gen_xori_i32(tcg_op, tcg_op, 0x80000000); + } + gen_helper_vfp_muladds(tcg_res, tcg_op, tcg_idx, + tcg_res, fpst); + break; + default: + g_assert_not_reached(); + } break; case 0x9: /* FMUL, FMULX */ - if (u) { - gen_helper_vfp_mulxs(tcg_res, tcg_op, tcg_idx, fpst); - } else { - gen_helper_vfp_muls(tcg_res, tcg_op, tcg_idx, fpst); + switch (size) { + case 1: + if (u) { + if (is_scalar) { + gen_helper_advsimd_mulxh(tcg_res, tcg_op, + tcg_idx, fpst); + } else { + gen_helper_advsimd_mulx2h(tcg_res, tcg_op, + tcg_idx, fpst); + } + } else { + if (is_scalar) { + gen_helper_advsimd_mulh(tcg_res, tcg_op, + tcg_idx, fpst); + } else { + gen_helper_advsimd_mul2h(tcg_res, tcg_op, + tcg_idx, fpst); + } + } + break; + case 2: + if (u) { + gen_helper_vfp_mulxs(tcg_res, tcg_op, tcg_idx, fpst); + } else { + gen_helper_vfp_muls(tcg_res, tcg_op, tcg_idx, fpst); + } + break; + default: + g_assert_not_reached(); } break; case 0xc: /* SQDMULH */ @@ -11937,6 +12820,9 @@ static const AArch64DecodeTable data_proc_simd[] = { { 0xce000000, 0xff808000, disas_crypto_four_reg }, { 0xce800000, 0xffe00000, disas_crypto_xar }, { 0xce408000, 0xffe0c000, disas_crypto_three_reg_imm2 }, + { 0x0e400400, 0x9f60c400, disas_simd_three_reg_same_fp16 }, + { 0x0e780800, 0x8f7e0c00, disas_simd_two_reg_misc_fp16 }, + { 0x5e400400, 0xdf60c400, disas_simd_scalar_three_reg_same_fp16 }, { 0x00000000, 0x00000000, NULL } }; diff --git a/target/arm/translate.c b/target/arm/translate.c index 1270022289..aa6dcaa577 100644 --- a/target/arm/translate.c +++ b/target/arm/translate.c @@ -3143,7 +3143,7 @@ static int handle_vrint(uint32_t insn, uint32_t rd, uint32_t rm, uint32_t dp, TCGv_i32 tcg_rmode; tcg_rmode = tcg_const_i32(arm_rmode_to_sf(rounding)); - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, fpst); if (dp) { TCGv_i64 tcg_op; @@ -3167,7 +3167,7 @@ static int handle_vrint(uint32_t insn, uint32_t rd, uint32_t rm, uint32_t dp, tcg_temp_free_i32(tcg_res); } - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, fpst); tcg_temp_free_i32(tcg_rmode); tcg_temp_free_ptr(fpst); @@ -3184,7 +3184,7 @@ static int handle_vcvt(uint32_t insn, uint32_t rd, uint32_t rm, uint32_t dp, tcg_shift = tcg_const_i32(0); tcg_rmode = tcg_const_i32(arm_rmode_to_sf(rounding)); - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, fpst); if (dp) { TCGv_i64 tcg_double, tcg_res; @@ -3222,7 +3222,7 @@ static int handle_vcvt(uint32_t insn, uint32_t rd, uint32_t rm, uint32_t dp, tcg_temp_free_i32(tcg_single); } - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, fpst); tcg_temp_free_i32(tcg_rmode); tcg_temp_free_i32(tcg_shift); @@ -3892,13 +3892,13 @@ static int disas_vfp_insn(DisasContext *s, uint32_t insn) TCGv_ptr fpst = get_fpstatus_ptr(0); TCGv_i32 tcg_rmode; tcg_rmode = tcg_const_i32(float_round_to_zero); - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, fpst); if (dp) { gen_helper_rintd(cpu_F0d, cpu_F0d, fpst); } else { gen_helper_rints(cpu_F0s, cpu_F0s, fpst); } - gen_helper_set_rmode(tcg_rmode, tcg_rmode, cpu_env); + gen_helper_set_rmode(tcg_rmode, tcg_rmode, fpst); tcg_temp_free_i32(tcg_rmode); tcg_temp_free_ptr(fpst); break; diff --git a/tests/Makefile.include b/tests/Makefile.include index 577eb573a2..937cbd874a 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -287,6 +287,7 @@ ifeq ($(CONFIG_VHOST_USER_NET_TEST_i386),) check-qtest-x86_64-$(CONFIG_VHOST_USER_NET_TEST_x86_64) += tests/vhost-user-test$(EXESUF) endif check-qtest-i386-$(CONFIG_TPM) += tests/tpm-crb-test$(EXESUF) +check-qtest-i386-$(CONFIG_TPM) += tests/tpm-tis-test$(EXESUF) check-qtest-i386-$(CONFIG_SLIRP) += tests/test-netfilter$(EXESUF) check-qtest-i386-$(CONFIG_POSIX) += tests/test-filter-mirror$(EXESUF) check-qtest-i386-$(CONFIG_POSIX) += tests/test-filter-redirector$(EXESUF) @@ -714,7 +715,8 @@ tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o \ tests/test-io-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y) tests/test-io-channel-socket$(EXESUF): tests/test-io-channel-socket.o \ tests/io-channel-helpers.o $(test-io-obj-y) -tests/tpm-crb-test$(EXESUF): tests/tpm-crb-test.o $(test-io-obj-y) +tests/tpm-crb-test$(EXESUF): tests/tpm-crb-test.o tests/tpm-emu.o $(test-io-obj-y) +tests/tpm-tis-test$(EXESUF): tests/tpm-tis-test.o tests/tpm-emu.o $(test-io-obj-y) tests/test-io-channel-file$(EXESUF): tests/test-io-channel-file.o \ tests/io-channel-helpers.o $(test-io-obj-y) tests/test-io-channel-tls$(EXESUF): tests/test-io-channel-tls.o \ diff --git a/tests/tpm-crb-test.c b/tests/tpm-crb-test.c index 8bf1507e00..e1513cb69e 100644 --- a/tests/tpm-crb-test.c +++ b/tests/tpm-crb-test.c @@ -14,177 +14,9 @@ #include <glib/gstdio.h> #include "hw/acpi/tpm.h" -#include "hw/tpm/tpm_ioctl.h" #include "io/channel-socket.h" #include "libqtest.h" -#include "qapi/error.h" - -#define TPM_RC_FAILURE 0x101 -#define TPM2_ST_NO_SESSIONS 0x8001 - -struct tpm_hdr { - uint16_t tag; - uint32_t len; - uint32_t code; /*ordinal/error */ - char buffer[]; -} QEMU_PACKED; - -typedef struct TestState { - CompatGMutex data_mutex; - CompatGCond data_cond; - SocketAddress *addr; - QIOChannel *tpm_ioc; - GThread *emu_tpm_thread; - struct tpm_hdr *tpm_msg; -} TestState; - -static void test_wait_cond(TestState *s) -{ - gint64 end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND; - - g_mutex_lock(&s->data_mutex); - if (!g_cond_wait_until(&s->data_cond, &s->data_mutex, end_time)) { - g_assert_not_reached(); - } - g_mutex_unlock(&s->data_mutex); -} - -static void *emu_tpm_thread(void *data) -{ - TestState *s = data; - QIOChannel *ioc = s->tpm_ioc; - - s->tpm_msg = g_new(struct tpm_hdr, 1); - while (true) { - int minhlen = sizeof(s->tpm_msg->tag) + sizeof(s->tpm_msg->len); - - if (!qio_channel_read(ioc, (char *)s->tpm_msg, minhlen, &error_abort)) { - break; - } - s->tpm_msg->tag = be16_to_cpu(s->tpm_msg->tag); - s->tpm_msg->len = be32_to_cpu(s->tpm_msg->len); - g_assert_cmpint(s->tpm_msg->len, >=, minhlen); - g_assert_cmpint(s->tpm_msg->tag, ==, TPM2_ST_NO_SESSIONS); - - s->tpm_msg = g_realloc(s->tpm_msg, s->tpm_msg->len); - qio_channel_read(ioc, (char *)&s->tpm_msg->code, - s->tpm_msg->len - minhlen, &error_abort); - s->tpm_msg->code = be32_to_cpu(s->tpm_msg->code); - - /* reply error */ - s->tpm_msg->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS); - s->tpm_msg->len = cpu_to_be32(sizeof(struct tpm_hdr)); - s->tpm_msg->code = cpu_to_be32(TPM_RC_FAILURE); - qio_channel_write(ioc, (char *)s->tpm_msg, be32_to_cpu(s->tpm_msg->len), - &error_abort); - } - - g_free(s->tpm_msg); - s->tpm_msg = NULL; - object_unref(OBJECT(s->tpm_ioc)); - return NULL; -} - -static void *emu_ctrl_thread(void *data) -{ - TestState *s = data; - QIOChannelSocket *lioc = qio_channel_socket_new(); - QIOChannel *ioc; - - qio_channel_socket_listen_sync(lioc, s->addr, &error_abort); - g_cond_signal(&s->data_cond); - - qio_channel_wait(QIO_CHANNEL(lioc), G_IO_IN); - ioc = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort)); - g_assert(ioc); - - { - uint32_t cmd = 0; - struct iovec iov = { .iov_base = &cmd, .iov_len = sizeof(cmd) }; - int *pfd = NULL; - size_t nfd = 0; - - qio_channel_readv_full(ioc, &iov, 1, &pfd, &nfd, &error_abort); - cmd = be32_to_cpu(cmd); - g_assert_cmpint(cmd, ==, CMD_SET_DATAFD); - g_assert_cmpint(nfd, ==, 1); - s->tpm_ioc = QIO_CHANNEL(qio_channel_socket_new_fd(*pfd, &error_abort)); - g_free(pfd); - - cmd = 0; - qio_channel_write(ioc, (char *)&cmd, sizeof(cmd), &error_abort); - - s->emu_tpm_thread = g_thread_new(NULL, emu_tpm_thread, s); - } - - while (true) { - uint32_t cmd; - ssize_t ret; - - ret = qio_channel_read(ioc, (char *)&cmd, sizeof(cmd), NULL); - if (ret <= 0) { - break; - } - - cmd = be32_to_cpu(cmd); - switch (cmd) { - case CMD_GET_CAPABILITY: { - ptm_cap cap = cpu_to_be64(0x3fff); - qio_channel_write(ioc, (char *)&cap, sizeof(cap), &error_abort); - break; - } - case CMD_INIT: { - ptm_init init; - qio_channel_read(ioc, (char *)&init.u.req, sizeof(init.u.req), - &error_abort); - init.u.resp.tpm_result = 0; - qio_channel_write(ioc, (char *)&init.u.resp, sizeof(init.u.resp), - &error_abort); - break; - } - case CMD_SHUTDOWN: { - ptm_res res = 0; - qio_channel_write(ioc, (char *)&res, sizeof(res), &error_abort); - qio_channel_close(s->tpm_ioc, &error_abort); - g_thread_join(s->emu_tpm_thread); - break; - } - case CMD_STOP: { - ptm_res res = 0; - qio_channel_write(ioc, (char *)&res, sizeof(res), &error_abort); - break; - } - case CMD_SET_BUFFERSIZE: { - ptm_setbuffersize sbs; - qio_channel_read(ioc, (char *)&sbs.u.req, sizeof(sbs.u.req), - &error_abort); - sbs.u.resp.buffersize = sbs.u.req.buffersize ?: cpu_to_be32(4096); - sbs.u.resp.tpm_result = 0; - sbs.u.resp.minsize = cpu_to_be32(128); - sbs.u.resp.maxsize = cpu_to_be32(4096); - qio_channel_write(ioc, (char *)&sbs.u.resp, sizeof(sbs.u.resp), - &error_abort); - break; - } - case CMD_SET_LOCALITY: { - ptm_loc loc; - /* Note: this time it's not u.req / u.resp... */ - qio_channel_read(ioc, (char *)&loc, sizeof(loc), &error_abort); - g_assert_cmpint(loc.u.req.loc, ==, 0); - loc.u.resp.tpm_result = 0; - qio_channel_write(ioc, (char *)&loc, sizeof(loc), &error_abort); - break; - } - default: - g_debug("unimplemented %u", cmd); - g_assert_not_reached(); - } - } - - object_unref(OBJECT(ioc)); - object_unref(OBJECT(lioc)); - return NULL; -} +#include "tpm-emu.h" #define TPM_CMD "\x80\x01\x00\x00\x00\x0c\x00\x00\x01\x44\x00\x00" @@ -250,8 +82,8 @@ int main(int argc, char **argv) g_mutex_init(&test.data_mutex); g_cond_init(&test.data_cond); - thread = g_thread_new(NULL, emu_ctrl_thread, &test); - test_wait_cond(&test); + thread = g_thread_new(NULL, tpm_emu_ctrl_thread, &test); + tpm_emu_test_wait_cond(&test); args = g_strdup_printf( "-chardev socket,id=chr,path=%s " diff --git a/tests/tpm-emu.c b/tests/tpm-emu.c new file mode 100644 index 0000000000..4dada76834 --- /dev/null +++ b/tests/tpm-emu.c @@ -0,0 +1,174 @@ +/* + * Minimal TPM emulator for TPM test cases + * + * Copyright (c) 2018 Red Hat, Inc. + * + * Authors: + * Marc-André Lureau <marcandre.lureau@redhat.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include <glib/gstdio.h> + +#include "hw/tpm/tpm_ioctl.h" +#include "io/channel-socket.h" +#include "qapi/error.h" +#include "tpm-emu.h" + +void tpm_emu_test_wait_cond(TestState *s) +{ + gint64 end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND; + + g_mutex_lock(&s->data_mutex); + if (!g_cond_wait_until(&s->data_cond, &s->data_mutex, end_time)) { + g_assert_not_reached(); + } + g_mutex_unlock(&s->data_mutex); +} + +static void *tpm_emu_tpm_thread(void *data) +{ + TestState *s = data; + QIOChannel *ioc = s->tpm_ioc; + + s->tpm_msg = g_new(struct tpm_hdr, 1); + while (true) { + int minhlen = sizeof(s->tpm_msg->tag) + sizeof(s->tpm_msg->len); + + if (!qio_channel_read(ioc, (char *)s->tpm_msg, minhlen, &error_abort)) { + break; + } + s->tpm_msg->tag = be16_to_cpu(s->tpm_msg->tag); + s->tpm_msg->len = be32_to_cpu(s->tpm_msg->len); + g_assert_cmpint(s->tpm_msg->len, >=, minhlen); + g_assert_cmpint(s->tpm_msg->tag, ==, TPM2_ST_NO_SESSIONS); + + s->tpm_msg = g_realloc(s->tpm_msg, s->tpm_msg->len); + qio_channel_read(ioc, (char *)&s->tpm_msg->code, + s->tpm_msg->len - minhlen, &error_abort); + s->tpm_msg->code = be32_to_cpu(s->tpm_msg->code); + + /* reply error */ + s->tpm_msg->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS); + s->tpm_msg->len = cpu_to_be32(sizeof(struct tpm_hdr)); + s->tpm_msg->code = cpu_to_be32(TPM_RC_FAILURE); + qio_channel_write(ioc, (char *)s->tpm_msg, be32_to_cpu(s->tpm_msg->len), + &error_abort); + } + + g_free(s->tpm_msg); + s->tpm_msg = NULL; + object_unref(OBJECT(s->tpm_ioc)); + return NULL; +} + +void *tpm_emu_ctrl_thread(void *data) +{ + TestState *s = data; + QIOChannelSocket *lioc = qio_channel_socket_new(); + QIOChannel *ioc; + + qio_channel_socket_listen_sync(lioc, s->addr, &error_abort); + g_cond_signal(&s->data_cond); + + qio_channel_wait(QIO_CHANNEL(lioc), G_IO_IN); + ioc = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort)); + g_assert(ioc); + + { + uint32_t cmd = 0; + struct iovec iov = { .iov_base = &cmd, .iov_len = sizeof(cmd) }; + int *pfd = NULL; + size_t nfd = 0; + + qio_channel_readv_full(ioc, &iov, 1, &pfd, &nfd, &error_abort); + cmd = be32_to_cpu(cmd); + g_assert_cmpint(cmd, ==, CMD_SET_DATAFD); + g_assert_cmpint(nfd, ==, 1); + s->tpm_ioc = QIO_CHANNEL(qio_channel_socket_new_fd(*pfd, &error_abort)); + g_free(pfd); + + cmd = 0; + qio_channel_write(ioc, (char *)&cmd, sizeof(cmd), &error_abort); + + s->emu_tpm_thread = g_thread_new(NULL, tpm_emu_tpm_thread, s); + } + + while (true) { + uint32_t cmd; + ssize_t ret; + + ret = qio_channel_read(ioc, (char *)&cmd, sizeof(cmd), NULL); + if (ret <= 0) { + break; + } + + cmd = be32_to_cpu(cmd); + switch (cmd) { + case CMD_GET_CAPABILITY: { + ptm_cap cap = cpu_to_be64(0x3fff); + qio_channel_write(ioc, (char *)&cap, sizeof(cap), &error_abort); + break; + } + case CMD_INIT: { + ptm_init init; + qio_channel_read(ioc, (char *)&init.u.req, sizeof(init.u.req), + &error_abort); + init.u.resp.tpm_result = 0; + qio_channel_write(ioc, (char *)&init.u.resp, sizeof(init.u.resp), + &error_abort); + break; + } + case CMD_SHUTDOWN: { + ptm_res res = 0; + qio_channel_write(ioc, (char *)&res, sizeof(res), &error_abort); + qio_channel_close(s->tpm_ioc, &error_abort); + g_thread_join(s->emu_tpm_thread); + break; + } + case CMD_STOP: { + ptm_res res = 0; + qio_channel_write(ioc, (char *)&res, sizeof(res), &error_abort); + break; + } + case CMD_SET_BUFFERSIZE: { + ptm_setbuffersize sbs; + qio_channel_read(ioc, (char *)&sbs.u.req, sizeof(sbs.u.req), + &error_abort); + sbs.u.resp.buffersize = sbs.u.req.buffersize ?: cpu_to_be32(4096); + sbs.u.resp.tpm_result = 0; + sbs.u.resp.minsize = cpu_to_be32(128); + sbs.u.resp.maxsize = cpu_to_be32(4096); + qio_channel_write(ioc, (char *)&sbs.u.resp, sizeof(sbs.u.resp), + &error_abort); + break; + } + case CMD_SET_LOCALITY: { + ptm_loc loc; + /* Note: this time it's not u.req / u.resp... */ + qio_channel_read(ioc, (char *)&loc, sizeof(loc), &error_abort); + g_assert_cmpint(loc.u.req.loc, ==, 0); + loc.u.resp.tpm_result = 0; + qio_channel_write(ioc, (char *)&loc, sizeof(loc), &error_abort); + break; + } + case CMD_GET_TPMESTABLISHED: { + ptm_est est = { + .u.resp.bit = 0, + }; + qio_channel_write(ioc, (char *)&est, sizeof(est), &error_abort); + break; + } + default: + g_debug("unimplemented %u", cmd); + g_assert_not_reached(); + } + } + + object_unref(OBJECT(ioc)); + object_unref(OBJECT(lioc)); + return NULL; +} diff --git a/tests/tpm-emu.h b/tests/tpm-emu.h new file mode 100644 index 0000000000..ef4bfa8800 --- /dev/null +++ b/tests/tpm-emu.h @@ -0,0 +1,38 @@ +/* + * Minimal TPM emulator for TPM test cases + * + * Copyright (c) 2018 Red Hat, Inc. + * + * Authors: + * Marc-André Lureau <marcandre.lureau@redhat.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#ifndef TESTS_TPM_EMU_H +#define TESTS_TPM_EMU_H + +#define TPM_RC_FAILURE 0x101 +#define TPM2_ST_NO_SESSIONS 0x8001 + +struct tpm_hdr { + uint16_t tag; + uint32_t len; + uint32_t code; /*ordinal/error */ + char buffer[]; +} QEMU_PACKED; + +typedef struct TestState { + CompatGMutex data_mutex; + CompatGCond data_cond; + SocketAddress *addr; + QIOChannel *tpm_ioc; + GThread *emu_tpm_thread; + struct tpm_hdr *tpm_msg; +} TestState; + +void tpm_emu_test_wait_cond(TestState *s); +void *tpm_emu_ctrl_thread(void *data); + +#endif /* TEST_TPM_EMU_H */ diff --git a/tests/tpm-tis-test.c b/tests/tpm-tis-test.c new file mode 100644 index 0000000000..14754d9706 --- /dev/null +++ b/tests/tpm-tis-test.c @@ -0,0 +1,486 @@ +/* + * QTest testcase for TPM TIS + * + * Copyright (c) 2018 Red Hat, Inc. + * Copyright (c) 2018 IBM Corporation + * + * Authors: + * Marc-André Lureau <marcandre.lureau@redhat.com> + * Stefan Berger <stefanb@linux.vnet.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include <glib/gstdio.h> + +#include "hw/acpi/tpm.h" +#include "io/channel-socket.h" +#include "libqtest.h" +#include "tpm-emu.h" + +#define TIS_REG(LOCTY, REG) \ + (TPM_TIS_ADDR_BASE + ((LOCTY) << 12) + REG) + +#define DEBUG_TIS_TEST 0 + +#define DPRINTF(fmt, ...) do { \ + if (DEBUG_TIS_TEST) { \ + printf(fmt, ## __VA_ARGS__); \ + } \ +} while (0) + +#define DPRINTF_ACCESS \ + DPRINTF("%s: %d: locty=%d l=%d access=0x%02x pending_request_flag=0x%x\n", \ + __func__, __LINE__, locty, l, access, pending_request_flag) + +#define DPRINTF_STS \ + DPRINTF("%s: %d: sts = 0x%08x\n", __func__, __LINE__, sts) + +static const uint8_t TPM_CMD[12] = + "\x80\x01\x00\x00\x00\x0c\x00\x00\x01\x44\x00\x00"; + +static void tpm_tis_test_check_localities(const void *data) +{ + uint8_t locty; + uint8_t access; + uint32_t ifaceid; + uint32_t capability; + uint32_t didvid; + uint32_t rid; + + for (locty = 0; locty < TPM_TIS_NUM_LOCALITIES; locty++) { + access = readb(TIS_REG(0, TPM_TIS_REG_ACCESS)); + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + capability = readl(TIS_REG(locty, TPM_TIS_REG_INTF_CAPABILITY)); + g_assert_cmpint(capability, ==, TPM_TIS_CAPABILITIES_SUPPORTED2_0); + + ifaceid = readl(TIS_REG(locty, TPM_TIS_REG_INTERFACE_ID)); + g_assert_cmpint(ifaceid, ==, TPM_TIS_IFACE_ID_SUPPORTED_FLAGS2_0); + + didvid = readl(TIS_REG(locty, TPM_TIS_REG_DID_VID)); + g_assert_cmpint(didvid, !=, 0); + g_assert_cmpint(didvid, !=, 0xffffffff); + + rid = readl(TIS_REG(locty, TPM_TIS_REG_RID)); + g_assert_cmpint(rid, !=, 0); + g_assert_cmpint(rid, !=, 0xffffffff); + } +} + +static void tpm_tis_test_check_access_reg(const void *data) +{ + uint8_t locty; + uint8_t access; + + /* do not test locality 4 (hw only) */ + for (locty = 0; locty < TPM_TIS_NUM_LOCALITIES - 1; locty++) { + access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS)); + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* request use of locality */ + writeb(TIS_REG(locty, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_REQUEST_USE); + + access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS)); + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_ACTIVE_LOCALITY | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* release access */ + writeb(TIS_REG(locty, TPM_TIS_REG_ACCESS), + TPM_TIS_ACCESS_ACTIVE_LOCALITY); + access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS)); + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + } +} + +/* + * Test case for seizing access by a higher number locality + */ +static void tpm_tis_test_check_access_reg_seize(const void *data) +{ + int locty, l; + uint8_t access; + uint8_t pending_request_flag; + + /* do not test locality 4 (hw only) */ + for (locty = 0; locty < TPM_TIS_NUM_LOCALITIES - 1; locty++) { + pending_request_flag = 0; + + access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS)); + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* request use of locality */ + writeb(TIS_REG(locty, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_REQUEST_USE); + access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS)); + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_ACTIVE_LOCALITY | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* lower localities cannot seize access */ + for (l = 0; l < locty; l++) { + /* lower locality is not active */ + access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS)); + DPRINTF_ACCESS; + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + pending_request_flag | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* try to request use from 'l' */ + writeb(TIS_REG(l, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_REQUEST_USE); + + /* requesting use from 'l' was not possible; + we must see REQUEST_USE and possibly PENDING_REQUEST */ + access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS)); + DPRINTF_ACCESS; + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_REQUEST_USE | + pending_request_flag | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* locality 'locty' must be unchanged; + we must see PENDING_REQUEST */ + access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS)); + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_ACTIVE_LOCALITY | + TPM_TIS_ACCESS_PENDING_REQUEST | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* try to seize from 'l' */ + writeb(TIS_REG(l, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_SEIZE); + /* seize from 'l' was not possible */ + access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS)); + DPRINTF_ACCESS; + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_REQUEST_USE | + pending_request_flag | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* locality 'locty' must be unchanged */ + access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS)); + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_ACTIVE_LOCALITY | + TPM_TIS_ACCESS_PENDING_REQUEST | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* on the next loop we will have a PENDING_REQUEST flag + set for locality 'l' */ + pending_request_flag = TPM_TIS_ACCESS_PENDING_REQUEST; + } + + /* higher localities can 'seize' access but not 'request use'; + note: this will activate first l+1, then l+2 etc. */ + for (l = locty + 1; l < TPM_TIS_NUM_LOCALITIES - 1; l++) { + /* try to 'request use' from 'l' */ + writeb(TIS_REG(l, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_REQUEST_USE); + + /* requesting use from 'l' was not possible; we should see + REQUEST_USE and may see PENDING_REQUEST */ + access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS)); + DPRINTF_ACCESS; + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_REQUEST_USE | + pending_request_flag | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* locality 'l-1' must be unchanged; we should always + see PENDING_REQUEST from 'l' requesting access */ + access = readb(TIS_REG(l - 1, TPM_TIS_REG_ACCESS)); + DPRINTF_ACCESS; + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_ACTIVE_LOCALITY | + TPM_TIS_ACCESS_PENDING_REQUEST | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* try to seize from 'l' */ + writeb(TIS_REG(l, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_SEIZE); + + /* seize from 'l' was possible */ + access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS)); + DPRINTF_ACCESS; + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_ACTIVE_LOCALITY | + pending_request_flag | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* l - 1 should show that it has BEEN_SEIZED */ + access = readb(TIS_REG(l - 1, TPM_TIS_REG_ACCESS)); + DPRINTF_ACCESS; + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_BEEN_SEIZED | + pending_request_flag | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* clear the BEEN_SEIZED flag and make sure it's gone */ + writeb(TIS_REG(l - 1, TPM_TIS_REG_ACCESS), + TPM_TIS_ACCESS_BEEN_SEIZED); + + access = readb(TIS_REG(l - 1, TPM_TIS_REG_ACCESS)); + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + pending_request_flag | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + } + + /* PENDING_REQUEST will not be set if locty = 0 since all localities + were active; in case of locty = 1, locality 0 will be active + but no PENDING_REQUEST anywhere */ + if (locty <= 1) { + pending_request_flag = 0; + } + + /* release access from l - 1; this activates locty - 1 */ + l--; + + access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS)); + DPRINTF_ACCESS; + + DPRINTF("%s: %d: relinquishing control on l = %d\n", + __func__, __LINE__, l); + writeb(TIS_REG(l, TPM_TIS_REG_ACCESS), + TPM_TIS_ACCESS_ACTIVE_LOCALITY); + + access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS)); + DPRINTF_ACCESS; + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + pending_request_flag | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + for (l = locty - 1; l >= 0; l--) { + access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS)); + DPRINTF_ACCESS; + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_ACTIVE_LOCALITY | + pending_request_flag | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* release this locality */ + writeb(TIS_REG(l, TPM_TIS_REG_ACCESS), + TPM_TIS_ACCESS_ACTIVE_LOCALITY); + + if (l == 1) { + pending_request_flag = 0; + } + } + + /* no locality may be active now */ + for (l = 0; l < TPM_TIS_NUM_LOCALITIES - 1; l++) { + access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS)); + DPRINTF_ACCESS; + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + } + } +} + +/* + * Test case for getting access when higher number locality relinquishes access + */ +static void tpm_tis_test_check_access_reg_release(const void *data) +{ + int locty, l; + uint8_t access; + uint8_t pending_request_flag; + + /* do not test locality 4 (hw only) */ + for (locty = TPM_TIS_NUM_LOCALITIES - 2; locty >= 0; locty--) { + pending_request_flag = 0; + + access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS)); + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* request use of locality */ + writeb(TIS_REG(locty, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_REQUEST_USE); + access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS)); + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_ACTIVE_LOCALITY | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + /* request use of all other localities */ + for (l = 0; l < TPM_TIS_NUM_LOCALITIES - 1; l++) { + if (l == locty) { + continue; + } + /* request use of locality 'l' -- we MUST see REQUEST USE and + may see PENDING_REQUEST */ + writeb(TIS_REG(l, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_REQUEST_USE); + access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS)); + DPRINTF_ACCESS; + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_REQUEST_USE | + pending_request_flag | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + pending_request_flag = TPM_TIS_ACCESS_PENDING_REQUEST; + } + /* release locality 'locty' */ + writeb(TIS_REG(locty, TPM_TIS_REG_ACCESS), + TPM_TIS_ACCESS_ACTIVE_LOCALITY); + /* highest locality should now be active; release it and make sure the + next higest locality is active afterwards */ + for (l = TPM_TIS_NUM_LOCALITIES - 2; l >= 0; l--) { + if (l == locty) { + continue; + } + /* 'l' should be active now */ + access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS)); + DPRINTF_ACCESS; + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_ACTIVE_LOCALITY | + pending_request_flag | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + /* 'l' relinquishes access */ + writeb(TIS_REG(l, TPM_TIS_REG_ACCESS), + TPM_TIS_ACCESS_ACTIVE_LOCALITY); + access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS)); + DPRINTF_ACCESS; + if (l == 1 || (locty <= 1 && l == 2)) { + pending_request_flag = 0; + } + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + pending_request_flag | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + } + } +} + +/* + * Test case for transmitting packets + */ +static void tpm_tis_test_check_transmit(const void *data) +{ + const TestState *s = data; + uint8_t access; + uint32_t sts; + uint16_t bcount; + size_t i; + + /* request use of locality 0 */ + writeb(TIS_REG(0, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_REQUEST_USE); + access = readb(TIS_REG(0, TPM_TIS_REG_ACCESS)); + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS | + TPM_TIS_ACCESS_ACTIVE_LOCALITY | + TPM_TIS_ACCESS_TPM_ESTABLISHMENT); + + sts = readl(TIS_REG(0, TPM_TIS_REG_STS)); + DPRINTF_STS; + + g_assert_cmpint(sts & 0xff, ==, 0); + g_assert_cmpint(sts & TPM_TIS_STS_TPM_FAMILY_MASK, ==, + TPM_TIS_STS_TPM_FAMILY2_0); + + bcount = (sts >> 8) & 0xffff; + g_assert_cmpint(bcount, >=, 128); + + writel(TIS_REG(0, TPM_TIS_REG_STS), TPM_TIS_STS_COMMAND_READY); + sts = readl(TIS_REG(0, TPM_TIS_REG_STS)); + DPRINTF_STS; + g_assert_cmpint(sts & 0xff, ==, TPM_TIS_STS_COMMAND_READY); + + /* transmit command */ + for (i = 0; i < sizeof(TPM_CMD); i++) { + writeb(TIS_REG(0, TPM_TIS_REG_DATA_FIFO), TPM_CMD[i]); + sts = readl(TIS_REG(0, TPM_TIS_REG_STS)); + DPRINTF_STS; + if (i < sizeof(TPM_CMD) - 1) { + g_assert_cmpint(sts & 0xff, ==, + TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID); + } else { + g_assert_cmpint(sts & 0xff, ==, TPM_TIS_STS_VALID); + } + g_assert_cmpint((sts >> 8) & 0xffff, ==, --bcount); + } + /* start processing */ + writeb(TIS_REG(0, TPM_TIS_REG_STS), TPM_TIS_STS_TPM_GO); + + uint64_t end_time = g_get_monotonic_time() + 50 * G_TIME_SPAN_SECOND; + do { + sts = readl(TIS_REG(0, TPM_TIS_REG_STS)); + if ((sts & TPM_TIS_STS_DATA_AVAILABLE) != 0) { + break; + } + } while (g_get_monotonic_time() < end_time); + + sts = readl(TIS_REG(0, TPM_TIS_REG_STS)); + DPRINTF_STS; + g_assert_cmpint(sts & 0xff, == , + TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE); + bcount = (sts >> 8) & 0xffff; + + /* read response */ + uint8_t tpm_msg[sizeof(struct tpm_hdr)]; + g_assert_cmpint(sizeof(tpm_msg), ==, bcount); + + for (i = 0; i < sizeof(tpm_msg); i++) { + tpm_msg[i] = readb(TIS_REG(0, TPM_TIS_REG_DATA_FIFO)); + sts = readl(TIS_REG(0, TPM_TIS_REG_STS)); + DPRINTF_STS; + if (sts & TPM_TIS_STS_DATA_AVAILABLE) { + g_assert_cmpint((sts >> 8) & 0xffff, ==, --bcount); + } + } + g_assert_cmpmem(tpm_msg, sizeof(tpm_msg), s->tpm_msg, sizeof(*s->tpm_msg)); + + /* relinquish use of locality 0 */ + writeb(TIS_REG(0, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_ACTIVE_LOCALITY); + access = readb(TIS_REG(0, TPM_TIS_REG_ACCESS)); +} + +int main(int argc, char **argv) +{ + int ret; + char *args, *tmp_path = g_dir_make_tmp("qemu-tpm-tis-test.XXXXXX", NULL); + GThread *thread; + TestState test; + + module_call_init(MODULE_INIT_QOM); + g_test_init(&argc, &argv, NULL); + + test.addr = g_new0(SocketAddress, 1); + test.addr->type = SOCKET_ADDRESS_TYPE_UNIX; + test.addr->u.q_unix.path = g_build_filename(tmp_path, "sock", NULL); + g_mutex_init(&test.data_mutex); + g_cond_init(&test.data_cond); + + thread = g_thread_new(NULL, tpm_emu_ctrl_thread, &test); + tpm_emu_test_wait_cond(&test); + + args = g_strdup_printf( + "-chardev socket,id=chr,path=%s " + "-tpmdev emulator,id=dev,chardev=chr " + "-device tpm-tis,tpmdev=dev", + test.addr->u.q_unix.path); + qtest_start(args); + + qtest_add_data_func("/tpm-tis/test_check_localities", &test, + tpm_tis_test_check_localities); + + qtest_add_data_func("/tpm-tis/test_check_access_reg", &test, + tpm_tis_test_check_access_reg); + + qtest_add_data_func("/tpm-tis/test_check_access_reg_seize", &test, + tpm_tis_test_check_access_reg_seize); + + qtest_add_data_func("/tpm-tis/test_check_access_reg_release", &test, + tpm_tis_test_check_access_reg_release); + + qtest_add_data_func("/tpm-tis/test_check_transmit", &test, + tpm_tis_test_check_transmit); + + ret = g_test_run(); + + qtest_end(); + + g_thread_join(thread); + g_unlink(test.addr->u.q_unix.path); + qapi_free_SocketAddress(test.addr); + g_rmdir(tmp_path); + g_free(tmp_path); + g_free(args); + return ret; +} diff --git a/util/vfio-helpers.c b/util/vfio-helpers.c index f478b68400..006674c916 100644 --- a/util/vfio-helpers.c +++ b/util/vfio-helpers.c @@ -104,7 +104,7 @@ static char *sysfs_find_group_file(const char *device, Error **errp) char *path = NULL; sysfs_link = g_strdup_printf("/sys/bus/pci/devices/%s/iommu_group", device); - sysfs_group = g_malloc(PATH_MAX); + sysfs_group = g_malloc0(PATH_MAX); if (readlink(sysfs_link, sysfs_group, PATH_MAX - 1) == -1) { error_setg_errno(errp, errno, "Failed to find iommu group sysfs path"); goto out; |