aboutsummaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
Diffstat (limited to 'hw')
-rw-r--r--hw/Kconfig1
-rw-r--r--hw/acpi/generic_event_device.c2
-rw-r--r--hw/adc/Kconfig3
-rw-r--r--hw/adc/max111x.c (renamed from hw/misc/max111x.c)2
-rw-r--r--hw/adc/meson.build2
-rw-r--r--hw/adc/zynq-xadc.c (renamed from hw/misc/zynq-xadc.c)2
-rw-r--r--hw/arm/Kconfig17
-rw-r--r--hw/arm/aspeed.c2
-rw-r--r--hw/arm/meson.build2
-rw-r--r--hw/arm/nseries.c5
-rw-r--r--hw/arm/pxa2xx.c2
-rw-r--r--hw/arm/spitz.c6
-rw-r--r--hw/arm/stellaris.c56
-rw-r--r--hw/arm/stm32f100_soc.c182
-rw-r--r--hw/arm/stm32vldiscovery.c66
-rw-r--r--hw/arm/virt.c3
-rw-r--r--hw/arm/xilinx_zynq.c2
-rw-r--r--hw/block/block.c42
-rw-r--r--hw/block/dataplane/virtio-blk.c16
-rw-r--r--hw/display/ati.c2
-rw-r--r--hw/display/qxl.c4
-rw-r--r--hw/display/sm501.c16
-rw-r--r--hw/display/vhost-user-gpu-pci.c1
-rw-r--r--hw/display/vhost-user-gpu.c1
-rw-r--r--hw/display/vhost-user-vga.c1
-rw-r--r--hw/display/virtio-gpu-base.c1
-rw-r--r--hw/display/virtio-gpu-gl.c3
-rw-r--r--hw/display/virtio-gpu-pci-gl.c3
-rw-r--r--hw/display/virtio-gpu-pci.c2
-rw-r--r--hw/display/virtio-gpu.c1
-rw-r--r--hw/display/virtio-vga-gl.c3
-rw-r--r--hw/display/virtio-vga.c2
-rw-r--r--hw/display/xlnx_dp.c2
-rw-r--r--hw/gpio/pl061.c345
-rw-r--r--hw/gpio/trace-events9
-rw-r--r--hw/hyperv/vmbus.c20
-rw-r--r--hw/i2c/Kconfig4
-rw-r--r--hw/i2c/core.c76
-rw-r--r--hw/i2c/imx_i2c.c2
-rw-r--r--hw/i2c/meson.build1
-rw-r--r--hw/i2c/pm_smbus.c4
-rw-r--r--hw/i2c/pmbus_device.c1612
-rw-r--r--hw/i2c/ppc4xx_i2c.c15
-rw-r--r--hw/i2c/smbus_master.c22
-rw-r--r--hw/i386/acpi-build.c9
-rw-r--r--hw/input/lm832x.c2
-rw-r--r--hw/intc/arm_gicv3_cpuif.c4
-rw-r--r--hw/intc/arm_gicv3_redist.c4
-rw-r--r--hw/ipmi/ipmi_bmc_sim.c4
-rw-r--r--hw/meson.build1
-rw-r--r--hw/misc/Kconfig15
-rw-r--r--hw/misc/auxbus.c68
-rw-r--r--hw/misc/meson.build6
-rw-r--r--hw/misc/tmp105.h55
-rw-r--r--hw/net/dp8393x.c208
-rw-r--r--hw/net/virtio-net.c1
-rw-r--r--hw/pci-host/Kconfig2
-rw-r--r--hw/pci-host/meson.build2
-rw-r--r--hw/pci-host/q35.c3
-rw-r--r--hw/pci-host/raven.c (renamed from hw/pci-host/prep.c)11
-rw-r--r--hw/ppc/Kconfig7
-rw-r--r--hw/ppc/meson.build3
-rw-r--r--hw/ppc/pegasos2.c789
-rw-r--r--hw/ppc/spapr.c79
-rw-r--r--hw/ppc/spapr_caps.c41
-rw-r--r--hw/ppc/spapr_hcall.c24
-rw-r--r--hw/ppc/spapr_vof.c167
-rw-r--r--hw/ppc/trace-events24
-rw-r--r--hw/ppc/vof.c1053
-rw-r--r--hw/s390x/virtio-ccw-gpu.c3
-rw-r--r--hw/s390x/virtio-ccw.c6
-rw-r--r--hw/scsi/virtio-scsi-dataplane.c16
-rw-r--r--hw/sensor/Kconfig19
-rw-r--r--hw/sensor/adm1272.c543
-rw-r--r--hw/sensor/emc141x.c (renamed from hw/misc/emc141x.c)2
-rw-r--r--hw/sensor/max34451.c775
-rw-r--r--hw/sensor/meson.build5
-rw-r--r--hw/sensor/tmp105.c (renamed from hw/misc/tmp105.c)2
-rw-r--r--hw/sensor/tmp421.c (renamed from hw/misc/tmp421.c)0
-rw-r--r--hw/usb/ccid-card-emulated.c1
-rw-r--r--hw/usb/ccid-card-passthru.c1
-rw-r--r--hw/usb/desc-msos.c2
-rw-r--r--hw/usb/dev-storage-bot.c1
-rw-r--r--hw/usb/dev-storage-classic.c1
-rw-r--r--hw/usb/dev-uas.c1
-rw-r--r--hw/usb/host-libusb.c38
-rw-r--r--hw/usb/host-stub.c45
-rw-r--r--hw/usb/meson.build10
-rw-r--r--hw/usb/redirect.c1
-rw-r--r--hw/vfio/common.c315
-rw-r--r--hw/virtio/vhost-user.c3
-rw-r--r--hw/virtio/virtio-mem.c391
-rw-r--r--hw/virtio/virtio-mmio.c6
-rw-r--r--hw/virtio/virtio-pci.c33
-rw-r--r--hw/virtio/virtio.c18
95 files changed, 6826 insertions, 559 deletions
diff --git a/hw/Kconfig b/hw/Kconfig
index 805860f564..8cb7664d70 100644
--- a/hw/Kconfig
+++ b/hw/Kconfig
@@ -32,6 +32,7 @@ source remote/Kconfig
source rtc/Kconfig
source scsi/Kconfig
source sd/Kconfig
+source sensor/Kconfig
source smbios/Kconfig
source ssi/Kconfig
source timer/Kconfig
diff --git a/hw/acpi/generic_event_device.c b/hw/acpi/generic_event_device.c
index 39c825763a..e28457a7d1 100644
--- a/hw/acpi/generic_event_device.c
+++ b/hw/acpi/generic_event_device.c
@@ -207,7 +207,7 @@ static void ged_regs_write(void *opaque, hwaddr addr, uint64_t data,
return;
case ACPI_GED_REG_RESET:
if (data == ACPI_GED_RESET_VALUE) {
- qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
}
return;
}
diff --git a/hw/adc/Kconfig b/hw/adc/Kconfig
index 25d2229fb8..a825bd3d34 100644
--- a/hw/adc/Kconfig
+++ b/hw/adc/Kconfig
@@ -1,2 +1,5 @@
config STM32F2XX_ADC
bool
+
+config MAX111X
+ bool
diff --git a/hw/misc/max111x.c b/hw/adc/max111x.c
index 1b3234a519..e8bf4cccd4 100644
--- a/hw/misc/max111x.c
+++ b/hw/adc/max111x.c
@@ -11,7 +11,7 @@
*/
#include "qemu/osdep.h"
-#include "hw/misc/max111x.h"
+#include "hw/adc/max111x.h"
#include "hw/irq.h"
#include "migration/vmstate.h"
#include "qemu/module.h"
diff --git a/hw/adc/meson.build b/hw/adc/meson.build
index 6ddee23813..ac4f093fea 100644
--- a/hw/adc/meson.build
+++ b/hw/adc/meson.build
@@ -1,2 +1,4 @@
softmmu_ss.add(when: 'CONFIG_STM32F2XX_ADC', if_true: files('stm32f2xx_adc.c'))
softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_adc.c'))
+softmmu_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq-xadc.c'))
+softmmu_ss.add(when: 'CONFIG_MAX111X', if_true: files('max111x.c'))
diff --git a/hw/misc/zynq-xadc.c b/hw/adc/zynq-xadc.c
index 7b1972ce06..cfc7bab065 100644
--- a/hw/misc/zynq-xadc.c
+++ b/hw/adc/zynq-xadc.c
@@ -15,7 +15,7 @@
#include "qemu/osdep.h"
#include "hw/irq.h"
-#include "hw/misc/zynq-xadc.h"
+#include "hw/adc/zynq-xadc.h"
#include "migration/vmstate.h"
#include "qemu/timer.h"
#include "qemu/log.h"
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 647b5c8b43..90b19c0861 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -239,6 +239,10 @@ config STELLARIS
select STELLARIS_ENET # ethernet
select UNIMP
+config STM32VLDISCOVERY
+ bool
+ select STM32F100_SOC
+
config STRONGARM
bool
select PXA2XX
@@ -296,7 +300,10 @@ config ZYNQ
config ARM_V7M
bool
+ # currently v7M must be included in a TCG build due to translate.c
+ default y if TCG && (ARM || AARCH64)
select PTIMER
+ select ARM_COMPATIBLE_SEMIHOSTING
config ALLWINNER_A10
bool
@@ -326,6 +333,12 @@ config RASPI
select SDHCI
select USB_DWC2
+config STM32F100_SOC
+ bool
+ select ARM_V7M
+ select STM32F2XX_USART
+ select STM32F2XX_SPI
+
config STM32F205_SOC
bool
select ARM_V7M
@@ -368,13 +381,17 @@ config XLNX_VERSAL
select UNIMP
select XLNX_ZDMA
select XLNX_ZYNQMP
+ select OR_IRQ
config NPCM7XX
bool
select A9MPCORE
+ select ADM1272
select ARM_GIC
select AT24C # EEPROM
+ select MAX34451
select PL310 # cache controller
+ select PMBUS
select SERIAL
select SSI
select UNIMP
diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c
index 1301e8fdff..9d43e26c51 100644
--- a/hw/arm/aspeed.c
+++ b/hw/arm/aspeed.c
@@ -17,7 +17,7 @@
#include "hw/i2c/i2c_mux_pca954x.h"
#include "hw/i2c/smbus_eeprom.h"
#include "hw/misc/pca9552.h"
-#include "hw/misc/tmp105.h"
+#include "hw/sensor/tmp105.h"
#include "hw/misc/led.h"
#include "hw/qdev-properties.h"
#include "sysemu/block-backend.h"
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index be39117b9b..721a8eb8be 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -24,6 +24,7 @@ arm_ss.add(when: 'CONFIG_Z2', if_true: files('z2.c'))
arm_ss.add(when: 'CONFIG_REALVIEW', if_true: files('realview.c'))
arm_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa-ref.c'))
arm_ss.add(when: 'CONFIG_STELLARIS', if_true: files('stellaris.c'))
+arm_ss.add(when: 'CONFIG_STM32VLDISCOVERY', if_true: files('stm32vldiscovery.c'))
arm_ss.add(when: 'CONFIG_COLLIE', if_true: files('collie.c'))
arm_ss.add(when: 'CONFIG_VERSATILE', if_true: files('versatilepb.c'))
arm_ss.add(when: 'CONFIG_VEXPRESS', if_true: files('vexpress.c'))
@@ -39,6 +40,7 @@ arm_ss.add(when: 'CONFIG_STRONGARM', if_true: files('strongarm.c'))
arm_ss.add(when: 'CONFIG_ALLWINNER_A10', if_true: files('allwinner-a10.c', 'cubieboard.c'))
arm_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3.c', 'orangepi.c'))
arm_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_peripherals.c', 'bcm2836.c', 'raspi.c'))
+arm_ss.add(when: 'CONFIG_STM32F100_SOC', if_true: files('stm32f100_soc.c'))
arm_ss.add(when: 'CONFIG_STM32F205_SOC', if_true: files('stm32f205_soc.c'))
arm_ss.add(when: 'CONFIG_STM32F405_SOC', if_true: files('stm32f405_soc.c'))
arm_ss.add(when: 'CONFIG_XLNX_ZYNQMP_ARM', if_true: files('xlnx-zynqmp.c', 'xlnx-zcu102.c'))
diff --git a/hw/arm/nseries.c b/hw/arm/nseries.c
index 0aefa5d0f3..906c915df7 100644
--- a/hw/arm/nseries.c
+++ b/hw/arm/nseries.c
@@ -34,9 +34,10 @@
#include "hw/boards.h"
#include "hw/i2c/i2c.h"
#include "hw/display/blizzard.h"
+#include "hw/input/lm832x.h"
#include "hw/input/tsc2xxx.h"
#include "hw/misc/cbus.h"
-#include "hw/misc/tmp105.h"
+#include "hw/sensor/tmp105.h"
#include "hw/qdev-properties.h"
#include "hw/block/flash.h"
#include "hw/hw.h"
@@ -416,7 +417,7 @@ static void n810_kbd_setup(struct n800_s *s)
/* Attach the LM8322 keyboard to the I2C bus,
* should happen in n8x0_i2c_setup and s->kbd be initialised here. */
s->kbd = DEVICE(i2c_slave_create_simple(omap_i2c_bus(s->mpu->i2c[0]),
- "lm8323", N810_LM8323_ADDR));
+ TYPE_LM8323, N810_LM8323_ADDR));
qdev_connect_gpio_out(s->kbd, 0, kbd_irq);
}
diff --git a/hw/arm/pxa2xx.c b/hw/arm/pxa2xx.c
index fdc4955e95..15a247efae 100644
--- a/hw/arm/pxa2xx.c
+++ b/hw/arm/pxa2xx.c
@@ -1437,7 +1437,7 @@ static void pxa2xx_i2c_write(void *opaque, hwaddr addr,
break;
case ISAR:
- i2c_set_slave_address(I2C_SLAVE(s->slave), value & 0x7f);
+ i2c_slave_set_address(I2C_SLAVE(s->slave), value & 0x7f);
break;
case IDBR:
diff --git a/hw/arm/spitz.c b/hw/arm/spitz.c
index b45a929cbd..5aab0b8565 100644
--- a/hw/arm/spitz.c
+++ b/hw/arm/spitz.c
@@ -30,7 +30,7 @@
#include "audio/audio.h"
#include "hw/boards.h"
#include "hw/sysbus.h"
-#include "hw/misc/max111x.h"
+#include "hw/adc/max111x.h"
#include "migration/vmstate.h"
#include "exec/address-spaces.h"
#include "cpu.h"
@@ -769,9 +769,9 @@ static void spitz_wm8750_addr(void *opaque, int line, int level)
{
I2CSlave *wm = (I2CSlave *) opaque;
if (level)
- i2c_set_slave_address(wm, SPITZ_WM_ADDRH);
+ i2c_slave_set_address(wm, SPITZ_WM_ADDRH);
else
- i2c_set_slave_address(wm, SPITZ_WM_ADDRL);
+ i2c_slave_set_address(wm, SPITZ_WM_ADDRL);
}
static void spitz_i2c_setup(PXA2xxState *cpu)
diff --git a/hw/arm/stellaris.c b/hw/arm/stellaris.c
index 8b4dab9b79..ad48cf2605 100644
--- a/hw/arm/stellaris.c
+++ b/hw/arm/stellaris.c
@@ -1453,13 +1453,67 @@ static void stellaris_init(MachineState *ms, stellaris_board_info *board)
DeviceState *sddev;
DeviceState *ssddev;
- /* Some boards have both an OLED controller and SD card connected to
+ /*
+ * Some boards have both an OLED controller and SD card connected to
* the same SSI port, with the SD card chip select connected to a
* GPIO pin. Technically the OLED chip select is connected to the
* SSI Fss pin. We do not bother emulating that as both devices
* should never be selected simultaneously, and our OLED controller
* ignores stray 0xff commands that occur when deselecting the SD
* card.
+ *
+ * The h/w wiring is:
+ * - GPIO pin D0 is wired to the active-low SD card chip select
+ * - GPIO pin A3 is wired to the active-low OLED chip select
+ * - The SoC wiring of the PL061 "auxiliary function" for A3 is
+ * SSI0Fss ("frame signal"), which is an output from the SoC's
+ * SSI controller. The SSI controller takes SSI0Fss low when it
+ * transmits a frame, so it can work as a chip-select signal.
+ * - GPIO A4 is aux-function SSI0Rx, and wired to the SD card Tx
+ * (the OLED never sends data to the CPU, so no wiring needed)
+ * - GPIO A5 is aux-function SSI0Tx, and wired to the SD card Rx
+ * and the OLED display-data-in
+ * - GPIO A2 is aux-function SSI0Clk, wired to SD card and OLED
+ * serial-clock input
+ * So a guest that wants to use the OLED can configure the PL061
+ * to make pins A2, A3, A5 aux-function, so they are connected
+ * directly to the SSI controller. When the SSI controller sends
+ * data it asserts SSI0Fss which selects the OLED.
+ * A guest that wants to use the SD card configures A2, A4 and A5
+ * as aux-function, but leaves A3 as a software-controlled GPIO
+ * line. It asserts the SD card chip-select by using the PL061
+ * to control pin D0, and lets the SSI controller handle Clk, Tx
+ * and Rx. (The SSI controller asserts Fss during tx cycles as
+ * usual, but because A3 is not set to aux-function this is not
+ * forwarded to the OLED, and so the OLED stays unselected.)
+ *
+ * The QEMU implementation instead is:
+ * - GPIO pin D0 is wired to the active-low SD card chip select,
+ * and also to the OLED chip-select which is implemented
+ * as *active-high*
+ * - SSI controller signals go to the devices regardless of
+ * whether the guest programs A2, A4, A5 as aux-function or not
+ *
+ * The problem with this implementation is if the guest doesn't
+ * care about the SD card and only uses the OLED. In that case it
+ * may choose never to do anything with D0 (leaving it in its
+ * default floating state, which reliably leaves the card disabled
+ * because an SD card has a pullup on CS within the card itself),
+ * and only set up A2, A3, A5. This for us would mean the OLED
+ * never gets the chip-select assert it needs. We work around
+ * this with a manual raise of D0 here (despite board creation
+ * code being the wrong place to raise IRQ lines) to put the OLED
+ * into an initially selected state.
+ *
+ * In theory the right way to model this would be:
+ * - Implement aux-function support in the PL061, with an
+ * extra set of AFIN and AFOUT GPIO lines (set up so that
+ * if a GPIO line is in auxfn mode the main GPIO in and out
+ * track the AFIN and AFOUT lines)
+ * - Wire the AFOUT for D0 up to either a line from the
+ * SSI controller that's pulled low around every transmit,
+ * or at least to an always-0 line here on the board
+ * - Make the ssd0323 OLED controller chipselect active-low
*/
bus = qdev_get_child_bus(dev, "ssi");
diff --git a/hw/arm/stm32f100_soc.c b/hw/arm/stm32f100_soc.c
new file mode 100644
index 0000000000..0c4a5c6645
--- /dev/null
+++ b/hw/arm/stm32f100_soc.c
@@ -0,0 +1,182 @@
+/*
+ * STM32F100 SoC
+ *
+ * Copyright (c) 2021 Alexandre Iooss <erdnaxe@crans.org>
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "hw/arm/boot.h"
+#include "exec/address-spaces.h"
+#include "hw/arm/stm32f100_soc.h"
+#include "hw/qdev-properties.h"
+#include "hw/misc/unimp.h"
+#include "sysemu/sysemu.h"
+
+/* stm32f100_soc implementation is derived from stm32f205_soc */
+
+static const uint32_t usart_addr[STM_NUM_USARTS] = { 0x40013800, 0x40004400,
+ 0x40004800 };
+static const uint32_t spi_addr[STM_NUM_SPIS] = { 0x40013000, 0x40003800 };
+
+static const int usart_irq[STM_NUM_USARTS] = {37, 38, 39};
+static const int spi_irq[STM_NUM_SPIS] = {35, 36};
+
+static void stm32f100_soc_initfn(Object *obj)
+{
+ STM32F100State *s = STM32F100_SOC(obj);
+ int i;
+
+ object_initialize_child(obj, "armv7m", &s->armv7m, TYPE_ARMV7M);
+
+ for (i = 0; i < STM_NUM_USARTS; i++) {
+ object_initialize_child(obj, "usart[*]", &s->usart[i],
+ TYPE_STM32F2XX_USART);
+ }
+
+ for (i = 0; i < STM_NUM_SPIS; i++) {
+ object_initialize_child(obj, "spi[*]", &s->spi[i], TYPE_STM32F2XX_SPI);
+ }
+}
+
+static void stm32f100_soc_realize(DeviceState *dev_soc, Error **errp)
+{
+ STM32F100State *s = STM32F100_SOC(dev_soc);
+ DeviceState *dev, *armv7m;
+ SysBusDevice *busdev;
+ int i;
+
+ MemoryRegion *system_memory = get_system_memory();
+ MemoryRegion *sram = g_new(MemoryRegion, 1);
+ MemoryRegion *flash = g_new(MemoryRegion, 1);
+ MemoryRegion *flash_alias = g_new(MemoryRegion, 1);
+
+ /*
+ * Init flash region
+ * Flash starts at 0x08000000 and then is aliased to boot memory at 0x0
+ */
+ memory_region_init_rom(flash, OBJECT(dev_soc), "STM32F100.flash",
+ FLASH_SIZE, &error_fatal);
+ memory_region_init_alias(flash_alias, OBJECT(dev_soc),
+ "STM32F100.flash.alias", flash, 0, FLASH_SIZE);
+ memory_region_add_subregion(system_memory, FLASH_BASE_ADDRESS, flash);
+ memory_region_add_subregion(system_memory, 0, flash_alias);
+
+ /* Init SRAM region */
+ memory_region_init_ram(sram, NULL, "STM32F100.sram", SRAM_SIZE,
+ &error_fatal);
+ memory_region_add_subregion(system_memory, SRAM_BASE_ADDRESS, sram);
+
+ /* Init ARMv7m */
+ armv7m = DEVICE(&s->armv7m);
+ qdev_prop_set_uint32(armv7m, "num-irq", 61);
+ qdev_prop_set_string(armv7m, "cpu-type", s->cpu_type);
+ qdev_prop_set_bit(armv7m, "enable-bitband", true);
+ object_property_set_link(OBJECT(&s->armv7m), "memory",
+ OBJECT(get_system_memory()), &error_abort);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) {
+ return;
+ }
+
+ /* Attach UART (uses USART registers) and USART controllers */
+ for (i = 0; i < STM_NUM_USARTS; i++) {
+ dev = DEVICE(&(s->usart[i]));
+ qdev_prop_set_chr(dev, "chardev", serial_hd(i));
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->usart[i]), errp)) {
+ return;
+ }
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, usart_addr[i]);
+ sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, usart_irq[i]));
+ }
+
+ /* SPI 1 and 2 */
+ for (i = 0; i < STM_NUM_SPIS; i++) {
+ dev = DEVICE(&(s->spi[i]));
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->spi[i]), errp)) {
+ return;
+ }
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, spi_addr[i]);
+ sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, spi_irq[i]));
+ }
+
+ create_unimplemented_device("timer[2]", 0x40000000, 0x400);
+ create_unimplemented_device("timer[3]", 0x40000400, 0x400);
+ create_unimplemented_device("timer[4]", 0x40000800, 0x400);
+ create_unimplemented_device("timer[6]", 0x40001000, 0x400);
+ create_unimplemented_device("timer[7]", 0x40001400, 0x400);
+ create_unimplemented_device("RTC", 0x40002800, 0x400);
+ create_unimplemented_device("WWDG", 0x40002C00, 0x400);
+ create_unimplemented_device("IWDG", 0x40003000, 0x400);
+ create_unimplemented_device("I2C1", 0x40005400, 0x400);
+ create_unimplemented_device("I2C2", 0x40005800, 0x400);
+ create_unimplemented_device("BKP", 0x40006C00, 0x400);
+ create_unimplemented_device("PWR", 0x40007000, 0x400);
+ create_unimplemented_device("DAC", 0x40007400, 0x400);
+ create_unimplemented_device("CEC", 0x40007800, 0x400);
+ create_unimplemented_device("AFIO", 0x40010000, 0x400);
+ create_unimplemented_device("EXTI", 0x40010400, 0x400);
+ create_unimplemented_device("GPIOA", 0x40010800, 0x400);
+ create_unimplemented_device("GPIOB", 0x40010C00, 0x400);
+ create_unimplemented_device("GPIOC", 0x40011000, 0x400);
+ create_unimplemented_device("GPIOD", 0x40011400, 0x400);
+ create_unimplemented_device("GPIOE", 0x40011800, 0x400);
+ create_unimplemented_device("ADC1", 0x40012400, 0x400);
+ create_unimplemented_device("timer[1]", 0x40012C00, 0x400);
+ create_unimplemented_device("timer[15]", 0x40014000, 0x400);
+ create_unimplemented_device("timer[16]", 0x40014400, 0x400);
+ create_unimplemented_device("timer[17]", 0x40014800, 0x400);
+ create_unimplemented_device("DMA", 0x40020000, 0x400);
+ create_unimplemented_device("RCC", 0x40021000, 0x400);
+ create_unimplemented_device("Flash Int", 0x40022000, 0x400);
+ create_unimplemented_device("CRC", 0x40023000, 0x400);
+}
+
+static Property stm32f100_soc_properties[] = {
+ DEFINE_PROP_STRING("cpu-type", STM32F100State, cpu_type),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void stm32f100_soc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = stm32f100_soc_realize;
+ device_class_set_props(dc, stm32f100_soc_properties);
+}
+
+static const TypeInfo stm32f100_soc_info = {
+ .name = TYPE_STM32F100_SOC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(STM32F100State),
+ .instance_init = stm32f100_soc_initfn,
+ .class_init = stm32f100_soc_class_init,
+};
+
+static void stm32f100_soc_types(void)
+{
+ type_register_static(&stm32f100_soc_info);
+}
+
+type_init(stm32f100_soc_types)
diff --git a/hw/arm/stm32vldiscovery.c b/hw/arm/stm32vldiscovery.c
new file mode 100644
index 0000000000..7e8191ebf5
--- /dev/null
+++ b/hw/arm/stm32vldiscovery.c
@@ -0,0 +1,66 @@
+/*
+ * ST STM32VLDISCOVERY machine
+ *
+ * Copyright (c) 2021 Alexandre Iooss <erdnaxe@crans.org>
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/boards.h"
+#include "hw/qdev-properties.h"
+#include "qemu/error-report.h"
+#include "hw/arm/stm32f100_soc.h"
+#include "hw/arm/boot.h"
+
+/* stm32vldiscovery implementation is derived from netduinoplus2 */
+
+/* Main SYSCLK frequency in Hz (24MHz) */
+#define SYSCLK_FRQ 24000000ULL
+
+static void stm32vldiscovery_init(MachineState *machine)
+{
+ DeviceState *dev;
+
+ /*
+ * TODO: ideally we would model the SoC RCC and let it handle
+ * system_clock_scale, including its ability to define different
+ * possible SYSCLK sources.
+ */
+ system_clock_scale = NANOSECONDS_PER_SECOND / SYSCLK_FRQ;
+
+ dev = qdev_new(TYPE_STM32F100_SOC);
+ qdev_prop_set_string(dev, "cpu-type", ARM_CPU_TYPE_NAME("cortex-m3"));
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+
+ armv7m_load_kernel(ARM_CPU(first_cpu),
+ machine->kernel_filename,
+ FLASH_SIZE);
+}
+
+static void stm32vldiscovery_machine_init(MachineClass *mc)
+{
+ mc->desc = "ST STM32VLDISCOVERY (Cortex-M3)";
+ mc->init = stm32vldiscovery_init;
+}
+
+DEFINE_MACHINE("stm32vldiscovery", stm32vldiscovery_machine_init)
+
diff --git a/hw/arm/virt.c b/hw/arm/virt.c
index 4b96f06014..93ab9d21ea 100644
--- a/hw/arm/virt.c
+++ b/hw/arm/virt.c
@@ -895,6 +895,9 @@ static void create_gpio_devices(const VirtMachineState *vms, int gpio,
MachineState *ms = MACHINE(vms);
pl061_dev = qdev_new("pl061");
+ /* Pull lines down to 0 if not driven by the PL061 */
+ qdev_prop_set_uint32(pl061_dev, "pullups", 0);
+ qdev_prop_set_uint32(pl061_dev, "pulldowns", 0xff);
s = SYS_BUS_DEVICE(pl061_dev);
sysbus_realize_and_unref(s, &error_fatal);
memory_region_add_subregion(mem, base, sysbus_mmio_get_region(s, 0));
diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c
index 81af32dc42..245af81bbb 100644
--- a/hw/arm/xilinx_zynq.c
+++ b/hw/arm/xilinx_zynq.c
@@ -26,7 +26,7 @@
#include "hw/boards.h"
#include "hw/block/flash.h"
#include "hw/loader.h"
-#include "hw/misc/zynq-xadc.h"
+#include "hw/adc/zynq-xadc.h"
#include "hw/ssi/ssi.h"
#include "hw/usb/chipidea.h"
#include "qemu/error-report.h"
diff --git a/hw/block/block.c b/hw/block/block.c
index 1e34573da7..d47ebf005a 100644
--- a/hw/block/block.c
+++ b/hw/block/block.c
@@ -65,24 +65,58 @@ bool blkconf_blocksizes(BlockConf *conf, Error **errp)
{
BlockBackend *blk = conf->blk;
BlockSizes blocksizes;
- int backend_ret;
+ BlockDriverState *bs;
+ bool use_blocksizes;
+ bool use_bs;
+
+ switch (conf->backend_defaults) {
+ case ON_OFF_AUTO_AUTO:
+ use_blocksizes = !blk_probe_blocksizes(blk, &blocksizes);
+ use_bs = false;
+ break;
+
+ case ON_OFF_AUTO_ON:
+ use_blocksizes = !blk_probe_blocksizes(blk, &blocksizes);
+ bs = blk_bs(blk);
+ use_bs = bs;
+ break;
+
+ case ON_OFF_AUTO_OFF:
+ use_blocksizes = false;
+ use_bs = false;
+ break;
+
+ default:
+ abort();
+ }
- backend_ret = blk_probe_blocksizes(blk, &blocksizes);
/* fill in detected values if they are not defined via qemu command line */
if (!conf->physical_block_size) {
- if (!backend_ret) {
+ if (use_blocksizes) {
conf->physical_block_size = blocksizes.phys;
} else {
conf->physical_block_size = BDRV_SECTOR_SIZE;
}
}
if (!conf->logical_block_size) {
- if (!backend_ret) {
+ if (use_blocksizes) {
conf->logical_block_size = blocksizes.log;
} else {
conf->logical_block_size = BDRV_SECTOR_SIZE;
}
}
+ if (use_bs) {
+ if (!conf->opt_io_size) {
+ conf->opt_io_size = bs->bl.opt_transfer;
+ }
+ if (conf->discard_granularity == -1) {
+ if (bs->bl.pdiscard_alignment) {
+ conf->discard_granularity = bs->bl.pdiscard_alignment;
+ } else if (bs->bl.request_alignment != 1) {
+ conf->discard_granularity = bs->bl.request_alignment;
+ }
+ }
+ }
if (conf->logical_block_size > conf->physical_block_size) {
error_setg(errp,
diff --git a/hw/block/dataplane/virtio-blk.c b/hw/block/dataplane/virtio-blk.c
index cd81893d1d..252c3a7a23 100644
--- a/hw/block/dataplane/virtio-blk.c
+++ b/hw/block/dataplane/virtio-blk.c
@@ -198,6 +198,10 @@ int virtio_blk_data_plane_start(VirtIODevice *vdev)
goto fail_guest_notifiers;
}
+ /*
+ * Batch all the host notifiers in a single transaction to avoid
+ * quadratic time complexity in address_space_update_ioeventfds().
+ */
memory_region_transaction_begin();
/* Set up virtqueue notify */
@@ -211,6 +215,10 @@ int virtio_blk_data_plane_start(VirtIODevice *vdev)
virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
}
+ /*
+ * The transaction expects the ioeventfds to be open when it
+ * commits. Do it now, before the cleanup loop.
+ */
memory_region_transaction_commit();
while (j--) {
@@ -330,12 +338,20 @@ void virtio_blk_data_plane_stop(VirtIODevice *vdev)
aio_context_release(s->ctx);
+ /*
+ * Batch all the host notifiers in a single transaction to avoid
+ * quadratic time complexity in address_space_update_ioeventfds().
+ */
memory_region_transaction_begin();
for (i = 0; i < nvqs; i++) {
virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
}
+ /*
+ * The transaction expects the ioeventfds to be open when it
+ * commits. Do it now, before the cleanup loop.
+ */
memory_region_transaction_commit();
for (i = 0; i < nvqs; i++) {
diff --git a/hw/display/ati.c b/hw/display/ati.c
index 4c3ad8f47b..31f22754dc 100644
--- a/hw/display/ati.c
+++ b/hw/display/ati.c
@@ -968,7 +968,7 @@ static void ati_vga_realize(PCIDevice *dev, Error **errp)
I2CBus *i2cbus = i2c_init_bus(DEVICE(s), "ati-vga.ddc");
bitbang_i2c_init(&s->bbi2c, i2cbus);
I2CSlave *i2cddc = I2C_SLAVE(qdev_new(TYPE_I2CDDC));
- i2c_set_slave_address(i2cddc, 0x50);
+ i2c_slave_set_address(i2cddc, 0x50);
qdev_realize_and_unref(DEVICE(i2cddc), BUS(i2cbus), &error_abort);
/* mmio register space */
diff --git a/hw/display/qxl.c b/hw/display/qxl.c
index 6e1f8ff1b2..84f99088e0 100644
--- a/hw/display/qxl.c
+++ b/hw/display/qxl.c
@@ -2522,6 +2522,7 @@ static const TypeInfo qxl_primary_info = {
.parent = TYPE_PCI_QXL,
.class_init = qxl_primary_class_init,
};
+module_obj("qxl-vga");
static void qxl_secondary_class_init(ObjectClass *klass, void *data)
{
@@ -2538,6 +2539,7 @@ static const TypeInfo qxl_secondary_info = {
.parent = TYPE_PCI_QXL,
.class_init = qxl_secondary_class_init,
};
+module_obj("qxl");
static void qxl_register_types(void)
{
@@ -2547,3 +2549,5 @@ static void qxl_register_types(void)
}
type_init(qxl_register_types)
+
+module_dep("ui-spice-core");
diff --git a/hw/display/sm501.c b/hw/display/sm501.c
index 8789722ef2..663c37e7f2 100644
--- a/hw/display/sm501.c
+++ b/hw/display/sm501.c
@@ -1033,16 +1033,18 @@ static void sm501_i2c_write(void *opaque, hwaddr addr, uint64_t value,
case SM501_I2C_CONTROL:
if (value & SM501_I2C_CONTROL_ENABLE) {
if (value & SM501_I2C_CONTROL_START) {
+ bool is_recv = s->i2c_addr & 1;
int res = i2c_start_transfer(s->i2c_bus,
s->i2c_addr >> 1,
- s->i2c_addr & 1);
- s->i2c_status |= (res ? SM501_I2C_STATUS_ERROR : 0);
- if (!res) {
+ is_recv);
+ if (res) {
+ s->i2c_status |= SM501_I2C_STATUS_ERROR;
+ } else {
int i;
for (i = 0; i <= s->i2c_byte_count; i++) {
- res = i2c_send_recv(s->i2c_bus, &s->i2c_data[i],
- !(s->i2c_addr & 1));
- if (res) {
+ if (is_recv) {
+ s->i2c_data[i] = i2c_recv(s->i2c_bus);
+ } else if (i2c_send(s->i2c_bus, s->i2c_data[i]) < 0) {
s->i2c_status |= SM501_I2C_STATUS_ERROR;
return;
}
@@ -1826,7 +1828,7 @@ static void sm501_init(SM501State *s, DeviceState *dev,
s->i2c_bus = i2c_init_bus(dev, "sm501.i2c");
/* ddc */
I2CDDCState *ddc = I2CDDC(qdev_new(TYPE_I2CDDC));
- i2c_set_slave_address(I2C_SLAVE(ddc), 0x50);
+ i2c_slave_set_address(I2C_SLAVE(ddc), 0x50);
qdev_realize_and_unref(DEVICE(ddc), BUS(s->i2c_bus), &error_abort);
/* mmio */
diff --git a/hw/display/vhost-user-gpu-pci.c b/hw/display/vhost-user-gpu-pci.c
index a02b23ecaf..daefcf7101 100644
--- a/hw/display/vhost-user-gpu-pci.c
+++ b/hw/display/vhost-user-gpu-pci.c
@@ -43,6 +43,7 @@ static const VirtioPCIDeviceTypeInfo vhost_user_gpu_pci_info = {
.instance_size = sizeof(VhostUserGPUPCI),
.instance_init = vhost_user_gpu_pci_initfn,
};
+module_obj(TYPE_VHOST_USER_GPU_PCI);
static void vhost_user_gpu_pci_register_types(void)
{
diff --git a/hw/display/vhost-user-gpu.c b/hw/display/vhost-user-gpu.c
index 389199e6ca..49df56cd14 100644
--- a/hw/display/vhost-user-gpu.c
+++ b/hw/display/vhost-user-gpu.c
@@ -598,6 +598,7 @@ static const TypeInfo vhost_user_gpu_info = {
.instance_finalize = vhost_user_gpu_instance_finalize,
.class_init = vhost_user_gpu_class_init,
};
+module_obj(TYPE_VHOST_USER_GPU);
static void vhost_user_gpu_register_types(void)
{
diff --git a/hw/display/vhost-user-vga.c b/hw/display/vhost-user-vga.c
index a34a99856d..072c9c65bc 100644
--- a/hw/display/vhost-user-vga.c
+++ b/hw/display/vhost-user-vga.c
@@ -44,6 +44,7 @@ static const VirtioPCIDeviceTypeInfo vhost_user_vga_info = {
.instance_size = sizeof(VhostUserVGA),
.instance_init = vhost_user_vga_inst_initfn,
};
+module_obj(TYPE_VHOST_USER_VGA);
static void vhost_user_vga_register_types(void)
{
diff --git a/hw/display/virtio-gpu-base.c b/hw/display/virtio-gpu-base.c
index dd294276cb..c8da4806e0 100644
--- a/hw/display/virtio-gpu-base.c
+++ b/hw/display/virtio-gpu-base.c
@@ -256,6 +256,7 @@ static const TypeInfo virtio_gpu_base_info = {
.class_init = virtio_gpu_base_class_init,
.abstract = true
};
+module_obj(TYPE_VIRTIO_GPU_BASE);
static void
virtio_register_types(void)
diff --git a/hw/display/virtio-gpu-gl.c b/hw/display/virtio-gpu-gl.c
index d971b48080..7ab93bf8c8 100644
--- a/hw/display/virtio-gpu-gl.c
+++ b/hw/display/virtio-gpu-gl.c
@@ -154,6 +154,7 @@ static const TypeInfo virtio_gpu_gl_info = {
.instance_size = sizeof(VirtIOGPUGL),
.class_init = virtio_gpu_gl_class_init,
};
+module_obj(TYPE_VIRTIO_GPU_GL);
static void virtio_register_types(void)
{
@@ -161,3 +162,5 @@ static void virtio_register_types(void)
}
type_init(virtio_register_types)
+
+module_dep("hw-display-virtio-gpu");
diff --git a/hw/display/virtio-gpu-pci-gl.c b/hw/display/virtio-gpu-pci-gl.c
index 902dda3452..99b14a0718 100644
--- a/hw/display/virtio-gpu-pci-gl.c
+++ b/hw/display/virtio-gpu-pci-gl.c
@@ -46,6 +46,7 @@ static const VirtioPCIDeviceTypeInfo virtio_gpu_gl_pci_info = {
.instance_size = sizeof(VirtIOGPUGLPCI),
.instance_init = virtio_gpu_gl_initfn,
};
+module_obj(TYPE_VIRTIO_GPU_GL_PCI);
static void virtio_gpu_gl_pci_register_types(void)
{
@@ -53,3 +54,5 @@ static void virtio_gpu_gl_pci_register_types(void)
}
type_init(virtio_gpu_gl_pci_register_types)
+
+module_dep("hw-display-virtio-gpu-pci");
diff --git a/hw/display/virtio-gpu-pci.c b/hw/display/virtio-gpu-pci.c
index d742a30aec..e36eee0c40 100644
--- a/hw/display/virtio-gpu-pci.c
+++ b/hw/display/virtio-gpu-pci.c
@@ -64,6 +64,7 @@ static const TypeInfo virtio_gpu_pci_base_info = {
.class_init = virtio_gpu_pci_base_class_init,
.abstract = true
};
+module_obj(TYPE_VIRTIO_GPU_PCI_BASE);
#define TYPE_VIRTIO_GPU_PCI "virtio-gpu-pci"
typedef struct VirtIOGPUPCI VirtIOGPUPCI;
@@ -90,6 +91,7 @@ static const VirtioPCIDeviceTypeInfo virtio_gpu_pci_info = {
.instance_size = sizeof(VirtIOGPUPCI),
.instance_init = virtio_gpu_initfn,
};
+module_obj(TYPE_VIRTIO_GPU_PCI);
static void virtio_gpu_pci_register_types(void)
{
diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c
index e183f4ecda..6b7f643951 100644
--- a/hw/display/virtio-gpu.c
+++ b/hw/display/virtio-gpu.c
@@ -1427,6 +1427,7 @@ static const TypeInfo virtio_gpu_info = {
.class_size = sizeof(VirtIOGPUClass),
.class_init = virtio_gpu_class_init,
};
+module_obj(TYPE_VIRTIO_GPU);
static void virtio_register_types(void)
{
diff --git a/hw/display/virtio-vga-gl.c b/hw/display/virtio-vga-gl.c
index c971340ebb..f22549097c 100644
--- a/hw/display/virtio-vga-gl.c
+++ b/hw/display/virtio-vga-gl.c
@@ -36,6 +36,7 @@ static VirtioPCIDeviceTypeInfo virtio_vga_gl_info = {
.instance_size = sizeof(VirtIOVGAGL),
.instance_init = virtio_vga_gl_inst_initfn,
};
+module_obj(TYPE_VIRTIO_VGA_GL);
static void virtio_vga_register_types(void)
{
@@ -45,3 +46,5 @@ static void virtio_vga_register_types(void)
}
type_init(virtio_vga_register_types)
+
+module_dep("hw-display-virtio-vga");
diff --git a/hw/display/virtio-vga.c b/hw/display/virtio-vga.c
index d3c6404061..9e57f61e9e 100644
--- a/hw/display/virtio-vga.c
+++ b/hw/display/virtio-vga.c
@@ -239,6 +239,7 @@ static TypeInfo virtio_vga_base_info = {
.class_init = virtio_vga_base_class_init,
.abstract = true,
};
+module_obj(TYPE_VIRTIO_VGA_BASE);
#define TYPE_VIRTIO_VGA "virtio-vga"
@@ -268,6 +269,7 @@ static VirtioPCIDeviceTypeInfo virtio_vga_info = {
.instance_size = sizeof(VirtIOVGA),
.instance_init = virtio_vga_inst_initfn,
};
+module_obj(TYPE_VIRTIO_VGA);
static void virtio_vga_register_types(void)
{
diff --git a/hw/display/xlnx_dp.c b/hw/display/xlnx_dp.c
index 4fd6aeb18b..2bb7a5441a 100644
--- a/hw/display/xlnx_dp.c
+++ b/hw/display/xlnx_dp.c
@@ -1253,7 +1253,7 @@ static void xlnx_dp_init(Object *obj)
object_property_add_child(OBJECT(s), "dpcd", OBJECT(s->dpcd));
s->edid = I2CDDC(qdev_new("i2c-ddc"));
- i2c_set_slave_address(I2C_SLAVE(s->edid), 0x50);
+ i2c_slave_set_address(I2C_SLAVE(s->edid), 0x50);
object_property_add_child(OBJECT(s), "edid", OBJECT(s->edid));
fifo8_create(&s->rx_fifo, 16);
diff --git a/hw/gpio/pl061.c b/hw/gpio/pl061.c
index e72e77572a..899be861cc 100644
--- a/hw/gpio/pl061.c
+++ b/hw/gpio/pl061.c
@@ -6,28 +6,39 @@
* Written by Paul Brook
*
* This code is licensed under the GPL.
+ *
+ * QEMU interface:
+ * + sysbus MMIO region 0: the device registers
+ * + sysbus IRQ: the GPIOINTR interrupt line
+ * + unnamed GPIO inputs 0..7: inputs to connect to the emulated GPIO lines
+ * + unnamed GPIO outputs 0..7: the emulated GPIO lines, considered as
+ * outputs
+ * + QOM property "pullups": an integer defining whether non-floating lines
+ * configured as inputs should be pulled up to logical 1 (ie whether in
+ * real hardware they have a pullup resistor on the line out of the PL061).
+ * This should be an 8-bit value, where bit 0 is 1 if GPIO line 0 should
+ * be pulled high, bit 1 configures line 1, and so on. The default is 0xff,
+ * indicating that all GPIO lines are pulled up to logical 1.
+ * + QOM property "pulldowns": an integer defining whether non-floating lines
+ * configured as inputs should be pulled down to logical 0 (ie whether in
+ * real hardware they have a pulldown resistor on the line out of the PL061).
+ * This should be an 8-bit value, where bit 0 is 1 if GPIO line 0 should
+ * be pulled low, bit 1 configures line 1, and so on. The default is 0x0.
+ * It is an error to set a bit in both "pullups" and "pulldowns". If a bit
+ * is 0 in both, then the line is considered to be floating, and it will
+ * not have qemu_set_irq() called on it when it is configured as an input.
*/
#include "qemu/osdep.h"
#include "hw/irq.h"
#include "hw/sysbus.h"
+#include "hw/qdev-properties.h"
#include "migration/vmstate.h"
+#include "qapi/error.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qom/object.h"
-
-//#define DEBUG_PL061 1
-
-#ifdef DEBUG_PL061
-#define DPRINTF(fmt, ...) \
-do { printf("pl061: " fmt , ## __VA_ARGS__); } while (0)
-#define BADF(fmt, ...) \
-do { fprintf(stderr, "pl061: error: " fmt , ## __VA_ARGS__); exit(1);} while (0)
-#else
-#define DPRINTF(fmt, ...) do {} while(0)
-#define BADF(fmt, ...) \
-do { fprintf(stderr, "pl061: error: " fmt , ## __VA_ARGS__);} while (0)
-#endif
+#include "trace.h"
static const uint8_t pl061_id[12] =
{ 0x00, 0x00, 0x00, 0x00, 0x61, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
@@ -67,7 +78,9 @@ struct PL061State {
qemu_irq irq;
qemu_irq out[N_GPIOS];
const unsigned char *id;
- uint32_t rsvd_start; /* reserved area: [rsvd_start, 0xfcc] */
+ /* Properties, for non-Luminary PL061 */
+ uint32_t pullups;
+ uint32_t pulldowns;
};
static const VMStateDescription vmstate_pl061 = {
@@ -100,26 +113,75 @@ static const VMStateDescription vmstate_pl061 = {
}
};
+static uint8_t pl061_floating(PL061State *s)
+{
+ /*
+ * Return mask of bits which correspond to pins configured as inputs
+ * and which are floating (neither pulled up to 1 nor down to 0).
+ */
+ uint8_t floating;
+
+ if (s->id == pl061_id_luminary) {
+ /*
+ * If both PUR and PDR bits are clear, there is neither a pullup
+ * nor a pulldown in place, and the output truly floats.
+ */
+ floating = ~(s->pur | s->pdr);
+ } else {
+ floating = ~(s->pullups | s->pulldowns);
+ }
+ return floating & ~s->dir;
+}
+
+static uint8_t pl061_pullups(PL061State *s)
+{
+ /*
+ * Return mask of bits which correspond to pins configured as inputs
+ * and which are pulled up to 1.
+ */
+ uint8_t pullups;
+
+ if (s->id == pl061_id_luminary) {
+ /*
+ * The Luminary variant of the PL061 has an extra registers which
+ * the guest can use to configure whether lines should be pullup
+ * or pulldown.
+ */
+ pullups = s->pur;
+ } else {
+ pullups = s->pullups;
+ }
+ return pullups & ~s->dir;
+}
+
static void pl061_update(PL061State *s)
{
uint8_t changed;
uint8_t mask;
uint8_t out;
int i;
-
- DPRINTF("dir = %d, data = %d\n", s->dir, s->data);
-
- /* Outputs float high. */
- /* FIXME: This is board dependent. */
- out = (s->data & s->dir) | ~s->dir;
+ uint8_t pullups = pl061_pullups(s);
+ uint8_t floating = pl061_floating(s);
+
+ trace_pl061_update(DEVICE(s)->canonical_path, s->dir, s->data,
+ pullups, floating);
+
+ /*
+ * Pins configured as output are driven from the data register;
+ * otherwise if they're pulled up they're 1, and if they're floating
+ * then we give them the same value they had previously, so we don't
+ * report any change to the other end.
+ */
+ out = (s->data & s->dir) | pullups | (s->old_out_data & floating);
changed = s->old_out_data ^ out;
if (changed) {
s->old_out_data = out;
for (i = 0; i < N_GPIOS; i++) {
mask = 1 << i;
if (changed & mask) {
- DPRINTF("Set output %d = %d\n", i, (out & mask) != 0);
- qemu_set_irq(s->out[i], (out & mask) != 0);
+ int level = (out & mask) != 0;
+ trace_pl061_set_output(DEVICE(s)->canonical_path, i, level);
+ qemu_set_irq(s->out[i], level);
}
}
}
@@ -131,7 +193,8 @@ static void pl061_update(PL061State *s)
for (i = 0; i < N_GPIOS; i++) {
mask = 1 << i;
if (changed & mask) {
- DPRINTF("Changed input %d = %d\n", i, (s->data & mask) != 0);
+ trace_pl061_input_change(DEVICE(s)->canonical_path, i,
+ (s->data & mask) != 0);
if (!(s->isense & mask)) {
/* Edge interrupt */
@@ -150,7 +213,8 @@ static void pl061_update(PL061State *s)
/* Level interrupt */
s->istate |= ~(s->data ^ s->iev) & s->isense;
- DPRINTF("istate = %02X\n", s->istate);
+ trace_pl061_update_istate(DEVICE(s)->canonical_path,
+ s->istate, s->im, (s->istate & s->im) != 0);
qemu_set_irq(s->irq, (s->istate & s->im) != 0);
}
@@ -159,62 +223,114 @@ static uint64_t pl061_read(void *opaque, hwaddr offset,
unsigned size)
{
PL061State *s = (PL061State *)opaque;
+ uint64_t r = 0;
- if (offset < 0x400) {
- return s->data & (offset >> 2);
- }
- if (offset >= s->rsvd_start && offset <= 0xfcc) {
- goto err_out;
- }
- if (offset >= 0xfd0 && offset < 0x1000) {
- return s->id[(offset - 0xfd0) >> 2];
- }
switch (offset) {
+ case 0x0 ... 0x3ff: /* Data */
+ r = s->data & (offset >> 2);
+ break;
case 0x400: /* Direction */
- return s->dir;
+ r = s->dir;
+ break;
case 0x404: /* Interrupt sense */
- return s->isense;
+ r = s->isense;
+ break;
case 0x408: /* Interrupt both edges */
- return s->ibe;
+ r = s->ibe;
+ break;
case 0x40c: /* Interrupt event */
- return s->iev;
+ r = s->iev;
+ break;
case 0x410: /* Interrupt mask */
- return s->im;
+ r = s->im;
+ break;
case 0x414: /* Raw interrupt status */
- return s->istate;
+ r = s->istate;
+ break;
case 0x418: /* Masked interrupt status */
- return s->istate & s->im;
+ r = s->istate & s->im;
+ break;
case 0x420: /* Alternate function select */
- return s->afsel;
+ r = s->afsel;
+ break;
case 0x500: /* 2mA drive */
- return s->dr2r;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->dr2r;
+ break;
case 0x504: /* 4mA drive */
- return s->dr4r;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->dr4r;
+ break;
case 0x508: /* 8mA drive */
- return s->dr8r;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->dr8r;
+ break;
case 0x50c: /* Open drain */
- return s->odr;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->odr;
+ break;
case 0x510: /* Pull-up */
- return s->pur;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->pur;
+ break;
case 0x514: /* Pull-down */
- return s->pdr;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->pdr;
+ break;
case 0x518: /* Slew rate control */
- return s->slr;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->slr;
+ break;
case 0x51c: /* Digital enable */
- return s->den;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->den;
+ break;
case 0x520: /* Lock */
- return s->locked;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->locked;
+ break;
case 0x524: /* Commit */
- return s->cr;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->cr;
+ break;
case 0x528: /* Analog mode select */
- return s->amsel;
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->amsel;
+ break;
+ case 0xfd0 ... 0xfff: /* ID registers */
+ r = s->id[(offset - 0xfd0) >> 2];
+ break;
default:
+ bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl061_read: Bad offset %x\n", (int)offset);
break;
}
-err_out:
- qemu_log_mask(LOG_GUEST_ERROR,
- "pl061_read: Bad offset %x\n", (int)offset);
- return 0;
+
+ trace_pl061_read(DEVICE(s)->canonical_path, offset, r);
+ return r;
}
static void pl061_write(void *opaque, hwaddr offset,
@@ -223,16 +339,14 @@ static void pl061_write(void *opaque, hwaddr offset,
PL061State *s = (PL061State *)opaque;
uint8_t mask;
- if (offset < 0x400) {
+ trace_pl061_write(DEVICE(s)->canonical_path, offset, value);
+
+ switch (offset) {
+ case 0 ... 0x3ff:
mask = (offset >> 2) & s->dir;
s->data = (s->data & ~mask) | (value & mask);
pl061_update(s);
return;
- }
- if (offset >= s->rsvd_start) {
- goto err_out;
- }
- switch (offset) {
case 0x400: /* Direction */
s->dir = value & 0xff;
break;
@@ -256,56 +370,99 @@ static void pl061_write(void *opaque, hwaddr offset,
s->afsel = (s->afsel & ~mask) | (value & mask);
break;
case 0x500: /* 2mA drive */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->dr2r = value & 0xff;
break;
case 0x504: /* 4mA drive */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->dr4r = value & 0xff;
break;
case 0x508: /* 8mA drive */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->dr8r = value & 0xff;
break;
case 0x50c: /* Open drain */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->odr = value & 0xff;
break;
case 0x510: /* Pull-up */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->pur = value & 0xff;
break;
case 0x514: /* Pull-down */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->pdr = value & 0xff;
break;
case 0x518: /* Slew rate control */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->slr = value & 0xff;
break;
case 0x51c: /* Digital enable */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->den = value & 0xff;
break;
case 0x520: /* Lock */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->locked = (value != 0xacce551);
break;
case 0x524: /* Commit */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
if (!s->locked)
s->cr = value & 0xff;
break;
case 0x528:
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
s->amsel = value & 0xff;
break;
default:
- goto err_out;
+ bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl061_write: Bad offset %x\n", (int)offset);
+ return;
}
pl061_update(s);
return;
-err_out:
- qemu_log_mask(LOG_GUEST_ERROR,
- "pl061_write: Bad offset %x\n", (int)offset);
}
-static void pl061_reset(DeviceState *dev)
+static void pl061_enter_reset(Object *obj, ResetType type)
{
- PL061State *s = PL061(dev);
+ PL061State *s = PL061(obj);
+
+ trace_pl061_reset(DEVICE(s)->canonical_path);
/* reset values from PL061 TRM, Stellaris LM3S5P31 & LM3S8962 Data Sheet */
+
+ /*
+ * FIXME: For the LM3S6965, not all of the PL061 instances have the
+ * same reset values for GPIOPUR, GPIOAFSEL and GPIODEN, so in theory
+ * we should allow the board to configure these via properties.
+ * In practice, we don't wire anything up to the affected GPIO lines
+ * (PB7, PC0, PC1, PC2, PC3 -- they're used for JTAG), so we can
+ * get away with this inaccuracy.
+ */
s->data = 0;
- s->old_out_data = 0;
s->old_in_data = 0;
s->dir = 0;
s->isense = 0;
@@ -327,6 +484,24 @@ static void pl061_reset(DeviceState *dev)
s->amsel = 0;
}
+static void pl061_hold_reset(Object *obj)
+{
+ PL061State *s = PL061(obj);
+ int i, level;
+ uint8_t floating = pl061_floating(s);
+ uint8_t pullups = pl061_pullups(s);
+
+ for (i = 0; i < N_GPIOS; i++) {
+ if (extract32(floating, i, 1)) {
+ continue;
+ }
+ level = extract32(pullups, i, 1);
+ trace_pl061_set_output(DEVICE(s)->canonical_path, i, level);
+ qemu_set_irq(s->out[i], level);
+ }
+ s->old_out_data = pullups;
+}
+
static void pl061_set_irq(void * opaque, int irq, int level)
{
PL061State *s = (PL061State *)opaque;
@@ -352,7 +527,6 @@ static void pl061_luminary_init(Object *obj)
PL061State *s = PL061(obj);
s->id = pl061_id_luminary;
- s->rsvd_start = 0x52c;
}
static void pl061_init(Object *obj)
@@ -362,7 +536,6 @@ static void pl061_init(Object *obj)
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
s->id = pl061_id;
- s->rsvd_start = 0x424;
memory_region_init_io(&s->iomem, obj, &pl061_ops, s, "pl061", 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
@@ -371,12 +544,40 @@ static void pl061_init(Object *obj)
qdev_init_gpio_out(dev, s->out, N_GPIOS);
}
+static void pl061_realize(DeviceState *dev, Error **errp)
+{
+ PL061State *s = PL061(dev);
+
+ if (s->pullups > 0xff) {
+ error_setg(errp, "pullups property must be between 0 and 0xff");
+ return;
+ }
+ if (s->pulldowns > 0xff) {
+ error_setg(errp, "pulldowns property must be between 0 and 0xff");
+ return;
+ }
+ if (s->pullups & s->pulldowns) {
+ error_setg(errp, "no bit may be set both in pullups and pulldowns");
+ return;
+ }
+}
+
+static Property pl061_props[] = {
+ DEFINE_PROP_UINT32("pullups", PL061State, pullups, 0xff),
+ DEFINE_PROP_UINT32("pulldowns", PL061State, pulldowns, 0x0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
static void pl061_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
dc->vmsd = &vmstate_pl061;
- dc->reset = &pl061_reset;
+ dc->realize = pl061_realize;
+ device_class_set_props(dc, pl061_props);
+ rc->phases.enter = pl061_enter_reset;
+ rc->phases.hold = pl061_hold_reset;
}
static const TypeInfo pl061_info = {
diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events
index f0b664158e..1dab99c560 100644
--- a/hw/gpio/trace-events
+++ b/hw/gpio/trace-events
@@ -13,6 +13,15 @@ nrf51_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x
nrf51_gpio_set(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
nrf51_gpio_update_output_irq(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
+# pl061.c
+pl061_update(const char *id, uint32_t dir, uint32_t data, uint32_t pullups, uint32_t floating) "%s GPIODIR 0x%x GPIODATA 0x%x pullups 0x%x floating 0x%x"
+pl061_set_output(const char *id, int gpio, int level) "%s setting output %d to %d"
+pl061_input_change(const char *id, int gpio, int level) "%s input %d changed to %d"
+pl061_update_istate(const char *id, uint32_t istate, uint32_t im, int level) "%s GPIORIS 0x%x GPIOIE 0x%x interrupt level %d"
+pl061_read(const char *id, uint64_t offset, uint64_t r) "%s offset 0x%" PRIx64 " value 0x%" PRIx64
+pl061_write(const char *id, uint64_t offset, uint64_t value) "%s offset 0x%" PRIx64 " value 0x%" PRIx64
+pl061_reset(const char *id) "%s reset"
+
# sifive_gpio.c
sifive_gpio_read(uint64_t offset, uint64_t r) "offset 0x%" PRIx64 " value 0x%" PRIx64
sifive_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64
diff --git a/hw/hyperv/vmbus.c b/hw/hyperv/vmbus.c
index 984caf898d..c9887d5a7b 100644
--- a/hw/hyperv/vmbus.c
+++ b/hw/hyperv/vmbus.c
@@ -2372,6 +2372,14 @@ static void vmbus_dev_realize(DeviceState *dev, Error **errp)
assert(!qemu_uuid_is_null(&vdev->instanceid));
+ if (!qemu_uuid_is_null(&vdc->instanceid)) {
+ /* Class wants to only have a single instance with a fixed UUID */
+ if (!qemu_uuid_is_equal(&vdev->instanceid, &vdc->instanceid)) {
+ error_setg(&err, "instance id can't be changed");
+ goto error_out;
+ }
+ }
+
/* Check for instance id collision for this class id */
QTAILQ_FOREACH(child, &BUS(vmbus)->children, sibling) {
VMBusDevice *child_dev = VMBUS_DEVICE(child->child);
@@ -2438,18 +2446,22 @@ static void vmbus_dev_unrealize(DeviceState *dev)
free_channels(vdev);
}
+static Property vmbus_dev_props[] = {
+ DEFINE_PROP_UUID("instanceid", VMBusDevice, instanceid),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+
static void vmbus_dev_class_init(ObjectClass *klass, void *data)
{
DeviceClass *kdev = DEVICE_CLASS(klass);
+ device_class_set_props(kdev, vmbus_dev_props);
kdev->bus_type = TYPE_VMBUS;
kdev->realize = vmbus_dev_realize;
kdev->unrealize = vmbus_dev_unrealize;
kdev->reset = vmbus_dev_reset;
}
-static Property vmbus_dev_instanceid =
- DEFINE_PROP_UUID("instanceid", VMBusDevice, instanceid);
-
static void vmbus_dev_instance_init(Object *obj)
{
VMBusDevice *vdev = VMBUS_DEVICE(obj);
@@ -2458,8 +2470,6 @@ static void vmbus_dev_instance_init(Object *obj)
if (!qemu_uuid_is_null(&vdc->instanceid)) {
/* Class wants to only have a single instance with a fixed UUID */
vdev->instanceid = vdc->instanceid;
- } else {
- qdev_property_add_static(DEVICE(vdev), &vmbus_dev_instanceid);
}
}
diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig
index 8d120a25d5..8217cb5041 100644
--- a/hw/i2c/Kconfig
+++ b/hw/i2c/Kconfig
@@ -32,3 +32,7 @@ config MPC_I2C
config PCA954X
bool
select I2C
+
+config PMBUS
+ bool
+ select SMBUS
diff --git a/hw/i2c/core.c b/hw/i2c/core.c
index 3a7bae311d..416372ad00 100644
--- a/hw/i2c/core.c
+++ b/hw/i2c/core.c
@@ -66,7 +66,7 @@ I2CBus *i2c_init_bus(DeviceState *parent, const char *name)
return bus;
}
-void i2c_set_slave_address(I2CSlave *dev, uint8_t address)
+void i2c_slave_set_address(I2CSlave *dev, uint8_t address)
{
dev->address = address;
}
@@ -114,8 +114,11 @@ bool i2c_scan_bus(I2CBus *bus, uint8_t address, bool broadcast,
* protocol uses a start transfer to switch from write to read mode
* without releasing the bus. If that fails, the bus is still
* in a transaction.
+ *
+ * @event must be I2C_START_RECV or I2C_START_SEND.
*/
-int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv)
+static int i2c_do_start_transfer(I2CBus *bus, uint8_t address,
+ enum i2c_event event)
{
I2CSlaveClass *sc;
I2CNode *node;
@@ -157,7 +160,7 @@ int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv)
if (sc->event) {
trace_i2c_event("start", s->address);
- rv = sc->event(s, recv ? I2C_START_RECV : I2C_START_SEND);
+ rv = sc->event(s, event);
if (rv && !bus->broadcast) {
if (bus_scanned) {
/* First call, terminate the transfer. */
@@ -170,6 +173,23 @@ int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv)
return 0;
}
+int i2c_start_transfer(I2CBus *bus, uint8_t address, bool is_recv)
+{
+ return i2c_do_start_transfer(bus, address, is_recv
+ ? I2C_START_RECV
+ : I2C_START_SEND);
+}
+
+int i2c_start_recv(I2CBus *bus, uint8_t address)
+{
+ return i2c_do_start_transfer(bus, address, I2C_START_RECV);
+}
+
+int i2c_start_send(I2CBus *bus, uint8_t address)
+{
+ return i2c_do_start_transfer(bus, address, I2C_START_SEND);
+}
+
void i2c_end_transfer(I2CBus *bus)
{
I2CSlaveClass *sc;
@@ -188,50 +208,42 @@ void i2c_end_transfer(I2CBus *bus)
bus->broadcast = false;
}
-int i2c_send_recv(I2CBus *bus, uint8_t *data, bool send)
+int i2c_send(I2CBus *bus, uint8_t data)
{
I2CSlaveClass *sc;
I2CSlave *s;
I2CNode *node;
int ret = 0;
- if (send) {
- QLIST_FOREACH(node, &bus->current_devs, next) {
- s = node->elt;
- sc = I2C_SLAVE_GET_CLASS(s);
- if (sc->send) {
- trace_i2c_send(s->address, *data);
- ret = ret || sc->send(s, *data);
- } else {
- ret = -1;
- }
- }
- return ret ? -1 : 0;
- } else {
- ret = 0xff;
- if (!QLIST_EMPTY(&bus->current_devs) && !bus->broadcast) {
- sc = I2C_SLAVE_GET_CLASS(QLIST_FIRST(&bus->current_devs)->elt);
- if (sc->recv) {
- s = QLIST_FIRST(&bus->current_devs)->elt;
- ret = sc->recv(s);
- trace_i2c_recv(s->address, ret);
- }
+ QLIST_FOREACH(node, &bus->current_devs, next) {
+ s = node->elt;
+ sc = I2C_SLAVE_GET_CLASS(s);
+ if (sc->send) {
+ trace_i2c_send(s->address, data);
+ ret = ret || sc->send(s, data);
+ } else {
+ ret = -1;
}
- *data = ret;
- return 0;
}
-}
-int i2c_send(I2CBus *bus, uint8_t data)
-{
- return i2c_send_recv(bus, &data, true);
+ return ret ? -1 : 0;
}
uint8_t i2c_recv(I2CBus *bus)
{
uint8_t data = 0xff;
+ I2CSlaveClass *sc;
+ I2CSlave *s;
+
+ if (!QLIST_EMPTY(&bus->current_devs) && !bus->broadcast) {
+ sc = I2C_SLAVE_GET_CLASS(QLIST_FIRST(&bus->current_devs)->elt);
+ if (sc->recv) {
+ s = QLIST_FIRST(&bus->current_devs)->elt;
+ data = sc->recv(s);
+ trace_i2c_recv(s->address, data);
+ }
+ }
- i2c_send_recv(bus, &data, false);
return data;
}
diff --git a/hw/i2c/imx_i2c.c b/hw/i2c/imx_i2c.c
index 2e02e1c4fa..9792583fea 100644
--- a/hw/i2c/imx_i2c.c
+++ b/hw/i2c/imx_i2c.c
@@ -171,7 +171,7 @@ static void imx_i2c_write(void *opaque, hwaddr offset,
switch (offset) {
case IADR_ADDR:
s->iadr = value & IADR_MASK;
- /* i2c_set_slave_address(s->bus, (uint8_t)s->iadr); */
+ /* i2c_slave_set_address(s->bus, (uint8_t)s->iadr); */
break;
case IFDR_ADDR:
s->ifdr = value & IFDR_MASK;
diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
index dd3aef02b2..d3df273251 100644
--- a/hw/i2c/meson.build
+++ b/hw/i2c/meson.build
@@ -15,4 +15,5 @@ i2c_ss.add(when: 'CONFIG_VERSATILE_I2C', if_true: files('versatile_i2c.c'))
i2c_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_i2c.c'))
i2c_ss.add(when: 'CONFIG_PPC4XX', if_true: files('ppc4xx_i2c.c'))
i2c_ss.add(when: 'CONFIG_PCA954X', if_true: files('i2c_mux_pca954x.c'))
+i2c_ss.add(when: 'CONFIG_PMBUS', if_true: files('pmbus_device.c'))
softmmu_ss.add_all(when: 'CONFIG_I2C', if_true: i2c_ss)
diff --git a/hw/i2c/pm_smbus.c b/hw/i2c/pm_smbus.c
index 06e1e5321b..d7eae548cb 100644
--- a/hw/i2c/pm_smbus.c
+++ b/hw/i2c/pm_smbus.c
@@ -128,14 +128,14 @@ static void smb_transaction(PMSMBus *s)
* So at least Linux may or may not set the read bit here.
* So just ignore the read bit for this command.
*/
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
goto error;
}
ret = i2c_send(bus, s->smb_data1);
if (ret) {
goto error;
}
- if (i2c_start_transfer(bus, addr, 1)) {
+ if (i2c_start_recv(bus, addr)) {
goto error;
}
s->in_i2c_block_read = true;
diff --git a/hw/i2c/pmbus_device.c b/hw/i2c/pmbus_device.c
new file mode 100644
index 0000000000..24f8f522d9
--- /dev/null
+++ b/hw/i2c/pmbus_device.c
@@ -0,0 +1,1612 @@
+/*
+ * PMBus wrapper over SMBus
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include <math.h>
+#include <string.h>
+#include "hw/i2c/pmbus_device.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+
+uint16_t pmbus_data2direct_mode(PMBusCoefficients c, uint32_t value)
+{
+ /* R is usually negative to fit large readings into 16 bits */
+ uint16_t y = (c.m * value + c.b) * pow(10, c.R);
+ return y;
+}
+
+uint32_t pmbus_direct_mode2data(PMBusCoefficients c, uint16_t value)
+{
+ /* X = (Y * 10^-R - b) / m */
+ uint32_t x = (value / pow(10, c.R) - c.b) / c.m;
+ return x;
+}
+
+void pmbus_send(PMBusDevice *pmdev, const uint8_t *data, uint16_t len)
+{
+ if (pmdev->out_buf_len + len > SMBUS_DATA_MAX_LEN) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMBus device tried to send too much data");
+ len = 0;
+ }
+
+ for (int i = len - 1; i >= 0; i--) {
+ pmdev->out_buf[i + pmdev->out_buf_len] = data[len - i - 1];
+ }
+ pmdev->out_buf_len += len;
+}
+
+/* Internal only, convert unsigned ints to the little endian bus */
+static void pmbus_send_uint(PMBusDevice *pmdev, uint64_t data, uint8_t size)
+{
+ uint8_t bytes[8];
+ g_assert(size <= 8);
+
+ for (int i = 0; i < size; i++) {
+ bytes[i] = data & 0xFF;
+ data = data >> 8;
+ }
+ pmbus_send(pmdev, bytes, size);
+}
+
+void pmbus_send8(PMBusDevice *pmdev, uint8_t data)
+{
+ pmbus_send_uint(pmdev, data, 1);
+}
+
+void pmbus_send16(PMBusDevice *pmdev, uint16_t data)
+{
+ pmbus_send_uint(pmdev, data, 2);
+}
+
+void pmbus_send32(PMBusDevice *pmdev, uint32_t data)
+{
+ pmbus_send_uint(pmdev, data, 4);
+}
+
+void pmbus_send64(PMBusDevice *pmdev, uint64_t data)
+{
+ pmbus_send_uint(pmdev, data, 8);
+}
+
+void pmbus_send_string(PMBusDevice *pmdev, const char *data)
+{
+ size_t len = strlen(data);
+ g_assert(len > 0);
+ g_assert(len + pmdev->out_buf_len < SMBUS_DATA_MAX_LEN);
+ pmdev->out_buf[len + pmdev->out_buf_len] = len;
+
+ for (int i = len - 1; i >= 0; i--) {
+ pmdev->out_buf[i + pmdev->out_buf_len] = data[len - 1 - i];
+ }
+ pmdev->out_buf_len += len + 1;
+}
+
+
+static uint64_t pmbus_receive_uint(const uint8_t *buf, uint8_t len)
+{
+ uint64_t ret = 0;
+
+ /* Exclude command code from return value */
+ buf++;
+ len--;
+
+ for (int i = len - 1; i >= 0; i--) {
+ ret = ret << 8 | buf[i];
+ }
+ return ret;
+}
+
+uint8_t pmbus_receive8(PMBusDevice *pmdev)
+{
+ if (pmdev->in_buf_len - 1 != 1) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: length mismatch. Expected 1 byte, got %d bytes\n",
+ __func__, pmdev->in_buf_len - 1);
+ }
+ return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
+}
+
+uint16_t pmbus_receive16(PMBusDevice *pmdev)
+{
+ if (pmdev->in_buf_len - 1 != 2) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: length mismatch. Expected 2 bytes, got %d bytes\n",
+ __func__, pmdev->in_buf_len - 1);
+ }
+ return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
+}
+
+uint32_t pmbus_receive32(PMBusDevice *pmdev)
+{
+ if (pmdev->in_buf_len - 1 != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: length mismatch. Expected 4 bytes, got %d bytes\n",
+ __func__, pmdev->in_buf_len - 1);
+ }
+ return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
+}
+
+uint64_t pmbus_receive64(PMBusDevice *pmdev)
+{
+ if (pmdev->in_buf_len - 1 != 8) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: length mismatch. Expected 8 bytes, got %d bytes\n",
+ __func__, pmdev->in_buf_len - 1);
+ }
+ return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
+}
+
+static uint8_t pmbus_out_buf_pop(PMBusDevice *pmdev)
+{
+ if (pmdev->out_buf_len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: tried to read from empty buffer",
+ __func__);
+ return 0xFF;
+ }
+ uint8_t data = pmdev->out_buf[pmdev->out_buf_len - 1];
+ pmdev->out_buf_len--;
+ return data;
+}
+
+static void pmbus_quick_cmd(SMBusDevice *smd, uint8_t read)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(smd);
+ PMBusDeviceClass *pmdc = PMBUS_DEVICE_GET_CLASS(pmdev);
+
+ if (pmdc->quick_cmd) {
+ pmdc->quick_cmd(pmdev, read);
+ }
+}
+
+static void pmbus_pages_alloc(PMBusDevice *pmdev)
+{
+ /* some PMBus devices don't use the PAGE command, so they get 1 page */
+ PMBusDeviceClass *k = PMBUS_DEVICE_GET_CLASS(pmdev);
+ if (k->device_num_pages == 0) {
+ k->device_num_pages = 1;
+ }
+ pmdev->num_pages = k->device_num_pages;
+ pmdev->pages = g_new0(PMBusPage, k->device_num_pages);
+}
+
+void pmbus_check_limits(PMBusDevice *pmdev)
+{
+ for (int i = 0; i < pmdev->num_pages; i++) {
+ if ((pmdev->pages[i].operation & PB_OP_ON) == 0) {
+ continue; /* don't check powered off devices */
+ }
+
+ if (pmdev->pages[i].read_vout > pmdev->pages[i].vout_ov_fault_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_VOUT;
+ pmdev->pages[i].status_vout |= PB_STATUS_VOUT_OV_FAULT;
+ }
+
+ if (pmdev->pages[i].read_vout > pmdev->pages[i].vout_ov_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_VOUT;
+ pmdev->pages[i].status_vout |= PB_STATUS_VOUT_OV_WARN;
+ }
+
+ if (pmdev->pages[i].read_vout < pmdev->pages[i].vout_uv_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_VOUT;
+ pmdev->pages[i].status_vout |= PB_STATUS_VOUT_UV_WARN;
+ }
+
+ if (pmdev->pages[i].read_vout < pmdev->pages[i].vout_uv_fault_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_VOUT;
+ pmdev->pages[i].status_vout |= PB_STATUS_VOUT_UV_FAULT;
+ }
+
+ if (pmdev->pages[i].read_vin > pmdev->pages[i].vin_ov_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_INPUT;
+ pmdev->pages[i].status_input |= PB_STATUS_INPUT_VIN_OV_WARN;
+ }
+
+ if (pmdev->pages[i].read_vin < pmdev->pages[i].vin_uv_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_INPUT;
+ pmdev->pages[i].status_input |= PB_STATUS_INPUT_VIN_UV_WARN;
+ }
+
+ if (pmdev->pages[i].read_iout > pmdev->pages[i].iout_oc_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_IOUT_POUT;
+ pmdev->pages[i].status_iout |= PB_STATUS_IOUT_OC_WARN;
+ }
+
+ if (pmdev->pages[i].read_iout > pmdev->pages[i].iout_oc_fault_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_IOUT_POUT;
+ pmdev->pages[i].status_iout |= PB_STATUS_IOUT_OC_FAULT;
+ }
+
+ if (pmdev->pages[i].read_pin > pmdev->pages[i].pin_op_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_INPUT;
+ pmdev->pages[i].status_input |= PB_STATUS_INPUT_PIN_OP_WARN;
+ }
+
+ if (pmdev->pages[i].read_temperature_1
+ > pmdev->pages[i].ot_fault_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_TEMPERATURE;
+ pmdev->pages[i].status_temperature |= PB_STATUS_OT_FAULT;
+ }
+
+ if (pmdev->pages[i].read_temperature_1
+ > pmdev->pages[i].ot_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_TEMPERATURE;
+ pmdev->pages[i].status_temperature |= PB_STATUS_OT_WARN;
+ }
+ }
+}
+
+static uint8_t pmbus_receive_byte(SMBusDevice *smd)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(smd);
+ PMBusDeviceClass *pmdc = PMBUS_DEVICE_GET_CLASS(pmdev);
+ uint8_t ret = 0xFF;
+ uint8_t index = pmdev->page;
+
+ if (pmdev->out_buf_len != 0) {
+ ret = pmbus_out_buf_pop(pmdev);
+ return ret;
+ }
+
+ switch (pmdev->code) {
+ case PMBUS_PAGE:
+ pmbus_send8(pmdev, pmdev->page);
+ break;
+
+ case PMBUS_OPERATION: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].operation);
+ break;
+
+ case PMBUS_ON_OFF_CONFIG: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].on_off_config);
+ break;
+
+ case PMBUS_PHASE: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].phase);
+ break;
+
+ case PMBUS_WRITE_PROTECT: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].write_protect);
+ break;
+
+ case PMBUS_CAPABILITY:
+ pmbus_send8(pmdev, pmdev->capability);
+ break;
+
+ case PMBUS_VOUT_MODE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MODE) {
+ pmbus_send8(pmdev, pmdev->pages[index].vout_mode);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_COMMAND: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_command);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_TRIM: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_trim);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_CAL_OFFSET: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_cal_offset);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_MAX: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_MARGIN_HIGH: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_margin_high);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_MARGIN_LOW: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_margin_low);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_TRANSITION_RATE: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_transition_rate);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_DROOP: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_droop);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_SCALE_LOOP: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_scale_loop);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_SCALE_MONITOR: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_scale_monitor);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ /* TODO: implement coefficients support */
+
+ case PMBUS_POUT_MAX: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].pout_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_ON: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_on);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_OFF: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_off);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_CAL_GAIN: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT_GAIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].iout_cal_gain);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_ov_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].vout_ov_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_ov_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_uv_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_uv_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].vout_uv_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].iout_oc_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].iout_oc_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_LV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].iout_oc_lv_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_LV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].iout_oc_lv_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].iout_oc_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_UC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].iout_uc_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_UC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].iout_uc_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_OT_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send16(pmdev, pmdev->pages[index].ot_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_OT_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send8(pmdev, pmdev->pages[index].ot_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_OT_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send16(pmdev, pmdev->pages[index].ot_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_UT_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send16(pmdev, pmdev->pages[index].ut_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_UT_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send16(pmdev, pmdev->pages[index].ut_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_UT_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send8(pmdev, pmdev->pages[index].ut_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_ov_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send8(pmdev, pmdev->pages[index].vin_ov_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_ov_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_uv_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_uv_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send8(pmdev, pmdev->pages[index].vin_uv_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].iin_oc_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmbus_send8(pmdev, pmdev->pages[index].iin_oc_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].iin_oc_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].pout_op_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].pout_op_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].pout_op_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_PIN_OP_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_PIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].pin_op_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_STATUS_BYTE: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].status_word & 0xFF);
+ break;
+
+ case PMBUS_STATUS_WORD: /* R/W word */
+ pmbus_send16(pmdev, pmdev->pages[index].status_word);
+ break;
+
+ case PMBUS_STATUS_VOUT: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].status_vout);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_STATUS_IOUT: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].status_iout);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_STATUS_INPUT: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN ||
+ pmdev->pages[index].page_flags & PB_HAS_IIN ||
+ pmdev->pages[index].page_flags & PB_HAS_PIN) {
+ pmbus_send8(pmdev, pmdev->pages[index].status_input);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_STATUS_TEMPERATURE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send8(pmdev, pmdev->pages[index].status_temperature);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_STATUS_CML: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].status_cml);
+ break;
+
+ case PMBUS_STATUS_OTHER: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].status_other);
+ break;
+
+ case PMBUS_READ_EIN: /* Read-Only block 5 bytes */
+ if (pmdev->pages[index].page_flags & PB_HAS_EIN) {
+ pmbus_send(pmdev, pmdev->pages[index].read_ein, 5);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_EOUT: /* Read-Only block 5 bytes */
+ if (pmdev->pages[index].page_flags & PB_HAS_EOUT) {
+ pmbus_send(pmdev, pmdev->pages[index].read_eout, 5);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_VIN: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_vin);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_IIN: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_iin);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_VOUT: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_vout);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_IOUT: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_iout);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_TEMPERATURE_1: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_temperature_1);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_TEMPERATURE_2: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMP2) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_temperature_2);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_TEMPERATURE_3: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMP3) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_temperature_3);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_POUT: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_pout);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_PIN: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_PIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_pin);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_REVISION: /* Read-Only byte */
+ pmbus_send8(pmdev, pmdev->pages[index].revision);
+ break;
+
+ case PMBUS_MFR_ID: /* R/W block */
+ if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
+ pmbus_send_string(pmdev, pmdev->pages[index].mfr_id);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_MODEL: /* R/W block */
+ if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
+ pmbus_send_string(pmdev, pmdev->pages[index].mfr_model);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_REVISION: /* R/W block */
+ if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
+ pmbus_send_string(pmdev, pmdev->pages[index].mfr_revision);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_LOCATION: /* R/W block */
+ if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
+ pmbus_send_string(pmdev, pmdev->pages[index].mfr_location);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_VIN_MIN: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_vin_min);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_VIN_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_vin_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_IIN_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_iin_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_PIN_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_PIN_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_pin_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_VOUT_MIN: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_vout_min);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_VOUT_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_vout_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_IOUT_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_iout_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_POUT_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_pout_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_MAX_TEMP_1: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMP_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_max_temp_1);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_MAX_TEMP_2: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMP_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_max_temp_2);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_MAX_TEMP_3: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMP_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_max_temp_3);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_CLEAR_FAULTS: /* Send Byte */
+ case PMBUS_PAGE_PLUS_WRITE: /* Block Write-only */
+ case PMBUS_STORE_DEFAULT_ALL: /* Send Byte */
+ case PMBUS_RESTORE_DEFAULT_ALL: /* Send Byte */
+ case PMBUS_STORE_DEFAULT_CODE: /* Write-only Byte */
+ case PMBUS_RESTORE_DEFAULT_CODE: /* Write-only Byte */
+ case PMBUS_STORE_USER_ALL: /* Send Byte */
+ case PMBUS_RESTORE_USER_ALL: /* Send Byte */
+ case PMBUS_STORE_USER_CODE: /* Write-only Byte */
+ case PMBUS_RESTORE_USER_CODE: /* Write-only Byte */
+ case PMBUS_QUERY: /* Write-Only */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: reading from write only register 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+
+passthough:
+ default:
+ /* Pass through read request if not handled */
+ if (pmdc->receive_byte) {
+ ret = pmdc->receive_byte(pmdev);
+ }
+ break;
+ }
+
+ if (pmdev->out_buf_len != 0) {
+ ret = pmbus_out_buf_pop(pmdev);
+ return ret;
+ }
+
+ return ret;
+}
+
+/*
+ * PMBus clear faults command applies to all status registers, existing faults
+ * should separately get re-asserted.
+ */
+static void pmbus_clear_faults(PMBusDevice *pmdev)
+{
+ for (uint8_t i = 0; i < pmdev->num_pages; i++) {
+ pmdev->pages[i].status_word = 0;
+ pmdev->pages[i].status_vout = 0;
+ pmdev->pages[i].status_iout = 0;
+ pmdev->pages[i].status_input = 0;
+ pmdev->pages[i].status_temperature = 0;
+ pmdev->pages[i].status_cml = 0;
+ pmdev->pages[i].status_other = 0;
+ pmdev->pages[i].status_mfr_specific = 0;
+ pmdev->pages[i].status_fans_1_2 = 0;
+ pmdev->pages[i].status_fans_3_4 = 0;
+ }
+
+}
+
+/*
+ * PMBus operation is used to turn On and Off PSUs
+ * Therefore, default value for the Operation should be PB_OP_ON or 0x80
+ */
+static void pmbus_operation(PMBusDevice *pmdev)
+{
+ uint8_t index = pmdev->page;
+ if ((pmdev->pages[index].operation & PB_OP_ON) == 0) {
+ pmdev->pages[index].read_vout = 0;
+ pmdev->pages[index].read_iout = 0;
+ pmdev->pages[index].read_pout = 0;
+ return;
+ }
+
+ if (pmdev->pages[index].operation & (PB_OP_ON | PB_OP_MARGIN_HIGH)) {
+ pmdev->pages[index].read_vout = pmdev->pages[index].vout_margin_high;
+ }
+
+ if (pmdev->pages[index].operation & (PB_OP_ON | PB_OP_MARGIN_LOW)) {
+ pmdev->pages[index].read_vout = pmdev->pages[index].vout_margin_low;
+ }
+ pmbus_check_limits(pmdev);
+}
+
+static int pmbus_write_data(SMBusDevice *smd, uint8_t *buf, uint8_t len)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(smd);
+ PMBusDeviceClass *pmdc = PMBUS_DEVICE_GET_CLASS(pmdev);
+ int ret = 0;
+ uint8_t index;
+
+ if (len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
+ return -1;
+ }
+
+ if (!pmdev->pages) { /* allocate memory for pages on first use */
+ pmbus_pages_alloc(pmdev);
+ }
+
+ pmdev->in_buf_len = len;
+ pmdev->in_buf = buf;
+
+ pmdev->code = buf[0]; /* PMBus command code */
+ if (len == 1) { /* Single length writes are command codes only */
+ return 0;
+ }
+
+ if (pmdev->code == PMBUS_PAGE) {
+ pmdev->page = pmbus_receive8(pmdev);
+ return 0;
+ }
+ /* loop through all the pages when 0xFF is received */
+ if (pmdev->page == PB_ALL_PAGES) {
+ for (int i = 0; i < pmdev->num_pages; i++) {
+ pmdev->page = i;
+ pmbus_write_data(smd, buf, len);
+ }
+ pmdev->page = PB_ALL_PAGES;
+ return 0;
+ }
+
+ index = pmdev->page;
+
+ switch (pmdev->code) {
+ case PMBUS_OPERATION: /* R/W byte */
+ pmdev->pages[index].operation = pmbus_receive8(pmdev);
+ pmbus_operation(pmdev);
+ break;
+
+ case PMBUS_ON_OFF_CONFIG: /* R/W byte */
+ pmdev->pages[index].on_off_config = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_CLEAR_FAULTS: /* Send Byte */
+ pmbus_clear_faults(pmdev);
+ break;
+
+ case PMBUS_PHASE: /* R/W byte */
+ pmdev->pages[index].phase = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_PAGE_PLUS_WRITE: /* Block Write-only */
+ case PMBUS_WRITE_PROTECT: /* R/W byte */
+ pmdev->pages[index].write_protect = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_VOUT_MODE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MODE) {
+ pmdev->pages[index].vout_mode = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_COMMAND: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_command = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_TRIM: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_trim = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_CAL_OFFSET: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_cal_offset = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_MAX: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_max = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_MARGIN_HIGH: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
+ pmdev->pages[index].vout_margin_high = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_MARGIN_LOW: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
+ pmdev->pages[index].vout_margin_low = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_TRANSITION_RATE: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_transition_rate = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_DROOP: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_droop = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_SCALE_LOOP: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_scale_loop = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_SCALE_MONITOR: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_scale_monitor = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_POUT_MAX: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].pout_max = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_ON: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_on = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_OFF: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_off = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_CAL_GAIN: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT_GAIN) {
+ pmdev->pages[index].iout_cal_gain = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_ov_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_ov_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_ov_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_uv_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_uv_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_uv_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_oc_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_oc_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_LV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_oc_lv_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_LV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_oc_lv_fault_response
+ = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_oc_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_UC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_uc_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_UC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_uc_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_OT_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ot_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_OT_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ot_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_OT_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ot_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_UT_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ut_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_UT_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ut_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_UT_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ut_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_ov_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_ov_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_ov_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_uv_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_uv_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_uv_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmdev->pages[index].iin_oc_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmdev->pages[index].iin_oc_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmdev->pages[index].iin_oc_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].pout_op_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].pout_op_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].pout_op_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_PIN_OP_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_PIN) {
+ pmdev->pages[index].pin_op_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_STATUS_BYTE: /* R/W byte */
+ pmdev->pages[index].status_word = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_STATUS_WORD: /* R/W word */
+ pmdev->pages[index].status_word = pmbus_receive16(pmdev);
+ break;
+
+ case PMBUS_STATUS_VOUT: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].status_vout = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_STATUS_IOUT: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].status_iout = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_STATUS_INPUT: /* R/W byte */
+ pmdev->pages[index].status_input = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_STATUS_TEMPERATURE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].status_temperature = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_STATUS_CML: /* R/W byte */
+ pmdev->pages[index].status_cml = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_STATUS_OTHER: /* R/W byte */
+ pmdev->pages[index].status_other = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_PAGE_PLUS_READ: /* Block Read-only */
+ case PMBUS_CAPABILITY: /* Read-Only byte */
+ case PMBUS_COEFFICIENTS: /* Read-only block 5 bytes */
+ case PMBUS_READ_EIN: /* Read-Only block 5 bytes */
+ case PMBUS_READ_EOUT: /* Read-Only block 5 bytes */
+ case PMBUS_READ_VIN: /* Read-Only word */
+ case PMBUS_READ_IIN: /* Read-Only word */
+ case PMBUS_READ_VCAP: /* Read-Only word */
+ case PMBUS_READ_VOUT: /* Read-Only word */
+ case PMBUS_READ_IOUT: /* Read-Only word */
+ case PMBUS_READ_TEMPERATURE_1: /* Read-Only word */
+ case PMBUS_READ_TEMPERATURE_2: /* Read-Only word */
+ case PMBUS_READ_TEMPERATURE_3: /* Read-Only word */
+ case PMBUS_READ_FAN_SPEED_1: /* Read-Only word */
+ case PMBUS_READ_FAN_SPEED_2: /* Read-Only word */
+ case PMBUS_READ_FAN_SPEED_3: /* Read-Only word */
+ case PMBUS_READ_FAN_SPEED_4: /* Read-Only word */
+ case PMBUS_READ_DUTY_CYCLE: /* Read-Only word */
+ case PMBUS_READ_FREQUENCY: /* Read-Only word */
+ case PMBUS_READ_POUT: /* Read-Only word */
+ case PMBUS_READ_PIN: /* Read-Only word */
+ case PMBUS_REVISION: /* Read-Only byte */
+ case PMBUS_APP_PROFILE_SUPPORT: /* Read-Only block-read */
+ case PMBUS_MFR_VIN_MIN: /* Read-Only word */
+ case PMBUS_MFR_VIN_MAX: /* Read-Only word */
+ case PMBUS_MFR_IIN_MAX: /* Read-Only word */
+ case PMBUS_MFR_PIN_MAX: /* Read-Only word */
+ case PMBUS_MFR_VOUT_MIN: /* Read-Only word */
+ case PMBUS_MFR_VOUT_MAX: /* Read-Only word */
+ case PMBUS_MFR_IOUT_MAX: /* Read-Only word */
+ case PMBUS_MFR_POUT_MAX: /* Read-Only word */
+ case PMBUS_MFR_TAMBIENT_MAX: /* Read-Only word */
+ case PMBUS_MFR_TAMBIENT_MIN: /* Read-Only word */
+ case PMBUS_MFR_EFFICIENCY_LL: /* Read-Only block 14 bytes */
+ case PMBUS_MFR_EFFICIENCY_HL: /* Read-Only block 14 bytes */
+ case PMBUS_MFR_PIN_ACCURACY: /* Read-Only byte */
+ case PMBUS_IC_DEVICE_ID: /* Read-Only block-read */
+ case PMBUS_IC_DEVICE_REV: /* Read-Only block-read */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to read-only register 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+
+passthrough:
+ /* Unimplimented registers get passed to the device */
+ default:
+ if (pmdc->write_data) {
+ ret = pmdc->write_data(pmdev, buf, len);
+ }
+ break;
+ }
+ pmbus_check_limits(pmdev);
+ pmdev->in_buf_len = 0;
+ return ret;
+}
+
+int pmbus_page_config(PMBusDevice *pmdev, uint8_t index, uint64_t flags)
+{
+ if (!pmdev->pages) { /* allocate memory for pages on first use */
+ pmbus_pages_alloc(pmdev);
+ }
+
+ /* The 0xFF page is special for commands applying to all pages */
+ if (index == PB_ALL_PAGES) {
+ for (int i = 0; i < pmdev->num_pages; i++) {
+ pmdev->pages[i].page_flags = flags;
+ }
+ return 0;
+ }
+
+ if (index > pmdev->num_pages - 1) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: index %u is out of range\n",
+ __func__, index);
+ return -1;
+ }
+
+ pmdev->pages[index].page_flags = flags;
+
+ return 0;
+}
+
+/* TODO: include pmbus page info in vmstate */
+const VMStateDescription vmstate_pmbus_device = {
+ .name = TYPE_PMBUS_DEVICE,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_SMBUS_DEVICE(smb, PMBusDevice),
+ VMSTATE_UINT8(num_pages, PMBusDevice),
+ VMSTATE_UINT8(code, PMBusDevice),
+ VMSTATE_UINT8(page, PMBusDevice),
+ VMSTATE_UINT8(capability, PMBusDevice),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pmbus_device_finalize(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ g_free(pmdev->pages);
+}
+
+static void pmbus_device_class_init(ObjectClass *klass, void *data)
+{
+ SMBusDeviceClass *k = SMBUS_DEVICE_CLASS(klass);
+
+ k->quick_cmd = pmbus_quick_cmd;
+ k->write_data = pmbus_write_data;
+ k->receive_byte = pmbus_receive_byte;
+}
+
+static const TypeInfo pmbus_device_type_info = {
+ .name = TYPE_PMBUS_DEVICE,
+ .parent = TYPE_SMBUS_DEVICE,
+ .instance_size = sizeof(PMBusDevice),
+ .instance_finalize = pmbus_device_finalize,
+ .abstract = true,
+ .class_size = sizeof(PMBusDeviceClass),
+ .class_init = pmbus_device_class_init,
+};
+
+static void pmbus_device_register_types(void)
+{
+ type_register_static(&pmbus_device_type_info);
+}
+
+type_init(pmbus_device_register_types)
diff --git a/hw/i2c/ppc4xx_i2c.c b/hw/i2c/ppc4xx_i2c.c
index c0a8e04567..75d50f1515 100644
--- a/hw/i2c/ppc4xx_i2c.c
+++ b/hw/i2c/ppc4xx_i2c.c
@@ -1,6 +1,8 @@
/*
* PPC4xx I2C controller emulation
*
+ * Documentation: PPC405GP User's Manual, Chapter 22. IIC Bus Interface
+ *
* Copyright (c) 2007 Jocelyn Mayer
* Copyright (c) 2012 François Revol
* Copyright (c) 2016-2018 BALATON Zoltan
@@ -238,11 +240,14 @@ static void ppc4xx_i2c_writeb(void *opaque, hwaddr addr, uint64_t value,
i2c->sts &= ~IIC_STS_ERR;
}
}
- if (!(i2c->sts & IIC_STS_ERR) &&
- i2c_send_recv(i2c->bus, &i2c->mdata[i], !recv)) {
- i2c->sts |= IIC_STS_ERR;
- i2c->extsts |= IIC_EXTSTS_XFRA;
- break;
+ if (!(i2c->sts & IIC_STS_ERR)) {
+ if (recv) {
+ i2c->mdata[i] = i2c_recv(i2c->bus);
+ } else if (i2c_send(i2c->bus, i2c->mdata[i]) < 0) {
+ i2c->sts |= IIC_STS_ERR;
+ i2c->extsts |= IIC_EXTSTS_XFRA;
+ break;
+ }
}
if (value & IIC_CNTL_RPST || !(value & IIC_CNTL_CHT)) {
i2c_end_transfer(i2c->bus);
diff --git a/hw/i2c/smbus_master.c b/hw/i2c/smbus_master.c
index dc43b8637d..6a53c34e70 100644
--- a/hw/i2c/smbus_master.c
+++ b/hw/i2c/smbus_master.c
@@ -29,7 +29,7 @@ int smbus_receive_byte(I2CBus *bus, uint8_t addr)
{
uint8_t data;
- if (i2c_start_transfer(bus, addr, 1)) {
+ if (i2c_start_recv(bus, addr)) {
return -1;
}
data = i2c_recv(bus);
@@ -40,7 +40,7 @@ int smbus_receive_byte(I2CBus *bus, uint8_t addr)
int smbus_send_byte(I2CBus *bus, uint8_t addr, uint8_t data)
{
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
return -1;
}
i2c_send(bus, data);
@@ -51,11 +51,11 @@ int smbus_send_byte(I2CBus *bus, uint8_t addr, uint8_t data)
int smbus_read_byte(I2CBus *bus, uint8_t addr, uint8_t command)
{
uint8_t data;
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
return -1;
}
i2c_send(bus, command);
- if (i2c_start_transfer(bus, addr, 1)) {
+ if (i2c_start_recv(bus, addr)) {
i2c_end_transfer(bus);
return -1;
}
@@ -67,7 +67,7 @@ int smbus_read_byte(I2CBus *bus, uint8_t addr, uint8_t command)
int smbus_write_byte(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t data)
{
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
return -1;
}
i2c_send(bus, command);
@@ -79,11 +79,11 @@ int smbus_write_byte(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t data)
int smbus_read_word(I2CBus *bus, uint8_t addr, uint8_t command)
{
uint16_t data;
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
return -1;
}
i2c_send(bus, command);
- if (i2c_start_transfer(bus, addr, 1)) {
+ if (i2c_start_recv(bus, addr)) {
i2c_end_transfer(bus);
return -1;
}
@@ -96,7 +96,7 @@ int smbus_read_word(I2CBus *bus, uint8_t addr, uint8_t command)
int smbus_write_word(I2CBus *bus, uint8_t addr, uint8_t command, uint16_t data)
{
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
return -1;
}
i2c_send(bus, command);
@@ -113,12 +113,12 @@ int smbus_read_block(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t *data,
int i;
if (send_cmd) {
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
return -1;
}
i2c_send(bus, command);
}
- if (i2c_start_transfer(bus, addr, 1)) {
+ if (i2c_start_recv(bus, addr)) {
if (send_cmd) {
i2c_end_transfer(bus);
}
@@ -149,7 +149,7 @@ int smbus_write_block(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t *data,
len = 32;
}
- if (i2c_start_transfer(bus, addr, 0)) {
+ if (i2c_start_send(bus, addr)) {
return -1;
}
i2c_send(bus, command);
diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
index 796ffc6f5c..357437ff1d 100644
--- a/hw/i386/acpi-build.c
+++ b/hw/i386/acpi-build.c
@@ -435,11 +435,15 @@ static void build_append_pci_bus_devices(Aml *parent_scope, PCIBus *bus,
aml_append(dev, aml_name_decl("_ADR", aml_int(slot << 16)));
if (bsel) {
- aml_append(dev, aml_name_decl("_SUN", aml_int(slot)));
+ /*
+ * Can't declare _SUN here for every device as it changes 'slot'
+ * enumeration order in linux kernel, so use another variable for it
+ */
+ aml_append(dev, aml_name_decl("ASUN", aml_int(slot)));
method = aml_method("_DSM", 4, AML_SERIALIZED);
aml_append(method, aml_return(
aml_call6("PDSM", aml_arg(0), aml_arg(1), aml_arg(2),
- aml_arg(3), aml_name("BSEL"), aml_name("_SUN"))
+ aml_arg(3), aml_name("BSEL"), aml_name("ASUN"))
));
aml_append(dev, method);
}
@@ -466,6 +470,7 @@ static void build_append_pci_bus_devices(Aml *parent_scope, PCIBus *bus,
aml_append(method, aml_return(aml_int(s3d)));
aml_append(dev, method);
} else if (hotplug_enabled_dev) {
+ aml_append(dev, aml_name_decl("_SUN", aml_int(slot)));
/* add _EJ0 to make slot hotpluggable */
method = aml_method("_EJ0", 1, AML_NOTSERIALIZED);
aml_append(method,
diff --git a/hw/input/lm832x.c b/hw/input/lm832x.c
index 4cb1e9de01..19a646d9bb 100644
--- a/hw/input/lm832x.c
+++ b/hw/input/lm832x.c
@@ -19,6 +19,7 @@
*/
#include "qemu/osdep.h"
+#include "hw/input/lm832x.h"
#include "hw/i2c/i2c.h"
#include "hw/irq.h"
#include "migration/vmstate.h"
@@ -27,7 +28,6 @@
#include "ui/console.h"
#include "qom/object.h"
-#define TYPE_LM8323 "lm8323"
OBJECT_DECLARE_SIMPLE_TYPE(LM823KbdState, LM8323)
struct LM823KbdState {
diff --git a/hw/intc/arm_gicv3_cpuif.c b/hw/intc/arm_gicv3_cpuif.c
index 3e0641aff9..a032d505f5 100644
--- a/hw/intc/arm_gicv3_cpuif.c
+++ b/hw/intc/arm_gicv3_cpuif.c
@@ -1227,7 +1227,7 @@ static void icv_dir_write(CPUARMState *env, const ARMCPRegInfo *ri,
trace_gicv3_icv_dir_write(gicv3_redist_affid(cs), value);
- if (irq >= cs->gic->num_irq) {
+ if (irq >= GICV3_MAXIRQ) {
/* Also catches special interrupt numbers and LPIs */
return;
}
@@ -1262,7 +1262,7 @@ static void icv_eoir_write(CPUARMState *env, const ARMCPRegInfo *ri,
trace_gicv3_icv_eoir_write(ri->crm == 8 ? 0 : 1,
gicv3_redist_affid(cs), value);
- if (irq >= cs->gic->num_irq) {
+ if (irq >= GICV3_MAXIRQ) {
/* Also catches special interrupt numbers and LPIs */
return;
}
diff --git a/hw/intc/arm_gicv3_redist.c b/hw/intc/arm_gicv3_redist.c
index 8645220d61..53da703ed8 100644
--- a/hw/intc/arm_gicv3_redist.c
+++ b/hw/intc/arm_gicv3_redist.c
@@ -453,7 +453,7 @@ MemTxResult gicv3_redist_read(void *opaque, hwaddr offset, uint64_t *data,
if (r == MEMTX_ERROR) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid guest read at offset " TARGET_FMT_plx
- "size %u\n", __func__, offset, size);
+ " size %u\n", __func__, offset, size);
trace_gicv3_redist_badread(gicv3_redist_affid(cs), offset,
size, attrs.secure);
/* The spec requires that reserved registers are RAZ/WI;
@@ -510,7 +510,7 @@ MemTxResult gicv3_redist_write(void *opaque, hwaddr offset, uint64_t data,
if (r == MEMTX_ERROR) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid guest write at offset " TARGET_FMT_plx
- "size %u\n", __func__, offset, size);
+ " size %u\n", __func__, offset, size);
trace_gicv3_redist_badwrite(gicv3_redist_affid(cs), offset, data,
size, attrs.secure);
/* The spec requires that reserved registers are RAZ/WI;
diff --git a/hw/ipmi/ipmi_bmc_sim.c b/hw/ipmi/ipmi_bmc_sim.c
index 55fb81fa5a..905e091094 100644
--- a/hw/ipmi/ipmi_bmc_sim.c
+++ b/hw/ipmi/ipmi_bmc_sim.c
@@ -189,7 +189,7 @@ struct IPMIBmcSim {
uint8_t watchdog_use;
uint8_t watchdog_action;
uint8_t watchdog_pretimeout; /* In seconds */
- bool watchdog_expired;
+ uint8_t watchdog_expired;
uint16_t watchdog_timeout; /* in 100's of milliseconds */
bool watchdog_running;
@@ -2110,7 +2110,7 @@ static const VMStateDescription vmstate_ipmi_sim = {
VMSTATE_UINT8(watchdog_use, IPMIBmcSim),
VMSTATE_UINT8(watchdog_action, IPMIBmcSim),
VMSTATE_UINT8(watchdog_pretimeout, IPMIBmcSim),
- VMSTATE_BOOL(watchdog_expired, IPMIBmcSim),
+ VMSTATE_UINT8(watchdog_expired, IPMIBmcSim),
VMSTATE_UINT16(watchdog_timeout, IPMIBmcSim),
VMSTATE_BOOL(watchdog_running, IPMIBmcSim),
VMSTATE_BOOL(watchdog_preaction_ran, IPMIBmcSim),
diff --git a/hw/meson.build b/hw/meson.build
index ba0601e36e..b3366c888e 100644
--- a/hw/meson.build
+++ b/hw/meson.build
@@ -31,6 +31,7 @@ subdir('rdma')
subdir('rtc')
subdir('scsi')
subdir('sd')
+subdir('sensor')
subdir('smbios')
subdir('ssi')
subdir('timer')
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index c71ed25820..507058d8bf 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -11,21 +11,6 @@ config ARMSSE_MHU
config ARMSSE_CPU_PWRCTRL
bool
-config MAX111X
- bool
-
-config TMP105
- bool
- depends on I2C
-
-config TMP421
- bool
- depends on I2C
-
-config EMC141X
- bool
- depends on I2C
-
config ISA_DEBUG
bool
depends on ISA_BUS
diff --git a/hw/misc/auxbus.c b/hw/misc/auxbus.c
index 6c099ae2a2..434ff8d910 100644
--- a/hw/misc/auxbus.c
+++ b/hw/misc/auxbus.c
@@ -106,7 +106,6 @@ AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
AUXReply ret = AUX_NACK;
I2CBus *i2c_bus = aux_get_i2c_bus(bus);
size_t i;
- bool is_write = false;
DPRINTF("request at address 0x%" PRIX32 ", command %u, len %u\n", address,
cmd, len);
@@ -117,11 +116,10 @@ AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
*/
case WRITE_AUX:
case READ_AUX:
- is_write = cmd == READ_AUX ? false : true;
for (i = 0; i < len; i++) {
if (!address_space_rw(&bus->aux_addr_space, address++,
MEMTXATTRS_UNSPECIFIED, data++, 1,
- is_write)) {
+ cmd == WRITE_AUX)) {
ret = AUX_I2C_ACK;
} else {
ret = AUX_NACK;
@@ -133,24 +131,37 @@ AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
* Classic I2C transactions..
*/
case READ_I2C:
+ if (i2c_bus_busy(i2c_bus)) {
+ i2c_end_transfer(i2c_bus);
+ }
+
+ if (i2c_start_recv(i2c_bus, address)) {
+ ret = AUX_I2C_NACK;
+ break;
+ }
+
+ ret = AUX_I2C_ACK;
+ for (i = 0; i < len; i++) {
+ data[i] = i2c_recv(i2c_bus);
+ }
+ i2c_end_transfer(i2c_bus);
+ break;
case WRITE_I2C:
- is_write = cmd == READ_I2C ? false : true;
if (i2c_bus_busy(i2c_bus)) {
i2c_end_transfer(i2c_bus);
}
- if (i2c_start_transfer(i2c_bus, address, is_write)) {
+ if (i2c_start_send(i2c_bus, address)) {
ret = AUX_I2C_NACK;
break;
}
ret = AUX_I2C_ACK;
- while (len > 0) {
- if (i2c_send_recv(i2c_bus, data++, is_write) < 0) {
+ for (i = 0; i < len; i++) {
+ if (i2c_send(i2c_bus, data[i]) < 0) {
ret = AUX_I2C_NACK;
break;
}
- len--;
}
i2c_end_transfer(i2c_bus);
break;
@@ -163,14 +174,12 @@ AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
* - We changed the address.
*/
case WRITE_I2C_MOT:
- case READ_I2C_MOT:
- is_write = cmd == READ_I2C_MOT ? false : true;
ret = AUX_I2C_NACK;
if (!i2c_bus_busy(i2c_bus)) {
/*
* No transactions started..
*/
- if (i2c_start_transfer(i2c_bus, address, is_write)) {
+ if (i2c_start_send(i2c_bus, address)) {
break;
}
} else if ((address != bus->last_i2c_address) ||
@@ -179,23 +188,48 @@ AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
* Transaction started but we need to restart..
*/
i2c_end_transfer(i2c_bus);
- if (i2c_start_transfer(i2c_bus, address, is_write)) {
+ if (i2c_start_send(i2c_bus, address)) {
break;
}
}
bus->last_transaction = cmd;
bus->last_i2c_address = address;
- while (len > 0) {
- if (i2c_send_recv(i2c_bus, data++, is_write) < 0) {
+ ret = AUX_I2C_ACK;
+ for (i = 0; i < len; i++) {
+ if (i2c_send(i2c_bus, data[i]) < 0) {
i2c_end_transfer(i2c_bus);
+ ret = AUX_I2C_NACK;
break;
}
- len--;
}
- if (len == 0) {
- ret = AUX_I2C_ACK;
+ break;
+ case READ_I2C_MOT:
+ ret = AUX_I2C_NACK;
+ if (!i2c_bus_busy(i2c_bus)) {
+ /*
+ * No transactions started..
+ */
+ if (i2c_start_recv(i2c_bus, address)) {
+ break;
+ }
+ } else if ((address != bus->last_i2c_address) ||
+ (bus->last_transaction != cmd)) {
+ /*
+ * Transaction started but we need to restart..
+ */
+ i2c_end_transfer(i2c_bus);
+ if (i2c_start_recv(i2c_bus, address)) {
+ break;
+ }
+ }
+
+ bus->last_transaction = cmd;
+ bus->last_i2c_address = address;
+ for (i = 0; i < len; i++) {
+ data[i] = i2c_recv(i2c_bus);
}
+ ret = AUX_I2C_ACK;
break;
default:
qemu_log_mask(LOG_UNIMP, "AUX cmd=%u not implemented\n", cmd);
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index f89b5c1643..a53b849a5a 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -3,13 +3,9 @@ softmmu_ss.add(when: 'CONFIG_EDU', if_true: files('edu.c'))
softmmu_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c'))
softmmu_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c'))
softmmu_ss.add(when: 'CONFIG_ISA_TESTDEV', if_true: files('pc-testdev.c'))
-softmmu_ss.add(when: 'CONFIG_MAX111X', if_true: files('max111x.c'))
softmmu_ss.add(when: 'CONFIG_PCA9552', if_true: files('pca9552.c'))
softmmu_ss.add(when: 'CONFIG_PCI_TESTDEV', if_true: files('pci-testdev.c'))
softmmu_ss.add(when: 'CONFIG_SGA', if_true: files('sga.c'))
-softmmu_ss.add(when: 'CONFIG_TMP105', if_true: files('tmp105.c'))
-softmmu_ss.add(when: 'CONFIG_TMP421', if_true: files('tmp421.c'))
-softmmu_ss.add(when: 'CONFIG_EMC141X', if_true: files('emc141x.c'))
softmmu_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c'))
softmmu_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c'))
softmmu_ss.add(when: 'CONFIG_LED', if_true: files('led.c'))
@@ -85,7 +81,7 @@ softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files(
'bcm2835_powermgt.c',
))
softmmu_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
-softmmu_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c', 'zynq-xadc.c'))
+softmmu_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
softmmu_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-xramc.c'))
softmmu_ss.add(when: 'CONFIG_STM32F2XX_SYSCFG', if_true: files('stm32f2xx_syscfg.c'))
softmmu_ss.add(when: 'CONFIG_STM32F4XX_SYSCFG', if_true: files('stm32f4xx_syscfg.c'))
diff --git a/hw/misc/tmp105.h b/hw/misc/tmp105.h
deleted file mode 100644
index 7c97071ad7..0000000000
--- a/hw/misc/tmp105.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Texas Instruments TMP105 Temperature Sensor
- *
- * Browse the data sheet:
- *
- * http://www.ti.com/lit/gpn/tmp105
- *
- * Copyright (C) 2012 Alex Horn <alex.horn@cs.ox.ac.uk>
- * Copyright (C) 2008-2012 Andrzej Zaborowski <balrogg@gmail.com>
- *
- * This work is licensed under the terms of the GNU GPL, version 2 or
- * later. See the COPYING file in the top-level directory.
- */
-#ifndef QEMU_TMP105_H
-#define QEMU_TMP105_H
-
-#include "hw/i2c/i2c.h"
-#include "hw/misc/tmp105_regs.h"
-#include "qom/object.h"
-
-#define TYPE_TMP105 "tmp105"
-OBJECT_DECLARE_SIMPLE_TYPE(TMP105State, TMP105)
-
-/**
- * TMP105State:
- * @config: Bits 5 and 6 (value 32 and 64) determine the precision of the
- * temperature. See Table 8 in the data sheet.
- *
- * @see_also: http://www.ti.com/lit/gpn/tmp105
- */
-struct TMP105State {
- /*< private >*/
- I2CSlave i2c;
- /*< public >*/
-
- uint8_t len;
- uint8_t buf[2];
- qemu_irq pin;
-
- uint8_t pointer;
- uint8_t config;
- int16_t temperature;
- int16_t limit[2];
- int faults;
- uint8_t alarm;
- /*
- * The TMP105 initially looks for a temperature rising above T_high;
- * once this is detected, the condition it looks for next is the
- * temperature falling below T_low. This flag is false when initially
- * looking for T_high, true when looking for T_low.
- */
- bool detect_falling;
-};
-
-#endif
diff --git a/hw/net/dp8393x.c b/hw/net/dp8393x.c
index 252c0a2664..45b954e46c 100644
--- a/hw/net/dp8393x.c
+++ b/hw/net/dp8393x.c
@@ -85,6 +85,7 @@ static const char *reg_names[] = {
#define SONIC_MPT 0x2e
#define SONIC_MDT 0x2f
#define SONIC_DCR2 0x3f
+#define SONIC_REG_COUNT 0x40
#define SONIC_CR_HTX 0x0001
#define SONIC_CR_TXP 0x0002
@@ -157,12 +158,11 @@ struct dp8393xState {
MemoryRegion mmio;
/* Registers */
- uint8_t cam[16][6];
- uint16_t regs[0x40];
+ uint16_t cam[16][3];
+ uint16_t regs[SONIC_REG_COUNT];
/* Temporaries */
uint8_t tx_buffer[0x10000];
- uint16_t data[12];
int loopback_packet;
/* Memory access */
@@ -219,34 +219,48 @@ static uint32_t dp8393x_wt(dp8393xState *s)
return s->regs[SONIC_WT1] << 16 | s->regs[SONIC_WT0];
}
-static uint16_t dp8393x_get(dp8393xState *s, int width, int offset)
+static uint16_t dp8393x_get(dp8393xState *s, hwaddr addr, int offset)
{
+ const MemTxAttrs attrs = MEMTXATTRS_UNSPECIFIED;
uint16_t val;
- if (s->big_endian) {
- val = be16_to_cpu(s->data[offset * width + width - 1]);
+ if (s->regs[SONIC_DCR] & SONIC_DCR_DW) {
+ addr += offset << 2;
+ if (s->big_endian) {
+ val = address_space_ldl_be(&s->as, addr, attrs, NULL);
+ } else {
+ val = address_space_ldl_le(&s->as, addr, attrs, NULL);
+ }
} else {
- val = le16_to_cpu(s->data[offset * width]);
+ addr += offset << 1;
+ if (s->big_endian) {
+ val = address_space_lduw_be(&s->as, addr, attrs, NULL);
+ } else {
+ val = address_space_lduw_le(&s->as, addr, attrs, NULL);
+ }
}
+
return val;
}
-static void dp8393x_put(dp8393xState *s, int width, int offset,
- uint16_t val)
+static void dp8393x_put(dp8393xState *s,
+ hwaddr addr, int offset, uint16_t val)
{
- if (s->big_endian) {
- if (width == 2) {
- s->data[offset * 2] = 0;
- s->data[offset * 2 + 1] = cpu_to_be16(val);
+ const MemTxAttrs attrs = MEMTXATTRS_UNSPECIFIED;
+
+ if (s->regs[SONIC_DCR] & SONIC_DCR_DW) {
+ addr += offset << 2;
+ if (s->big_endian) {
+ address_space_stl_be(&s->as, addr, val, attrs, NULL);
} else {
- s->data[offset] = cpu_to_be16(val);
+ address_space_stl_le(&s->as, addr, val, attrs, NULL);
}
} else {
- if (width == 2) {
- s->data[offset * 2] = cpu_to_le16(val);
- s->data[offset * 2 + 1] = 0;
+ addr += offset << 1;
+ if (s->big_endian) {
+ address_space_stw_be(&s->as, addr, val, attrs, NULL);
} else {
- s->data[offset] = cpu_to_le16(val);
+ address_space_stw_le(&s->as, addr, val, attrs, NULL);
}
}
}
@@ -270,34 +284,28 @@ static void dp8393x_update_irq(dp8393xState *s)
static void dp8393x_do_load_cam(dp8393xState *s)
{
int width, size;
- uint16_t index = 0;
+ uint16_t index;
width = (s->regs[SONIC_DCR] & SONIC_DCR_DW) ? 2 : 1;
size = sizeof(uint16_t) * 4 * width;
while (s->regs[SONIC_CDC] & 0x1f) {
/* Fill current entry */
- address_space_read(&s->as, dp8393x_cdp(s),
- MEMTXATTRS_UNSPECIFIED, s->data, size);
- s->cam[index][0] = dp8393x_get(s, width, 1) & 0xff;
- s->cam[index][1] = dp8393x_get(s, width, 1) >> 8;
- s->cam[index][2] = dp8393x_get(s, width, 2) & 0xff;
- s->cam[index][3] = dp8393x_get(s, width, 2) >> 8;
- s->cam[index][4] = dp8393x_get(s, width, 3) & 0xff;
- s->cam[index][5] = dp8393x_get(s, width, 3) >> 8;
- trace_dp8393x_load_cam(index, s->cam[index][0], s->cam[index][1],
- s->cam[index][2], s->cam[index][3],
- s->cam[index][4], s->cam[index][5]);
+ index = dp8393x_get(s, dp8393x_cdp(s), 0) & 0xf;
+ s->cam[index][0] = dp8393x_get(s, dp8393x_cdp(s), 1);
+ s->cam[index][1] = dp8393x_get(s, dp8393x_cdp(s), 2);
+ s->cam[index][2] = dp8393x_get(s, dp8393x_cdp(s), 3);
+ trace_dp8393x_load_cam(index,
+ s->cam[index][0] >> 8, s->cam[index][0] & 0xff,
+ s->cam[index][1] >> 8, s->cam[index][1] & 0xff,
+ s->cam[index][2] >> 8, s->cam[index][2] & 0xff);
/* Move to next entry */
s->regs[SONIC_CDC]--;
s->regs[SONIC_CDP] += size;
- index++;
}
/* Read CAM enable */
- address_space_read(&s->as, dp8393x_cdp(s),
- MEMTXATTRS_UNSPECIFIED, s->data, size);
- s->regs[SONIC_CE] = dp8393x_get(s, width, 0);
+ s->regs[SONIC_CE] = dp8393x_get(s, dp8393x_cdp(s), 0);
trace_dp8393x_load_cam_done(s->regs[SONIC_CE]);
/* Done */
@@ -313,14 +321,12 @@ static void dp8393x_do_read_rra(dp8393xState *s)
/* Read memory */
width = (s->regs[SONIC_DCR] & SONIC_DCR_DW) ? 2 : 1;
size = sizeof(uint16_t) * 4 * width;
- address_space_read(&s->as, dp8393x_rrp(s),
- MEMTXATTRS_UNSPECIFIED, s->data, size);
/* Update SONIC registers */
- s->regs[SONIC_CRBA0] = dp8393x_get(s, width, 0);
- s->regs[SONIC_CRBA1] = dp8393x_get(s, width, 1);
- s->regs[SONIC_RBWC0] = dp8393x_get(s, width, 2);
- s->regs[SONIC_RBWC1] = dp8393x_get(s, width, 3);
+ s->regs[SONIC_CRBA0] = dp8393x_get(s, dp8393x_rrp(s), 0);
+ s->regs[SONIC_CRBA1] = dp8393x_get(s, dp8393x_rrp(s), 1);
+ s->regs[SONIC_RBWC0] = dp8393x_get(s, dp8393x_rrp(s), 2);
+ s->regs[SONIC_RBWC1] = dp8393x_get(s, dp8393x_rrp(s), 3);
trace_dp8393x_read_rra_regs(s->regs[SONIC_CRBA0], s->regs[SONIC_CRBA1],
s->regs[SONIC_RBWC0], s->regs[SONIC_RBWC1]);
@@ -416,28 +422,22 @@ static void dp8393x_do_receiver_disable(dp8393xState *s)
static void dp8393x_do_transmit_packets(dp8393xState *s)
{
NetClientState *nc = qemu_get_queue(s->nic);
- int width, size;
int tx_len, len;
uint16_t i;
- width = (s->regs[SONIC_DCR] & SONIC_DCR_DW) ? 2 : 1;
-
while (1) {
/* Read memory */
- size = sizeof(uint16_t) * 6 * width;
s->regs[SONIC_TTDA] = s->regs[SONIC_CTDA];
trace_dp8393x_transmit_packet(dp8393x_ttda(s));
- address_space_read(&s->as, dp8393x_ttda(s) + sizeof(uint16_t) * width,
- MEMTXATTRS_UNSPECIFIED, s->data, size);
tx_len = 0;
/* Update registers */
- s->regs[SONIC_TCR] = dp8393x_get(s, width, 0) & 0xf000;
- s->regs[SONIC_TPS] = dp8393x_get(s, width, 1);
- s->regs[SONIC_TFC] = dp8393x_get(s, width, 2);
- s->regs[SONIC_TSA0] = dp8393x_get(s, width, 3);
- s->regs[SONIC_TSA1] = dp8393x_get(s, width, 4);
- s->regs[SONIC_TFS] = dp8393x_get(s, width, 5);
+ s->regs[SONIC_TCR] = dp8393x_get(s, dp8393x_ttda(s), 1) & 0xf000;
+ s->regs[SONIC_TPS] = dp8393x_get(s, dp8393x_ttda(s), 2);
+ s->regs[SONIC_TFC] = dp8393x_get(s, dp8393x_ttda(s), 3);
+ s->regs[SONIC_TSA0] = dp8393x_get(s, dp8393x_ttda(s), 4);
+ s->regs[SONIC_TSA1] = dp8393x_get(s, dp8393x_ttda(s), 5);
+ s->regs[SONIC_TFS] = dp8393x_get(s, dp8393x_ttda(s), 6);
/* Handle programmable interrupt */
if (s->regs[SONIC_TCR] & SONIC_TCR_PINT) {
@@ -459,15 +459,12 @@ static void dp8393x_do_transmit_packets(dp8393xState *s)
i++;
if (i != s->regs[SONIC_TFC]) {
/* Read next fragment details */
- size = sizeof(uint16_t) * 3 * width;
- address_space_read(&s->as,
- dp8393x_ttda(s)
- + sizeof(uint16_t) * width * (4 + 3 * i),
- MEMTXATTRS_UNSPECIFIED, s->data,
- size);
- s->regs[SONIC_TSA0] = dp8393x_get(s, width, 0);
- s->regs[SONIC_TSA1] = dp8393x_get(s, width, 1);
- s->regs[SONIC_TFS] = dp8393x_get(s, width, 2);
+ s->regs[SONIC_TSA0] = dp8393x_get(s, dp8393x_ttda(s),
+ 4 + 3 * i);
+ s->regs[SONIC_TSA1] = dp8393x_get(s, dp8393x_ttda(s),
+ 5 + 3 * i);
+ s->regs[SONIC_TFS] = dp8393x_get(s, dp8393x_ttda(s),
+ 6 + 3 * i);
}
}
@@ -500,22 +497,12 @@ static void dp8393x_do_transmit_packets(dp8393xState *s)
s->regs[SONIC_TCR] |= SONIC_TCR_PTX;
/* Write status */
- dp8393x_put(s, width, 0,
- s->regs[SONIC_TCR] & 0x0fff); /* status */
- size = sizeof(uint16_t) * width;
- address_space_write(&s->as, dp8393x_ttda(s),
- MEMTXATTRS_UNSPECIFIED, s->data, size);
+ dp8393x_put(s, dp8393x_ttda(s), 0, s->regs[SONIC_TCR] & 0x0fff);
if (!(s->regs[SONIC_CR] & SONIC_CR_HTX)) {
/* Read footer of packet */
- size = sizeof(uint16_t) * width;
- address_space_read(&s->as,
- dp8393x_ttda(s)
- + sizeof(uint16_t) * width
- * (4 + 3 * s->regs[SONIC_TFC]),
- MEMTXATTRS_UNSPECIFIED, s->data,
- size);
- s->regs[SONIC_CTDA] = dp8393x_get(s, width, 0);
+ s->regs[SONIC_CTDA] = dp8393x_get(s, dp8393x_ttda(s),
+ 4 + 3 * s->regs[SONIC_TFC]);
if (s->regs[SONIC_CTDA] & SONIC_DESC_EOL) {
/* EOL detected */
break;
@@ -591,8 +578,7 @@ static uint64_t dp8393x_read(void *opaque, hwaddr addr, unsigned int size)
case SONIC_CAP1:
case SONIC_CAP0:
if (s->regs[SONIC_CR] & SONIC_CR_RST) {
- val = s->cam[s->regs[SONIC_CEP] & 0xf][2 * (SONIC_CAP0 - reg) + 1] << 8;
- val |= s->cam[s->regs[SONIC_CEP] & 0xf][2 * (SONIC_CAP0 - reg)];
+ val = s->cam[s->regs[SONIC_CEP] & 0xf][SONIC_CAP0 - reg];
}
break;
/* All other registers have no special contraints */
@@ -602,15 +588,14 @@ static uint64_t dp8393x_read(void *opaque, hwaddr addr, unsigned int size)
trace_dp8393x_read(reg, reg_names[reg], val, size);
- return s->big_endian ? val << 16 : val;
+ return val;
}
-static void dp8393x_write(void *opaque, hwaddr addr, uint64_t data,
+static void dp8393x_write(void *opaque, hwaddr addr, uint64_t val,
unsigned int size)
{
dp8393xState *s = opaque;
int reg = addr >> s->it_shift;
- uint32_t val = s->big_endian ? data >> 16 : data;
trace_dp8393x_write(reg, reg_names[reg], val, size);
@@ -691,11 +676,16 @@ static void dp8393x_write(void *opaque, hwaddr addr, uint64_t data,
}
}
+/*
+ * Since .impl.max_access_size is effectively controlled by the it_shift
+ * property, leave it unspecified for now to allow the memory API to
+ * correctly zero extend the 16-bit register values to the access size up to and
+ * including it_shift.
+ */
static const MemoryRegionOps dp8393x_ops = {
.read = dp8393x_read,
.write = dp8393x_write,
- .impl.min_access_size = 4,
- .impl.max_access_size = 4,
+ .impl.min_access_size = 2,
.endianness = DEVICE_NATIVE_ENDIAN,
};
@@ -764,7 +754,7 @@ static ssize_t dp8393x_receive(NetClientState *nc, const uint8_t * buf,
dp8393xState *s = qemu_get_nic_opaque(nc);
int packet_type;
uint32_t available, address;
- int width, rx_len, padded_len;
+ int rx_len, padded_len;
uint32_t checksum;
int size;
@@ -777,10 +767,8 @@ static ssize_t dp8393x_receive(NetClientState *nc, const uint8_t * buf,
rx_len = pkt_size + sizeof(checksum);
if (s->regs[SONIC_DCR] & SONIC_DCR_DW) {
- width = 2;
padded_len = ((rx_len - 1) | 3) + 1;
} else {
- width = 1;
padded_len = ((rx_len - 1) | 1) + 1;
}
@@ -801,11 +789,7 @@ static ssize_t dp8393x_receive(NetClientState *nc, const uint8_t * buf,
/* Check for EOL */
if (s->regs[SONIC_LLFA] & SONIC_DESC_EOL) {
/* Are we still in resource exhaustion? */
- size = sizeof(uint16_t) * 1 * width;
- address = dp8393x_crda(s) + sizeof(uint16_t) * 5 * width;
- address_space_read(&s->as, address, MEMTXATTRS_UNSPECIFIED,
- s->data, size);
- s->regs[SONIC_LLFA] = dp8393x_get(s, width, 0);
+ s->regs[SONIC_LLFA] = dp8393x_get(s, dp8393x_crda(s), 5);
if (s->regs[SONIC_LLFA] & SONIC_DESC_EOL) {
/* Still EOL ; stop reception */
return -1;
@@ -813,11 +797,7 @@ static ssize_t dp8393x_receive(NetClientState *nc, const uint8_t * buf,
/* Link has been updated by host */
/* Clear in_use */
- size = sizeof(uint16_t) * width;
- address = dp8393x_crda(s) + sizeof(uint16_t) * 6 * width;
- dp8393x_put(s, width, 0, 0);
- address_space_rw(&s->as, address, MEMTXATTRS_UNSPECIFIED,
- (uint8_t *)s->data, size, 1);
+ dp8393x_put(s, dp8393x_crda(s), 6, 0x0000);
/* Move to next descriptor */
s->regs[SONIC_CRDA] = s->regs[SONIC_LLFA];
@@ -846,8 +826,8 @@ static ssize_t dp8393x_receive(NetClientState *nc, const uint8_t * buf,
/* Pad short packets to keep pointers aligned */
if (rx_len < padded_len) {
size = padded_len - rx_len;
- address_space_rw(&s->as, address, MEMTXATTRS_UNSPECIFIED,
- (uint8_t *)"\xFF\xFF\xFF", size, 1);
+ address_space_write(&s->as, address, MEMTXATTRS_UNSPECIFIED,
+ "\xFF\xFF\xFF", size);
address += size;
}
@@ -871,32 +851,20 @@ static ssize_t dp8393x_receive(NetClientState *nc, const uint8_t * buf,
/* Write status to memory */
trace_dp8393x_receive_write_status(dp8393x_crda(s));
- dp8393x_put(s, width, 0, s->regs[SONIC_RCR]); /* status */
- dp8393x_put(s, width, 1, rx_len); /* byte count */
- dp8393x_put(s, width, 2, s->regs[SONIC_TRBA0]); /* pkt_ptr0 */
- dp8393x_put(s, width, 3, s->regs[SONIC_TRBA1]); /* pkt_ptr1 */
- dp8393x_put(s, width, 4, s->regs[SONIC_RSC]); /* seq_no */
- size = sizeof(uint16_t) * 5 * width;
- address_space_write(&s->as, dp8393x_crda(s),
- MEMTXATTRS_UNSPECIFIED,
- s->data, size);
+ dp8393x_put(s, dp8393x_crda(s), 0, s->regs[SONIC_RCR]); /* status */
+ dp8393x_put(s, dp8393x_crda(s), 1, rx_len); /* byte count */
+ dp8393x_put(s, dp8393x_crda(s), 2, s->regs[SONIC_TRBA0]); /* pkt_ptr0 */
+ dp8393x_put(s, dp8393x_crda(s), 3, s->regs[SONIC_TRBA1]); /* pkt_ptr1 */
+ dp8393x_put(s, dp8393x_crda(s), 4, s->regs[SONIC_RSC]); /* seq_no */
/* Check link field */
- size = sizeof(uint16_t) * width;
- address_space_read(&s->as,
- dp8393x_crda(s) + sizeof(uint16_t) * 5 * width,
- MEMTXATTRS_UNSPECIFIED, s->data, size);
- s->regs[SONIC_LLFA] = dp8393x_get(s, width, 0);
+ s->regs[SONIC_LLFA] = dp8393x_get(s, dp8393x_crda(s), 5);
if (s->regs[SONIC_LLFA] & SONIC_DESC_EOL) {
/* EOL detected */
s->regs[SONIC_ISR] |= SONIC_ISR_RDE;
} else {
/* Clear in_use */
- size = sizeof(uint16_t) * width;
- address = dp8393x_crda(s) + sizeof(uint16_t) * 6 * width;
- dp8393x_put(s, width, 0, 0);
- address_space_write(&s->as, address, MEMTXATTRS_UNSPECIFIED,
- s->data, size);
+ dp8393x_put(s, dp8393x_crda(s), 6, 0x0000);
/* Move to next descriptor */
s->regs[SONIC_CRDA] = s->regs[SONIC_LLFA];
@@ -972,7 +940,7 @@ static void dp8393x_realize(DeviceState *dev, Error **errp)
address_space_init(&s->as, s->dma_mr, "dp8393x");
memory_region_init_io(&s->mmio, OBJECT(dev), &dp8393x_ops, s,
- "dp8393x-regs", 0x40 << s->it_shift);
+ "dp8393x-regs", SONIC_REG_COUNT << s->it_shift);
s->nic = qemu_new_nic(&net_dp83932_info, &s->conf,
object_get_typename(OBJECT(dev)), dev->id, s);
@@ -983,11 +951,11 @@ static void dp8393x_realize(DeviceState *dev, Error **errp)
static const VMStateDescription vmstate_dp8393x = {
.name = "dp8393x",
- .version_id = 0,
- .minimum_version_id = 0,
+ .version_id = 1,
+ .minimum_version_id = 1,
.fields = (VMStateField []) {
- VMSTATE_BUFFER_UNSAFE(cam, dp8393xState, 0, 16 * 6),
- VMSTATE_UINT16_ARRAY(regs, dp8393xState, 0x40),
+ VMSTATE_UINT16_2DARRAY(cam, dp8393xState, 16, 3),
+ VMSTATE_UINT16_ARRAY(regs, dp8393xState, SONIC_REG_COUNT),
VMSTATE_END_OF_LIST()
}
};
diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c
index bd7958b9f0..16d20cdee5 100644
--- a/hw/net/virtio-net.c
+++ b/hw/net/virtio-net.c
@@ -3234,6 +3234,7 @@ static bool failover_replug_primary(VirtIONet *n, DeviceState *dev,
}
hotplug_handler_plug(hotplug_ctrl, dev, &err);
}
+ pdev->partially_hotplugged = false;
out:
error_propagate(errp, err);
diff --git a/hw/pci-host/Kconfig b/hw/pci-host/Kconfig
index 79c20bf28b..84494400b8 100644
--- a/hw/pci-host/Kconfig
+++ b/hw/pci-host/Kconfig
@@ -6,7 +6,7 @@ config XEN_IGD_PASSTHROUGH
default y
depends on XEN && PCI_I440FX
-config PREP_PCI
+config RAVEN_PCI
bool
select PCI
select OR_IRQ
diff --git a/hw/pci-host/meson.build b/hw/pci-host/meson.build
index 1698d3a192..4c4f39c15c 100644
--- a/hw/pci-host/meson.build
+++ b/hw/pci-host/meson.build
@@ -13,7 +13,7 @@ pci_ss.add(when: 'CONFIG_REMOTE_PCIHOST', if_true: files('remote.c'))
pci_ss.add(when: 'CONFIG_SH_PCI', if_true: files('sh_pci.c'))
# PPC devices
-pci_ss.add(when: 'CONFIG_PREP_PCI', if_true: files('prep.c'))
+pci_ss.add(when: 'CONFIG_RAVEN_PCI', if_true: files('raven.c'))
pci_ss.add(when: 'CONFIG_GRACKLE_PCI', if_true: files('grackle.c'))
# NewWorld PowerMac
pci_ss.add(when: 'CONFIG_UNIN_PCI', if_true: files('uninorth.c'))
diff --git a/hw/pci-host/q35.c b/hw/pci-host/q35.c
index 2eb729dff5..0f37cf056a 100644
--- a/hw/pci-host/q35.c
+++ b/hw/pci-host/q35.c
@@ -29,6 +29,7 @@
*/
#include "qemu/osdep.h"
+#include "qemu/log.h"
#include "hw/i386/pc.h"
#include "hw/pci-host/q35.h"
#include "hw/qdev-properties.h"
@@ -318,6 +319,8 @@ static void mch_update_pciexbar(MCHPCIState *mch)
addr_mask |= MCH_HOST_BRIDGE_PCIEXBAR_64ADMSK;
break;
case MCH_HOST_BRIDGE_PCIEXBAR_LENGTH_RVD:
+ qemu_log_mask(LOG_GUEST_ERROR, "Q35: Reserved PCIEXBAR LENGTH\n");
+ return;
default:
abort();
}
diff --git a/hw/pci-host/prep.c b/hw/pci-host/raven.c
index 9fef74fc56..3be27f0a14 100644
--- a/hw/pci-host/prep.c
+++ b/hw/pci-host/raven.c
@@ -81,6 +81,8 @@ struct PRePPCIState {
#define BIOS_SIZE (1 * MiB)
+#define PCI_IO_BASE_ADDR 0x80000000 /* Physical address on main bus */
+
static inline uint32_t raven_pci_io_config(hwaddr addr)
{
int i;
@@ -158,7 +160,7 @@ static uint64_t raven_io_read(void *opaque, hwaddr addr,
uint8_t buf[4];
addr = raven_io_address(s, addr);
- address_space_read(&s->pci_io_as, addr + 0x80000000,
+ address_space_read(&s->pci_io_as, addr + PCI_IO_BASE_ADDR,
MEMTXATTRS_UNSPECIFIED, buf, size);
if (size == 1) {
@@ -190,7 +192,7 @@ static void raven_io_write(void *opaque, hwaddr addr,
g_assert_not_reached();
}
- address_space_write(&s->pci_io_as, addr + 0x80000000,
+ address_space_write(&s->pci_io_as, addr + PCI_IO_BASE_ADDR,
MEMTXATTRS_UNSPECIFIED, buf, size);
}
@@ -293,8 +295,9 @@ static void raven_pcihost_initfn(Object *obj)
address_space_init(&s->pci_io_as, &s->pci_io, "raven-io");
/* CPU address space */
- memory_region_add_subregion(address_space_mem, 0x80000000, &s->pci_io);
- memory_region_add_subregion_overlap(address_space_mem, 0x80000000,
+ memory_region_add_subregion(address_space_mem, PCI_IO_BASE_ADDR,
+ &s->pci_io);
+ memory_region_add_subregion_overlap(address_space_mem, PCI_IO_BASE_ADDR,
&s->pci_io_non_contiguous, 1);
memory_region_add_subregion(address_space_mem, 0xc0000000, &s->pci_memory);
pci_root_bus_new_inplace(&s->pci_bus, sizeof(s->pci_bus), DEVICE(obj), NULL,
diff --git a/hw/ppc/Kconfig b/hw/ppc/Kconfig
index 66e0b15d9e..322a7eb031 100644
--- a/hw/ppc/Kconfig
+++ b/hw/ppc/Kconfig
@@ -13,6 +13,7 @@ config PSERIES
select MSI_NONBROKEN
select FDT_PPC
select CHRP_NVRAM
+ select VOF
config SPAPR_RNG
bool
@@ -75,6 +76,7 @@ config PEGASOS2
select VT82C686
select IDE_VIA
select SMBUS_EEPROM
+ select VOF
# This should come with VT82C686
select ACPI_X86
@@ -83,7 +85,7 @@ config PREP
imply PCI_DEVICES
imply TEST_DEVICES
select CS4231A
- select PREP_PCI
+ select RAVEN_PCI
select I82378
select LSI_SCSI_PCI
select M48T59
@@ -144,3 +146,6 @@ config FW_CFG_PPC
config FDT_PPC
bool
+
+config VOF
+ bool
diff --git a/hw/ppc/meson.build b/hw/ppc/meson.build
index 597d974dd4..aa4c8e6a2e 100644
--- a/hw/ppc/meson.build
+++ b/hw/ppc/meson.build
@@ -84,4 +84,7 @@ ppc_ss.add(when: 'CONFIG_VIRTEX', if_true: files('virtex_ml507.c'))
# Pegasos2
ppc_ss.add(when: 'CONFIG_PEGASOS2', if_true: files('pegasos2.c'))
+ppc_ss.add(when: 'CONFIG_VOF', if_true: files('vof.c'))
+ppc_ss.add(when: ['CONFIG_VOF', 'CONFIG_PSERIES'], if_true: files('spapr_vof.c'))
+
hw_arch += {'ppc': ppc_ss}
diff --git a/hw/ppc/pegasos2.c b/hw/ppc/pegasos2.c
index 0bfd0928aa..9a6ae867e4 100644
--- a/hw/ppc/pegasos2.c
+++ b/hw/ppc/pegasos2.c
@@ -1,7 +1,7 @@
/*
* QEMU PowerPC CHRP (Genesi/bPlan Pegasos II) hardware System Emulator
*
- * Copyright (c) 2018-2020 BALATON Zoltan
+ * Copyright (c) 2018-2021 BALATON Zoltan
*
* This work is licensed under the GNU GPL license version 2 or later.
*
@@ -34,26 +34,68 @@
#include "trace.h"
#include "qemu/datadir.h"
#include "sysemu/device_tree.h"
+#include "hw/ppc/vof.h"
-#define PROM_FILENAME "pegasos2.rom"
+#include <libfdt.h>
+
+#define PROM_FILENAME "vof.bin"
#define PROM_ADDR 0xfff00000
#define PROM_SIZE 0x80000
+#define KVMPPC_HCALL_BASE 0xf000
+#define KVMPPC_H_RTAS (KVMPPC_HCALL_BASE + 0x0)
+#define KVMPPC_H_VOF_CLIENT (KVMPPC_HCALL_BASE + 0x5)
+
+#define H_SUCCESS 0
+#define H_PRIVILEGE -3 /* Caller not privileged */
+#define H_PARAMETER -4 /* Parameter invalid, out-of-range or conflicting */
+
#define BUS_FREQ_HZ 133333333
+#define PCI0_MEM_BASE 0xc0000000
+#define PCI0_MEM_SIZE 0x20000000
+#define PCI0_IO_BASE 0xf8000000
+#define PCI0_IO_SIZE 0x10000
+
+#define PCI1_MEM_BASE 0x80000000
+#define PCI1_MEM_SIZE 0x40000000
+#define PCI1_IO_BASE 0xfe000000
+#define PCI1_IO_SIZE 0x10000
+
+#define TYPE_PEGASOS2_MACHINE MACHINE_TYPE_NAME("pegasos2")
+OBJECT_DECLARE_TYPE(Pegasos2MachineState, MachineClass, PEGASOS2_MACHINE)
+
+struct Pegasos2MachineState {
+ MachineState parent_obj;
+ PowerPCCPU *cpu;
+ DeviceState *mv;
+ Vof *vof;
+ void *fdt_blob;
+ uint64_t kernel_addr;
+ uint64_t kernel_entry;
+ uint64_t kernel_size;
+};
+
+static void *build_fdt(MachineState *machine, int *fdt_size);
+
static void pegasos2_cpu_reset(void *opaque)
{
PowerPCCPU *cpu = opaque;
+ Pegasos2MachineState *pm = PEGASOS2_MACHINE(current_machine);
cpu_reset(CPU(cpu));
cpu->env.spr[SPR_HID1] = 7ULL << 28;
+ if (pm->vof) {
+ cpu->env.gpr[1] = 2 * VOF_STACK_SIZE - 0x20;
+ cpu->env.nip = 0x100;
+ }
}
static void pegasos2_init(MachineState *machine)
{
- PowerPCCPU *cpu = NULL;
+ Pegasos2MachineState *pm = PEGASOS2_MACHINE(machine);
+ CPUPPCState *env;
MemoryRegion *rom = g_new(MemoryRegion, 1);
- DeviceState *mv;
PCIBus *pci_bus;
PCIDevice *dev;
I2CBus *i2c_bus;
@@ -63,15 +105,16 @@ static void pegasos2_init(MachineState *machine)
uint8_t *spd_data;
/* init CPU */
- cpu = POWERPC_CPU(cpu_create(machine->cpu_type));
- if (PPC_INPUT(&cpu->env) != PPC_FLAGS_INPUT_6xx) {
+ pm->cpu = POWERPC_CPU(cpu_create(machine->cpu_type));
+ env = &pm->cpu->env;
+ if (PPC_INPUT(env) != PPC_FLAGS_INPUT_6xx) {
error_report("Incompatible CPU, only 6xx bus supported");
exit(1);
}
/* Set time-base frequency */
- cpu_ppc_tb_init(&cpu->env, BUS_FREQ_HZ / 4);
- qemu_register_reset(pegasos2_cpu_reset, cpu);
+ cpu_ppc_tb_init(env, BUS_FREQ_HZ / 4);
+ qemu_register_reset(pegasos2_cpu_reset, pm->cpu);
/* RAM */
memory_region_add_subregion(get_system_memory(), 0, machine->ram);
@@ -82,30 +125,36 @@ static void pegasos2_init(MachineState *machine)
error_report("Could not find firmware '%s'", fwname);
exit(1);
}
+ if (!machine->firmware && !pm->vof) {
+ pm->vof = g_malloc0(sizeof(*pm->vof));
+ }
memory_region_init_rom(rom, NULL, "pegasos2.rom", PROM_SIZE, &error_fatal);
memory_region_add_subregion(get_system_memory(), PROM_ADDR, rom);
sz = load_elf(filename, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1,
PPC_ELF_MACHINE, 0, 0);
if (sz <= 0) {
- sz = load_image_targphys(filename, PROM_ADDR, PROM_SIZE);
+ sz = load_image_targphys(filename, pm->vof ? 0 : PROM_ADDR, PROM_SIZE);
}
if (sz <= 0 || sz > PROM_SIZE) {
error_report("Could not load firmware '%s'", filename);
exit(1);
}
g_free(filename);
+ if (pm->vof) {
+ pm->vof->fw_size = sz;
+ }
/* Marvell Discovery II system controller */
- mv = DEVICE(sysbus_create_simple(TYPE_MV64361, -1,
- ((qemu_irq *)cpu->env.irq_inputs)[PPC6xx_INPUT_INT]));
- pci_bus = mv64361_get_pci_bus(mv, 1);
+ pm->mv = DEVICE(sysbus_create_simple(TYPE_MV64361, -1,
+ ((qemu_irq *)env->irq_inputs)[PPC6xx_INPUT_INT]));
+ pci_bus = mv64361_get_pci_bus(pm->mv, 1);
/* VIA VT8231 South Bridge (multifunction PCI device) */
/* VT8231 function 0: PCI-to-ISA Bridge */
dev = pci_create_simple_multifunction(pci_bus, PCI_DEVFN(12, 0), true,
TYPE_VT8231_ISA);
qdev_connect_gpio_out(DEVICE(dev), 0,
- qdev_get_gpio_in_named(mv, "gpp", 31));
+ qdev_get_gpio_in_named(pm->mv, "gpp", 31));
/* VT8231 function 1: IDE Controller */
dev = pci_create_simple(pci_bus, PCI_DEVFN(12, 1), "via-ide");
@@ -127,18 +176,728 @@ static void pegasos2_init(MachineState *machine)
/* other PC hardware */
pci_vga_init(pci_bus);
+
+ if (machine->kernel_filename) {
+ sz = load_elf(machine->kernel_filename, NULL, NULL, NULL,
+ &pm->kernel_entry, &pm->kernel_addr, NULL, NULL, 1,
+ PPC_ELF_MACHINE, 0, 0);
+ if (sz <= 0) {
+ error_report("Could not load kernel '%s'",
+ machine->kernel_filename);
+ exit(1);
+ }
+ pm->kernel_size = sz;
+ if (!pm->vof) {
+ warn_report("Option -kernel may be ineffective with -bios.");
+ }
+ }
+ if (machine->kernel_cmdline && !pm->vof) {
+ warn_report("Option -append may be ineffective with -bios.");
+ }
+}
+
+static uint32_t pegasos2_pci_config_read(AddressSpace *as, int bus,
+ uint32_t addr, uint32_t len)
+{
+ hwaddr pcicfg = (bus ? 0xf1000c78 : 0xf1000cf8);
+ uint32_t val = 0xffffffff;
+
+ stl_le_phys(as, pcicfg, addr | BIT(31));
+ switch (len) {
+ case 4:
+ val = ldl_le_phys(as, pcicfg + 4);
+ break;
+ case 2:
+ val = lduw_le_phys(as, pcicfg + 4);
+ break;
+ case 1:
+ val = ldub_phys(as, pcicfg + 4);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid length\n", __func__);
+ break;
+ }
+ return val;
+}
+
+static void pegasos2_pci_config_write(AddressSpace *as, int bus, uint32_t addr,
+ uint32_t len, uint32_t val)
+{
+ hwaddr pcicfg = (bus ? 0xf1000c78 : 0xf1000cf8);
+
+ stl_le_phys(as, pcicfg, addr | BIT(31));
+ switch (len) {
+ case 4:
+ stl_le_phys(as, pcicfg + 4, val);
+ break;
+ case 2:
+ stw_le_phys(as, pcicfg + 4, val);
+ break;
+ case 1:
+ stb_phys(as, pcicfg + 4, val);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid length\n", __func__);
+ break;
+ }
+}
+
+static void pegasos2_machine_reset(MachineState *machine)
+{
+ Pegasos2MachineState *pm = PEGASOS2_MACHINE(machine);
+ AddressSpace *as = CPU(pm->cpu)->as;
+ void *fdt;
+ uint64_t d[2];
+ int sz;
+
+ qemu_devices_reset();
+ if (!pm->vof) {
+ return; /* Firmware should set up machine so nothing to do */
+ }
+
+ /* Otherwise, set up devices that board firmware would normally do */
+ stl_le_phys(as, 0xf1000000, 0x28020ff);
+ stl_le_phys(as, 0xf1000278, 0xa31fc);
+ stl_le_phys(as, 0xf100f300, 0x11ff0400);
+ stl_le_phys(as, 0xf100f10c, 0x80000000);
+ stl_le_phys(as, 0xf100001c, 0x8000000);
+ pegasos2_pci_config_write(as, 0, PCI_COMMAND, 2, PCI_COMMAND_IO |
+ PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
+ pegasos2_pci_config_write(as, 1, PCI_COMMAND, 2, PCI_COMMAND_IO |
+ PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
+
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 0) << 8) |
+ PCI_INTERRUPT_LINE, 2, 0x9);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 0) << 8) |
+ 0x50, 1, 0x2);
+
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 1) << 8) |
+ PCI_INTERRUPT_LINE, 2, 0x109);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 1) << 8) |
+ PCI_CLASS_PROG, 1, 0xf);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 1) << 8) |
+ 0x40, 1, 0xb);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 1) << 8) |
+ 0x50, 4, 0x17171717);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 1) << 8) |
+ PCI_COMMAND, 2, 0x87);
+
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 2) << 8) |
+ PCI_INTERRUPT_LINE, 2, 0x409);
+
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 3) << 8) |
+ PCI_INTERRUPT_LINE, 2, 0x409);
+
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 4) << 8) |
+ PCI_INTERRUPT_LINE, 2, 0x9);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 4) << 8) |
+ 0x48, 4, 0xf00);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 4) << 8) |
+ 0x40, 4, 0x558020);
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 4) << 8) |
+ 0x90, 4, 0xd00);
+
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 5) << 8) |
+ PCI_INTERRUPT_LINE, 2, 0x309);
+
+ pegasos2_pci_config_write(as, 1, (PCI_DEVFN(12, 6) << 8) |
+ PCI_INTERRUPT_LINE, 2, 0x309);
+
+ /* Device tree and VOF set up */
+ vof_init(pm->vof, machine->ram_size, &error_fatal);
+ if (vof_claim(pm->vof, 0, VOF_STACK_SIZE, VOF_STACK_SIZE) == -1) {
+ error_report("Memory allocation for stack failed");
+ exit(1);
+ }
+ if (pm->kernel_size &&
+ vof_claim(pm->vof, pm->kernel_addr, pm->kernel_size, 0) == -1) {
+ error_report("Memory for kernel is in use");
+ exit(1);
+ }
+ fdt = build_fdt(machine, &sz);
+ /* FIXME: VOF assumes entry is same as load address */
+ d[0] = cpu_to_be64(pm->kernel_entry);
+ d[1] = cpu_to_be64(pm->kernel_size - (pm->kernel_entry - pm->kernel_addr));
+ qemu_fdt_setprop(fdt, "/chosen", "qemu,boot-kernel", d, sizeof(d));
+
+ qemu_fdt_dumpdtb(fdt, fdt_totalsize(fdt));
+ g_free(pm->fdt_blob);
+ pm->fdt_blob = fdt;
+
+ vof_build_dt(fdt, pm->vof);
+ vof_client_open_store(fdt, pm->vof, "/chosen", "stdout", "/failsafe");
+ pm->cpu->vhyp = PPC_VIRTUAL_HYPERVISOR(machine);
}
-static void pegasos2_machine(MachineClass *mc)
+enum pegasos2_rtas_tokens {
+ RTAS_RESTART_RTAS = 0,
+ RTAS_NVRAM_FETCH = 1,
+ RTAS_NVRAM_STORE = 2,
+ RTAS_GET_TIME_OF_DAY = 3,
+ RTAS_SET_TIME_OF_DAY = 4,
+ RTAS_EVENT_SCAN = 6,
+ RTAS_CHECK_EXCEPTION = 7,
+ RTAS_READ_PCI_CONFIG = 8,
+ RTAS_WRITE_PCI_CONFIG = 9,
+ RTAS_DISPLAY_CHARACTER = 10,
+ RTAS_SET_INDICATOR = 11,
+ RTAS_POWER_OFF = 17,
+ RTAS_SUSPEND = 18,
+ RTAS_HIBERNATE = 19,
+ RTAS_SYSTEM_REBOOT = 20,
+};
+
+static target_ulong pegasos2_rtas(PowerPCCPU *cpu, Pegasos2MachineState *pm,
+ target_ulong args_real)
{
+ AddressSpace *as = CPU(cpu)->as;
+ uint32_t token = ldl_be_phys(as, args_real);
+ uint32_t nargs = ldl_be_phys(as, args_real + 4);
+ uint32_t nrets = ldl_be_phys(as, args_real + 8);
+ uint32_t args = args_real + 12;
+ uint32_t rets = args_real + 12 + nargs * 4;
+
+ if (nrets < 1) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Too few return values in RTAS call\n");
+ return H_PARAMETER;
+ }
+ switch (token) {
+ case RTAS_READ_PCI_CONFIG:
+ {
+ uint32_t addr, len, val;
+
+ if (nargs != 2 || nrets != 2) {
+ stl_be_phys(as, rets, -1);
+ return H_PARAMETER;
+ }
+ addr = ldl_be_phys(as, args);
+ len = ldl_be_phys(as, args + 4);
+ val = pegasos2_pci_config_read(as, !(addr >> 24),
+ addr & 0x0fffffff, len);
+ stl_be_phys(as, rets, 0);
+ stl_be_phys(as, rets + 4, val);
+ return H_SUCCESS;
+ }
+ case RTAS_WRITE_PCI_CONFIG:
+ {
+ uint32_t addr, len, val;
+
+ if (nargs != 3 || nrets != 1) {
+ stl_be_phys(as, rets, -1);
+ return H_PARAMETER;
+ }
+ addr = ldl_be_phys(as, args);
+ len = ldl_be_phys(as, args + 4);
+ val = ldl_be_phys(as, args + 8);
+ pegasos2_pci_config_write(as, !(addr >> 24),
+ addr & 0x0fffffff, len, val);
+ stl_be_phys(as, rets, 0);
+ return H_SUCCESS;
+ }
+ case RTAS_DISPLAY_CHARACTER:
+ if (nargs != 1 || nrets != 1) {
+ stl_be_phys(as, rets, -1);
+ return H_PARAMETER;
+ }
+ qemu_log_mask(LOG_UNIMP, "%c", ldl_be_phys(as, args));
+ stl_be_phys(as, rets, 0);
+ return H_SUCCESS;
+ default:
+ qemu_log_mask(LOG_UNIMP, "Unknown RTAS token %u (args=%u, rets=%u)\n",
+ token, nargs, nrets);
+ stl_be_phys(as, rets, 0);
+ return H_SUCCESS;
+ }
+}
+
+static void pegasos2_hypercall(PPCVirtualHypervisor *vhyp, PowerPCCPU *cpu)
+{
+ Pegasos2MachineState *pm = PEGASOS2_MACHINE(vhyp);
+ CPUPPCState *env = &cpu->env;
+
+ /* The TCG path should also be holding the BQL at this point */
+ g_assert(qemu_mutex_iothread_locked());
+
+ if (msr_pr) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Hypercall made with MSR[PR]=1\n");
+ env->gpr[3] = H_PRIVILEGE;
+ } else if (env->gpr[3] == KVMPPC_H_RTAS) {
+ env->gpr[3] = pegasos2_rtas(cpu, pm, env->gpr[4]);
+ } else if (env->gpr[3] == KVMPPC_H_VOF_CLIENT) {
+ int ret = vof_client_call(MACHINE(pm), pm->vof, pm->fdt_blob,
+ env->gpr[4]);
+ env->gpr[3] = (ret ? H_PARAMETER : H_SUCCESS);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "Unsupported hypercall " TARGET_FMT_lx
+ "\n", env->gpr[3]);
+ env->gpr[3] = -1;
+ }
+}
+
+static void vhyp_nop(PPCVirtualHypervisor *vhyp, PowerPCCPU *cpu)
+{
+}
+
+static target_ulong vhyp_encode_hpt_for_kvm_pr(PPCVirtualHypervisor *vhyp)
+{
+ return POWERPC_CPU(current_cpu)->env.spr[SPR_SDR1];
+}
+
+static void pegasos2_machine_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+ PPCVirtualHypervisorClass *vhc = PPC_VIRTUAL_HYPERVISOR_CLASS(oc);
+
mc->desc = "Genesi/bPlan Pegasos II";
mc->init = pegasos2_init;
+ mc->reset = pegasos2_machine_reset;
mc->block_default_type = IF_IDE;
mc->default_boot_order = "cd";
mc->default_display = "std";
mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("7400_v2.9");
mc->default_ram_id = "pegasos2.ram";
mc->default_ram_size = 512 * MiB;
+
+ vhc->hypercall = pegasos2_hypercall;
+ vhc->cpu_exec_enter = vhyp_nop;
+ vhc->cpu_exec_exit = vhyp_nop;
+ vhc->encode_hpt_for_kvm_pr = vhyp_encode_hpt_for_kvm_pr;
+}
+
+static const TypeInfo pegasos2_machine_info = {
+ .name = TYPE_PEGASOS2_MACHINE,
+ .parent = TYPE_MACHINE,
+ .class_init = pegasos2_machine_class_init,
+ .instance_size = sizeof(Pegasos2MachineState),
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_PPC_VIRTUAL_HYPERVISOR },
+ { }
+ },
+};
+
+static void pegasos2_machine_register_types(void)
+{
+ type_register_static(&pegasos2_machine_info);
+}
+
+type_init(pegasos2_machine_register_types)
+
+/* FDT creation for passing to firmware */
+
+typedef struct {
+ void *fdt;
+ const char *path;
+} FDTInfo;
+
+/* We do everything in reverse order so it comes out right in the tree */
+
+static void dt_ide(PCIBus *bus, PCIDevice *d, FDTInfo *fi)
+{
+ qemu_fdt_setprop_string(fi->fdt, fi->path, "device_type", "spi");
}
-DEFINE_MACHINE("pegasos2", pegasos2_machine)
+static void dt_usb(PCIBus *bus, PCIDevice *d, FDTInfo *fi)
+{
+ qemu_fdt_setprop_cell(fi->fdt, fi->path, "#size-cells", 0);
+ qemu_fdt_setprop_cell(fi->fdt, fi->path, "#address-cells", 1);
+ qemu_fdt_setprop_string(fi->fdt, fi->path, "device_type", "usb");
+}
+
+static void dt_isa(PCIBus *bus, PCIDevice *d, FDTInfo *fi)
+{
+ GString *name = g_string_sized_new(64);
+ uint32_t cells[3];
+
+ qemu_fdt_setprop_cell(fi->fdt, fi->path, "#size-cells", 1);
+ qemu_fdt_setprop_cell(fi->fdt, fi->path, "#address-cells", 2);
+ qemu_fdt_setprop_string(fi->fdt, fi->path, "device_type", "isa");
+ qemu_fdt_setprop_string(fi->fdt, fi->path, "name", "isa");
+
+ /* addional devices */
+ g_string_printf(name, "%s/lpt@i3bc", fi->path);
+ qemu_fdt_add_subnode(fi->fdt, name->str);
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "clock-frequency", 0);
+ cells[0] = cpu_to_be32(7);
+ cells[1] = 0;
+ qemu_fdt_setprop(fi->fdt, name->str, "interrupts",
+ cells, 2 * sizeof(cells[0]));
+ cells[0] = cpu_to_be32(1);
+ cells[1] = cpu_to_be32(0x3bc);
+ cells[2] = cpu_to_be32(8);
+ qemu_fdt_setprop(fi->fdt, name->str, "reg", cells, 3 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, name->str, "device_type", "lpt");
+ qemu_fdt_setprop_string(fi->fdt, name->str, "name", "lpt");
+
+ g_string_printf(name, "%s/fdc@i3f0", fi->path);
+ qemu_fdt_add_subnode(fi->fdt, name->str);
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "clock-frequency", 0);
+ cells[0] = cpu_to_be32(6);
+ cells[1] = 0;
+ qemu_fdt_setprop(fi->fdt, name->str, "interrupts",
+ cells, 2 * sizeof(cells[0]));
+ cells[0] = cpu_to_be32(1);
+ cells[1] = cpu_to_be32(0x3f0);
+ cells[2] = cpu_to_be32(8);
+ qemu_fdt_setprop(fi->fdt, name->str, "reg", cells, 3 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, name->str, "device_type", "fdc");
+ qemu_fdt_setprop_string(fi->fdt, name->str, "name", "fdc");
+
+ g_string_printf(name, "%s/timer@i40", fi->path);
+ qemu_fdt_add_subnode(fi->fdt, name->str);
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "clock-frequency", 0);
+ cells[0] = cpu_to_be32(1);
+ cells[1] = cpu_to_be32(0x40);
+ cells[2] = cpu_to_be32(8);
+ qemu_fdt_setprop(fi->fdt, name->str, "reg", cells, 3 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, name->str, "device_type", "timer");
+ qemu_fdt_setprop_string(fi->fdt, name->str, "name", "timer");
+
+ g_string_printf(name, "%s/rtc@i70", fi->path);
+ qemu_fdt_add_subnode(fi->fdt, name->str);
+ qemu_fdt_setprop_string(fi->fdt, name->str, "compatible", "ds1385-rtc");
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "clock-frequency", 0);
+ cells[0] = cpu_to_be32(8);
+ cells[1] = 0;
+ qemu_fdt_setprop(fi->fdt, name->str, "interrupts",
+ cells, 2 * sizeof(cells[0]));
+ cells[0] = cpu_to_be32(1);
+ cells[1] = cpu_to_be32(0x70);
+ cells[2] = cpu_to_be32(2);
+ qemu_fdt_setprop(fi->fdt, name->str, "reg", cells, 3 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, name->str, "device_type", "rtc");
+ qemu_fdt_setprop_string(fi->fdt, name->str, "name", "rtc");
+
+ g_string_printf(name, "%s/keyboard@i60", fi->path);
+ qemu_fdt_add_subnode(fi->fdt, name->str);
+ cells[0] = cpu_to_be32(1);
+ cells[1] = 0;
+ qemu_fdt_setprop(fi->fdt, name->str, "interrupts",
+ cells, 2 * sizeof(cells[0]));
+ cells[0] = cpu_to_be32(1);
+ cells[1] = cpu_to_be32(0x60);
+ cells[2] = cpu_to_be32(5);
+ qemu_fdt_setprop(fi->fdt, name->str, "reg", cells, 3 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, name->str, "device_type", "keyboard");
+ qemu_fdt_setprop_string(fi->fdt, name->str, "name", "keyboard");
+
+ g_string_printf(name, "%s/8042@i60", fi->path);
+ qemu_fdt_add_subnode(fi->fdt, name->str);
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "#interrupt-cells", 2);
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "#size-cells", 0);
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "#address-cells", 1);
+ qemu_fdt_setprop_string(fi->fdt, name->str, "interrupt-controller", "");
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "clock-frequency", 0);
+ cells[0] = cpu_to_be32(1);
+ cells[1] = cpu_to_be32(0x60);
+ cells[2] = cpu_to_be32(5);
+ qemu_fdt_setprop(fi->fdt, name->str, "reg", cells, 3 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, name->str, "device_type", "");
+ qemu_fdt_setprop_string(fi->fdt, name->str, "name", "8042");
+
+ g_string_printf(name, "%s/serial@i2f8", fi->path);
+ qemu_fdt_add_subnode(fi->fdt, name->str);
+ qemu_fdt_setprop_cell(fi->fdt, name->str, "clock-frequency", 0);
+ cells[0] = cpu_to_be32(3);
+ cells[1] = 0;
+ qemu_fdt_setprop(fi->fdt, name->str, "interrupts",
+ cells, 2 * sizeof(cells[0]));
+ cells[0] = cpu_to_be32(1);
+ cells[1] = cpu_to_be32(0x2f8);
+ cells[2] = cpu_to_be32(8);
+ qemu_fdt_setprop(fi->fdt, name->str, "reg", cells, 3 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, name->str, "device_type", "serial");
+ qemu_fdt_setprop_string(fi->fdt, name->str, "name", "serial");
+
+ g_string_free(name, TRUE);
+}
+
+static struct {
+ const char *id;
+ const char *name;
+ void (*dtf)(PCIBus *bus, PCIDevice *d, FDTInfo *fi);
+} device_map[] = {
+ { "pci11ab,6460", "host", NULL },
+ { "pci1106,8231", "isa", dt_isa },
+ { "pci1106,571", "ide", dt_ide },
+ { "pci1106,3044", "firewire", NULL },
+ { "pci1106,3038", "usb", dt_usb },
+ { "pci1106,8235", "other", NULL },
+ { "pci1106,3058", "sound", NULL },
+ { NULL, NULL }
+};
+
+static void add_pci_device(PCIBus *bus, PCIDevice *d, void *opaque)
+{
+ FDTInfo *fi = opaque;
+ GString *node = g_string_new(NULL);
+ uint32_t cells[(PCI_NUM_REGIONS + 1) * 5];
+ int i, j;
+ const char *name = NULL;
+ g_autofree const gchar *pn = g_strdup_printf("pci%x,%x",
+ pci_get_word(&d->config[PCI_VENDOR_ID]),
+ pci_get_word(&d->config[PCI_DEVICE_ID]));
+
+ for (i = 0; device_map[i].id; i++) {
+ if (!strcmp(pn, device_map[i].id)) {
+ name = device_map[i].name;
+ break;
+ }
+ }
+ g_string_printf(node, "%s/%s@%x", fi->path, (name ?: pn),
+ PCI_SLOT(d->devfn));
+ if (PCI_FUNC(d->devfn)) {
+ g_string_append_printf(node, ",%x", PCI_FUNC(d->devfn));
+ }
+
+ qemu_fdt_add_subnode(fi->fdt, node->str);
+ if (device_map[i].dtf) {
+ FDTInfo cfi = { fi->fdt, node->str };
+ device_map[i].dtf(bus, d, &cfi);
+ }
+ cells[0] = cpu_to_be32(d->devfn << 8);
+ cells[1] = 0;
+ cells[2] = 0;
+ cells[3] = 0;
+ cells[4] = 0;
+ j = 5;
+ for (i = 0; i < PCI_NUM_REGIONS; i++) {
+ if (!d->io_regions[i].size) {
+ continue;
+ }
+ cells[j] = cpu_to_be32(d->devfn << 8 | (PCI_BASE_ADDRESS_0 + i * 4));
+ if (d->io_regions[i].type & PCI_BASE_ADDRESS_SPACE_IO) {
+ cells[j] |= cpu_to_be32(1 << 24);
+ } else {
+ cells[j] |= cpu_to_be32(2 << 24);
+ if (d->io_regions[i].type & PCI_BASE_ADDRESS_MEM_PREFETCH) {
+ cells[j] |= cpu_to_be32(4 << 28);
+ }
+ }
+ cells[j + 1] = 0;
+ cells[j + 2] = 0;
+ cells[j + 3] = cpu_to_be32(d->io_regions[i].size >> 32);
+ cells[j + 4] = cpu_to_be32(d->io_regions[i].size);
+ j += 5;
+ }
+ qemu_fdt_setprop(fi->fdt, node->str, "reg", cells, j * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fi->fdt, node->str, "name", name ?: pn);
+ if (pci_get_byte(&d->config[PCI_INTERRUPT_PIN])) {
+ qemu_fdt_setprop_cell(fi->fdt, node->str, "interrupts",
+ pci_get_byte(&d->config[PCI_INTERRUPT_PIN]));
+ }
+ /* Pegasos2 firmware has subsystem-id amd subsystem-vendor-id swapped */
+ qemu_fdt_setprop_cell(fi->fdt, node->str, "subsystem-vendor-id",
+ pci_get_word(&d->config[PCI_SUBSYSTEM_ID]));
+ qemu_fdt_setprop_cell(fi->fdt, node->str, "subsystem-id",
+ pci_get_word(&d->config[PCI_SUBSYSTEM_VENDOR_ID]));
+ cells[0] = pci_get_long(&d->config[PCI_CLASS_REVISION]);
+ qemu_fdt_setprop_cell(fi->fdt, node->str, "class-code", cells[0] >> 8);
+ qemu_fdt_setprop_cell(fi->fdt, node->str, "revision-id", cells[0] & 0xff);
+ qemu_fdt_setprop_cell(fi->fdt, node->str, "device-id",
+ pci_get_word(&d->config[PCI_DEVICE_ID]));
+ qemu_fdt_setprop_cell(fi->fdt, node->str, "vendor-id",
+ pci_get_word(&d->config[PCI_VENDOR_ID]));
+
+ g_string_free(node, TRUE);
+}
+
+static void *build_fdt(MachineState *machine, int *fdt_size)
+{
+ Pegasos2MachineState *pm = PEGASOS2_MACHINE(machine);
+ PowerPCCPU *cpu = pm->cpu;
+ PCIBus *pci_bus;
+ FDTInfo fi;
+ uint32_t cells[16];
+ void *fdt = create_device_tree(fdt_size);
+
+ fi.fdt = fdt;
+
+ /* root node */
+ qemu_fdt_setprop_string(fdt, "/", "CODEGEN,description",
+ "Pegasos CHRP PowerPC System");
+ qemu_fdt_setprop_string(fdt, "/", "CODEGEN,board", "Pegasos2");
+ qemu_fdt_setprop_string(fdt, "/", "CODEGEN,vendor", "bplan GmbH");
+ qemu_fdt_setprop_string(fdt, "/", "revision", "2B");
+ qemu_fdt_setprop_string(fdt, "/", "model", "Pegasos2");
+ qemu_fdt_setprop_string(fdt, "/", "device_type", "chrp");
+ qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 1);
+ qemu_fdt_setprop_string(fdt, "/", "name", "bplan,Pegasos2");
+
+ /* pci@c0000000 */
+ qemu_fdt_add_subnode(fdt, "/pci@c0000000");
+ cells[0] = 0;
+ cells[1] = 0;
+ qemu_fdt_setprop(fdt, "/pci@c0000000", "bus-range",
+ cells, 2 * sizeof(cells[0]));
+ qemu_fdt_setprop_cell(fdt, "/pci@c0000000", "pci-bridge-number", 1);
+ cells[0] = cpu_to_be32(PCI0_MEM_BASE);
+ cells[1] = cpu_to_be32(PCI0_MEM_SIZE);
+ qemu_fdt_setprop(fdt, "/pci@c0000000", "reg", cells, 2 * sizeof(cells[0]));
+ cells[0] = cpu_to_be32(0x01000000);
+ cells[1] = 0;
+ cells[2] = 0;
+ cells[3] = cpu_to_be32(PCI0_IO_BASE);
+ cells[4] = 0;
+ cells[5] = cpu_to_be32(PCI0_IO_SIZE);
+ cells[6] = cpu_to_be32(0x02000000);
+ cells[7] = 0;
+ cells[8] = cpu_to_be32(PCI0_MEM_BASE);
+ cells[9] = cpu_to_be32(PCI0_MEM_BASE);
+ cells[10] = 0;
+ cells[11] = cpu_to_be32(PCI0_MEM_SIZE);
+ qemu_fdt_setprop(fdt, "/pci@c0000000", "ranges",
+ cells, 12 * sizeof(cells[0]));
+ qemu_fdt_setprop_cell(fdt, "/pci@c0000000", "#size-cells", 2);
+ qemu_fdt_setprop_cell(fdt, "/pci@c0000000", "#address-cells", 3);
+ qemu_fdt_setprop_string(fdt, "/pci@c0000000", "device_type", "pci");
+ qemu_fdt_setprop_string(fdt, "/pci@c0000000", "name", "pci");
+
+ fi.path = "/pci@c0000000";
+ pci_bus = mv64361_get_pci_bus(pm->mv, 0);
+ pci_for_each_device_reverse(pci_bus, 0, add_pci_device, &fi);
+
+ /* pci@80000000 */
+ qemu_fdt_add_subnode(fdt, "/pci@80000000");
+ cells[0] = 0;
+ cells[1] = 0;
+ qemu_fdt_setprop(fdt, "/pci@80000000", "bus-range",
+ cells, 2 * sizeof(cells[0]));
+ qemu_fdt_setprop_cell(fdt, "/pci@80000000", "pci-bridge-number", 0);
+ cells[0] = cpu_to_be32(PCI1_MEM_BASE);
+ cells[1] = cpu_to_be32(PCI1_MEM_SIZE);
+ qemu_fdt_setprop(fdt, "/pci@80000000", "reg", cells, 2 * sizeof(cells[0]));
+ qemu_fdt_setprop_cell(fdt, "/pci@80000000", "8259-interrupt-acknowledge",
+ 0xf1000cb4);
+ cells[0] = cpu_to_be32(0x01000000);
+ cells[1] = 0;
+ cells[2] = 0;
+ cells[3] = cpu_to_be32(PCI1_IO_BASE);
+ cells[4] = 0;
+ cells[5] = cpu_to_be32(PCI1_IO_SIZE);
+ cells[6] = cpu_to_be32(0x02000000);
+ cells[7] = 0;
+ cells[8] = cpu_to_be32(PCI1_MEM_BASE);
+ cells[9] = cpu_to_be32(PCI1_MEM_BASE);
+ cells[10] = 0;
+ cells[11] = cpu_to_be32(PCI1_MEM_SIZE);
+ qemu_fdt_setprop(fdt, "/pci@80000000", "ranges",
+ cells, 12 * sizeof(cells[0]));
+ qemu_fdt_setprop_cell(fdt, "/pci@80000000", "#size-cells", 2);
+ qemu_fdt_setprop_cell(fdt, "/pci@80000000", "#address-cells", 3);
+ qemu_fdt_setprop_string(fdt, "/pci@80000000", "device_type", "pci");
+ qemu_fdt_setprop_string(fdt, "/pci@80000000", "name", "pci");
+
+ fi.path = "/pci@80000000";
+ pci_bus = mv64361_get_pci_bus(pm->mv, 1);
+ pci_for_each_device_reverse(pci_bus, 0, add_pci_device, &fi);
+
+ qemu_fdt_add_subnode(fdt, "/failsafe");
+ qemu_fdt_setprop_string(fdt, "/failsafe", "device_type", "serial");
+ qemu_fdt_setprop_string(fdt, "/failsafe", "name", "failsafe");
+
+ qemu_fdt_add_subnode(fdt, "/rtas");
+ qemu_fdt_setprop_cell(fdt, "/rtas", "system-reboot", RTAS_SYSTEM_REBOOT);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "hibernate", RTAS_HIBERNATE);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "suspend", RTAS_SUSPEND);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "power-off", RTAS_POWER_OFF);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "set-indicator", RTAS_SET_INDICATOR);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "display-character",
+ RTAS_DISPLAY_CHARACTER);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "write-pci-config",
+ RTAS_WRITE_PCI_CONFIG);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "read-pci-config",
+ RTAS_READ_PCI_CONFIG);
+ /* Pegasos2 firmware misspells check-exception and guests use that */
+ qemu_fdt_setprop_cell(fdt, "/rtas", "check-execption",
+ RTAS_CHECK_EXCEPTION);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "event-scan", RTAS_EVENT_SCAN);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "set-time-of-day",
+ RTAS_SET_TIME_OF_DAY);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "get-time-of-day",
+ RTAS_GET_TIME_OF_DAY);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "nvram-store", RTAS_NVRAM_STORE);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "nvram-fetch", RTAS_NVRAM_FETCH);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "restart-rtas", RTAS_RESTART_RTAS);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "rtas-error-log-max", 0);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "rtas-event-scan-rate", 0);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "rtas-display-device", 0);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "rtas-size", 20);
+ qemu_fdt_setprop_cell(fdt, "/rtas", "rtas-version", 1);
+
+ /* cpus */
+ qemu_fdt_add_subnode(fdt, "/cpus");
+ qemu_fdt_setprop_cell(fdt, "/cpus", "#cpus", 1);
+ qemu_fdt_setprop_cell(fdt, "/cpus", "#address-cells", 1);
+ qemu_fdt_setprop_cell(fdt, "/cpus", "#size-cells", 0);
+ qemu_fdt_setprop_string(fdt, "/cpus", "name", "cpus");
+
+ /* FIXME Get CPU name from CPU object */
+ const char *cp = "/cpus/PowerPC,G4";
+ qemu_fdt_add_subnode(fdt, cp);
+ qemu_fdt_setprop_cell(fdt, cp, "l2cr", 0);
+ qemu_fdt_setprop_cell(fdt, cp, "d-cache-size", 0x8000);
+ qemu_fdt_setprop_cell(fdt, cp, "d-cache-block-size",
+ cpu->env.dcache_line_size);
+ qemu_fdt_setprop_cell(fdt, cp, "d-cache-line-size",
+ cpu->env.dcache_line_size);
+ qemu_fdt_setprop_cell(fdt, cp, "i-cache-size", 0x8000);
+ qemu_fdt_setprop_cell(fdt, cp, "i-cache-block-size",
+ cpu->env.icache_line_size);
+ qemu_fdt_setprop_cell(fdt, cp, "i-cache-line-size",
+ cpu->env.icache_line_size);
+ if (cpu->env.id_tlbs) {
+ qemu_fdt_setprop_cell(fdt, cp, "i-tlb-sets", cpu->env.nb_ways);
+ qemu_fdt_setprop_cell(fdt, cp, "i-tlb-size", cpu->env.tlb_per_way);
+ qemu_fdt_setprop_cell(fdt, cp, "d-tlb-sets", cpu->env.nb_ways);
+ qemu_fdt_setprop_cell(fdt, cp, "d-tlb-size", cpu->env.tlb_per_way);
+ qemu_fdt_setprop_string(fdt, cp, "tlb-split", "");
+ }
+ qemu_fdt_setprop_cell(fdt, cp, "tlb-sets", cpu->env.nb_ways);
+ qemu_fdt_setprop_cell(fdt, cp, "tlb-size", cpu->env.nb_tlb);
+ qemu_fdt_setprop_string(fdt, cp, "state", "running");
+ if (cpu->env.insns_flags & PPC_ALTIVEC) {
+ qemu_fdt_setprop_string(fdt, cp, "altivec", "");
+ qemu_fdt_setprop_string(fdt, cp, "data-streams", "");
+ }
+ /*
+ * FIXME What flags do data-streams, external-control and
+ * performance-monitor depend on?
+ */
+ qemu_fdt_setprop_string(fdt, cp, "external-control", "");
+ if (cpu->env.insns_flags & PPC_FLOAT_FSQRT) {
+ qemu_fdt_setprop_string(fdt, cp, "general-purpose", "");
+ }
+ qemu_fdt_setprop_string(fdt, cp, "performance-monitor", "");
+ if (cpu->env.insns_flags & PPC_FLOAT_FRES) {
+ qemu_fdt_setprop_string(fdt, cp, "graphics", "");
+ }
+ qemu_fdt_setprop_cell(fdt, cp, "reservation-granule-size", 4);
+ qemu_fdt_setprop_cell(fdt, cp, "timebase-frequency",
+ cpu->env.tb_env->tb_freq);
+ qemu_fdt_setprop_cell(fdt, cp, "bus-frequency", BUS_FREQ_HZ);
+ qemu_fdt_setprop_cell(fdt, cp, "clock-frequency", BUS_FREQ_HZ * 7.5);
+ qemu_fdt_setprop_cell(fdt, cp, "cpu-version", cpu->env.spr[SPR_PVR]);
+ cells[0] = 0;
+ cells[1] = 0;
+ qemu_fdt_setprop(fdt, cp, "reg", cells, 2 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fdt, cp, "device_type", "cpu");
+ qemu_fdt_setprop_string(fdt, cp, "name", strrchr(cp, '/') + 1);
+
+ /* memory */
+ qemu_fdt_add_subnode(fdt, "/memory@0");
+ cells[0] = 0;
+ cells[1] = cpu_to_be32(machine->ram_size);
+ qemu_fdt_setprop(fdt, "/memory@0", "reg", cells, 2 * sizeof(cells[0]));
+ qemu_fdt_setprop_string(fdt, "/memory@0", "device_type", "memory");
+ qemu_fdt_setprop_string(fdt, "/memory@0", "name", "memory");
+
+ qemu_fdt_add_subnode(fdt, "/chosen");
+ qemu_fdt_setprop_string(fdt, "/chosen", "bootargs",
+ machine->kernel_cmdline ?: "");
+ qemu_fdt_setprop_string(fdt, "/chosen", "name", "chosen");
+
+ qemu_fdt_add_subnode(fdt, "/openprom");
+ qemu_fdt_setprop_string(fdt, "/openprom", "model", "Pegasos2,1.1");
+
+ return fdt;
+}
diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c
index 4dd90b75cc..81699d4f8b 100644
--- a/hw/ppc/spapr.c
+++ b/hw/ppc/spapr.c
@@ -101,6 +101,7 @@
#define FDT_MAX_ADDR 0x80000000 /* FDT must stay below that */
#define FW_MAX_SIZE 0x400000
#define FW_FILE_NAME "slof.bin"
+#define FW_FILE_NAME_VOF "vof.bin"
#define FW_OVERHEAD 0x2800000
#define KERNEL_LOAD_ADDR FW_MAX_SIZE
@@ -880,6 +881,10 @@ static void spapr_dt_rtas(SpaprMachineState *spapr, void *fdt)
add_str(hypertas, "hcall-copy");
add_str(hypertas, "hcall-debug");
add_str(hypertas, "hcall-vphn");
+ if (spapr_get_cap(spapr, SPAPR_CAP_RPT_INVALIDATE) == SPAPR_CAP_ON) {
+ add_str(hypertas, "hcall-rpt-invalidate");
+ }
+
add_str(qemu_hypertas, "hcall-memop1");
if (!kvm_enabled() || kvmppc_spapr_use_multitce()) {
@@ -919,9 +924,13 @@ static void spapr_dt_rtas(SpaprMachineState *spapr, void *fdt)
*
* The extra 8 bytes is required because Linux's FWNMI error log check
* is off-by-one.
+ *
+ * RTAS_MIN_SIZE is required for the RTAS blob itself.
*/
- _FDT(fdt_setprop_cell(fdt, rtas, "rtas-size", RTAS_ERROR_LOG_MAX +
- ms->smp.max_cpus * sizeof(uint64_t)*2 + sizeof(uint64_t)));
+ _FDT(fdt_setprop_cell(fdt, rtas, "rtas-size", RTAS_MIN_SIZE +
+ RTAS_ERROR_LOG_MAX +
+ ms->smp.max_cpus * sizeof(uint64_t) * 2 +
+ sizeof(uint64_t)));
_FDT(fdt_setprop_cell(fdt, rtas, "rtas-error-log-max",
RTAS_ERROR_LOG_MAX));
_FDT(fdt_setprop_cell(fdt, rtas, "rtas-event-scan-rate",
@@ -1639,22 +1648,29 @@ static void spapr_machine_reset(MachineState *machine)
fdt_addr = MIN(spapr->rma_size, FDT_MAX_ADDR) - FDT_MAX_SIZE;
fdt = spapr_build_fdt(spapr, true, FDT_MAX_SIZE);
+ if (spapr->vof) {
+ spapr_vof_reset(spapr, fdt, &error_fatal);
+ /*
+ * Do not pack the FDT as the client may change properties.
+ * VOF client does not expect the FDT so we do not load it to the VM.
+ */
+ } else {
+ rc = fdt_pack(fdt);
+ /* Should only fail if we've built a corrupted tree */
+ assert(rc == 0);
- rc = fdt_pack(fdt);
-
- /* Should only fail if we've built a corrupted tree */
- assert(rc == 0);
-
- /* Load the fdt */
+ spapr_cpu_set_entry_state(first_ppc_cpu, SPAPR_ENTRY_POINT,
+ 0, fdt_addr, 0);
+ cpu_physical_memory_write(fdt_addr, fdt, fdt_totalsize(fdt));
+ }
qemu_fdt_dumpdtb(fdt, fdt_totalsize(fdt));
- cpu_physical_memory_write(fdt_addr, fdt, fdt_totalsize(fdt));
+
g_free(spapr->fdt_blob);
spapr->fdt_size = fdt_totalsize(fdt);
spapr->fdt_initial_size = spapr->fdt_size;
spapr->fdt_blob = fdt;
/* Set up the entry state */
- spapr_cpu_set_entry_state(first_ppc_cpu, SPAPR_ENTRY_POINT, 0, fdt_addr, 0);
first_ppc_cpu->env.gpr[5] = 0;
spapr->fwnmi_system_reset_addr = -1;
@@ -2018,6 +2034,7 @@ static const VMStateDescription vmstate_spapr = {
&vmstate_spapr_cap_ccf_assist,
&vmstate_spapr_cap_fwnmi,
&vmstate_spapr_fwnmi,
+ &vmstate_spapr_cap_rpt_invalidate,
NULL
}
};
@@ -2657,7 +2674,8 @@ static void spapr_machine_init(MachineState *machine)
SpaprMachineState *spapr = SPAPR_MACHINE(machine);
SpaprMachineClass *smc = SPAPR_MACHINE_GET_CLASS(machine);
MachineClass *mc = MACHINE_GET_CLASS(machine);
- const char *bios_name = machine->firmware ?: FW_FILE_NAME;
+ const char *bios_default = spapr->vof ? FW_FILE_NAME_VOF : FW_FILE_NAME;
+ const char *bios_name = machine->firmware ?: bios_default;
const char *kernel_filename = machine->kernel_filename;
const char *initrd_filename = machine->initrd_filename;
PCIHostState *phb;
@@ -3014,6 +3032,10 @@ static void spapr_machine_init(MachineState *machine)
}
qemu_cond_init(&spapr->fwnmi_machine_check_interlock_cond);
+ if (spapr->vof) {
+ spapr->vof->fw_size = fw_size; /* for claim() on itself */
+ spapr_register_hypercall(KVMPPC_H_VOF_CLIENT, spapr_h_vof_client);
+ }
}
#define DEFAULT_KVM_TYPE "auto"
@@ -3106,7 +3128,7 @@ static char *spapr_get_fw_dev_path(FWPathProvider *p, BusState *bus,
*/
if (strcmp("usb-host", qdev_fw_name(dev)) == 0) {
USBDevice *usbdev = CAST(USBDevice, dev, TYPE_USB_DEVICE);
- if (usb_host_dev_is_scsi_storage(usbdev)) {
+ if (usb_device_is_scsi_storage(usbdev)) {
return g_strdup_printf("storage@%s/disk", usbdev->port->path);
}
}
@@ -3204,6 +3226,28 @@ static void spapr_set_resize_hpt(Object *obj, const char *value, Error **errp)
}
}
+static bool spapr_get_vof(Object *obj, Error **errp)
+{
+ SpaprMachineState *spapr = SPAPR_MACHINE(obj);
+
+ return spapr->vof != NULL;
+}
+
+static void spapr_set_vof(Object *obj, bool value, Error **errp)
+{
+ SpaprMachineState *spapr = SPAPR_MACHINE(obj);
+
+ if (spapr->vof) {
+ vof_cleanup(spapr->vof);
+ g_free(spapr->vof);
+ spapr->vof = NULL;
+ }
+ if (!value) {
+ return;
+ }
+ spapr->vof = g_malloc0(sizeof(*spapr->vof));
+}
+
static char *spapr_get_ic_mode(Object *obj, Error **errp)
{
SpaprMachineState *spapr = SPAPR_MACHINE(obj);
@@ -3329,6 +3373,11 @@ static void spapr_instance_init(Object *obj)
stringify(KERNEL_LOAD_ADDR)
" for -kernel is the default");
spapr->kernel_addr = KERNEL_LOAD_ADDR;
+
+ object_property_add_bool(obj, "x-vof", spapr_get_vof, spapr_set_vof);
+ object_property_set_description(obj, "x-vof",
+ "Enable Virtual Open Firmware (experimental)");
+
/* The machine class defines the default interrupt controller mode */
spapr->irq = smc->irq;
object_property_add_str(obj, "ic-mode", spapr_get_ic_mode,
@@ -4492,6 +4541,7 @@ static void spapr_machine_class_init(ObjectClass *oc, void *data)
XICSFabricClass *xic = XICS_FABRIC_CLASS(oc);
InterruptStatsProviderClass *ispc = INTERRUPT_STATS_PROVIDER_CLASS(oc);
XiveFabricClass *xfc = XIVE_FABRIC_CLASS(oc);
+ VofMachineIfClass *vmc = VOF_MACHINE_CLASS(oc);
mc->desc = "pSeries Logical Partition (PAPR compliant)";
mc->ignore_boot_device_suffixes = true;
@@ -4573,6 +4623,7 @@ static void spapr_machine_class_init(ObjectClass *oc, void *data)
smc->default_caps.caps[SPAPR_CAP_LARGE_DECREMENTER] = SPAPR_CAP_ON;
smc->default_caps.caps[SPAPR_CAP_CCF_ASSIST] = SPAPR_CAP_ON;
smc->default_caps.caps[SPAPR_CAP_FWNMI] = SPAPR_CAP_ON;
+ smc->default_caps.caps[SPAPR_CAP_RPT_INVALIDATE] = SPAPR_CAP_OFF;
spapr_caps_add_properties(smc);
smc->irq = &spapr_irq_dual;
smc->dr_phb_enabled = true;
@@ -4580,6 +4631,9 @@ static void spapr_machine_class_init(ObjectClass *oc, void *data)
smc->smp_threads_vsmt = true;
smc->nr_xirqs = SPAPR_NR_XIRQS;
xfc->match_nvt = spapr_match_nvt;
+ vmc->client_architecture_support = spapr_vof_client_architecture_support;
+ vmc->quiesce = spapr_vof_quiesce;
+ vmc->setprop = spapr_vof_setprop;
}
static const TypeInfo spapr_machine_info = {
@@ -4599,6 +4653,7 @@ static const TypeInfo spapr_machine_info = {
{ TYPE_XICS_FABRIC },
{ TYPE_INTERRUPT_STATS_PROVIDER },
{ TYPE_XIVE_FABRIC },
+ { TYPE_VOF_MACHINE_IF },
{ }
},
};
diff --git a/hw/ppc/spapr_caps.c b/hw/ppc/spapr_caps.c
index d0c419b392..ed7c077a0d 100644
--- a/hw/ppc/spapr_caps.c
+++ b/hw/ppc/spapr_caps.c
@@ -582,6 +582,37 @@ static void cap_fwnmi_apply(SpaprMachineState *spapr, uint8_t val,
}
}
+static void cap_rpt_invalidate_apply(SpaprMachineState *spapr,
+ uint8_t val, Error **errp)
+{
+ ERRP_GUARD();
+
+ if (!val) {
+ /* capability disabled by default */
+ return;
+ }
+
+ if (tcg_enabled()) {
+ error_setg(errp, "No H_RPT_INVALIDATE support in TCG");
+ error_append_hint(errp,
+ "Try appending -machine cap-rpt-invalidate=off\n");
+ } else if (kvm_enabled()) {
+ if (!kvmppc_has_cap_mmu_radix()) {
+ error_setg(errp, "H_RPT_INVALIDATE only supported on Radix");
+ return;
+ }
+
+ if (!kvmppc_has_cap_rpt_invalidate()) {
+ error_setg(errp,
+ "KVM implementation does not support H_RPT_INVALIDATE");
+ error_append_hint(errp,
+ "Try appending -machine cap-rpt-invalidate=off\n");
+ } else {
+ kvmppc_enable_h_rpt_invalidate();
+ }
+ }
+}
+
SpaprCapabilityInfo capability_table[SPAPR_CAP_NUM] = {
[SPAPR_CAP_HTM] = {
.name = "htm",
@@ -690,6 +721,15 @@ SpaprCapabilityInfo capability_table[SPAPR_CAP_NUM] = {
.type = "bool",
.apply = cap_fwnmi_apply,
},
+ [SPAPR_CAP_RPT_INVALIDATE] = {
+ .name = "rpt-invalidate",
+ .description = "Allow H_RPT_INVALIDATE",
+ .index = SPAPR_CAP_RPT_INVALIDATE,
+ .get = spapr_cap_get_bool,
+ .set = spapr_cap_set_bool,
+ .type = "bool",
+ .apply = cap_rpt_invalidate_apply,
+ },
};
static SpaprCapabilities default_caps_with_cpu(SpaprMachineState *spapr,
@@ -830,6 +870,7 @@ SPAPR_CAP_MIG_STATE(nested_kvm_hv, SPAPR_CAP_NESTED_KVM_HV);
SPAPR_CAP_MIG_STATE(large_decr, SPAPR_CAP_LARGE_DECREMENTER);
SPAPR_CAP_MIG_STATE(ccf_assist, SPAPR_CAP_CCF_ASSIST);
SPAPR_CAP_MIG_STATE(fwnmi, SPAPR_CAP_FWNMI);
+SPAPR_CAP_MIG_STATE(rpt_invalidate, SPAPR_CAP_RPT_INVALIDATE);
void spapr_caps_init(SpaprMachineState *spapr)
{
diff --git a/hw/ppc/spapr_hcall.c b/hw/ppc/spapr_hcall.c
index f25014afda..0e9a5b2e40 100644
--- a/hw/ppc/spapr_hcall.c
+++ b/hw/ppc/spapr_hcall.c
@@ -1233,8 +1233,7 @@ target_ulong do_client_architecture_support(PowerPCCPU *cpu,
spapr_setup_hpt(spapr);
}
- fdt = spapr_build_fdt(spapr, false, fdt_bufsize);
-
+ fdt = spapr_build_fdt(spapr, spapr->vof != NULL, fdt_bufsize);
g_free(spapr->fdt_blob);
spapr->fdt_size = fdt_totalsize(fdt);
spapr->fdt_initial_size = spapr->fdt_size;
@@ -1277,6 +1276,25 @@ static target_ulong h_client_architecture_support(PowerPCCPU *cpu,
return ret;
}
+target_ulong spapr_vof_client_architecture_support(MachineState *ms,
+ CPUState *cs,
+ target_ulong ovec_addr)
+{
+ SpaprMachineState *spapr = SPAPR_MACHINE(ms);
+
+ target_ulong ret = do_client_architecture_support(POWERPC_CPU(cs), spapr,
+ ovec_addr, FDT_MAX_SIZE);
+
+ /*
+ * This adds stdout and generates phandles for boottime and CAS FDTs.
+ * It is alright to update the FDT here as do_client_architecture_support()
+ * does not pack it.
+ */
+ spapr_vof_client_dt_finalize(spapr, spapr->fdt_blob);
+
+ return ret;
+}
+
static target_ulong h_get_cpu_characteristics(PowerPCCPU *cpu,
SpaprMachineState *spapr,
target_ulong opcode,
@@ -1299,6 +1317,8 @@ static target_ulong h_get_cpu_characteristics(PowerPCCPU *cpu,
behaviour |= H_CPU_BEHAV_L1D_FLUSH_PR;
break;
case SPAPR_CAP_FIXED:
+ behaviour |= H_CPU_BEHAV_NO_L1D_FLUSH_ENTRY;
+ behaviour |= H_CPU_BEHAV_NO_L1D_FLUSH_UACCESS;
break;
default: /* broken */
assert(safe_cache == SPAPR_CAP_BROKEN);
diff --git a/hw/ppc/spapr_vof.c b/hw/ppc/spapr_vof.c
new file mode 100644
index 0000000000..40ce8fe003
--- /dev/null
+++ b/hw/ppc/spapr_vof.c
@@ -0,0 +1,167 @@
+/*
+ * SPAPR machine hooks to Virtual Open Firmware,
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qapi/error.h"
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_vio.h"
+#include "hw/ppc/spapr_cpu_core.h"
+#include "hw/ppc/fdt.h"
+#include "hw/ppc/vof.h"
+#include "sysemu/sysemu.h"
+#include "qom/qom-qobject.h"
+#include "trace.h"
+
+target_ulong spapr_h_vof_client(PowerPCCPU *cpu, SpaprMachineState *spapr,
+ target_ulong opcode, target_ulong *_args)
+{
+ int ret = vof_client_call(MACHINE(spapr), spapr->vof, spapr->fdt_blob,
+ ppc64_phys_to_real(_args[0]));
+
+ if (ret) {
+ return H_PARAMETER;
+ }
+ return H_SUCCESS;
+}
+
+void spapr_vof_client_dt_finalize(SpaprMachineState *spapr, void *fdt)
+{
+ char *stdout_path = spapr_vio_stdout_path(spapr->vio_bus);
+
+ vof_build_dt(fdt, spapr->vof);
+
+ if (spapr->vof->bootargs) {
+ int chosen;
+
+ _FDT(chosen = fdt_path_offset(fdt, "/chosen"));
+ /*
+ * If the client did not change "bootargs", spapr_dt_chosen() must have
+ * stored machine->kernel_cmdline in it before getting here.
+ */
+ _FDT(fdt_setprop_string(fdt, chosen, "bootargs", spapr->vof->bootargs));
+ }
+
+ /*
+ * SLOF-less setup requires an open instance of stdout for early
+ * kernel printk. By now all phandles are settled so we can open
+ * the default serial console.
+ */
+ if (stdout_path) {
+ _FDT(vof_client_open_store(fdt, spapr->vof, "/chosen", "stdout",
+ stdout_path));
+ }
+}
+
+void spapr_vof_reset(SpaprMachineState *spapr, void *fdt, Error **errp)
+{
+ target_ulong stack_ptr;
+ Vof *vof = spapr->vof;
+ PowerPCCPU *first_ppc_cpu = POWERPC_CPU(first_cpu);
+
+ vof_init(vof, spapr->rma_size, errp);
+
+ stack_ptr = vof_claim(vof, 0, VOF_STACK_SIZE, VOF_STACK_SIZE);
+ if (stack_ptr == -1) {
+ error_setg(errp, "Memory allocation for stack failed");
+ return;
+ }
+ /* Stack grows downwards plus reserve space for the minimum stack frame */
+ stack_ptr += VOF_STACK_SIZE - 0x20;
+
+ if (spapr->kernel_size &&
+ vof_claim(vof, spapr->kernel_addr, spapr->kernel_size, 0) == -1) {
+ error_setg(errp, "Memory for kernel is in use");
+ return;
+ }
+
+ if (spapr->initrd_size &&
+ vof_claim(vof, spapr->initrd_base, spapr->initrd_size, 0) == -1) {
+ error_setg(errp, "Memory for initramdisk is in use");
+ return;
+ }
+
+ spapr_vof_client_dt_finalize(spapr, fdt);
+
+ spapr_cpu_set_entry_state(first_ppc_cpu, SPAPR_ENTRY_POINT,
+ stack_ptr, spapr->initrd_base,
+ spapr->initrd_size);
+ /* VOF is 32bit BE so enforce MSR here */
+ first_ppc_cpu->env.msr &= ~((1ULL << MSR_SF) | (1ULL << MSR_LE));
+
+ /*
+ * At this point the expected allocation map is:
+ *
+ * 0..c38 - the initial firmware
+ * 8000..10000 - stack
+ * 400000.. - kernel
+ * 3ea0000.. - initramdisk
+ *
+ * We skip writing FDT as nothing expects it; OF client interface is
+ * going to be used for reading the device tree.
+ */
+}
+
+void spapr_vof_quiesce(MachineState *ms)
+{
+ SpaprMachineState *spapr = SPAPR_MACHINE(ms);
+
+ spapr->fdt_size = fdt_totalsize(spapr->fdt_blob);
+ spapr->fdt_initial_size = spapr->fdt_size;
+}
+
+bool spapr_vof_setprop(MachineState *ms, const char *path, const char *propname,
+ void *val, int vallen)
+{
+ SpaprMachineState *spapr = SPAPR_MACHINE(ms);
+
+ /*
+ * We only allow changing properties which we know how to update in QEMU
+ * OR
+ * the ones which we know that they need to survive during "quiesce".
+ */
+
+ if (strcmp(path, "/rtas") == 0) {
+ if (strcmp(propname, "linux,rtas-base") == 0 ||
+ strcmp(propname, "linux,rtas-entry") == 0) {
+ /* These need to survive quiesce so let them store in the FDT */
+ return true;
+ }
+ }
+
+ if (strcmp(path, "/chosen") == 0) {
+ if (strcmp(propname, "bootargs") == 0) {
+ Vof *vof = spapr->vof;
+
+ g_free(vof->bootargs);
+ vof->bootargs = g_strndup(val, vallen);
+ return true;
+ }
+ if (strcmp(propname, "linux,initrd-start") == 0) {
+ if (vallen == sizeof(uint32_t)) {
+ spapr->initrd_base = ldl_be_p(val);
+ return true;
+ }
+ if (vallen == sizeof(uint64_t)) {
+ spapr->initrd_base = ldq_be_p(val);
+ return true;
+ }
+ return false;
+ }
+ if (strcmp(propname, "linux,initrd-end") == 0) {
+ if (vallen == sizeof(uint32_t)) {
+ spapr->initrd_size = ldl_be_p(val) - spapr->initrd_base;
+ return true;
+ }
+ if (vallen == sizeof(uint64_t)) {
+ spapr->initrd_size = ldq_be_p(val) - spapr->initrd_base;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/hw/ppc/trace-events b/hw/ppc/trace-events
index 0ba3e40353..6e90a01072 100644
--- a/hw/ppc/trace-events
+++ b/hw/ppc/trace-events
@@ -71,6 +71,30 @@ spapr_rtas_ibm_configure_connector_invalid(uint32_t index) "DRC index: 0x%"PRIx3
spapr_vio_h_reg_crq(uint64_t reg, uint64_t queue_addr, uint64_t queue_len) "CRQ for dev 0x%" PRIx64 " registered at 0x%" PRIx64 "/0x%" PRIx64
spapr_vio_free_crq(uint32_t reg) "CRQ for dev 0x%" PRIx32 " freed"
+# vof.c
+vof_error_str_truncated(const char *s, int len) "%s truncated to %d"
+vof_error_param(const char *method, int nargscheck, int nretcheck, int nargs, int nret) "%s takes/returns %d/%d, not %d/%d"
+vof_error_unknown_service(const char *service, int nargs, int nret) "\"%s\" args=%d rets=%d"
+vof_error_unknown_method(const char *method) "\"%s\""
+vof_error_unknown_ihandle_close(uint32_t ih) "ih=0x%x"
+vof_error_unknown_path(const char *path) "\"%s\""
+vof_error_write(uint32_t ih) "ih=0x%x"
+vof_finddevice(const char *path, uint32_t ph) "\"%s\" => ph=0x%x"
+vof_claim(uint32_t virt, uint32_t size, uint32_t align, uint32_t ret) "virt=0x%x size=0x%x align=0x%x => 0x%x"
+vof_release(uint32_t virt, uint32_t size, uint32_t ret) "virt=0x%x size=0x%x => 0x%x"
+vof_method(uint32_t ihandle, const char *method, uint32_t param, uint32_t ret, uint32_t ret2) "ih=0x%x \"%s\"(0x%x) => 0x%x 0x%x"
+vof_getprop(uint32_t ph, const char *prop, uint32_t ret, const char *val) "ph=0x%x \"%s\" => len=%d [%s]"
+vof_getproplen(uint32_t ph, const char *prop, uint32_t ret) "ph=0x%x \"%s\" => len=%d"
+vof_setprop(uint32_t ph, const char *prop, const char *val, uint32_t vallen, uint32_t ret) "ph=0x%x \"%s\" [%s] len=%d => ret=%d"
+vof_open(const char *path, uint32_t ph, uint32_t ih) "%s ph=0x%x => ih=0x%x"
+vof_interpret(const char *cmd, uint32_t param1, uint32_t param2, uint32_t ret, uint32_t ret2) "[%s] 0x%x 0x%x => 0x%x 0x%x"
+vof_package_to_path(uint32_t ph, const char *tmp, uint32_t ret) "ph=0x%x => %s len=%d"
+vof_instance_to_path(uint32_t ih, uint32_t ph, const char *tmp, uint32_t ret) "ih=0x%x ph=0x%x => %s len=%d"
+vof_instance_to_package(uint32_t ih, uint32_t ph) "ih=0x%x => ph=0x%x"
+vof_write(uint32_t ih, unsigned cb, const char *msg) "ih=0x%x [%u] \"%s\""
+vof_avail(uint64_t start, uint64_t end, uint64_t size) "0x%"PRIx64"..0x%"PRIx64" size=0x%"PRIx64
+vof_claimed(uint64_t start, uint64_t end, uint64_t size) "0x%"PRIx64"..0x%"PRIx64" size=0x%"PRIx64
+
# ppc.c
ppc_tb_adjust(uint64_t offs1, uint64_t offs2, int64_t diff, int64_t seconds) "adjusted from 0x%"PRIx64" to 0x%"PRIx64", diff %"PRId64" (%"PRId64"s)"
diff --git a/hw/ppc/vof.c b/hw/ppc/vof.c
new file mode 100644
index 0000000000..81f6596215
--- /dev/null
+++ b/hw/ppc/vof.c
@@ -0,0 +1,1053 @@
+/*
+ * QEMU PowerPC Virtual Open Firmware.
+ *
+ * This implements client interface from OpenFirmware IEEE1275 on the QEMU
+ * side to leave only a very basic firmware in the VM.
+ *
+ * Copyright (c) 2021 IBM Corporation.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/timer.h"
+#include "qemu/range.h"
+#include "qemu/units.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "exec/ram_addr.h"
+#include "exec/address-spaces.h"
+#include "hw/ppc/vof.h"
+#include "hw/ppc/fdt.h"
+#include "sysemu/runstate.h"
+#include "qom/qom-qobject.h"
+#include "trace.h"
+
+#include <libfdt.h>
+
+/*
+ * OF 1275 "nextprop" description suggests is it 32 bytes max but
+ * LoPAPR defines "ibm,query-interrupt-source-number" which is 33 chars long.
+ */
+#define OF_PROPNAME_LEN_MAX 64
+
+#define VOF_MAX_PATH 256
+#define VOF_MAX_SETPROPLEN 2048
+#define VOF_MAX_METHODLEN 256
+#define VOF_MAX_FORTHCODE 256
+#define VOF_VTY_BUF_SIZE 256
+
+typedef struct {
+ uint64_t start;
+ uint64_t size;
+} OfClaimed;
+
+typedef struct {
+ char *path; /* the path used to open the instance */
+ uint32_t phandle;
+} OfInstance;
+
+static int readstr(hwaddr pa, char *buf, int size)
+{
+ if (VOF_MEM_READ(pa, buf, size) != MEMTX_OK) {
+ return -1;
+ }
+ if (strnlen(buf, size) == size) {
+ buf[size - 1] = '\0';
+ trace_vof_error_str_truncated(buf, size);
+ return -1;
+ }
+ return 0;
+}
+
+static bool cmpservice(const char *s, unsigned nargs, unsigned nret,
+ const char *s1, unsigned nargscheck, unsigned nretcheck)
+{
+ if (strcmp(s, s1)) {
+ return false;
+ }
+ if ((nargscheck && (nargs != nargscheck)) ||
+ (nretcheck && (nret != nretcheck))) {
+ trace_vof_error_param(s, nargscheck, nretcheck, nargs, nret);
+ return false;
+ }
+
+ return true;
+}
+
+static void prop_format(char *tval, int tlen, const void *prop, int len)
+{
+ int i;
+ const unsigned char *c;
+ char *t;
+ const char bin[] = "...";
+
+ for (i = 0, c = prop; i < len; ++i, ++c) {
+ if (*c == '\0' && i == len - 1) {
+ strncpy(tval, prop, tlen - 1);
+ return;
+ }
+ if (*c < 0x20 || *c >= 0x80) {
+ break;
+ }
+ }
+
+ for (i = 0, c = prop, t = tval; i < len; ++i, ++c) {
+ if (t >= tval + tlen - sizeof(bin) - 1 - 2 - 1) {
+ strcpy(t, bin);
+ return;
+ }
+ if (i && i % 4 == 0 && i != len - 1) {
+ strcat(t, " ");
+ ++t;
+ }
+ t += sprintf(t, "%02X", *c & 0xFF);
+ }
+}
+
+static int get_path(const void *fdt, int offset, char *buf, int len)
+{
+ int ret;
+
+ ret = fdt_get_path(fdt, offset, buf, len - 1);
+ if (ret < 0) {
+ return ret;
+ }
+
+ buf[len - 1] = '\0';
+
+ return strlen(buf) + 1;
+}
+
+static int phandle_to_path(const void *fdt, uint32_t ph, char *buf, int len)
+{
+ int ret;
+
+ ret = fdt_node_offset_by_phandle(fdt, ph);
+ if (ret < 0) {
+ return ret;
+ }
+
+ return get_path(fdt, ret, buf, len);
+}
+
+static int path_offset(const void *fdt, const char *path)
+{
+ g_autofree char *p = NULL;
+ char *at;
+
+ /*
+ * https://www.devicetree.org/open-firmware/bindings/ppc/release/ppc-2_1.html#HDR16
+ *
+ * "Conversion from numeric representation to text representation shall use
+ * the lower case forms of the hexadecimal digits in the range a..f,
+ * suppressing leading zeros".
+ */
+ p = g_strdup(path);
+ for (at = strchr(p, '@'); at && *at; ) {
+ if (*at == '/') {
+ at = strchr(at, '@');
+ } else {
+ *at = tolower(*at);
+ ++at;
+ }
+ }
+
+ return fdt_path_offset(fdt, p);
+}
+
+static uint32_t vof_finddevice(const void *fdt, uint32_t nodeaddr)
+{
+ char fullnode[VOF_MAX_PATH];
+ uint32_t ret = -1;
+ int offset;
+
+ if (readstr(nodeaddr, fullnode, sizeof(fullnode))) {
+ return (uint32_t) ret;
+ }
+
+ offset = path_offset(fdt, fullnode);
+ if (offset >= 0) {
+ ret = fdt_get_phandle(fdt, offset);
+ }
+ trace_vof_finddevice(fullnode, ret);
+ return (uint32_t) ret;
+}
+
+static const void *getprop(const void *fdt, int nodeoff, const char *propname,
+ int *proplen, bool *write0)
+{
+ const char *unit, *prop;
+ const void *ret = fdt_getprop(fdt, nodeoff, propname, proplen);
+
+ if (ret) {
+ if (write0) {
+ *write0 = false;
+ }
+ return ret;
+ }
+
+ if (strcmp(propname, "name")) {
+ return NULL;
+ }
+ /*
+ * We return a value for "name" from path if queried but property does not
+ * exist. @proplen does not include the unit part in this case.
+ */
+ prop = fdt_get_name(fdt, nodeoff, proplen);
+ if (!prop) {
+ *proplen = 0;
+ return NULL;
+ }
+
+ unit = memchr(prop, '@', *proplen);
+ if (unit) {
+ *proplen = unit - prop;
+ }
+ *proplen += 1;
+
+ /*
+ * Since it might be cut at "@" and there will be no trailing zero
+ * in the prop buffer, tell the caller to write zero at the end.
+ */
+ if (write0) {
+ *write0 = true;
+ }
+ return prop;
+}
+
+static uint32_t vof_getprop(const void *fdt, uint32_t nodeph, uint32_t pname,
+ uint32_t valaddr, uint32_t vallen)
+{
+ char propname[OF_PROPNAME_LEN_MAX + 1];
+ uint32_t ret = 0;
+ int proplen = 0;
+ const void *prop;
+ char trval[64] = "";
+ int nodeoff = fdt_node_offset_by_phandle(fdt, nodeph);
+ bool write0;
+
+ if (nodeoff < 0) {
+ return -1;
+ }
+ if (readstr(pname, propname, sizeof(propname))) {
+ return -1;
+ }
+ prop = getprop(fdt, nodeoff, propname, &proplen, &write0);
+ if (prop) {
+ const char zero = 0;
+ int cb = MIN(proplen, vallen);
+
+ if (VOF_MEM_WRITE(valaddr, prop, cb) != MEMTX_OK ||
+ /* if that was "name" with a unit address, overwrite '@' with '0' */
+ (write0 &&
+ cb == proplen &&
+ VOF_MEM_WRITE(valaddr + cb - 1, &zero, 1) != MEMTX_OK)) {
+ ret = -1;
+ } else {
+ /*
+ * OF1275 says:
+ * "Size is either the actual size of the property, or -1 if name
+ * does not exist", hence returning proplen instead of cb.
+ */
+ ret = proplen;
+ /* Do not format a value if tracepoint is silent, for performance */
+ if (trace_event_get_state(TRACE_VOF_GETPROP) &&
+ qemu_loglevel_mask(LOG_TRACE)) {
+ prop_format(trval, sizeof(trval), prop, ret);
+ }
+ }
+ } else {
+ ret = -1;
+ }
+ trace_vof_getprop(nodeph, propname, ret, trval);
+
+ return ret;
+}
+
+static uint32_t vof_getproplen(const void *fdt, uint32_t nodeph, uint32_t pname)
+{
+ char propname[OF_PROPNAME_LEN_MAX + 1];
+ uint32_t ret = 0;
+ int proplen = 0;
+ const void *prop;
+ int nodeoff = fdt_node_offset_by_phandle(fdt, nodeph);
+
+ if (nodeoff < 0) {
+ return -1;
+ }
+ if (readstr(pname, propname, sizeof(propname))) {
+ return -1;
+ }
+ prop = getprop(fdt, nodeoff, propname, &proplen, NULL);
+ if (prop) {
+ ret = proplen;
+ } else {
+ ret = -1;
+ }
+ trace_vof_getproplen(nodeph, propname, ret);
+
+ return ret;
+}
+
+static uint32_t vof_setprop(MachineState *ms, void *fdt, Vof *vof,
+ uint32_t nodeph, uint32_t pname,
+ uint32_t valaddr, uint32_t vallen)
+{
+ char propname[OF_PROPNAME_LEN_MAX + 1];
+ uint32_t ret = -1;
+ int offset;
+ char trval[64] = "";
+ char nodepath[VOF_MAX_PATH] = "";
+ Object *vmo = object_dynamic_cast(OBJECT(ms), TYPE_VOF_MACHINE_IF);
+ VofMachineIfClass *vmc;
+ g_autofree char *val = NULL;
+
+ if (vallen > VOF_MAX_SETPROPLEN) {
+ goto trace_exit;
+ }
+ if (readstr(pname, propname, sizeof(propname))) {
+ goto trace_exit;
+ }
+ offset = fdt_node_offset_by_phandle(fdt, nodeph);
+ if (offset < 0) {
+ goto trace_exit;
+ }
+ ret = get_path(fdt, offset, nodepath, sizeof(nodepath));
+ if (ret <= 0) {
+ goto trace_exit;
+ }
+
+ val = g_malloc0(vallen);
+ if (VOF_MEM_READ(valaddr, val, vallen) != MEMTX_OK) {
+ goto trace_exit;
+ }
+
+ if (!vmo) {
+ goto trace_exit;
+ }
+
+ vmc = VOF_MACHINE_GET_CLASS(vmo);
+ if (!vmc->setprop || !vmc->setprop(ms, nodepath, propname, val, vallen)) {
+ goto trace_exit;
+ }
+
+ ret = fdt_setprop(fdt, offset, propname, val, vallen);
+ if (ret) {
+ goto trace_exit;
+ }
+
+ if (trace_event_get_state(TRACE_VOF_SETPROP) &&
+ qemu_loglevel_mask(LOG_TRACE)) {
+ prop_format(trval, sizeof(trval), val, vallen);
+ }
+ ret = vallen;
+
+trace_exit:
+ trace_vof_setprop(nodeph, propname, trval, vallen, ret);
+
+ return ret;
+}
+
+static uint32_t vof_nextprop(const void *fdt, uint32_t phandle,
+ uint32_t prevaddr, uint32_t nameaddr)
+{
+ int offset, nodeoff = fdt_node_offset_by_phandle(fdt, phandle);
+ char prev[OF_PROPNAME_LEN_MAX + 1];
+ const char *tmp;
+
+ if (readstr(prevaddr, prev, sizeof(prev))) {
+ return -1;
+ }
+
+ fdt_for_each_property_offset(offset, fdt, nodeoff) {
+ if (!fdt_getprop_by_offset(fdt, offset, &tmp, NULL)) {
+ return 0;
+ }
+ if (prev[0] == '\0' || strcmp(prev, tmp) == 0) {
+ if (prev[0] != '\0') {
+ offset = fdt_next_property_offset(fdt, offset);
+ if (offset < 0) {
+ return 0;
+ }
+ }
+ if (!fdt_getprop_by_offset(fdt, offset, &tmp, NULL)) {
+ return 0;
+ }
+
+ if (VOF_MEM_WRITE(nameaddr, tmp, strlen(tmp) + 1) != MEMTX_OK) {
+ return -1;
+ }
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static uint32_t vof_peer(const void *fdt, uint32_t phandle)
+{
+ int ret;
+
+ if (phandle == 0) {
+ ret = fdt_path_offset(fdt, "/");
+ } else {
+ ret = fdt_next_subnode(fdt, fdt_node_offset_by_phandle(fdt, phandle));
+ }
+
+ if (ret < 0) {
+ ret = 0;
+ } else {
+ ret = fdt_get_phandle(fdt, ret);
+ }
+
+ return ret;
+}
+
+static uint32_t vof_child(const void *fdt, uint32_t phandle)
+{
+ int ret = fdt_first_subnode(fdt, fdt_node_offset_by_phandle(fdt, phandle));
+
+ if (ret < 0) {
+ ret = 0;
+ } else {
+ ret = fdt_get_phandle(fdt, ret);
+ }
+
+ return ret;
+}
+
+static uint32_t vof_parent(const void *fdt, uint32_t phandle)
+{
+ int ret = fdt_parent_offset(fdt, fdt_node_offset_by_phandle(fdt, phandle));
+
+ if (ret < 0) {
+ ret = 0;
+ } else {
+ ret = fdt_get_phandle(fdt, ret);
+ }
+
+ return ret;
+}
+
+static uint32_t vof_do_open(void *fdt, Vof *vof, int offset, const char *path)
+{
+ uint32_t ret = -1;
+ OfInstance *inst = NULL;
+
+ if (vof->of_instance_last == 0xFFFFFFFF) {
+ /* We do not recycle ihandles yet */
+ goto trace_exit;
+ }
+
+ inst = g_new0(OfInstance, 1);
+ inst->phandle = fdt_get_phandle(fdt, offset);
+ g_assert(inst->phandle);
+ ++vof->of_instance_last;
+
+ inst->path = g_strdup(path);
+ g_hash_table_insert(vof->of_instances,
+ GINT_TO_POINTER(vof->of_instance_last),
+ inst);
+ ret = vof->of_instance_last;
+
+trace_exit:
+ trace_vof_open(path, inst ? inst->phandle : 0, ret);
+
+ return ret;
+}
+
+uint32_t vof_client_open_store(void *fdt, Vof *vof, const char *nodename,
+ const char *prop, const char *path)
+{
+ int node = fdt_path_offset(fdt, nodename);
+ int inst, offset;
+
+ offset = fdt_path_offset(fdt, path);
+ if (offset < 0) {
+ trace_vof_error_unknown_path(path);
+ return offset;
+ }
+
+ inst = vof_do_open(fdt, vof, offset, path);
+
+ return fdt_setprop_cell(fdt, node, prop, inst);
+}
+
+static uint32_t vof_open(void *fdt, Vof *vof, uint32_t pathaddr)
+{
+ char path[VOF_MAX_PATH];
+ int offset;
+
+ if (readstr(pathaddr, path, sizeof(path))) {
+ return -1;
+ }
+
+ offset = path_offset(fdt, path);
+ if (offset < 0) {
+ trace_vof_error_unknown_path(path);
+ return offset;
+ }
+
+ return vof_do_open(fdt, vof, offset, path);
+}
+
+static void vof_close(Vof *vof, uint32_t ihandle)
+{
+ if (!g_hash_table_remove(vof->of_instances, GINT_TO_POINTER(ihandle))) {
+ trace_vof_error_unknown_ihandle_close(ihandle);
+ }
+}
+
+static uint32_t vof_instance_to_package(Vof *vof, uint32_t ihandle)
+{
+ gpointer instp = g_hash_table_lookup(vof->of_instances,
+ GINT_TO_POINTER(ihandle));
+ uint32_t ret = -1;
+
+ if (instp) {
+ ret = ((OfInstance *)instp)->phandle;
+ }
+ trace_vof_instance_to_package(ihandle, ret);
+
+ return ret;
+}
+
+static uint32_t vof_package_to_path(const void *fdt, uint32_t phandle,
+ uint32_t buf, uint32_t len)
+{
+ uint32_t ret = -1;
+ char tmp[VOF_MAX_PATH] = "";
+
+ ret = phandle_to_path(fdt, phandle, tmp, sizeof(tmp));
+ if (ret > 0) {
+ if (VOF_MEM_WRITE(buf, tmp, ret) != MEMTX_OK) {
+ ret = -1;
+ }
+ }
+
+ trace_vof_package_to_path(phandle, tmp, ret);
+
+ return ret;
+}
+
+static uint32_t vof_instance_to_path(void *fdt, Vof *vof, uint32_t ihandle,
+ uint32_t buf, uint32_t len)
+{
+ uint32_t ret = -1;
+ uint32_t phandle = vof_instance_to_package(vof, ihandle);
+ char tmp[VOF_MAX_PATH] = "";
+
+ if (phandle != -1) {
+ ret = phandle_to_path(fdt, phandle, tmp, sizeof(tmp));
+ if (ret > 0) {
+ if (VOF_MEM_WRITE(buf, tmp, ret) != MEMTX_OK) {
+ ret = -1;
+ }
+ }
+ }
+ trace_vof_instance_to_path(ihandle, phandle, tmp, ret);
+
+ return ret;
+}
+
+static uint32_t vof_write(Vof *vof, uint32_t ihandle, uint32_t buf,
+ uint32_t len)
+{
+ char tmp[VOF_VTY_BUF_SIZE];
+ unsigned cb;
+ OfInstance *inst = (OfInstance *)
+ g_hash_table_lookup(vof->of_instances, GINT_TO_POINTER(ihandle));
+
+ if (!inst) {
+ trace_vof_error_write(ihandle);
+ return -1;
+ }
+
+ for ( ; len > 0; len -= cb) {
+ cb = MIN(len, sizeof(tmp) - 1);
+ if (VOF_MEM_READ(buf, tmp, cb) != MEMTX_OK) {
+ return -1;
+ }
+
+ /* FIXME: there is no backend(s) yet so just call a trace */
+ if (trace_event_get_state(TRACE_VOF_WRITE) &&
+ qemu_loglevel_mask(LOG_TRACE)) {
+ tmp[cb] = '\0';
+ trace_vof_write(ihandle, cb, tmp);
+ }
+ }
+
+ return len;
+}
+
+static void vof_claimed_dump(GArray *claimed)
+{
+ int i;
+ OfClaimed c;
+
+ if (trace_event_get_state(TRACE_VOF_CLAIMED) &&
+ qemu_loglevel_mask(LOG_TRACE)) {
+
+ for (i = 0; i < claimed->len; ++i) {
+ c = g_array_index(claimed, OfClaimed, i);
+ trace_vof_claimed(c.start, c.start + c.size, c.size);
+ }
+ }
+}
+
+static bool vof_claim_avail(GArray *claimed, uint64_t virt, uint64_t size)
+{
+ int i;
+ OfClaimed c;
+
+ for (i = 0; i < claimed->len; ++i) {
+ c = g_array_index(claimed, OfClaimed, i);
+ if (ranges_overlap(c.start, c.size, virt, size)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void vof_claim_add(GArray *claimed, uint64_t virt, uint64_t size)
+{
+ OfClaimed newclaim;
+
+ newclaim.start = virt;
+ newclaim.size = size;
+ g_array_append_val(claimed, newclaim);
+}
+
+static gint of_claimed_compare_func(gconstpointer a, gconstpointer b)
+{
+ return ((OfClaimed *)a)->start - ((OfClaimed *)b)->start;
+}
+
+static void vof_dt_memory_available(void *fdt, GArray *claimed, uint64_t base)
+{
+ int i, n, offset, proplen = 0, sc, ac;
+ target_ulong mem0_end;
+ const uint8_t *mem0_reg;
+ g_autofree uint8_t *avail = NULL;
+ uint8_t *availcur;
+
+ if (!fdt || !claimed) {
+ return;
+ }
+
+ offset = fdt_path_offset(fdt, "/");
+ _FDT(offset);
+ ac = fdt_address_cells(fdt, offset);
+ g_assert(ac == 1 || ac == 2);
+ sc = fdt_size_cells(fdt, offset);
+ g_assert(sc == 1 || sc == 2);
+
+ offset = fdt_path_offset(fdt, "/memory@0");
+ _FDT(offset);
+
+ mem0_reg = fdt_getprop(fdt, offset, "reg", &proplen);
+ g_assert(mem0_reg && proplen == sizeof(uint32_t) * (ac + sc));
+ if (sc == 2) {
+ mem0_end = be64_to_cpu(*(uint64_t *)(mem0_reg + sizeof(uint32_t) * ac));
+ } else {
+ mem0_end = be32_to_cpu(*(uint32_t *)(mem0_reg + sizeof(uint32_t) * ac));
+ }
+
+ g_array_sort(claimed, of_claimed_compare_func);
+ vof_claimed_dump(claimed);
+
+ /*
+ * VOF resides in the first page so we do not need to check if there is
+ * available memory before the first claimed block
+ */
+ g_assert(claimed->len && (g_array_index(claimed, OfClaimed, 0).start == 0));
+
+ avail = g_malloc0(sizeof(uint32_t) * (ac + sc) * claimed->len);
+ for (i = 0, n = 0, availcur = avail; i < claimed->len; ++i) {
+ OfClaimed c = g_array_index(claimed, OfClaimed, i);
+ uint64_t start, size;
+
+ start = c.start + c.size;
+ if (i < claimed->len - 1) {
+ OfClaimed cn = g_array_index(claimed, OfClaimed, i + 1);
+
+ size = cn.start - start;
+ } else {
+ size = mem0_end - start;
+ }
+
+ if (ac == 2) {
+ *(uint64_t *) availcur = cpu_to_be64(start);
+ } else {
+ *(uint32_t *) availcur = cpu_to_be32(start);
+ }
+ availcur += sizeof(uint32_t) * ac;
+ if (sc == 2) {
+ *(uint64_t *) availcur = cpu_to_be64(size);
+ } else {
+ *(uint32_t *) availcur = cpu_to_be32(size);
+ }
+ availcur += sizeof(uint32_t) * sc;
+
+ if (size) {
+ trace_vof_avail(c.start + c.size, c.start + c.size + size, size);
+ ++n;
+ }
+ }
+ _FDT((fdt_setprop(fdt, offset, "available", avail, availcur - avail)));
+}
+
+/*
+ * OF1275:
+ * "Allocates size bytes of memory. If align is zero, the allocated range
+ * begins at the virtual address virt. Otherwise, an aligned address is
+ * automatically chosen and the input argument virt is ignored".
+ *
+ * In other words, exactly one of @virt and @align is non-zero.
+ */
+uint64_t vof_claim(Vof *vof, uint64_t virt, uint64_t size,
+ uint64_t align)
+{
+ uint64_t ret;
+
+ if (size == 0) {
+ ret = -1;
+ } else if (align == 0) {
+ if (!vof_claim_avail(vof->claimed, virt, size)) {
+ ret = -1;
+ } else {
+ ret = virt;
+ }
+ } else {
+ vof->claimed_base = QEMU_ALIGN_UP(vof->claimed_base, align);
+ while (1) {
+ if (vof->claimed_base >= vof->top_addr) {
+ error_report("Out of RMA memory for the OF client");
+ return -1;
+ }
+ if (vof_claim_avail(vof->claimed, vof->claimed_base, size)) {
+ break;
+ }
+ vof->claimed_base += size;
+ }
+ ret = vof->claimed_base;
+ }
+
+ if (ret != -1) {
+ vof->claimed_base = MAX(vof->claimed_base, ret + size);
+ vof_claim_add(vof->claimed, ret, size);
+ }
+ trace_vof_claim(virt, size, align, ret);
+
+ return ret;
+}
+
+static uint32_t vof_release(Vof *vof, uint64_t virt, uint64_t size)
+{
+ uint32_t ret = -1;
+ int i;
+ GArray *claimed = vof->claimed;
+ OfClaimed c;
+
+ for (i = 0; i < claimed->len; ++i) {
+ c = g_array_index(claimed, OfClaimed, i);
+ if (c.start == virt && c.size == size) {
+ g_array_remove_index(claimed, i);
+ ret = 0;
+ break;
+ }
+ }
+
+ trace_vof_release(virt, size, ret);
+
+ return ret;
+}
+
+static void vof_instantiate_rtas(Error **errp)
+{
+ error_setg(errp, "The firmware should have instantiated RTAS");
+}
+
+static uint32_t vof_call_method(MachineState *ms, Vof *vof, uint32_t methodaddr,
+ uint32_t ihandle, uint32_t param1,
+ uint32_t param2, uint32_t param3,
+ uint32_t param4, uint32_t *ret2)
+{
+ uint32_t ret = -1;
+ char method[VOF_MAX_METHODLEN] = "";
+ OfInstance *inst;
+
+ if (!ihandle) {
+ goto trace_exit;
+ }
+
+ inst = (OfInstance *)g_hash_table_lookup(vof->of_instances,
+ GINT_TO_POINTER(ihandle));
+ if (!inst) {
+ goto trace_exit;
+ }
+
+ if (readstr(methodaddr, method, sizeof(method))) {
+ goto trace_exit;
+ }
+
+ if (strcmp(inst->path, "/") == 0) {
+ if (strcmp(method, "ibm,client-architecture-support") == 0) {
+ Object *vmo = object_dynamic_cast(OBJECT(ms), TYPE_VOF_MACHINE_IF);
+
+ if (vmo) {
+ VofMachineIfClass *vmc = VOF_MACHINE_GET_CLASS(vmo);
+
+ g_assert(vmc->client_architecture_support);
+ ret = vmc->client_architecture_support(ms, first_cpu, param1);
+ }
+
+ *ret2 = 0;
+ }
+ } else if (strcmp(inst->path, "/rtas") == 0) {
+ if (strcmp(method, "instantiate-rtas") == 0) {
+ vof_instantiate_rtas(&error_fatal);
+ ret = 0;
+ *ret2 = param1; /* rtas-base */
+ }
+ } else {
+ trace_vof_error_unknown_method(method);
+ }
+
+trace_exit:
+ trace_vof_method(ihandle, method, param1, ret, *ret2);
+
+ return ret;
+}
+
+static uint32_t vof_call_interpret(uint32_t cmdaddr, uint32_t param1,
+ uint32_t param2, uint32_t *ret2)
+{
+ uint32_t ret = -1;
+ char cmd[VOF_MAX_FORTHCODE] = "";
+
+ /* No interpret implemented so just call a trace */
+ readstr(cmdaddr, cmd, sizeof(cmd));
+ trace_vof_interpret(cmd, param1, param2, ret, *ret2);
+
+ return ret;
+}
+
+static void vof_quiesce(MachineState *ms, void *fdt, Vof *vof)
+{
+ Object *vmo = object_dynamic_cast(OBJECT(ms), TYPE_VOF_MACHINE_IF);
+ /* After "quiesce", no change is expected to the FDT, pack FDT to ensure */
+ int rc = fdt_pack(fdt);
+
+ assert(rc == 0);
+
+ if (vmo) {
+ VofMachineIfClass *vmc = VOF_MACHINE_GET_CLASS(vmo);
+
+ if (vmc->quiesce) {
+ vmc->quiesce(ms);
+ }
+ }
+
+ vof_claimed_dump(vof->claimed);
+}
+
+static uint32_t vof_client_handle(MachineState *ms, void *fdt, Vof *vof,
+ const char *service,
+ uint32_t *args, unsigned nargs,
+ uint32_t *rets, unsigned nrets)
+{
+ uint32_t ret = 0;
+
+ /* @nrets includes the value which this function returns */
+#define cmpserv(s, a, r) \
+ cmpservice(service, nargs, nrets, (s), (a), (r))
+
+ if (cmpserv("finddevice", 1, 1)) {
+ ret = vof_finddevice(fdt, args[0]);
+ } else if (cmpserv("getprop", 4, 1)) {
+ ret = vof_getprop(fdt, args[0], args[1], args[2], args[3]);
+ } else if (cmpserv("getproplen", 2, 1)) {
+ ret = vof_getproplen(fdt, args[0], args[1]);
+ } else if (cmpserv("setprop", 4, 1)) {
+ ret = vof_setprop(ms, fdt, vof, args[0], args[1], args[2], args[3]);
+ } else if (cmpserv("nextprop", 3, 1)) {
+ ret = vof_nextprop(fdt, args[0], args[1], args[2]);
+ } else if (cmpserv("peer", 1, 1)) {
+ ret = vof_peer(fdt, args[0]);
+ } else if (cmpserv("child", 1, 1)) {
+ ret = vof_child(fdt, args[0]);
+ } else if (cmpserv("parent", 1, 1)) {
+ ret = vof_parent(fdt, args[0]);
+ } else if (cmpserv("open", 1, 1)) {
+ ret = vof_open(fdt, vof, args[0]);
+ } else if (cmpserv("close", 1, 0)) {
+ vof_close(vof, args[0]);
+ } else if (cmpserv("instance-to-package", 1, 1)) {
+ ret = vof_instance_to_package(vof, args[0]);
+ } else if (cmpserv("package-to-path", 3, 1)) {
+ ret = vof_package_to_path(fdt, args[0], args[1], args[2]);
+ } else if (cmpserv("instance-to-path", 3, 1)) {
+ ret = vof_instance_to_path(fdt, vof, args[0], args[1], args[2]);
+ } else if (cmpserv("write", 3, 1)) {
+ ret = vof_write(vof, args[0], args[1], args[2]);
+ } else if (cmpserv("claim", 3, 1)) {
+ ret = vof_claim(vof, args[0], args[1], args[2]);
+ if (ret != -1) {
+ vof_dt_memory_available(fdt, vof->claimed, vof->claimed_base);
+ }
+ } else if (cmpserv("release", 2, 0)) {
+ ret = vof_release(vof, args[0], args[1]);
+ if (ret != -1) {
+ vof_dt_memory_available(fdt, vof->claimed, vof->claimed_base);
+ }
+ } else if (cmpserv("call-method", 0, 0)) {
+ ret = vof_call_method(ms, vof, args[0], args[1], args[2], args[3],
+ args[4], args[5], rets);
+ } else if (cmpserv("interpret", 0, 0)) {
+ ret = vof_call_interpret(args[0], args[1], args[2], rets);
+ } else if (cmpserv("milliseconds", 0, 1)) {
+ ret = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
+ } else if (cmpserv("quiesce", 0, 0)) {
+ vof_quiesce(ms, fdt, vof);
+ } else if (cmpserv("exit", 0, 0)) {
+ error_report("Stopped as the VM requested \"exit\"");
+ vm_stop(RUN_STATE_PAUSED);
+ } else {
+ trace_vof_error_unknown_service(service, nargs, nrets);
+ ret = -1;
+ }
+
+#undef cmpserv
+
+ return ret;
+}
+
+/* Defined as Big Endian */
+struct prom_args {
+ uint32_t service;
+ uint32_t nargs;
+ uint32_t nret;
+ uint32_t args[10];
+} QEMU_PACKED;
+
+int vof_client_call(MachineState *ms, Vof *vof, void *fdt,
+ target_ulong args_real)
+{
+ struct prom_args args_be;
+ uint32_t args[ARRAY_SIZE(args_be.args)];
+ uint32_t rets[ARRAY_SIZE(args_be.args)] = { 0 }, ret;
+ char service[64];
+ unsigned nargs, nret, i;
+
+ if (VOF_MEM_READ(args_real, &args_be, sizeof(args_be)) != MEMTX_OK) {
+ return -EINVAL;
+ }
+ nargs = be32_to_cpu(args_be.nargs);
+ if (nargs >= ARRAY_SIZE(args_be.args)) {
+ return -EINVAL;
+ }
+
+ if (VOF_MEM_READ(be32_to_cpu(args_be.service), service, sizeof(service)) !=
+ MEMTX_OK) {
+ return -EINVAL;
+ }
+ if (strnlen(service, sizeof(service)) == sizeof(service)) {
+ /* Too long service name */
+ return -EINVAL;
+ }
+
+ for (i = 0; i < nargs; ++i) {
+ args[i] = be32_to_cpu(args_be.args[i]);
+ }
+
+ nret = be32_to_cpu(args_be.nret);
+ ret = vof_client_handle(ms, fdt, vof, service, args, nargs, rets, nret);
+ if (!nret) {
+ return 0;
+ }
+
+ args_be.args[nargs] = cpu_to_be32(ret);
+ for (i = 1; i < nret; ++i) {
+ args_be.args[nargs + i] = cpu_to_be32(rets[i - 1]);
+ }
+
+ if (VOF_MEM_WRITE(args_real + offsetof(struct prom_args, args[nargs]),
+ args_be.args + nargs, sizeof(args_be.args[0]) * nret) !=
+ MEMTX_OK) {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void vof_instance_free(gpointer data)
+{
+ OfInstance *inst = (OfInstance *)data;
+
+ g_free(inst->path);
+ g_free(inst);
+}
+
+void vof_init(Vof *vof, uint64_t top_addr, Error **errp)
+{
+ vof_cleanup(vof);
+
+ vof->of_instances = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+ NULL, vof_instance_free);
+ vof->claimed = g_array_new(false, false, sizeof(OfClaimed));
+
+ /* Keep allocations in 32bit as CLI ABI can only return cells==32bit */
+ vof->top_addr = MIN(top_addr, 4 * GiB);
+ if (vof_claim(vof, 0, vof->fw_size, 0) == -1) {
+ error_setg(errp, "Memory for firmware is in use");
+ }
+}
+
+void vof_cleanup(Vof *vof)
+{
+ if (vof->claimed) {
+ g_array_unref(vof->claimed);
+ }
+ if (vof->of_instances) {
+ g_hash_table_unref(vof->of_instances);
+ }
+ vof->claimed = NULL;
+ vof->of_instances = NULL;
+}
+
+void vof_build_dt(void *fdt, Vof *vof)
+{
+ uint32_t phandle = fdt_get_max_phandle(fdt);
+ int offset, proplen = 0;
+ const void *prop;
+
+ /* Assign phandles to nodes without predefined phandles (like XICS/XIVE) */
+ for (offset = fdt_next_node(fdt, -1, NULL);
+ offset >= 0;
+ offset = fdt_next_node(fdt, offset, NULL)) {
+ prop = fdt_getprop(fdt, offset, "phandle", &proplen);
+ if (prop) {
+ continue;
+ }
+ ++phandle;
+ _FDT(fdt_setprop_cell(fdt, offset, "phandle", phandle));
+ }
+
+ vof_dt_memory_available(fdt, vof->claimed, vof->claimed_base);
+}
+
+static const TypeInfo vof_machine_if_info = {
+ .name = TYPE_VOF_MACHINE_IF,
+ .parent = TYPE_INTERFACE,
+ .class_size = sizeof(VofMachineIfClass),
+};
+
+static void vof_machine_if_register_types(void)
+{
+ type_register_static(&vof_machine_if_info);
+}
+type_init(vof_machine_if_register_types)
diff --git a/hw/s390x/virtio-ccw-gpu.c b/hw/s390x/virtio-ccw-gpu.c
index 75a9e4bb39..5868a2a070 100644
--- a/hw/s390x/virtio-ccw-gpu.c
+++ b/hw/s390x/virtio-ccw-gpu.c
@@ -59,6 +59,7 @@ static const TypeInfo virtio_ccw_gpu = {
.instance_init = virtio_ccw_gpu_instance_init,
.class_init = virtio_ccw_gpu_class_init,
};
+module_obj(TYPE_VIRTIO_GPU_CCW);
static void virtio_ccw_gpu_register(void)
{
@@ -68,3 +69,5 @@ static void virtio_ccw_gpu_register(void)
}
type_init(virtio_ccw_gpu_register)
+
+module_arch("s390x");
diff --git a/hw/s390x/virtio-ccw.c b/hw/s390x/virtio-ccw.c
index d68888fccd..6a2df1c1e9 100644
--- a/hw/s390x/virtio-ccw.c
+++ b/hw/s390x/virtio-ccw.c
@@ -31,6 +31,7 @@
#include "trace.h"
#include "hw/s390x/css-bridge.h"
#include "hw/s390x/s390-virtio-ccw.h"
+#include "sysemu/replay.h"
#define NR_CLASSIC_INDICATOR_BITS 64
@@ -770,6 +771,11 @@ static void virtio_ccw_device_realize(VirtioCcwDevice *dev, Error **errp)
dev->flags &= ~VIRTIO_CCW_FLAG_USE_IOEVENTFD;
}
+ /* fd-based ioevents can't be synchronized in record/replay */
+ if (replay_mode != REPLAY_MODE_NONE) {
+ dev->flags &= ~VIRTIO_CCW_FLAG_USE_IOEVENTFD;
+ }
+
if (k->realize) {
k->realize(dev, &err);
if (err) {
diff --git a/hw/scsi/virtio-scsi-dataplane.c b/hw/scsi/virtio-scsi-dataplane.c
index 28e003250a..18eb824c97 100644
--- a/hw/scsi/virtio-scsi-dataplane.c
+++ b/hw/scsi/virtio-scsi-dataplane.c
@@ -152,6 +152,10 @@ int virtio_scsi_dataplane_start(VirtIODevice *vdev)
goto fail_guest_notifiers;
}
+ /*
+ * Batch all the host notifiers in a single transaction to avoid
+ * quadratic time complexity in address_space_update_ioeventfds().
+ */
memory_region_transaction_begin();
rc = virtio_scsi_set_host_notifier(s, vs->ctrl_vq, 0);
@@ -198,6 +202,10 @@ fail_host_notifiers:
virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
}
+ /*
+ * The transaction expects the ioeventfds to be open when it
+ * commits. Do it now, before the cleanup loop.
+ */
memory_region_transaction_commit();
for (i = 0; i < vq_init_count; i++) {
@@ -238,12 +246,20 @@ void virtio_scsi_dataplane_stop(VirtIODevice *vdev)
blk_drain_all(); /* ensure there are no in-flight requests */
+ /*
+ * Batch all the host notifiers in a single transaction to avoid
+ * quadratic time complexity in address_space_update_ioeventfds().
+ */
memory_region_transaction_begin();
for (i = 0; i < vs->conf.num_queues + 2; i++) {
virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
}
+ /*
+ * The transaction expects the ioeventfds to be open when it
+ * commits. Do it now, before the cleanup loop.
+ */
memory_region_transaction_commit();
for (i = 0; i < vs->conf.num_queues + 2; i++) {
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
new file mode 100644
index 0000000000..a2b55a4fdb
--- /dev/null
+++ b/hw/sensor/Kconfig
@@ -0,0 +1,19 @@
+config TMP105
+ bool
+ depends on I2C
+
+config TMP421
+ bool
+ depends on I2C
+
+config EMC141X
+ bool
+ depends on I2C
+
+config ADM1272
+ bool
+ depends on I2C
+
+config MAX34451
+ bool
+ depends on I2C
diff --git a/hw/sensor/adm1272.c b/hw/sensor/adm1272.c
new file mode 100644
index 0000000000..7310c769be
--- /dev/null
+++ b/hw/sensor/adm1272.c
@@ -0,0 +1,543 @@
+/*
+ * Analog Devices ADM1272 High Voltage Positive Hot Swap Controller and Digital
+ * Power Monitor with PMBus
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include <string.h>
+#include "hw/i2c/pmbus_device.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define TYPE_ADM1272 "adm1272"
+#define ADM1272(obj) OBJECT_CHECK(ADM1272State, (obj), TYPE_ADM1272)
+
+#define ADM1272_RESTART_TIME 0xCC
+#define ADM1272_MFR_PEAK_IOUT 0xD0
+#define ADM1272_MFR_PEAK_VIN 0xD1
+#define ADM1272_MFR_PEAK_VOUT 0xD2
+#define ADM1272_MFR_PMON_CONTROL 0xD3
+#define ADM1272_MFR_PMON_CONFIG 0xD4
+#define ADM1272_MFR_ALERT1_CONFIG 0xD5
+#define ADM1272_MFR_ALERT2_CONFIG 0xD6
+#define ADM1272_MFR_PEAK_TEMPERATURE 0xD7
+#define ADM1272_MFR_DEVICE_CONFIG 0xD8
+#define ADM1272_MFR_POWER_CYCLE 0xD9
+#define ADM1272_MFR_PEAK_PIN 0xDA
+#define ADM1272_MFR_READ_PIN_EXT 0xDB
+#define ADM1272_MFR_READ_EIN_EXT 0xDC
+
+#define ADM1272_HYSTERESIS_LOW 0xF2
+#define ADM1272_HYSTERESIS_HIGH 0xF3
+#define ADM1272_STATUS_HYSTERESIS 0xF4
+#define ADM1272_STATUS_GPIO 0xF5
+#define ADM1272_STRT_UP_IOUT_LIM 0xF6
+
+/* Defaults */
+#define ADM1272_OPERATION_DEFAULT 0x80
+#define ADM1272_CAPABILITY_DEFAULT 0xB0
+#define ADM1272_CAPABILITY_NO_PEC 0x30
+#define ADM1272_DIRECT_MODE 0x40
+#define ADM1272_HIGH_LIMIT_DEFAULT 0x0FFF
+#define ADM1272_PIN_OP_DEFAULT 0x7FFF
+#define ADM1272_PMBUS_REVISION_DEFAULT 0x22
+#define ADM1272_MFR_ID_DEFAULT "ADI"
+#define ADM1272_MODEL_DEFAULT "ADM1272-A1"
+#define ADM1272_MFR_DEFAULT_REVISION "25"
+#define ADM1272_DEFAULT_DATE "160301"
+#define ADM1272_RESTART_TIME_DEFAULT 0x64
+#define ADM1272_PMON_CONTROL_DEFAULT 0x1
+#define ADM1272_PMON_CONFIG_DEFAULT 0x3F35
+#define ADM1272_DEVICE_CONFIG_DEFAULT 0x8
+#define ADM1272_HYSTERESIS_HIGH_DEFAULT 0xFFFF
+#define ADM1272_STRT_UP_IOUT_LIM_DEFAULT 0x000F
+#define ADM1272_VOLT_DEFAULT 12000
+#define ADM1272_IOUT_DEFAULT 25000
+#define ADM1272_PWR_DEFAULT 300 /* 12V 25A */
+#define ADM1272_SHUNT 300 /* micro-ohms */
+#define ADM1272_VOLTAGE_COEFF_DEFAULT 1
+#define ADM1272_CURRENT_COEFF_DEFAULT 3
+#define ADM1272_PWR_COEFF_DEFAULT 7
+#define ADM1272_IOUT_OFFSET 0x5000
+#define ADM1272_IOUT_OFFSET 0x5000
+
+
+typedef struct ADM1272State {
+ PMBusDevice parent;
+
+ uint64_t ein_ext;
+ uint32_t pin_ext;
+ uint8_t restart_time;
+
+ uint16_t peak_vin;
+ uint16_t peak_vout;
+ uint16_t peak_iout;
+ uint16_t peak_temperature;
+ uint16_t peak_pin;
+
+ uint8_t pmon_control;
+ uint16_t pmon_config;
+ uint16_t alert1_config;
+ uint16_t alert2_config;
+ uint16_t device_config;
+
+ uint16_t hysteresis_low;
+ uint16_t hysteresis_high;
+ uint8_t status_hysteresis;
+ uint8_t status_gpio;
+
+ uint16_t strt_up_iout_lim;
+
+} ADM1272State;
+
+static const PMBusCoefficients adm1272_coefficients[] = {
+ [0] = { 6770, 0, -2 }, /* voltage, vrange 60V */
+ [1] = { 4062, 0, -2 }, /* voltage, vrange 100V */
+ [2] = { 1326, 20480, -1 }, /* current, vsense range 15mV */
+ [3] = { 663, 20480, -1 }, /* current, vsense range 30mV */
+ [4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */
+ [5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */
+ [6] = { 17561, 0, -3 }, /* power, vrange 60V, irange 30mV */
+ [7] = { 10535, 0, -3 }, /* power, vrange 100V, irange 30mV */
+ [8] = { 42, 31871, -1 }, /* temperature */
+};
+
+static void adm1272_check_limits(ADM1272State *s)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(s);
+
+ pmbus_check_limits(pmdev);
+
+ if (pmdev->pages[0].read_vout > s->peak_vout) {
+ s->peak_vout = pmdev->pages[0].read_vout;
+ }
+
+ if (pmdev->pages[0].read_vin > s->peak_vin) {
+ s->peak_vin = pmdev->pages[0].read_vin;
+ }
+
+ if (pmdev->pages[0].read_iout > s->peak_iout) {
+ s->peak_iout = pmdev->pages[0].read_iout;
+ }
+
+ if (pmdev->pages[0].read_temperature_1 > s->peak_temperature) {
+ s->peak_temperature = pmdev->pages[0].read_temperature_1;
+ }
+
+ if (pmdev->pages[0].read_pin > s->peak_pin) {
+ s->peak_pin = pmdev->pages[0].read_pin;
+ }
+}
+
+static uint16_t adm1272_millivolts_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_VOLTAGE_COEFF_DEFAULT];
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_millivolts(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_VOLTAGE_COEFF_DEFAULT];
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static uint16_t adm1272_milliamps_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_CURRENT_COEFF_DEFAULT];
+ /* Y = (m * r_sense * x - b) * 10^R */
+ c.m = c.m * ADM1272_SHUNT / 1000; /* micro-ohms */
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_milliamps(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_CURRENT_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static uint16_t adm1272_watts_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_PWR_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_watts(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_PWR_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static void adm1272_exit_reset(Object *obj)
+{
+ ADM1272State *s = ADM1272(obj);
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+
+ pmdev->page = 0;
+ pmdev->pages[0].operation = ADM1272_OPERATION_DEFAULT;
+
+
+ pmdev->capability = ADM1272_CAPABILITY_NO_PEC;
+ pmdev->pages[0].revision = ADM1272_PMBUS_REVISION_DEFAULT;
+ pmdev->pages[0].vout_mode = ADM1272_DIRECT_MODE;
+ pmdev->pages[0].vout_ov_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].vout_uv_warn_limit = 0;
+ pmdev->pages[0].iout_oc_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].ot_fault_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].ot_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].vin_ov_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].vin_uv_warn_limit = 0;
+ pmdev->pages[0].pin_op_warn_limit = ADM1272_PIN_OP_DEFAULT;
+
+ pmdev->pages[0].status_word = 0;
+ pmdev->pages[0].status_vout = 0;
+ pmdev->pages[0].status_iout = 0;
+ pmdev->pages[0].status_input = 0;
+ pmdev->pages[0].status_temperature = 0;
+ pmdev->pages[0].status_mfr_specific = 0;
+
+ pmdev->pages[0].read_vin
+ = adm1272_millivolts_to_direct(ADM1272_VOLT_DEFAULT);
+ pmdev->pages[0].read_vout
+ = adm1272_millivolts_to_direct(ADM1272_VOLT_DEFAULT);
+ pmdev->pages[0].read_iout
+ = adm1272_milliamps_to_direct(ADM1272_IOUT_DEFAULT);
+ pmdev->pages[0].read_temperature_1 = 0;
+ pmdev->pages[0].read_pin = adm1272_watts_to_direct(ADM1272_PWR_DEFAULT);
+ pmdev->pages[0].revision = ADM1272_PMBUS_REVISION_DEFAULT;
+ pmdev->pages[0].mfr_id = ADM1272_MFR_ID_DEFAULT;
+ pmdev->pages[0].mfr_model = ADM1272_MODEL_DEFAULT;
+ pmdev->pages[0].mfr_revision = ADM1272_MFR_DEFAULT_REVISION;
+ pmdev->pages[0].mfr_date = ADM1272_DEFAULT_DATE;
+
+ s->pin_ext = 0;
+ s->ein_ext = 0;
+ s->restart_time = ADM1272_RESTART_TIME_DEFAULT;
+
+ s->peak_vin = 0;
+ s->peak_vout = 0;
+ s->peak_iout = 0;
+ s->peak_temperature = 0;
+ s->peak_pin = 0;
+
+ s->pmon_control = ADM1272_PMON_CONTROL_DEFAULT;
+ s->pmon_config = ADM1272_PMON_CONFIG_DEFAULT;
+ s->alert1_config = 0;
+ s->alert2_config = 0;
+ s->device_config = ADM1272_DEVICE_CONFIG_DEFAULT;
+
+ s->hysteresis_low = 0;
+ s->hysteresis_high = ADM1272_HYSTERESIS_HIGH_DEFAULT;
+ s->status_hysteresis = 0;
+ s->status_gpio = 0;
+
+ s->strt_up_iout_lim = ADM1272_STRT_UP_IOUT_LIM_DEFAULT;
+}
+
+static uint8_t adm1272_read_byte(PMBusDevice *pmdev)
+{
+ ADM1272State *s = ADM1272(pmdev);
+
+ switch (pmdev->code) {
+ case ADM1272_RESTART_TIME:
+ pmbus_send8(pmdev, s->restart_time);
+ break;
+
+ case ADM1272_MFR_PEAK_IOUT:
+ pmbus_send16(pmdev, s->peak_iout);
+ break;
+
+ case ADM1272_MFR_PEAK_VIN:
+ pmbus_send16(pmdev, s->peak_vin);
+ break;
+
+ case ADM1272_MFR_PEAK_VOUT:
+ pmbus_send16(pmdev, s->peak_vout);
+ break;
+
+ case ADM1272_MFR_PMON_CONTROL:
+ pmbus_send8(pmdev, s->pmon_control);
+ break;
+
+ case ADM1272_MFR_PMON_CONFIG:
+ pmbus_send16(pmdev, s->pmon_config);
+ break;
+
+ case ADM1272_MFR_ALERT1_CONFIG:
+ pmbus_send16(pmdev, s->alert1_config);
+ break;
+
+ case ADM1272_MFR_ALERT2_CONFIG:
+ pmbus_send16(pmdev, s->alert2_config);
+ break;
+
+ case ADM1272_MFR_PEAK_TEMPERATURE:
+ pmbus_send16(pmdev, s->peak_temperature);
+ break;
+
+ case ADM1272_MFR_DEVICE_CONFIG:
+ pmbus_send16(pmdev, s->device_config);
+ break;
+
+ case ADM1272_MFR_PEAK_PIN:
+ pmbus_send16(pmdev, s->peak_pin);
+ break;
+
+ case ADM1272_MFR_READ_PIN_EXT:
+ pmbus_send32(pmdev, s->pin_ext);
+ break;
+
+ case ADM1272_MFR_READ_EIN_EXT:
+ pmbus_send64(pmdev, s->ein_ext);
+ break;
+
+ case ADM1272_HYSTERESIS_LOW:
+ pmbus_send16(pmdev, s->hysteresis_low);
+ break;
+
+ case ADM1272_HYSTERESIS_HIGH:
+ pmbus_send16(pmdev, s->hysteresis_high);
+ break;
+
+ case ADM1272_STATUS_HYSTERESIS:
+ pmbus_send16(pmdev, s->status_hysteresis);
+ break;
+
+ case ADM1272_STATUS_GPIO:
+ pmbus_send16(pmdev, s->status_gpio);
+ break;
+
+ case ADM1272_STRT_UP_IOUT_LIM:
+ pmbus_send16(pmdev, s->strt_up_iout_lim);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: reading from unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ return 0xFF;
+ break;
+ }
+
+ return 0;
+}
+
+static int adm1272_write_data(PMBusDevice *pmdev, const uint8_t *buf,
+ uint8_t len)
+{
+ ADM1272State *s = ADM1272(pmdev);
+
+ if (len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
+ return -1;
+ }
+
+ pmdev->code = buf[0]; /* PMBus command code */
+
+ if (len == 1) {
+ return 0;
+ }
+
+ /* Exclude command code from buffer */
+ buf++;
+ len--;
+
+ switch (pmdev->code) {
+
+ case ADM1272_RESTART_TIME:
+ s->restart_time = pmbus_receive8(pmdev);
+ break;
+
+ case ADM1272_MFR_PMON_CONTROL:
+ s->pmon_control = pmbus_receive8(pmdev);
+ break;
+
+ case ADM1272_MFR_PMON_CONFIG:
+ s->pmon_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_ALERT1_CONFIG:
+ s->alert1_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_ALERT2_CONFIG:
+ s->alert2_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_DEVICE_CONFIG:
+ s->device_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_POWER_CYCLE:
+ adm1272_exit_reset((Object *)s);
+ break;
+
+ case ADM1272_HYSTERESIS_LOW:
+ s->hysteresis_low = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_HYSTERESIS_HIGH:
+ s->hysteresis_high = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_STRT_UP_IOUT_LIM:
+ s->strt_up_iout_lim = pmbus_receive16(pmdev);
+ adm1272_check_limits(s);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+ }
+ return 0;
+}
+
+static void adm1272_get(Object *obj, Visitor *v, const char *name, void *opaque,
+ Error **errp)
+{
+ uint16_t value;
+
+ if (strcmp(name, "vin") == 0 || strcmp(name, "vout") == 0) {
+ value = adm1272_direct_to_millivolts(*(uint16_t *)opaque);
+ } else if (strcmp(name, "iout") == 0) {
+ value = adm1272_direct_to_milliamps(*(uint16_t *)opaque);
+ } else if (strcmp(name, "pin") == 0) {
+ value = adm1272_direct_to_watts(*(uint16_t *)opaque);
+ } else {
+ value = *(uint16_t *)opaque;
+ }
+
+ visit_type_uint16(v, name, &value, errp);
+}
+
+static void adm1272_set(Object *obj, Visitor *v, const char *name, void *opaque,
+ Error **errp)
+{
+ ADM1272State *s = ADM1272(obj);
+ uint16_t *internal = opaque;
+ uint16_t value;
+
+ if (!visit_type_uint16(v, name, &value, errp)) {
+ return;
+ }
+
+ if (strcmp(name, "vin") == 0 || strcmp(name, "vout") == 0) {
+ *internal = adm1272_millivolts_to_direct(value);
+ } else if (strcmp(name, "iout") == 0) {
+ *internal = adm1272_milliamps_to_direct(value);
+ } else if (strcmp(name, "pin") == 0) {
+ *internal = adm1272_watts_to_direct(value);
+ } else {
+ *internal = value;
+ }
+
+ adm1272_check_limits(s);
+}
+
+static const VMStateDescription vmstate_adm1272 = {
+ .name = "ADM1272",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]){
+ VMSTATE_PMBUS_DEVICE(parent, ADM1272State),
+ VMSTATE_UINT64(ein_ext, ADM1272State),
+ VMSTATE_UINT32(pin_ext, ADM1272State),
+ VMSTATE_UINT8(restart_time, ADM1272State),
+
+ VMSTATE_UINT16(peak_vin, ADM1272State),
+ VMSTATE_UINT16(peak_vout, ADM1272State),
+ VMSTATE_UINT16(peak_iout, ADM1272State),
+ VMSTATE_UINT16(peak_temperature, ADM1272State),
+ VMSTATE_UINT16(peak_pin, ADM1272State),
+
+ VMSTATE_UINT8(pmon_control, ADM1272State),
+ VMSTATE_UINT16(pmon_config, ADM1272State),
+ VMSTATE_UINT16(alert1_config, ADM1272State),
+ VMSTATE_UINT16(alert2_config, ADM1272State),
+ VMSTATE_UINT16(device_config, ADM1272State),
+
+ VMSTATE_UINT16(hysteresis_low, ADM1272State),
+ VMSTATE_UINT16(hysteresis_high, ADM1272State),
+ VMSTATE_UINT8(status_hysteresis, ADM1272State),
+ VMSTATE_UINT8(status_gpio, ADM1272State),
+
+ VMSTATE_UINT16(strt_up_iout_lim, ADM1272State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void adm1272_init(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ uint64_t flags = PB_HAS_VOUT_MODE | PB_HAS_VOUT | PB_HAS_VIN | PB_HAS_IOUT |
+ PB_HAS_PIN | PB_HAS_TEMPERATURE | PB_HAS_MFR_INFO;
+
+ pmbus_page_config(pmdev, 0, flags);
+
+ object_property_add(obj, "vin", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_vin);
+
+ object_property_add(obj, "vout", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_vout);
+
+ object_property_add(obj, "iout", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_iout);
+
+ object_property_add(obj, "pin", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_pin);
+
+}
+
+static void adm1272_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass);
+
+ dc->desc = "Analog Devices ADM1272 Hot Swap controller";
+ dc->vmsd = &vmstate_adm1272;
+ k->write_data = adm1272_write_data;
+ k->receive_byte = adm1272_read_byte;
+ k->device_num_pages = 1;
+
+ rc->phases.exit = adm1272_exit_reset;
+}
+
+static const TypeInfo adm1272_info = {
+ .name = TYPE_ADM1272,
+ .parent = TYPE_PMBUS_DEVICE,
+ .instance_size = sizeof(ADM1272State),
+ .instance_init = adm1272_init,
+ .class_init = adm1272_class_init,
+};
+
+static void adm1272_register_types(void)
+{
+ type_register_static(&adm1272_info);
+}
+
+type_init(adm1272_register_types)
diff --git a/hw/misc/emc141x.c b/hw/sensor/emc141x.c
index f7c53d48a4..7ce8f4e979 100644
--- a/hw/misc/emc141x.c
+++ b/hw/sensor/emc141x.c
@@ -25,7 +25,7 @@
#include "qapi/visitor.h"
#include "qemu/module.h"
#include "qom/object.h"
-#include "hw/misc/emc141x_regs.h"
+#include "hw/sensor/emc141x_regs.h"
#define SENSORS_COUNT_MAX 4
diff --git a/hw/sensor/max34451.c b/hw/sensor/max34451.c
new file mode 100644
index 0000000000..a91d8bd487
--- /dev/null
+++ b/hw/sensor/max34451.c
@@ -0,0 +1,775 @@
+/*
+ * Maxim MAX34451 PMBus 16-Channel V/I monitor and 12-Channel Sequencer/Marginer
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/pmbus_device.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define TYPE_MAX34451 "max34451"
+#define MAX34451(obj) OBJECT_CHECK(MAX34451State, (obj), TYPE_MAX34451)
+
+#define MAX34451_MFR_MODE 0xD1
+#define MAX34451_MFR_PSEN_CONFIG 0xD2
+#define MAX34451_MFR_VOUT_PEAK 0xD4
+#define MAX34451_MFR_IOUT_PEAK 0xD5
+#define MAX34451_MFR_TEMPERATURE_PEAK 0xD6
+#define MAX34451_MFR_VOUT_MIN 0xD7
+#define MAX34451_MFR_NV_LOG_CONFIG 0xD8
+#define MAX34451_MFR_FAULT_RESPONSE 0xD9
+#define MAX34451_MFR_FAULT_RETRY 0xDA
+#define MAX34451_MFR_NV_FAULT_LOG 0xDC
+#define MAX34451_MFR_TIME_COUNT 0xDD
+#define MAX34451_MFR_MARGIN_CONFIG 0xDF
+#define MAX34451_MFR_FW_SERIAL 0xE0
+#define MAX34451_MFR_IOUT_AVG 0xE2
+#define MAX34451_MFR_CHANNEL_CONFIG 0xE4
+#define MAX34451_MFR_TON_SEQ_MAX 0xE6
+#define MAX34451_MFR_PWM_CONFIG 0xE7
+#define MAX34451_MFR_SEQ_CONFIG 0xE8
+#define MAX34451_MFR_STORE_ALL 0xEE
+#define MAX34451_MFR_RESTORE_ALL 0xEF
+#define MAX34451_MFR_TEMP_SENSOR_CONFIG 0xF0
+#define MAX34451_MFR_STORE_SINGLE 0xFC
+#define MAX34451_MFR_CRC 0xFE
+
+#define MAX34451_NUM_MARGINED_PSU 12
+#define MAX34451_NUM_PWR_DEVICES 16
+#define MAX34451_NUM_TEMP_DEVICES 5
+#define MAX34451_NUM_PAGES 21
+
+#define DEFAULT_OP_ON 0x80
+#define DEFAULT_CAPABILITY 0x20
+#define DEFAULT_ON_OFF_CONFIG 0x1a
+#define DEFAULT_VOUT_MODE 0x40
+#define DEFAULT_TEMPERATURE 2500
+#define DEFAULT_SCALE 0x7FFF
+#define DEFAULT_OV_LIMIT 0x7FFF
+#define DEFAULT_OC_LIMIT 0x7FFF
+#define DEFAULT_OT_LIMIT 0x7FFF
+#define DEFAULT_VMIN 0x7FFF
+#define DEFAULT_TON_FAULT_LIMIT 0xFFFF
+#define DEFAULT_CHANNEL_CONFIG 0x20
+#define DEFAULT_TEXT 0x3130313031303130
+
+/**
+ * MAX34451State:
+ * @code: The command code received
+ * @page: Each page corresponds to a device monitored by the Max 34451
+ * The page register determines the available commands depending on device
+ ___________________________________________________________________________
+ | 0 | Power supply monitored by RS0, controlled by PSEN0, and |
+ | | margined with PWM0. |
+ |_______|___________________________________________________________________|
+ | 1 | Power supply monitored by RS1, controlled by PSEN1, and |
+ | | margined with PWM1. |
+ |_______|___________________________________________________________________|
+ | 2 | Power supply monitored by RS2, controlled by PSEN2, and |
+ | | margined with PWM2. |
+ |_______|___________________________________________________________________|
+ | 3 | Power supply monitored by RS3, controlled by PSEN3, and |
+ | | margined with PWM3. |
+ |_______|___________________________________________________________________|
+ | 4 | Power supply monitored by RS4, controlled by PSEN4, and |
+ | | margined with PWM4. |
+ |_______|___________________________________________________________________|
+ | 5 | Power supply monitored by RS5, controlled by PSEN5, and |
+ | | margined with PWM5. |
+ |_______|___________________________________________________________________|
+ | 6 | Power supply monitored by RS6, controlled by PSEN6, and |
+ | | margined with PWM6. |
+ |_______|___________________________________________________________________|
+ | 7 | Power supply monitored by RS7, controlled by PSEN7, and |
+ | | margined with PWM7. |
+ |_______|___________________________________________________________________|
+ | 8 | Power supply monitored by RS8, controlled by PSEN8, and |
+ | | optionally margined by OUT0 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 9 | Power supply monitored by RS9, controlled by PSEN9, and |
+ | | optionally margined by OUT1 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 10 | Power supply monitored by RS10, controlled by PSEN10, and |
+ | | optionally margined by OUT2 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 11 | Power supply monitored by RS11, controlled by PSEN11, and |
+ | | optionally margined by OUT3 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 12 | ADC channel 12 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 13 | ADC channel 13 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 14 | ADC channel 14 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 15 | ADC channel 15 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 16 | Internal temperature sensor. |
+ |_______|___________________________________________________________________|
+ | 17 | External DS75LV temperature sensor with I2C address 90h. |
+ |_______|___________________________________________________________________|
+ | 18 | External DS75LV temperature sensor with I2C address 92h. |
+ |_______|___________________________________________________________________|
+ | 19 | External DS75LV temperature sensor with I2C address 94h. |
+ |_______|___________________________________________________________________|
+ | 20 | External DS75LV temperature sensor with I2C address 96h. |
+ |_______|___________________________________________________________________|
+ | 21=E2=80=93254| Reserved. |
+ |_______|___________________________________________________________________|
+ | 255 | Applies to all pages. |
+ |_______|___________________________________________________________________|
+ *
+ * @operation: Turn on and off power supplies
+ * @on_off_config: Configure the power supply on and off transition behaviour
+ * @write_protect: protect against changes to the device's memory
+ * @vout_margin_high: the voltage when OPERATION is set to margin high
+ * @vout_margin_low: the voltage when OPERATION is set to margin low
+ * @vout_scale: scale ADC reading to actual device reading if different
+ * @iout_cal_gain: set ratio of the voltage at the ADC input to sensed current
+ */
+typedef struct MAX34451State {
+ PMBusDevice parent;
+
+ uint16_t power_good_on[MAX34451_NUM_PWR_DEVICES];
+ uint16_t power_good_off[MAX34451_NUM_PWR_DEVICES];
+ uint16_t ton_delay[MAX34451_NUM_MARGINED_PSU];
+ uint16_t ton_max_fault_limit[MAX34451_NUM_MARGINED_PSU];
+ uint16_t toff_delay[MAX34451_NUM_MARGINED_PSU];
+ uint8_t status_mfr_specific[MAX34451_NUM_PWR_DEVICES];
+ /* Manufacturer specific function */
+ uint64_t mfr_location;
+ uint64_t mfr_date;
+ uint64_t mfr_serial;
+ uint16_t mfr_mode;
+ uint32_t psen_config[MAX34451_NUM_MARGINED_PSU];
+ uint16_t vout_peak[MAX34451_NUM_PWR_DEVICES];
+ uint16_t iout_peak[MAX34451_NUM_PWR_DEVICES];
+ uint16_t temperature_peak[MAX34451_NUM_TEMP_DEVICES];
+ uint16_t vout_min[MAX34451_NUM_PWR_DEVICES];
+ uint16_t nv_log_config;
+ uint32_t fault_response[MAX34451_NUM_PWR_DEVICES];
+ uint16_t fault_retry;
+ uint32_t fault_log;
+ uint32_t time_count;
+ uint16_t margin_config[MAX34451_NUM_MARGINED_PSU];
+ uint16_t fw_serial;
+ uint16_t iout_avg[MAX34451_NUM_PWR_DEVICES];
+ uint16_t channel_config[MAX34451_NUM_PWR_DEVICES];
+ uint16_t ton_seq_max[MAX34451_NUM_MARGINED_PSU];
+ uint32_t pwm_config[MAX34451_NUM_MARGINED_PSU];
+ uint32_t seq_config[MAX34451_NUM_MARGINED_PSU];
+ uint16_t temp_sensor_config[MAX34451_NUM_TEMP_DEVICES];
+ uint16_t store_single;
+ uint16_t crc;
+} MAX34451State;
+
+
+static void max34451_check_limits(MAX34451State *s)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(s);
+
+ pmbus_check_limits(pmdev);
+
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ if (pmdev->pages[i].read_vout == 0) { /* PSU disabled */
+ continue;
+ }
+
+ if (pmdev->pages[i].read_vout > s->vout_peak[i]) {
+ s->vout_peak[i] = pmdev->pages[i].read_vout;
+ }
+
+ if (pmdev->pages[i].read_vout < s->vout_min[i]) {
+ s->vout_min[i] = pmdev->pages[i].read_vout;
+ }
+
+ if (pmdev->pages[i].read_iout > s->iout_peak[i]) {
+ s->iout_peak[i] = pmdev->pages[i].read_iout;
+ }
+ }
+
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ if (pmdev->pages[i + 16].read_temperature_1 > s->temperature_peak[i]) {
+ s->temperature_peak[i] = pmdev->pages[i + 16].read_temperature_1;
+ }
+ }
+}
+
+static uint8_t max34451_read_byte(PMBusDevice *pmdev)
+{
+ MAX34451State *s = MAX34451(pmdev);
+ switch (pmdev->code) {
+
+ case PMBUS_POWER_GOOD_ON:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->power_good_on[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_POWER_GOOD_OFF:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->power_good_off[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_TON_DELAY:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->ton_delay[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_TON_MAX_FAULT_LIMIT:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->ton_max_fault_limit[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_TOFF_DELAY:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->toff_delay[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_STATUS_MFR_SPECIFIC:
+ if (pmdev->page < 16) {
+ pmbus_send8(pmdev, s->status_mfr_specific[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_MFR_ID:
+ pmbus_send8(pmdev, 0x4d); /* Maxim */
+ break;
+
+ case PMBUS_MFR_MODEL:
+ pmbus_send8(pmdev, 0x59);
+ break;
+
+ case PMBUS_MFR_LOCATION:
+ pmbus_send64(pmdev, s->mfr_location);
+ break;
+
+ case PMBUS_MFR_DATE:
+ pmbus_send64(pmdev, s->mfr_date);
+ break;
+
+ case PMBUS_MFR_SERIAL:
+ pmbus_send64(pmdev, s->mfr_serial);
+ break;
+
+ case MAX34451_MFR_MODE:
+ pmbus_send16(pmdev, s->mfr_mode);
+ break;
+
+ case MAX34451_MFR_PSEN_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send32(pmdev, s->psen_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_PEAK:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->vout_peak[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_IOUT_PEAK:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->iout_peak[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_TEMPERATURE_PEAK:
+ if (15 < pmdev->page && pmdev->page < 21) {
+ pmbus_send16(pmdev, s->temperature_peak[pmdev->page % 16]);
+ } else {
+ pmbus_send16(pmdev, s->temperature_peak[0]);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_MIN:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->vout_min[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_NV_LOG_CONFIG:
+ pmbus_send16(pmdev, s->nv_log_config);
+ break;
+
+ case MAX34451_MFR_FAULT_RESPONSE:
+ if (pmdev->page < 16) {
+ pmbus_send32(pmdev, s->fault_response[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_FAULT_RETRY:
+ pmbus_send32(pmdev, s->fault_retry);
+ break;
+
+ case MAX34451_MFR_NV_FAULT_LOG:
+ pmbus_send32(pmdev, s->fault_log);
+ break;
+
+ case MAX34451_MFR_TIME_COUNT:
+ pmbus_send32(pmdev, s->time_count);
+ break;
+
+ case MAX34451_MFR_MARGIN_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->margin_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_FW_SERIAL:
+ if (pmdev->page == 255) {
+ pmbus_send16(pmdev, 1); /* Firmware revision */
+ }
+ break;
+
+ case MAX34451_MFR_IOUT_AVG:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->iout_avg[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_CHANNEL_CONFIG:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->channel_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_TON_SEQ_MAX:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->ton_seq_max[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_PWM_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send32(pmdev, s->pwm_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_SEQ_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send32(pmdev, s->seq_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_TEMP_SENSOR_CONFIG:
+ if (15 < pmdev->page && pmdev->page < 21) {
+ pmbus_send32(pmdev, s->temp_sensor_config[pmdev->page % 16]);
+ }
+ break;
+
+ case MAX34451_MFR_STORE_SINGLE:
+ pmbus_send32(pmdev, s->store_single);
+ break;
+
+ case MAX34451_MFR_CRC:
+ pmbus_send32(pmdev, s->crc);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: reading from unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+ }
+ return 0xFF;
+}
+
+static int max34451_write_data(PMBusDevice *pmdev, const uint8_t *buf,
+ uint8_t len)
+{
+ MAX34451State *s = MAX34451(pmdev);
+
+ if (len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
+ return -1;
+ }
+
+ pmdev->code = buf[0]; /* PMBus command code */
+
+ if (len == 1) {
+ return 0;
+ }
+
+ /* Exclude command code from buffer */
+ buf++;
+ len--;
+ uint8_t index = pmdev->page;
+
+ switch (pmdev->code) {
+ case MAX34451_MFR_STORE_ALL:
+ case MAX34451_MFR_RESTORE_ALL:
+ case MAX34451_MFR_STORE_SINGLE:
+ /*
+ * TODO: hardware behaviour is to move the contents of volatile
+ * memory to non-volatile memory.
+ */
+ break;
+
+ case PMBUS_POWER_GOOD_ON: /* R/W word */
+ if (pmdev->page < MAX34451_NUM_PWR_DEVICES) {
+ s->power_good_on[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_POWER_GOOD_OFF: /* R/W word */
+ if (pmdev->page < MAX34451_NUM_PWR_DEVICES) {
+ s->power_good_off[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_TON_DELAY: /* R/W word */
+ if (pmdev->page < 12) {
+ s->ton_delay[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_TON_MAX_FAULT_LIMIT: /* R/W word */
+ if (pmdev->page < 12) {
+ s->ton_max_fault_limit[pmdev->page]
+ = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_TOFF_DELAY: /* R/W word */
+ if (pmdev->page < 12) {
+ s->toff_delay[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_MFR_LOCATION: /* R/W 64 */
+ s->mfr_location = pmbus_receive64(pmdev);
+ break;
+
+ case PMBUS_MFR_DATE: /* R/W 64 */
+ s->mfr_date = pmbus_receive64(pmdev);
+ break;
+
+ case PMBUS_MFR_SERIAL: /* R/W 64 */
+ s->mfr_serial = pmbus_receive64(pmdev);
+ break;
+
+ case MAX34451_MFR_MODE: /* R/W word */
+ s->mfr_mode = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_PSEN_CONFIG: /* R/W 32 */
+ if (pmdev->page < 12) {
+ s->psen_config[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_PEAK: /* R/W word */
+ if (pmdev->page < 16) {
+ s->vout_peak[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_IOUT_PEAK: /* R/W word */
+ if (pmdev->page < 16) {
+ s->iout_peak[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_TEMPERATURE_PEAK: /* R/W word */
+ if (15 < pmdev->page && pmdev->page < 21) {
+ s->temperature_peak[pmdev->page % 16]
+ = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_MIN: /* R/W word */
+ if (pmdev->page < 16) {
+ s->vout_min[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_NV_LOG_CONFIG: /* R/W word */
+ s->nv_log_config = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_FAULT_RESPONSE: /* R/W 32 */
+ if (pmdev->page < 16) {
+ s->fault_response[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_FAULT_RETRY: /* R/W word */
+ s->fault_retry = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_TIME_COUNT: /* R/W 32 */
+ s->time_count = pmbus_receive32(pmdev);
+ break;
+
+ case MAX34451_MFR_MARGIN_CONFIG: /* R/W word */
+ if (pmdev->page < 12) {
+ s->margin_config[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_CHANNEL_CONFIG: /* R/W word */
+ if (pmdev->page < 16) {
+ s->channel_config[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_TON_SEQ_MAX: /* R/W word */
+ if (pmdev->page < 12) {
+ s->ton_seq_max[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_PWM_CONFIG: /* R/W 32 */
+ if (pmdev->page < 12) {
+ s->pwm_config[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_SEQ_CONFIG: /* R/W 32 */
+ if (pmdev->page < 12) {
+ s->seq_config[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_TEMP_SENSOR_CONFIG: /* R/W word */
+ if (15 < pmdev->page && pmdev->page < 21) {
+ s->temp_sensor_config[pmdev->page % 16]
+ = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_CRC: /* R/W word */
+ s->crc = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_NV_FAULT_LOG:
+ case MAX34451_MFR_FW_SERIAL:
+ case MAX34451_MFR_IOUT_AVG:
+ /* Read only commands */
+ pmdev->pages[index].status_word |= PMBUS_STATUS_CML;
+ pmdev->pages[index].status_cml |= PB_CML_FAULT_INVALID_DATA;
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to read-only register 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+ }
+
+ return 0;
+}
+
+static void max34451_get(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ visit_type_uint16(v, name, (uint16_t *)opaque, errp);
+}
+
+static void max34451_set(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ MAX34451State *s = MAX34451(obj);
+ uint16_t *internal = opaque;
+ uint16_t value;
+ if (!visit_type_uint16(v, name, &value, errp)) {
+ return;
+ }
+
+ *internal = value;
+ max34451_check_limits(s);
+}
+
+/* used to init uint16_t arrays */
+static inline void *memset_word(void *s, uint16_t c, size_t n)
+{
+ size_t i;
+ uint16_t *p = s;
+
+ for (i = 0; i < n; i++) {
+ p[i] = c;
+ }
+
+ return s;
+}
+
+static void max34451_exit_reset(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ MAX34451State *s = MAX34451(obj);
+ pmdev->capability = DEFAULT_CAPABILITY;
+
+ for (int i = 0; i < MAX34451_NUM_PAGES; i++) {
+ pmdev->pages[i].operation = DEFAULT_OP_ON;
+ pmdev->pages[i].on_off_config = DEFAULT_ON_OFF_CONFIG;
+ pmdev->pages[i].revision = 0x11;
+ pmdev->pages[i].vout_mode = DEFAULT_VOUT_MODE;
+ }
+
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ pmdev->pages[i].vout_scale_monitor = DEFAULT_SCALE;
+ pmdev->pages[i].vout_ov_fault_limit = DEFAULT_OV_LIMIT;
+ pmdev->pages[i].vout_ov_warn_limit = DEFAULT_OV_LIMIT;
+ pmdev->pages[i].iout_oc_warn_limit = DEFAULT_OC_LIMIT;
+ pmdev->pages[i].iout_oc_fault_limit = DEFAULT_OC_LIMIT;
+ }
+
+ for (int i = 0; i < MAX34451_NUM_MARGINED_PSU; i++) {
+ pmdev->pages[i].ton_max_fault_limit = DEFAULT_TON_FAULT_LIMIT;
+ }
+
+ for (int i = 16; i < MAX34451_NUM_TEMP_DEVICES + 16; i++) {
+ pmdev->pages[i].read_temperature_1 = DEFAULT_TEMPERATURE;
+ pmdev->pages[i].ot_warn_limit = DEFAULT_OT_LIMIT;
+ pmdev->pages[i].ot_fault_limit = DEFAULT_OT_LIMIT;
+ }
+
+ memset_word(s->ton_max_fault_limit, DEFAULT_TON_FAULT_LIMIT,
+ MAX34451_NUM_MARGINED_PSU);
+ memset_word(s->channel_config, DEFAULT_CHANNEL_CONFIG,
+ MAX34451_NUM_PWR_DEVICES);
+ memset_word(s->vout_min, DEFAULT_VMIN, MAX34451_NUM_PWR_DEVICES);
+
+ s->mfr_location = DEFAULT_TEXT;
+ s->mfr_date = DEFAULT_TEXT;
+ s->mfr_serial = DEFAULT_TEXT;
+}
+
+static const VMStateDescription vmstate_max34451 = {
+ .name = TYPE_MAX34451,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]){
+ VMSTATE_PMBUS_DEVICE(parent, MAX34451State),
+ VMSTATE_UINT16_ARRAY(power_good_on, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(power_good_off, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(ton_delay, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT16_ARRAY(ton_max_fault_limit, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT16_ARRAY(toff_delay, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT8_ARRAY(status_mfr_specific, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT64(mfr_location, MAX34451State),
+ VMSTATE_UINT64(mfr_date, MAX34451State),
+ VMSTATE_UINT64(mfr_serial, MAX34451State),
+ VMSTATE_UINT16(mfr_mode, MAX34451State),
+ VMSTATE_UINT32_ARRAY(psen_config, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT16_ARRAY(vout_peak, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(iout_peak, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(temperature_peak, MAX34451State,
+ MAX34451_NUM_TEMP_DEVICES),
+ VMSTATE_UINT16_ARRAY(vout_min, MAX34451State, MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16(nv_log_config, MAX34451State),
+ VMSTATE_UINT32_ARRAY(fault_response, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16(fault_retry, MAX34451State),
+ VMSTATE_UINT32(fault_log, MAX34451State),
+ VMSTATE_UINT32(time_count, MAX34451State),
+ VMSTATE_UINT16_ARRAY(margin_config, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT16(fw_serial, MAX34451State),
+ VMSTATE_UINT16_ARRAY(iout_avg, MAX34451State, MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(channel_config, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(ton_seq_max, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT32_ARRAY(pwm_config, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT32_ARRAY(seq_config, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT16_ARRAY(temp_sensor_config, MAX34451State,
+ MAX34451_NUM_TEMP_DEVICES),
+ VMSTATE_UINT16(store_single, MAX34451State),
+ VMSTATE_UINT16(crc, MAX34451State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void max34451_init(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ uint64_t psu_flags = PB_HAS_VOUT | PB_HAS_IOUT | PB_HAS_VOUT_MODE |
+ PB_HAS_IOUT_GAIN;
+
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ pmbus_page_config(pmdev, i, psu_flags);
+ }
+
+ for (int i = 0; i < MAX34451_NUM_MARGINED_PSU; i++) {
+ pmbus_page_config(pmdev, i, psu_flags | PB_HAS_VOUT_MARGIN);
+ }
+
+ for (int i = 16; i < MAX34451_NUM_TEMP_DEVICES + 16; i++) {
+ pmbus_page_config(pmdev, i, PB_HAS_TEMPERATURE | PB_HAS_VOUT_MODE);
+ }
+
+ /* get and set the voltage in millivolts, max is 32767 mV */
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ object_property_add(obj, "vout[*]", "uint16",
+ max34451_get,
+ max34451_set, NULL, &pmdev->pages[i].read_vout);
+ }
+
+ /*
+ * get and set the temperature of the internal temperature sensor in
+ * centidegrees Celcius i.e.: 2500 -> 25.00 C, max is 327.67 C
+ */
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ object_property_add(obj, "temperature[*]", "uint16",
+ max34451_get,
+ max34451_set,
+ NULL,
+ &pmdev->pages[i + 16].read_temperature_1);
+ }
+
+}
+
+static void max34451_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass);
+ dc->desc = "Maxim MAX34451 16-Channel V/I monitor";
+ dc->vmsd = &vmstate_max34451;
+ k->write_data = max34451_write_data;
+ k->receive_byte = max34451_read_byte;
+ k->device_num_pages = MAX34451_NUM_PAGES;
+ rc->phases.exit = max34451_exit_reset;
+}
+
+static const TypeInfo max34451_info = {
+ .name = TYPE_MAX34451,
+ .parent = TYPE_PMBUS_DEVICE,
+ .instance_size = sizeof(MAX34451State),
+ .instance_init = max34451_init,
+ .class_init = max34451_class_init,
+};
+
+static void max34451_register_types(void)
+{
+ type_register_static(&max34451_info);
+}
+
+type_init(max34451_register_types)
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
new file mode 100644
index 0000000000..034e3e0207
--- /dev/null
+++ b/hw/sensor/meson.build
@@ -0,0 +1,5 @@
+softmmu_ss.add(when: 'CONFIG_TMP105', if_true: files('tmp105.c'))
+softmmu_ss.add(when: 'CONFIG_TMP421', if_true: files('tmp421.c'))
+softmmu_ss.add(when: 'CONFIG_EMC141X', if_true: files('emc141x.c'))
+softmmu_ss.add(when: 'CONFIG_ADM1272', if_true: files('adm1272.c'))
+softmmu_ss.add(when: 'CONFIG_MAX34451', if_true: files('max34451.c'))
diff --git a/hw/misc/tmp105.c b/hw/sensor/tmp105.c
index d299d9b21b..2056449489 100644
--- a/hw/misc/tmp105.c
+++ b/hw/sensor/tmp105.c
@@ -22,7 +22,7 @@
#include "hw/i2c/i2c.h"
#include "hw/irq.h"
#include "migration/vmstate.h"
-#include "tmp105.h"
+#include "hw/sensor/tmp105.h"
#include "qapi/error.h"
#include "qapi/visitor.h"
#include "qemu/module.h"
diff --git a/hw/misc/tmp421.c b/hw/sensor/tmp421.c
index a3db57dcb5..a3db57dcb5 100644
--- a/hw/misc/tmp421.c
+++ b/hw/sensor/tmp421.c
diff --git a/hw/usb/ccid-card-emulated.c b/hw/usb/ccid-card-emulated.c
index 5c76bed77a..6c8c0355e0 100644
--- a/hw/usb/ccid-card-emulated.c
+++ b/hw/usb/ccid-card-emulated.c
@@ -612,6 +612,7 @@ static const TypeInfo emulated_card_info = {
.instance_size = sizeof(EmulatedState),
.class_init = emulated_class_initfn,
};
+module_obj(TYPE_EMULATED_CCID);
static void ccid_card_emulated_register_types(void)
{
diff --git a/hw/usb/ccid-card-passthru.c b/hw/usb/ccid-card-passthru.c
index 7212d0d7fb..fa3040fb71 100644
--- a/hw/usb/ccid-card-passthru.c
+++ b/hw/usb/ccid-card-passthru.c
@@ -414,6 +414,7 @@ static const TypeInfo passthru_card_info = {
.instance_size = sizeof(PassthruState),
.class_init = passthru_class_initfn,
};
+module_obj(TYPE_CCID_PASSTHRU);
static void ccid_card_passthru_register_types(void)
{
diff --git a/hw/usb/desc-msos.c b/hw/usb/desc-msos.c
index 3a5ad7c8d0..836e38c67e 100644
--- a/hw/usb/desc-msos.c
+++ b/hw/usb/desc-msos.c
@@ -181,7 +181,7 @@ static int usb_desc_msos_prop(const USBDesc *desc, uint8_t *dest)
if (desc->msos->Label) {
/*
- * Given as example in the specs. Havn't figured yet where
+ * Given as example in the specs. Haven't figured yet where
* this label shows up in the windows gui.
*/
length += usb_desc_msos_prop_str(dest+length, MSOS_REG_SZ,
diff --git a/hw/usb/dev-storage-bot.c b/hw/usb/dev-storage-bot.c
index 6aad026d11..68ebaca10c 100644
--- a/hw/usb/dev-storage-bot.c
+++ b/hw/usb/dev-storage-bot.c
@@ -32,6 +32,7 @@ static void usb_msd_bot_realize(USBDevice *dev, Error **errp)
usb_desc_create_serial(dev);
usb_desc_init(dev);
+ dev->flags |= (1 << USB_DEV_FLAG_IS_SCSI_STORAGE);
if (d->hotplugged) {
s->dev.auto_attach = 0;
}
diff --git a/hw/usb/dev-storage-classic.c b/hw/usb/dev-storage-classic.c
index 00cb34b22f..3d017a4e67 100644
--- a/hw/usb/dev-storage-classic.c
+++ b/hw/usb/dev-storage-classic.c
@@ -64,6 +64,7 @@ static void usb_msd_storage_realize(USBDevice *dev, Error **errp)
usb_desc_create_serial(dev);
usb_desc_init(dev);
+ dev->flags |= (1 << USB_DEV_FLAG_IS_SCSI_STORAGE);
scsi_bus_new(&s->bus, sizeof(s->bus), DEVICE(dev),
&usb_msd_scsi_info_storage, NULL);
scsi_dev = scsi_bus_legacy_add_drive(&s->bus, blk, 0, !!s->removable,
diff --git a/hw/usb/dev-uas.c b/hw/usb/dev-uas.c
index d2bd85d3f6..263056231c 100644
--- a/hw/usb/dev-uas.c
+++ b/hw/usb/dev-uas.c
@@ -926,6 +926,7 @@ static void usb_uas_realize(USBDevice *dev, Error **errp)
QTAILQ_INIT(&uas->requests);
uas->status_bh = qemu_bh_new(usb_uas_send_status_bh, uas);
+ dev->flags |= (1 << USB_DEV_FLAG_IS_SCSI_STORAGE);
scsi_bus_new(&uas->bus, sizeof(uas->bus), DEVICE(dev),
&usb_uas_scsi_info, NULL);
}
diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index 2518306f52..c0f314462a 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -770,6 +770,13 @@ static void usb_host_speed_compat(USBHostDevice *s)
for (i = 0; i < conf->bNumInterfaces; i++) {
for (a = 0; a < conf->interface[i].num_altsetting; a++) {
intf = &conf->interface[i].altsetting[a];
+
+ if (intf->bInterfaceClass == LIBUSB_CLASS_MASS_STORAGE &&
+ intf->bInterfaceSubClass == 6) { /* SCSI */
+ udev->flags |= (1 << USB_DEV_FLAG_IS_SCSI_STORAGE);
+ break;
+ }
+
for (e = 0; e < intf->bNumEndpoints; e++) {
endp = &intf->endpoint[e];
type = endp->bmAttributes & 0x3;
@@ -1770,10 +1777,12 @@ static TypeInfo usb_host_dev_info = {
.class_init = usb_host_class_initfn,
.instance_init = usb_host_instance_init,
};
+module_obj(TYPE_USB_HOST_DEVICE);
static void usb_host_register_types(void)
{
type_register_static(&usb_host_dev_info);
+ monitor_register_hmp("usbhost", true, hmp_info_usbhost);
}
type_init(usb_host_register_types)
@@ -1893,35 +1902,6 @@ static void usb_host_auto_check(void *unused)
timer_mod(usb_auto_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 2000);
}
-/**
- * Check whether USB host device has a USB mass storage SCSI interface
- */
-bool usb_host_dev_is_scsi_storage(USBDevice *ud)
-{
- USBHostDevice *uhd = USB_HOST_DEVICE(ud);
- struct libusb_config_descriptor *conf;
- const struct libusb_interface_descriptor *intf;
- bool is_scsi_storage = false;
- int i;
-
- if (!uhd || libusb_get_active_config_descriptor(uhd->dev, &conf) != 0) {
- return false;
- }
-
- for (i = 0; i < conf->bNumInterfaces; i++) {
- intf = &conf->interface[i].altsetting[ud->altsetting[i]];
- if (intf->bInterfaceClass == LIBUSB_CLASS_MASS_STORAGE &&
- intf->bInterfaceSubClass == 6) { /* 6 means SCSI */
- is_scsi_storage = true;
- break;
- }
- }
-
- libusb_free_config_descriptor(conf);
-
- return is_scsi_storage;
-}
-
void hmp_info_usbhost(Monitor *mon, const QDict *qdict)
{
libusb_device **devs = NULL;
diff --git a/hw/usb/host-stub.c b/hw/usb/host-stub.c
deleted file mode 100644
index 80809ceba5..0000000000
--- a/hw/usb/host-stub.c
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Stub host USB redirector
- *
- * Copyright (c) 2005 Fabrice Bellard
- *
- * Copyright (c) 2008 Max Krasnyansky
- * Support for host device auto connect & disconnect
- * Major rewrite to support fully async operation
- *
- * Copyright 2008 TJ <linux@tjworld.net>
- * Added flexible support for /dev/bus/usb /sys/bus/usb/devices in addition
- * to the legacy /proc/bus/usb USB device discovery and handling
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-#include "qemu/osdep.h"
-#include "hw/usb.h"
-#include "monitor/monitor.h"
-
-void hmp_info_usbhost(Monitor *mon, const QDict *qdict)
-{
- monitor_printf(mon, "USB host devices not supported\n");
-}
-
-bool usb_host_dev_is_scsi_storage(USBDevice *ud)
-{
- return false;
-}
diff --git a/hw/usb/meson.build b/hw/usb/meson.build
index 4f24b5274d..3ca6127937 100644
--- a/hw/usb/meson.build
+++ b/hw/usb/meson.build
@@ -72,10 +72,12 @@ if usbredir.found()
endif
# usb pass-through
-softmmu_ss.add(when: ['CONFIG_USB', libusb],
- if_true: files('host-libusb.c'),
- if_false: files('host-stub.c'))
-softmmu_ss.add(when: 'CONFIG_ALL', if_true: files('host-stub.c'))
+if config_host.has_key('CONFIG_USB_LIBUSB')
+ usbhost_ss = ss.source_set()
+ usbhost_ss.add(when: ['CONFIG_USB', libusb],
+ if_true: files('host-libusb.c'))
+ hw_usb_modules += {'host': usbhost_ss}
+endif
softmmu_ss.add(when: ['CONFIG_USB', 'CONFIG_XEN', libusb], if_true: files('xen-usb.c'))
diff --git a/hw/usb/redirect.c b/hw/usb/redirect.c
index 6a75b0dc4a..4ec9326e05 100644
--- a/hw/usb/redirect.c
+++ b/hw/usb/redirect.c
@@ -2608,6 +2608,7 @@ static const TypeInfo usbredir_dev_info = {
.class_init = usbredir_class_initfn,
.instance_init = usbredir_instance_init,
};
+module_obj(TYPE_USB_REDIR);
static void usbredir_register_types(void)
{
diff --git a/hw/vfio/common.c b/hw/vfio/common.c
index ae5654fcdb..3f0d111360 100644
--- a/hw/vfio/common.c
+++ b/hw/vfio/common.c
@@ -36,6 +36,7 @@
#include "qemu/range.h"
#include "sysemu/kvm.h"
#include "sysemu/reset.h"
+#include "sysemu/runstate.h"
#include "trace.h"
#include "qapi/error.h"
#include "migration/migration.h"
@@ -134,6 +135,29 @@ static const char *index_to_str(VFIODevice *vbasedev, int index)
}
}
+static int vfio_ram_block_discard_disable(VFIOContainer *container, bool state)
+{
+ switch (container->iommu_type) {
+ case VFIO_TYPE1v2_IOMMU:
+ case VFIO_TYPE1_IOMMU:
+ /*
+ * We support coordinated discarding of RAM via the RamDiscardManager.
+ */
+ return ram_block_uncoordinated_discard_disable(state);
+ default:
+ /*
+ * VFIO_SPAPR_TCE_IOMMU most probably works just fine with
+ * RamDiscardManager, however, it is completely untested.
+ *
+ * VFIO_SPAPR_TCE_v2_IOMMU with "DMA memory preregistering" does
+ * completely the opposite of managing mapping/pinning dynamically as
+ * required by RamDiscardManager. We would have to special-case sections
+ * with a RamDiscardManager.
+ */
+ return ram_block_discard_disable(state);
+ }
+}
+
int vfio_set_irq_signaling(VFIODevice *vbasedev, int index, int subindex,
int action, int fd, Error **errp)
{
@@ -569,6 +593,44 @@ static bool vfio_get_xlat_addr(IOMMUTLBEntry *iotlb, void **vaddr,
error_report("iommu map to non memory area %"HWADDR_PRIx"",
xlat);
return false;
+ } else if (memory_region_has_ram_discard_manager(mr)) {
+ RamDiscardManager *rdm = memory_region_get_ram_discard_manager(mr);
+ MemoryRegionSection tmp = {
+ .mr = mr,
+ .offset_within_region = xlat,
+ .size = int128_make64(len),
+ };
+
+ /*
+ * Malicious VMs can map memory into the IOMMU, which is expected
+ * to remain discarded. vfio will pin all pages, populating memory.
+ * Disallow that. vmstate priorities make sure any RamDiscardManager
+ * were already restored before IOMMUs are restored.
+ */
+ if (!ram_discard_manager_is_populated(rdm, &tmp)) {
+ error_report("iommu map to discarded memory (e.g., unplugged via"
+ " virtio-mem): %"HWADDR_PRIx"",
+ iotlb->translated_addr);
+ return false;
+ }
+
+ /*
+ * Malicious VMs might trigger discarding of IOMMU-mapped memory. The
+ * pages will remain pinned inside vfio until unmapped, resulting in a
+ * higher memory consumption than expected. If memory would get
+ * populated again later, there would be an inconsistency between pages
+ * pinned by vfio and pages seen by QEMU. This is the case until
+ * unmapped from the IOMMU (e.g., during device reset).
+ *
+ * With malicious guests, we really only care about pinning more memory
+ * than expected. RLIMIT_MEMLOCK set for the user/process can never be
+ * exceeded and can be used to mitigate this problem.
+ */
+ warn_report_once("Using vfio with vIOMMUs and coordinated discarding of"
+ " RAM (e.g., virtio-mem) works, however, malicious"
+ " guests can trigger pinning of more memory than"
+ " intended via an IOMMU. It's possible to mitigate "
+ " by setting/adjusting RLIMIT_MEMLOCK.");
}
/*
@@ -649,6 +711,153 @@ out:
rcu_read_unlock();
}
+static void vfio_ram_discard_notify_discard(RamDiscardListener *rdl,
+ MemoryRegionSection *section)
+{
+ VFIORamDiscardListener *vrdl = container_of(rdl, VFIORamDiscardListener,
+ listener);
+ const hwaddr size = int128_get64(section->size);
+ const hwaddr iova = section->offset_within_address_space;
+ int ret;
+
+ /* Unmap with a single call. */
+ ret = vfio_dma_unmap(vrdl->container, iova, size , NULL);
+ if (ret) {
+ error_report("%s: vfio_dma_unmap() failed: %s", __func__,
+ strerror(-ret));
+ }
+}
+
+static int vfio_ram_discard_notify_populate(RamDiscardListener *rdl,
+ MemoryRegionSection *section)
+{
+ VFIORamDiscardListener *vrdl = container_of(rdl, VFIORamDiscardListener,
+ listener);
+ const hwaddr end = section->offset_within_region +
+ int128_get64(section->size);
+ hwaddr start, next, iova;
+ void *vaddr;
+ int ret;
+
+ /*
+ * Map in (aligned within memory region) minimum granularity, so we can
+ * unmap in minimum granularity later.
+ */
+ for (start = section->offset_within_region; start < end; start = next) {
+ next = ROUND_UP(start + 1, vrdl->granularity);
+ next = MIN(next, end);
+
+ iova = start - section->offset_within_region +
+ section->offset_within_address_space;
+ vaddr = memory_region_get_ram_ptr(section->mr) + start;
+
+ ret = vfio_dma_map(vrdl->container, iova, next - start,
+ vaddr, section->readonly);
+ if (ret) {
+ /* Rollback */
+ vfio_ram_discard_notify_discard(rdl, section);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static void vfio_register_ram_discard_listener(VFIOContainer *container,
+ MemoryRegionSection *section)
+{
+ RamDiscardManager *rdm = memory_region_get_ram_discard_manager(section->mr);
+ VFIORamDiscardListener *vrdl;
+
+ /* Ignore some corner cases not relevant in practice. */
+ g_assert(QEMU_IS_ALIGNED(section->offset_within_region, TARGET_PAGE_SIZE));
+ g_assert(QEMU_IS_ALIGNED(section->offset_within_address_space,
+ TARGET_PAGE_SIZE));
+ g_assert(QEMU_IS_ALIGNED(int128_get64(section->size), TARGET_PAGE_SIZE));
+
+ vrdl = g_new0(VFIORamDiscardListener, 1);
+ vrdl->container = container;
+ vrdl->mr = section->mr;
+ vrdl->offset_within_address_space = section->offset_within_address_space;
+ vrdl->size = int128_get64(section->size);
+ vrdl->granularity = ram_discard_manager_get_min_granularity(rdm,
+ section->mr);
+
+ g_assert(vrdl->granularity && is_power_of_2(vrdl->granularity));
+ g_assert(vrdl->granularity >= 1 << ctz64(container->pgsizes));
+
+ ram_discard_listener_init(&vrdl->listener,
+ vfio_ram_discard_notify_populate,
+ vfio_ram_discard_notify_discard, true);
+ ram_discard_manager_register_listener(rdm, &vrdl->listener, section);
+ QLIST_INSERT_HEAD(&container->vrdl_list, vrdl, next);
+
+ /*
+ * Sanity-check if we have a theoretically problematic setup where we could
+ * exceed the maximum number of possible DMA mappings over time. We assume
+ * that each mapped section in the same address space as a RamDiscardManager
+ * section consumes exactly one DMA mapping, with the exception of
+ * RamDiscardManager sections; i.e., we don't expect to have gIOMMU sections
+ * in the same address space as RamDiscardManager sections.
+ *
+ * We assume that each section in the address space consumes one memslot.
+ * We take the number of KVM memory slots as a best guess for the maximum
+ * number of sections in the address space we could have over time,
+ * also consuming DMA mappings.
+ */
+ if (container->dma_max_mappings) {
+ unsigned int vrdl_count = 0, vrdl_mappings = 0, max_memslots = 512;
+
+#ifdef CONFIG_KVM
+ if (kvm_enabled()) {
+ max_memslots = kvm_get_max_memslots();
+ }
+#endif
+
+ QLIST_FOREACH(vrdl, &container->vrdl_list, next) {
+ hwaddr start, end;
+
+ start = QEMU_ALIGN_DOWN(vrdl->offset_within_address_space,
+ vrdl->granularity);
+ end = ROUND_UP(vrdl->offset_within_address_space + vrdl->size,
+ vrdl->granularity);
+ vrdl_mappings += (end - start) / vrdl->granularity;
+ vrdl_count++;
+ }
+
+ if (vrdl_mappings + max_memslots - vrdl_count >
+ container->dma_max_mappings) {
+ warn_report("%s: possibly running out of DMA mappings. E.g., try"
+ " increasing the 'block-size' of virtio-mem devies."
+ " Maximum possible DMA mappings: %d, Maximum possible"
+ " memslots: %d", __func__, container->dma_max_mappings,
+ max_memslots);
+ }
+ }
+}
+
+static void vfio_unregister_ram_discard_listener(VFIOContainer *container,
+ MemoryRegionSection *section)
+{
+ RamDiscardManager *rdm = memory_region_get_ram_discard_manager(section->mr);
+ VFIORamDiscardListener *vrdl = NULL;
+
+ QLIST_FOREACH(vrdl, &container->vrdl_list, next) {
+ if (vrdl->mr == section->mr &&
+ vrdl->offset_within_address_space ==
+ section->offset_within_address_space) {
+ break;
+ }
+ }
+
+ if (!vrdl) {
+ hw_error("vfio: Trying to unregister missing RAM discard listener");
+ }
+
+ ram_discard_manager_unregister_listener(rdm, &vrdl->listener);
+ QLIST_REMOVE(vrdl, next);
+ g_free(vrdl);
+}
+
static void vfio_listener_region_add(MemoryListener *listener,
MemoryRegionSection *section)
{
@@ -810,6 +1019,16 @@ static void vfio_listener_region_add(MemoryListener *listener,
/* Here we assume that memory_region_is_ram(section->mr)==true */
+ /*
+ * For RAM memory regions with a RamDiscardManager, we only want to map the
+ * actually populated parts - and update the mapping whenever we're notified
+ * about changes.
+ */
+ if (memory_region_has_ram_discard_manager(section->mr)) {
+ vfio_register_ram_discard_listener(container, section);
+ return;
+ }
+
vaddr = memory_region_get_ram_ptr(section->mr) +
section->offset_within_region +
(iova - section->offset_within_address_space);
@@ -947,6 +1166,10 @@ static void vfio_listener_region_del(MemoryListener *listener,
pgmask = (1ULL << ctz64(hostwin->iova_pgsizes)) - 1;
try_unmap = !((iova & pgmask) || (int128_get64(llsize) & pgmask));
+ } else if (memory_region_has_ram_discard_manager(section->mr)) {
+ vfio_unregister_ram_discard_listener(container, section);
+ /* Unregistering will trigger an unmap. */
+ try_unmap = false;
}
if (try_unmap) {
@@ -1108,6 +1331,49 @@ static void vfio_iommu_map_dirty_notify(IOMMUNotifier *n, IOMMUTLBEntry *iotlb)
rcu_read_unlock();
}
+static int vfio_ram_discard_get_dirty_bitmap(MemoryRegionSection *section,
+ void *opaque)
+{
+ const hwaddr size = int128_get64(section->size);
+ const hwaddr iova = section->offset_within_address_space;
+ const ram_addr_t ram_addr = memory_region_get_ram_addr(section->mr) +
+ section->offset_within_region;
+ VFIORamDiscardListener *vrdl = opaque;
+
+ /*
+ * Sync the whole mapped region (spanning multiple individual mappings)
+ * in one go.
+ */
+ return vfio_get_dirty_bitmap(vrdl->container, iova, size, ram_addr);
+}
+
+static int vfio_sync_ram_discard_listener_dirty_bitmap(VFIOContainer *container,
+ MemoryRegionSection *section)
+{
+ RamDiscardManager *rdm = memory_region_get_ram_discard_manager(section->mr);
+ VFIORamDiscardListener *vrdl = NULL;
+
+ QLIST_FOREACH(vrdl, &container->vrdl_list, next) {
+ if (vrdl->mr == section->mr &&
+ vrdl->offset_within_address_space ==
+ section->offset_within_address_space) {
+ break;
+ }
+ }
+
+ if (!vrdl) {
+ hw_error("vfio: Trying to sync missing RAM discard listener");
+ }
+
+ /*
+ * We only want/can synchronize the bitmap for actually mapped parts -
+ * which correspond to populated parts. Replay all populated parts.
+ */
+ return ram_discard_manager_replay_populated(rdm, section,
+ vfio_ram_discard_get_dirty_bitmap,
+ &vrdl);
+}
+
static int vfio_sync_dirty_bitmap(VFIOContainer *container,
MemoryRegionSection *section)
{
@@ -1139,6 +1405,8 @@ static int vfio_sync_dirty_bitmap(VFIOContainer *container,
}
}
return 0;
+ } else if (memory_region_has_ram_discard_manager(section->mr)) {
+ return vfio_sync_ram_discard_listener_dirty_bitmap(container, section);
}
ram_addr = memory_region_get_ram_addr(section->mr) +
@@ -1732,15 +2000,25 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as,
* new memory, it will not yet set ram_block_discard_set_required() and
* therefore, neither stops us here or deals with the sudden memory
* consumption of inflated memory.
+ *
+ * We do support discarding of memory coordinated via the RamDiscardManager
+ * with some IOMMU types. vfio_ram_block_discard_disable() handles the
+ * details once we know which type of IOMMU we are using.
*/
- ret = ram_block_discard_disable(true);
- if (ret) {
- error_setg_errno(errp, -ret, "Cannot set discarding of RAM broken");
- return ret;
- }
QLIST_FOREACH(container, &space->containers, next) {
if (!ioctl(group->fd, VFIO_GROUP_SET_CONTAINER, &container->fd)) {
+ ret = vfio_ram_block_discard_disable(container, true);
+ if (ret) {
+ error_setg_errno(errp, -ret,
+ "Cannot set discarding of RAM broken");
+ if (ioctl(group->fd, VFIO_GROUP_UNSET_CONTAINER,
+ &container->fd)) {
+ error_report("vfio: error disconnecting group %d from"
+ " container", group->groupid);
+ }
+ return ret;
+ }
group->container = container;
QLIST_INSERT_HEAD(&container->group_list, group, container_next);
vfio_kvm_device_add_group(group);
@@ -1768,14 +2046,22 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as,
container->fd = fd;
container->error = NULL;
container->dirty_pages_supported = false;
+ container->dma_max_mappings = 0;
QLIST_INIT(&container->giommu_list);
QLIST_INIT(&container->hostwin_list);
+ QLIST_INIT(&container->vrdl_list);
ret = vfio_init_container(container, group->fd, errp);
if (ret) {
goto free_container_exit;
}
+ ret = vfio_ram_block_discard_disable(container, true);
+ if (ret) {
+ error_setg_errno(errp, -ret, "Cannot set discarding of RAM broken");
+ goto free_container_exit;
+ }
+
switch (container->iommu_type) {
case VFIO_TYPE1v2_IOMMU:
case VFIO_TYPE1_IOMMU:
@@ -1798,7 +2084,10 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as,
vfio_host_win_add(container, 0, (hwaddr)-1, info->iova_pgsizes);
container->pgsizes = info->iova_pgsizes;
+ /* The default in the kernel ("dma_entry_limit") is 65535. */
+ container->dma_max_mappings = 65535;
if (!ret) {
+ vfio_get_info_dma_avail(info, &container->dma_max_mappings);
vfio_get_iommu_info_migration(container, info);
}
g_free(info);
@@ -1820,7 +2109,7 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as,
if (ret) {
error_setg_errno(errp, errno, "failed to enable container");
ret = -errno;
- goto free_container_exit;
+ goto enable_discards_exit;
}
} else {
container->prereg_listener = vfio_prereg_listener;
@@ -1832,7 +2121,7 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as,
ret = -1;
error_propagate_prepend(errp, container->error,
"RAM memory listener initialization failed: ");
- goto free_container_exit;
+ goto enable_discards_exit;
}
}
@@ -1845,7 +2134,7 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as,
if (v2) {
memory_listener_unregister(&container->prereg_listener);
}
- goto free_container_exit;
+ goto enable_discards_exit;
}
if (v2) {
@@ -1860,7 +2149,7 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as,
if (ret) {
error_setg_errno(errp, -ret,
"failed to remove existing window");
- goto free_container_exit;
+ goto enable_discards_exit;
}
} else {
/* The default table uses 4K pages */
@@ -1901,6 +2190,9 @@ listener_release_exit:
vfio_kvm_device_del_group(group);
vfio_listener_release(container);
+enable_discards_exit:
+ vfio_ram_block_discard_disable(container, false);
+
free_container_exit:
g_free(container);
@@ -1908,7 +2200,6 @@ close_fd_exit:
close(fd);
put_space_exit:
- ram_block_discard_disable(false);
vfio_put_address_space(space);
return ret;
@@ -2030,7 +2321,7 @@ void vfio_put_group(VFIOGroup *group)
}
if (!group->ram_block_discard_allowed) {
- ram_block_discard_disable(false);
+ vfio_ram_block_discard_disable(group->container, false);
}
vfio_kvm_device_del_group(group);
vfio_disconnect_container(group);
@@ -2084,7 +2375,7 @@ int vfio_get_device(VFIOGroup *group, const char *name,
if (!group->ram_block_discard_allowed) {
group->ram_block_discard_allowed = true;
- ram_block_discard_disable(false);
+ vfio_ram_block_discard_disable(group->container, false);
}
}
diff --git a/hw/virtio/vhost-user.c b/hw/virtio/vhost-user.c
index 1ac4a2ebec..29ea2b4fce 100644
--- a/hw/virtio/vhost-user.c
+++ b/hw/virtio/vhost-user.c
@@ -1913,7 +1913,10 @@ static int vhost_user_backend_init(struct vhost_dev *dev, void *opaque,
if (err < 0) {
return -EPROTO;
}
+ } else {
+ dev->max_queues = 1;
}
+
if (dev->num_queues && dev->max_queues < dev->num_queues) {
error_setg(errp, "The maximum number of queues supported by the "
"backend is %" PRIu64, dev->max_queues);
diff --git a/hw/virtio/virtio-mem.c b/hw/virtio/virtio-mem.c
index 75aa7d6f1b..df91e454b2 100644
--- a/hw/virtio/virtio-mem.c
+++ b/hw/virtio/virtio-mem.c
@@ -145,7 +145,173 @@ static bool virtio_mem_is_busy(void)
return migration_in_incoming_postcopy() || !migration_is_idle();
}
-static bool virtio_mem_test_bitmap(VirtIOMEM *vmem, uint64_t start_gpa,
+typedef int (*virtio_mem_range_cb)(const VirtIOMEM *vmem, void *arg,
+ uint64_t offset, uint64_t size);
+
+static int virtio_mem_for_each_unplugged_range(const VirtIOMEM *vmem, void *arg,
+ virtio_mem_range_cb cb)
+{
+ unsigned long first_zero_bit, last_zero_bit;
+ uint64_t offset, size;
+ int ret = 0;
+
+ first_zero_bit = find_first_zero_bit(vmem->bitmap, vmem->bitmap_size);
+ while (first_zero_bit < vmem->bitmap_size) {
+ offset = first_zero_bit * vmem->block_size;
+ last_zero_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size,
+ first_zero_bit + 1) - 1;
+ size = (last_zero_bit - first_zero_bit + 1) * vmem->block_size;
+
+ ret = cb(vmem, arg, offset, size);
+ if (ret) {
+ break;
+ }
+ first_zero_bit = find_next_zero_bit(vmem->bitmap, vmem->bitmap_size,
+ last_zero_bit + 2);
+ }
+ return ret;
+}
+
+/*
+ * Adjust the memory section to cover the intersection with the given range.
+ *
+ * Returns false if the intersection is empty, otherwise returns true.
+ */
+static bool virito_mem_intersect_memory_section(MemoryRegionSection *s,
+ uint64_t offset, uint64_t size)
+{
+ uint64_t start = MAX(s->offset_within_region, offset);
+ uint64_t end = MIN(s->offset_within_region + int128_get64(s->size),
+ offset + size);
+
+ if (end <= start) {
+ return false;
+ }
+
+ s->offset_within_address_space += start - s->offset_within_region;
+ s->offset_within_region = start;
+ s->size = int128_make64(end - start);
+ return true;
+}
+
+typedef int (*virtio_mem_section_cb)(MemoryRegionSection *s, void *arg);
+
+static int virtio_mem_for_each_plugged_section(const VirtIOMEM *vmem,
+ MemoryRegionSection *s,
+ void *arg,
+ virtio_mem_section_cb cb)
+{
+ unsigned long first_bit, last_bit;
+ uint64_t offset, size;
+ int ret = 0;
+
+ first_bit = s->offset_within_region / vmem->bitmap_size;
+ first_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size, first_bit);
+ while (first_bit < vmem->bitmap_size) {
+ MemoryRegionSection tmp = *s;
+
+ offset = first_bit * vmem->block_size;
+ last_bit = find_next_zero_bit(vmem->bitmap, vmem->bitmap_size,
+ first_bit + 1) - 1;
+ size = (last_bit - first_bit + 1) * vmem->block_size;
+
+ if (!virito_mem_intersect_memory_section(&tmp, offset, size)) {
+ break;
+ }
+ ret = cb(&tmp, arg);
+ if (ret) {
+ break;
+ }
+ first_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size,
+ last_bit + 2);
+ }
+ return ret;
+}
+
+static int virtio_mem_notify_populate_cb(MemoryRegionSection *s, void *arg)
+{
+ RamDiscardListener *rdl = arg;
+
+ return rdl->notify_populate(rdl, s);
+}
+
+static int virtio_mem_notify_discard_cb(MemoryRegionSection *s, void *arg)
+{
+ RamDiscardListener *rdl = arg;
+
+ rdl->notify_discard(rdl, s);
+ return 0;
+}
+
+static void virtio_mem_notify_unplug(VirtIOMEM *vmem, uint64_t offset,
+ uint64_t size)
+{
+ RamDiscardListener *rdl;
+
+ QLIST_FOREACH(rdl, &vmem->rdl_list, next) {
+ MemoryRegionSection tmp = *rdl->section;
+
+ if (!virito_mem_intersect_memory_section(&tmp, offset, size)) {
+ continue;
+ }
+ rdl->notify_discard(rdl, &tmp);
+ }
+}
+
+static int virtio_mem_notify_plug(VirtIOMEM *vmem, uint64_t offset,
+ uint64_t size)
+{
+ RamDiscardListener *rdl, *rdl2;
+ int ret = 0;
+
+ QLIST_FOREACH(rdl, &vmem->rdl_list, next) {
+ MemoryRegionSection tmp = *rdl->section;
+
+ if (!virito_mem_intersect_memory_section(&tmp, offset, size)) {
+ continue;
+ }
+ ret = rdl->notify_populate(rdl, &tmp);
+ if (ret) {
+ break;
+ }
+ }
+
+ if (ret) {
+ /* Notify all already-notified listeners. */
+ QLIST_FOREACH(rdl2, &vmem->rdl_list, next) {
+ MemoryRegionSection tmp = *rdl->section;
+
+ if (rdl2 == rdl) {
+ break;
+ }
+ if (!virito_mem_intersect_memory_section(&tmp, offset, size)) {
+ continue;
+ }
+ rdl2->notify_discard(rdl2, &tmp);
+ }
+ }
+ return ret;
+}
+
+static void virtio_mem_notify_unplug_all(VirtIOMEM *vmem)
+{
+ RamDiscardListener *rdl;
+
+ if (!vmem->size) {
+ return;
+ }
+
+ QLIST_FOREACH(rdl, &vmem->rdl_list, next) {
+ if (rdl->double_discard_supported) {
+ rdl->notify_discard(rdl, rdl->section);
+ } else {
+ virtio_mem_for_each_plugged_section(vmem, rdl->section, rdl,
+ virtio_mem_notify_discard_cb);
+ }
+ }
+}
+
+static bool virtio_mem_test_bitmap(const VirtIOMEM *vmem, uint64_t start_gpa,
uint64_t size, bool plugged)
{
const unsigned long first_bit = (start_gpa - vmem->addr) / vmem->block_size;
@@ -198,7 +364,8 @@ static void virtio_mem_send_response_simple(VirtIOMEM *vmem,
virtio_mem_send_response(vmem, elem, &resp);
}
-static bool virtio_mem_valid_range(VirtIOMEM *vmem, uint64_t gpa, uint64_t size)
+static bool virtio_mem_valid_range(const VirtIOMEM *vmem, uint64_t gpa,
+ uint64_t size)
{
if (!QEMU_IS_ALIGNED(gpa, vmem->block_size)) {
return false;
@@ -219,19 +386,21 @@ static int virtio_mem_set_block_state(VirtIOMEM *vmem, uint64_t start_gpa,
uint64_t size, bool plug)
{
const uint64_t offset = start_gpa - vmem->addr;
- int ret;
+ RAMBlock *rb = vmem->memdev->mr.ram_block;
if (virtio_mem_is_busy()) {
return -EBUSY;
}
if (!plug) {
- ret = ram_block_discard_range(vmem->memdev->mr.ram_block, offset, size);
- if (ret) {
- error_report("Unexpected error discarding RAM: %s",
- strerror(-ret));
+ if (ram_block_discard_range(rb, offset, size)) {
return -EBUSY;
}
+ virtio_mem_notify_unplug(vmem, offset, size);
+ } else if (virtio_mem_notify_plug(vmem, offset, size)) {
+ /* Could be a mapping attempt resulted in memory getting populated. */
+ ram_block_discard_range(vmem->memdev->mr.ram_block, offset, size);
+ return -EBUSY;
}
virtio_mem_set_bitmap(vmem, start_gpa, size, plug);
return 0;
@@ -318,17 +487,16 @@ static void virtio_mem_resize_usable_region(VirtIOMEM *vmem,
static int virtio_mem_unplug_all(VirtIOMEM *vmem)
{
RAMBlock *rb = vmem->memdev->mr.ram_block;
- int ret;
if (virtio_mem_is_busy()) {
return -EBUSY;
}
- ret = ram_block_discard_range(rb, 0, qemu_ram_get_used_length(rb));
- if (ret) {
- error_report("Unexpected error discarding RAM: %s", strerror(-ret));
+ if (ram_block_discard_range(rb, 0, qemu_ram_get_used_length(rb))) {
return -EBUSY;
}
+ virtio_mem_notify_unplug_all(vmem);
+
bitmap_clear(vmem->bitmap, 0, vmem->bitmap_size);
if (vmem->size) {
vmem->size = 0;
@@ -551,7 +719,7 @@ static void virtio_mem_device_realize(DeviceState *dev, Error **errp)
return;
}
- if (ram_block_discard_require(true)) {
+ if (ram_block_coordinated_discard_require(true)) {
error_setg(errp, "Discarding RAM is disabled");
return;
}
@@ -559,7 +727,7 @@ static void virtio_mem_device_realize(DeviceState *dev, Error **errp)
ret = ram_block_discard_range(rb, 0, qemu_ram_get_used_length(rb));
if (ret) {
error_setg_errno(errp, -ret, "Unexpected error discarding RAM");
- ram_block_discard_require(false);
+ ram_block_coordinated_discard_require(false);
return;
}
@@ -577,6 +745,13 @@ static void virtio_mem_device_realize(DeviceState *dev, Error **errp)
vmstate_register_ram(&vmem->memdev->mr, DEVICE(vmem));
qemu_register_reset(virtio_mem_system_reset, vmem);
precopy_add_notifier(&vmem->precopy_notifier);
+
+ /*
+ * Set ourselves as RamDiscardManager before the plug handler maps the
+ * memory region and exposes it via an address space.
+ */
+ memory_region_set_ram_discard_manager(&vmem->memdev->mr,
+ RAM_DISCARD_MANAGER(vmem));
}
static void virtio_mem_device_unrealize(DeviceState *dev)
@@ -584,6 +759,11 @@ static void virtio_mem_device_unrealize(DeviceState *dev)
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
VirtIOMEM *vmem = VIRTIO_MEM(dev);
+ /*
+ * The unplug handler unmapped the memory region, it cannot be
+ * found via an address space anymore. Unset ourselves.
+ */
+ memory_region_set_ram_discard_manager(&vmem->memdev->mr, NULL);
precopy_remove_notifier(&vmem->precopy_notifier);
qemu_unregister_reset(virtio_mem_system_reset, vmem);
vmstate_unregister_ram(&vmem->memdev->mr, DEVICE(vmem));
@@ -591,43 +771,47 @@ static void virtio_mem_device_unrealize(DeviceState *dev)
virtio_del_queue(vdev, 0);
virtio_cleanup(vdev);
g_free(vmem->bitmap);
- ram_block_discard_require(false);
+ ram_block_coordinated_discard_require(false);
}
-static int virtio_mem_restore_unplugged(VirtIOMEM *vmem)
+static int virtio_mem_discard_range_cb(const VirtIOMEM *vmem, void *arg,
+ uint64_t offset, uint64_t size)
{
RAMBlock *rb = vmem->memdev->mr.ram_block;
- unsigned long first_zero_bit, last_zero_bit;
- uint64_t offset, length;
- int ret;
- /* Find consecutive unplugged blocks and discard the consecutive range. */
- first_zero_bit = find_first_zero_bit(vmem->bitmap, vmem->bitmap_size);
- while (first_zero_bit < vmem->bitmap_size) {
- offset = first_zero_bit * vmem->block_size;
- last_zero_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size,
- first_zero_bit + 1) - 1;
- length = (last_zero_bit - first_zero_bit + 1) * vmem->block_size;
+ return ram_block_discard_range(rb, offset, size) ? -EINVAL : 0;
+}
- ret = ram_block_discard_range(rb, offset, length);
- if (ret) {
- error_report("Unexpected error discarding RAM: %s",
- strerror(-ret));
- return -EINVAL;
- }
- first_zero_bit = find_next_zero_bit(vmem->bitmap, vmem->bitmap_size,
- last_zero_bit + 2);
- }
- return 0;
+static int virtio_mem_restore_unplugged(VirtIOMEM *vmem)
+{
+ /* Make sure all memory is really discarded after migration. */
+ return virtio_mem_for_each_unplugged_range(vmem, NULL,
+ virtio_mem_discard_range_cb);
}
static int virtio_mem_post_load(void *opaque, int version_id)
{
+ VirtIOMEM *vmem = VIRTIO_MEM(opaque);
+ RamDiscardListener *rdl;
+ int ret;
+
+ /*
+ * We started out with all memory discarded and our memory region is mapped
+ * into an address space. Replay, now that we updated the bitmap.
+ */
+ QLIST_FOREACH(rdl, &vmem->rdl_list, next) {
+ ret = virtio_mem_for_each_plugged_section(vmem, rdl->section, rdl,
+ virtio_mem_notify_populate_cb);
+ if (ret) {
+ return ret;
+ }
+ }
+
if (migration_in_incoming_postcopy()) {
return 0;
}
- return virtio_mem_restore_unplugged(VIRTIO_MEM(opaque));
+ return virtio_mem_restore_unplugged(vmem);
}
typedef struct VirtIOMEMMigSanityChecks {
@@ -702,6 +886,7 @@ static const VMStateDescription vmstate_virtio_mem_device = {
.name = "virtio-mem-device",
.minimum_version_id = 1,
.version_id = 1,
+ .priority = MIG_PRI_VIRTIO_MEM,
.post_load = virtio_mem_post_load,
.fields = (VMStateField[]) {
VMSTATE_WITH_TMP(VirtIOMEM, VirtIOMEMMigSanityChecks,
@@ -872,28 +1057,19 @@ static void virtio_mem_set_block_size(Object *obj, Visitor *v, const char *name,
vmem->block_size = value;
}
-static void virtio_mem_precopy_exclude_unplugged(VirtIOMEM *vmem)
+static int virtio_mem_precopy_exclude_range_cb(const VirtIOMEM *vmem, void *arg,
+ uint64_t offset, uint64_t size)
{
void * const host = qemu_ram_get_host_addr(vmem->memdev->mr.ram_block);
- unsigned long first_zero_bit, last_zero_bit;
- uint64_t offset, length;
- /*
- * Find consecutive unplugged blocks and exclude them from migration.
- *
- * Note: Blocks cannot get (un)plugged during precopy, no locking needed.
- */
- first_zero_bit = find_first_zero_bit(vmem->bitmap, vmem->bitmap_size);
- while (first_zero_bit < vmem->bitmap_size) {
- offset = first_zero_bit * vmem->block_size;
- last_zero_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size,
- first_zero_bit + 1) - 1;
- length = (last_zero_bit - first_zero_bit + 1) * vmem->block_size;
+ qemu_guest_free_page_hint(host + offset, size);
+ return 0;
+}
- qemu_guest_free_page_hint(host + offset, length);
- first_zero_bit = find_next_zero_bit(vmem->bitmap, vmem->bitmap_size,
- last_zero_bit + 2);
- }
+static void virtio_mem_precopy_exclude_unplugged(VirtIOMEM *vmem)
+{
+ virtio_mem_for_each_unplugged_range(vmem, NULL,
+ virtio_mem_precopy_exclude_range_cb);
}
static int virtio_mem_precopy_notify(NotifierWithReturn *n, void *data)
@@ -918,6 +1094,7 @@ static void virtio_mem_instance_init(Object *obj)
notifier_list_init(&vmem->size_change_notifiers);
vmem->precopy_notifier.notify = virtio_mem_precopy_notify;
+ QLIST_INIT(&vmem->rdl_list);
object_property_add(obj, VIRTIO_MEM_SIZE_PROP, "size", virtio_mem_get_size,
NULL, NULL, NULL);
@@ -937,11 +1114,107 @@ static Property virtio_mem_properties[] = {
DEFINE_PROP_END_OF_LIST(),
};
+static uint64_t virtio_mem_rdm_get_min_granularity(const RamDiscardManager *rdm,
+ const MemoryRegion *mr)
+{
+ const VirtIOMEM *vmem = VIRTIO_MEM(rdm);
+
+ g_assert(mr == &vmem->memdev->mr);
+ return vmem->block_size;
+}
+
+static bool virtio_mem_rdm_is_populated(const RamDiscardManager *rdm,
+ const MemoryRegionSection *s)
+{
+ const VirtIOMEM *vmem = VIRTIO_MEM(rdm);
+ uint64_t start_gpa = vmem->addr + s->offset_within_region;
+ uint64_t end_gpa = start_gpa + int128_get64(s->size);
+
+ g_assert(s->mr == &vmem->memdev->mr);
+
+ start_gpa = QEMU_ALIGN_DOWN(start_gpa, vmem->block_size);
+ end_gpa = QEMU_ALIGN_UP(end_gpa, vmem->block_size);
+
+ if (!virtio_mem_valid_range(vmem, start_gpa, end_gpa - start_gpa)) {
+ return false;
+ }
+
+ return virtio_mem_test_bitmap(vmem, start_gpa, end_gpa - start_gpa, true);
+}
+
+struct VirtIOMEMReplayData {
+ void *fn;
+ void *opaque;
+};
+
+static int virtio_mem_rdm_replay_populated_cb(MemoryRegionSection *s, void *arg)
+{
+ struct VirtIOMEMReplayData *data = arg;
+
+ return ((ReplayRamPopulate)data->fn)(s, data->opaque);
+}
+
+static int virtio_mem_rdm_replay_populated(const RamDiscardManager *rdm,
+ MemoryRegionSection *s,
+ ReplayRamPopulate replay_fn,
+ void *opaque)
+{
+ const VirtIOMEM *vmem = VIRTIO_MEM(rdm);
+ struct VirtIOMEMReplayData data = {
+ .fn = replay_fn,
+ .opaque = opaque,
+ };
+
+ g_assert(s->mr == &vmem->memdev->mr);
+ return virtio_mem_for_each_plugged_section(vmem, s, &data,
+ virtio_mem_rdm_replay_populated_cb);
+}
+
+static void virtio_mem_rdm_register_listener(RamDiscardManager *rdm,
+ RamDiscardListener *rdl,
+ MemoryRegionSection *s)
+{
+ VirtIOMEM *vmem = VIRTIO_MEM(rdm);
+ int ret;
+
+ g_assert(s->mr == &vmem->memdev->mr);
+ rdl->section = memory_region_section_new_copy(s);
+
+ QLIST_INSERT_HEAD(&vmem->rdl_list, rdl, next);
+ ret = virtio_mem_for_each_plugged_section(vmem, rdl->section, rdl,
+ virtio_mem_notify_populate_cb);
+ if (ret) {
+ error_report("%s: Replaying plugged ranges failed: %s", __func__,
+ strerror(-ret));
+ }
+}
+
+static void virtio_mem_rdm_unregister_listener(RamDiscardManager *rdm,
+ RamDiscardListener *rdl)
+{
+ VirtIOMEM *vmem = VIRTIO_MEM(rdm);
+
+ g_assert(rdl->section->mr == &vmem->memdev->mr);
+ if (vmem->size) {
+ if (rdl->double_discard_supported) {
+ rdl->notify_discard(rdl, rdl->section);
+ } else {
+ virtio_mem_for_each_plugged_section(vmem, rdl->section, rdl,
+ virtio_mem_notify_discard_cb);
+ }
+ }
+
+ memory_region_section_free_copy(rdl->section);
+ rdl->section = NULL;
+ QLIST_REMOVE(rdl, next);
+}
+
static void virtio_mem_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
VirtIOMEMClass *vmc = VIRTIO_MEM_CLASS(klass);
+ RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_CLASS(klass);
device_class_set_props(dc, virtio_mem_properties);
dc->vmsd = &vmstate_virtio_mem;
@@ -957,6 +1230,12 @@ static void virtio_mem_class_init(ObjectClass *klass, void *data)
vmc->get_memory_region = virtio_mem_get_memory_region;
vmc->add_size_change_notifier = virtio_mem_add_size_change_notifier;
vmc->remove_size_change_notifier = virtio_mem_remove_size_change_notifier;
+
+ rdmc->get_min_granularity = virtio_mem_rdm_get_min_granularity;
+ rdmc->is_populated = virtio_mem_rdm_is_populated;
+ rdmc->replay_populated = virtio_mem_rdm_replay_populated;
+ rdmc->register_listener = virtio_mem_rdm_register_listener;
+ rdmc->unregister_listener = virtio_mem_rdm_unregister_listener;
}
static const TypeInfo virtio_mem_info = {
@@ -966,6 +1245,10 @@ static const TypeInfo virtio_mem_info = {
.instance_init = virtio_mem_instance_init,
.class_init = virtio_mem_class_init,
.class_size = sizeof(VirtIOMEMClass),
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_RAM_DISCARD_MANAGER },
+ { }
+ },
};
static void virtio_register_types(void)
diff --git a/hw/virtio/virtio-mmio.c b/hw/virtio/virtio-mmio.c
index 5952471b38..1af48a1b04 100644
--- a/hw/virtio/virtio-mmio.c
+++ b/hw/virtio/virtio-mmio.c
@@ -29,6 +29,7 @@
#include "qemu/host-utils.h"
#include "qemu/module.h"
#include "sysemu/kvm.h"
+#include "sysemu/replay.h"
#include "hw/virtio/virtio-mmio.h"
#include "qemu/error-report.h"
#include "qemu/log.h"
@@ -740,6 +741,11 @@ static void virtio_mmio_realizefn(DeviceState *d, Error **errp)
proxy->flags &= ~VIRTIO_IOMMIO_FLAG_USE_IOEVENTFD;
}
+ /* fd-based ioevents can't be synchronized in record/replay */
+ if (replay_mode != REPLAY_MODE_NONE) {
+ proxy->flags &= ~VIRTIO_IOMMIO_FLAG_USE_IOEVENTFD;
+ }
+
if (proxy->legacy) {
memory_region_init_io(&proxy->iomem, OBJECT(d),
&virtio_legacy_mem_ops, proxy,
diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c
index b321604d9b..433060ac02 100644
--- a/hw/virtio/virtio-pci.c
+++ b/hw/virtio/virtio-pci.c
@@ -37,6 +37,7 @@
#include "qemu/range.h"
#include "hw/virtio/virtio-bus.h"
#include "qapi/visitor.h"
+#include "sysemu/replay.h"
#define VIRTIO_PCI_REGION_SIZE(dev) VIRTIO_PCI_CONFIG_OFF(msix_present(dev))
@@ -423,6 +424,11 @@ static uint64_t virtio_pci_config_read(void *opaque, hwaddr addr,
VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
uint32_t config = VIRTIO_PCI_CONFIG_SIZE(&proxy->pci_dev);
uint64_t val = 0;
+
+ if (vdev == NULL) {
+ return UINT64_MAX;
+ }
+
if (addr < config) {
return virtio_ioport_read(proxy, addr);
}
@@ -454,6 +460,11 @@ static void virtio_pci_config_write(void *opaque, hwaddr addr,
VirtIOPCIProxy *proxy = opaque;
uint32_t config = VIRTIO_PCI_CONFIG_SIZE(&proxy->pci_dev);
VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+
+ if (vdev == NULL) {
+ return;
+ }
+
if (addr < config) {
virtio_ioport_write(proxy, addr, val);
return;
@@ -1146,6 +1157,10 @@ static uint64_t virtio_pci_common_read(void *opaque, hwaddr addr,
uint32_t val = 0;
int i;
+ if (vdev == NULL) {
+ return UINT64_MAX;
+ }
+
switch (addr) {
case VIRTIO_PCI_COMMON_DFSELECT:
val = proxy->dfselect;
@@ -1229,6 +1244,10 @@ static void virtio_pci_common_write(void *opaque, hwaddr addr,
VirtIOPCIProxy *proxy = opaque;
VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
+ if (vdev == NULL) {
+ return;
+ }
+
switch (addr) {
case VIRTIO_PCI_COMMON_DFSELECT:
proxy->dfselect = val;
@@ -1330,6 +1349,11 @@ static void virtio_pci_common_write(void *opaque, hwaddr addr,
static uint64_t virtio_pci_notify_read(void *opaque, hwaddr addr,
unsigned size)
{
+ VirtIOPCIProxy *proxy = opaque;
+ if (virtio_bus_get_device(&proxy->bus) == NULL) {
+ return UINT64_MAX;
+ }
+
return 0;
}
@@ -1367,7 +1391,7 @@ static uint64_t virtio_pci_isr_read(void *opaque, hwaddr addr,
uint64_t val;
if (vdev == NULL) {
- return 0;
+ return UINT64_MAX;
}
val = qatomic_xchg(&vdev->isr, 0);
@@ -1388,7 +1412,7 @@ static uint64_t virtio_pci_device_read(void *opaque, hwaddr addr,
uint64_t val;
if (vdev == NULL) {
- return 0;
+ return UINT64_MAX;
}
switch (size) {
@@ -1760,6 +1784,11 @@ static void virtio_pci_realize(PCIDevice *pci_dev, Error **errp)
proxy->flags &= ~VIRTIO_PCI_FLAG_USE_IOEVENTFD;
}
+ /* fd-based ioevents can't be synchronized in record/replay */
+ if (replay_mode != REPLAY_MODE_NONE) {
+ proxy->flags &= ~VIRTIO_PCI_FLAG_USE_IOEVENTFD;
+ }
+
/*
* virtio pci bar layout used by default.
* subclasses can re-arrange things if needed.
diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c
index ab516ac614..874377f37a 100644
--- a/hw/virtio/virtio.c
+++ b/hw/virtio/virtio.c
@@ -2447,6 +2447,7 @@ static void virtio_set_isr(VirtIODevice *vdev, int value)
}
}
+/* Called within rcu_read_lock(). */
static bool virtio_split_should_notify(VirtIODevice *vdev, VirtQueue *vq)
{
uint16_t old, new;
@@ -2483,6 +2484,7 @@ static bool vring_packed_need_event(VirtQueue *vq, bool wrap,
return vring_need_event(off, new, old);
}
+/* Called within rcu_read_lock(). */
static bool virtio_packed_should_notify(VirtIODevice *vdev, VirtQueue *vq)
{
VRingPackedDescEvent e;
@@ -3728,6 +3730,10 @@ static int virtio_device_start_ioeventfd_impl(VirtIODevice *vdev)
VirtioBusState *qbus = VIRTIO_BUS(qdev_get_parent_bus(DEVICE(vdev)));
int i, n, r, err;
+ /*
+ * Batch all the host notifiers in a single transaction to avoid
+ * quadratic time complexity in address_space_update_ioeventfds().
+ */
memory_region_transaction_begin();
for (n = 0; n < VIRTIO_QUEUE_MAX; n++) {
VirtQueue *vq = &vdev->vq[n];
@@ -3766,6 +3772,10 @@ assign_error:
r = virtio_bus_set_host_notifier(qbus, n, false);
assert(r >= 0);
}
+ /*
+ * The transaction expects the ioeventfds to be open when it
+ * commits. Do it now, before the cleanup loop.
+ */
memory_region_transaction_commit();
while (--i >= 0) {
@@ -3790,6 +3800,10 @@ static void virtio_device_stop_ioeventfd_impl(VirtIODevice *vdev)
VirtioBusState *qbus = VIRTIO_BUS(qdev_get_parent_bus(DEVICE(vdev)));
int n, r;
+ /*
+ * Batch all the host notifiers in a single transaction to avoid
+ * quadratic time complexity in address_space_update_ioeventfds().
+ */
memory_region_transaction_begin();
for (n = 0; n < VIRTIO_QUEUE_MAX; n++) {
VirtQueue *vq = &vdev->vq[n];
@@ -3801,6 +3815,10 @@ static void virtio_device_stop_ioeventfd_impl(VirtIODevice *vdev)
r = virtio_bus_set_host_notifier(qbus, n, false);
assert(r >= 0);
}
+ /*
+ * The transaction expects the ioeventfds to be open when it
+ * commits. Do it now, before the cleanup loop.
+ */
memory_region_transaction_commit();
for (n = 0; n < VIRTIO_QUEUE_MAX; n++) {