From 95be1196030c003a65052fc7b8a3394fdac690a8 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sun, 12 Feb 2012 21:02:01 +0200 Subject: [PATCH 01/21] pci_bridge: fix status: do not override cap bit --- hw/pci_bridge.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/pci_bridge.c b/hw/pci_bridge.c index 1ed43394de..fea3873943 100644 --- a/hw/pci_bridge.c +++ b/hw/pci_bridge.c @@ -305,8 +305,8 @@ int pci_bridge_initfn(PCIDevice *dev) PCIBridge *br = DO_UPCAST(PCIBridge, dev, dev); PCIBus *sec_bus = &br->sec_bus; - pci_set_word(dev->config + PCI_STATUS, - PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK); + pci_word_test_and_set_mask(dev->config + PCI_STATUS, + PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK); pci_config_set_class(dev->config, PCI_CLASS_BRIDGE_PCI); dev->config[PCI_HEADER_TYPE] = (dev->config[PCI_HEADER_TYPE] & PCI_HEADER_TYPE_MULTI_FUNCTION) | From fdba487859bff44db21dc119ee2b1b3691c69f0f Mon Sep 17 00:00:00 2001 From: Anthony PERARD Date: Mon, 13 Feb 2012 12:20:13 +0000 Subject: [PATCH 02/21] pci: Do not check if a bus exist in pci_parse_devaddr. Actually, pci_parse_devaddr checks if the dom/bus of the PCI address exist. But this should be the jobs of a caller. In fact, the two callers of this function will try to retrieve the PCIBus related to the devaddr and return an error if they cannot. Signed-off-by: Anthony PERARD Signed-off-by: Michael S. Tsirkin --- hw/pci.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/hw/pci.c b/hw/pci.c index bf046bfcad..38e1de566c 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -529,10 +529,6 @@ int pci_parse_devaddr(const char *addr, int *domp, int *busp, if (*e) return -1; - /* Note: QEMU doesn't implement domains other than 0 */ - if (!pci_find_bus(pci_find_root_bus(dom), bus)) - return -1; - *domp = dom; *busp = bus; *slotp = slot; From 15a7a7780675eb99c093e29deefc5d6cc371bd87 Mon Sep 17 00:00:00 2001 From: Anthony PERARD Date: Fri, 17 Feb 2012 17:08:36 +0000 Subject: [PATCH 03/21] pci_regs: Fix value of PCI_EXP_TYPE_RC_EC. Value check in PCI Express Base Specification rev 1.1 Signed-off-by: Anthony PERARD Signed-off-by: Michael S. Tsirkin --- hw/pci_regs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/pci_regs.h b/hw/pci_regs.h index e8357c3ea6..6b4251593b 100644 --- a/hw/pci_regs.h +++ b/hw/pci_regs.h @@ -393,7 +393,7 @@ #define PCI_EXP_TYPE_DOWNSTREAM 0x6 /* Downstream Port */ #define PCI_EXP_TYPE_PCI_BRIDGE 0x7 /* PCI/PCI-X Bridge */ #define PCI_EXP_TYPE_RC_END 0x9 /* Root Complex Integrated Endpoint */ -#define PCI_EXP_TYPE_RC_EC 0x10 /* Root Complex Event Collector */ +#define PCI_EXP_TYPE_RC_EC 0xa /* Root Complex Event Collector */ #define PCI_EXP_FLAGS_SLOT 0x0100 /* Slot implemented */ #define PCI_EXP_FLAGS_IRQ 0x3e00 /* Interrupt message number */ #define PCI_EXP_DEVCAP 4 /* Device capabilities */ From 94a09e2c846374a96719cda2b4e1312d8c4b08a7 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Wed, 15 Feb 2012 23:32:00 +0200 Subject: [PATCH 04/21] pci: don't export an internal function Make an internal function, pci_parse_devaddr, static. Signed-off-by: Michael S. Tsirkin --- hw/pci.c | 2 +- hw/pci.h | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/hw/pci.c b/hw/pci.c index 38e1de566c..691322da0f 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -478,7 +478,7 @@ static void pci_set_default_subsystem_id(PCIDevice *pci_dev) * Parse [[:]:], return -1 on error if funcp == NULL * [[:]:]., return -1 on error */ -int pci_parse_devaddr(const char *addr, int *domp, int *busp, +static int pci_parse_devaddr(const char *addr, int *domp, int *busp, unsigned int *slotp, unsigned int *funcp) { const char *p; diff --git a/hw/pci.h b/hw/pci.h index 4f19fdbd89..403aadb8f9 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -304,8 +304,6 @@ PCIDevice *pci_find_device(PCIBus *bus, int bus_num, uint8_t devfn); int pci_qdev_find_device(const char *id, PCIDevice **pdev); PCIBus *pci_get_bus_devfn(int *devfnp, const char *devaddr); -int pci_parse_devaddr(const char *addr, int *domp, int *busp, - unsigned int *slotp, unsigned int *funcp); int pci_read_devaddr(Monitor *mon, const char *addr, int *domp, int *busp, unsigned *slotp); From d662210a440bf90bf0f5fb5eb8abd6244478c97d Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sun, 19 Feb 2012 18:16:02 +0200 Subject: [PATCH 05/21] pci: make another unused extern function static Make pci_find_bus static and rename to pci_find_bus_nr to match functionality. Signed-off-by: Michael S. Tsirkin --- hw/pci.c | 15 ++++++++------- hw/pci.h | 1 - 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/hw/pci.c b/hw/pci.c index 691322da0f..3ca5f4c820 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -63,6 +63,7 @@ struct BusInfo pci_bus_info = { } }; +static PCIBus *pci_find_bus_nr(PCIBus *bus, int bus_num); static void pci_update_mappings(PCIDevice *d); static void pci_set_irq(void *opaque, int irq_num, int level); static int pci_add_option_rom(PCIDevice *pdev, bool is_default_rom); @@ -558,7 +559,7 @@ PCIBus *pci_get_bus_devfn(int *devfnp, const char *devaddr) if (!devaddr) { *devfnp = -1; - return pci_find_bus(pci_find_root_bus(0), 0); + return pci_find_bus_nr(pci_find_root_bus(0), 0); } if (pci_parse_devaddr(devaddr, &dom, &bus, &slot, NULL) < 0) { @@ -566,7 +567,7 @@ PCIBus *pci_get_bus_devfn(int *devfnp, const char *devaddr) } *devfnp = PCI_DEVFN(slot, 0); - return pci_find_bus(pci_find_root_bus(dom), bus); + return pci_find_bus_nr(pci_find_root_bus(dom), bus); } static void pci_init_cmask(PCIDevice *dev) @@ -1140,7 +1141,7 @@ static void pci_for_each_device_under_bus(PCIBus *bus, void pci_for_each_device(PCIBus *bus, int bus_num, void (*fn)(PCIBus *b, PCIDevice *d)) { - bus = pci_find_bus(bus, bus_num); + bus = pci_find_bus_nr(bus, bus_num); if (bus) { pci_for_each_device_under_bus(bus, fn); @@ -1227,7 +1228,7 @@ static PciBridgeInfo *qmp_query_pci_bridge(PCIDevice *dev, PCIBus *bus, info->bus.prefetchable_range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_MEM_PREFETCH); if (dev->config[PCI_SECONDARY_BUS] != 0) { - PCIBus *child_bus = pci_find_bus(bus, dev->config[PCI_SECONDARY_BUS]); + PCIBus *child_bus = pci_find_bus_nr(bus, dev->config[PCI_SECONDARY_BUS]); if (child_bus) { info->has_devices = true; info->devices = qmp_query_pci_devices(child_bus, dev->config[PCI_SECONDARY_BUS]); @@ -1306,7 +1307,7 @@ static PciInfo *qmp_query_pci_bus(PCIBus *bus, int bus_num) { PciInfo *info = NULL; - bus = pci_find_bus(bus, bus_num); + bus = pci_find_bus_nr(bus, bus_num); if (bus) { info = g_malloc0(sizeof(*info)); info->bus = bus_num; @@ -1416,7 +1417,7 @@ static bool pci_secondary_bus_in_range(PCIDevice *dev, int bus_num) bus_num <= dev->config[PCI_SUBORDINATE_BUS]; } -PCIBus *pci_find_bus(PCIBus *bus, int bus_num) +static PCIBus *pci_find_bus_nr(PCIBus *bus, int bus_num) { PCIBus *sec; @@ -1452,7 +1453,7 @@ PCIBus *pci_find_bus(PCIBus *bus, int bus_num) PCIDevice *pci_find_device(PCIBus *bus, int bus_num, uint8_t devfn) { - bus = pci_find_bus(bus, bus_num); + bus = pci_find_bus_nr(bus, bus_num); if (!bus) return NULL; diff --git a/hw/pci.h b/hw/pci.h index 403aadb8f9..704d3f3b44 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -299,7 +299,6 @@ int pci_bus_num(PCIBus *s); void pci_for_each_device(PCIBus *bus, int bus_num, void (*fn)(PCIBus *bus, PCIDevice *d)); PCIBus *pci_find_root_bus(int domain); int pci_find_domain(const PCIBus *bus); -PCIBus *pci_find_bus(PCIBus *bus, int bus_num); PCIDevice *pci_find_device(PCIBus *bus, int bus_num, uint8_t devfn); int pci_qdev_find_device(const char *id, PCIDevice **pdev); PCIBus *pci_get_bus_devfn(int *devfnp, const char *devaddr); From 8a3d80faf7794820da5e9666e4adf6a7d4f80f7b Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Mon, 20 Feb 2012 01:34:01 +0200 Subject: [PATCH 06/21] pci_bridge: user-friendly default bus name For a pci bridge device, if we don't override the name with custom code, the bus will be addressed as .0, where id is the id specified by the user. Since PCI Bridge devices have a single bus each, we don't need the index: address the bus using the parent device name. This is better since this way users don't care about our internal bus/device distinctions. As far as I could see, we only have built-in bridges at this point which always override the name. So this change will only affect ioh3420.c. Signed-off-by: Michael S. Tsirkin --- hw/pci_bridge.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hw/pci_bridge.c b/hw/pci_bridge.c index fea3873943..eeee8a6ad0 100644 --- a/hw/pci_bridge.c +++ b/hw/pci_bridge.c @@ -314,6 +314,16 @@ int pci_bridge_initfn(PCIDevice *dev) pci_set_word(dev->config + PCI_SEC_STATUS, PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK); + /* + * If we don't specify the name, the bus will be addressed as .0, where + * id is the device id. + * Since PCI Bridge devices have a single bus each, we don't need the index: + * let users address the bus using the device name. + */ + if (!br->bus_name && dev->qdev.id && *dev->qdev.id) { + br->bus_name = dev->qdev.id; + } + qbus_create_inplace(&sec_bus->qbus, &pci_bus_info, &dev->qdev, br->bus_name); sec_bus->parent_dev = dev; From 1dc324d20f7404fd6a416f16c2cb9a4ec50a4dd7 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sun, 12 Feb 2012 14:12:21 +0200 Subject: [PATCH 07/21] shpc: standard hot plug controller This adds support for SHPC interface, as defined by PCI Standard Hot-Plug Controller and Subsystem Specification, Rev 1.0 http://www.pcisig.com/specifications/conventional/pci_hot_plug/SHPC_10 Only SHPC intergrated with a PCI-to-PCI bridge is supported, SHPC integrated with a host bridge would need more work. All main SHPC features are supported: - MRL sensor - Attention button - Attention indicator - Power indicator Wake on hotplug and serr generation are stubbed out but unused as we don't have interfaces to generate these events ATM. One issue that isn't completely resolved is that qemu currently expects an "eject" interface, which SHPC does not provide: it merely removes the power to device and it's up to the user to remove the device from slot. This patch works around that by ejecting the device when power is removed and power LED goes off. Signed-off-by: Michael S. Tsirkin --- Makefile.objs | 1 + hw/pci.h | 6 + hw/shpc.c | 681 ++++++++++++++++++++++++++++++++++++++++++++++++++ hw/shpc.h | 48 ++++ qemu-common.h | 1 + 5 files changed, 737 insertions(+) create mode 100644 hw/shpc.c create mode 100644 hw/shpc.h diff --git a/Makefile.objs b/Makefile.objs index 226b01df96..1e8034cde9 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -215,6 +215,7 @@ hw-obj-$(CONFIG_VIRTIO_PCI) += virtio-pci.o hw-obj-y += fw_cfg.o hw-obj-$(CONFIG_PCI) += pci.o pci_bridge.o hw-obj-$(CONFIG_PCI) += msix.o msi.o +hw-obj-$(CONFIG_PCI) += shpc.o hw-obj-$(CONFIG_PCI) += pci_host.o pcie_host.o hw-obj-$(CONFIG_PCI) += ioh3420.o xio3130_upstream.o xio3130_downstream.o hw-obj-y += watchdog.o diff --git a/hw/pci.h b/hw/pci.h index 704d3f3b44..dc4d9d10a7 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -126,6 +126,9 @@ enum { /* command register SERR bit enabled */ #define QEMU_PCI_CAP_SERR_BITNR 4 QEMU_PCI_CAP_SERR = (1 << QEMU_PCI_CAP_SERR_BITNR), + /* Standard hot plug controller. */ +#define QEMU_PCI_SHPC_BITNR 5 + QEMU_PCI_CAP_SHPC = (1 << QEMU_PCI_SHPC_BITNR), }; #define TYPE_PCI_DEVICE "pci-device" @@ -230,6 +233,9 @@ struct PCIDevice { /* PCI Express */ PCIExpressDevice exp; + /* SHPC */ + SHPCDevice *shpc; + /* Location of option rom */ char *romfile; bool has_rom; diff --git a/hw/shpc.c b/hw/shpc.c new file mode 100644 index 0000000000..a5baf246f1 --- /dev/null +++ b/hw/shpc.c @@ -0,0 +1,681 @@ +#include +#include +#include "range.h" +#include "range.h" +#include "shpc.h" +#include "pci.h" +#include "pci_internals.h" +#include "msi.h" + +/* TODO: model power only and disabled slot states. */ +/* TODO: handle SERR and wakeups */ +/* TODO: consider enabling 66MHz support */ + +/* TODO: remove fully only on state DISABLED and LED off. + * track state to properly record this. */ + +/* SHPC Working Register Set */ +#define SHPC_BASE_OFFSET 0x00 /* 4 bytes */ +#define SHPC_SLOTS_33 0x04 /* 4 bytes. Also encodes PCI-X slots. */ +#define SHPC_SLOTS_66 0x08 /* 4 bytes. */ +#define SHPC_NSLOTS 0x0C /* 1 byte */ +#define SHPC_FIRST_DEV 0x0D /* 1 byte */ +#define SHPC_PHYS_SLOT 0x0E /* 2 byte */ +#define SHPC_PHYS_NUM_MAX 0x7ff +#define SHPC_PHYS_NUM_UP 0x2000 +#define SHPC_PHYS_MRL 0x4000 +#define SHPC_PHYS_BUTTON 0x8000 +#define SHPC_SEC_BUS 0x10 /* 2 bytes */ +#define SHPC_SEC_BUS_33 0x0 +#define SHPC_SEC_BUS_66 0x1 /* Unused */ +#define SHPC_SEC_BUS_MASK 0x7 +#define SHPC_MSI_CTL 0x12 /* 1 byte */ +#define SHPC_PROG_IFC 0x13 /* 1 byte */ +#define SHPC_PROG_IFC_1_0 0x1 +#define SHPC_CMD_CODE 0x14 /* 1 byte */ +#define SHPC_CMD_TRGT 0x15 /* 1 byte */ +#define SHPC_CMD_TRGT_MIN 0x1 +#define SHPC_CMD_TRGT_MAX 0x1f +#define SHPC_CMD_STATUS 0x16 /* 2 bytes */ +#define SHPC_CMD_STATUS_BUSY 0x1 +#define SHPC_CMD_STATUS_MRL_OPEN 0x2 +#define SHPC_CMD_STATUS_INVALID_CMD 0x4 +#define SHPC_CMD_STATUS_INVALID_MODE 0x8 +#define SHPC_INT_LOCATOR 0x18 /* 4 bytes */ +#define SHPC_INT_COMMAND 0x1 +#define SHPC_SERR_LOCATOR 0x1C /* 4 bytes */ +#define SHPC_SERR_INT 0x20 /* 4 bytes */ +#define SHPC_INT_DIS 0x1 +#define SHPC_SERR_DIS 0x2 +#define SHPC_CMD_INT_DIS 0x4 +#define SHPC_ARB_SERR_DIS 0x8 +#define SHPC_CMD_DETECTED 0x10000 +#define SHPC_ARB_DETECTED 0x20000 + /* 4 bytes * slot # (start from 0) */ +#define SHPC_SLOT_REG(s) (0x24 + (s) * 4) + /* 2 bytes */ +#define SHPC_SLOT_STATUS(s) (0x0 + SHPC_SLOT_REG(s)) + +/* Same slot state masks are used for command and status registers */ +#define SHPC_SLOT_STATE_MASK 0x03 +#define SHPC_SLOT_STATE_SHIFT \ + (ffs(SHPC_SLOT_STATE_MASK) - 1) + +#define SHPC_STATE_NO 0x0 +#define SHPC_STATE_PWRONLY 0x1 +#define SHPC_STATE_ENABLED 0x2 +#define SHPC_STATE_DISABLED 0x3 + +#define SHPC_SLOT_PWR_LED_MASK 0xC +#define SHPC_SLOT_PWR_LED_SHIFT \ + (ffs(SHPC_SLOT_PWR_LED_MASK) - 1) +#define SHPC_SLOT_ATTN_LED_MASK 0x30 +#define SHPC_SLOT_ATTN_LED_SHIFT \ + (ffs(SHPC_SLOT_ATTN_LED_MASK) - 1) + +#define SHPC_LED_NO 0x0 +#define SHPC_LED_ON 0x1 +#define SHPC_LED_BLINK 0x2 +#define SHPC_LED_OFF 0x3 + +#define SHPC_SLOT_STATUS_PWR_FAULT 0x40 +#define SHPC_SLOT_STATUS_BUTTON 0x80 +#define SHPC_SLOT_STATUS_MRL_OPEN 0x100 +#define SHPC_SLOT_STATUS_66 0x200 +#define SHPC_SLOT_STATUS_PRSNT_MASK 0xC00 +#define SHPC_SLOT_STATUS_PRSNT_EMPTY 0x3 +#define SHPC_SLOT_STATUS_PRSNT_25W 0x1 +#define SHPC_SLOT_STATUS_PRSNT_15W 0x2 +#define SHPC_SLOT_STATUS_PRSNT_7_5W 0x0 + +#define SHPC_SLOT_STATUS_PRSNT_PCIX 0x3000 + + + /* 1 byte */ +#define SHPC_SLOT_EVENT_LATCH(s) (0x2 + SHPC_SLOT_REG(s)) + /* 1 byte */ +#define SHPC_SLOT_EVENT_SERR_INT_DIS(d, s) (0x3 + SHPC_SLOT_REG(s)) +#define SHPC_SLOT_EVENT_PRESENCE 0x01 +#define SHPC_SLOT_EVENT_ISOLATED_FAULT 0x02 +#define SHPC_SLOT_EVENT_BUTTON 0x04 +#define SHPC_SLOT_EVENT_MRL 0x08 +#define SHPC_SLOT_EVENT_CONNECTED_FAULT 0x10 +/* Bits below are used for Serr/Int disable only */ +#define SHPC_SLOT_EVENT_MRL_SERR_DIS 0x20 +#define SHPC_SLOT_EVENT_CONNECTED_FAULT_SERR_DIS 0x40 + +#define SHPC_MIN_SLOTS 1 +#define SHPC_MAX_SLOTS 31 +#define SHPC_SIZEOF(d) SHPC_SLOT_REG((d)->shpc->nslots) + +/* SHPC Slot identifiers */ + +/* Hotplug supported at 31 slots out of the total 32. We reserve slot 0, + and give the rest of them physical *and* pci numbers starting from 1, so + they match logical numbers. Note: this means that multiple slots must have + different chassis number values, to make chassis+physical slot unique. + TODO: make this configurable? */ +#define SHPC_IDX_TO_LOGICAL(slot) ((slot) + 1) +#define SHPC_LOGICAL_TO_IDX(target) ((target) - 1) +#define SHPC_IDX_TO_PCI(slot) ((slot) + 1) +#define SHPC_PCI_TO_IDX(pci_slot) ((pci_slot) - 1) +#define SHPC_IDX_TO_PHYSICAL(slot) ((slot) + 1) + +static int roundup_pow_of_two(int x) +{ + x |= (x >> 1); + x |= (x >> 2); + x |= (x >> 4); + x |= (x >> 8); + x |= (x >> 16); + return x + 1; +} + +static uint16_t shpc_get_status(SHPCDevice *shpc, int slot, uint16_t msk) +{ + uint8_t *status = shpc->config + SHPC_SLOT_STATUS(slot); + return (pci_get_word(status) & msk) >> (ffs(msk) - 1); +} + +static void shpc_set_status(SHPCDevice *shpc, + int slot, uint8_t value, uint16_t msk) +{ + uint8_t *status = shpc->config + SHPC_SLOT_STATUS(slot); + pci_word_test_and_clear_mask(status, msk); + pci_word_test_and_set_mask(status, value << (ffs(msk) - 1)); +} + +static void shpc_interrupt_update(PCIDevice *d) +{ + SHPCDevice *shpc = d->shpc; + int slot; + int level = 0; + uint32_t serr_int; + uint32_t int_locator = 0; + + /* Update interrupt locator register */ + for (slot = 0; slot < shpc->nslots; ++slot) { + uint8_t event = shpc->config[SHPC_SLOT_EVENT_LATCH(slot)]; + uint8_t disable = shpc->config[SHPC_SLOT_EVENT_SERR_INT_DIS(d, slot)]; + uint32_t mask = 1 << SHPC_IDX_TO_LOGICAL(slot); + if (event & ~disable) { + int_locator |= mask; + } + } + serr_int = pci_get_long(shpc->config + SHPC_SERR_INT); + if ((serr_int & SHPC_CMD_DETECTED) && !(serr_int & SHPC_CMD_INT_DIS)) { + int_locator |= SHPC_INT_COMMAND; + } + pci_set_long(shpc->config + SHPC_INT_LOCATOR, int_locator); + level = (!(serr_int & SHPC_INT_DIS) && int_locator) ? 1 : 0; + if (msi_enabled(d) && shpc->msi_requested != level) + msi_notify(d, 0); + else + qemu_set_irq(d->irq[0], level); + shpc->msi_requested = level; +} + +static void shpc_set_sec_bus_speed(SHPCDevice *shpc, uint8_t speed) +{ + switch (speed) { + case SHPC_SEC_BUS_33: + shpc->config[SHPC_SEC_BUS] &= ~SHPC_SEC_BUS_MASK; + shpc->config[SHPC_SEC_BUS] |= speed; + break; + default: + pci_word_test_and_set_mask(shpc->config + SHPC_CMD_STATUS, + SHPC_CMD_STATUS_INVALID_MODE); + } +} + +void shpc_reset(PCIDevice *d) +{ + SHPCDevice *shpc = d->shpc; + int nslots = shpc->nslots; + int i; + memset(shpc->config, 0, SHPC_SIZEOF(d)); + pci_set_byte(shpc->config + SHPC_NSLOTS, nslots); + pci_set_long(shpc->config + SHPC_SLOTS_33, nslots); + pci_set_long(shpc->config + SHPC_SLOTS_66, 0); + pci_set_byte(shpc->config + SHPC_FIRST_DEV, SHPC_IDX_TO_PCI(0)); + pci_set_word(shpc->config + SHPC_PHYS_SLOT, + SHPC_IDX_TO_PHYSICAL(0) | + SHPC_PHYS_NUM_UP | + SHPC_PHYS_MRL | + SHPC_PHYS_BUTTON); + pci_set_long(shpc->config + SHPC_SERR_INT, SHPC_INT_DIS | + SHPC_SERR_DIS | + SHPC_CMD_INT_DIS | + SHPC_ARB_SERR_DIS); + pci_set_byte(shpc->config + SHPC_PROG_IFC, SHPC_PROG_IFC_1_0); + pci_set_word(shpc->config + SHPC_SEC_BUS, SHPC_SEC_BUS_33); + for (i = 0; i < shpc->nslots; ++i) { + pci_set_byte(shpc->config + SHPC_SLOT_EVENT_SERR_INT_DIS(d, i), + SHPC_SLOT_EVENT_PRESENCE | + SHPC_SLOT_EVENT_ISOLATED_FAULT | + SHPC_SLOT_EVENT_BUTTON | + SHPC_SLOT_EVENT_MRL | + SHPC_SLOT_EVENT_CONNECTED_FAULT | + SHPC_SLOT_EVENT_MRL_SERR_DIS | + SHPC_SLOT_EVENT_CONNECTED_FAULT_SERR_DIS); + if (shpc->sec_bus->devices[PCI_DEVFN(SHPC_IDX_TO_PCI(i), 0)]) { + shpc_set_status(shpc, i, SHPC_STATE_ENABLED, SHPC_SLOT_STATE_MASK); + shpc_set_status(shpc, i, 0, SHPC_SLOT_STATUS_MRL_OPEN); + shpc_set_status(shpc, i, SHPC_SLOT_STATUS_PRSNT_7_5W, + SHPC_SLOT_STATUS_PRSNT_MASK); + shpc_set_status(shpc, i, SHPC_LED_ON, SHPC_SLOT_PWR_LED_MASK); + } else { + shpc_set_status(shpc, i, SHPC_STATE_DISABLED, SHPC_SLOT_STATE_MASK); + shpc_set_status(shpc, i, 1, SHPC_SLOT_STATUS_MRL_OPEN); + shpc_set_status(shpc, i, SHPC_SLOT_STATUS_PRSNT_EMPTY, + SHPC_SLOT_STATUS_PRSNT_MASK); + shpc_set_status(shpc, i, SHPC_LED_OFF, SHPC_SLOT_PWR_LED_MASK); + } + shpc_set_status(shpc, i, 0, SHPC_SLOT_STATUS_66); + } + shpc_set_sec_bus_speed(shpc, SHPC_SEC_BUS_33); + shpc->msi_requested = 0; + shpc_interrupt_update(d); +} + +static void shpc_invalid_command(SHPCDevice *shpc) +{ + pci_word_test_and_set_mask(shpc->config + SHPC_CMD_STATUS, + SHPC_CMD_STATUS_INVALID_CMD); +} + +static void shpc_free_devices_in_slot(SHPCDevice *shpc, int slot) +{ + int devfn; + int pci_slot = SHPC_IDX_TO_PCI(slot); + for (devfn = PCI_DEVFN(pci_slot, 0); + devfn <= PCI_DEVFN(pci_slot, PCI_FUNC_MAX - 1); + ++devfn) { + PCIDevice *affected_dev = shpc->sec_bus->devices[devfn]; + if (affected_dev) { + qdev_free(&affected_dev->qdev); + } + } +} + +static void shpc_slot_command(SHPCDevice *shpc, uint8_t target, + uint8_t state, uint8_t power, uint8_t attn) +{ + uint8_t current_state; + int slot = SHPC_LOGICAL_TO_IDX(target); + if (target < SHPC_CMD_TRGT_MIN || slot >= shpc->nslots) { + shpc_invalid_command(shpc); + return; + } + current_state = shpc_get_status(shpc, slot, SHPC_SLOT_STATE_MASK); + if (current_state == SHPC_STATE_ENABLED && state == SHPC_STATE_PWRONLY) { + shpc_invalid_command(shpc); + return; + } + + switch (power) { + case SHPC_LED_NO: + break; + default: + /* TODO: send event to monitor */ + shpc_set_status(shpc, slot, power, SHPC_SLOT_PWR_LED_MASK); + } + switch (attn) { + case SHPC_LED_NO: + break; + default: + /* TODO: send event to monitor */ + shpc_set_status(shpc, slot, attn, SHPC_SLOT_ATTN_LED_MASK); + } + + if ((current_state == SHPC_STATE_DISABLED && state == SHPC_STATE_PWRONLY) || + (current_state == SHPC_STATE_DISABLED && state == SHPC_STATE_ENABLED)) { + shpc_set_status(shpc, slot, state, SHPC_SLOT_STATE_MASK); + } else if ((current_state == SHPC_STATE_ENABLED || + current_state == SHPC_STATE_PWRONLY) && + state == SHPC_STATE_DISABLED) { + shpc_set_status(shpc, slot, state, SHPC_SLOT_STATE_MASK); + power = shpc_get_status(shpc, slot, SHPC_SLOT_PWR_LED_MASK); + /* TODO: track what monitor requested. */ + /* Look at LED to figure out whether it's ok to remove the device. */ + if (power == SHPC_LED_OFF) { + shpc_free_devices_in_slot(shpc, slot); + shpc_set_status(shpc, slot, 1, SHPC_SLOT_STATUS_MRL_OPEN); + shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_EMPTY, + SHPC_SLOT_STATUS_PRSNT_MASK); + shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |= + SHPC_SLOT_EVENT_BUTTON | + SHPC_SLOT_EVENT_MRL | + SHPC_SLOT_EVENT_PRESENCE; + } + } +} + +static void shpc_command(SHPCDevice *shpc) +{ + uint8_t code = pci_get_byte(shpc->config + SHPC_CMD_CODE); + uint8_t speed; + uint8_t target; + uint8_t attn; + uint8_t power; + uint8_t state; + int i; + + /* Clear status from the previous command. */ + pci_word_test_and_clear_mask(shpc->config + SHPC_CMD_STATUS, + SHPC_CMD_STATUS_BUSY | + SHPC_CMD_STATUS_MRL_OPEN | + SHPC_CMD_STATUS_INVALID_CMD | + SHPC_CMD_STATUS_INVALID_MODE); + switch (code) { + case 0x00 ... 0x3f: + target = shpc->config[SHPC_CMD_TRGT] & SHPC_CMD_TRGT_MAX; + state = (code & SHPC_SLOT_STATE_MASK) >> SHPC_SLOT_STATE_SHIFT; + power = (code & SHPC_SLOT_PWR_LED_MASK) >> SHPC_SLOT_PWR_LED_SHIFT; + attn = (code & SHPC_SLOT_ATTN_LED_MASK) >> SHPC_SLOT_ATTN_LED_SHIFT; + shpc_slot_command(shpc, target, state, power, attn); + break; + case 0x40 ... 0x47: + speed = code & SHPC_SEC_BUS_MASK; + shpc_set_sec_bus_speed(shpc, speed); + break; + case 0x48: + /* Power only all slots */ + /* first verify no slots are enabled */ + for (i = 0; i < shpc->nslots; ++i) { + state = shpc_get_status(shpc, i, SHPC_SLOT_STATE_MASK); + if (state == SHPC_STATE_ENABLED) { + shpc_invalid_command(shpc); + goto done; + } + } + for (i = 0; i < shpc->nslots; ++i) { + if (!(shpc_get_status(shpc, i, SHPC_SLOT_STATUS_MRL_OPEN))) { + shpc_slot_command(shpc, i + SHPC_CMD_TRGT_MIN, + SHPC_STATE_PWRONLY, SHPC_LED_ON, SHPC_LED_NO); + } else { + shpc_slot_command(shpc, i + SHPC_CMD_TRGT_MIN, + SHPC_STATE_NO, SHPC_LED_OFF, SHPC_LED_NO); + } + } + break; + case 0x49: + /* Enable all slots */ + /* TODO: Spec says this shall fail if some are already enabled. + * This doesn't make sense - why not? a spec bug? */ + for (i = 0; i < shpc->nslots; ++i) { + state = shpc_get_status(shpc, i, SHPC_SLOT_STATE_MASK); + if (state == SHPC_STATE_ENABLED) { + shpc_invalid_command(shpc); + goto done; + } + } + for (i = 0; i < shpc->nslots; ++i) { + if (!(shpc_get_status(shpc, i, SHPC_SLOT_STATUS_MRL_OPEN))) { + shpc_slot_command(shpc, i + SHPC_CMD_TRGT_MIN, + SHPC_STATE_ENABLED, SHPC_LED_ON, SHPC_LED_NO); + } else { + shpc_slot_command(shpc, i + SHPC_CMD_TRGT_MIN, + SHPC_STATE_NO, SHPC_LED_OFF, SHPC_LED_NO); + } + } + break; + default: + shpc_invalid_command(shpc); + break; + } +done: + pci_long_test_and_set_mask(shpc->config + SHPC_SERR_INT, SHPC_CMD_DETECTED); +} + +static void shpc_write(PCIDevice *d, unsigned addr, uint64_t val, int l) +{ + SHPCDevice *shpc = d->shpc; + int i; + if (addr >= SHPC_SIZEOF(d)) { + return; + } + l = MIN(l, SHPC_SIZEOF(d) - addr); + + /* TODO: code duplicated from pci.c */ + for (i = 0; i < l; val >>= 8, ++i) { + unsigned a = addr + i; + uint8_t wmask = shpc->wmask[a]; + uint8_t w1cmask = shpc->w1cmask[a]; + assert(!(wmask & w1cmask)); + shpc->config[a] = (shpc->config[a] & ~wmask) | (val & wmask); + shpc->config[a] &= ~(val & w1cmask); /* W1C: Write 1 to Clear */ + } + if (ranges_overlap(addr, l, SHPC_CMD_CODE, 2)) { + shpc_command(shpc); + } + shpc_interrupt_update(d); +} + +static uint64_t shpc_read(PCIDevice *d, unsigned addr, int l) +{ + uint64_t val = 0x0; + if (addr >= SHPC_SIZEOF(d)) { + return val; + } + l = MIN(l, SHPC_SIZEOF(d) - addr); + memcpy(&val, d->shpc->config + addr, l); + return val; +} + +/* SHPC Bridge Capability */ +#define SHPC_CAP_LENGTH 0x08 +#define SHPC_CAP_DWORD_SELECT 0x2 /* 1 byte */ +#define SHPC_CAP_CxP 0x3 /* 1 byte: CSP, CIP */ +#define SHPC_CAP_DWORD_DATA 0x4 /* 4 bytes */ +#define SHPC_CAP_CSP_MASK 0x4 +#define SHPC_CAP_CIP_MASK 0x8 + +static uint8_t shpc_cap_dword(PCIDevice *d) +{ + return pci_get_byte(d->config + d->shpc->cap + SHPC_CAP_DWORD_SELECT); +} + +/* Update dword data capability register */ +static void shpc_cap_update_dword(PCIDevice *d) +{ + unsigned data; + data = shpc_read(d, shpc_cap_dword(d) * 4, 4); + pci_set_long(d->config + d->shpc->cap + SHPC_CAP_DWORD_DATA, data); +} + +/* Add SHPC capability to the config space for the device. */ +static int shpc_cap_add_config(PCIDevice *d) +{ + uint8_t *config; + int config_offset; + config_offset = pci_add_capability(d, PCI_CAP_ID_SHPC, + 0, SHPC_CAP_LENGTH); + if (config_offset < 0) { + return config_offset; + } + config = d->config + config_offset; + + pci_set_byte(config + SHPC_CAP_DWORD_SELECT, 0); + pci_set_byte(config + SHPC_CAP_CxP, 0); + pci_set_long(config + SHPC_CAP_DWORD_DATA, 0); + d->shpc->cap = config_offset; + /* Make dword select and data writeable. */ + pci_set_byte(d->wmask + config_offset + SHPC_CAP_DWORD_SELECT, 0xff); + pci_set_long(d->wmask + config_offset + SHPC_CAP_DWORD_DATA, 0xffffffff); + return 0; +} + +static uint64_t shpc_mmio_read(void *opaque, target_phys_addr_t addr, + unsigned size) +{ + return shpc_read(opaque, addr, size); +} + +static void shpc_mmio_write(void *opaque, target_phys_addr_t addr, + uint64_t val, unsigned size) +{ + shpc_write(opaque, addr, val, size); +} + +static const MemoryRegionOps shpc_mmio_ops = { + .read = shpc_mmio_read, + .write = shpc_mmio_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + /* SHPC ECN requires dword accesses, but the original 1.0 spec doesn't. + * It's easier to suppport all sizes than worry about it. */ + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static int shpc_device_hotplug(DeviceState *qdev, PCIDevice *affected_dev, + PCIHotplugState hotplug_state) +{ + int pci_slot = PCI_SLOT(affected_dev->devfn); + uint8_t state; + uint8_t led; + PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev); + SHPCDevice *shpc = d->shpc; + int slot = SHPC_PCI_TO_IDX(pci_slot); + if (pci_slot < SHPC_IDX_TO_PCI(0) || slot >= shpc->nslots) { + error_report("Unsupported PCI slot %d for standard hotplug " + "controller. Valid slots are between %d and %d.", + pci_slot, SHPC_IDX_TO_PCI(0), + SHPC_IDX_TO_PCI(shpc->nslots) - 1); + return -1; + } + /* Don't send event when device is enabled during qemu machine creation: + * it is present on boot, no hotplug event is necessary. We do send an + * event when the device is disabled later. */ + if (hotplug_state == PCI_COLDPLUG_ENABLED) { + shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_MRL_OPEN); + shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_7_5W, + SHPC_SLOT_STATUS_PRSNT_MASK); + return 0; + } + if (hotplug_state == PCI_HOTPLUG_DISABLED) { + shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |= SHPC_SLOT_EVENT_BUTTON; + state = shpc_get_status(shpc, slot, SHPC_SLOT_STATE_MASK); + led = shpc_get_status(shpc, slot, SHPC_SLOT_PWR_LED_MASK); + if (state == SHPC_STATE_DISABLED && led == SHPC_LED_OFF) { + shpc_free_devices_in_slot(shpc, slot); + shpc_set_status(shpc, slot, 1, SHPC_SLOT_STATUS_MRL_OPEN); + shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_EMPTY, + SHPC_SLOT_STATUS_PRSNT_MASK); + shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |= + SHPC_SLOT_EVENT_MRL | + SHPC_SLOT_EVENT_PRESENCE; + } + } else { + /* This could be a cancellation of the previous removal. + * We check MRL state to figure out. */ + if (shpc_get_status(shpc, slot, SHPC_SLOT_STATUS_MRL_OPEN)) { + shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_MRL_OPEN); + shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_7_5W, + SHPC_SLOT_STATUS_PRSNT_MASK); + shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |= + SHPC_SLOT_EVENT_BUTTON | + SHPC_SLOT_EVENT_MRL | + SHPC_SLOT_EVENT_PRESENCE; + } else { + /* Press attention button to cancel removal */ + shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |= + SHPC_SLOT_EVENT_BUTTON; + } + } + shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_66); + shpc_interrupt_update(d); + return 0; +} + +/* Initialize the SHPC structure in bridge's BAR. */ +int shpc_init(PCIDevice *d, PCIBus *sec_bus, MemoryRegion *bar, unsigned offset) +{ + int i, ret; + int nslots = SHPC_MAX_SLOTS; /* TODO: qdev property? */ + SHPCDevice *shpc = d->shpc = g_malloc0(sizeof(*d->shpc)); + shpc->sec_bus = sec_bus; + ret = shpc_cap_add_config(d); + if (ret) { + g_free(d->shpc); + return ret; + } + if (nslots < SHPC_MIN_SLOTS) { + return 0; + } + if (nslots > SHPC_MAX_SLOTS || + SHPC_IDX_TO_PCI(nslots) > PCI_SLOT_MAX) { + /* TODO: report an error mesage that makes sense. */ + return -EINVAL; + } + shpc->nslots = nslots; + shpc->config = g_malloc0(SHPC_SIZEOF(d)); + shpc->cmask = g_malloc0(SHPC_SIZEOF(d)); + shpc->wmask = g_malloc0(SHPC_SIZEOF(d)); + shpc->w1cmask = g_malloc0(SHPC_SIZEOF(d)); + + shpc_reset(d); + + pci_set_long(shpc->config + SHPC_BASE_OFFSET, offset); + + pci_set_byte(shpc->wmask + SHPC_CMD_CODE, 0xff); + pci_set_byte(shpc->wmask + SHPC_CMD_TRGT, SHPC_CMD_TRGT_MAX); + pci_set_byte(shpc->wmask + SHPC_CMD_TRGT, SHPC_CMD_TRGT_MAX); + pci_set_long(shpc->wmask + SHPC_SERR_INT, + SHPC_INT_DIS | + SHPC_SERR_DIS | + SHPC_CMD_INT_DIS | + SHPC_ARB_SERR_DIS); + pci_set_long(shpc->w1cmask + SHPC_SERR_INT, + SHPC_CMD_DETECTED | + SHPC_ARB_DETECTED); + for (i = 0; i < nslots; ++i) { + pci_set_byte(shpc->wmask + + SHPC_SLOT_EVENT_SERR_INT_DIS(d, i), + SHPC_SLOT_EVENT_PRESENCE | + SHPC_SLOT_EVENT_ISOLATED_FAULT | + SHPC_SLOT_EVENT_BUTTON | + SHPC_SLOT_EVENT_MRL | + SHPC_SLOT_EVENT_CONNECTED_FAULT | + SHPC_SLOT_EVENT_MRL_SERR_DIS | + SHPC_SLOT_EVENT_CONNECTED_FAULT_SERR_DIS); + pci_set_byte(shpc->w1cmask + + SHPC_SLOT_EVENT_LATCH(i), + SHPC_SLOT_EVENT_PRESENCE | + SHPC_SLOT_EVENT_ISOLATED_FAULT | + SHPC_SLOT_EVENT_BUTTON | + SHPC_SLOT_EVENT_MRL | + SHPC_SLOT_EVENT_CONNECTED_FAULT); + } + + /* TODO: init cmask */ + memory_region_init_io(&shpc->mmio, &shpc_mmio_ops, d, "shpc-mmio", + SHPC_SIZEOF(d)); + shpc_cap_update_dword(d); + memory_region_add_subregion(bar, offset, &shpc->mmio); + pci_bus_hotplug(sec_bus, shpc_device_hotplug, &d->qdev); + + d->cap_present |= QEMU_PCI_CAP_SHPC; + return 0; +} + +int shpc_bar_size(PCIDevice *d) +{ + return roundup_pow_of_two(SHPC_SLOT_REG(SHPC_MAX_SLOTS)); +} + +void shpc_cleanup(PCIDevice *d, MemoryRegion *bar) +{ + SHPCDevice *shpc = d->shpc; + d->cap_present &= ~QEMU_PCI_CAP_SHPC; + memory_region_del_subregion(bar, &shpc->mmio); + /* TODO: cleanup config space changes? */ + g_free(shpc->config); + g_free(shpc->cmask); + g_free(shpc->wmask); + g_free(shpc->w1cmask); + memory_region_destroy(&shpc->mmio); + g_free(shpc); +} + +void shpc_cap_write_config(PCIDevice *d, uint32_t addr, uint32_t val, int l) +{ + if (!ranges_overlap(addr, l, d->shpc->cap, SHPC_CAP_LENGTH)) { + return; + } + if (ranges_overlap(addr, l, d->shpc->cap + SHPC_CAP_DWORD_DATA, 4)) { + unsigned dword_data; + dword_data = pci_get_long(d->shpc->config + d->shpc->cap + + SHPC_CAP_DWORD_DATA); + shpc_write(d, shpc_cap_dword(d) * 4, dword_data, 4); + } + /* Update cap dword data in case guest is going to read it. */ + shpc_cap_update_dword(d); +} + +static void shpc_save(QEMUFile *f, void *pv, size_t size) +{ + PCIDevice *d = container_of(pv, PCIDevice, shpc); + qemu_put_buffer(f, d->shpc->config, SHPC_SIZEOF(d)); +} + +static int shpc_load(QEMUFile *f, void *pv, size_t size) +{ + PCIDevice *d = container_of(pv, PCIDevice, shpc); + int ret = qemu_get_buffer(f, d->shpc->config, SHPC_SIZEOF(d)); + if (ret != SHPC_SIZEOF(d)) { + return -EINVAL; + } + /* Make sure we don't lose notifications. An extra interrupt is harmless. */ + d->shpc->msi_requested = 0; + shpc_interrupt_update(d); + return 0; +} + +VMStateInfo shpc_vmstate_info = { + .name = "shpc", + .get = shpc_load, + .put = shpc_save, +}; diff --git a/hw/shpc.h b/hw/shpc.h new file mode 100644 index 0000000000..130b71df30 --- /dev/null +++ b/hw/shpc.h @@ -0,0 +1,48 @@ +#ifndef SHPC_H +#define SHPC_H + +#include "qemu-common.h" +#include "memory.h" +#include "vmstate.h" + +struct SHPCDevice { + /* Capability offset in device's config space */ + int cap; + + /* # of hot-pluggable slots */ + int nslots; + + /* SHPC WRS: working register set */ + uint8_t *config; + + /* Used to enable checks on load. Note that writable bits are + * never checked even if set in cmask. */ + uint8_t *cmask; + + /* Used to implement R/W bytes */ + uint8_t *wmask; + + /* Used to implement RW1C(Write 1 to Clear) bytes */ + uint8_t *w1cmask; + + /* MMIO for the SHPC BAR */ + MemoryRegion mmio; + + /* Bus controlled by this SHPC */ + PCIBus *sec_bus; + + /* MSI already requested for this event */ + int msi_requested; +}; + +void shpc_reset(PCIDevice *d); +int shpc_bar_size(PCIDevice *dev); +int shpc_init(PCIDevice *dev, PCIBus *sec_bus, MemoryRegion *bar, unsigned off); +void shpc_cleanup(PCIDevice *dev, MemoryRegion *bar); +void shpc_cap_write_config(PCIDevice *d, uint32_t addr, uint32_t val, int len); + +extern VMStateInfo shpc_vmstate_info; +#define SHPC_VMSTATE(_field, _type) \ + VMSTATE_BUFFER_UNSAFE_INFO(_field, _type, 0, shpc_vmstate_info, 0) + +#endif diff --git a/qemu-common.h b/qemu-common.h index b0fdf5c2b4..320f46901c 100644 --- a/qemu-common.h +++ b/qemu-common.h @@ -260,6 +260,7 @@ typedef struct SSIBus SSIBus; typedef struct EventNotifier EventNotifier; typedef struct VirtIODevice VirtIODevice; typedef struct QEMUSGList QEMUSGList; +typedef struct SHPCDevice SHPCDevice; typedef uint64_t pcibus_t; From 762833b3b88b7b79af442439a8b138a04786ec3d Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Wed, 15 Feb 2012 19:17:59 +0200 Subject: [PATCH 08/21] slotid: add slot id capability This capability makes it possible for the guest to report a unique chassis identifier to the user. The spec also recommends making chassis indentifier persist in eeprom. This isn't implemented. Signed-off-by: Michael S. Tsirkin --- Makefile.objs | 1 + hw/pci.h | 2 ++ hw/slotid_cap.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ hw/slotid_cap.h | 11 +++++++++++ 4 files changed, 58 insertions(+) create mode 100644 hw/slotid_cap.c create mode 100644 hw/slotid_cap.h diff --git a/Makefile.objs b/Makefile.objs index 1e8034cde9..5b978713d7 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -216,6 +216,7 @@ hw-obj-y += fw_cfg.o hw-obj-$(CONFIG_PCI) += pci.o pci_bridge.o hw-obj-$(CONFIG_PCI) += msix.o msi.o hw-obj-$(CONFIG_PCI) += shpc.o +hw-obj-$(CONFIG_PCI) += slotid_cap.o hw-obj-$(CONFIG_PCI) += pci_host.o pcie_host.o hw-obj-$(CONFIG_PCI) += ioh3420.o xio3130_upstream.o xio3130_downstream.o hw-obj-y += watchdog.o diff --git a/hw/pci.h b/hw/pci.h index dc4d9d10a7..8d0aa498e5 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -129,6 +129,8 @@ enum { /* Standard hot plug controller. */ #define QEMU_PCI_SHPC_BITNR 5 QEMU_PCI_CAP_SHPC = (1 << QEMU_PCI_SHPC_BITNR), +#define QEMU_PCI_SLOTID_BITNR 6 + QEMU_PCI_CAP_SLOTID = (1 << QEMU_PCI_SLOTID_BITNR), }; #define TYPE_PCI_DEVICE "pci-device" diff --git a/hw/slotid_cap.c b/hw/slotid_cap.c new file mode 100644 index 0000000000..01064521a9 --- /dev/null +++ b/hw/slotid_cap.c @@ -0,0 +1,44 @@ +#include "slotid_cap.h" +#include "pci.h" + +#define SLOTID_CAP_LENGTH 4 +#define SLOTID_NSLOTS_SHIFT (ffs(PCI_SID_ESR_NSLOTS) - 1) + +int slotid_cap_init(PCIDevice *d, int nslots, + uint8_t chassis, + unsigned offset) +{ + int cap; + if (!chassis) { + error_report("Bridge chassis not specified. Each bridge is required " + "to be assigned a unique chassis id > 0."); + return -EINVAL; + } + if (nslots < 0 || nslots > (PCI_SID_ESR_NSLOTS >> SLOTID_NSLOTS_SHIFT)) { + /* TODO: error report? */ + return -EINVAL; + } + + cap = pci_add_capability(d, PCI_CAP_ID_SLOTID, offset, SLOTID_CAP_LENGTH); + if (cap < 0) { + return cap; + } + /* We make each chassis unique, this way each bridge is First in Chassis */ + d->config[cap + PCI_SID_ESR] = PCI_SID_ESR_FIC | + (nslots << SLOTID_NSLOTS_SHIFT); + d->cmask[cap + PCI_SID_ESR] = 0xff; + d->config[cap + PCI_SID_CHASSIS_NR] = chassis; + /* Note: Chassis number register is non-volatile, + so we don't reset it. */ + /* TODO: store in eeprom? */ + d->wmask[cap + PCI_SID_CHASSIS_NR] = 0xff; + + d->cap_present |= QEMU_PCI_CAP_SLOTID; + return 0; +} + +void slotid_cap_cleanup(PCIDevice *d) +{ + /* TODO: cleanup config space? */ + d->cap_present &= ~QEMU_PCI_CAP_SLOTID; +} diff --git a/hw/slotid_cap.h b/hw/slotid_cap.h new file mode 100644 index 0000000000..70db0470b0 --- /dev/null +++ b/hw/slotid_cap.h @@ -0,0 +1,11 @@ +#ifndef PCI_SLOTID_CAP_H +#define PCI_SLOTID_CAP_H + +#include "qemu-common.h" + +int slotid_cap_init(PCIDevice *dev, int nslots, + uint8_t chassis, + unsigned offset); +void slotid_cap_cleanup(PCIDevice *dev); + +#endif From 4eb812f7603f57dc63f6b74e3c464a60ed5bc91b Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sun, 3 Jul 2011 16:00:09 +0300 Subject: [PATCH 09/21] pci: add standard bridge device This adds support for a standard pci to pci bridge, enabling support for more than 32 PCI devices in the system. Device hotplug is supported by means of SHPC controller. For guests with an SHPC driver, this allows robust hotplug and even hotplug of nested bridges, up to 31 devices per bridge. Signed-off-by: Michael S. Tsirkin --- Makefile.objs | 2 +- hw/pci_bridge_dev.c | 174 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 hw/pci_bridge_dev.c diff --git a/Makefile.objs b/Makefile.objs index 5b978713d7..25400e98c4 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -213,7 +213,7 @@ hw-obj-$(CONFIG_VIRTIO) += virtio-console.o hw-obj-y += usb/libhw.o hw-obj-$(CONFIG_VIRTIO_PCI) += virtio-pci.o hw-obj-y += fw_cfg.o -hw-obj-$(CONFIG_PCI) += pci.o pci_bridge.o +hw-obj-$(CONFIG_PCI) += pci.o pci_bridge.o pci_bridge_dev.o hw-obj-$(CONFIG_PCI) += msix.o msi.o hw-obj-$(CONFIG_PCI) += shpc.o hw-obj-$(CONFIG_PCI) += slotid_cap.o diff --git a/hw/pci_bridge_dev.c b/hw/pci_bridge_dev.c new file mode 100644 index 0000000000..eccaa5831e --- /dev/null +++ b/hw/pci_bridge_dev.c @@ -0,0 +1,174 @@ +/* + * Standard PCI Bridge Device + * + * Copyright (c) 2011 Red Hat Inc. Author: Michael S. Tsirkin + * + * http://www.pcisig.com/specifications/conventional/pci_to_pci_bridge_architecture/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see . + */ + +#include "pci_bridge.h" +#include "pci_ids.h" +#include "msi.h" +#include "shpc.h" +#include "slotid_cap.h" +#include "memory.h" +#include "pci_internals.h" + +#define REDHAT_PCI_VENDOR_ID 0x1b36 +#define PCI_BRIDGE_DEV_VENDOR_ID REDHAT_PCI_VENDOR_ID +#define PCI_BRIDGE_DEV_DEVICE_ID 0x1 + +struct PCIBridgeDev { + PCIBridge bridge; + MemoryRegion bar; + uint8_t chassis_nr; +#define PCI_BRIDGE_DEV_F_MSI_REQ 0 + uint32_t flags; +}; +typedef struct PCIBridgeDev PCIBridgeDev; + +/* Mapping mandated by PCI-to-PCI Bridge architecture specification, + * revision 1.2 */ +/* Table 9-1: Interrupt Binding for Devices Behind a Bridge */ +static int pci_bridge_dev_map_irq_fn(PCIDevice *dev, int irq_num) +{ + return (irq_num + PCI_SLOT(dev->devfn)) % PCI_NUM_PINS; +} + +static int pci_bridge_dev_initfn(PCIDevice *dev) +{ + PCIBridge *br = DO_UPCAST(PCIBridge, dev, dev); + PCIBridgeDev *bridge_dev = DO_UPCAST(PCIBridgeDev, bridge, br); + int err; + pci_bridge_map_irq(br, NULL, pci_bridge_dev_map_irq_fn); + err = pci_bridge_initfn(dev); + if (err) { + goto bridge_error; + } + memory_region_init(&bridge_dev->bar, "shpc-bar", shpc_bar_size(dev)); + err = shpc_init(dev, &br->sec_bus, &bridge_dev->bar, 0); + if (err) { + goto shpc_error; + } + err = slotid_cap_init(dev, 0, bridge_dev->chassis_nr, 0); + if (err) { + goto slotid_error; + } + if ((bridge_dev->flags & (1 << PCI_BRIDGE_DEV_F_MSI_REQ)) && + msi_supported) { + err = msi_init(dev, 0, 1, true, true); + if (err < 0) { + goto msi_error; + } + } + /* TODO: spec recommends using 64 bit prefetcheable BAR. + * Check whether that works well. */ + pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY | + PCI_BASE_ADDRESS_MEM_TYPE_64, &bridge_dev->bar); + dev->config[PCI_INTERRUPT_PIN] = 0x1; + return 0; +msi_error: + slotid_cap_cleanup(dev); +slotid_error: + shpc_cleanup(dev, &bridge_dev->bar); +shpc_error: + memory_region_destroy(&bridge_dev->bar); +bridge_error: + return err; +} + +static int pci_bridge_dev_exitfn(PCIDevice *dev) +{ + PCIBridge *br = DO_UPCAST(PCIBridge, dev, dev); + PCIBridgeDev *bridge_dev = DO_UPCAST(PCIBridgeDev, bridge, br); + int ret; + if (msi_present(dev)) { + msi_uninit(dev); + } + slotid_cap_cleanup(dev); + shpc_cleanup(dev, &bridge_dev->bar); + memory_region_destroy(&bridge_dev->bar); + ret = pci_bridge_exitfn(dev); + assert(!ret); + return 0; +} + +static void pci_bridge_dev_write_config(PCIDevice *d, + uint32_t address, uint32_t val, int len) +{ + pci_bridge_write_config(d, address, val, len); + if (msi_present(d)) { + msi_write_config(d, address, val, len); + } + shpc_cap_write_config(d, address, val, len); +} + +static void qdev_pci_bridge_dev_reset(DeviceState *qdev) +{ + PCIDevice *dev = DO_UPCAST(PCIDevice, qdev, qdev); + pci_bridge_reset(qdev); + if (msi_present(dev)) { + msi_reset(dev); + } + shpc_reset(dev); +} + +static Property pci_bridge_dev_properties[] = { + /* Note: 0 is not a legal chassis number. */ + DEFINE_PROP_UINT8("chassis_nr", PCIBridgeDev, chassis_nr, 0), + DEFINE_PROP_BIT("msi", PCIBridgeDev, flags, PCI_BRIDGE_DEV_F_MSI_REQ, true), + DEFINE_PROP_END_OF_LIST(), +}; + +static const VMStateDescription pci_bridge_dev_vmstate = { + .name = "pci_bridge", + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(bridge.dev, PCIBridgeDev), + SHPC_VMSTATE(bridge.dev.shpc, PCIBridgeDev), + VMSTATE_END_OF_LIST() + } +}; + +static void pci_bridge_dev_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + k->init = pci_bridge_dev_initfn; + k->exit = pci_bridge_dev_exitfn; + k->config_write = pci_bridge_dev_write_config; + k->vendor_id = PCI_BRIDGE_DEV_VENDOR_ID; + k->device_id = PCI_BRIDGE_DEV_DEVICE_ID; + k->class_id = PCI_CLASS_BRIDGE_PCI; + k->is_bridge = 1, + dc->desc = "Standard PCI Bridge"; + dc->reset = qdev_pci_bridge_dev_reset; + dc->props = pci_bridge_dev_properties; + dc->vmsd = &pci_bridge_dev_vmstate; +} + +static TypeInfo pci_bridge_dev_info = { + .name = "pci-bridge", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(PCIBridgeDev), + .class_init = pci_bridge_dev_class_init, +}; + +static void pci_bridge_dev_register(void) +{ + type_register_static(&pci_bridge_dev_info); +} + +type_init(pci_bridge_dev_register); From 15ab7a75335108c76700bea67602017fe305350d Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Tue, 21 Feb 2012 15:49:01 +0200 Subject: [PATCH 10/21] pci: set memory type for memory behind the bridge As we make upper bits in IO and prefetcheable memory registers writeable, we should declare support for 64 bit prefetcheable memory and 32 bit io in the bridge. This changes the default for apb, dec, but I'm guessing they got the defaults wrong by accident. Alternatively, we could let bridges declare lack of 64 bit support and make the upper bits read-only zero. Reported-by: Gerd Hoffmann Signed-off-by: Michael S. Tsirkin --- hw/pci.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hw/pci.c b/hw/pci.c index 3ca5f4c820..fee27fcf87 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -663,8 +663,11 @@ static void pci_init_mask_bridge(PCIDevice *d) pci_set_word(d->w1cmask + PCI_BRIDGE_CONTROL, PCI_BRIDGE_CTL_DISCARD_STATUS); d->cmask[PCI_IO_BASE] |= PCI_IO_RANGE_TYPE_MASK; + d->cmask[PCI_IO_LIMIT] |= PCI_IO_RANGE_TYPE_MASK; pci_word_test_and_set_mask(d->cmask + PCI_PREF_MEMORY_BASE, PCI_PREF_RANGE_TYPE_MASK); + pci_word_test_and_set_mask(d->cmask + PCI_PREF_MEMORY_LIMIT, + PCI_PREF_RANGE_TYPE_MASK); } static int pci_init_multifunction(PCIBus *bus, PCIDevice *dev) From 633442ff56909214576549d34f5be1f50a67c5a6 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Tue, 21 Feb 2012 15:58:59 +0200 Subject: [PATCH 11/21] pcie: drop functionality moved to core Now that core sets memory type correctly, remove this code from pcie port implementation. Signed-off-by: Michael S. Tsirkin --- hw/pcie_port.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/hw/pcie_port.c b/hw/pcie_port.c index 8a36f5cfc7..d6350e5e73 100644 --- a/hw/pcie_port.c +++ b/hw/pcie_port.c @@ -27,23 +27,13 @@ void pcie_port_init_reg(PCIDevice *d) pci_set_word(d->config + PCI_STATUS, 0); pci_set_word(d->config + PCI_SEC_STATUS, 0); - /* Unlike conventional pci bridge, some bits are hardwared to 0. */ + /* Unlike conventional pci bridge, some bits are hardwired to 0. */ pci_set_word(d->wmask + PCI_BRIDGE_CONTROL, PCI_BRIDGE_CTL_PARITY | PCI_BRIDGE_CTL_ISA | PCI_BRIDGE_CTL_VGA | PCI_BRIDGE_CTL_SERR | PCI_BRIDGE_CTL_BUS_RESET); - - /* 7.5.3.5 Prefetchable Memory Base Limit - * The Prefetchable Memory Base and Prefetchable Memory Limit registers - * must indicate that 64-bit addresses are supported, as defined in - * PCI-to-PCI Bridge Architecture Specification, Revision 1.2. - */ - pci_word_test_and_set_mask(d->config + PCI_PREF_MEMORY_BASE, - PCI_PREF_RANGE_TYPE_64); - pci_word_test_and_set_mask(d->config + PCI_PREF_MEMORY_LIMIT, - PCI_PREF_RANGE_TYPE_64); } /************************************************************************** From 68917102740d9aa96c8f3ed4b95eab9917e8c61b Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sun, 4 Mar 2012 11:36:35 +0200 Subject: [PATCH 12/21] pci: fix bridge IO/BASE commit 5caef97a16010f818ea8b950e2ee24ba876643ad introduced a regression: we do not make IO base/limit upper 16 bit registers writeable, so we should report a 16 bit IO range type, not a 32 bit one. Note that PCI_PREF_RANGE_TYPE_32 is 0x0, but PCI_IO_RANGE_TYPE_32 is 0x1. In particular, this broke sparc64. Note: this just reverts to behaviour prior to the commit above. Making PCI_IO_BASE_UPPER16 and PCI_IO_LIMIT_UPPER16 registers writeable should, and seems to, work just as well, but as no system seems to actually be interested in 32 bit IO, let's not make unnecessary changes. Signed-off-by: Michael S. Tsirkin --- hw/pci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/pci.c b/hw/pci.c index fee27fcf87..6d08cef87b 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -633,8 +633,8 @@ static void pci_init_mask_bridge(PCIDevice *d) memset(d->wmask + PCI_PREF_BASE_UPPER32, 0xff, 8); /* Supported memory and i/o types */ - d->config[PCI_IO_BASE] |= PCI_IO_RANGE_TYPE_32; - d->config[PCI_IO_LIMIT] |= PCI_IO_RANGE_TYPE_32; + d->config[PCI_IO_BASE] |= PCI_IO_RANGE_TYPE_16; + d->config[PCI_IO_LIMIT] |= PCI_IO_RANGE_TYPE_16; pci_word_test_and_set_mask(d->config + PCI_PREF_MEMORY_BASE, PCI_PREF_RANGE_TYPE_64); pci_word_test_and_set_mask(d->config + PCI_PREF_MEMORY_LIMIT, From cd7898f714773f7a317b122eaa45e701fd4f9849 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sun, 4 Mar 2012 15:35:29 +0200 Subject: [PATCH 13/21] pci_bridge: upper 32 bit are long registers Use pci_set_long for accesses. Signed-off-by: Michael S. Tsirkin --- hw/pci_bridge.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hw/pci_bridge.c b/hw/pci_bridge.c index eeee8a6ad0..866f0b6c52 100644 --- a/hw/pci_bridge.c +++ b/hw/pci_bridge.c @@ -249,8 +249,8 @@ void pci_bridge_disable_base_limit(PCIDevice *dev) PCI_PREF_RANGE_MASK & 0xffff); pci_word_test_and_clear_mask(conf + PCI_PREF_MEMORY_LIMIT, PCI_PREF_RANGE_MASK & 0xffff); - pci_set_word(conf + PCI_PREF_BASE_UPPER32, 0); - pci_set_word(conf + PCI_PREF_LIMIT_UPPER32, 0); + pci_set_long(conf + PCI_PREF_BASE_UPPER32, 0); + pci_set_long(conf + PCI_PREF_LIMIT_UPPER32, 0); } /* reset bridge specific configuration registers */ @@ -285,8 +285,8 @@ void pci_bridge_reset_reg(PCIDevice *dev) PCI_PREF_RANGE_MASK & 0xffff); pci_word_test_and_clear_mask(conf + PCI_PREF_MEMORY_LIMIT, PCI_PREF_RANGE_MASK & 0xffff); - pci_set_word(conf + PCI_PREF_BASE_UPPER32, 0); - pci_set_word(conf + PCI_PREF_LIMIT_UPPER32, 0); + pci_set_long(conf + PCI_PREF_BASE_UPPER32, 0); + pci_set_long(conf + PCI_PREF_LIMIT_UPPER32, 0); pci_set_word(conf + PCI_BRIDGE_CONTROL, 0); } From ca384784af5f472f0b94c9205f8c500c1b5a43b6 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Fri, 16 Mar 2012 00:43:25 +0200 Subject: [PATCH 14/21] virtio-net: add DATA_VALID flag Add DATA_VALID flag from the Linux header, to keep us in sync with that. Signed-off-by: Michael S. Tsirkin --- hw/virtio-net.h | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/virtio-net.h b/hw/virtio-net.h index 44687414c9..36aa463812 100644 --- a/hw/virtio-net.h +++ b/hw/virtio-net.h @@ -79,6 +79,7 @@ struct virtio_net_config struct virtio_net_hdr { #define VIRTIO_NET_HDR_F_NEEDS_CSUM 1 // Use csum_start, csum_offset +#define VIRTIO_NET_HDR_F_DATA_VALID 2 // Csum is valid uint8_t flags; #define VIRTIO_NET_HDR_GSO_NONE 0 // Not a GSO frame #define VIRTIO_NET_HDR_GSO_TCPV4 1 // GSO frame, IPv4 TCP (TSO) From 453058d3e7111632b795c85f7d3bb36df25d5beb Mon Sep 17 00:00:00 2001 From: Anthony PERARD Date: Fri, 17 Feb 2012 17:08:37 +0000 Subject: [PATCH 15/21] pci_regs: Add PCI_EXP_TYPE_PCIE_BRIDGE Signed-off-by: Anthony PERARD Signed-off-by: Michael S. Tsirkin --- hw/pci_regs.h | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/pci_regs.h b/hw/pci_regs.h index 6b4251593b..56a404be6e 100644 --- a/hw/pci_regs.h +++ b/hw/pci_regs.h @@ -392,6 +392,7 @@ #define PCI_EXP_TYPE_UPSTREAM 0x5 /* Upstream Port */ #define PCI_EXP_TYPE_DOWNSTREAM 0x6 /* Downstream Port */ #define PCI_EXP_TYPE_PCI_BRIDGE 0x7 /* PCI/PCI-X Bridge */ +#define PCI_EXP_TYPE_PCIE_BRIDGE 0x8 /* PCI/PCI-X to PCIE Bridge */ #define PCI_EXP_TYPE_RC_END 0x9 /* Root Complex Integrated Endpoint */ #define PCI_EXP_TYPE_RC_EC 0xa /* Root Complex Event Collector */ #define PCI_EXP_FLAGS_SLOT 0x0100 /* Slot implemented */ From cde31a0e3dc0e4ac83e454d6096350cec584adf1 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Wed, 7 Mar 2012 11:17:48 +0800 Subject: [PATCH 16/21] rtl8139: limit transmission buffer size in c+ mode The tx buffer would be re-allocated for tx descriptor with big size and without LS bit set, this would make guest driver could easily let qemu to allocate unlimited. In linux host, a glib failure were easy to be triggered: GLib-ERROR **: gmem.c:176: failed to allocate 18446744071562067968 bytes This patch fix this by adding a limit. As the spec didn't tell the maximum size of buffer allowed, stick it to current CP_TX_BUFFER_SIZE (65536). Changes from V1: Drop the while statement and s->cplus_txbuffer check. Signed-off-by: Jason Wang Reviewed-by: Stefan Hajnoczi Signed-off-by: Michael S. Tsirkin --- hw/rtl8139.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/hw/rtl8139.c b/hw/rtl8139.c index 05b8e1e3d7..be4bc6a947 100644 --- a/hw/rtl8139.c +++ b/hw/rtl8139.c @@ -2061,13 +2061,12 @@ static int rtl8139_cplus_transmit_one(RTL8139State *s) s->cplus_txbuffer_len); } - while (s->cplus_txbuffer && s->cplus_txbuffer_offset + txsize >= s->cplus_txbuffer_len) + if (s->cplus_txbuffer_offset + txsize >= s->cplus_txbuffer_len) { - s->cplus_txbuffer_len += CP_TX_BUFFER_SIZE; - s->cplus_txbuffer = g_realloc(s->cplus_txbuffer, s->cplus_txbuffer_len); - - DPRINTF("+++ C+ mode transmission buffer space changed to %d\n", - s->cplus_txbuffer_len); + /* The spec didn't tell the maximum size, stick to CP_TX_BUFFER_SIZE */ + txsize = s->cplus_txbuffer_len - s->cplus_txbuffer_offset; + DPRINTF("+++ C+ mode transmission buffer overrun, truncated descriptor" + "length to %d\n", txsize); } if (!s->cplus_txbuffer) From b44ce451df7840a0c5dea784673529994bbe1b32 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 5 Mar 2012 11:08:33 +0800 Subject: [PATCH 17/21] rtl8139: remove unused marco Signed-off-by: Jason Wang Signed-off-by: Michael S. Tsirkin --- hw/rtl8139.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/hw/rtl8139.c b/hw/rtl8139.c index be4bc6a947..ccbde27fc2 100644 --- a/hw/rtl8139.c +++ b/hw/rtl8139.c @@ -65,9 +65,6 @@ #define PCI_FREQUENCY 33000000L -/* debug RTL8139 card C+ mode only */ -//#define DEBUG_RTL8139CP 1 - #define SET_MASKED(input, mask, curr) \ ( ( (input) & ~(mask) ) | ( (curr) & (mask) ) ) From afe0a595356192d5f79703cf6462fcc112df007c Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 5 Mar 2012 11:08:42 +0800 Subject: [PATCH 18/21] rtl8139: support byte read to TxStatus registers Some drivers (such as win7) use byte read for TxStatus registers, so we need to support this to let guest driver behave correctly. For writing, only double-word access is allowed by spec. Signed-off-by: Jason Wang Signed-off-by: Michael S. Tsirkin --- hw/rtl8139.c | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/hw/rtl8139.c b/hw/rtl8139.c index ccbde27fc2..1d5f78bbee 100644 --- a/hw/rtl8139.c +++ b/hw/rtl8139.c @@ -2495,11 +2495,30 @@ static void rtl8139_TxStatus_write(RTL8139State *s, uint32_t txRegOffset, uint32 rtl8139_transmit(s); } -static uint32_t rtl8139_TxStatus_read(RTL8139State *s, uint32_t txRegOffset) +static uint32_t rtl8139_TxStatus_read(RTL8139State *s, uint8_t addr, int size) { - uint32_t ret = s->TxStatus[txRegOffset/4]; + uint32_t reg = (addr - TxStatus0) / 4; + uint32_t offset = addr & 0x3; + uint32_t ret = 0; - DPRINTF("TxStatus read offset=0x%x val=0x%08x\n", txRegOffset, ret); + if (addr & (size - 1)) { + DPRINTF("not implemented read for TxStatus addr=0x%x size=0x%x\n", addr, + size); + return ret; + } + + switch (size) { + case 1: /* fall through */ + case 2: /* fall through */ + case 4: + ret = (s->TxStatus[reg] >> offset * 8) & ((1 << (size * 8)) - 1); + DPRINTF("TxStatus[%d] read addr=0x%x size=0x%x val=0x%08x\n", reg, addr, + size, ret); + break; + default: + DPRINTF("unsupported size 0x%x of TxStatus reading\n", size); + break; + } return ret; } @@ -2970,6 +2989,9 @@ static uint32_t rtl8139_io_readb(void *opaque, uint8_t addr) case MAR0 ... MAR0+7: ret = s->mult[addr - MAR0]; break; + case TxStatus0 ... TxStatus0+4*4-1: + ret = rtl8139_TxStatus_read(s, addr, 1); + break; case ChipCmd: ret = rtl8139_ChipCmd_read(s); break; @@ -3033,6 +3055,9 @@ static uint32_t rtl8139_io_readw(void *opaque, uint8_t addr) switch (addr) { + case TxAddr0 ... TxAddr0+4*4-1: + ret = rtl8139_TxStatus_read(s, addr, 2); + break; case IntrMask: ret = rtl8139_IntrMask_read(s); break; @@ -3123,7 +3148,7 @@ static uint32_t rtl8139_io_readl(void *opaque, uint8_t addr) break; case TxStatus0 ... TxStatus0+4*4-1: - ret = rtl8139_TxStatus_read(s, addr-TxStatus0); + ret = rtl8139_TxStatus_read(s, addr, 4); break; case TxAddr0 ... TxAddr0+4*4-1: From 7fc8d918b9674c3e9233d6d25da2457345d414a0 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 5 Mar 2012 11:08:50 +0800 Subject: [PATCH 19/21] net: move compute_mcast_idx() to net.h Reduce duplicated codes. Signed-off-by: Jason Wang Signed-off-by: Michael S. Tsirkin --- hw/eepro100.c | 25 ------------------------- hw/ne2000.c | 24 ------------------------ hw/opencores_eth.c | 25 ------------------------- hw/rtl8139.c | 24 ------------------------ net.c | 23 +++++++++++++++++++++++ net.h | 3 +++ 6 files changed, 26 insertions(+), 98 deletions(-) diff --git a/hw/eepro100.c b/hw/eepro100.c index e3ba71974e..02e6f7ef31 100644 --- a/hw/eepro100.c +++ b/hw/eepro100.c @@ -322,33 +322,8 @@ static const uint16_t eepro100_mdi_mask[] = { 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, }; -#define POLYNOMIAL 0x04c11db6 - static E100PCIDeviceInfo *eepro100_get_class(EEPRO100State *s); -/* From FreeBSD */ -/* XXX: optimize */ -static unsigned compute_mcast_idx(const uint8_t * ep) -{ - uint32_t crc; - int carry, i, j; - uint8_t b; - - crc = 0xffffffff; - for (i = 0; i < 6; i++) { - b = *ep++; - for (j = 0; j < 8; j++) { - carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01); - crc <<= 1; - b >>= 1; - if (carry) { - crc = ((crc ^ POLYNOMIAL) | carry); - } - } - } - return (crc & BITS(7, 2)) >> 2; -} - /* Read a 16 bit control/status (CSR) register. */ static uint16_t e100_read_reg2(EEPRO100State *s, E100RegisterOffset addr) { diff --git a/hw/ne2000.c b/hw/ne2000.c index 71452e1f2e..d02e60c4a6 100644 --- a/hw/ne2000.c +++ b/hw/ne2000.c @@ -150,30 +150,6 @@ static void ne2000_update_irq(NE2000State *s) qemu_set_irq(s->irq, (isr != 0)); } -#define POLYNOMIAL 0x04c11db6 - -/* From FreeBSD */ -/* XXX: optimize */ -static int compute_mcast_idx(const uint8_t *ep) -{ - uint32_t crc; - int carry, i, j; - uint8_t b; - - crc = 0xffffffff; - for (i = 0; i < 6; i++) { - b = *ep++; - for (j = 0; j < 8; j++) { - carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01); - crc <<= 1; - b >>= 1; - if (carry) - crc = ((crc ^ POLYNOMIAL) | carry); - } - } - return (crc >> 26); -} - static int ne2000_buffer_full(NE2000State *s) { int avail, index, boundary; diff --git a/hw/opencores_eth.c b/hw/opencores_eth.c index 4c7696935f..350f73173a 100644 --- a/hw/opencores_eth.c +++ b/hw/opencores_eth.c @@ -351,31 +351,6 @@ static int open_eth_can_receive(VLANClientState *nc) (rx_desc(s)->len_flags & RXD_E); } -#define POLYNOMIAL 0x04c11db6 - -/* From FreeBSD */ -/* XXX: optimize */ -static unsigned compute_mcast_idx(const uint8_t *ep) -{ - uint32_t crc; - int carry, i, j; - uint8_t b; - - crc = 0xffffffff; - for (i = 0; i < 6; i++) { - b = *ep++; - for (j = 0; j < 8; j++) { - carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01); - crc <<= 1; - b >>= 1; - if (carry) { - crc = ((crc ^ POLYNOMIAL) | carry); - } - } - } - return crc >> 26; -} - static ssize_t open_eth_receive(VLANClientState *nc, const uint8_t *buf, size_t size) { diff --git a/hw/rtl8139.c b/hw/rtl8139.c index 1d5f78bbee..5b608f53ec 100644 --- a/hw/rtl8139.c +++ b/hw/rtl8139.c @@ -708,30 +708,6 @@ static void rtl8139_update_irq(RTL8139State *s) qemu_set_irq(s->dev.irq[0], (isr != 0)); } -#define POLYNOMIAL 0x04c11db6 - -/* From FreeBSD */ -/* XXX: optimize */ -static int compute_mcast_idx(const uint8_t *ep) -{ - uint32_t crc; - int carry, i, j; - uint8_t b; - - crc = 0xffffffff; - for (i = 0; i < 6; i++) { - b = *ep++; - for (j = 0; j < 8; j++) { - carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01); - crc <<= 1; - b >>= 1; - if (carry) - crc = ((crc ^ POLYNOMIAL) | carry); - } - } - return (crc >> 26); -} - static int rtl8139_RxWrap(RTL8139State *s) { /* wrapping enabled; assume 1.5k more buffer space if size < 65536 */ diff --git a/net.c b/net.c index c34474fe5f..1922d8abd1 100644 --- a/net.c +++ b/net.c @@ -1475,3 +1475,26 @@ int net_client_parse(QemuOptsList *opts_list, const char *optarg) default_net = 0; return 0; } + +/* From FreeBSD */ +/* XXX: optimize */ +unsigned compute_mcast_idx(const uint8_t *ep) +{ + uint32_t crc; + int carry, i, j; + uint8_t b; + + crc = 0xffffffff; + for (i = 0; i < 6; i++) { + b = *ep++; + for (j = 0; j < 8; j++) { + carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01); + crc <<= 1; + b >>= 1; + if (carry) { + crc = ((crc ^ POLYNOMIAL) | carry); + } + } + } + return crc >> 26; +} diff --git a/net.h b/net.h index 75a8c155e5..64993b43ef 100644 --- a/net.h +++ b/net.h @@ -182,6 +182,9 @@ void qdev_set_nic_properties(DeviceState *dev, NICInfo *nd); int net_handle_fd_param(Monitor *mon, const char *param); +#define POLYNOMIAL 0x04c11db6 +unsigned compute_mcast_idx(const uint8_t *ep); + #define vmstate_offset_macaddr(_state, _field) \ vmstate_offset_array(_state, _field.a, uint8_t, \ sizeof(typeof_field(_state, _field))) From eb46c5eda7d8b38c1407dd55f67cf4a6aa3b7b23 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 5 Mar 2012 11:08:59 +0800 Subject: [PATCH 20/21] rtl8139: correctly check the opmode According to the spec, only when opmode is "Config. Register Write Enable" could driver write to CONFIG0,1,3,4 and bits 13,12,8 of BMCR. Currently, we allow modifying to those registers also when 8139 is in "Auto-load" mode and "93C46 (93C56) Programming" mode. This patch fixes this. Signed-off-by: Jason Wang Signed-off-by: Michael S. Tsirkin --- hw/rtl8139.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/hw/rtl8139.c b/hw/rtl8139.c index 5b608f53ec..f3322ea167 100644 --- a/hw/rtl8139.c +++ b/hw/rtl8139.c @@ -332,8 +332,10 @@ enum CSCRBits { }; enum Cfg9346Bits { - Cfg9346_Lock = 0x00, - Cfg9346_Unlock = 0xC0, + Cfg9346_Normal = 0x00, + Cfg9346_Autoload = 0x40, + Cfg9346_Programming = 0x80, + Cfg9346_ConfigWrite = 0xC0, }; typedef enum { @@ -1451,7 +1453,7 @@ static uint32_t rtl8139_IntrMitigate_read(RTL8139State *s) static int rtl8139_config_writable(RTL8139State *s) { - if (s->Cfg9346 & Cfg9346_Unlock) + if ((s->Cfg9346 & Chip9346_op_mask) == Cfg9346_ConfigWrite) { return 1; } From ff71f2e8cacefae99179993204172bc65e4303df Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 5 Mar 2012 11:09:08 +0800 Subject: [PATCH 21/21] rtl8139: do the network/host communication only in normal operating mode According the spec, the card works in network/host communication mode only when both EEM1 and EEM0 are unset in 93C46 Command Register (normal op mode). So this patch check these bits before trying to receive packets. As some guest driver (such as linux, see cp_init_hw() in 8139cp.c) allocate rx ring after the recevier were enabled, this would cause our emulation codes tries to dma into guest memory when the rx descriptor is not properly configured. This patch fixes this. Signed-off-by: Jason Wang Signed-off-by: Michael S. Tsirkin --- hw/rtl8139.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hw/rtl8139.c b/hw/rtl8139.c index f3322ea167..4d553a8a59 100644 --- a/hw/rtl8139.c +++ b/hw/rtl8139.c @@ -791,6 +791,9 @@ static int rtl8139_can_receive(VLANClientState *nc) return 1; if (!rtl8139_receiver_enabled(s)) return 1; + /* network/host communication happens only in normal mode */ + if ((s->Cfg9346 & Chip9346_op_mask) != Cfg9346_Normal) + return 0; if (rtl8139_cp_receiver_enabled(s)) { /* ??? Flow control not implemented in c+ mode. @@ -833,6 +836,12 @@ static ssize_t rtl8139_do_receive(VLANClientState *nc, const uint8_t *buf, size_ return -1; } + /* check whether we are in normal mode */ + if ((s->Cfg9346 & Chip9346_op_mask) != Cfg9346_Normal) { + DPRINTF("not in normal op mode\n"); + return -1; + } + /* XXX: check this */ if (s->RxConfig & AcceptAllPhys) { /* promiscuous: receive all */