* [Qemu-devel] [PATCH v2 0/9] pcie port switch emulators
@ 2010-09-08 7:39 Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 1/9] msi: implemented msi Isaku Yamahata
` (8 more replies)
0 siblings, 9 replies; 25+ messages in thread
From: Isaku Yamahata @ 2010-09-08 7:39 UTC (permalink / raw)
To: qemu-devel; +Cc: skandasa, etmartin, wexu2, mst, adhyas, yamahata
Now updated msi implemenation by following the review.
Other patches were just adjusted.
Michael, thank you for merging portion of patch series.
That makes my life much easier.
> pcie port: define struct PCIEPort/PCIESlot and helper functions
> msix: clear not only INTA, but all INTx when MSI-X is enabled.
These 2 patches don't depend on MSI. Other patches do.
changes v1 -> v2:
- update msi
- dropped already pushed out patches.
- added msix patches.
Patch description:
This patch series implements pcie port switch emulators
which is basic part for pcie/q35 support.
This is for mst/pci tree.
Isaku Yamahata (9):
msi: implemented msi.
pcie: helper functions for pcie extended capability.
pcie port: define struct PCIEPort/PCIESlot and helper functions
pcie root port: implement pcie root port.
pcie upstream port: pci express switch upstream port.
pcie downstream port: pci express switch downstream port.
pcie/hotplug: glue pushing attention button command. pcie_abp
pcie/aer: glue aer error injection into qemu monitor.
msix: clear not only INTA, but all INTx when MSI-X is enabled.
Makefile.objs | 6 +-
hw/msi.c | 360 +++++++++++
hw/msi.h | 41 ++
hw/msix.c | 5 +-
hw/pci.h | 34 +-
hw/pcie.c | 1753 ++++++++++++++++++++++++++++++++++++++++++++++++++
hw/pcie.h | 186 ++++++
hw/pcie_downstream.c | 225 +++++++
hw/pcie_downstream.h | 33 +
hw/pcie_port.c | 188 ++++++
hw/pcie_port.h | 51 ++
hw/pcie_root.c | 248 +++++++
hw/pcie_root.h | 32 +
hw/pcie_upstream.c | 207 ++++++
hw/pcie_upstream.h | 32 +
qemu-common.h | 3 +
qemu-monitor.hx | 36 +
sysemu.h | 9 +
18 files changed, 3443 insertions(+), 6 deletions(-)
create mode 100644 hw/msi.c
create mode 100644 hw/msi.h
create mode 100644 hw/pcie.c
create mode 100644 hw/pcie.h
create mode 100644 hw/pcie_downstream.c
create mode 100644 hw/pcie_downstream.h
create mode 100644 hw/pcie_port.c
create mode 100644 hw/pcie_port.h
create mode 100644 hw/pcie_root.c
create mode 100644 hw/pcie_root.h
create mode 100644 hw/pcie_upstream.c
create mode 100644 hw/pcie_upstream.h
^ permalink raw reply [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH v2 1/9] msi: implemented msi.
2010-09-08 7:39 [Qemu-devel] [PATCH v2 0/9] pcie port switch emulators Isaku Yamahata
@ 2010-09-08 7:39 ` Isaku Yamahata
2010-09-08 9:13 ` [Qemu-devel] " Michael S. Tsirkin
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 2/9] pcie: helper functions for pcie extended capability Isaku Yamahata
` (7 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Isaku Yamahata @ 2010-09-08 7:39 UTC (permalink / raw)
To: qemu-devel; +Cc: skandasa, etmartin, wexu2, mst, adhyas, yamahata
implemented msi support functions.
Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
---
Changes v1 -> v2:
- opencode some oneline helper function/macros for readability
- use ffs where appropriate
- rename some functions/variables as suggested.
- added assert()
- 1 -> 1U
- clear INTx# when MSI is enabled
- clear pending bits for freed vectors.
- check the requested number of vectors.
---
Makefile.objs | 2 +-
hw/msi.c | 360 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
hw/msi.h | 41 +++++++
hw/pci.h | 10 +-
4 files changed, 409 insertions(+), 4 deletions(-)
create mode 100644 hw/msi.c
create mode 100644 hw/msi.h
diff --git a/Makefile.objs b/Makefile.objs
index 594894b..5f5a4c5 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -186,7 +186,7 @@ hw-obj-$(CONFIG_PIIX4) += piix4.o
# PCI watchdog devices
hw-obj-y += wdt_i6300esb.o
-hw-obj-y += msix.o
+hw-obj-y += msix.o msi.o
# PCI network cards
hw-obj-y += ne2000.o
diff --git a/hw/msi.c b/hw/msi.c
new file mode 100644
index 0000000..32dc953
--- /dev/null
+++ b/hw/msi.c
@@ -0,0 +1,360 @@
+/*
+ * msi.c
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "msi.h"
+
+/* Eventually those constants should go to Linux pci_regs.h */
+#define PCI_MSI_PENDING_32 0x10
+#define PCI_MSI_PENDING_64 0x14
+
+/* PCI_MSI_ADDRESS_LO */
+#define PCI_MSI_ADDRESS_LO_MASK (~0x3)
+
+/* If we get rid of cap allocator, we won't need those. */
+#define PCI_MSI_32_SIZEOF 0x0a
+#define PCI_MSI_64_SIZEOF 0x0e
+#define PCI_MSI_32M_SIZEOF 0x14
+#define PCI_MSI_64M_SIZEOF 0x18
+
+/* If we get rid of cap allocator, we won't need this. */
+static inline uint8_t msi_cap_sizeof(uint16_t flags)
+{
+ switch (flags & (PCI_MSI_FLAGS_MASKBIT | PCI_MSI_FLAGS_64BIT)) {
+ case PCI_MSI_FLAGS_MASKBIT | PCI_MSI_FLAGS_64BIT:
+ return PCI_MSI_64M_SIZEOF;
+ case PCI_MSI_FLAGS_64BIT:
+ return PCI_MSI_64_SIZEOF;
+ case PCI_MSI_FLAGS_MASKBIT:
+ return PCI_MSI_32M_SIZEOF;
+ case 0:
+ return PCI_MSI_32_SIZEOF;
+ default:
+ abort();
+ break;
+ }
+ return 0;
+}
+
+//#define MSI_DEBUG
+
+#ifdef MSI_DEBUG
+# define MSI_DPRINTF(fmt, ...) \
+ fprintf(stderr, "%s:%d " fmt, __func__, __LINE__, ## __VA_ARGS__)
+#else
+# define MSI_DPRINTF(fmt, ...) do { } while (0)
+#endif
+#define MSI_DEV_PRINTF(dev, fmt, ...) \
+ MSI_DPRINTF("%s:%x " fmt, (dev)->name, (dev)->devfn, ## __VA_ARGS__)
+
+static inline uint8_t msi_nr_vectors(uint16_t flags)
+{
+ return 1U <<
+ ((flags & PCI_MSI_FLAGS_QSIZE) >> (ffs(PCI_MSI_FLAGS_QSIZE) - 1));
+}
+
+static inline uint8_t msi_flags_off(const PCIDevice* dev)
+{
+ return dev->msi_cap + PCI_MSI_FLAGS;
+}
+
+static inline uint8_t msi_address_lo_off(const PCIDevice* dev)
+{
+ return dev->msi_cap + PCI_MSI_ADDRESS_LO;
+}
+
+static inline uint8_t msi_address_hi_off(const PCIDevice* dev)
+{
+ return dev->msi_cap + PCI_MSI_ADDRESS_HI;
+}
+
+static inline uint8_t msi_data_off(const PCIDevice* dev, bool msi64bit)
+{
+ return dev->msi_cap + (msi64bit ? PCI_MSI_DATA_64 : PCI_MSI_DATA_32);
+}
+
+static inline uint8_t msi_mask_off(const PCIDevice* dev, bool msi64bit)
+{
+ return dev->msi_cap + (msi64bit ? PCI_MSI_MASK_64 : PCI_MSI_MASK_32);
+}
+
+static inline uint8_t msi_pending_off(const PCIDevice* dev, bool msi64bit)
+{
+ return dev->msi_cap + (msi64bit ? PCI_MSI_PENDING_64 : PCI_MSI_PENDING_32);
+}
+
+bool msi_enabled(const PCIDevice *dev)
+{
+ return msi_present(dev) &&
+ (pci_get_word(dev->config + msi_flags_off(dev)) &
+ PCI_MSI_FLAGS_ENABLE);
+}
+
+int msi_init(struct PCIDevice *dev, uint8_t offset,
+ uint8_t nr_vectors, bool msi64bit, bool msi_per_vector_mask)
+{
+ uint8_t vectors_order;
+ uint16_t flags;
+ uint8_t cap_size;
+ int config_offset;
+ MSI_DEV_PRINTF(dev,
+ "init offset: 0x%"PRIx8" vector: %"PRId8
+ " 64bit %d mask %d\n",
+ offset, nr_vectors, msi64bit, msi_per_vector_mask);
+
+ assert(!(nr_vectors & (nr_vectors - 1))); /* power of 2 */
+ assert(nr_vectors > 0);
+ assert(nr_vectors <= 32); /* the nr of MSI vectors is up to 32 */
+ vectors_order = ffs(nr_vectors) - 1;
+
+ flags = (vectors_order << (ffs(PCI_MSI_FLAGS_QMASK) - 1)) &
+ PCI_MSI_FLAGS_QMASK;
+ if (msi64bit) {
+ flags |= PCI_MSI_FLAGS_64BIT;
+ }
+ if (msi_per_vector_mask) {
+ flags |= PCI_MSI_FLAGS_MASKBIT;
+ }
+
+ cap_size = msi_cap_sizeof(flags);
+ config_offset = pci_add_capability(dev, PCI_CAP_ID_MSI, offset, cap_size);
+ if (config_offset < 0) {
+ return config_offset;
+ }
+
+ dev->msi_cap = config_offset;
+ dev->cap_present |= QEMU_PCI_CAP_MSI;
+
+ pci_set_word(dev->config + msi_flags_off(dev), flags);
+ pci_set_word(dev->wmask + msi_flags_off(dev),
+ PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
+ pci_set_long(dev->wmask + msi_address_lo_off(dev),
+ PCI_MSI_ADDRESS_LO_MASK);
+ if (msi64bit) {
+ pci_set_long(dev->wmask + msi_address_hi_off(dev), 0xffffffff);
+ }
+ pci_set_word(dev->wmask + msi_data_off(dev, msi64bit), 0xffff);
+
+ if (msi_per_vector_mask) {
+ pci_set_long(dev->wmask + msi_mask_off(dev, msi64bit),
+ (1U << nr_vectors) - 1);
+ }
+ return config_offset;
+}
+
+void msi_uninit(struct PCIDevice *dev)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ uint8_t cap_size = msi_cap_sizeof(flags);
+ pci_del_capability(dev, PCI_CAP_ID_MSIX, cap_size);
+ MSI_DEV_PRINTF(dev, "uninit\n");
+}
+
+void msi_reset(PCIDevice *dev)
+{
+ uint16_t flags;
+ bool msi64bit;
+
+ flags = pci_get_word(dev->config + msi_flags_off(dev));
+ flags &= ~(PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
+ msi64bit = flags & PCI_MSI_FLAGS_64BIT;
+
+ pci_set_word(dev->config + msi_flags_off(dev), flags);
+ pci_set_long(dev->config + msi_address_lo_off(dev), 0);
+ if (msi64bit) {
+ pci_set_long(dev->config + msi_address_hi_off(dev), 0);
+ }
+ pci_set_word(dev->config + msi_data_off(dev, msi64bit), 0);
+ if (flags & PCI_MSI_FLAGS_MASKBIT) {
+ pci_set_long(dev->config + msi_mask_off(dev, msi64bit), 0);
+ pci_set_long(dev->config + msi_pending_off(dev, msi64bit), 0);
+ }
+ MSI_DEV_PRINTF(dev, "reset\n");
+}
+
+static bool msi_is_masked(const PCIDevice *dev, uint8_t vector)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ uint32_t mask;
+
+ if (!(flags & PCI_MSI_FLAGS_MASKBIT)) {
+ return false;
+ }
+
+ mask = pci_get_long(dev->config +
+ msi_mask_off(dev, flags & PCI_MSI_FLAGS_64BIT));
+ return mask & (1U << vector);
+}
+
+static void msi_set_pending(PCIDevice *dev, uint8_t vector)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
+ uint32_t pending;
+
+ assert(flags & PCI_MSI_FLAGS_MASKBIT);
+
+ pending = pci_get_long(dev->config + msi_pending_off(dev, msi64bit));
+ pending |= 1U << vector;
+ pci_set_long(dev->config + msi_pending_off(dev, msi64bit), pending);
+ MSI_DEV_PRINTF(dev, "pending vector 0x%"PRIx8"\n", vector);
+}
+
+void msi_notify(PCIDevice *dev, uint8_t vector)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
+ uint8_t nr_vectors = msi_nr_vectors(flags);
+ uint64_t address;
+ uint32_t data;
+
+ assert(vector < nr_vectors);
+ if (msi_is_masked(dev, vector)) {
+ msi_set_pending(dev, vector);
+ return;
+ }
+
+ if (msi64bit){
+ address = pci_get_quad(dev->config + msi_address_lo_off(dev));
+ } else {
+ address = pci_get_long(dev->config + msi_address_lo_off(dev));
+ }
+
+ /* upper bit 31:16 is zero */
+ data = pci_get_word(dev->config + msi_data_off(dev, msi64bit));
+ if (nr_vectors > 1) {
+ data &= ~(nr_vectors - 1);
+ data |= vector;
+ }
+
+ MSI_DEV_PRINTF(dev,
+ "notify vector 0x%"PRIx8
+ " address: 0x%"PRIx64" data: 0x%"PRIx32"\n",
+ vector, address, data);
+ stl_phys(address, data);
+}
+
+/* call this function after updating configs by pci_default_write_config(). */
+void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
+ bool msi_per_vector_mask = flags & PCI_MSI_FLAGS_MASKBIT;
+ uint8_t nr_vectors;
+ uint8_t nr_vectors_capable;
+ uint8_t vector;
+ uint32_t pending;
+ int i;
+
+#ifdef MSI_DEBUG
+ if (ranges_overlap(addr, len, dev->msi_cap, msi_cap_sizeof(flags))) {
+ MSI_DEV_PRINTF(dev, "addr 0x%"PRIx32" val 0x%"PRIx32" len %d\n",
+ addr, val, len);
+ MSI_DEV_PRINTF(dev, "ctrl: 0x%"PRIx16" address: 0x%"PRIx32,
+ flags,
+ pci_get_long(dev->config + msi_address_lo_off(dev)));
+ if (msi64bit) {
+ fprintf(stderr, " addrss-hi: 0x%"PRIx32,
+ pci_get_long(dev->config + msi_address_hi_off(dev)));
+ }
+ fprintf(stderr, " data: 0x%"PRIx16,
+ pci_get_word(dev->config + msi_data_off(dev, msi64bit)));
+ if (flags & PCI_MSI_FLAGS_MASKBIT) {
+ fprintf(stderr, " mask 0x%"PRIx32" pending 0x%"PRIx32,
+ pci_get_long(dev->config + msi_mask_off(dev, msi64bit)),
+ pci_get_long(dev->config + msi_pending_off(dev, msi64bit)));
+ }
+ fprintf(stderr, "\n");
+ }
+#endif
+
+ /* do we modified? */
+ if (!(ranges_overlap(addr, len, msi_flags_off(dev), 2) ||
+ (msi_per_vector_mask &&
+ ranges_overlap(addr, len, msi_mask_off(dev, msi64bit), 4)))) {
+ return;
+ }
+
+ if (!(flags & PCI_MSI_FLAGS_ENABLE)) {
+ return;
+ }
+
+ /*
+ * Now MSI is enabled, clear INTx# interrupts.
+ * Guest is prohibited from enabling msi while interrupt is asserted.
+ * But it can. So we just discard the interrupts as moderate fallback.
+ *
+ * 6.8.3.3. Enabling Operation
+ * While enabled for MSI or MSI-X operation, a function is prohibited
+ * from using its INTx# pin (if implemented) to request
+ * service (MSI, MSI-X, and INTx# are mutually exclusive).
+ */
+ for (i = 0; i < PCI_NUM_PINS; ++i) {
+ qemu_set_irq(dev->irq[i], 0);
+ }
+
+ /* nr_vectors might be set bigger than capable. So clamp it. */
+ nr_vectors = msi_nr_vectors(flags);
+ nr_vectors_capable = 1U <<
+ ((flags & PCI_MSI_FLAGS_QMASK) >> (ffs(PCI_MSI_FLAGS_QMASK) - 1));
+ if (nr_vectors > nr_vectors_capable) {
+ uint8_t vectors_order;
+ nr_vectors = nr_vectors_capable;
+ vectors_order = ffs(nr_vectors) - 1;
+
+ flags &= ~PCI_MSI_FLAGS_QSIZE;
+ flags |= (vectors_order << (ffs(PCI_MSI_FLAGS_QSIZE) - 1)) &
+ PCI_MSI_FLAGS_QSIZE;
+ pci_set_word(dev->config + msi_flags_off(dev), flags);
+ }
+
+ if (!msi_per_vector_mask) {
+ /* if per vector masking isn't supported,
+ there is no pending interrupt. */
+ return;
+ }
+
+ /*
+ * If the number of vectors are reduced, clear pending bit for
+ * freed vectors.
+ * Since it is unknown here which interrupt will be used for pending bit
+ * of freed vector, just discard the interrupt.
+ */
+ pending = pci_get_long(dev->config + msi_pending_off(dev, msi64bit));
+ pending &= (1U << pending) - 1;
+ pci_set_long(dev->config + msi_pending_off(dev, msi64bit), pending);
+
+ /* deliver pending interrupts which are unmasked */
+ for (vector = 0; vector < nr_vectors; ++vector) {
+ if (msi_is_masked(dev, vector) || !(pending & (1U << vector))) {
+ continue;
+ }
+
+ pending &= ~(1U << vector);
+ pci_set_long(dev->config + msi_pending_off(dev, msi64bit),
+ pending);
+ msi_notify(dev, vector);
+ }
+}
+
+uint8_t msi_nr_vectors_allocated(const PCIDevice *dev)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ return msi_nr_vectors(flags);
+}
diff --git a/hw/msi.h b/hw/msi.h
new file mode 100644
index 0000000..eac9c78
--- /dev/null
+++ b/hw/msi.h
@@ -0,0 +1,41 @@
+/*
+ * msi.h
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef QEMU_MSI_H
+#define QEMU_MSI_H
+
+#include "qemu-common.h"
+#include "pci.h"
+
+bool msi_enabled(const PCIDevice *dev);
+int msi_init(struct PCIDevice *dev, uint8_t offset,
+ uint8_t nr_vectors, bool msi64bit, bool msi_per_vector_mask);
+void msi_uninit(struct PCIDevice *dev);
+void msi_reset(PCIDevice *dev);
+void msi_notify(PCIDevice *dev, uint8_t vector);
+void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len);
+uint8_t msi_nr_vectors_allocated(const PCIDevice *dev);
+
+static inline bool msi_present(const PCIDevice *dev)
+{
+ return dev->cap_present & QEMU_PCI_CAP_MSI;
+}
+
+#endif /* QEMU_MSI_H */
diff --git a/hw/pci.h b/hw/pci.h
index 2b4c318..296c7ba 100644
--- a/hw/pci.h
+++ b/hw/pci.h
@@ -109,11 +109,12 @@ typedef struct PCIIORegion {
/* Bits in cap_present field. */
enum {
- QEMU_PCI_CAP_MSIX = 0x1,
- QEMU_PCI_CAP_EXPRESS = 0x2,
+ QEMU_PCI_CAP_MSI = 0x1,
+ QEMU_PCI_CAP_MSIX = 0x2,
+ QEMU_PCI_CAP_EXPRESS = 0x4,
/* multifunction capable device */
-#define QEMU_PCI_CAP_MULTIFUNCTION_BITNR 2
+#define QEMU_PCI_CAP_MULTIFUNCTION_BITNR 3
QEMU_PCI_CAP_MULTIFUNCTION = (1 << QEMU_PCI_CAP_MULTIFUNCTION_BITNR),
};
@@ -168,6 +169,9 @@ struct PCIDevice {
/* Version id needed for VMState */
int32_t version_id;
+ /* Offset of MSI capability in config space */
+ uint8_t msi_cap;
+
/* Location of option rom */
char *romfile;
ram_addr_t rom_offset;
--
1.7.1.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH v2 2/9] pcie: helper functions for pcie extended capability.
2010-09-08 7:39 [Qemu-devel] [PATCH v2 0/9] pcie port switch emulators Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 1/9] msi: implemented msi Isaku Yamahata
@ 2010-09-08 7:39 ` Isaku Yamahata
2010-09-08 10:31 ` [Qemu-devel] " Michael S. Tsirkin
` (2 more replies)
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 3/9] pcie port: define struct PCIEPort/PCIESlot and helper functions Isaku Yamahata
` (6 subsequent siblings)
8 siblings, 3 replies; 25+ messages in thread
From: Isaku Yamahata @ 2010-09-08 7:39 UTC (permalink / raw)
To: qemu-devel; +Cc: skandasa, etmartin, wexu2, mst, adhyas, yamahata
This patch implements helper functions for pci express extended capability.
NOTE: presence detection depends on pci_qdev_init() change.
PCIExpressDevice::aer_log_max is in PCIDevice for device property.
Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
---
Makefile.objs | 1 +
hw/pci.h | 24 +
hw/pcie.c | 1668 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
hw/pcie.h | 186 +++++++
qemu-common.h | 1 +
5 files changed, 1880 insertions(+), 0 deletions(-)
create mode 100644 hw/pcie.c
create mode 100644 hw/pcie.h
diff --git a/Makefile.objs b/Makefile.objs
index 5f5a4c5..eeb5134 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -186,6 +186,7 @@ hw-obj-$(CONFIG_PIIX4) += piix4.o
# PCI watchdog devices
hw-obj-y += wdt_i6300esb.o
+hw-obj-y += pcie.o
hw-obj-y += msix.o msi.o
# PCI network cards
diff --git a/hw/pci.h b/hw/pci.h
index 296c7ba..bccab3a 100644
--- a/hw/pci.h
+++ b/hw/pci.h
@@ -9,6 +9,8 @@
/* PCI includes legacy ISA access. */
#include "isa.h"
+#include "pcie.h"
+
/* PCI bus */
#define PCI_DEVFN(slot, func) ((((slot) & 0x1f) << 3) | ((func) & 0x07))
@@ -172,6 +174,12 @@ struct PCIDevice {
/* Offset of MSI capability in config space */
uint8_t msi_cap;
+ /* PCI Express */
+ PCIExpressDevice *exp;
+ /* Theoretically this belongs to PCIExpressDevice.
+ However it is here for property and save/load */
+ struct pcie_aer_log aer_log;
+
/* Location of option rom */
char *romfile;
ram_addr_t rom_offset;
@@ -367,6 +375,22 @@ static inline uint32_t pci_config_size(const PCIDevice *d)
return pci_is_express(d) ? PCIE_CONFIG_SPACE_SIZE : PCI_CONFIG_SPACE_SIZE;
}
+
+/* These are pci express specific, so should belong to pcie.h.
+ they're here to avoid header inclusion error. */
+static inline uint8_t pci_pcie_cap(const PCIDevice *d)
+{
+ return d->exp ? d->exp->exp_cap : 0;
+}
+
+/* AER */
+static inline uint16_t pcie_aer_cap(const PCIDevice *d)
+{
+ assert(d->exp);
+ return d->exp->aer_cap;
+}
+
+
/* These are not pci specific. Should move into a separate header.
* Only pci.c uses them, so keep them here for now.
*/
diff --git a/hw/pcie.c b/hw/pcie.c
new file mode 100644
index 0000000..1f24c2a
--- /dev/null
+++ b/hw/pcie.c
@@ -0,0 +1,1668 @@
+/*
+ * pcie.c
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "sysemu.h"
+#include "pci_bridge.h"
+#include "pcie.h"
+#include "msix.h"
+#include "msi.h"
+#include "pci_internals.h"
+
+//#define DEBUG_PCIE
+#ifdef DEBUG_PCIE
+# define PCIE_DPRINTF(fmt, ...) \
+ fprintf(stderr, "%s:%d " fmt, __func__, __LINE__, ## __VA_ARGS__)
+#else
+# define PCIE_DPRINTF(fmt, ...) do {} while (0)
+#endif
+#define PCIE_DEV_PRINTF(dev, fmt, ...) \
+ PCIE_DPRINTF("%s:%x "fmt, (dev)->name, (dev)->devfn, ## __VA_ARGS__)
+
+static inline const char *pcie_hp_event_name(enum PCIExpressHotPlugEvent event)
+{
+ switch (event) {
+ case PCI_EXP_HP_EV_ABP:
+ return "attention button pushed";
+ case PCI_EXP_HP_EV_PDC:
+ return "present detection changed";
+ case PCI_EXP_HP_EV_CCI:
+ return "command completed";
+ default:
+ break;
+ }
+ return "Unknown event";
+}
+
+static void pcie_aer_clear_error(PCIDevice *dev);
+static void pcie_aer_root_notify(PCIDevice *dev, bool trigger, int level);
+static AER_ERR_MSG_RESULT
+pcie_aer_errmsg_alldev(PCIDevice *dev, const struct pcie_aer_err_msg *msg);
+static AER_ERR_MSG_RESULT
+pcie_aer_errmsg_vbridge(PCIDevice *dev, const struct pcie_aer_err_msg *msg);
+
+/***************************************************************************
+ * pci express capability helper functions
+ */
+
+#define PCI_EXP_VER2_SIZEOF 0x3c /* express capability of version 2 */
+
+/* PCI_EXP_FLAGS */
+#define PCI_EXP_FLAGS_VER2 2 /* for now, supports only version 2 */
+#define PCI_EXP_FLAGS_IRQ_SHIFT 9
+#define PCI_EXP_FLAGS_IRQ_REG(irq) (((irq) << PCI_EXP_FLAGS_IRQ_SHIFT) & PCI_EXP_FLAGS_IRQ)
+#define PCI_EXP_FLAGS_TYPE_SHIFT 4
+
+/* PCI_EXP_LINK{CAP, STA} */
+/* link speed */
+#define PCI_EXP_LNK_LS_25 1
+
+#define PCI_EXP_LNK_MLW_SHIFT 4
+#define PCI_EXP_LNK_MLW_1 (1 << PCI_EXP_LNK_MLW_SHIFT)
+
+/* PCI_EXP_LINKCAP */
+#define PCI_EXP_LNKCAP_ASPMS_SHIFT 10
+#define PCI_EXP_LNKCAP_ASPMS_0S (1 << PCI_EXP_LNKCAP_ASPMS_SHIFT)
+
+#define PCI_EXP_LNKCAP_PN_SHIFT 24
+#define PCI_EXP_LNKCAP_PN_REG(pn) (((pn) << PCI_EXP_LNKCAP_PN_SHIFT) & PCI_EXP_LNKCAP_PN)
+
+#define PCI_EXP_SLTCAP_PSN_SHIFT 19
+#define PCI_EXP_SLTCAP_PSN_REG(slot) (((slot) << PCI_EXP_SLTCAP_PSN_SHIFT) & PCI_EXP_SLTCAP_PSN)
+
+#define PCI_EXP_SLTCTL_AIC_SHIFT 6
+#define PCI_EXP_SLTCTL_AIC_ON (PCI_EXP_HP_IND_ON << PCI_EXP_SLTCTL_AIC_SHIFT)
+#define PCI_EXP_SLTCTL_AIC_BLINK (PCI_EXP_HP_IND_BLINK << PCI_EXP_SLTCTL_AIC_SHIFT)
+#define PCI_EXP_SLTCTL_AIC_OFF (PCI_EXP_HP_IND_OFF << PCI_EXP_SLTCTL_AIC_SHIFT)
+
+#define PCI_EXP_SLTCTL_PIC_SHIFT 8
+#define PCI_EXP_SLTCTL_PIC_ON (PCI_EXP_HP_IND_ON << PCI_EXP_SLTCTL_PIC_SHIFT)
+#define PCI_EXP_SLTCTL_PIC_BLINK (PCI_EXP_HP_IND_BLINK << PCI_EXP_SLTCTL_PIC_SHIFT)
+#define PCI_EXP_SLTCTL_PIC_OFF (PCI_EXP_HP_IND_OFF << PCI_EXP_SLTCTL_PIC_SHIFT)
+
+#define PCI_EXP_DEVCAP2_EFF 0x100000
+#define PCI_EXP_DEVCAP2_EETLPP 0x200000
+
+#define PCI_EXP_DEVCTL2_EETLPPB 0x80
+
+static void pcie_notify(PCIDevice *dev, uint16_t vector,
+ bool trigger, int level)
+{
+ /* masking/masking interrupt is handled by upper layer.
+ * i.e. msix_notify() for MSI-X
+ * msi_notify() for MSI
+ * pci_set_irq() for INTx
+ */
+ PCIE_DEV_PRINTF(dev, "noitfy vector %d tirgger:%d level:%d\n",
+ vector, trigger, level);
+ if (msix_enabled(dev)) {
+ if (trigger) {
+ msix_notify(dev, vector);
+ }
+ } else if (msi_enabled(dev)) {
+ if (trigger){
+ msi_notify(dev, vector);
+ }
+ } else {
+ qemu_set_irq(dev->irq[0], level);
+ }
+}
+
+static inline uint32_t pcie_written_val_long(uint32_t addr, uint32_t val,
+ uint32_t pos)
+{
+ if (addr >= pos) {
+ val <<= addr - pos;
+ } else {
+ val >>= pos - addr;
+ }
+ return val;
+}
+
+static inline uint16_t pcie_written_val_word(uint32_t addr, uint32_t val,
+ uint32_t pos)
+{
+ return pcie_written_val_long(addr, val, pos) & 0xffff;
+}
+
+/*
+ * RW1C: Write-1-to-clear
+ * regiger written val result
+ * 0 0 => 0
+ * 1 0 => 1
+ * 0 1 => 0
+ * 1 1 => 0
+ */
+static inline void pcie_w1c_long(PCIDevice *d, uint32_t pos, uint32_t mask,
+ uint32_t addr, uint32_t val)
+{
+ uint32_t written = pcie_written_val_long(addr, val, pos) & mask;
+ uint32_t reg = pci_get_long(d->config + pos);
+ reg &= ~written;
+ pci_set_long(d->config + pos, reg);
+}
+
+static inline void pcie_w1c_word(PCIDevice *d, uint32_t pos, uint16_t mask,
+ uint32_t addr, uint32_t val)
+{
+ uint16_t written = pcie_written_val_word(addr, val, pos) & mask;
+ uint16_t reg = pci_get_word(d->config + pos);
+ reg &= ~written;
+ pci_set_word(d->config + pos, reg);
+}
+
+int pci_pcie_cap_init(PCIDevice *dev,
+ uint8_t offset, uint8_t type, uint8_t port)
+{
+ int exp_cap;
+ uint8_t *pcie_cap;
+
+ assert(pci_is_express(dev));
+ dev->exp = qemu_mallocz(sizeof(*dev->exp));
+
+ exp_cap = pci_add_capability(dev, PCI_CAP_ID_EXP, offset,
+ PCI_EXP_VER2_SIZEOF);
+ if (exp_cap < 0) {
+ qemu_free(dev->exp);
+ dev->exp = NULL;
+ return exp_cap;
+ }
+ dev->exp->exp_cap = exp_cap;
+ /* dev->cap_present |= QEMU_PCI_CAP_EXPRESS; */ /* already done in pci_qdev_init() */
+
+ pcie_cap = dev->config + pci_pcie_cap(dev);
+
+ /* capability register
+ interrupt message number defaults to 0 */
+ pci_set_word(pcie_cap + PCI_EXP_FLAGS,
+ ((type << PCI_EXP_FLAGS_TYPE_SHIFT) & PCI_EXP_FLAGS_TYPE) |
+ PCI_EXP_FLAGS_VER2);
+
+ /* device capability register
+ * table 7-12:
+ * roll based error reporting bit must be set by all
+ * Functions conforming to the ECN, PCI Express Base
+ * Specification, Revision 1.1., or subsequent PCI Express Base
+ * Specification revisions.
+ */
+ pci_set_long(pcie_cap + PCI_EXP_DEVCAP, PCI_EXP_DEVCAP_RBER);
+
+ pci_set_long(pcie_cap + PCI_EXP_LNKCAP,
+ PCI_EXP_LNKCAP_PN_REG(port) |
+ PCI_EXP_LNKCAP_ASPMS_0S |
+ PCI_EXP_LNK_MLW_1 |
+ PCI_EXP_LNK_LS_25);
+
+ pci_set_word(pcie_cap + PCI_EXP_LNKSTA,
+ PCI_EXP_LNK_MLW_1 | PCI_EXP_LNK_LS_25);
+
+ pci_set_long(pcie_cap + PCI_EXP_DEVCAP2,
+ PCI_EXP_DEVCAP2_EFF | PCI_EXP_DEVCAP2_EETLPP);
+
+ pci_set_word(dev->wmask + exp_cap, PCI_EXP_DEVCTL2_EETLPPB);
+ return exp_cap;
+}
+
+int pci_pcie_cap_exit(PCIDevice *dev)
+{
+ /* pci_del_capability(dev, PCI_CAP_ID_EXP, PCI_EXP_VER2_SIZEOF); */
+ qemu_free(dev->exp);
+ return 0;
+}
+
+uint8_t pcie_cap_get_type(const PCIDevice *dev)
+{
+ uint32_t pos = pci_pcie_cap(dev);
+ assert(pos > 0);
+ return (pci_get_word(dev->config + pos + PCI_EXP_FLAGS) &
+ PCI_EXP_FLAGS_TYPE) >> PCI_EXP_FLAGS_TYPE_SHIFT;
+}
+
+/* MSI/MSI-X */
+/* pci express interrupt message number */
+void pcie_cap_flags_set_vector(PCIDevice *dev, uint8_t vector)
+{
+ uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
+ uint16_t tmp;
+
+ assert(vector <= 32);
+ tmp = pci_get_word(pcie_cap + PCI_EXP_FLAGS);
+ tmp &= ~PCI_EXP_FLAGS_IRQ;
+ tmp |= PCI_EXP_FLAGS_IRQ_REG(vector);
+ pci_set_word(pcie_cap + PCI_EXP_FLAGS, tmp);
+}
+
+uint8_t pcie_cap_flags_get_vector(PCIDevice *dev)
+{
+ return (pci_get_word(dev->config + pci_pcie_cap(dev) + PCI_EXP_FLAGS) &
+ PCI_EXP_FLAGS_IRQ) >> PCI_EXP_FLAGS_IRQ_SHIFT;
+}
+
+static void pcie_cap_notify(PCIDevice *dev, bool trigger, int level)
+{
+ pcie_notify(dev, pcie_cap_flags_get_vector(dev), trigger, level);
+}
+
+void pcie_cap_deverr_init(PCIDevice *dev)
+{
+ uint32_t pos = pci_pcie_cap(dev);
+ uint8_t *pcie_cap = dev->config + pos;
+ uint8_t *pcie_wmask = dev->wmask + pos;
+
+ pci_set_long(pcie_cap + PCI_EXP_DEVCAP,
+ pci_get_long(pcie_cap + PCI_EXP_DEVCAP) |
+ PCI_EXP_DEVCAP_RBER);
+
+ pci_set_long(pcie_wmask + PCI_EXP_DEVCTL,
+ pci_get_long(pcie_wmask + PCI_EXP_DEVCTL) |
+ PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE |
+ PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE);
+}
+
+void pcie_cap_deverr_reset(PCIDevice *dev)
+{
+ uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
+ pci_set_long(pcie_cap + PCI_EXP_DEVCTL,
+ pci_get_long(pcie_cap + PCI_EXP_DEVCTL) &
+ ~(PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE |
+ PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE));
+}
+
+void pcie_cap_deverr_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len)
+{
+ uint32_t pos = pci_pcie_cap(dev);
+ if (ranges_overlap(addr, len, pos + PCI_EXP_DEVSTA, 4)) {
+ /* RW1C */
+ pcie_w1c_long(dev, pos + PCI_EXP_DEVSTA,
+ PCI_EXP_DEVSTA_CED | PCI_EXP_DEVSTA_NFED |
+ PCI_EXP_DEVSTA_URD | PCI_EXP_DEVSTA_URD,
+ addr, val);
+ }
+}
+
+/*
+ * events: PCI_EXP_HP_EV_xxx
+ * status: bit or of PCI_EXP_SLTSTA_xxx
+ */
+static void pcie_cap_slot_event(PCIDevice *dev,
+ enum PCIExpressHotPlugEvent events,
+ uint16_t status)
+{
+ bool trigger = false;
+ int level = 0;
+ uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
+ uint16_t sltctl = pci_get_word(pcie_cap + PCI_EXP_SLTCTL);
+ uint16_t sltsta = pci_get_word(pcie_cap + PCI_EXP_SLTSTA);
+
+ PCIE_DEV_PRINTF(dev,
+ "sltctl: 0x%0x2 sltsta: 0x%02x event:%x %s status:%d\n",
+ sltctl, sltsta,
+ events, pcie_hp_event_name(events), status);
+ events &= PCI_EXP_HP_EV_SUPPORTED;
+ if ((sltctl & PCI_EXP_SLTCTL_HPIE) && (sltctl & events) &&
+ ((sltsta ^ events) & events) /* 0 -> 1 */) {
+ trigger = true;
+ }
+
+ if (events & PCI_EXP_HP_EV_PDC) {
+ sltsta &= ~PCI_EXP_SLTSTA_PDS;
+ sltsta |= (status & PCI_EXP_SLTSTA_PDS);
+ }
+ sltsta |= events;
+ pci_set_word(pcie_cap + PCI_EXP_SLTSTA, sltsta);
+ PCIE_DEV_PRINTF(dev, "sltsta -> %02xn", sltsta);
+
+ if ((sltctl & PCI_EXP_SLTCTL_HPIE) && (sltsta & PCI_EXP_HP_EV_SUPPORTED)) {
+ level = 1;
+ }
+
+ pcie_cap_notify(dev, trigger, level);
+}
+
+static int pcie_cap_slot_hotplug(DeviceState *qdev,
+ PCIDevice *pci_dev, int state)
+{
+ PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev);
+ uint8_t *pcie_cap = d->config + pci_pcie_cap(d);
+ uint16_t sltsta = pci_get_word(pcie_cap + PCI_EXP_SLTSTA);
+
+ if (!pci_dev->qdev.hotplugged) {
+ assert(state); /* this case only happens machine creation. */
+ sltsta |= PCI_EXP_SLTSTA_PDS;
+ pci_set_word(pcie_cap + PCI_EXP_SLTSTA, sltsta);
+ return 0;
+ }
+
+ PCIE_DEV_PRINTF(pci_dev, "hotplug state: %d\n", state);
+ if (sltsta & PCI_EXP_SLTSTA_EIS) {
+ /* the slot is electromechanically locked. */
+ return -EBUSY;
+ }
+
+ if (state) {
+ if (PCI_FUNC(pci_dev->devfn) == 0) {
+ /* event is per slot. Not per function
+ * only generates event for function = 0.
+ * When hot plug, populate functions > 0
+ * and then add function = 0 last.
+ */
+ pcie_cap_slot_event(d, PCI_EXP_HP_EV_PDC, PCI_EXP_SLTSTA_PDS);
+ }
+ } else {
+ PCIBridge *br;
+ PCIBus *bus;
+ DeviceState *next;
+ if (PCI_FUNC(pci_dev->devfn) != 0) {
+ /* event is per slot. Not per function.
+ accepts function = 0 only. */
+ return -EINVAL;
+ }
+
+ /* zap all functions. */
+ br = DO_UPCAST(PCIBridge, dev, d);
+ bus = pci_bridge_get_sec_bus(br);
+ QLIST_FOREACH_SAFE(qdev, &bus->qbus.children, sibling, next) {
+ qdev_free(qdev);
+ }
+
+ pcie_cap_slot_event(d, PCI_EXP_HP_EV_PDC, 0);
+ }
+ return 0;
+}
+
+/* pci express slot for pci express root/downstream port
+ PCI express capability slot registers */
+void pcie_cap_slot_init(PCIDevice *dev, uint16_t slot)
+{
+ uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
+ uint8_t *pcie_wmask = dev->wmask + pci_pcie_cap(dev);
+ uint32_t tmp;
+
+ pci_set_word(pcie_cap + PCI_EXP_FLAGS,
+ pci_get_word(pcie_cap + PCI_EXP_FLAGS) | PCI_EXP_FLAGS_SLOT);
+
+ tmp = pci_get_long(pcie_cap + PCI_EXP_SLTCAP);
+ tmp &= PCI_EXP_SLTCAP_PSN;
+ tmp |=
+ PCI_EXP_SLTCAP_PSN_REG(slot) |
+ PCI_EXP_SLTCAP_EIP |
+ PCI_EXP_SLTCAP_HPS |
+ PCI_EXP_SLTCAP_HPC |
+ PCI_EXP_SLTCAP_PIP |
+ PCI_EXP_SLTCAP_AIP |
+ PCI_EXP_SLTCAP_ABP;
+ pci_set_long(pcie_cap + PCI_EXP_SLTCAP, tmp);
+
+ tmp = pci_get_word(pcie_cap + PCI_EXP_SLTCTL);
+ tmp &= ~(PCI_EXP_SLTCTL_PIC | PCI_EXP_SLTCTL_AIC);
+ tmp |= PCI_EXP_SLTCTL_PIC_OFF | PCI_EXP_SLTCTL_AIC_OFF;
+ pci_set_word(pcie_cap + PCI_EXP_SLTCTL, tmp);
+ pci_set_word(pcie_wmask + PCI_EXP_SLTCTL,
+ pci_get_word(pcie_wmask + PCI_EXP_SLTCTL) |
+ PCI_EXP_SLTCTL_PIC |
+ PCI_EXP_SLTCTL_AIC |
+ PCI_EXP_SLTCTL_HPIE |
+ PCI_EXP_SLTCTL_CCIE |
+ PCI_EXP_SLTCTL_PDCE |
+ PCI_EXP_SLTCTL_ABPE);
+
+ pci_bus_hotplug(pci_bridge_get_sec_bus(DO_UPCAST(PCIBridge, dev, dev)),
+ pcie_cap_slot_hotplug, &dev->qdev);
+}
+
+void pcie_cap_slot_reset(PCIDevice *dev)
+{
+ uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
+ uint32_t tmp;
+
+ PCIE_DEV_PRINTF(dev, "reset\n");
+
+ tmp = pci_get_word(pcie_cap + PCI_EXP_SLTCTL);
+ tmp &= ~(PCI_EXP_SLTCTL_EIC |
+ PCI_EXP_SLTCTL_PIC |
+ PCI_EXP_SLTCTL_AIC |
+ PCI_EXP_SLTCTL_HPIE |
+ PCI_EXP_SLTCTL_CCIE |
+ PCI_EXP_SLTCTL_PDCE |
+ PCI_EXP_SLTCTL_ABPE);
+ tmp |= PCI_EXP_SLTCTL_PIC_OFF | PCI_EXP_SLTCTL_AIC_OFF;
+ pci_set_word(pcie_cap + PCI_EXP_SLTCTL, tmp);
+
+ tmp = pci_get_word(pcie_cap + PCI_EXP_SLTSTA);
+ tmp &= ~(PCI_EXP_SLTSTA_EIS | /* by reset, the lock is released */
+ PCI_EXP_SLTSTA_CC |
+ PCI_EXP_SLTSTA_PDC |
+ PCI_EXP_SLTSTA_ABP);
+ pci_set_word(pcie_cap + PCI_EXP_SLTSTA, tmp);
+}
+
+void pcie_cap_slot_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len,
+ uint16_t sltctl_prev)
+{
+ uint32_t pos = pci_pcie_cap(dev);
+ uint8_t *pcie_cap = dev->config + pos;
+ uint16_t sltctl = pci_get_word(pcie_cap + PCI_EXP_SLTCTL);
+ uint16_t sltsta = pci_get_word(pcie_cap + PCI_EXP_SLTSTA);
+
+ PCIE_DEV_PRINTF(dev,
+ "addr: 0x%x val: 0x%x len: %d\n"
+ "\tsltctl_prev: 0x%02x sltctl: 0x%02x sltsta 0x%02x\n",
+ addr, val, len, sltctl_prev, sltctl, sltsta);
+ /* SLTSTA: process SLTSTA before SLTCTL to avoid spurious interrupt */
+ if (ranges_overlap(addr, len, pos + PCI_EXP_SLTSTA, 2)) {
+ /* RW1C */
+ pcie_w1c_word(dev, pos + PCI_EXP_SLTSTA, PCI_EXP_HP_EV_SUPPORTED,
+ addr, val);
+ sltsta = pci_get_word(pcie_cap + PCI_EXP_SLTSTA);
+
+ /* write to stlsta results in clearing bits,
+ so new interrupts won't be generated. */
+ PCIE_DEV_PRINTF(dev, "sltsta -> 0x%02x\n", sltsta);
+ }
+
+ /* SLTCTL */
+ if (ranges_overlap(addr, len, pos + PCI_EXP_SLTCTL, 2)) {
+ PCIE_DEV_PRINTF(dev, "sltctl: 0x%02x -> 0x%02x\n",
+ sltctl_prev, sltctl);
+ if (pcie_written_val_word(addr, val, pos + PCI_EXP_SLTCTL) &
+ PCI_EXP_SLTCTL_EIC) {
+ /* toggle PCI_EXP_SLTSTA_EIS */
+ sltsta = (sltsta & ~PCI_EXP_SLTSTA_EIS) |
+ ((sltsta ^ PCI_EXP_SLTSTA_EIS) & PCI_EXP_SLTSTA_EIS);
+ pci_set_word(pcie_cap + PCI_EXP_SLTSTA, sltsta);
+ PCIE_DEV_PRINTF(dev, "PCI_EXP_SLTCTL_EIC: sltsta -> 0x%02x\n",
+ sltsta);
+ }
+
+ if (sltctl & PCI_EXP_SLTCTL_HPIE) {
+ bool trigger = false;
+ int level = 0;
+
+ if (((sltctl_prev ^ sltctl) & sltctl) & PCI_EXP_HP_EV_SUPPORTED) {
+ /* 0 -> 1 */
+ trigger = true;
+ }
+ if ((sltctl & sltsta) & PCI_EXP_HP_EV_SUPPORTED) {
+ level = 1;
+ }
+ pcie_cap_notify(dev, trigger, level);
+ }
+
+ /* command completed.
+ unlike real hardware, command completes instantaneously */
+#define PCI_EXP_SLTCTL_SUPPORTED \
+ (PCI_EXP_SLTCTL_ABPE | \
+ PCI_EXP_SLTCTL_PDCE | \
+ PCI_EXP_SLTCTL_CCIE | \
+ PCI_EXP_SLTCTL_HPIE | \
+ PCI_EXP_SLTCTL_AIC | \
+ PCI_EXP_SLTCTL_PCC | \
+ PCI_EXP_SLTCTL_EIC)
+ if ( 1 /* (sltctl_prev ^ sltctl) & PCI_EXP_SLTCTL_SUPPORTED */ ) {
+ /* set command completed bit */
+ pcie_cap_slot_event(dev, PCI_EXP_HP_EV_CCI, 0);
+ }
+ }
+}
+
+void pcie_cap_slot_push_attention_button(PCIDevice *dev)
+{
+ pcie_cap_slot_event(dev, PCI_EXP_HP_EV_ABP, 0);
+}
+
+/* root control/capabilities/status. PME isn't emulated for now */
+void pcie_cap_root_init(PCIDevice *dev)
+{
+ uint8_t pos = pci_pcie_cap(dev);
+ pci_set_word(dev->wmask + pos + PCI_EXP_RTCTL,
+ PCI_EXP_RTCTL_SECEE | PCI_EXP_RTCTL_SENFEE |
+ PCI_EXP_RTCTL_SEFEE);
+}
+
+void pcie_cap_root_reset(PCIDevice *dev)
+{
+ uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
+ pci_set_word(pcie_cap + PCI_EXP_RTCTL, 0);
+}
+
+/* function level reset(FLR) */
+void pcie_cap_flr_init(PCIDevice *dev, pcie_flr_fn flr)
+{
+ uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
+ pci_set_word(pcie_cap + PCI_EXP_DEVCAP,
+ pci_get_word(pcie_cap + PCI_EXP_DEVCAP) | PCI_EXP_DEVCAP_FLR);
+ dev->exp->flr = flr;
+}
+
+void pcie_cap_flr_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len)
+{
+ uint32_t pos = pci_pcie_cap(dev);
+ if (ranges_overlap(addr, len, pos + PCI_EXP_DEVCTL, 2)) {
+ uint16_t val16 = pcie_written_val_word(addr, val,
+ pos + PCI_EXP_DEVCTL);
+ if ((val16 & PCI_EXP_DEVCTL_BCR_FLR) && dev->exp->flr) {
+ dev->exp->flr(dev);
+ }
+ }
+}
+
+
+/* Alternative Routing-ID Interpretation (ARI) */
+/* ari forwarding support for down stream port */
+void pcie_cap_ari_init(PCIDevice *dev)
+{
+ uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
+ uint8_t *pcie_wmask = dev->wmask + pci_pcie_cap(dev);
+
+ pci_set_long(pcie_cap + PCI_EXP_DEVCAP2,
+ pci_get_long(pcie_cap + PCI_EXP_DEVCAP2) |
+ PCI_EXP_DEVCAP2_ARI);
+
+ pci_set_long(pcie_wmask + PCI_EXP_DEVCTL2,
+ pci_get_long(pcie_wmask + PCI_EXP_DEVCTL2) |
+ PCI_EXP_DEVCTL2_ARI);
+}
+
+void pcie_cap_ari_reset(PCIDevice *dev)
+{
+ uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
+
+ pci_set_long(pcie_cap + PCI_EXP_DEVCTL2,
+ pci_get_long(pcie_cap + PCI_EXP_DEVCTL2) &
+ ~PCI_EXP_DEVCTL2_ARI);
+}
+
+bool pcie_cap_is_ari_enabled(const PCIDevice *dev)
+{
+ if (!pci_is_express(dev)) {
+ return false;
+ }
+ if (!pci_pcie_cap(dev)) {
+ return false;
+ }
+
+ return pci_get_long(dev->config + pci_pcie_cap(dev) + PCI_EXP_DEVCTL2) &
+ PCI_EXP_DEVCTL2_ARI;
+}
+
+/**************************************************************************
+ * pci express extended capability allocation functions
+ * uint16_t ext_cap_id (16 bit)
+ * uint8_t cap_ver (4 bit)
+ * uint16_t cap_offset (12 bit)
+ * uint16_t ext_cap_size
+ */
+
+#define PCI_EXT_CAP_VER_SHIFT 16
+#define PCI_EXT_CAP_NEXT_MASK 0xfff00000
+#define PCI_EXT_CAP_NEXT_SHIFT 20
+
+#define PCI_EXT_CAP(id, ver, next) ((id) | ((ver) << PCI_EXT_CAP_VER_SHIFT) | ((next) << PCI_EXT_CAP_NEXT_SHIFT))
+
+#define PCI_EXT_CAP_ALIGN 4
+#define PCI_EXT_CAP_ALIGNUP(x) (((x) + PCI_EXT_CAP_ALIGN - 1) & ~(PCI_EXT_CAP_ALIGN - 1))
+
+static int16_t pcie_ext_cap_find_space(PCIDevice *dev, uint16_t size)
+{
+ uint16_t offset = PCI_CONFIG_SPACE_SIZE;
+ uint16_t i = offset;
+
+ while (i < PCIE_CONFIG_SPACE_SIZE - size) {
+ if (dev->used[i]) {
+ offset = PCI_EXT_CAP_ALIGNUP(i + 1);
+ i = offset;
+ continue;
+ } else if (i - offset + 1 == size) {
+ return offset;
+ }
+
+ ++i;
+ }
+
+ return 0;
+}
+
+static uint16_t pcie_find_ext_capability_list(PCIDevice *dev, uint16_t cap_id,
+ uint16_t *prev_p)
+{
+ int ttl;
+
+ uint16_t prev = 0;
+ uint16_t next = PCI_CONFIG_SPACE_SIZE;
+ uint32_t header = pci_get_long(dev->config + next);
+
+ if (!header) {
+ return 0;
+ }
+
+ /* minimum 8 bytes per capability */
+ ttl = (PCIE_CONFIG_SPACE_SIZE - PCI_CONFIG_SPACE_SIZE) / 8;
+
+ while (ttl-- > 0) {
+ if (PCI_EXT_CAP_ID(header) == cap_id) {
+ break;
+ }
+
+ prev = next;
+ next = PCI_EXT_CAP_NEXT(header);
+ if (next < PCI_CONFIG_SPACE_SIZE) {
+ return 0;
+ }
+ header = pci_get_long(dev->config + prev);
+ }
+
+ if (!ttl) {
+ return 0;
+ }
+ if (prev_p) {
+ *prev_p = prev;
+ }
+ return next;
+}
+
+uint16_t pcie_find_ext_capability(PCIDevice *dev, uint16_t cap_id)
+{
+ return pcie_find_ext_capability_list(dev, cap_id, NULL);
+}
+
+static void pcie_ext_cap_set_next(PCIDevice *dev, uint16_t pos, uint16_t next)
+{
+ uint16_t header = pci_get_long(dev->config + pos);
+ assert(!(next & (PCI_EXT_CAP_ALIGN - 1)));
+ header = (header & ~PCI_EXT_CAP_NEXT_MASK) |
+ ((next << PCI_EXT_CAP_NEXT_SHIFT) & PCI_EXT_CAP_NEXT_MASK);
+ pci_set_long(dev->config + pos, header);
+}
+
+static void pcie_allocate_ext_capability(PCIDevice *dev,
+ uint16_t cap_id, uint8_t cap_ver,
+ uint16_t offset, uint16_t size)
+{
+ uint32_t header;
+ uint16_t next;
+
+ assert(offset < offset + size);
+ assert(offset + size < PCIE_CONFIG_SPACE_SIZE);
+ assert(size >= 8);
+
+ if (offset == PCI_CONFIG_SPACE_SIZE) {
+ header = pci_get_long(dev->config + offset);
+ next = PCI_EXT_CAP_NEXT(header);
+ } else {
+ /* find last ext cap */
+ int ttl = (PCIE_CONFIG_SPACE_SIZE - PCI_CONFIG_SPACE_SIZE) / 8;
+ uint16_t pos = PCI_CONFIG_SPACE_SIZE;
+ while (ttl-- > 0) {
+ header = pci_get_long(dev->config + pos);
+ if (PCI_EXT_CAP_NEXT(header) < PCI_CONFIG_SPACE_SIZE) {
+ break;
+ }
+
+ pos = PCI_EXT_CAP_NEXT(header);
+ }
+
+ assert(ttl > 0); /* since it is known that [offset, offset + size]
+ is unused, so ttl shouldn't be zero */
+ pcie_ext_cap_set_next(dev, pos, offset);
+ next = 0;
+ }
+ pci_set_long(dev->config + offset, PCI_EXT_CAP(cap_id, cap_ver, next));
+
+ memset(dev->used + offset, 0xFF, size);
+ /* Make capability read-only by default */
+ memset(dev->wmask + offset, 0, size);
+ /* Check capability by default */
+ memset(dev->cmask + offset, 0xFF, size);
+}
+
+int pcie_add_ext_capability(PCIDevice *dev,
+ uint16_t cap_id, uint8_t cap_ver, uint16_t size)
+{
+ uint16_t offset = pcie_ext_cap_find_space(dev, size);
+
+ if (!offset) {
+ return -ENOSPC;
+ }
+
+ pcie_allocate_ext_capability(dev, cap_id, cap_ver, offset, size);
+ return offset;
+}
+
+int pcie_append_ext_capability(PCIDevice *dev,
+ uint16_t cap_id, uint8_t cap_ver,
+ uint16_t offset, uint16_t size)
+{
+ uint16_t i;
+
+ if (!offset) {
+ return pcie_add_ext_capability(dev, cap_id, cap_ver, size);
+ }
+
+ assert(offset < offset + size);
+ assert(offset + size < PCIE_CONFIG_SPACE_SIZE);
+ assert(size >= 8);
+
+ for (i = offset; i < offset + size; ++i) {
+ if (dev->used[i]) {
+ return -EBUSY;
+ }
+ }
+
+ pcie_allocate_ext_capability(dev, cap_id, cap_ver, offset, size);
+ return offset;
+}
+
+void pcie_del_ext_capability(PCIDevice *dev, uint16_t cap_id, uint16_t size)
+{
+ uint16_t prev;
+ uint16_t offset = pcie_find_ext_capability_list(dev, cap_id, &prev);
+ uint32_t header;
+
+ if (!offset) {
+ return;
+ }
+
+ header = pci_get_long(dev->config + offset);
+ if (prev) {
+ pcie_ext_cap_set_next(dev, prev, PCI_EXT_CAP_NEXT(header));
+ } else {
+ /* move up next ext cap to PCI_CONFIG_SPACE_SIZE? */
+ assert(offset == PCI_CONFIG_SPACE_SIZE);
+ pci_set_long(dev->config + offset,
+ PCI_EXT_CAP(0, 0, PCI_EXT_CAP_NEXT(header)));
+ }
+
+ /* Make capability writeable again */
+ memset(dev->wmask + offset, 0xff, size);
+ /* Clear cmask as device-specific registers can't be checked */
+ memset(dev->cmask + offset, 0, size);
+ memset(dev->used + offset, 0, size);
+}
+
+void pcie_reserve_ext_capability(PCIDevice *dev,
+ uint16_t offset, uint16_t size)
+{
+ memset(dev->used + offset, 0xff, size);
+}
+
+/**************************************************************************
+ * pci express extended capability helper functions
+ */
+
+/* ARI */
+#define PCI_ARI_VER 1
+#define PCI_ARI_SIZEOF 8
+
+int pcie_ari_init(PCIDevice *dev, uint16_t offset, uint16_t nextfn)
+{
+ int pos;
+ pos = pcie_append_ext_capability(dev, PCI_EXT_CAP_ID_ARI, PCI_ARI_VER,
+ offset, PCI_ARI_SIZEOF);
+ if (pos < 0) {
+ return pos;
+ }
+
+ pci_set_long(dev->config + pos + PCI_ARI_CAP, PCI_ARI_CAP_NFN(nextfn));
+ return pos;
+}
+
+/* AER */
+#define PCI_ERR_VER 2
+#define PCI_ERR_SIZEOF 0x48
+
+#define PCI_ERR_UNC_SDN 0x00000020 /* surprise down */
+#define PCI_ERR_UNC_ACSV 0x00200000 /* ACS Violation */
+#define PCI_ERR_UNC_INTN 0x00400000 /* Internal Error */
+#define PCI_ERR_UNC_MCBTLP 0x00800000 /* MC Blcoked TLP */
+#define PCI_ERR_UNC_ATOP_EBLOCKED 0x01000000 /* atomic op egress blocked */
+#define PCI_ERR_UNC_TLP_PRF_BLOCKED 0x02000000 /* TLP Prefix Blocked */
+#define PCI_ERR_UNC_SUPPORTED (PCI_ERR_UNC_DLP | \
+ PCI_ERR_UNC_SDN | \
+ PCI_ERR_UNC_POISON_TLP | \
+ PCI_ERR_UNC_FCP | \
+ PCI_ERR_UNC_COMP_TIME | \
+ PCI_ERR_UNC_COMP_ABORT | \
+ PCI_ERR_UNC_UNX_COMP | \
+ PCI_ERR_UNC_RX_OVER | \
+ PCI_ERR_UNC_MALF_TLP | \
+ PCI_ERR_UNC_ECRC | \
+ PCI_ERR_UNC_UNSUP | \
+ PCI_ERR_UNC_ACSV | \
+ PCI_ERR_UNC_INTN | \
+ PCI_ERR_UNC_MCBTLP | \
+ PCI_ERR_UNC_ATOP_EBLOCKED | \
+ PCI_ERR_UNC_TLP_PRF_BLOCKED)
+
+#define PCI_ERR_UNC_SEVERITY_DEFAULT (PCI_ERR_UNC_DLP | \
+ PCI_ERR_UNC_SDN | \
+ PCI_ERR_UNC_FCP | \
+ PCI_ERR_UNC_RX_OVER | \
+ PCI_ERR_UNC_MALF_TLP | \
+ PCI_ERR_UNC_INTN)
+
+#define PCI_ERR_COR_ADV_NONFATAL 0x00002000 /* Advisory Non-Fatal */
+#define PCI_ERR_COR_INTERNAL 0x00004000 /* Corrected Internal */
+#define PCI_ERR_COR_HL_OVERFLOW 0x00008000 /* Header Long Overflow */
+#define PCI_ERR_COR_SUPPORTED (PCI_ERR_COR_RCVR | \
+ PCI_ERR_COR_BAD_TLP | \
+ PCI_ERR_COR_BAD_DLLP | \
+ PCI_ERR_COR_REP_ROLL | \
+ PCI_ERR_COR_REP_TIMER | \
+ PCI_ERR_COR_ADV_NONFATAL | \
+ PCI_ERR_COR_INTERNAL | \
+ PCI_ERR_COR_HL_OVERFLOW)
+#define PCI_ERR_COR_MASK_DEFAULT (PCI_ERR_COR_ADV_NONFATAL | \
+ PCI_ERR_COR_INTERNAL | \
+ PCI_ERR_COR_HL_OVERFLOW)
+
+
+#define PCI_ERR_CAP_FEP_MASK 0x0000001f
+#define PCI_ERR_CAP_MHRC 0x00000200
+#define PCI_ERR_CAP_MHRE 0x00000400
+#define PCI_ERR_CAP_TLP 0x00000800
+
+#define PCI_ERR_TLP_PREFIX_LOG 0x38
+
+/* From 6.2.7 Error Listing and Rules. Table 6-2, 6-3 and 6-4 */
+static enum PCIE_AER_SEVERITY pcie_aer_uncor_default_severity(uint32_t status)
+{
+ switch (status) {
+ case PCI_ERR_UNC_INTN:
+ case PCI_ERR_UNC_DLP:
+ case PCI_ERR_UNC_SDN:
+ case PCI_ERR_UNC_RX_OVER:
+ case PCI_ERR_UNC_FCP:
+ case PCI_ERR_UNC_MALF_TLP:
+ return AER_ERR_FATAL;
+ case PCI_ERR_UNC_POISON_TLP:
+ case PCI_ERR_UNC_ECRC:
+ case PCI_ERR_UNC_UNSUP:
+ case PCI_ERR_UNC_COMP_TIME:
+ case PCI_ERR_UNC_COMP_ABORT:
+ case PCI_ERR_UNC_UNX_COMP:
+ case PCI_ERR_UNC_ACSV:
+ case PCI_ERR_UNC_MCBTLP:
+ case PCI_ERR_UNC_ATOP_EBLOCKED:
+ case PCI_ERR_UNC_TLP_PRF_BLOCKED:
+ return AER_ERR_NONFATAL;
+ default:
+ break;
+ }
+ abort();
+ return AER_ERR_FATAL;
+}
+
+static uint32_t pcie_aer_log_next(uint32_t i, uint32_t max)
+{
+ return (i + 1) % max;
+}
+
+static bool pcie_aer_log_empty_index(uint32_t producer, uint32_t consumer)
+{
+ return producer == consumer;
+}
+
+static bool pcie_aer_log_empty(struct pcie_aer_log *aer_log)
+{
+ return pcie_aer_log_empty_index(aer_log->producer, aer_log->consumer);
+}
+
+static bool pcie_aer_log_full(struct pcie_aer_log *aer_log)
+{
+ return pcie_aer_log_next(aer_log->producer, aer_log->log_max) ==
+ aer_log->consumer;
+}
+
+static uint32_t pcie_aer_log_add(struct pcie_aer_log *aer_log)
+{
+ uint32_t i = aer_log->producer;
+ aer_log->producer = pcie_aer_log_next(aer_log->producer, aer_log->log_max);
+ return i;
+}
+
+static uint32_t pcie_aer_log_del(struct pcie_aer_log *aer_log)
+{
+ uint32_t i = aer_log->consumer;
+ aer_log->consumer = pcie_aer_log_next(aer_log->consumer, aer_log->log_max);
+ return i;
+}
+
+static int pcie_aer_log_add_err(struct pcie_aer_log *aer_log,
+ const struct pcie_aer_err *err)
+{
+ uint32_t i;
+ if (pcie_aer_log_full(aer_log)) {
+ return -1;
+ }
+ i = pcie_aer_log_add(aer_log);
+ memcpy(&aer_log->log[i], err, sizeof(*err));
+ return 0;
+}
+
+static const struct pcie_aer_err*
+pcie_aer_log_del_err(struct pcie_aer_log *aer_log)
+{
+ uint32_t i;
+ assert(!pcie_aer_log_empty(aer_log));
+ i = pcie_aer_log_del(aer_log);
+ return &aer_log->log[i];
+}
+
+static void pcie_aer_log_clear_all_err(struct pcie_aer_log *aer_log)
+{
+ aer_log->producer = 0;
+ aer_log->consumer = 0;
+}
+
+int pcie_aer_init(PCIDevice *dev, uint16_t offset)
+{
+ int pos;
+ PCIExpressDevice *exp;
+
+ pci_set_word(dev->wmask + PCI_COMMAND,
+ pci_get_word(dev->wmask + PCI_COMMAND) | PCI_COMMAND_SERR);
+
+ pos = pcie_append_ext_capability(dev, PCI_EXT_CAP_ID_ERR, PCI_ERR_VER,
+ offset, PCI_ERR_SIZEOF);
+ if (pos < 0) {
+ return pos;
+ }
+ exp = dev->exp;
+ exp->aer_cap = pos;
+ if (dev->aer_log.log_max == PCIE_AER_LOG_MAX_UNSET) {
+ dev->aer_log.log_max = PCIE_AER_LOG_MAX_DEFAULT;
+ }
+ if (dev->aer_log.log_max > PCIE_AER_LOG_MAX_MAX) {
+ dev->aer_log.log_max = PCIE_AER_LOG_MAX_MAX;
+ }
+ dev->aer_log.log =
+ qemu_mallocz(sizeof(dev->aer_log.log[0]) * dev->aer_log.log_max);
+
+ pci_set_long(dev->wmask + pos + PCI_ERR_UNCOR_MASK,
+ PCI_ERR_UNC_SUPPORTED);
+
+ pci_set_long(dev->config + pos + PCI_ERR_UNCOR_SEVER,
+ PCI_ERR_UNC_SEVERITY_DEFAULT);
+ pci_set_long(dev->wmask + pos + PCI_ERR_UNCOR_SEVER,
+ PCI_ERR_UNC_SUPPORTED);
+
+ pci_set_long(dev->config + pos + PCI_ERR_COR_MASK,
+ PCI_ERR_COR_MASK_DEFAULT);
+ pci_set_long(dev->wmask + pos + PCI_ERR_COR_MASK,
+ PCI_ERR_COR_SUPPORTED);
+
+ /* capabilities and control. multiple header logging is supported */
+ if (dev->aer_log.log_max > 0) {
+ pci_set_long(dev->config + pos + PCI_ERR_CAP,
+ PCI_ERR_CAP_ECRC_GENC | PCI_ERR_CAP_ECRC_CHKC |
+ PCI_ERR_CAP_MHRC);
+ pci_set_long(dev->wmask + pos + PCI_ERR_CAP,
+ PCI_ERR_CAP_ECRC_GENE | PCI_ERR_CAP_ECRC_CHKE |
+ PCI_ERR_CAP_MHRE);
+ } else {
+ pci_set_long(dev->config + pos + PCI_ERR_CAP,
+ PCI_ERR_CAP_ECRC_GENC | PCI_ERR_CAP_ECRC_CHKC);
+ pci_set_long(dev->wmask + pos + PCI_ERR_CAP,
+ PCI_ERR_CAP_ECRC_GENE | PCI_ERR_CAP_ECRC_CHKE);
+ }
+
+ switch (pcie_cap_get_type(dev)) {
+ case PCI_EXP_TYPE_ROOT_PORT:
+ /* this case will be set by pcie_aer_root_init() */
+ /* fallthrough */
+ case PCI_EXP_TYPE_DOWNSTREAM:
+ case PCI_EXP_TYPE_UPSTREAM:
+ pci_set_word(dev->wmask + PCI_BRIDGE_CONTROL,
+ pci_get_word(dev->wmask + PCI_BRIDGE_CONTROL) |
+ PCI_BRIDGE_CTL_SERR);
+ exp->aer_errmsg = pcie_aer_errmsg_vbridge;
+ break;
+ default:
+ exp->aer_errmsg = pcie_aer_errmsg_alldev;
+ break;
+ }
+ return pos;
+}
+
+void pcie_aer_exit(PCIDevice *dev)
+{
+ pci_del_capability(dev, PCI_EXT_CAP_ID_ERR, PCI_ERR_SIZEOF);
+ qemu_free(dev->aer_log.log);
+}
+
+/* Multiple Header recording isn't implemented. Is it wanted? */
+void pcie_aer_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len)
+{
+ uint32_t pos = dev->exp->aer_cap;
+
+ /* PCI_STATUS_SIG_SYSTEM_ERROR */
+ if (ranges_overlap(addr, len, PCI_STATUS, 2)) {
+ pcie_w1c_word(dev, PCI_STATUS, PCI_STATUS_SIG_SYSTEM_ERROR, addr, val);
+ }
+
+ /* uncorrectable */
+ if (ranges_overlap(addr, len, pos + PCI_ERR_UNCOR_STATUS, 4)) {
+ uint32_t written =
+ pcie_written_val_long(addr, val, pos + PCI_ERR_UNCOR_STATUS) &
+ PCI_ERR_UNC_SUPPORTED;
+ uint32_t uncorsta =
+ pci_get_long(dev->config + pos + PCI_ERR_UNCOR_STATUS);
+ uint32_t errcap = pci_get_long(dev->config + pos + PCI_ERR_CAP);
+ uint32_t first_error = (1 << PCI_ERR_CAP_FEP(errcap));
+
+ if ((uncorsta & first_error) && (written & first_error)) {
+ pcie_aer_clear_error(dev);
+ }
+ if (!(errcap & PCI_ERR_CAP_MHRE)) {
+ /* RW1CS */
+ pcie_w1c_long(dev, pos + PCI_ERR_UNCOR_STATUS,
+ PCI_ERR_UNC_SUPPORTED, addr, val);
+ }
+ }
+
+ /* correctable */
+ if (ranges_overlap(addr, len, pos + PCI_ERR_COR_STATUS, 4)) {
+ /* RW1CS */
+ pcie_w1c_long(dev, pos + PCI_ERR_COR_STATUS, PCI_ERR_COR_SUPPORTED,
+ addr, val);
+ }
+
+ /* capability & control */
+ if (ranges_overlap(addr, len, pos + PCI_ERR_CAP, 4)) {
+ uint32_t err_cap = pci_get_long(dev->config + pos + PCI_ERR_CAP);
+ if (!(err_cap & PCI_ERR_CAP_MHRE)) {
+ pcie_aer_log_clear_all_err(&dev->aer_log);
+ }
+ }
+}
+
+#define PCI_SEC_STATUS_RCV_SYSTEM_ERROR 0x4000
+
+void pcie_aer_write_config_vbridge(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len)
+{
+ /* PCI_SEC_STATUS_RCV_SYSTEM_ERROR */
+ if (ranges_overlap(addr, len, PCI_STATUS, 2)) {
+ pcie_w1c_word(dev, PCI_SEC_STATUS, PCI_SEC_STATUS_RCV_SYSTEM_ERROR,
+ addr, val);
+ }
+}
+
+static inline void pcie_aer_errmsg(PCIDevice *dev,
+ const struct pcie_aer_err_msg *msg)
+{
+ assert(dev->exp);
+ assert(dev->exp->aer_errmsg);
+ dev->exp->aer_errmsg(dev, msg);
+}
+
+static AER_ERR_MSG_RESULT
+pcie_aer_errmsg_alldev(PCIDevice *dev, const struct pcie_aer_err_msg *msg)
+{
+ uint16_t cmd = pci_get_word(dev->config + PCI_COMMAND);
+ bool transmit1 =
+ pcie_aer_err_msg_is_uncor(msg) && (cmd & PCI_COMMAND_SERR);
+ uint32_t pos = pci_pcie_cap(dev);
+ uint32_t devctl = pci_get_word(dev->config + pos + PCI_EXP_DEVCTL);
+ bool transmit2 = msg->severity & devctl;
+ PCIDevice *parent_port;
+
+ if (transmit1) {
+ if (pcie_aer_err_msg_is_uncor(msg)) {
+ /* Signaled System Error */
+ uint8_t *status = dev->config + PCI_STATUS;
+ pci_set_word(status,
+ pci_get_word(status) | PCI_STATUS_SIG_SYSTEM_ERROR);
+ }
+ }
+
+ if (!(transmit1 || transmit2)) {
+ return AER_ERR_MSG_MASKED;
+ }
+
+ /* send up error message */
+ if (pci_is_express(dev) &&
+ pcie_cap_get_type(dev) == PCI_EXP_TYPE_ROOT_PORT) {
+ /* Root port notify system itself,
+ or send the error message to root complex event collector. */
+ /*
+ * if root port is associated to event collector, set
+ * parent_port = root complex event collector
+ * For now root complex event collector isn't supported.
+ */
+ parent_port = NULL;
+ } else {
+ parent_port = pci_bridge_get_device(dev->bus);
+ }
+ if (parent_port) {
+ if (!pci_is_express(parent_port)) {
+ /* What to do? */
+ return AER_ERR_MSG_MASKED;
+ }
+ pcie_aer_errmsg(parent_port, msg);
+ }
+ return AER_ERR_MSG_SENT;
+}
+
+static AER_ERR_MSG_RESULT
+pcie_aer_errmsg_vbridge(PCIDevice *dev, const struct pcie_aer_err_msg *msg)
+{
+ uint16_t bridge_control = pci_get_word(dev->config + PCI_BRIDGE_CONTROL);
+
+ if (pcie_aer_err_msg_is_uncor(msg)) {
+ /* Received System Error */
+ uint8_t *sec_status = dev->config + PCI_SEC_STATUS;
+ pci_set_word(sec_status,
+ pci_get_word(sec_status) |
+ PCI_SEC_STATUS_RCV_SYSTEM_ERROR);
+ }
+
+ if (!(bridge_control & PCI_BRIDGE_CTL_SERR)) {
+ return AER_ERR_MSG_MASKED;
+ }
+ return pcie_aer_errmsg_alldev(dev, msg);
+}
+
+static AER_ERR_MSG_RESULT
+pcie_aer_errmsg_root_port(PCIDevice *dev, const struct pcie_aer_err_msg *msg)
+{
+ AER_ERR_MSG_RESULT ret;
+ uint16_t cmd;
+ uint8_t *aer_cap;
+ uint32_t root_cmd;
+ uint32_t root_sta;
+ bool trigger;
+
+ ret = pcie_aer_errmsg_vbridge(dev, msg);
+ if (ret != AER_ERR_MSG_SENT) {
+ return ret;
+ }
+
+ ret = AER_ERR_MSG_MASKED;
+ cmd = pci_get_word(dev->config + PCI_COMMAND);
+ aer_cap = dev->config + pcie_aer_cap(dev);
+ root_cmd = pci_get_long(aer_cap + PCI_ERR_ROOT_COMMAND);
+ root_sta = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
+ trigger = false;
+
+ if (cmd & PCI_COMMAND_SERR) {
+ /* System Error. Platform Specific */
+ /* ret = AER_ERR_MSG_SENT; */
+ }
+
+ /* Errro Message Received: Root Error Status register */
+ switch (msg->severity) {
+ case AER_ERR_COR:
+ if (root_sta & PCI_ERR_ROOT_COR_RCV) {
+ root_sta |= PCI_ERR_ROOT_MULTI_COR_RCV;
+ } else {
+ if (root_cmd & PCI_ERR_ROOT_CMD_COR_EN) {
+ trigger = true;
+ }
+ pci_set_word(aer_cap + PCI_ERR_ROOT_COR_SRC, msg->source_id);
+ }
+ root_sta |= PCI_ERR_ROOT_COR_RCV;
+ break;
+ case AER_ERR_NONFATAL:
+ if (!(root_sta & PCI_ERR_ROOT_NONFATAL_RCV) &&
+ root_cmd & PCI_ERR_ROOT_CMD_NONFATAL_EN) {
+ trigger = true;
+ }
+ root_sta |= PCI_ERR_ROOT_NONFATAL_RCV;
+ break;
+ case AER_ERR_FATAL:
+ if (!(root_sta & PCI_ERR_ROOT_FATAL_RCV) &&
+ root_cmd & PCI_ERR_ROOT_CMD_FATAL_EN) {
+ trigger = true;
+ }
+ if (!(root_sta & PCI_ERR_ROOT_UNCOR_RCV)) {
+ root_sta |= PCI_ERR_ROOT_FIRST_FATAL;
+ }
+ root_sta |= PCI_ERR_ROOT_FATAL_RCV;
+ break;
+ }
+ if (pcie_aer_err_msg_is_uncor(msg)) {
+ if (root_sta & PCI_ERR_ROOT_UNCOR_RCV) {
+ root_sta |= PCI_ERR_ROOT_MULTI_UNCOR_RCV;
+ } else {
+ pci_set_word(aer_cap + PCI_ERR_ROOT_SRC, msg->source_id);
+ }
+ root_sta |= PCI_ERR_ROOT_UNCOR_RCV;
+ }
+ pci_set_long(aer_cap + PCI_ERR_ROOT_STATUS, root_sta);
+
+ if (root_cmd & msg->severity) {
+ /* Error Interrupt(INTx or MSI) */
+ pcie_aer_root_notify(dev, trigger, 1);
+ ret = AER_ERR_MSG_SENT;
+ }
+ return ret;
+}
+
+static void pcie_aer_update_log(PCIDevice *dev, const struct pcie_aer_err *err)
+{
+ uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
+ uint8_t first_bit = ffsl(err->status) - 1;
+ uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
+ int i;
+ uint32_t dw;
+
+ errcap &= ~(PCI_ERR_CAP_FEP_MASK | PCI_ERR_CAP_TLP);
+ errcap |= PCI_ERR_CAP_FEP(first_bit);
+
+ if (err->flags & PCIE_AER_ERR_HEADER_VALID) {
+ for (i = 0; i < ARRAY_SIZE(err->header); ++i) {
+ /* 7.10.8 Header Log Register */
+ cpu_to_be32wu(&dw, err->header[i]);
+ memcpy(aer_cap + PCI_ERR_HEADER_LOG + sizeof(err->header[0]) * i,
+ &dw, sizeof(dw));
+ }
+ } else {
+ assert(!(err->flags & PCIE_AER_ERR_TLP_PRESENT));
+ memset(aer_cap + PCI_ERR_HEADER_LOG, 0, sizeof(err->header));
+ }
+
+ if ((err->flags & PCIE_AER_ERR_TLP_PRESENT) &&
+ (pci_get_long(dev->config + pci_pcie_cap(dev) + PCI_EXP_DEVCTL2) &
+ PCI_EXP_DEVCAP2_EETLPP)) {
+ for (i = 0; i < ARRAY_SIZE(err->prefix); ++i) {
+ /* 7.10.12 tlp prefix log register */
+ cpu_to_be32wu(&dw, err->prefix[i]);
+ memcpy(aer_cap + PCI_ERR_TLP_PREFIX_LOG +
+ sizeof(err->prefix[0]) * i, &dw, sizeof(dw));
+ }
+ errcap |= PCI_ERR_CAP_TLP;
+ } else {
+ memset(aer_cap + PCI_ERR_TLP_PREFIX_LOG, 0, sizeof(err->prefix));
+ }
+ pci_set_long(aer_cap + PCI_ERR_CAP, errcap);
+}
+
+static void pcie_aer_clear_log(PCIDevice *dev)
+{
+ struct pcie_aer_err *err;
+ uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
+ uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
+
+ errcap &= ~(PCI_ERR_CAP_FEP_MASK | PCI_ERR_CAP_TLP);
+ pci_set_long(aer_cap + PCI_ERR_CAP, errcap);
+
+ memset(aer_cap + PCI_ERR_HEADER_LOG, 0, sizeof(err->header));
+ memset(aer_cap + PCI_ERR_TLP_PREFIX_LOG, 0, sizeof(err->prefix));
+}
+
+static int pcie_aer_record_error(PCIDevice *dev,
+ const struct pcie_aer_err *err)
+{
+ uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
+ uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
+ int fep = PCI_ERR_CAP_FEP(errcap);
+
+ if (errcap & PCI_ERR_CAP_MHRE &&
+ (pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) & (1ULL << fep))) {
+ /* Not first error. queue error */
+ if (pcie_aer_log_add_err(&dev->aer_log, err) < 0) {
+ /* overflow */
+ return -1;
+ }
+ return 0;
+ }
+
+ pcie_aer_update_log(dev, err);
+ return 0;
+}
+
+static void pcie_aer_clear_error(PCIDevice *dev)
+{
+ uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
+ uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
+ uint32_t old_err = (1UL << PCI_ERR_CAP_FEP(errcap));
+ struct pcie_aer_log *aer_log = &dev->aer_log;
+ const struct pcie_aer_err *err;
+ uint32_t consumer;
+
+ if (!(errcap & PCI_ERR_CAP_MHRE) || pcie_aer_log_empty(aer_log)) {
+ pcie_aer_clear_log(dev);
+ pci_set_long(aer_cap + PCI_ERR_UNCOR_STATUS,
+ pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) & ~old_err);
+ return;
+ }
+
+ /* if no same error is queued, clear bit in uncorrectable error status */
+ for (consumer = dev->aer_log.consumer;
+ !pcie_aer_log_empty_index(dev->aer_log.producer, consumer);
+ consumer = pcie_aer_log_next(consumer, dev->aer_log.log_max)) {
+ if (dev->aer_log.log[consumer].status & old_err) {
+ old_err = 0;
+ break;
+ }
+ }
+ if (old_err) {
+ pci_set_long(aer_cap + PCI_ERR_UNCOR_STATUS,
+ pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) & ~old_err);
+ }
+
+ err = pcie_aer_log_del_err(aer_log);
+ pcie_aer_update_log(dev, err);
+}
+
+/*
+ * non-Function specific error must be recorded in all functions.
+ * It is the responsibility of the caller of this function.
+ * It is also caller's responsiblity to determine which function should
+ * report the rerror.
+ *
+ * 6.2.4 Error Logging
+ * 6.2.5 Sqeucne of Device Error Signaling and Logging Operations
+ * table 6-2: Flowchard Showing Sequence of Device Error Signaling and Logging
+ * Operations
+ *
+ * Although this implementation can be shortened/optimized, this is kept
+ * parallel to table 6-2.
+ */
+void pcie_aer_inject_error(PCIDevice *dev, const struct pcie_aer_err *err)
+{
+ uint8_t *exp_cap;
+ uint8_t *aer_cap = NULL;
+ uint32_t devctl = 0;
+ uint32_t devsta = 0;
+ uint32_t status = err->status;
+ uint32_t mask;
+ bool is_unsupported_request =
+ (!(err->flags & PCIE_AER_ERR_IS_CORRECTABLE) &&
+ err->status == PCI_ERR_UNC_UNSUP);
+ bool is_advisory_nonfatal = false; /* for advisory non-fatal error */
+ uint32_t uncor_status = 0; /* for advisory non-fatal error */
+ struct pcie_aer_err_msg msg;
+ int is_header_log_overflowed = 0;
+
+ if (!pci_is_express(dev)) {
+ /* What to do? */
+ return;
+ }
+
+ if (err->flags & PCIE_AER_ERR_IS_CORRECTABLE) {
+ status &= PCI_ERR_COR_SUPPORTED;
+ } else {
+ status &= PCI_ERR_UNC_SUPPORTED;
+ }
+ if (!status || status & (status - 1)) {
+ /* invalid status bit. one and only one bit must be set */
+ return;
+ }
+
+ exp_cap = dev->config + pci_pcie_cap(dev);
+ if (dev->exp->aer_cap) {
+ aer_cap = dev->config + pcie_aer_cap(dev);
+ devctl = pci_get_long(exp_cap + PCI_EXP_DEVCTL);
+ devsta = pci_get_long(exp_cap + PCI_EXP_DEVSTA);
+ }
+ if (err->flags & PCIE_AER_ERR_IS_CORRECTABLE) {
+ correctable_error:
+ devsta |= PCI_EXP_DEVSTA_CED;
+ if (is_unsupported_request) {
+ devsta |= PCI_EXP_DEVSTA_URD;
+ }
+ pci_set_word(exp_cap + PCI_EXP_DEVSTA, devsta);
+
+ if (aer_cap) {
+ pci_set_long(aer_cap + PCI_ERR_COR_STATUS,
+ pci_get_long(aer_cap + PCI_ERR_COR_STATUS) | status);
+ mask = pci_get_long(aer_cap + PCI_ERR_COR_MASK);
+ if (mask & status) {
+ return;
+ }
+ if (is_advisory_nonfatal) {
+ uint32_t uncor_mask =
+ pci_get_long(aer_cap + PCI_ERR_UNCOR_MASK);
+ if (!(uncor_mask & uncor_status)) {
+ is_header_log_overflowed = pcie_aer_record_error(dev, err);
+ }
+ pci_set_long(aer_cap + PCI_ERR_UNCOR_STATUS,
+ pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) |
+ uncor_status);
+ }
+ }
+
+ if (is_unsupported_request && !(devctl & PCI_EXP_DEVCTL_URRE)) {
+ return;
+ }
+ if (!(devctl & PCI_EXP_DEVCTL_CERE)) {
+ return;
+ }
+ msg.severity = AER_ERR_COR;
+ } else {
+ bool is_fatal =
+ (pcie_aer_uncor_default_severity(status) == AER_ERR_FATAL);
+ uint16_t cmd;
+
+ if (aer_cap) {
+ is_fatal = status & pci_get_long(aer_cap + PCI_ERR_UNCOR_SEVER);
+ }
+ if (!is_fatal && (err->flags & PCIE_AER_ERR_MAYBE_ADVISORY)) {
+ is_advisory_nonfatal = true;
+ uncor_status = status;
+ status = PCI_ERR_COR_ADV_NONFATAL;
+ goto correctable_error;
+ }
+ if (is_fatal) {
+ devsta |= PCI_EXP_DEVSTA_FED;
+ } else {
+ devsta |= PCI_EXP_DEVSTA_NFED;
+ }
+ if (is_unsupported_request) {
+ devsta |= PCI_EXP_DEVSTA_URD;
+ }
+ pci_set_long(exp_cap + PCI_EXP_DEVSTA, devsta);
+
+ if (aer_cap) {
+ mask = pci_get_long(aer_cap + PCI_ERR_UNCOR_MASK);
+ if (mask & status) {
+ pci_set_long(aer_cap + PCI_ERR_UNCOR_STATUS,
+ pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) |
+ status);
+ return;
+ }
+
+ is_header_log_overflowed = pcie_aer_record_error(dev, err);
+ pci_set_long(aer_cap + PCI_ERR_UNCOR_STATUS,
+ pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) |
+ status);
+ }
+
+ cmd = pci_get_word(dev->config + PCI_COMMAND);
+ if (is_unsupported_request &&
+ !(devctl & PCI_EXP_DEVCTL_URRE) && !(cmd & PCI_COMMAND_SERR)) {
+ return;
+ }
+ if (is_fatal) {
+ if (!((cmd & PCI_COMMAND_SERR) ||
+ (devctl & PCI_EXP_DEVCTL_FERE))) {
+ return;
+ }
+ msg.severity = AER_ERR_FATAL;
+ } else {
+ if (!((cmd & PCI_COMMAND_SERR) ||
+ (devctl & PCI_EXP_DEVCTL_NFERE))) {
+ return;
+ }
+ msg.severity = AER_ERR_NONFATAL;
+ }
+ }
+
+ /* send up error message */
+ msg.source_id = err->source_id;
+ pcie_aer_errmsg(dev, &msg);
+
+ if (is_header_log_overflowed) {
+ struct pcie_aer_err header_log_overflow = {
+ .status = PCI_ERR_COR_HL_OVERFLOW,
+ .flags = PCIE_AER_ERR_IS_CORRECTABLE,
+ .header = {0, 0, 0, 0},
+ .prefix = {0, 0, 0, 0},
+ };
+ pcie_aer_inject_error(dev, &header_log_overflow);
+ }
+}
+
+/* aer root error command/status */
+#define PCI_ERR_ROOT_CMD_EN_MASK (PCI_ERR_ROOT_CMD_COR_EN | \
+ PCI_ERR_ROOT_CMD_NONFATAL_EN | \
+ PCI_ERR_ROOT_CMD_FATAL_EN)
+
+#define PCI_ERR_ROOT_IRQ_SHIFT 26
+#define PCI_ERR_ROOT_IRQ 0xf8000000
+#define PCI_ERR_ROOT_STATUS_REPORT_MASK (PCI_ERR_ROOT_COR_RCV | \
+ PCI_ERR_ROOT_MULTI_COR_RCV | \
+ PCI_ERR_ROOT_UNCOR_RCV | \
+ PCI_ERR_ROOT_MULTI_UNCOR_RCV | \
+ PCI_ERR_ROOT_FIRST_FATAL | \
+ PCI_ERR_ROOT_NONFATAL_RCV | \
+ PCI_ERR_ROOT_FATAL_RCV)
+
+void pcie_aer_root_set_vector(PCIDevice *dev, uint8_t vector)
+{
+ uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
+ uint32_t root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
+ root_status &= ~PCI_ERR_ROOT_IRQ;
+ root_status |=
+ (((uint32_t)vector) << PCI_ERR_ROOT_IRQ_SHIFT) & PCI_ERR_ROOT_IRQ;
+ pci_set_long(aer_cap + PCI_ERR_ROOT_STATUS, root_status);
+}
+
+static uint8_t pcie_aer_root_get_vector(PCIDevice *dev)
+{
+ uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
+ uint32_t root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
+ return (root_status & PCI_ERR_ROOT_IRQ) >> PCI_ERR_ROOT_IRQ_SHIFT;
+}
+
+static void pcie_aer_root_notify(PCIDevice *dev, bool trigger, int level)
+{
+ pcie_notify(dev, pcie_aer_root_get_vector(dev), trigger, level);
+}
+
+void pcie_aer_root_init(PCIDevice *dev)
+{
+ uint16_t pos = pcie_aer_cap(dev);
+
+ pci_set_long(dev->wmask + pos + PCI_ERR_ROOT_COMMAND,
+ PCI_ERR_ROOT_CMD_EN_MASK);
+ dev->exp->aer_errmsg = pcie_aer_errmsg_root_port;
+}
+
+void pcie_aer_root_reset(PCIDevice *dev)
+{
+ uint8_t* aer_cap = dev->config + pcie_aer_cap(dev);
+
+ pci_set_long(aer_cap + PCI_ERR_ROOT_COMMAND, 0);
+
+ /*
+ * Advanced Error Interrupt Message Number in Root Error Status Register
+ * must be updated by chip dependent code.
+ */
+}
+
+static bool pcie_aer_root_does_trigger(uint32_t cmd, uint32_t sta)
+{
+ return
+ ((cmd & PCI_ERR_ROOT_CMD_COR_EN) && (sta & PCI_ERR_ROOT_COR_RCV)) ||
+ ((cmd & PCI_ERR_ROOT_CMD_NONFATAL_EN) &&
+ (sta & PCI_ERR_ROOT_NONFATAL_RCV)) ||
+ ((cmd & PCI_ERR_ROOT_CMD_FATAL_EN) && (sta & PCI_ERR_ROOT_FATAL_RCV));
+}
+
+void pcie_aer_root_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len,
+ uint32_t root_cmd_prev)
+{
+ uint16_t pos = pcie_aer_cap(dev);
+ uint8_t *aer_cap = dev->config + pos;
+ uint32_t root_status;
+
+ if (ranges_overlap(addr, len, pos + PCI_ERR_ROOT_STATUS, 4)) {
+ /* RW1CS */
+ pcie_w1c_long(dev, pos + PCI_ERR_ROOT_STATUS,
+ PCI_ERR_ROOT_STATUS_REPORT_MASK, addr, val);
+ }
+
+ /* root command */
+ if (ranges_overlap(addr, len, pos + PCI_ERR_ROOT_COMMAND, 4)) {
+ uint32_t root_cmd = pci_get_long(aer_cap + PCI_ERR_ROOT_COMMAND);
+ if (root_cmd & PCI_ERR_ROOT_CMD_EN_MASK) {
+ bool trigger = false;
+ int level = 0;
+ uint32_t root_cmd_set = (root_cmd_prev ^ root_cmd) & root_cmd;
+
+ /* 0 -> 1 */
+ root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
+ if (pcie_aer_root_does_trigger(root_cmd_set, root_status)) {
+ trigger = true;
+ }
+ if (pcie_aer_root_does_trigger(root_cmd, root_status)) {
+ level = 1;
+ }
+ pcie_aer_root_notify(dev, trigger, level);
+ }
+ }
+}
+
+static const VMStateDescription vmstate_pcie_aer_err = {
+ .name = "PCIE_AER_ERROR",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(status, struct pcie_aer_err),
+ VMSTATE_UINT16(source_id, struct pcie_aer_err),
+ VMSTATE_UINT16(flags, struct pcie_aer_err),
+ VMSTATE_UINT32_ARRAY(header, struct pcie_aer_err, 4),
+ VMSTATE_UINT32_ARRAY(prefix, struct pcie_aer_err, 4),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define VMSTATE_PCIE_AER_ERRS(_field, _state, _field_num, _vmsd, _type) { \
+ .name = (stringify(_field)), \
+ .version_id = 0, \
+ .num_offset = vmstate_offset_value(_state, _field_num, uint16_t), \
+ .size = sizeof(_type), \
+ .vmsd = &(_vmsd), \
+ .flags = VMS_POINTER | VMS_VARRAY_UINT16 | VMS_STRUCT, \
+ .offset = vmstate_offset_pointer(_state, _field, _type), \
+}
+
+const VMStateDescription vmstate_pcie_aer_log = {
+ .name = "PCIE_AER_ERROR_LOG",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(producer, struct pcie_aer_log),
+ VMSTATE_UINT32(consumer, struct pcie_aer_log),
+ VMSTATE_UINT16(log_max, struct pcie_aer_log),
+ VMSTATE_PCIE_AER_ERRS(log, struct pcie_aer_log, log_max,
+ vmstate_pcie_aer_err, struct pcie_aer_err),
+ VMSTATE_END_OF_LIST()
+ }
+};
diff --git a/hw/pcie.h b/hw/pcie.h
new file mode 100644
index 0000000..07f42c6
--- /dev/null
+++ b/hw/pcie.h
@@ -0,0 +1,186 @@
+/*
+ * pcie.h
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef QEMU_PCIE_H
+#define QEMU_PCIE_H
+
+#include "hw.h"
+
+enum PCIExpressIndicator {
+ /* for attention and power indicator */
+ PCI_EXP_HP_IND_RESERVED = 0b00,
+ PCI_EXP_HP_IND_ON = 0b01,
+ PCI_EXP_HP_IND_BLINK = 0b10,
+ PCI_EXP_HP_IND_OFF = 0b11,
+};
+
+enum PCIExpressHotPlugEvent {
+ /* the bits match the bits in Slot Control/Status registers.
+ * PCI_EXP_HP_EV_xxx = PCI_EXP_SLTCTL_xxxE = PCI_EXP_SLTSTA_xxx
+ */
+ PCI_EXP_HP_EV_ABP = 0b00001, /* attention button preseed */
+ PCI_EXP_HP_EV_PDC = 0b01000, /* presence detect changed */
+ PCI_EXP_HP_EV_CCI = 0b10000, /* command completed */
+
+ PCI_EXP_HP_EV_SUPPORTED = 0b11001, /* supported event mask */
+ /* events not listed aren't supported */
+};
+
+typedef void (*pcie_flr_fn)(PCIDevice *dev);
+
+struct pcie_aer_err_msg;
+enum AER_ERR_MSG_RESULT {
+ AER_ERR_MSG_MASKED,
+ AER_ERR_MSG_SENT,
+};
+typedef enum AER_ERR_MSG_RESULT AER_ERR_MSG_RESULT;
+typedef AER_ERR_MSG_RESULT (*pcie_aer_errmsg_fn)(PCIDevice *dev, const struct pcie_aer_err_msg *msg);
+
+struct PCIExpressDevice {
+ /* Offset of express capability in config space */
+ uint8_t exp_cap;
+
+ /* FLR */
+ pcie_flr_fn flr;
+
+ /* AER */
+ uint16_t aer_cap;
+ pcie_aer_errmsg_fn aer_errmsg;
+};
+
+struct pcie_aer_log {
+ uint32_t producer;
+ uint32_t consumer;
+
+#define PCIE_AER_LOG_MAX_DEFAULT 8
+#define PCIE_AER_LOG_MAX_MAX 128 /* what is appropriate? */
+#define PCIE_AER_LOG_MAX_UNSET (~(uint16_t)0)
+ uint16_t log_max;
+
+ struct pcie_aer_err *log;
+};
+
+extern const VMStateDescription vmstate_pcie_aer_log;
+
+/* PCI express capability helper functions */
+int pci_pcie_cap_init(PCIDevice *dev,
+ uint8_t offset, uint8_t type, uint8_t port);
+int pci_pcie_cap_exit(PCIDevice *dev);
+uint8_t pcie_cap_get_type(const PCIDevice *dev);
+void pcie_cap_flags_set_vector(PCIDevice *dev, uint8_t vector);
+uint8_t pcie_cap_flags_get_vector(PCIDevice *dev);
+
+void pcie_cap_deverr_init(PCIDevice *dev);
+void pcie_cap_deverr_reset(PCIDevice *dev);
+void pcie_cap_deverr_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len);
+
+void pcie_cap_slot_init(PCIDevice *dev, uint16_t slot);
+void pcie_cap_slot_reset(PCIDevice *dev);
+void pcie_cap_slot_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len,
+ uint16_t sltctl_prev);
+void pcie_cap_slot_push_attention_button(PCIDevice *dev);
+
+void pcie_cap_root_init(PCIDevice *dev);
+void pcie_cap_root_reset(PCIDevice *dev);
+
+void pcie_cap_flr_init(PCIDevice *dev, pcie_flr_fn flr);
+void pcie_cap_flr_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len);
+
+void pcie_cap_ari_init(PCIDevice *dev);
+void pcie_cap_ari_reset(PCIDevice *dev);
+bool pcie_cap_is_ari_enabled(const PCIDevice *dev);
+
+/* PCI express extended capability helper functions */
+uint16_t pcie_find_ext_capability(PCIDevice *dev, uint16_t cap_id);
+int pcie_add_ext_capability(PCIDevice *dev,
+ uint16_t cap_id, uint8_t cap_ver, uint16_t size);
+int pcie_append_ext_capability(PCIDevice *dev,
+ uint16_t cap_id, uint8_t cap_ver,
+ uint16_t offset, uint16_t size);
+void pcie_del_ext_capability(PCIDevice *dev, uint16_t cap_id, uint16_t size);
+void pcie_reserve_ext_capability(PCIDevice *dev,
+ uint16_t offset, uint16_t size);
+
+int pcie_ari_init(PCIDevice *dev, uint16_t offset, uint16_t nextfn);
+
+/* PCI express extended capabilities */
+
+/* AER */
+/* aer error severity */
+enum PCIE_AER_SEVERITY {
+ /* those value are same as
+ * Root error command register in aer extended cap and
+ * root control register in pci express cap.
+ */
+ AER_ERR_COR = 0x1,
+ AER_ERR_NONFATAL = 0x2,
+ AER_ERR_FATAL = 0x4,
+};
+
+/* aer error message: error signaling message has only error sevirity and
+ source id. See 2.2.8.3 error signaling messages */
+struct pcie_aer_err_msg {
+ enum PCIE_AER_SEVERITY severity;
+ uint16_t source_id; /* bdf */
+};
+
+static inline bool
+pcie_aer_err_msg_is_uncor(const struct pcie_aer_err_msg *msg)
+{
+ return msg->severity == AER_ERR_NONFATAL || msg->severity == AER_ERR_FATAL;
+}
+
+/* error */
+struct pcie_aer_err {
+ uint32_t status; /* error status bits */
+ uint16_t source_id; /* bdf */
+
+#define PCIE_AER_ERR_IS_CORRECTABLE 0x1 /* correctable/uncorrectable */
+#define PCIE_AER_ERR_MAYBE_ADVISORY 0x2 /* maybe advisory non-fatal */
+#define PCIE_AER_ERR_HEADER_VALID 0x4 /* TLP header is logged */
+#define PCIE_AER_ERR_TLP_PRESENT 0x8 /* TLP Prefix is logged */
+ uint16_t flags;
+
+ uint32_t header[4]; /* TLP header */
+ uint32_t prefix[4]; /* TLP header prefix */
+};
+
+int pcie_aer_init(PCIDevice *dev, uint16_t offset);
+void pcie_aer_exit(PCIDevice *dev);
+void pcie_aer_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len);
+void pcie_aer_write_config_vbridge(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len);
+
+/* aer root port */
+void pcie_aer_root_set_vector(PCIDevice *dev, uint8_t vector);
+void pcie_aer_root_init(PCIDevice *dev);
+void pcie_aer_root_reset(PCIDevice *dev);
+void pcie_aer_root_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int len,
+ uint32_t root_cmd_prev);
+
+/* error injection */
+void pcie_aer_inject_error(PCIDevice *dev, const struct pcie_aer_err *err);
+
+#endif /* QEMU_PCIE_H */
diff --git a/qemu-common.h b/qemu-common.h
index d735235..6d9ee26 100644
--- a/qemu-common.h
+++ b/qemu-common.h
@@ -219,6 +219,7 @@ typedef struct PCIHostState PCIHostState;
typedef struct PCIExpressHost PCIExpressHost;
typedef struct PCIBus PCIBus;
typedef struct PCIDevice PCIDevice;
+typedef struct PCIExpressDevice PCIExpressDevice;
typedef struct PCIBridge PCIBridge;
typedef struct SerialState SerialState;
typedef struct IRQState *qemu_irq;
--
1.7.1.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH v2 3/9] pcie port: define struct PCIEPort/PCIESlot and helper functions
2010-09-08 7:39 [Qemu-devel] [PATCH v2 0/9] pcie port switch emulators Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 1/9] msi: implemented msi Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 2/9] pcie: helper functions for pcie extended capability Isaku Yamahata
@ 2010-09-08 7:39 ` Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 4/9] pcie root port: implement pcie root port Isaku Yamahata
` (5 subsequent siblings)
8 siblings, 0 replies; 25+ messages in thread
From: Isaku Yamahata @ 2010-09-08 7:39 UTC (permalink / raw)
To: qemu-devel; +Cc: skandasa, etmartin, wexu2, mst, adhyas, yamahata
define struct PCIEPort which represents common part
of pci express port.(root, upstream and downstream.)
add a helper function for pcie port which can be used commonly by
root/upstream/downstream port.
define struct PCIESlot which represents common part of
pcie slot.(root and downstream.) and helper functions for it.
helper functions for chassis, slot -> PCIESlot conversion.
Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
---
Makefile.objs | 2 +-
hw/pcie_port.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
hw/pcie_port.h | 51 +++++++++++++++++++++++++++
qemu-common.h | 2 +
4 files changed, 160 insertions(+), 1 deletions(-)
create mode 100644 hw/pcie_port.c
create mode 100644 hw/pcie_port.h
diff --git a/Makefile.objs b/Makefile.objs
index eeb5134..c73d12b 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -186,7 +186,7 @@ hw-obj-$(CONFIG_PIIX4) += piix4.o
# PCI watchdog devices
hw-obj-y += wdt_i6300esb.o
-hw-obj-y += pcie.o
+hw-obj-y += pcie.o pcie_port.o
hw-obj-y += msix.o msi.o
# PCI network cards
diff --git a/hw/pcie_port.c b/hw/pcie_port.c
new file mode 100644
index 0000000..d2b1f38
--- /dev/null
+++ b/hw/pcie_port.c
@@ -0,0 +1,106 @@
+/*
+ * pcie_port.c
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pcie_port.h"
+
+void pcie_port_init_reg(PCIDevice *d)
+{
+ /* Unlike pci bridge,
+ 66MHz and fast back to back don't apply to pci express port. */
+ pci_set_word(d->config + PCI_STATUS, 0);
+ pci_set_word(d->config + PCI_SEC_STATUS, 0);
+}
+
+/**************************************************************************
+ * (chassis number, pcie physical slot number) -> pcie slot conversion
+ */
+struct PCIEChassis {
+ uint8_t number;
+
+ QLIST_HEAD(, PCIESlot) slots;
+ QLIST_ENTRY(PCIEChassis) next;
+};
+
+QLIST_HEAD(, PCIEChassis) chassis = QLIST_HEAD_INITIALIZER(chassis);
+
+static struct PCIEChassis *pcie_chassis_find(uint8_t chassis_number)
+{
+ struct PCIEChassis *c;
+ QLIST_FOREACH(c, &chassis, next) {
+ if (c->number == chassis_number) {
+ break;
+ }
+ }
+ return c;
+}
+
+void pcie_chassis_create(uint8_t chassis_number)
+{
+ struct PCIEChassis *c;
+ c = pcie_chassis_find(chassis_number);
+ if (c) {
+ return;
+ }
+ c = qemu_mallocz(sizeof(*c));
+ c->number = chassis_number;
+ QLIST_INIT(&c->slots);
+ QLIST_INSERT_HEAD(&chassis, c, next);
+}
+
+static PCIESlot *pcie_chassis_find_slot_with_chassis(struct PCIEChassis *c,
+ uint8_t slot)
+{
+ PCIESlot *s;
+ QLIST_FOREACH(s, &c->slots, next) {
+ if (s->slot == slot) {
+ break;
+ }
+ }
+ return s;
+}
+
+PCIESlot *pcie_chassis_find_slot(uint8_t chassis_number, uint16_t slot)
+{
+ struct PCIEChassis *c;
+ c = pcie_chassis_find(chassis_number);
+ if (!c) {
+ return NULL;
+ }
+ return pcie_chassis_find_slot_with_chassis(c, slot);
+}
+
+int pcie_chassis_add_slot(struct PCIESlot *slot)
+{
+ struct PCIEChassis *c;
+ c = pcie_chassis_find(slot->chassis);
+ if (!c) {
+ return -ENODEV;
+ }
+ if (pcie_chassis_find_slot_with_chassis(c, slot->slot)) {
+ return -EBUSY;
+ }
+ QLIST_INSERT_HEAD(&c->slots, slot, next);
+ return 0;
+}
+
+void pcie_chassis_del_slot(PCIESlot *s)
+{
+ QLIST_REMOVE(s, next);
+}
diff --git a/hw/pcie_port.h b/hw/pcie_port.h
new file mode 100644
index 0000000..3709583
--- /dev/null
+++ b/hw/pcie_port.h
@@ -0,0 +1,51 @@
+/*
+ * pcie_port.h
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef QEMU_PCIE_PORT_H
+#define QEMU_PCIE_PORT_H
+
+#include "pci_bridge.h"
+#include "pci_internals.h"
+
+struct PCIEPort {
+ PCIBridge br;
+
+ /* pci express switch port */
+ uint8_t port;
+};
+
+void pcie_port_init_reg(PCIDevice *d);
+
+struct PCIESlot {
+ PCIEPort port;
+
+ /* pci express switch port with slot */
+ uint8_t chassis;
+ uint16_t slot;
+ QLIST_ENTRY(PCIESlot) next;
+};
+
+void pcie_chassis_create(uint8_t chassis_number);
+void pcie_main_chassis_create(void);
+PCIESlot *pcie_chassis_find_slot(uint8_t chassis, uint16_t slot);
+int pcie_chassis_add_slot(struct PCIESlot *slot);
+void pcie_chassis_del_slot(PCIESlot *s);
+
+#endif /* QEMU_PCIE_PORT_H */
diff --git a/qemu-common.h b/qemu-common.h
index 6d9ee26..b97b16e 100644
--- a/qemu-common.h
+++ b/qemu-common.h
@@ -221,6 +221,8 @@ typedef struct PCIBus PCIBus;
typedef struct PCIDevice PCIDevice;
typedef struct PCIExpressDevice PCIExpressDevice;
typedef struct PCIBridge PCIBridge;
+typedef struct PCIEPort PCIEPort;
+typedef struct PCIESlot PCIESlot;
typedef struct SerialState SerialState;
typedef struct IRQState *qemu_irq;
typedef struct PCMCIACardState PCMCIACardState;
--
1.7.1.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH v2 4/9] pcie root port: implement pcie root port.
2010-09-08 7:39 [Qemu-devel] [PATCH v2 0/9] pcie port switch emulators Isaku Yamahata
` (2 preceding siblings ...)
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 3/9] pcie port: define struct PCIEPort/PCIESlot and helper functions Isaku Yamahata
@ 2010-09-08 7:39 ` Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 5/9] pcie upstream port: pci express switch upstream port Isaku Yamahata
` (4 subsequent siblings)
8 siblings, 0 replies; 25+ messages in thread
From: Isaku Yamahata @ 2010-09-08 7:39 UTC (permalink / raw)
To: qemu-devel; +Cc: skandasa, etmartin, wexu2, mst, adhyas, yamahata
pcie root port.
Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
---
Makefile.objs | 2 +-
hw/pcie_root.c | 248 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
hw/pcie_root.h | 32 +++++++
3 files changed, 281 insertions(+), 1 deletions(-)
create mode 100644 hw/pcie_root.c
create mode 100644 hw/pcie_root.h
diff --git a/Makefile.objs b/Makefile.objs
index c73d12b..658c399 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -186,7 +186,7 @@ hw-obj-$(CONFIG_PIIX4) += piix4.o
# PCI watchdog devices
hw-obj-y += wdt_i6300esb.o
-hw-obj-y += pcie.o pcie_port.o
+hw-obj-y += pcie.o pcie_port.o pcie_root.o
hw-obj-y += msix.o msi.o
# PCI network cards
diff --git a/hw/pcie_root.c b/hw/pcie_root.c
new file mode 100644
index 0000000..985187e
--- /dev/null
+++ b/hw/pcie_root.c
@@ -0,0 +1,248 @@
+/*
+ * pcie_root.c
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pci_ids.h"
+#include "msi.h"
+#include "pcie.h"
+#include "pcie_root.h"
+
+/* For now, Intel X58 IOH corporate deice exporess* root port.
+ need to get its own id? */
+#define PCI_DEVICE_ID_IOH_EPORT 0x3420 /* D0:F0 express mode */
+#define PCI_DEVICE_ID_IOH_REV 0x2
+#define IOH_EP_SSVID_OFFSET 0x40
+#define IOH_EP_SSVID_SVID PCI_VENDOR_ID_INTEL
+#define IOH_EP_SSVID_SSID 0
+#define IOH_EP_MSI_OFFSET 0x60
+#define IOH_EP_MSI_SUPPORTED_FLAGS PCI_MSI_FLAGS_MASKBIT
+#define IOH_EP_MSI_NR_VECTOR 2
+#define IOH_EP_EXP_OFFSET 0x90
+#define IOH_EP_AER_OFFSET 0x100
+
+#define PCIE_ROOT_VID PCI_VENDOR_ID_INTEL
+#define PCIE_ROOT_DID PCI_DEVICE_ID_IOH_EPORT
+#define PCIE_ROOT_REV PCI_DEVICE_ID_IOH_REV
+#define PCIE_ROOT_SSVID_OFFSET IOH_EP_SSVID_OFFSET
+#define PCIE_ROOT_SVID IOH_EP_SSVID_SVID
+#define PCIE_ROOT_SSID IOH_EP_SSVID_SSID
+#define PCIE_ROOT_MSI_SUPPORTED_FLAGS IOH_EP_MSI_SUPPORTED_FLAGS
+#define PCIE_ROOT_MSI_NR_VECTOR IOH_EP_MSI_NR_VECTOR
+#define PCIE_ROOT_MSI_OFFSET IOH_EP_MSI_OFFSET
+#define PCIE_ROOT_EXP_OFFSET IOH_EP_EXP_OFFSET
+#define PCIE_ROOT_AER_OFFSET IOH_EP_AER_OFFSET
+
+/*
+ * If two MSI vector are allocated, Advanced Error Interrupt Message Number
+ * is 1. otherwise 0.
+ * 17.12.5.10 RPERRSTS, 32:27 bit Advanced Error Interrupt Message Number.
+ */
+static uint8_t pcie_root_aer_vector(const PCIDevice *d)
+{
+ switch (msi_nr_vectors_allocated(d)) {
+ case 1:
+ return 0;
+ case 2:
+ return 1;
+ case 4:
+ case 8:
+ case 16:
+ case 32:
+ default:
+ break;
+ }
+ abort();
+ return 0;
+}
+
+static void pcie_root_aer_vector_update(PCIDevice *d)
+{
+ pcie_aer_root_set_vector(d, pcie_root_aer_vector(d));
+}
+
+static void pcie_root_write_config(PCIDevice *d,
+ uint32_t address, uint32_t val, int len)
+{
+ uint16_t sltctl =
+ pci_get_word(d->config + pci_pcie_cap(d) + PCI_EXP_SLTCTL);
+ uint32_t root_cmd =
+ pci_get_long(d->config + pcie_aer_cap(d) + PCI_ERR_ROOT_COMMAND);
+
+ pci_bridge_write_config(d, address, val, len);
+ msi_write_config(d, address, val, len);
+ pcie_root_aer_vector_update(d);
+ pcie_cap_deverr_write_config(d, address, val, len);
+ pcie_cap_slot_write_config(d, address, val, len, sltctl);
+ pcie_aer_write_config_vbridge(d, address, val, len);
+ pcie_aer_write_config(d, address, val, len);
+ pcie_aer_root_write_config(d, address, val, len, root_cmd);
+}
+
+static void pcie_root_reset(DeviceState *qdev)
+{
+ PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev);
+ msi_reset(d);
+ pcie_root_aer_vector_update(d);
+ pcie_cap_root_reset(d);
+ pcie_cap_deverr_reset(d);
+ pcie_cap_slot_reset(d);
+ pcie_aer_root_reset(d);
+ pci_bridge_reset(qdev);
+}
+
+static int pcie_root_initfn(PCIDevice *d)
+{
+ PCIBridge* br = DO_UPCAST(PCIBridge, dev, d);
+ PCIEPort *p = DO_UPCAST(PCIEPort, br, br);
+ PCIESlot *s = DO_UPCAST(PCIESlot, port, p);
+ int rc;
+
+ rc = pci_bridge_initfn(d);
+ if (rc < 0) {
+ return rc;
+ }
+
+ d->config[PCI_REVISION_ID] = PCIE_ROOT_REV;
+ pcie_port_init_reg(d);
+
+ pci_config_set_vendor_id(d->config, PCIE_ROOT_VID);
+ pci_config_set_device_id(d->config, PCIE_ROOT_DID);
+
+ rc = pci_bridge_ssvid_init(d, PCIE_ROOT_SSVID_OFFSET,
+ PCIE_ROOT_SVID, PCIE_ROOT_SSID);
+ if (rc < 0) {
+ return rc;
+ }
+ rc = msi_init(d, PCIE_ROOT_MSI_OFFSET, PCIE_ROOT_MSI_NR_VECTOR,
+ PCIE_ROOT_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_64BIT,
+ PCIE_ROOT_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_MASKBIT);
+ if (rc < 0) {
+ return rc;
+ }
+ rc = pci_pcie_cap_init(d, PCIE_ROOT_EXP_OFFSET, PCI_EXP_TYPE_ROOT_PORT,
+ p->port);
+ if (rc < 0) {
+ return rc;
+ }
+ pcie_cap_deverr_init(d);
+ pcie_cap_slot_init(d, s->slot);
+ pcie_chassis_create(s->chassis);
+ rc = pcie_chassis_add_slot(s);
+ if (rc < 0) {
+ return rc;
+ }
+ pcie_cap_root_init(d);
+ rc = pcie_aer_init(d, PCIE_ROOT_AER_OFFSET);
+ if (rc < 0) {
+ return rc;
+ }
+ pcie_aer_root_init(d);
+ pcie_root_aer_vector_update(d);
+ return 0;
+}
+
+static int pcie_root_exitfn(PCIDevice *d)
+{
+ int rc;
+
+ pcie_aer_exit(d);
+ msi_uninit(d);
+ rc = pci_pcie_cap_exit(d);
+ if (rc < 0) {
+ return rc;
+ }
+ return pci_bridge_exitfn(d);
+}
+
+PCIESlot *pcie_root_init(PCIBus *bus, int devfn, bool multifunction,
+ const char *bus_name, pci_map_irq_fn map_irq,
+ uint8_t port, uint8_t chassis, uint16_t slot)
+{
+ PCIDevice *d;
+ PCIBridge *br;
+ DeviceState *qdev;
+
+ d = pci_create_multifunction(bus, devfn, multifunction, PCIE_ROOT_PORT);
+ if (!d) {
+ return NULL;
+ }
+ br = DO_UPCAST(PCIBridge, dev, d);
+
+ qdev = &br->dev.qdev;
+ pci_bridge_map_irq(br, bus_name, map_irq);
+ qdev_prop_set_uint8(qdev, "port", port);
+ qdev_prop_set_uint8(qdev, "chassis", chassis);
+ qdev_prop_set_uint16(qdev, "slot", slot);
+ qdev_init_nofail(qdev);
+
+ return DO_UPCAST(PCIESlot, port, DO_UPCAST(PCIEPort, br, br));
+}
+
+static const VMStateDescription vmstate_pcie_root = {
+ .name = "pcie-root-port",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCIE_DEVICE(port.br.dev, PCIESlot),
+ VMSTATE_STRUCT(port.br.dev.aer_log, PCIESlot, 0, vmstate_pcie_aer_log,
+ struct pcie_aer_log),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static PCIDeviceInfo pcie_root_info = {
+ .qdev.name = PCIE_ROOT_PORT,
+ .qdev.desc = "Root Port of PCI Express Switch",
+ .qdev.size = sizeof(PCIESlot),
+ .qdev.reset = pcie_root_reset,
+ .qdev.vmsd = &vmstate_pcie_root,
+
+ .is_express = 1,
+ .is_bridge = 1,
+ .config_write = pcie_root_write_config,
+ .init = pcie_root_initfn,
+ .exit = pcie_root_exitfn,
+
+ .qdev.props = (Property[]) {
+ DEFINE_PROP_UINT8("port", PCIESlot, port.port, 0),
+ DEFINE_PROP_UINT8("chassis", PCIESlot, chassis, 0),
+ DEFINE_PROP_UINT16("slot", PCIESlot, slot, 0),
+ DEFINE_PROP_UINT16("aer_log_max", PCIESlot,
+ port.br.dev.aer_log.log_max,
+ PCIE_AER_LOG_MAX_DEFAULT),
+ DEFINE_PROP_END_OF_LIST(),
+ }
+};
+
+static void pcie_root_register(void)
+{
+ pci_qdev_register(&pcie_root_info);
+}
+
+device_init(pcie_root_register);
+
+/*
+ * Local variables:
+ * c-indent-level: 4
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tab-mode: nil
+ * End:
+ */
diff --git a/hw/pcie_root.h b/hw/pcie_root.h
new file mode 100644
index 0000000..9c5d4d0
--- /dev/null
+++ b/hw/pcie_root.h
@@ -0,0 +1,32 @@
+/*
+ * pcie_root.h
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef QEMU_PCIE_ROOT_H
+#define QEMU_PCIE_ROOT_H
+
+#include "pcie_port.h"
+
+#define PCIE_ROOT_PORT "pcie-root-port"
+
+PCIESlot *pcie_root_init(PCIBus *bus, int devfn, bool multifunction,
+ const char *bus_name, pci_map_irq_fn map_irq,
+ uint8_t port, uint8_t chassis, uint16_t slot);
+
+#endif /* QEMU_PCIE_ROOT_H */
--
1.7.1.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH v2 5/9] pcie upstream port: pci express switch upstream port.
2010-09-08 7:39 [Qemu-devel] [PATCH v2 0/9] pcie port switch emulators Isaku Yamahata
` (3 preceding siblings ...)
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 4/9] pcie root port: implement pcie root port Isaku Yamahata
@ 2010-09-08 7:39 ` Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 6/9] pcie downstream port: pci express switch downstream port Isaku Yamahata
` (3 subsequent siblings)
8 siblings, 0 replies; 25+ messages in thread
From: Isaku Yamahata @ 2010-09-08 7:39 UTC (permalink / raw)
To: qemu-devel; +Cc: skandasa, etmartin, wexu2, mst, adhyas, yamahata
pci express switch upstream port.
Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
---
Makefile.objs | 2 +-
hw/pcie_upstream.c | 207 ++++++++++++++++++++++++++++++++++++++++++++++++++++
hw/pcie_upstream.h | 32 ++++++++
3 files changed, 240 insertions(+), 1 deletions(-)
create mode 100644 hw/pcie_upstream.c
create mode 100644 hw/pcie_upstream.h
diff --git a/Makefile.objs b/Makefile.objs
index 658c399..e7a08dc 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -139,7 +139,7 @@ user-obj-y += cutils.o cache-utils.o
hw-obj-y =
hw-obj-y += vl.o loader.o
hw-obj-y += virtio.o virtio-console.o
-hw-obj-y += fw_cfg.o pci.o pci_host.o pcie_host.o pci_bridge.o
+hw-obj-y += fw_cfg.o pci.o pci_host.o pcie_host.o pci_bridge.o pcie_upstream.o
hw-obj-y += watchdog.o
hw-obj-$(CONFIG_ISA_MMIO) += isa_mmio.o
hw-obj-$(CONFIG_ECC) += ecc.o
diff --git a/hw/pcie_upstream.c b/hw/pcie_upstream.c
new file mode 100644
index 0000000..36531c0
--- /dev/null
+++ b/hw/pcie_upstream.c
@@ -0,0 +1,207 @@
+/*
+ * pcie_upstream.c
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pci_ids.h"
+#include "msi.h"
+#include "pcie.h"
+#include "pcie_upstream.h"
+
+/* For now, TI XIO3130 is borrowed. need to get its own id? */
+#define PCI_DEVICE_ID_TI_XIO3130U 0x8232 /* upstream port */
+#define XIO3130_REVISION 0x2
+#define XIO3130_MSI_OFFSET 0x70
+#define XIO3130_MSI_SUPPORTED_FLAGS PCI_MSI_FLAGS_64BIT
+#define XIO3130_MSI_NR_VECTOR 1
+#define XIO3130_SSVID_OFFSET 0x80
+#define XIO3130_SSVID_SVID 0
+#define XIO3130_SSVID_SSID 0
+#define XIO3130_EXP_OFFSET 0x90
+#define XIO3130_AER_OFFSET 0x100
+
+#define PCIE_UPSTREAM_VID PCI_VENDOR_ID_TI
+#define PCIE_UPSTREAM_DID PCI_DEVICE_ID_TI_XIO3130U
+#define PCIE_UPSTREAM_REVISION XIO3130_REVISION
+#define PCIE_UPSTREAM_MSI_SUPPORTED_FLAGS XIO3130_MSI_SUPPORTED_FLAGS
+#define PCIE_UPSTREAM_MSI_NR_VECTOR XIO3130_MSI_NR_VECTOR
+#define PCIE_UPSTREAM_MSI_OFFSET XIO3130_MSI_OFFSET
+#define PCIE_UPSTREAM_SSVID_OFFSET XIO3130_SSVID_OFFSET
+#define PCIE_UPSTREAM_SVID XIO3130_SSVID_SVID
+#define PCIE_UPSTREAM_SSID XIO3130_SSVID_SSID
+#define PCIE_UPSTREAM_EXP_OFFSET XIO3130_EXP_OFFSET
+#define PCIE_UPSTREAM_AER_OFFSET XIO3130_AER_OFFSET
+
+static void pcie_upstream_write_config(PCIDevice *d,
+ uint32_t address, uint32_t val, int len)
+{
+ pci_bridge_write_config(d, address, val, len);
+ pcie_cap_deverr_write_config(d, address, val, len);
+ pcie_cap_flr_write_config(d, address, val, len);
+ msi_write_config(d, address, val, len);
+ pcie_aer_write_config_vbridge(d, address, val, len);
+ pcie_aer_write_config(d, address, val, len);
+}
+
+static void pcie_upstream_reset(DeviceState *qdev)
+{
+ PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev);
+ msi_reset(d);
+ pci_bridge_reset(qdev);
+ pcie_cap_deverr_reset(d);
+}
+
+static void pcie_upstream_flr(PCIDevice *d)
+{
+ /* TODO: not enabled until qdev reset clean up
+ waiting for Anthony's qdev cealn up */
+#if 0
+ /* So far, sticky bit registers or register which must be preserved
+ over FLR aren't emulated. So just reset this device. */
+ pci_device_reset(d);
+#endif
+}
+
+static int pcie_upstream_initfn(PCIDevice *d)
+{
+ PCIBridge* br = DO_UPCAST(PCIBridge, dev, d);
+ PCIEPort *p = DO_UPCAST(PCIEPort, br, br);
+ int rc;
+
+ rc = pci_bridge_initfn(d);
+ if (rc < 0) {
+ return rc;
+ }
+
+ pcie_port_init_reg(d);
+ pci_config_set_vendor_id(d->config, PCIE_UPSTREAM_VID);
+ pci_config_set_device_id(d->config, PCIE_UPSTREAM_DID);
+ d->config[PCI_REVISION_ID] = PCIE_UPSTREAM_REVISION;
+
+ rc = msi_init(d, PCIE_UPSTREAM_MSI_OFFSET, PCIE_UPSTREAM_MSI_NR_VECTOR,
+ PCIE_UPSTREAM_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_64BIT,
+ PCIE_UPSTREAM_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_MASKBIT);
+ if (rc < 0) {
+ return rc;
+ }
+ rc = pci_bridge_ssvid_init(d, PCIE_UPSTREAM_SSVID_OFFSET,
+ PCIE_UPSTREAM_SVID, PCIE_UPSTREAM_SSID);
+ if (rc < 0) {
+ return rc;
+ }
+ rc = pci_pcie_cap_init(d, PCIE_UPSTREAM_EXP_OFFSET, PCI_EXP_TYPE_UPSTREAM,
+ p->port);
+ if (rc < 0) {
+ return rc;
+ }
+ pcie_cap_flr_init(d, &pcie_upstream_flr);
+ pcie_cap_deverr_init(d);
+ rc = pcie_aer_init(d, PCIE_UPSTREAM_AER_OFFSET);
+ if (rc < 0) {
+ return rc;
+ }
+
+ return 0;
+}
+
+static int pcie_upstream_exitfn(PCIDevice *d)
+{
+ int rc;
+
+ pcie_aer_exit(d);
+ msi_uninit(d);
+ rc = pci_pcie_cap_exit(d);
+ if (rc < 0) {
+ return rc;
+ }
+ return pci_bridge_exitfn(d);
+}
+
+PCIEPort *pcie_upstream_init(PCIBus *bus, int devfn, bool multifunction,
+ const char *bus_name, pci_map_irq_fn map_irq,
+ uint8_t port)
+{
+ PCIDevice *d;
+ PCIBridge *br;
+ DeviceState *qdev;
+
+ d = pci_create_multifunction(bus, devfn, multifunction,
+ PCIE_UPSTREAM_PORT);
+ if (!d) {
+ return NULL;
+ }
+ br = DO_UPCAST(PCIBridge, dev, d);
+
+ qdev = &br->dev.qdev;
+ pci_bridge_map_irq(br, bus_name, map_irq);
+ qdev_prop_set_uint8(qdev, "port", port);
+ qdev_init_nofail(qdev);
+
+ return DO_UPCAST(PCIEPort, br, br);
+}
+
+static const VMStateDescription vmstate_pcie_upstream = {
+ .name = "pcie-upstream-port",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCIE_DEVICE(br.dev, PCIEPort),
+ VMSTATE_STRUCT(br.dev.aer_log, PCIEPort, 0, vmstate_pcie_aer_log,
+ struct pcie_aer_log),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static PCIDeviceInfo pcie_upstream_info = {
+ .qdev.name = PCIE_UPSTREAM_PORT,
+ .qdev.desc = "Upstream Port of PCI Express Switch",
+ .qdev.size = sizeof(PCIEPort),
+ .qdev.reset = pcie_upstream_reset,
+ .qdev.vmsd = &vmstate_pcie_upstream,
+
+ .is_express = 1,
+ .is_bridge = 1,
+ .config_write = pcie_upstream_write_config,
+ .init = pcie_upstream_initfn,
+ .exit = pcie_upstream_exitfn,
+
+ .qdev.props = (Property[]) {
+ DEFINE_PROP_UINT8("port", PCIEPort, port, 0),
+ DEFINE_PROP_UINT16("aer_log_max", PCIEPort,
+ br.dev.aer_log.log_max, PCIE_AER_LOG_MAX_DEFAULT),
+ DEFINE_PROP_END_OF_LIST(),
+ }
+};
+
+static void pcie_upstream_register(void)
+{
+ pci_qdev_register(&pcie_upstream_info);
+}
+
+device_init(pcie_upstream_register);
+
+
+/*
+ * Local variables:
+ * c-indent-level: 4
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tab-mode: nil
+ * End:
+ */
diff --git a/hw/pcie_upstream.h b/hw/pcie_upstream.h
new file mode 100644
index 0000000..1d36317
--- /dev/null
+++ b/hw/pcie_upstream.h
@@ -0,0 +1,32 @@
+/*
+ * pcie_upstream.h
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef QEMU_PCIE_UPSTREAM_H
+#define QEMU_PCIE_UPSTREAM_H
+
+#include "pcie_port.h"
+
+#define PCIE_UPSTREAM_PORT "pcie-upstream-port"
+
+PCIEPort *pcie_upstream_init(PCIBus *bus, int devfn, bool multifunction,
+ const char *bus_name, pci_map_irq_fn map_irq,
+ uint8_t port);
+
+#endif /* QEMU_PCIE_UPSTREAM_H */
--
1.7.1.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH v2 6/9] pcie downstream port: pci express switch downstream port.
2010-09-08 7:39 [Qemu-devel] [PATCH v2 0/9] pcie port switch emulators Isaku Yamahata
` (4 preceding siblings ...)
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 5/9] pcie upstream port: pci express switch upstream port Isaku Yamahata
@ 2010-09-08 7:39 ` Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 7/9] pcie/hotplug: glue pushing attention button command. pcie_abp Isaku Yamahata
` (2 subsequent siblings)
8 siblings, 0 replies; 25+ messages in thread
From: Isaku Yamahata @ 2010-09-08 7:39 UTC (permalink / raw)
To: qemu-devel; +Cc: skandasa, etmartin, wexu2, mst, adhyas, yamahata
pcie switch downstream port.
Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
---
Makefile.objs | 1 +
hw/pcie_downstream.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++
hw/pcie_downstream.h | 33 ++++++++
3 files changed, 259 insertions(+), 0 deletions(-)
create mode 100644 hw/pcie_downstream.c
create mode 100644 hw/pcie_downstream.h
diff --git a/Makefile.objs b/Makefile.objs
index e7a08dc..6904b02 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -140,6 +140,7 @@ hw-obj-y =
hw-obj-y += vl.o loader.o
hw-obj-y += virtio.o virtio-console.o
hw-obj-y += fw_cfg.o pci.o pci_host.o pcie_host.o pci_bridge.o pcie_upstream.o
+hw-obj-y += pcie_downstream.o
hw-obj-y += watchdog.o
hw-obj-$(CONFIG_ISA_MMIO) += isa_mmio.o
hw-obj-$(CONFIG_ECC) += ecc.o
diff --git a/hw/pcie_downstream.c b/hw/pcie_downstream.c
new file mode 100644
index 0000000..c1c8259
--- /dev/null
+++ b/hw/pcie_downstream.c
@@ -0,0 +1,225 @@
+/*
+ * pcie_downstream.c
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pci_ids.h"
+#include "msi.h"
+#include "pcie.h"
+#include "pcie_downstream.h"
+
+/* For now, TI XIO3130 is borrowed. need to get its own id? */
+#define PCI_DEVICE_ID_TI_XIO3130D 0x8233 /* downstream port */
+#define XIO3130_REVISION 0x1
+#define XIO3130_MSI_OFFSET 0x70
+#define XIO3130_MSI_SUPPORTED_FLAGS PCI_MSI_FLAGS_64BIT
+#define XIO3130_MSI_NR_VECTOR 1
+#define XIO3130_SSVID_OFFSET 0x80
+#define XIO3130_SSVID_SVID 0
+#define XIO3130_SSVID_SSID 0
+#define XIO3130_EXP_OFFSET 0x90
+#define XIO3130_AER_OFFSET 0x100
+
+#define PCIE_DOWNSTREAM_VID PCI_VENDOR_ID_TI
+#define PCIE_DOWNSTREAM_DID PCI_DEVICE_ID_TI_XIO3130D
+#define PCIE_DOWNSTREAM_REVISION XIO3130_REVISION
+#define PCIE_DOWNSTREAM_MSI_SUPPORTED_FLAGS XIO3130_MSI_SUPPORTED_FLAGS
+#define PCIE_DOWNSTREAM_MSI_NR_VECTOR XIO3130_MSI_NR_VECTOR
+#define PCIE_DOWNSTREAM_MSI_OFFSET XIO3130_MSI_OFFSET
+#define PCIE_DOWNSTREAM_SSVID_OFFSET XIO3130_SSVID_OFFSET
+#define PCIE_DOWNSTREAM_SVID XIO3130_SSVID_SVID
+#define PCIE_DOWNSTREAM_SSID XIO3130_SSVID_SSID
+#define PCIE_DOWNSTREAM_EXP_OFFSET XIO3130_EXP_OFFSET
+#define PCIE_DOWNSTREAM_AER_OFFSET XIO3130_AER_OFFSET
+
+static void pcie_downstream_write_config(PCIDevice *d, uint32_t address,
+ uint32_t val, int len)
+{
+ uint16_t sltctl =
+ pci_get_word(d->config + pci_pcie_cap(d) + PCI_EXP_SLTCTL);
+ pci_bridge_write_config(d, address, val, len);
+ pcie_cap_flr_write_config(d, address, val, len);
+ pcie_cap_deverr_write_config(d, address, val, len);
+ pcie_cap_slot_write_config(d, address, val, len, sltctl);
+ msi_write_config(d, address, val, len);
+ pcie_aer_write_config_vbridge(d, address, val, len);
+ pcie_aer_write_config(d, address, val, len);
+}
+
+static void pcie_downstream_reset(DeviceState *qdev)
+{
+ PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev);
+ msi_reset(d);
+ pcie_cap_deverr_reset(d);
+ pcie_cap_slot_reset(d);
+ pcie_cap_ari_reset(d);
+ pci_bridge_reset(qdev);
+}
+
+static void pcie_downstream_flr(PCIDevice *d)
+{
+ /* TODO: not enabled until qdev reset clean up
+ waiting for Anthony's qdev cealn up */
+#if 0
+ /* So far, sticky bit registers or register which must be preserved
+ over FLR aren't emulated. So just reset this device. */
+ pci_device_reset(d);
+#endif
+}
+
+static int pcie_downstream_initfn(PCIDevice *d)
+{
+ PCIBridge* br = DO_UPCAST(PCIBridge, dev, d);
+ PCIEPort *p = DO_UPCAST(PCIEPort, br, br);
+ PCIESlot *s = DO_UPCAST(PCIESlot, port, p);
+ int rc;
+
+ rc = pci_bridge_initfn(d);
+ if (rc < 0) {
+ return rc;
+ }
+
+ pcie_port_init_reg(d);
+ pci_config_set_vendor_id(d->config, PCIE_DOWNSTREAM_VID);
+ pci_config_set_device_id(d->config, PCIE_DOWNSTREAM_DID);
+ d->config[PCI_REVISION_ID] = PCIE_DOWNSTREAM_REVISION;
+
+ rc = msi_init(d, PCIE_DOWNSTREAM_MSI_OFFSET, PCIE_DOWNSTREAM_MSI_NR_VECTOR,
+ PCIE_DOWNSTREAM_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_64BIT,
+ PCIE_DOWNSTREAM_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_MASKBIT);
+ if (rc < 0) {
+ return rc;
+ }
+ rc = pci_bridge_ssvid_init(d, PCIE_DOWNSTREAM_SSVID_OFFSET,
+ PCIE_DOWNSTREAM_SVID, PCIE_DOWNSTREAM_SSID);
+ if (rc < 0) {
+ return rc;
+ }
+ rc = pci_pcie_cap_init(d, PCIE_DOWNSTREAM_EXP_OFFSET,
+ PCI_EXP_TYPE_DOWNSTREAM, p->port);
+ if (rc < 0) {
+ return rc;
+ }
+ pcie_cap_flr_init(d, &pcie_downstream_flr);
+ pcie_cap_deverr_init(d);
+ pcie_cap_slot_init(d, s->slot);
+ pcie_chassis_create(s->chassis);
+ rc = pcie_chassis_add_slot(s);
+ if (rc < 0) {
+ return rc;
+ }
+ pcie_cap_ari_init(d);
+ rc = pcie_aer_init(d, PCIE_DOWNSTREAM_AER_OFFSET);
+ if (rc < 0) {
+ return rc;
+ }
+
+ return 0;
+}
+
+static int pcie_downstream_exitfn(PCIDevice *d)
+{
+ int rc;
+
+ pcie_aer_exit(d);
+ msi_uninit(d);
+ rc = pci_pcie_cap_exit(d);
+ if (rc < 0) {
+ return rc;
+ }
+ return pci_bridge_exitfn(d);
+}
+
+PCIESlot *pcie_downstream_init(PCIBus *bus,
+ int devfn, bool multifunction,
+ const char *bus_name, pci_map_irq_fn map_irq,
+ uint8_t port, uint8_t chassis, uint16_t slot)
+{
+ PCIDevice *d;
+ PCIBridge *br;
+ DeviceState *qdev;
+
+ d = pci_create_multifunction(bus, devfn, multifunction,
+ PCIE_DOWNSTREAM_PORT);
+ if (!d) {
+ return NULL;
+ }
+ br = DO_UPCAST(PCIBridge, dev, d);
+
+ qdev = &br->dev.qdev;
+ pci_bridge_map_irq(br, bus_name, map_irq);
+ qdev_prop_set_uint8(qdev, "port", port);
+ qdev_prop_set_uint8(qdev, "chassis", chassis);
+ qdev_prop_set_uint16(qdev, "slot", slot);
+ qdev_init_nofail(qdev);
+
+ return DO_UPCAST(PCIESlot, port, DO_UPCAST(PCIEPort, br, br));
+}
+
+static const VMStateDescription vmstate_pcie_downstream = {
+ .name = "pcie-downstream-port",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCIE_DEVICE(port.br.dev, PCIESlot),
+ VMSTATE_STRUCT(port.br.dev.aer_log, PCIESlot, 0, vmstate_pcie_aer_log,
+ struct pcie_aer_log),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static PCIDeviceInfo pcie_downstream_info = {
+ .qdev.name = PCIE_DOWNSTREAM_PORT,
+ .qdev.desc = "Downstream Port of PCI Express Switch",
+ .qdev.size = sizeof(PCIESlot),
+ .qdev.reset = pcie_downstream_reset,
+ .qdev.vmsd = &vmstate_pcie_downstream,
+
+ .is_express = 1,
+ .is_bridge = 1,
+ .config_write = pcie_downstream_write_config,
+ .init = pcie_downstream_initfn,
+ .exit = pcie_downstream_exitfn,
+
+ .qdev.props = (Property[]) {
+ DEFINE_PROP_UINT8("port", PCIESlot, port.port, 0),
+ DEFINE_PROP_UINT8("chassis", PCIESlot, chassis, 0),
+ DEFINE_PROP_UINT16("slot", PCIESlot, slot, 0),
+ DEFINE_PROP_UINT16("aer_log_max", PCIESlot,
+ port.br.dev.aer_log.log_max,
+ PCIE_AER_LOG_MAX_DEFAULT),
+ DEFINE_PROP_END_OF_LIST(),
+ }
+};
+
+static void pcie_downstream_register(void)
+{
+ pci_qdev_register(&pcie_downstream_info);
+}
+
+device_init(pcie_downstream_register);
+
+/*
+ * Local variables:
+ * c-indent-level: 4
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tab-mode: nil
+ * End:
+ */
diff --git a/hw/pcie_downstream.h b/hw/pcie_downstream.h
new file mode 100644
index 0000000..686fdac
--- /dev/null
+++ b/hw/pcie_downstream.h
@@ -0,0 +1,33 @@
+/*
+ * pcie_downstream.h
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef QEMU_PCIE_DOWNSTREAM_H
+#define QEMU_PCIE_DOWNSTREAM_H
+
+#include "pcie_port.h"
+
+#define PCIE_DOWNSTREAM_PORT "pcie-downstream-port"
+
+PCIESlot *pcie_downstream_init(PCIBus *bus,
+ int devfn, bool multifunction,
+ const char *bus_name, pci_map_irq_fn map_irq,
+ uint8_t port, uint8_t chassis, uint16_t slot);
+
+#endif /* QEMU_PCIE_DOWNSTREAM_H */
--
1.7.1.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH v2 7/9] pcie/hotplug: glue pushing attention button command. pcie_abp
2010-09-08 7:39 [Qemu-devel] [PATCH v2 0/9] pcie port switch emulators Isaku Yamahata
` (5 preceding siblings ...)
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 6/9] pcie downstream port: pci express switch downstream port Isaku Yamahata
@ 2010-09-08 7:39 ` Isaku Yamahata
2010-09-08 10:34 ` [Qemu-devel] " Michael S. Tsirkin
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 8/9] pcie/aer: glue aer error injection into qemu monitor Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 9/9] msix: clear not only INTA, but all INTx when MSI-X is enabled Isaku Yamahata
8 siblings, 1 reply; 25+ messages in thread
From: Isaku Yamahata @ 2010-09-08 7:39 UTC (permalink / raw)
To: qemu-devel; +Cc: skandasa, etmartin, wexu2, mst, adhyas, yamahata
glue to pcie_abp monitor command.
Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
---
hw/pcie_port.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
qemu-monitor.hx | 14 +++++++++
sysemu.h | 4 +++
3 files changed, 100 insertions(+), 0 deletions(-)
diff --git a/hw/pcie_port.c b/hw/pcie_port.c
index d2b1f38..0aa8c53 100644
--- a/hw/pcie_port.c
+++ b/hw/pcie_port.c
@@ -18,6 +18,10 @@
* with this program; if not, see <http://www.gnu.org/licenses/>.
*/
+#include "qemu-objects.h"
+#include "sysemu.h"
+#include "monitor.h"
+#include "pcie.h"
#include "pcie_port.h"
void pcie_port_init_reg(PCIDevice *d)
@@ -104,3 +108,81 @@ void pcie_chassis_del_slot(PCIESlot *s)
{
QLIST_REMOVE(s, next);
}
+
+/**************************************************************************
+ * glue for qemu monitor
+ */
+
+/* Parse [<chassis>.]<slot>, return -1 on error */
+static int pcie_parse_slot_addr(const char* slot_addr,
+ uint8_t *chassisp, uint16_t *slotp)
+{
+ const char *p;
+ char *e;
+ unsigned long val;
+ unsigned long chassis = 0;
+ unsigned long slot;
+
+ p = slot_addr;
+ val = strtoul(p, &e, 0);
+ if (e == p) {
+ return -1;
+ }
+ if (*e == '.') {
+ chassis = val;
+ p = e + 1;
+ val = strtoul(p, &e, 0);
+ if (e == p) {
+ return -1;
+ }
+ }
+ slot = val;
+
+ if (*e) {
+ return -1;
+ }
+
+ if (chassis > 0xff || slot > 0xffff) {
+ return -1;
+ }
+
+ *chassisp = chassis;
+ *slotp = slot;
+ return 0;
+}
+
+void pcie_attention_button_push_print(Monitor *mon, const QObject *data)
+{
+ QDict *qdict;
+
+ assert(qobject_type(data) == QTYPE_QDICT);
+ qdict = qobject_to_qdict(data);
+
+ monitor_printf(mon, "OK chassis %d, slot %d\n",
+ (int) qdict_get_int(qdict, "chassis"),
+ (int) qdict_get_int(qdict, "slot"));
+}
+
+int pcie_attention_button_push(Monitor *mon, const QDict *qdict,
+ QObject **ret_data)
+{
+ const char* pcie_slot = qdict_get_str(qdict, "pcie_slot");
+ uint8_t chassis;
+ uint16_t slot;
+ PCIESlot *s;
+
+ if (pcie_parse_slot_addr(pcie_slot, &chassis, &slot) < 0) {
+ monitor_printf(mon, "invalid pcie slot address %s\n", pcie_slot);
+ return -1;
+ }
+ s = pcie_chassis_find_slot(chassis, slot);
+ if (!s) {
+ monitor_printf(mon, "slot is not found. %s\n", pcie_slot);
+ return -1;
+ }
+ pcie_cap_slot_push_attention_button(&s->port.br.dev);
+ *ret_data = qobject_from_jsonf("{ 'chassis': %d, 'slot': %d}",
+ chassis, slot);
+ assert(*ret_data);
+ return 0;
+}
diff --git a/qemu-monitor.hx b/qemu-monitor.hx
index 2af3de6..02fbda1 100644
--- a/qemu-monitor.hx
+++ b/qemu-monitor.hx
@@ -1154,6 +1154,20 @@ Hot remove PCI device.
ETEXI
{
+ .name = "pcie_abp",
+ .args_type = "pcie_slot:s",
+ .params = "[<chassis>.]<slot>",
+ .help = "push pci express attention button",
+ .user_print = pcie_attention_button_push_print,
+ .mhandler.cmd_new = pcie_attention_button_push,
+ },
+
+STEXI
+@item pcie_abp
+Push PCI express attention button
+ETEXI
+
+ {
.name = "host_net_add",
.args_type = "device:s,opts:s?",
.params = "tap|user|socket|vde|dump [options]",
diff --git a/sysemu.h b/sysemu.h
index 9c988bb..cca411d 100644
--- a/sysemu.h
+++ b/sysemu.h
@@ -150,6 +150,10 @@ extern unsigned int nb_prom_envs;
void pci_device_hot_add(Monitor *mon, const QDict *qdict);
void drive_hot_add(Monitor *mon, const QDict *qdict);
void do_pci_device_hot_remove(Monitor *mon, const QDict *qdict);
+/* pcie hotplug */
+void pcie_attention_button_push_print(Monitor *mon, const QObject *data);
+int pcie_attention_button_push(Monitor *mon, const QDict *qdict,
+ QObject **ret_data);
/* serial ports */
--
1.7.1.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH v2 8/9] pcie/aer: glue aer error injection into qemu monitor.
2010-09-08 7:39 [Qemu-devel] [PATCH v2 0/9] pcie port switch emulators Isaku Yamahata
` (6 preceding siblings ...)
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 7/9] pcie/hotplug: glue pushing attention button command. pcie_abp Isaku Yamahata
@ 2010-09-08 7:39 ` Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 9/9] msix: clear not only INTA, but all INTx when MSI-X is enabled Isaku Yamahata
8 siblings, 0 replies; 25+ messages in thread
From: Isaku Yamahata @ 2010-09-08 7:39 UTC (permalink / raw)
To: qemu-devel; +Cc: skandasa, etmartin, wexu2, mst, adhyas, yamahata
glue aer error injection into qemu monitor.
Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
Conflicts:
hw/pcie.c
---
hw/pcie.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
qemu-monitor.hx | 22 ++++++++++++++
sysemu.h | 5 +++
3 files changed, 112 insertions(+), 0 deletions(-)
diff --git a/hw/pcie.c b/hw/pcie.c
index 1f24c2a..f02a868 100644
--- a/hw/pcie.c
+++ b/hw/pcie.c
@@ -19,6 +19,8 @@
*/
#include "sysemu.h"
+#include "qemu-objects.h"
+#include "monitor.h"
#include "pci_bridge.h"
#include "pcie.h"
#include "msix.h"
@@ -1666,3 +1668,86 @@ const VMStateDescription vmstate_pcie_aer_log = {
VMSTATE_END_OF_LIST()
}
};
+
+void pcie_aer_inject_error_print(Monitor *mon, const QObject *data)
+{
+ QDict *qdict;
+ int devfn;
+ assert(qobject_type(data) == QTYPE_QDICT);
+ qdict = qobject_to_qdict(data);
+
+ devfn = (int)qdict_get_int(qdict, "devfn");
+ monitor_printf(mon, "OK domain: %x, bus: %x devfn: %x.%x\n",
+ (int) qdict_get_int(qdict, "domain"),
+ (int) qdict_get_int(qdict, "bus"),
+ PCI_SLOT(devfn), PCI_FUNC(devfn));
+}
+
+int do_pcie_aer_inejct_error(Monitor *mon,
+ const QDict *qdict, QObject **ret_data)
+{
+ const char *pci_addr = qdict_get_str(qdict, "pci_addr");
+ int dom;
+ int bus;
+ unsigned int slot;
+ unsigned int func;
+ PCIDevice *dev;
+ struct pcie_aer_err err;
+
+ /* Ideally qdev device path should be used.
+ * However at the moment there is no reliable way to determine
+ * wheher a given qdev is pci device or not.
+ * so pci_addr is used.
+ */
+ if (pci_parse_devaddr(pci_addr, &dom, &bus, &slot, &func)) {
+ monitor_printf(mon, "invalid pci address %s\n", pci_addr);
+ return -1;
+ }
+ dev = pci_find_device(pci_find_root_bus(dom), bus, slot, func);
+ if (!dev) {
+ monitor_printf(mon, "device is not found. 0x%x:0x%x.0x%x\n",
+ bus, slot, func);
+ return -1;
+ }
+ if (!pci_is_express(dev)) {
+ monitor_printf(mon, "the device doesn't support pci express. "
+ "0x%x:0x%x.0x%x\n",
+ bus, slot, func);
+ return -1;
+ }
+
+ err.status = qdict_get_int(qdict, "error_status");
+ err.source_id = (pci_bus_num(dev->bus) << 8) | dev->devfn;
+
+ err.flags = 0;
+ if (qdict_get_int(qdict, "is_correctable")) {
+ err.flags |= PCIE_AER_ERR_IS_CORRECTABLE;
+ }
+ if (qdict_get_int(qdict, "advisory_non_fatal")) {
+ err.flags |= PCIE_AER_ERR_MAYBE_ADVISORY;
+ }
+ if (qdict_haskey(qdict, "tlph0")) {
+ err.flags |= PCIE_AER_ERR_HEADER_VALID;
+ }
+ if (qdict_haskey(qdict, "hpfx0")) {
+ err.flags |= PCIE_AER_ERR_TLP_PRESENT;
+ }
+
+ err.header[0] = qdict_get_try_int(qdict, "tlph0", 0);
+ err.header[1] = qdict_get_try_int(qdict, "tlph1", 0);
+ err.header[2] = qdict_get_try_int(qdict, "tlph2", 0);
+ err.header[3] = qdict_get_try_int(qdict, "tlph3", 0);
+
+ err.prefix[0] = qdict_get_try_int(qdict, "hpfx0", 0);
+ err.prefix[1] = qdict_get_try_int(qdict, "hpfx1", 0);
+ err.prefix[2] = qdict_get_try_int(qdict, "hpfx2", 0);
+ err.prefix[3] = qdict_get_try_int(qdict, "hpfx3", 0);
+
+ pcie_aer_inject_error(dev, &err);
+ *ret_data = qobject_from_jsonf("{ 'domain': %d, 'bus': %d, 'devfn': %d }",
+ pci_find_domain(dev->bus),
+ pci_bus_num(dev->bus), dev->devfn);
+ assert(*ret_data);
+
+ return 0;
+}
diff --git a/qemu-monitor.hx b/qemu-monitor.hx
index 02fbda1..080c90e 100644
--- a/qemu-monitor.hx
+++ b/qemu-monitor.hx
@@ -1168,6 +1168,28 @@ Push PCI express attention button
ETEXI
{
+ .name = "pcie_aer_inject_error",
+ .args_type = "advisory_non_fatal:-a,is_correctable:-c,"
+ "pci_addr:s,error_status:i,"
+ "tlph0:i?,tlph1:i?,tlph2:i?,tlph3:i?,"
+ "hpfx0:i?,hpfx1:i?,hpfx2:i?,hpfx3:i?",
+ .params = "[-a] [-c] [[<domain>:]<bus>:]<slot>.<func> "
+ "<error status:32bit> "
+ "[<tlp header:(32bit x 4)>] "
+ "[<tlp header prefix:(32bit x 4)>]",
+ .help = "inject pcie aer error "
+ "(use -a for advisory non fatal error) "
+ "(use -c for correctrable error)",
+ .user_print = pcie_aer_inject_error_print,
+ .mhandler.cmd_new = do_pcie_aer_inejct_error,
+ },
+
+STEXI
+@item pcie_abp
+Push PCI express attention button
+ETEXI
+
+ {
.name = "host_net_add",
.args_type = "device:s,opts:s?",
.params = "tap|user|socket|vde|dump [options]",
diff --git a/sysemu.h b/sysemu.h
index cca411d..2f7157c 100644
--- a/sysemu.h
+++ b/sysemu.h
@@ -155,6 +155,11 @@ void pcie_attention_button_push_print(Monitor *mon, const QObject *data);
int pcie_attention_button_push(Monitor *mon, const QDict *qdict,
QObject **ret_data);
+/* pcie aer error injection */
+void pcie_aer_inject_error_print(Monitor *mon, const QObject *data);
+int do_pcie_aer_inejct_error(Monitor *mon,
+ const QDict *qdict, QObject **ret_data);
+
/* serial ports */
#define MAX_SERIAL_PORTS 4
--
1.7.1.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH v2 9/9] msix: clear not only INTA, but all INTx when MSI-X is enabled.
2010-09-08 7:39 [Qemu-devel] [PATCH v2 0/9] pcie port switch emulators Isaku Yamahata
` (7 preceding siblings ...)
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 8/9] pcie/aer: glue aer error injection into qemu monitor Isaku Yamahata
@ 2010-09-08 7:39 ` Isaku Yamahata
2010-09-08 10:33 ` [Qemu-devel] " Michael S. Tsirkin
8 siblings, 1 reply; 25+ messages in thread
From: Isaku Yamahata @ 2010-09-08 7:39 UTC (permalink / raw)
To: qemu-devel; +Cc: skandasa, etmartin, wexu2, mst, adhyas, yamahata
clear not only INTA, but all INTx when MSI-X is enabled.
Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
---
hw/msix.c | 5 ++++-
1 files changed, 4 insertions(+), 1 deletions(-)
diff --git a/hw/msix.c b/hw/msix.c
index 7ce63eb..b202ff7 100644
--- a/hw/msix.c
+++ b/hw/msix.c
@@ -158,6 +158,7 @@ void msix_write_config(PCIDevice *dev, uint32_t addr,
{
unsigned enable_pos = dev->msix_cap + MSIX_CONTROL_OFFSET;
int vector;
+ int i;
if (!range_covers_byte(addr, len, enable_pos)) {
return;
@@ -167,7 +168,9 @@ void msix_write_config(PCIDevice *dev, uint32_t addr,
return;
}
- qemu_set_irq(dev->irq[0], 0);
+ for (i = 0; i < PCI_NUM_PINS; ++i) {
+ qemu_set_irq(dev->irq[i], 0);
+ }
if (msix_function_masked(dev)) {
return;
--
1.7.1.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] Re: [PATCH v2 1/9] msi: implemented msi.
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 1/9] msi: implemented msi Isaku Yamahata
@ 2010-09-08 9:13 ` Michael S. Tsirkin
2010-09-08 9:49 ` Isaku Yamahata
0 siblings, 1 reply; 25+ messages in thread
From: Michael S. Tsirkin @ 2010-09-08 9:13 UTC (permalink / raw)
To: Isaku Yamahata; +Cc: skandasa, adhyas, etmartin, qemu-devel, wexu2
On Wed, Sep 08, 2010 at 04:39:34PM +0900, Isaku Yamahata wrote:
> implemented msi support functions.
>
> Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
Good stuff. Some minor corrections mostly to the comments.
It's up to you whether to address or ignore them.
> ---
> Changes v1 -> v2:
> - opencode some oneline helper function/macros for readability
> - use ffs where appropriate
> - rename some functions/variables as suggested.
> - added assert()
> - 1 -> 1U
> - clear INTx# when MSI is enabled
> - clear pending bits for freed vectors.
> - check the requested number of vectors.
> ---
> Makefile.objs | 2 +-
> hw/msi.c | 360 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> hw/msi.h | 41 +++++++
> hw/pci.h | 10 +-
> 4 files changed, 409 insertions(+), 4 deletions(-)
> create mode 100644 hw/msi.c
> create mode 100644 hw/msi.h
>
> diff --git a/Makefile.objs b/Makefile.objs
> index 594894b..5f5a4c5 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -186,7 +186,7 @@ hw-obj-$(CONFIG_PIIX4) += piix4.o
> # PCI watchdog devices
> hw-obj-y += wdt_i6300esb.o
>
> -hw-obj-y += msix.o
> +hw-obj-y += msix.o msi.o
>
> # PCI network cards
> hw-obj-y += ne2000.o
> diff --git a/hw/msi.c b/hw/msi.c
> new file mode 100644
> index 0000000..32dc953
> --- /dev/null
> +++ b/hw/msi.c
> @@ -0,0 +1,360 @@
> +/*
> + * msi.c
> + *
> + * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
> + * VA Linux Systems Japan K.K.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> +
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> +
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "msi.h"
> +
> +/* Eventually those constants should go to Linux pci_regs.h */
> +#define PCI_MSI_PENDING_32 0x10
> +#define PCI_MSI_PENDING_64 0x14
> +
> +/* PCI_MSI_ADDRESS_LO */
> +#define PCI_MSI_ADDRESS_LO_MASK (~0x3)
> +
> +/* If we get rid of cap allocator, we won't need those. */
> +#define PCI_MSI_32_SIZEOF 0x0a
> +#define PCI_MSI_64_SIZEOF 0x0e
> +#define PCI_MSI_32M_SIZEOF 0x14
> +#define PCI_MSI_64M_SIZEOF 0x18
> +
> +/* If we get rid of cap allocator, we won't need this. */
> +static inline uint8_t msi_cap_sizeof(uint16_t flags)
> +{
> + switch (flags & (PCI_MSI_FLAGS_MASKBIT | PCI_MSI_FLAGS_64BIT)) {
> + case PCI_MSI_FLAGS_MASKBIT | PCI_MSI_FLAGS_64BIT:
> + return PCI_MSI_64M_SIZEOF;
> + case PCI_MSI_FLAGS_64BIT:
> + return PCI_MSI_64_SIZEOF;
> + case PCI_MSI_FLAGS_MASKBIT:
> + return PCI_MSI_32M_SIZEOF;
> + case 0:
> + return PCI_MSI_32_SIZEOF;
> + default:
> + abort();
> + break;
> + }
> + return 0;
> +}
> +
> +//#define MSI_DEBUG
> +
> +#ifdef MSI_DEBUG
> +# define MSI_DPRINTF(fmt, ...) \
> + fprintf(stderr, "%s:%d " fmt, __func__, __LINE__, ## __VA_ARGS__)
> +#else
> +# define MSI_DPRINTF(fmt, ...) do { } while (0)
> +#endif
> +#define MSI_DEV_PRINTF(dev, fmt, ...) \
> + MSI_DPRINTF("%s:%x " fmt, (dev)->name, (dev)->devfn, ## __VA_ARGS__)
> +
> +static inline uint8_t msi_nr_vectors(uint16_t flags)
> +{
> + return 1U <<
> + ((flags & PCI_MSI_FLAGS_QSIZE) >> (ffs(PCI_MSI_FLAGS_QSIZE) - 1));
> +}
> +
> +static inline uint8_t msi_flags_off(const PCIDevice* dev)
> +{
> + return dev->msi_cap + PCI_MSI_FLAGS;
> +}
> +
> +static inline uint8_t msi_address_lo_off(const PCIDevice* dev)
> +{
> + return dev->msi_cap + PCI_MSI_ADDRESS_LO;
> +}
> +
> +static inline uint8_t msi_address_hi_off(const PCIDevice* dev)
> +{
> + return dev->msi_cap + PCI_MSI_ADDRESS_HI;
> +}
> +
> +static inline uint8_t msi_data_off(const PCIDevice* dev, bool msi64bit)
> +{
> + return dev->msi_cap + (msi64bit ? PCI_MSI_DATA_64 : PCI_MSI_DATA_32);
> +}
> +
> +static inline uint8_t msi_mask_off(const PCIDevice* dev, bool msi64bit)
> +{
> + return dev->msi_cap + (msi64bit ? PCI_MSI_MASK_64 : PCI_MSI_MASK_32);
> +}
> +
> +static inline uint8_t msi_pending_off(const PCIDevice* dev, bool msi64bit)
> +{
> + return dev->msi_cap + (msi64bit ? PCI_MSI_PENDING_64 : PCI_MSI_PENDING_32);
> +}
> +
> +bool msi_enabled(const PCIDevice *dev)
> +{
> + return msi_present(dev) &&
> + (pci_get_word(dev->config + msi_flags_off(dev)) &
> + PCI_MSI_FLAGS_ENABLE);
> +}
> +
> +int msi_init(struct PCIDevice *dev, uint8_t offset,
> + uint8_t nr_vectors, bool msi64bit, bool msi_per_vector_mask)
> +{
> + uint8_t vectors_order;
> + uint16_t flags;
> + uint8_t cap_size;
> + int config_offset;
> + MSI_DEV_PRINTF(dev,
> + "init offset: 0x%"PRIx8" vector: %"PRId8
> + " 64bit %d mask %d\n",
> + offset, nr_vectors, msi64bit, msi_per_vector_mask);
> +
> + assert(!(nr_vectors & (nr_vectors - 1))); /* power of 2 */
> + assert(nr_vectors > 0);
> + assert(nr_vectors <= 32); /* the nr of MSI vectors is up to 32 */
> + vectors_order = ffs(nr_vectors) - 1;
> +
> + flags = (vectors_order << (ffs(PCI_MSI_FLAGS_QMASK) - 1)) &
> + PCI_MSI_FLAGS_QMASK;
The & PCI_MSI_FLAGS_QMASK is not needed, is it?
> + if (msi64bit) {
> + flags |= PCI_MSI_FLAGS_64BIT;
> + }
> + if (msi_per_vector_mask) {
> + flags |= PCI_MSI_FLAGS_MASKBIT;
> + }
> +
> + cap_size = msi_cap_sizeof(flags);
> + config_offset = pci_add_capability(dev, PCI_CAP_ID_MSI, offset, cap_size);
> + if (config_offset < 0) {
> + return config_offset;
> + }
> +
> + dev->msi_cap = config_offset;
> + dev->cap_present |= QEMU_PCI_CAP_MSI;
> +
> + pci_set_word(dev->config + msi_flags_off(dev), flags);
> + pci_set_word(dev->wmask + msi_flags_off(dev),
> + PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
> + pci_set_long(dev->wmask + msi_address_lo_off(dev),
> + PCI_MSI_ADDRESS_LO_MASK);
> + if (msi64bit) {
> + pci_set_long(dev->wmask + msi_address_hi_off(dev), 0xffffffff);
> + }
> + pci_set_word(dev->wmask + msi_data_off(dev, msi64bit), 0xffff);
> +
> + if (msi_per_vector_mask) {
> + pci_set_long(dev->wmask + msi_mask_off(dev, msi64bit),
> + (1U << nr_vectors) - 1);
> + }
> + return config_offset;
> +}
> +
> +void msi_uninit(struct PCIDevice *dev)
> +{
> + uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
> + uint8_t cap_size = msi_cap_sizeof(flags);
> + pci_del_capability(dev, PCI_CAP_ID_MSIX, cap_size);
> + MSI_DEV_PRINTF(dev, "uninit\n");
> +}
> +
> +void msi_reset(PCIDevice *dev)
> +{
> + uint16_t flags;
> + bool msi64bit;
> +
> + flags = pci_get_word(dev->config + msi_flags_off(dev));
> + flags &= ~(PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
> + msi64bit = flags & PCI_MSI_FLAGS_64BIT;
> +
> + pci_set_word(dev->config + msi_flags_off(dev), flags);
> + pci_set_long(dev->config + msi_address_lo_off(dev), 0);
> + if (msi64bit) {
> + pci_set_long(dev->config + msi_address_hi_off(dev), 0);
> + }
> + pci_set_word(dev->config + msi_data_off(dev, msi64bit), 0);
> + if (flags & PCI_MSI_FLAGS_MASKBIT) {
> + pci_set_long(dev->config + msi_mask_off(dev, msi64bit), 0);
> + pci_set_long(dev->config + msi_pending_off(dev, msi64bit), 0);
> + }
> + MSI_DEV_PRINTF(dev, "reset\n");
> +}
> +
> +static bool msi_is_masked(const PCIDevice *dev, uint8_t vector)
> +{
> + uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
> + uint32_t mask;
> +
> + if (!(flags & PCI_MSI_FLAGS_MASKBIT)) {
> + return false;
> + }
> +
> + mask = pci_get_long(dev->config +
> + msi_mask_off(dev, flags & PCI_MSI_FLAGS_64BIT));
> + return mask & (1U << vector);
> +}
> +
> +static void msi_set_pending(PCIDevice *dev, uint8_t vector)
> +{
> + uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
> + bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
> + uint32_t pending;
> +
> + assert(flags & PCI_MSI_FLAGS_MASKBIT);
> +
> + pending = pci_get_long(dev->config + msi_pending_off(dev, msi64bit));
> + pending |= 1U << vector;
> + pci_set_long(dev->config + msi_pending_off(dev, msi64bit), pending);
> + MSI_DEV_PRINTF(dev, "pending vector 0x%"PRIx8"\n", vector);
> +}
> +
> +void msi_notify(PCIDevice *dev, uint8_t vector)
> +{
> + uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
> + bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
> + uint8_t nr_vectors = msi_nr_vectors(flags);
> + uint64_t address;
> + uint32_t data;
> +
> + assert(vector < nr_vectors);
> + if (msi_is_masked(dev, vector)) {
> + msi_set_pending(dev, vector);
> + return;
> + }
> +
> + if (msi64bit){
> + address = pci_get_quad(dev->config + msi_address_lo_off(dev));
> + } else {
> + address = pci_get_long(dev->config + msi_address_lo_off(dev));
> + }
> +
> + /* upper bit 31:16 is zero */
> + data = pci_get_word(dev->config + msi_data_off(dev, msi64bit));
> + if (nr_vectors > 1) {
> + data &= ~(nr_vectors - 1);
> + data |= vector;
> + }
> +
> + MSI_DEV_PRINTF(dev,
> + "notify vector 0x%"PRIx8
> + " address: 0x%"PRIx64" data: 0x%"PRIx32"\n",
> + vector, address, data);
> + stl_phys(address, data);
> +}
> +
> +/* call this function after updating configs by pci_default_write_config(). */
> +void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len)
> +{
> + uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
> + bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
> + bool msi_per_vector_mask = flags & PCI_MSI_FLAGS_MASKBIT;
> + uint8_t nr_vectors;
> + uint8_t nr_vectors_capable;
> + uint8_t vector;
> + uint32_t pending;
> + int i;
> +
> +#ifdef MSI_DEBUG
> + if (ranges_overlap(addr, len, dev->msi_cap, msi_cap_sizeof(flags))) {
> + MSI_DEV_PRINTF(dev, "addr 0x%"PRIx32" val 0x%"PRIx32" len %d\n",
> + addr, val, len);
> + MSI_DEV_PRINTF(dev, "ctrl: 0x%"PRIx16" address: 0x%"PRIx32,
> + flags,
> + pci_get_long(dev->config + msi_address_lo_off(dev)));
> + if (msi64bit) {
> + fprintf(stderr, " addrss-hi: 0x%"PRIx32,
> + pci_get_long(dev->config + msi_address_hi_off(dev)));
> + }
> + fprintf(stderr, " data: 0x%"PRIx16,
> + pci_get_word(dev->config + msi_data_off(dev, msi64bit)));
> + if (flags & PCI_MSI_FLAGS_MASKBIT) {
> + fprintf(stderr, " mask 0x%"PRIx32" pending 0x%"PRIx32,
> + pci_get_long(dev->config + msi_mask_off(dev, msi64bit)),
> + pci_get_long(dev->config + msi_pending_off(dev, msi64bit)));
> + }
> + fprintf(stderr, "\n");
> + }
> +#endif
> +
> + /* do we modified? */
english:
do -> Are
> + if (!(ranges_overlap(addr, len, msi_flags_off(dev), 2) ||
> + (msi_per_vector_mask &&
> + ranges_overlap(addr, len, msi_mask_off(dev, msi64bit), 4)))) {
> + return;
> + }
> +
> + if (!(flags & PCI_MSI_FLAGS_ENABLE)) {
> + return;
> + }
> +
> + /*
> + * Now MSI is enabled, clear INTx# interrupts.
> + * Guest is prohibited from enabling msi while interrupt is asserted.
> + * But it can.
I think the above wording is misleading: the spec says that
the *driver* is prohibited from writing enable bit to mask
a service request. But the guest OS could do this.
>+ * So we just discard the interrupts as moderate fallback.
> + *
> + * 6.8.3.3. Enabling Operation
> + * While enabled for MSI or MSI-X operation, a function is prohibited
> + * from using its INTx# pin (if implemented) to request
> + * service (MSI, MSI-X, and INTx# are mutually exclusive).
> + */
> + for (i = 0; i < PCI_NUM_PINS; ++i) {
> + qemu_set_irq(dev->irq[i], 0);
> + }
> +
> + /* nr_vectors might be set bigger than capable. So clamp it. */
Maybe add that this is not legal by spec, so we
can do anything we like, just don't crash the host.
> + nr_vectors = msi_nr_vectors(flags);
> + nr_vectors_capable = 1U <<
> + ((flags & PCI_MSI_FLAGS_QMASK) >> (ffs(PCI_MSI_FLAGS_QMASK) - 1));
> + if (nr_vectors > nr_vectors_capable) {
> + uint8_t vectors_order;
> + nr_vectors = nr_vectors_capable;
> + vectors_order = ffs(nr_vectors) - 1;
> +
> + flags &= ~PCI_MSI_FLAGS_QSIZE;
> + flags |= (vectors_order << (ffs(PCI_MSI_FLAGS_QSIZE) - 1)) &
> + PCI_MSI_FLAGS_QSIZE;
> + pci_set_word(dev->config + msi_flags_off(dev), flags);
> + }
We do the shift/ffs dance many times here. This can be done easier
if we compare the log values:
log_num_vecs = (flags & PCI_MSI_FLAGS_QSIZE) >> (ffs(PCI_MSI_FLAGS_QSIZE) - 1);
log_max_vecs = (flags & PCI_MSI_FLAGS_QMASK) >> (ffs(PCI_MSI_FLAGS_QMASK) - 1);
if (log_num_vecs > log_max_vecs) {
flags &= ~PCI_MSI_FLAGS_QSIZE;
flags |= log_max_vecs << (ffs(PCI_MSI_FLAGS_QSIZE) - 1);
pci_set_word(dev->config + msi_flags_off(dev), flags);
}
> +
> + if (!msi_per_vector_mask) {
> + /* if per vector masking isn't supported,
> + there is no pending interrupt. */
> + return;
> + }
> +
> + /*
> + * If the number of vectors are reduced, clear pending bit for
> + * freed vectors.
> + * Since it is unknown here which interrupt will be used for pending bit
> + * of freed vector, just discard the interrupt.
The last two lines seem to confuse more than they explain.
Replace with 'This will discard pending interrupts, if any.'?
> + */
> + pending = pci_get_long(dev->config + msi_pending_off(dev, msi64bit));
> + pending &= (1U << pending) - 1;
> + pci_set_long(dev->config + msi_pending_off(dev, msi64bit), pending);
> +
> + /* deliver pending interrupts which are unmasked */
> + for (vector = 0; vector < nr_vectors; ++vector) {
> + if (msi_is_masked(dev, vector) || !(pending & (1U << vector))) {
> + continue;
> + }
> +
> + pending &= ~(1U << vector);
> + pci_set_long(dev->config + msi_pending_off(dev, msi64bit),
> + pending);
> + msi_notify(dev, vector);
> + }
> +}
> +
> +uint8_t msi_nr_vectors_allocated(const PCIDevice *dev)
> +{
> + uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
> + return msi_nr_vectors(flags);
> +}
> diff --git a/hw/msi.h b/hw/msi.h
> new file mode 100644
> index 0000000..eac9c78
> --- /dev/null
> +++ b/hw/msi.h
> @@ -0,0 +1,41 @@
> +/*
> + * msi.h
> + *
> + * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
> + * VA Linux Systems Japan K.K.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> +
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> +
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef QEMU_MSI_H
> +#define QEMU_MSI_H
> +
> +#include "qemu-common.h"
> +#include "pci.h"
> +
> +bool msi_enabled(const PCIDevice *dev);
> +int msi_init(struct PCIDevice *dev, uint8_t offset,
> + uint8_t nr_vectors, bool msi64bit, bool msi_per_vector_mask);
> +void msi_uninit(struct PCIDevice *dev);
> +void msi_reset(PCIDevice *dev);
> +void msi_notify(PCIDevice *dev, uint8_t vector);
> +void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len);
> +uint8_t msi_nr_vectors_allocated(const PCIDevice *dev);
> +
> +static inline bool msi_present(const PCIDevice *dev)
> +{
> + return dev->cap_present & QEMU_PCI_CAP_MSI;
> +}
> +
> +#endif /* QEMU_MSI_H */
> diff --git a/hw/pci.h b/hw/pci.h
> index 2b4c318..296c7ba 100644
> --- a/hw/pci.h
> +++ b/hw/pci.h
> @@ -109,11 +109,12 @@ typedef struct PCIIORegion {
>
> /* Bits in cap_present field. */
> enum {
> - QEMU_PCI_CAP_MSIX = 0x1,
> - QEMU_PCI_CAP_EXPRESS = 0x2,
> + QEMU_PCI_CAP_MSI = 0x1,
> + QEMU_PCI_CAP_MSIX = 0x2,
> + QEMU_PCI_CAP_EXPRESS = 0x4,
>
> /* multifunction capable device */
> -#define QEMU_PCI_CAP_MULTIFUNCTION_BITNR 2
> +#define QEMU_PCI_CAP_MULTIFUNCTION_BITNR 3
> QEMU_PCI_CAP_MULTIFUNCTION = (1 << QEMU_PCI_CAP_MULTIFUNCTION_BITNR),
> };
>
> @@ -168,6 +169,9 @@ struct PCIDevice {
> /* Version id needed for VMState */
> int32_t version_id;
>
> + /* Offset of MSI capability in config space */
> + uint8_t msi_cap;
> +
> /* Location of option rom */
> char *romfile;
> ram_addr_t rom_offset;
> --
> 1.7.1.1
^ permalink raw reply [flat|nested] 25+ messages in thread
* [Qemu-devel] Re: [PATCH v2 1/9] msi: implemented msi.
2010-09-08 9:13 ` [Qemu-devel] " Michael S. Tsirkin
@ 2010-09-08 9:49 ` Isaku Yamahata
0 siblings, 0 replies; 25+ messages in thread
From: Isaku Yamahata @ 2010-09-08 9:49 UTC (permalink / raw)
To: Michael S. Tsirkin; +Cc: skandasa, adhyas, etmartin, qemu-devel, wexu2
On Wed, Sep 08, 2010 at 12:13:44PM +0300, Michael S. Tsirkin wrote:
> On Wed, Sep 08, 2010 at 04:39:34PM +0900, Isaku Yamahata wrote:
> > implemented msi support functions.
> >
> > Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
>
> Good stuff. Some minor corrections mostly to the comments.
> It's up to you whether to address or ignore them.
Here is v3. Other patches remains same so I didn't re-send the whole series.
>From f17589c138ede4e79984a65b637f239c167fe007 Mon Sep 17 00:00:00 2001
Message-Id: <f17589c138ede4e79984a65b637f239c167fe007.1283939101.git.yamahata@valinux.co.jp>
In-Reply-To: <cover.1283939101.git.yamahata@valinux.co.jp>
References: <cover.1283939101.git.yamahata@valinux.co.jp>
From: Isaku Yamahata <yamahata@valinux.co.jp>
Date: Mon, 6 Sep 2010 15:03:00 +0900
Subject: [PATCH 1/9] msi: implemented msi.
implemented msi support functions.
Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
---
Changes v2 -> v3:
- improved comment wording.
- simplified shift/ffs dance.
Changes v1 -> v2:
- opencode some oneline helper function/macros for readability
- use ffs where appropriate
- rename some functions/variables as suggested.
- added assert()
- 1 -> 1U
- clear INTx# when MSI is enabled
- clear pending bits for freed vectors.
- check the requested number of vectors.
---
Makefile.objs | 2 +-
hw/msi.c | 358 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
hw/msi.h | 41 +++++++
hw/pci.h | 10 +-
4 files changed, 407 insertions(+), 4 deletions(-)
create mode 100644 hw/msi.c
create mode 100644 hw/msi.h
diff --git a/Makefile.objs b/Makefile.objs
index 594894b..5f5a4c5 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -186,7 +186,7 @@ hw-obj-$(CONFIG_PIIX4) += piix4.o
# PCI watchdog devices
hw-obj-y += wdt_i6300esb.o
-hw-obj-y += msix.o
+hw-obj-y += msix.o msi.o
# PCI network cards
hw-obj-y += ne2000.o
diff --git a/hw/msi.c b/hw/msi.c
new file mode 100644
index 0000000..65c163f
--- /dev/null
+++ b/hw/msi.c
@@ -0,0 +1,358 @@
+/*
+ * msi.c
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "msi.h"
+
+/* Eventually those constants should go to Linux pci_regs.h */
+#define PCI_MSI_PENDING_32 0x10
+#define PCI_MSI_PENDING_64 0x14
+
+/* PCI_MSI_ADDRESS_LO */
+#define PCI_MSI_ADDRESS_LO_MASK (~0x3)
+
+/* If we get rid of cap allocator, we won't need those. */
+#define PCI_MSI_32_SIZEOF 0x0a
+#define PCI_MSI_64_SIZEOF 0x0e
+#define PCI_MSI_32M_SIZEOF 0x14
+#define PCI_MSI_64M_SIZEOF 0x18
+
+/* If we get rid of cap allocator, we won't need this. */
+static inline uint8_t msi_cap_sizeof(uint16_t flags)
+{
+ switch (flags & (PCI_MSI_FLAGS_MASKBIT | PCI_MSI_FLAGS_64BIT)) {
+ case PCI_MSI_FLAGS_MASKBIT | PCI_MSI_FLAGS_64BIT:
+ return PCI_MSI_64M_SIZEOF;
+ case PCI_MSI_FLAGS_64BIT:
+ return PCI_MSI_64_SIZEOF;
+ case PCI_MSI_FLAGS_MASKBIT:
+ return PCI_MSI_32M_SIZEOF;
+ case 0:
+ return PCI_MSI_32_SIZEOF;
+ default:
+ abort();
+ break;
+ }
+ return 0;
+}
+
+//#define MSI_DEBUG
+
+#ifdef MSI_DEBUG
+# define MSI_DPRINTF(fmt, ...) \
+ fprintf(stderr, "%s:%d " fmt, __func__, __LINE__, ## __VA_ARGS__)
+#else
+# define MSI_DPRINTF(fmt, ...) do { } while (0)
+#endif
+#define MSI_DEV_PRINTF(dev, fmt, ...) \
+ MSI_DPRINTF("%s:%x " fmt, (dev)->name, (dev)->devfn, ## __VA_ARGS__)
+
+static inline uint8_t msi_nr_vectors(uint16_t flags)
+{
+ return 1U <<
+ ((flags & PCI_MSI_FLAGS_QSIZE) >> (ffs(PCI_MSI_FLAGS_QSIZE) - 1));
+}
+
+static inline uint8_t msi_flags_off(const PCIDevice* dev)
+{
+ return dev->msi_cap + PCI_MSI_FLAGS;
+}
+
+static inline uint8_t msi_address_lo_off(const PCIDevice* dev)
+{
+ return dev->msi_cap + PCI_MSI_ADDRESS_LO;
+}
+
+static inline uint8_t msi_address_hi_off(const PCIDevice* dev)
+{
+ return dev->msi_cap + PCI_MSI_ADDRESS_HI;
+}
+
+static inline uint8_t msi_data_off(const PCIDevice* dev, bool msi64bit)
+{
+ return dev->msi_cap + (msi64bit ? PCI_MSI_DATA_64 : PCI_MSI_DATA_32);
+}
+
+static inline uint8_t msi_mask_off(const PCIDevice* dev, bool msi64bit)
+{
+ return dev->msi_cap + (msi64bit ? PCI_MSI_MASK_64 : PCI_MSI_MASK_32);
+}
+
+static inline uint8_t msi_pending_off(const PCIDevice* dev, bool msi64bit)
+{
+ return dev->msi_cap + (msi64bit ? PCI_MSI_PENDING_64 : PCI_MSI_PENDING_32);
+}
+
+bool msi_enabled(const PCIDevice *dev)
+{
+ return msi_present(dev) &&
+ (pci_get_word(dev->config + msi_flags_off(dev)) &
+ PCI_MSI_FLAGS_ENABLE);
+}
+
+int msi_init(struct PCIDevice *dev, uint8_t offset,
+ uint8_t nr_vectors, bool msi64bit, bool msi_per_vector_mask)
+{
+ uint8_t vectors_order;
+ uint16_t flags;
+ uint8_t cap_size;
+ int config_offset;
+ MSI_DEV_PRINTF(dev,
+ "init offset: 0x%"PRIx8" vector: %"PRId8
+ " 64bit %d mask %d\n",
+ offset, nr_vectors, msi64bit, msi_per_vector_mask);
+
+ assert(!(nr_vectors & (nr_vectors - 1))); /* power of 2 */
+ assert(nr_vectors > 0);
+ assert(nr_vectors <= 32); /* the nr of MSI vectors is up to 32 */
+ vectors_order = ffs(nr_vectors) - 1;
+
+ flags = vectors_order << (ffs(PCI_MSI_FLAGS_QMASK) - 1);
+ if (msi64bit) {
+ flags |= PCI_MSI_FLAGS_64BIT;
+ }
+ if (msi_per_vector_mask) {
+ flags |= PCI_MSI_FLAGS_MASKBIT;
+ }
+
+ cap_size = msi_cap_sizeof(flags);
+ config_offset = pci_add_capability(dev, PCI_CAP_ID_MSI, offset, cap_size);
+ if (config_offset < 0) {
+ return config_offset;
+ }
+
+ dev->msi_cap = config_offset;
+ dev->cap_present |= QEMU_PCI_CAP_MSI;
+
+ pci_set_word(dev->config + msi_flags_off(dev), flags);
+ pci_set_word(dev->wmask + msi_flags_off(dev),
+ PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
+ pci_set_long(dev->wmask + msi_address_lo_off(dev),
+ PCI_MSI_ADDRESS_LO_MASK);
+ if (msi64bit) {
+ pci_set_long(dev->wmask + msi_address_hi_off(dev), 0xffffffff);
+ }
+ pci_set_word(dev->wmask + msi_data_off(dev, msi64bit), 0xffff);
+
+ if (msi_per_vector_mask) {
+ pci_set_long(dev->wmask + msi_mask_off(dev, msi64bit),
+ (1U << nr_vectors) - 1);
+ }
+ return config_offset;
+}
+
+void msi_uninit(struct PCIDevice *dev)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ uint8_t cap_size = msi_cap_sizeof(flags);
+ pci_del_capability(dev, PCI_CAP_ID_MSIX, cap_size);
+ MSI_DEV_PRINTF(dev, "uninit\n");
+}
+
+void msi_reset(PCIDevice *dev)
+{
+ uint16_t flags;
+ bool msi64bit;
+
+ flags = pci_get_word(dev->config + msi_flags_off(dev));
+ flags &= ~(PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
+ msi64bit = flags & PCI_MSI_FLAGS_64BIT;
+
+ pci_set_word(dev->config + msi_flags_off(dev), flags);
+ pci_set_long(dev->config + msi_address_lo_off(dev), 0);
+ if (msi64bit) {
+ pci_set_long(dev->config + msi_address_hi_off(dev), 0);
+ }
+ pci_set_word(dev->config + msi_data_off(dev, msi64bit), 0);
+ if (flags & PCI_MSI_FLAGS_MASKBIT) {
+ pci_set_long(dev->config + msi_mask_off(dev, msi64bit), 0);
+ pci_set_long(dev->config + msi_pending_off(dev, msi64bit), 0);
+ }
+ MSI_DEV_PRINTF(dev, "reset\n");
+}
+
+static bool msi_is_masked(const PCIDevice *dev, uint8_t vector)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ uint32_t mask;
+
+ if (!(flags & PCI_MSI_FLAGS_MASKBIT)) {
+ return false;
+ }
+
+ mask = pci_get_long(dev->config +
+ msi_mask_off(dev, flags & PCI_MSI_FLAGS_64BIT));
+ return mask & (1U << vector);
+}
+
+static void msi_set_pending(PCIDevice *dev, uint8_t vector)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
+ uint32_t pending;
+
+ assert(flags & PCI_MSI_FLAGS_MASKBIT);
+
+ pending = pci_get_long(dev->config + msi_pending_off(dev, msi64bit));
+ pending |= 1U << vector;
+ pci_set_long(dev->config + msi_pending_off(dev, msi64bit), pending);
+ MSI_DEV_PRINTF(dev, "pending vector 0x%"PRIx8"\n", vector);
+}
+
+void msi_notify(PCIDevice *dev, uint8_t vector)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
+ uint8_t nr_vectors = msi_nr_vectors(flags);
+ uint64_t address;
+ uint32_t data;
+
+ assert(vector < nr_vectors);
+ if (msi_is_masked(dev, vector)) {
+ msi_set_pending(dev, vector);
+ return;
+ }
+
+ if (msi64bit){
+ address = pci_get_quad(dev->config + msi_address_lo_off(dev));
+ } else {
+ address = pci_get_long(dev->config + msi_address_lo_off(dev));
+ }
+
+ /* upper bit 31:16 is zero */
+ data = pci_get_word(dev->config + msi_data_off(dev, msi64bit));
+ if (nr_vectors > 1) {
+ data &= ~(nr_vectors - 1);
+ data |= vector;
+ }
+
+ MSI_DEV_PRINTF(dev,
+ "notify vector 0x%"PRIx8
+ " address: 0x%"PRIx64" data: 0x%"PRIx32"\n",
+ vector, address, data);
+ stl_phys(address, data);
+}
+
+/* call this function after updating configs by pci_default_write_config(). */
+void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
+ bool msi_per_vector_mask = flags & PCI_MSI_FLAGS_MASKBIT;
+ uint8_t nr_vectors;
+ uint8_t log_num_vecs;
+ uint8_t log_max_vecs;
+ uint8_t vector;
+ uint32_t pending;
+ int i;
+
+#ifdef MSI_DEBUG
+ if (ranges_overlap(addr, len, dev->msi_cap, msi_cap_sizeof(flags))) {
+ MSI_DEV_PRINTF(dev, "addr 0x%"PRIx32" val 0x%"PRIx32" len %d\n",
+ addr, val, len);
+ MSI_DEV_PRINTF(dev, "ctrl: 0x%"PRIx16" address: 0x%"PRIx32,
+ flags,
+ pci_get_long(dev->config + msi_address_lo_off(dev)));
+ if (msi64bit) {
+ fprintf(stderr, " addrss-hi: 0x%"PRIx32,
+ pci_get_long(dev->config + msi_address_hi_off(dev)));
+ }
+ fprintf(stderr, " data: 0x%"PRIx16,
+ pci_get_word(dev->config + msi_data_off(dev, msi64bit)));
+ if (flags & PCI_MSI_FLAGS_MASKBIT) {
+ fprintf(stderr, " mask 0x%"PRIx32" pending 0x%"PRIx32,
+ pci_get_long(dev->config + msi_mask_off(dev, msi64bit)),
+ pci_get_long(dev->config + msi_pending_off(dev, msi64bit)));
+ }
+ fprintf(stderr, "\n");
+ }
+#endif
+
+ /* Are we modified? */
+ if (!(ranges_overlap(addr, len, msi_flags_off(dev), 2) ||
+ (msi_per_vector_mask &&
+ ranges_overlap(addr, len, msi_mask_off(dev, msi64bit), 4)))) {
+ return;
+ }
+
+ if (!(flags & PCI_MSI_FLAGS_ENABLE)) {
+ return;
+ }
+
+ /*
+ * Now MSI is enabled, clear INTx# interrupts.
+ * the driver is prohibited from writing enable bit to mask
+ * a service request. But the guest OS could do this.
+ * So we just discard the interrupts as moderate fallback.
+ *
+ * 6.8.3.3. Enabling Operation
+ * While enabled for MSI or MSI-X operation, a function is prohibited
+ * from using its INTx# pin (if implemented) to request
+ * service (MSI, MSI-X, and INTx# are mutually exclusive).
+ */
+ for (i = 0; i < PCI_NUM_PINS; ++i) {
+ qemu_set_irq(dev->irq[i], 0);
+ }
+
+ /*
+ * nr_vectors might be set bigger than capable. So clamp it.
+ * This is not legal by spec, so we can do anything we like,
+ * just don't crash the host
+ */
+ log_num_vecs =
+ (flags & PCI_MSI_FLAGS_QSIZE) >> (ffs(PCI_MSI_FLAGS_QSIZE) - 1);
+ log_max_vecs =
+ (flags & PCI_MSI_FLAGS_QMASK) >> (ffs(PCI_MSI_FLAGS_QMASK) - 1);
+ if (log_num_vecs > log_max_vecs) {
+ flags &= ~PCI_MSI_FLAGS_QSIZE;
+ flags |= log_max_vecs << (ffs(PCI_MSI_FLAGS_QSIZE) - 1);
+ pci_set_word(dev->config + msi_flags_off(dev), flags);
+ }
+
+ if (!msi_per_vector_mask) {
+ /* if per vector masking isn't supported,
+ there is no pending interrupt. */
+ return;
+ }
+
+ nr_vectors = msi_nr_vectors(flags);
+
+ /* This will discard pending interrupts, if any. */
+ pending = pci_get_long(dev->config + msi_pending_off(dev, msi64bit));
+ pending &= (1U << nr_vectors) - 1;
+ pci_set_long(dev->config + msi_pending_off(dev, msi64bit), pending);
+
+ /* deliver pending interrupts which are unmasked */
+ for (vector = 0; vector < nr_vectors; ++vector) {
+ if (msi_is_masked(dev, vector) || !(pending & (1U << vector))) {
+ continue;
+ }
+
+ pending &= ~(1U << vector);
+ pci_set_long(dev->config + msi_pending_off(dev, msi64bit),
+ pending);
+ msi_notify(dev, vector);
+ }
+}
+
+uint8_t msi_nr_vectors_allocated(const PCIDevice *dev)
+{
+ uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+ return msi_nr_vectors(flags);
+}
diff --git a/hw/msi.h b/hw/msi.h
new file mode 100644
index 0000000..eac9c78
--- /dev/null
+++ b/hw/msi.h
@@ -0,0 +1,41 @@
+/*
+ * msi.h
+ *
+ * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef QEMU_MSI_H
+#define QEMU_MSI_H
+
+#include "qemu-common.h"
+#include "pci.h"
+
+bool msi_enabled(const PCIDevice *dev);
+int msi_init(struct PCIDevice *dev, uint8_t offset,
+ uint8_t nr_vectors, bool msi64bit, bool msi_per_vector_mask);
+void msi_uninit(struct PCIDevice *dev);
+void msi_reset(PCIDevice *dev);
+void msi_notify(PCIDevice *dev, uint8_t vector);
+void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len);
+uint8_t msi_nr_vectors_allocated(const PCIDevice *dev);
+
+static inline bool msi_present(const PCIDevice *dev)
+{
+ return dev->cap_present & QEMU_PCI_CAP_MSI;
+}
+
+#endif /* QEMU_MSI_H */
diff --git a/hw/pci.h b/hw/pci.h
index 2b4c318..296c7ba 100644
--- a/hw/pci.h
+++ b/hw/pci.h
@@ -109,11 +109,12 @@ typedef struct PCIIORegion {
/* Bits in cap_present field. */
enum {
- QEMU_PCI_CAP_MSIX = 0x1,
- QEMU_PCI_CAP_EXPRESS = 0x2,
+ QEMU_PCI_CAP_MSI = 0x1,
+ QEMU_PCI_CAP_MSIX = 0x2,
+ QEMU_PCI_CAP_EXPRESS = 0x4,
/* multifunction capable device */
-#define QEMU_PCI_CAP_MULTIFUNCTION_BITNR 2
+#define QEMU_PCI_CAP_MULTIFUNCTION_BITNR 3
QEMU_PCI_CAP_MULTIFUNCTION = (1 << QEMU_PCI_CAP_MULTIFUNCTION_BITNR),
};
@@ -168,6 +169,9 @@ struct PCIDevice {
/* Version id needed for VMState */
int32_t version_id;
+ /* Offset of MSI capability in config space */
+ uint8_t msi_cap;
+
/* Location of option rom */
char *romfile;
ram_addr_t rom_offset;
--
1.7.1.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] Re: [PATCH v2 2/9] pcie: helper functions for pcie extended capability.
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 2/9] pcie: helper functions for pcie extended capability Isaku Yamahata
@ 2010-09-08 10:31 ` Michael S. Tsirkin
2010-09-14 10:29 ` Isaku Yamahata
2010-09-15 5:50 ` Isaku Yamahata
2010-09-08 17:38 ` Wei Xu
2010-09-12 13:26 ` Michael S. Tsirkin
2 siblings, 2 replies; 25+ messages in thread
From: Michael S. Tsirkin @ 2010-09-08 10:31 UTC (permalink / raw)
To: Isaku Yamahata; +Cc: skandasa, adhyas, etmartin, qemu-devel, wexu2
On Wed, Sep 08, 2010 at 04:39:35PM +0900, Isaku Yamahata wrote:
> This patch implements helper functions for pci express extended capability.
> NOTE: presence detection depends on pci_qdev_init() change.
> PCIExpressDevice::aer_log_max is in PCIDevice for device property.
>
> Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
Thanks!
Started looking into this. Didn't complete before I run out of time
buit since I'll be offline for several days, here's what I have.
> ---
> Makefile.objs | 1 +
> hw/pci.h | 24 +
> hw/pcie.c | 1668 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> hw/pcie.h | 186 +++++++
> qemu-common.h | 1 +
> 5 files changed, 1880 insertions(+), 0 deletions(-)
> create mode 100644 hw/pcie.c
> create mode 100644 hw/pcie.h
>
> diff --git a/Makefile.objs b/Makefile.objs
> index 5f5a4c5..eeb5134 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -186,6 +186,7 @@ hw-obj-$(CONFIG_PIIX4) += piix4.o
> # PCI watchdog devices
> hw-obj-y += wdt_i6300esb.o
>
> +hw-obj-y += pcie.o
> hw-obj-y += msix.o msi.o
>
> # PCI network cards
> diff --git a/hw/pci.h b/hw/pci.h
> index 296c7ba..bccab3a 100644
> --- a/hw/pci.h
> +++ b/hw/pci.h
> @@ -9,6 +9,8 @@
> /* PCI includes legacy ISA access. */
> #include "isa.h"
>
> +#include "pcie.h"
> +
> /* PCI bus */
>
> #define PCI_DEVFN(slot, func) ((((slot) & 0x1f) << 3) | ((func) & 0x07))
> @@ -172,6 +174,12 @@ struct PCIDevice {
> /* Offset of MSI capability in config space */
> uint8_t msi_cap;
>
> + /* PCI Express */
> + PCIExpressDevice *exp;
> + /* Theoretically this belongs to PCIExpressDevice.
> + However it is here for property and save/load */
> + struct pcie_aer_log aer_log;
> +
Hmm. Instead, let's put PCIExpressDevice instead of a pointer here,
and move aer_log? As a bonus, pcie_ APIs could get the express device
(if you like, I'm not sure it's a good idea, but it's possible).
> /* Location of option rom */
> char *romfile;
> ram_addr_t rom_offset;
> @@ -367,6 +375,22 @@ static inline uint32_t pci_config_size(const PCIDevice *d)
> return pci_is_express(d) ? PCIE_CONFIG_SPACE_SIZE : PCI_CONFIG_SPACE_SIZE;
> }
>
> +
> +/* These are pci express specific, so should belong to pcie.h.
> + they're here to avoid header inclusion error. */
What is 'header inclusion error' we are trying to avoid?
> +static inline uint8_t pci_pcie_cap(const PCIDevice *d)
> +{
> + return d->exp ? d->exp->exp_cap : 0;
> +}
> +
> +/* AER */
> +static inline uint16_t pcie_aer_cap(const PCIDevice *d)
> +{
> + assert(d->exp);
> + return d->exp->aer_cap;
> +}
> +
> +
> /* These are not pci specific. Should move into a separate header.
> * Only pci.c uses them, so keep them here for now.
> */
> diff --git a/hw/pcie.c b/hw/pcie.c
> new file mode 100644
> index 0000000..1f24c2a
> --- /dev/null
> +++ b/hw/pcie.c
> @@ -0,0 +1,1668 @@
> +/*
> + * pcie.c
> + *
> + * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
> + * VA Linux Systems Japan K.K.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "sysemu.h"
> +#include "pci_bridge.h"
> +#include "pcie.h"
> +#include "msix.h"
> +#include "msi.h"
> +#include "pci_internals.h"
> +
> +//#define DEBUG_PCIE
> +#ifdef DEBUG_PCIE
> +# define PCIE_DPRINTF(fmt, ...) \
> + fprintf(stderr, "%s:%d " fmt, __func__, __LINE__, ## __VA_ARGS__)
> +#else
> +# define PCIE_DPRINTF(fmt, ...) do {} while (0)
> +#endif
> +#define PCIE_DEV_PRINTF(dev, fmt, ...) \
> + PCIE_DPRINTF("%s:%x "fmt, (dev)->name, (dev)->devfn, ## __VA_ARGS__)
> +
> +static inline const char *pcie_hp_event_name(enum PCIExpressHotPlugEvent event)
> +{
> + switch (event) {
> + case PCI_EXP_HP_EV_ABP:
> + return "attention button pushed";
> + case PCI_EXP_HP_EV_PDC:
> + return "present detection changed";
> + case PCI_EXP_HP_EV_CCI:
> + return "command completed";
> + default:
> + break;
> + }
> + return "Unknown event";
> +}
> +
> +static void pcie_aer_clear_error(PCIDevice *dev);
> +static void pcie_aer_root_notify(PCIDevice *dev, bool trigger, int level);
> +static AER_ERR_MSG_RESULT
> +pcie_aer_errmsg_alldev(PCIDevice *dev, const struct pcie_aer_err_msg *msg);
> +static AER_ERR_MSG_RESULT
> +pcie_aer_errmsg_vbridge(PCIDevice *dev, const struct pcie_aer_err_msg *msg);
> +
> +/***************************************************************************
> + * pci express capability helper functions
> + */
> +
> +#define PCI_EXP_VER2_SIZEOF 0x3c /* express capability of version 2 */
> +
> +/* PCI_EXP_FLAGS */
> +#define PCI_EXP_FLAGS_VER2 2 /* for now, supports only version 2 */
> +#define PCI_EXP_FLAGS_IRQ_SHIFT 9
Please define this as (ffs(PCI_EXP_FLAGS_IRQ) - 1)
or open-code.
> +#define PCI_EXP_FLAGS_IRQ_REG(irq) (((irq) << PCI_EXP_FLAGS_IRQ_SHIFT) & PCI_EXP_FLAGS_IRQ)
line too long. Do we need the & PCI_EXP_FLAGS_IRQ?
I think it would be cleaner to open-code this.
> +#define PCI_EXP_FLAGS_TYPE_SHIFT 4
Please define this as (ffs(PCI_EXP_FLAGS_TYPE) - 1)
or open-code.
> +
> +/* PCI_EXP_LINK{CAP, STA} */
> +/* link speed */
> +#define PCI_EXP_LNK_LS_25 1
> +
> +#define PCI_EXP_LNK_MLW_SHIFT 4
> +#define PCI_EXP_LNK_MLW_1 (1 << PCI_EXP_LNK_MLW_SHIFT)
> +
> +/* PCI_EXP_LINKCAP */
> +#define PCI_EXP_LNKCAP_ASPMS_SHIFT 10
> +#define PCI_EXP_LNKCAP_ASPMS_0S (1 << PCI_EXP_LNKCAP_ASPMS_SHIFT)
> +
> +#define PCI_EXP_LNKCAP_PN_SHIFT 24
> +#define PCI_EXP_LNKCAP_PN_REG(pn) (((pn) << PCI_EXP_LNKCAP_PN_SHIFT) & PCI_EXP_LNKCAP_PN)
> +
> +#define PCI_EXP_SLTCAP_PSN_SHIFT 19
> +#define PCI_EXP_SLTCAP_PSN_REG(slot) (((slot) << PCI_EXP_SLTCAP_PSN_SHIFT) & PCI_EXP_SLTCAP_PSN)
> +
> +#define PCI_EXP_SLTCTL_AIC_SHIFT 6
> +#define PCI_EXP_SLTCTL_AIC_ON (PCI_EXP_HP_IND_ON << PCI_EXP_SLTCTL_AIC_SHIFT)
> +#define PCI_EXP_SLTCTL_AIC_BLINK (PCI_EXP_HP_IND_BLINK << PCI_EXP_SLTCTL_AIC_SHIFT)
> +#define PCI_EXP_SLTCTL_AIC_OFF (PCI_EXP_HP_IND_OFF << PCI_EXP_SLTCTL_AIC_SHIFT)
> +
> +#define PCI_EXP_SLTCTL_PIC_SHIFT 8
> +#define PCI_EXP_SLTCTL_PIC_ON (PCI_EXP_HP_IND_ON << PCI_EXP_SLTCTL_PIC_SHIFT)
> +#define PCI_EXP_SLTCTL_PIC_BLINK (PCI_EXP_HP_IND_BLINK << PCI_EXP_SLTCTL_PIC_SHIFT)
> +#define PCI_EXP_SLTCTL_PIC_OFF (PCI_EXP_HP_IND_OFF << PCI_EXP_SLTCTL_PIC_SHIFT)
> +
> +#define PCI_EXP_DEVCAP2_EFF 0x100000
> +#define PCI_EXP_DEVCAP2_EETLPP 0x200000
> +
> +#define PCI_EXP_DEVCTL2_EETLPPB 0x80
Many constants here to audit.
Let's define _SHIFT macros as ffs, _REG macros
don't really need the & masking, and when we
remove it it becomes small enough to open-code.
Also some macros seem unused?
> +
> +static void pcie_notify(PCIDevice *dev, uint16_t vector,
> + bool trigger, int level)
> +{
> + /* masking/masking interrupt is handled by upper layer.
> + * i.e. msix_notify() for MSI-X
> + * msi_notify() for MSI
> + * pci_set_irq() for INTx
> + */
So this will send another interrupt when level is 0?
> + PCIE_DEV_PRINTF(dev, "noitfy vector %d tirgger:%d level:%d\n",
> + vector, trigger, level);
> + if (msix_enabled(dev)) {
> + if (trigger) {
> + msix_notify(dev, vector);
> + }
> + } else if (msi_enabled(dev)) {
> + if (trigger){
> + msi_notify(dev, vector);
> + }
> + } else {
two spaces before {
> + qemu_set_irq(dev->irq[0], level);
> + }
> +}
> +
> +static inline uint32_t pcie_written_val_long(uint32_t addr, uint32_t val,
> + uint32_t pos)
> +{
> + if (addr >= pos) {
> + val <<= addr - pos;
> + } else {
> + val >>= pos - addr;
> + }
> + return val;
> +}
> +
Note that above is undefined if |pos - addr| > 32.
> +static inline uint16_t pcie_written_val_word(uint32_t addr, uint32_t val,
> + uint32_t pos)
> +{
> + return pcie_written_val_long(addr, val, pos) & 0xffff;
> +}
> +
& 0xffff is not needed here.
two functions above are not clearly named.
Thery have nothing to do with write: they just get
word from address/value pair.
> +/*
> + * RW1C: Write-1-to-clear
> + * regiger written val result
> + * 0 0 => 0
> + * 1 0 => 1
> + * 0 1 => 0
> + * 1 1 => 0
> + */
> +static inline void pcie_w1c_long(PCIDevice *d, uint32_t pos, uint32_t mask,
> + uint32_t addr, uint32_t val)
> +{
> + uint32_t written = pcie_written_val_long(addr, val, pos) & mask;
> + uint32_t reg = pci_get_long(d->config + pos);
> + reg &= ~written;
> + pci_set_long(d->config + pos, reg);
> +}
> +
> +static inline void pcie_w1c_word(PCIDevice *d, uint32_t pos, uint16_t mask,
> + uint32_t addr, uint32_t val)
> +{
> + uint16_t written = pcie_written_val_word(addr, val, pos) & mask;
> + uint16_t reg = pci_get_word(d->config + pos);
> + reg &= ~written;
> + pci_set_word(d->config + pos, reg);
> +}
> +
So the SERR bit support IMO belongs in pci. And this means the W1C
inline functions need to move there.
pci.c implemented this in a simpler way, by shifting
val by 8 bytes each time. Can we find a way to do it
in a similar way? I'll try to think about it.
> +int pci_pcie_cap_init(PCIDevice *dev,
> + uint8_t offset, uint8_t type, uint8_t port)
I think we should have
pcie_init
that would init everything and be external,
and this one should be static, and this one
should be static.
> +{
> + int exp_cap;
> + uint8_t *pcie_cap;
> +
> + assert(pci_is_express(dev));
> + dev->exp = qemu_mallocz(sizeof(*dev->exp));
> +
> + exp_cap = pci_add_capability(dev, PCI_CAP_ID_EXP, offset,
> + PCI_EXP_VER2_SIZEOF);
> + if (exp_cap < 0) {
> + qemu_free(dev->exp);
> + dev->exp = NULL;
> + return exp_cap;
> + }
> + dev->exp->exp_cap = exp_cap;
> + /* dev->cap_present |= QEMU_PCI_CAP_EXPRESS; */ /* already done in pci_qdev_init() */
> +
> + pcie_cap = dev->config + pci_pcie_cap(dev);
> +
> + /* capability register
> + interrupt message number defaults to 0 */
> + pci_set_word(pcie_cap + PCI_EXP_FLAGS,
> + ((type << PCI_EXP_FLAGS_TYPE_SHIFT) & PCI_EXP_FLAGS_TYPE) |
> + PCI_EXP_FLAGS_VER2);
> +
> + /* device capability register
> + * table 7-12:
> + * roll based error reporting bit must be set by all
> + * Functions conforming to the ECN, PCI Express Base
> + * Specification, Revision 1.1., or subsequent PCI Express Base
> + * Specification revisions.
> + */
> + pci_set_long(pcie_cap + PCI_EXP_DEVCAP, PCI_EXP_DEVCAP_RBER);
> +
> + pci_set_long(pcie_cap + PCI_EXP_LNKCAP,
> + PCI_EXP_LNKCAP_PN_REG(port) |
> + PCI_EXP_LNKCAP_ASPMS_0S |
> + PCI_EXP_LNK_MLW_1 |
> + PCI_EXP_LNK_LS_25);
> +
> + pci_set_word(pcie_cap + PCI_EXP_LNKSTA,
> + PCI_EXP_LNK_MLW_1 | PCI_EXP_LNK_LS_25);
> +
> + pci_set_long(pcie_cap + PCI_EXP_DEVCAP2,
> + PCI_EXP_DEVCAP2_EFF | PCI_EXP_DEVCAP2_EETLPP);
> +
> + pci_set_word(dev->wmask + exp_cap, PCI_EXP_DEVCTL2_EETLPPB);
> + return exp_cap;
> +}
> +
> +int pci_pcie_cap_exit(PCIDevice *dev)
> +{
> + /* pci_del_capability(dev, PCI_CAP_ID_EXP, PCI_EXP_VER2_SIZEOF); */
> + qemu_free(dev->exp);
> + return 0;
> +}
> +
> +uint8_t pcie_cap_get_type(const PCIDevice *dev)
> +{
> + uint32_t pos = pci_pcie_cap(dev);
> + assert(pos > 0);
> + return (pci_get_word(dev->config + pos + PCI_EXP_FLAGS) &
> + PCI_EXP_FLAGS_TYPE) >> PCI_EXP_FLAGS_TYPE_SHIFT;
> +}
> +
> +/* MSI/MSI-X */
> +/* pci express interrupt message number */
> +void pcie_cap_flags_set_vector(PCIDevice *dev, uint8_t vector)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + uint16_t tmp;
> +
> + assert(vector <= 32);
> + tmp = pci_get_word(pcie_cap + PCI_EXP_FLAGS);
> + tmp &= ~PCI_EXP_FLAGS_IRQ;
> + tmp |= PCI_EXP_FLAGS_IRQ_REG(vector);
> + pci_set_word(pcie_cap + PCI_EXP_FLAGS, tmp);
> +}
> +
> +uint8_t pcie_cap_flags_get_vector(PCIDevice *dev)
> +{
> + return (pci_get_word(dev->config + pci_pcie_cap(dev) + PCI_EXP_FLAGS) &
> + PCI_EXP_FLAGS_IRQ) >> PCI_EXP_FLAGS_IRQ_SHIFT;
> +}
> +
> +static void pcie_cap_notify(PCIDevice *dev, bool trigger, int level)
> +{
> + pcie_notify(dev, pcie_cap_flags_get_vector(dev), trigger, level);
> +}
> +
better opencoded.
> +void pcie_cap_deverr_init(PCIDevice *dev)
> +{
> + uint32_t pos = pci_pcie_cap(dev);
> + uint8_t *pcie_cap = dev->config + pos;
> + uint8_t *pcie_wmask = dev->wmask + pos;
> +
> + pci_set_long(pcie_cap + PCI_EXP_DEVCAP,
> + pci_get_long(pcie_cap + PCI_EXP_DEVCAP) |
> + PCI_EXP_DEVCAP_RBER);
> +
> + pci_set_long(pcie_wmask + PCI_EXP_DEVCTL,
> + pci_get_long(pcie_wmask + PCI_EXP_DEVCTL) |
> + PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE |
> + PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE);
> +}
> +
> +void pcie_cap_deverr_reset(PCIDevice *dev)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + pci_set_long(pcie_cap + PCI_EXP_DEVCTL,
> + pci_get_long(pcie_cap + PCI_EXP_DEVCTL) &
> + ~(PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE |
> + PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE));
> +}
> +
> +void pcie_cap_deverr_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len)
> +{
> + uint32_t pos = pci_pcie_cap(dev);
> + if (ranges_overlap(addr, len, pos + PCI_EXP_DEVSTA, 4)) {
> + /* RW1C */
> + pcie_w1c_long(dev, pos + PCI_EXP_DEVSTA,
> + PCI_EXP_DEVSTA_CED | PCI_EXP_DEVSTA_NFED |
> + PCI_EXP_DEVSTA_URD | PCI_EXP_DEVSTA_URD,
> + addr, val);
> + }
> +}
> +
> +/*
> + * events: PCI_EXP_HP_EV_xxx
> + * status: bit or of PCI_EXP_SLTSTA_xxx
> + */
> +static void pcie_cap_slot_event(PCIDevice *dev,
> + enum PCIExpressHotPlugEvent events,
> + uint16_t status)
> +{
> + bool trigger = false;
> + int level = 0;
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + uint16_t sltctl = pci_get_word(pcie_cap + PCI_EXP_SLTCTL);
> + uint16_t sltsta = pci_get_word(pcie_cap + PCI_EXP_SLTSTA);
> +
> + PCIE_DEV_PRINTF(dev,
> + "sltctl: 0x%0x2 sltsta: 0x%02x event:%x %s status:%d\n",
> + sltctl, sltsta,
> + events, pcie_hp_event_name(events), status);
> + events &= PCI_EXP_HP_EV_SUPPORTED;
> + if ((sltctl & PCI_EXP_SLTCTL_HPIE) && (sltctl & events) &&
> + ((sltsta ^ events) & events) /* 0 -> 1 */) {
> + trigger = true;
> + }
else trigger = false and do not initialize
> +
> + if (events & PCI_EXP_HP_EV_PDC) {
> + sltsta &= ~PCI_EXP_SLTSTA_PDS;
> + sltsta |= (status & PCI_EXP_SLTSTA_PDS);
> + }
> + sltsta |= events;
> + pci_set_word(pcie_cap + PCI_EXP_SLTSTA, sltsta);
> + PCIE_DEV_PRINTF(dev, "sltsta -> %02xn", sltsta);
> +
> + if ((sltctl & PCI_EXP_SLTCTL_HPIE) && (sltsta & PCI_EXP_HP_EV_SUPPORTED)) {
> + level = 1;
> + }
else level = 0
and don't initialize would be clearer.
> +
> + pcie_cap_notify(dev, trigger, level);
> +}
> +
> +static int pcie_cap_slot_hotplug(DeviceState *qdev,
> + PCIDevice *pci_dev, int state)
> +{
> + PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev);
> + uint8_t *pcie_cap = d->config + pci_pcie_cap(d);
> + uint16_t sltsta = pci_get_word(pcie_cap + PCI_EXP_SLTSTA);
> +
> + if (!pci_dev->qdev.hotplugged) {
> + assert(state); /* this case only happens machine creation. */
> + sltsta |= PCI_EXP_SLTSTA_PDS;
> + pci_set_word(pcie_cap + PCI_EXP_SLTSTA, sltsta);
> + return 0;
> + }
> +
> + PCIE_DEV_PRINTF(pci_dev, "hotplug state: %d\n", state);
> + if (sltsta & PCI_EXP_SLTSTA_EIS) {
> + /* the slot is electromechanically locked. */
> + return -EBUSY;
> + }
> +
> + if (state) {
> + if (PCI_FUNC(pci_dev->devfn) == 0) {
> + /* event is per slot. Not per function
> + * only generates event for function = 0.
> + * When hot plug, populate functions > 0
> + * and then add function = 0 last.
> + */
> + pcie_cap_slot_event(d, PCI_EXP_HP_EV_PDC, PCI_EXP_SLTSTA_PDS);
> + }
> + } else {
> + PCIBridge *br;
> + PCIBus *bus;
> + DeviceState *next;
> + if (PCI_FUNC(pci_dev->devfn) != 0) {
> + /* event is per slot. Not per function.
> + accepts function = 0 only. */
> + return -EINVAL;
> + }
> +
> + /* zap all functions. */
> + br = DO_UPCAST(PCIBridge, dev, d);
> + bus = pci_bridge_get_sec_bus(br);
> + QLIST_FOREACH_SAFE(qdev, &bus->qbus.children, sibling, next) {
> + qdev_free(qdev);
> + }
> +
> + pcie_cap_slot_event(d, PCI_EXP_HP_EV_PDC, 0);
> + }
> + return 0;
> +}
> +
> +/* pci express slot for pci express root/downstream port
> + PCI express capability slot registers */
> +void pcie_cap_slot_init(PCIDevice *dev, uint16_t slot)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + uint8_t *pcie_wmask = dev->wmask + pci_pcie_cap(dev);
> + uint32_t tmp;
> +
> + pci_set_word(pcie_cap + PCI_EXP_FLAGS,
> + pci_get_word(pcie_cap + PCI_EXP_FLAGS) | PCI_EXP_FLAGS_SLOT);
> +
> + tmp = pci_get_long(pcie_cap + PCI_EXP_SLTCAP);
> + tmp &= PCI_EXP_SLTCAP_PSN;
> + tmp |=
> + PCI_EXP_SLTCAP_PSN_REG(slot) |
> + PCI_EXP_SLTCAP_EIP |
> + PCI_EXP_SLTCAP_HPS |
> + PCI_EXP_SLTCAP_HPC |
> + PCI_EXP_SLTCAP_PIP |
> + PCI_EXP_SLTCAP_AIP |
> + PCI_EXP_SLTCAP_ABP;
> + pci_set_long(pcie_cap + PCI_EXP_SLTCAP, tmp);
> +
> + tmp = pci_get_word(pcie_cap + PCI_EXP_SLTCTL);
> + tmp &= ~(PCI_EXP_SLTCTL_PIC | PCI_EXP_SLTCTL_AIC);
> + tmp |= PCI_EXP_SLTCTL_PIC_OFF | PCI_EXP_SLTCTL_AIC_OFF;
> + pci_set_word(pcie_cap + PCI_EXP_SLTCTL, tmp);
> + pci_set_word(pcie_wmask + PCI_EXP_SLTCTL,
> + pci_get_word(pcie_wmask + PCI_EXP_SLTCTL) |
> + PCI_EXP_SLTCTL_PIC |
> + PCI_EXP_SLTCTL_AIC |
> + PCI_EXP_SLTCTL_HPIE |
> + PCI_EXP_SLTCTL_CCIE |
> + PCI_EXP_SLTCTL_PDCE |
> + PCI_EXP_SLTCTL_ABPE);
> +
> + pci_bus_hotplug(pci_bridge_get_sec_bus(DO_UPCAST(PCIBridge, dev, dev)),
> + pcie_cap_slot_hotplug, &dev->qdev);
> +}
> +
> +void pcie_cap_slot_reset(PCIDevice *dev)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + uint32_t tmp;
> +
> + PCIE_DEV_PRINTF(dev, "reset\n");
> +
> + tmp = pci_get_word(pcie_cap + PCI_EXP_SLTCTL);
> + tmp &= ~(PCI_EXP_SLTCTL_EIC |
> + PCI_EXP_SLTCTL_PIC |
> + PCI_EXP_SLTCTL_AIC |
> + PCI_EXP_SLTCTL_HPIE |
> + PCI_EXP_SLTCTL_CCIE |
> + PCI_EXP_SLTCTL_PDCE |
> + PCI_EXP_SLTCTL_ABPE);
> + tmp |= PCI_EXP_SLTCTL_PIC_OFF | PCI_EXP_SLTCTL_AIC_OFF;
> + pci_set_word(pcie_cap + PCI_EXP_SLTCTL, tmp);
> +
> + tmp = pci_get_word(pcie_cap + PCI_EXP_SLTSTA);
> + tmp &= ~(PCI_EXP_SLTSTA_EIS | /* by reset, the lock is released */
> + PCI_EXP_SLTSTA_CC |
> + PCI_EXP_SLTSTA_PDC |
> + PCI_EXP_SLTSTA_ABP);
> + pci_set_word(pcie_cap + PCI_EXP_SLTSTA, tmp);
> +}
> +
> +void pcie_cap_slot_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len,
> + uint16_t sltctl_prev)
> +{
> + uint32_t pos = pci_pcie_cap(dev);
> + uint8_t *pcie_cap = dev->config + pos;
> + uint16_t sltctl = pci_get_word(pcie_cap + PCI_EXP_SLTCTL);
> + uint16_t sltsta = pci_get_word(pcie_cap + PCI_EXP_SLTSTA);
> +
> + PCIE_DEV_PRINTF(dev,
> + "addr: 0x%x val: 0x%x len: %d\n"
> + "\tsltctl_prev: 0x%02x sltctl: 0x%02x sltsta 0x%02x\n",
> + addr, val, len, sltctl_prev, sltctl, sltsta);
> + /* SLTSTA: process SLTSTA before SLTCTL to avoid spurious interrupt */
> + if (ranges_overlap(addr, len, pos + PCI_EXP_SLTSTA, 2)) {
> + /* RW1C */
> + pcie_w1c_word(dev, pos + PCI_EXP_SLTSTA, PCI_EXP_HP_EV_SUPPORTED,
> + addr, val);
> + sltsta = pci_get_word(pcie_cap + PCI_EXP_SLTSTA);
> +
> + /* write to stlsta results in clearing bits,
> + so new interrupts won't be generated. */
> + PCIE_DEV_PRINTF(dev, "sltsta -> 0x%02x\n", sltsta);
> + }
> +
> + /* SLTCTL */
> + if (ranges_overlap(addr, len, pos + PCI_EXP_SLTCTL, 2)) {
> + PCIE_DEV_PRINTF(dev, "sltctl: 0x%02x -> 0x%02x\n",
> + sltctl_prev, sltctl);
> + if (pcie_written_val_word(addr, val, pos + PCI_EXP_SLTCTL) &
> + PCI_EXP_SLTCTL_EIC) {
> + /* toggle PCI_EXP_SLTSTA_EIS */
> + sltsta = (sltsta & ~PCI_EXP_SLTSTA_EIS) |
> + ((sltsta ^ PCI_EXP_SLTSTA_EIS) & PCI_EXP_SLTSTA_EIS);
> + pci_set_word(pcie_cap + PCI_EXP_SLTSTA, sltsta);
> + PCIE_DEV_PRINTF(dev, "PCI_EXP_SLTCTL_EIC: sltsta -> 0x%02x\n",
> + sltsta);
> + }
> +
> + if (sltctl & PCI_EXP_SLTCTL_HPIE) {
> + bool trigger = false;
> + int level = 0;
> +
> + if (((sltctl_prev ^ sltctl) & sltctl) & PCI_EXP_HP_EV_SUPPORTED) {
> + /* 0 -> 1 */
> + trigger = true;
> + }
else trigger = false and do not initialize.
> + if ((sltctl & sltsta) & PCI_EXP_HP_EV_SUPPORTED) {
> + level = 1;
> + }
else level = 0 and avoid init would be clearer
> + pcie_cap_notify(dev, trigger, level);
> + }
> +
> + /* command completed.
> + unlike real hardware, command completes instantaneously */
> +#define PCI_EXP_SLTCTL_SUPPORTED \
> + (PCI_EXP_SLTCTL_ABPE | \
> + PCI_EXP_SLTCTL_PDCE | \
> + PCI_EXP_SLTCTL_CCIE | \
> + PCI_EXP_SLTCTL_HPIE | \
> + PCI_EXP_SLTCTL_AIC | \
> + PCI_EXP_SLTCTL_PCC | \
> + PCI_EXP_SLTCTL_EIC)
> + if ( 1 /* (sltctl_prev ^ sltctl) & PCI_EXP_SLTCTL_SUPPORTED */ ) {
what does the above comment mean?
> + /* set command completed bit */
> + pcie_cap_slot_event(dev, PCI_EXP_HP_EV_CCI, 0);
> + }
> + }
> +}
> +
> +void pcie_cap_slot_push_attention_button(PCIDevice *dev)
> +{
> + pcie_cap_slot_event(dev, PCI_EXP_HP_EV_ABP, 0);
> +}
> +
> +/* root control/capabilities/status. PME isn't emulated for now */
> +void pcie_cap_root_init(PCIDevice *dev)
> +{
> + uint8_t pos = pci_pcie_cap(dev);
> + pci_set_word(dev->wmask + pos + PCI_EXP_RTCTL,
> + PCI_EXP_RTCTL_SECEE | PCI_EXP_RTCTL_SENFEE |
> + PCI_EXP_RTCTL_SEFEE);
> +}
> +
> +void pcie_cap_root_reset(PCIDevice *dev)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + pci_set_word(pcie_cap + PCI_EXP_RTCTL, 0);
> +}
> +
> +/* function level reset(FLR) */
> +void pcie_cap_flr_init(PCIDevice *dev, pcie_flr_fn flr)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + pci_set_word(pcie_cap + PCI_EXP_DEVCAP,
> + pci_get_word(pcie_cap + PCI_EXP_DEVCAP) | PCI_EXP_DEVCAP_FLR);
> + dev->exp->flr = flr;
> +}
> +
> +void pcie_cap_flr_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len)
> +{
> + uint32_t pos = pci_pcie_cap(dev);
> + if (ranges_overlap(addr, len, pos + PCI_EXP_DEVCTL, 2)) {
> + uint16_t val16 = pcie_written_val_word(addr, val,
> + pos + PCI_EXP_DEVCTL);
> + if ((val16 & PCI_EXP_DEVCTL_BCR_FLR) && dev->exp->flr) {
> + dev->exp->flr(dev);
> + }
> + }
> +}
> +
> +
> +/* Alternative Routing-ID Interpretation (ARI) */
> +/* ari forwarding support for down stream port */
> +void pcie_cap_ari_init(PCIDevice *dev)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + uint8_t *pcie_wmask = dev->wmask + pci_pcie_cap(dev);
> +
> + pci_set_long(pcie_cap + PCI_EXP_DEVCAP2,
> + pci_get_long(pcie_cap + PCI_EXP_DEVCAP2) |
> + PCI_EXP_DEVCAP2_ARI);
> +
> + pci_set_long(pcie_wmask + PCI_EXP_DEVCTL2,
> + pci_get_long(pcie_wmask + PCI_EXP_DEVCTL2) |
> + PCI_EXP_DEVCTL2_ARI);
> +}
> +
> +void pcie_cap_ari_reset(PCIDevice *dev)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> +
> + pci_set_long(pcie_cap + PCI_EXP_DEVCTL2,
> + pci_get_long(pcie_cap + PCI_EXP_DEVCTL2) &
> + ~PCI_EXP_DEVCTL2_ARI);
> +}
> +
> +bool pcie_cap_is_ari_enabled(const PCIDevice *dev)
> +{
> + if (!pci_is_express(dev)) {
> + return false;
> + }
> + if (!pci_pcie_cap(dev)) {
> + return false;
> + }
> +
> + return pci_get_long(dev->config + pci_pcie_cap(dev) + PCI_EXP_DEVCTL2) &
> + PCI_EXP_DEVCTL2_ARI;
> +}
> +
> +/**************************************************************************
> + * pci express extended capability allocation functions
> + * uint16_t ext_cap_id (16 bit)
> + * uint8_t cap_ver (4 bit)
> + * uint16_t cap_offset (12 bit)
> + * uint16_t ext_cap_size
> + */
> +
> +#define PCI_EXT_CAP_VER_SHIFT 16
> +#define PCI_EXT_CAP_NEXT_MASK 0xfff00000
Should be 0xffc << PCI_EXT_CAP_NEXT_SHIFT ?
Pls add a TODO to try and add this to pci_regs.h in linux.
> +#define PCI_EXT_CAP_NEXT_SHIFT 20
> +
> +#define PCI_EXT_CAP(id, ver, next) ((id) | ((ver) << PCI_EXT_CAP_VER_SHIFT) | ((next) << PCI_EXT_CAP_NEXT_SHIFT))
line too long. I think this will cause integer overflow
if next is uint16_t and has high bit set.
Better make this an inline function with uint32_t arguments.
> +
> +#define PCI_EXT_CAP_ALIGN 4
> +#define PCI_EXT_CAP_ALIGNUP(x) (((x) + PCI_EXT_CAP_ALIGN - 1) & ~(PCI_EXT_CAP_ALIGN - 1))
line too long
> +
> +static int16_t pcie_ext_cap_find_space(PCIDevice *dev, uint16_t size)
> +{
> + uint16_t offset = PCI_CONFIG_SPACE_SIZE;
> + uint16_t i = offset;
> +
> + while (i < PCIE_CONFIG_SPACE_SIZE - size) {
> + if (dev->used[i]) {
> + offset = PCI_EXT_CAP_ALIGNUP(i + 1);
> + i = offset;
> + continue;
> + } else if (i - offset + 1 == size) {
> + return offset;
> + }
> +
> + ++i;
> + }
> +
> + return 0;
> +}
> +
So here, is this allocator worth it?
Migration requires that offsets never change, so
let's precompute them and pass to
> +static uint16_t pcie_find_ext_capability_list(PCIDevice *dev, uint16_t cap_id,
> + uint16_t *prev_p)
Maybe rename
static uint16_t pcie_find_capability_list(PCIDevice *dev, uint16_t cap_id,
it is clear it's extended as pci_ one does regular caps.
> +{
> + int ttl;
> +
> + uint16_t prev = 0;
> + uint16_t next = PCI_CONFIG_SPACE_SIZE;
> + uint32_t header = pci_get_long(dev->config + next);
> +
> + if (!header) {
> + return 0;
> + }
> +
> + /* minimum 8 bytes per capability */
> + ttl = (PCIE_CONFIG_SPACE_SIZE - PCI_CONFIG_SPACE_SIZE) / 8;
> +
> + while (ttl-- > 0) {
> + if (PCI_EXT_CAP_ID(header) == cap_id) {
> + break;
> + }
> +
> + prev = next;
> + next = PCI_EXT_CAP_NEXT(header);
> + if (next < PCI_CONFIG_SPACE_SIZE) {
> + return 0;
> + }
> + header = pci_get_long(dev->config + prev);
> + }
> +
> + if (!ttl) {
> + return 0;
> + }
> + if (prev_p) {
> + *prev_p = prev;
> + }
> + return next;
> +}
I don't yet understand this code.
This seems to do in 38 lines what the pci.c equivalent did in 18?
What eactly is ttl? Why do we need assumptions on capability size?
The following is a copy from pci.c, with PCI macros replaced by PCI_EXT
ones. Would this work? If not what did I miss?
static uint8_t pcie_find_capability_list(PCIDevice *pdev, uint8_t cap_id,
uint8_t *prev_p)
{
uint16_t next, prev;
for (prev = PCI_CONFIG_SPACE_SIZE;
next = PCI_EXT_CAP_NEXT(pci_get_long(pdev->config + prev));
prev = next)
if (PCI_EXT_CAP_ID(pci_get_long(pdev->config + next) == cap_id))
break;
if (prev_p)
*prev_p = prev;
return next;
}
> +uint16_t pcie_find_ext_capability(PCIDevice *dev, uint16_t cap_id)
> +{
> + return pcie_find_ext_capability_list(dev, cap_id, NULL);
> +}
> +
> +static void pcie_ext_cap_set_next(PCIDevice *dev, uint16_t pos, uint16_t next)
> +{
> + uint16_t header = pci_get_long(dev->config + pos);
> + assert(!(next & (PCI_EXT_CAP_ALIGN - 1)));
> + header = (header & ~PCI_EXT_CAP_NEXT_MASK) |
> + ((next << PCI_EXT_CAP_NEXT_SHIFT) & PCI_EXT_CAP_NEXT_MASK);
> + pci_set_long(dev->config + pos, header);
> +}
> +
> +static void pcie_allocate_ext_capability(PCIDevice *dev,
> + uint16_t cap_id, uint8_t cap_ver,
> + uint16_t offset, uint16_t size)
> +{
> + uint32_t header;
> + uint16_t next;
> +
> + assert(offset < offset + size);
> + assert(offset + size < PCIE_CONFIG_SPACE_SIZE);
> + assert(size >= 8);
> +
> + if (offset == PCI_CONFIG_SPACE_SIZE) {
Let's make offset == 0 have special meaning, same as pci please.
> + header = pci_get_long(dev->config + offset);
> + next = PCI_EXT_CAP_NEXT(header);
> + } else {
I think we can reuse the find_list routine (e.g. pass 0 as cap_id)?
> + /* find last ext cap */
> + int ttl = (PCIE_CONFIG_SPACE_SIZE - PCI_CONFIG_SPACE_SIZE) / 8;
> + uint16_t pos = PCI_CONFIG_SPACE_SIZE;
> + while (ttl-- > 0) {
> + header = pci_get_long(dev->config + pos);
> + if (PCI_EXT_CAP_NEXT(header) < PCI_CONFIG_SPACE_SIZE) {
> + break;
> + }
> +
> + pos = PCI_EXT_CAP_NEXT(header);
> + }
> +
> + assert(ttl > 0); /* since it is known that [offset, offset + size]
> + is unused, so ttl shouldn't be zero */
> + pcie_ext_cap_set_next(dev, pos, offset);
> + next = 0;
> + }
> + pci_set_long(dev->config + offset, PCI_EXT_CAP(cap_id, cap_ver, next));
> +
> + memset(dev->used + offset, 0xFF, size);
> + /* Make capability read-only by default */
> + memset(dev->wmask + offset, 0, size);
> + /* Check capability by default */
> + memset(dev->cmask + offset, 0xFF, size);
> +}
> +
> +int pcie_add_ext_capability(PCIDevice *dev,
> + uint16_t cap_id, uint8_t cap_ver, uint16_t size)
this should be static
> +{
> + uint16_t offset = pcie_ext_cap_find_space(dev, size);
> +
> + if (!offset) {
> + return -ENOSPC;
> + }
> +
> + pcie_allocate_ext_capability(dev, cap_id, cap_ver, offset, size);
> + return offset;
> +}
> +
> +int pcie_append_ext_capability(PCIDevice *dev,
> + uint16_t cap_id, uint8_t cap_ver,
> + uint16_t offset, uint16_t size)
rename this pcie_add_capability
> +{
> + uint16_t i;
> +
> + if (!offset) {
> + return pcie_add_ext_capability(dev, cap_id, cap_ver, size);
open-code this call and remove pcie_add_ext_capability function.
> + }
> +
> + assert(offset < offset + size);
> + assert(offset + size < PCIE_CONFIG_SPACE_SIZE);
> + assert(size >= 8);
> +
> + for (i = offset; i < offset + size; ++i) {
> + if (dev->used[i]) {
> + return -EBUSY;
> + }
> + }
> +
> + pcie_allocate_ext_capability(dev, cap_id, cap_ver, offset, size);
> + return offset;
> +}
> +
> +void pcie_del_ext_capability(PCIDevice *dev, uint16_t cap_id, uint16_t size)
rename pcie_del_capability
> +{
> + uint16_t prev;
> + uint16_t offset = pcie_find_ext_capability_list(dev, cap_id, &prev);
> + uint32_t header;
> +
> + if (!offset) {
> + return;
> + }
> +
> + header = pci_get_long(dev->config + offset);
> + if (prev) {
> + pcie_ext_cap_set_next(dev, prev, PCI_EXT_CAP_NEXT(header));
> + } else {
> + /* move up next ext cap to PCI_CONFIG_SPACE_SIZE? */
> + assert(offset == PCI_CONFIG_SPACE_SIZE);
> + pci_set_long(dev->config + offset,
> + PCI_EXT_CAP(0, 0, PCI_EXT_CAP_NEXT(header)));
> + }
> +
> + /* Make capability writeable again */
> + memset(dev->wmask + offset, 0xff, size);
> + /* Clear cmask as device-specific registers can't be checked */
> + memset(dev->cmask + offset, 0, size);
> + memset(dev->used + offset, 0, size);
> +}
> +
> +void pcie_reserve_ext_capability(PCIDevice *dev,
> + uint16_t offset, uint16_t size)
> +{
> + memset(dev->used + offset, 0xff, size);
> +}
> +
> +/**************************************************************************
> + * pci express extended capability helper functions
> + */
Split out ari.c and aer.c? Just a thought ...
> +/* ARI */
> +#define PCI_ARI_VER 1
> +#define PCI_ARI_SIZEOF 8
> +
> +int pcie_ari_init(PCIDevice *dev, uint16_t offset, uint16_t nextfn)
> +{
> + int pos;
> + pos = pcie_append_ext_capability(dev, PCI_EXT_CAP_ID_ARI, PCI_ARI_VER,
> + offset, PCI_ARI_SIZEOF);
> + if (pos < 0) {
> + return pos;
> + }
> +
> + pci_set_long(dev->config + pos + PCI_ARI_CAP, PCI_ARI_CAP_NFN(nextfn));
> + return pos;
> +}
> +
> +/* AER */
> +#define PCI_ERR_VER 2
> +#define PCI_ERR_SIZEOF 0x48
> +
> +#define PCI_ERR_UNC_SDN 0x00000020 /* surprise down */
> +#define PCI_ERR_UNC_ACSV 0x00200000 /* ACS Violation */
> +#define PCI_ERR_UNC_INTN 0x00400000 /* Internal Error */
> +#define PCI_ERR_UNC_MCBTLP 0x00800000 /* MC Blcoked TLP */
> +#define PCI_ERR_UNC_ATOP_EBLOCKED 0x01000000 /* atomic op egress blocked */
> +#define PCI_ERR_UNC_TLP_PRF_BLOCKED 0x02000000 /* TLP Prefix Blocked */
> +#define PCI_ERR_UNC_SUPPORTED (PCI_ERR_UNC_DLP | \
> + PCI_ERR_UNC_SDN | \
> + PCI_ERR_UNC_POISON_TLP | \
> + PCI_ERR_UNC_FCP | \
> + PCI_ERR_UNC_COMP_TIME | \
> + PCI_ERR_UNC_COMP_ABORT | \
> + PCI_ERR_UNC_UNX_COMP | \
> + PCI_ERR_UNC_RX_OVER | \
> + PCI_ERR_UNC_MALF_TLP | \
> + PCI_ERR_UNC_ECRC | \
> + PCI_ERR_UNC_UNSUP | \
> + PCI_ERR_UNC_ACSV | \
> + PCI_ERR_UNC_INTN | \
> + PCI_ERR_UNC_MCBTLP | \
> + PCI_ERR_UNC_ATOP_EBLOCKED | \
> + PCI_ERR_UNC_TLP_PRF_BLOCKED)
> +
> +#define PCI_ERR_UNC_SEVERITY_DEFAULT (PCI_ERR_UNC_DLP | \
> + PCI_ERR_UNC_SDN | \
> + PCI_ERR_UNC_FCP | \
> + PCI_ERR_UNC_RX_OVER | \
> + PCI_ERR_UNC_MALF_TLP | \
> + PCI_ERR_UNC_INTN)
> +
> +#define PCI_ERR_COR_ADV_NONFATAL 0x00002000 /* Advisory Non-Fatal */
> +#define PCI_ERR_COR_INTERNAL 0x00004000 /* Corrected Internal */
> +#define PCI_ERR_COR_HL_OVERFLOW 0x00008000 /* Header Long Overflow */
> +#define PCI_ERR_COR_SUPPORTED (PCI_ERR_COR_RCVR | \
> + PCI_ERR_COR_BAD_TLP | \
> + PCI_ERR_COR_BAD_DLLP | \
> + PCI_ERR_COR_REP_ROLL | \
> + PCI_ERR_COR_REP_TIMER | \
> + PCI_ERR_COR_ADV_NONFATAL | \
> + PCI_ERR_COR_INTERNAL | \
> + PCI_ERR_COR_HL_OVERFLOW)
> +#define PCI_ERR_COR_MASK_DEFAULT (PCI_ERR_COR_ADV_NONFATAL | \
> + PCI_ERR_COR_INTERNAL | \
> + PCI_ERR_COR_HL_OVERFLOW)
> +
> +
> +#define PCI_ERR_CAP_FEP_MASK 0x0000001f
> +#define PCI_ERR_CAP_MHRC 0x00000200
> +#define PCI_ERR_CAP_MHRE 0x00000400
> +#define PCI_ERR_CAP_TLP 0x00000800
> +
> +#define PCI_ERR_TLP_PREFIX_LOG 0x38
> +
> +/* From 6.2.7 Error Listing and Rules. Table 6-2, 6-3 and 6-4 */
> +static enum PCIE_AER_SEVERITY pcie_aer_uncor_default_severity(uint32_t status)
> +{
> + switch (status) {
> + case PCI_ERR_UNC_INTN:
> + case PCI_ERR_UNC_DLP:
> + case PCI_ERR_UNC_SDN:
> + case PCI_ERR_UNC_RX_OVER:
> + case PCI_ERR_UNC_FCP:
> + case PCI_ERR_UNC_MALF_TLP:
> + return AER_ERR_FATAL;
> + case PCI_ERR_UNC_POISON_TLP:
> + case PCI_ERR_UNC_ECRC:
> + case PCI_ERR_UNC_UNSUP:
> + case PCI_ERR_UNC_COMP_TIME:
> + case PCI_ERR_UNC_COMP_ABORT:
> + case PCI_ERR_UNC_UNX_COMP:
> + case PCI_ERR_UNC_ACSV:
> + case PCI_ERR_UNC_MCBTLP:
> + case PCI_ERR_UNC_ATOP_EBLOCKED:
> + case PCI_ERR_UNC_TLP_PRF_BLOCKED:
> + return AER_ERR_NONFATAL;
> + default:
> + break;
> + }
> + abort();
> + return AER_ERR_FATAL;
> +}
> +
> +static uint32_t pcie_aer_log_next(uint32_t i, uint32_t max)
> +{
> + return (i + 1) % max;
> +}
> +
> +static bool pcie_aer_log_empty_index(uint32_t producer, uint32_t consumer)
> +{
> + return producer == consumer;
> +}
> +
> +static bool pcie_aer_log_empty(struct pcie_aer_log *aer_log)
> +{
> + return pcie_aer_log_empty_index(aer_log->producer, aer_log->consumer);
> +}
> +
> +static bool pcie_aer_log_full(struct pcie_aer_log *aer_log)
> +{
> + return pcie_aer_log_next(aer_log->producer, aer_log->log_max) ==
> + aer_log->consumer;
> +}
> +
> +static uint32_t pcie_aer_log_add(struct pcie_aer_log *aer_log)
> +{
> + uint32_t i = aer_log->producer;
> + aer_log->producer = pcie_aer_log_next(aer_log->producer, aer_log->log_max);
> + return i;
> +}
> +
> +static uint32_t pcie_aer_log_del(struct pcie_aer_log *aer_log)
> +{
> + uint32_t i = aer_log->consumer;
> + aer_log->consumer = pcie_aer_log_next(aer_log->consumer, aer_log->log_max);
> + return i;
> +}
> +
> +static int pcie_aer_log_add_err(struct pcie_aer_log *aer_log,
> + const struct pcie_aer_err *err)
> +{
> + uint32_t i;
> + if (pcie_aer_log_full(aer_log)) {
> + return -1;
> + }
> + i = pcie_aer_log_add(aer_log);
> + memcpy(&aer_log->log[i], err, sizeof(*err));
> + return 0;
> +}
> +
> +static const struct pcie_aer_err*
> +pcie_aer_log_del_err(struct pcie_aer_log *aer_log)
> +{
> + uint32_t i;
> + assert(!pcie_aer_log_empty(aer_log));
> + i = pcie_aer_log_del(aer_log);
> + return &aer_log->log[i];
> +}
> +
> +static void pcie_aer_log_clear_all_err(struct pcie_aer_log *aer_log)
> +{
> + aer_log->producer = 0;
> + aer_log->consumer = 0;
> +}
> +
> +int pcie_aer_init(PCIDevice *dev, uint16_t offset)
> +{
> + int pos;
> + PCIExpressDevice *exp;
> +
> + pci_set_word(dev->wmask + PCI_COMMAND,
> + pci_get_word(dev->wmask + PCI_COMMAND) | PCI_COMMAND_SERR);
> +
> + pos = pcie_append_ext_capability(dev, PCI_EXT_CAP_ID_ERR, PCI_ERR_VER,
> + offset, PCI_ERR_SIZEOF);
> + if (pos < 0) {
> + return pos;
> + }
> + exp = dev->exp;
> + exp->aer_cap = pos;
> + if (dev->aer_log.log_max == PCIE_AER_LOG_MAX_UNSET) {
> + dev->aer_log.log_max = PCIE_AER_LOG_MAX_DEFAULT;
> + }
> + if (dev->aer_log.log_max > PCIE_AER_LOG_MAX_MAX) {
> + dev->aer_log.log_max = PCIE_AER_LOG_MAX_MAX;
> + }
> + dev->aer_log.log =
> + qemu_mallocz(sizeof(dev->aer_log.log[0]) * dev->aer_log.log_max);
> +
> + pci_set_long(dev->wmask + pos + PCI_ERR_UNCOR_MASK,
> + PCI_ERR_UNC_SUPPORTED);
> +
> + pci_set_long(dev->config + pos + PCI_ERR_UNCOR_SEVER,
> + PCI_ERR_UNC_SEVERITY_DEFAULT);
> + pci_set_long(dev->wmask + pos + PCI_ERR_UNCOR_SEVER,
> + PCI_ERR_UNC_SUPPORTED);
> +
> + pci_set_long(dev->config + pos + PCI_ERR_COR_MASK,
> + PCI_ERR_COR_MASK_DEFAULT);
> + pci_set_long(dev->wmask + pos + PCI_ERR_COR_MASK,
> + PCI_ERR_COR_SUPPORTED);
> +
> + /* capabilities and control. multiple header logging is supported */
> + if (dev->aer_log.log_max > 0) {
> + pci_set_long(dev->config + pos + PCI_ERR_CAP,
> + PCI_ERR_CAP_ECRC_GENC | PCI_ERR_CAP_ECRC_CHKC |
> + PCI_ERR_CAP_MHRC);
> + pci_set_long(dev->wmask + pos + PCI_ERR_CAP,
> + PCI_ERR_CAP_ECRC_GENE | PCI_ERR_CAP_ECRC_CHKE |
> + PCI_ERR_CAP_MHRE);
> + } else {
> + pci_set_long(dev->config + pos + PCI_ERR_CAP,
> + PCI_ERR_CAP_ECRC_GENC | PCI_ERR_CAP_ECRC_CHKC);
> + pci_set_long(dev->wmask + pos + PCI_ERR_CAP,
> + PCI_ERR_CAP_ECRC_GENE | PCI_ERR_CAP_ECRC_CHKE);
> + }
> +
> + switch (pcie_cap_get_type(dev)) {
> + case PCI_EXP_TYPE_ROOT_PORT:
> + /* this case will be set by pcie_aer_root_init() */
> + /* fallthrough */
> + case PCI_EXP_TYPE_DOWNSTREAM:
> + case PCI_EXP_TYPE_UPSTREAM:
> + pci_set_word(dev->wmask + PCI_BRIDGE_CONTROL,
> + pci_get_word(dev->wmask + PCI_BRIDGE_CONTROL) |
> + PCI_BRIDGE_CTL_SERR);
> + exp->aer_errmsg = pcie_aer_errmsg_vbridge;
> + break;
> + default:
> + exp->aer_errmsg = pcie_aer_errmsg_alldev;
> + break;
> + }
> + return pos;
> +}
> +
> +void pcie_aer_exit(PCIDevice *dev)
> +{
> + pci_del_capability(dev, PCI_EXT_CAP_ID_ERR, PCI_ERR_SIZEOF);
> + qemu_free(dev->aer_log.log);
> +}
> +
> +/* Multiple Header recording isn't implemented. Is it wanted? */
> +void pcie_aer_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len)
> +{
> + uint32_t pos = dev->exp->aer_cap;
> +
> + /* PCI_STATUS_SIG_SYSTEM_ERROR */
> + if (ranges_overlap(addr, len, PCI_STATUS, 2)) {
> + pcie_w1c_word(dev, PCI_STATUS, PCI_STATUS_SIG_SYSTEM_ERROR, addr, val);
> + }
> +
> + /* uncorrectable */
> + if (ranges_overlap(addr, len, pos + PCI_ERR_UNCOR_STATUS, 4)) {
> + uint32_t written =
> + pcie_written_val_long(addr, val, pos + PCI_ERR_UNCOR_STATUS) &
> + PCI_ERR_UNC_SUPPORTED;
> + uint32_t uncorsta =
> + pci_get_long(dev->config + pos + PCI_ERR_UNCOR_STATUS);
> + uint32_t errcap = pci_get_long(dev->config + pos + PCI_ERR_CAP);
> + uint32_t first_error = (1 << PCI_ERR_CAP_FEP(errcap));
> +
> + if ((uncorsta & first_error) && (written & first_error)) {
> + pcie_aer_clear_error(dev);
> + }
> + if (!(errcap & PCI_ERR_CAP_MHRE)) {
> + /* RW1CS */
> + pcie_w1c_long(dev, pos + PCI_ERR_UNCOR_STATUS,
> + PCI_ERR_UNC_SUPPORTED, addr, val);
> + }
> + }
> +
> + /* correctable */
> + if (ranges_overlap(addr, len, pos + PCI_ERR_COR_STATUS, 4)) {
> + /* RW1CS */
> + pcie_w1c_long(dev, pos + PCI_ERR_COR_STATUS, PCI_ERR_COR_SUPPORTED,
> + addr, val);
> + }
> +
> + /* capability & control */
> + if (ranges_overlap(addr, len, pos + PCI_ERR_CAP, 4)) {
> + uint32_t err_cap = pci_get_long(dev->config + pos + PCI_ERR_CAP);
> + if (!(err_cap & PCI_ERR_CAP_MHRE)) {
> + pcie_aer_log_clear_all_err(&dev->aer_log);
> + }
> + }
> +}
> +
> +#define PCI_SEC_STATUS_RCV_SYSTEM_ERROR 0x4000
> +
> +void pcie_aer_write_config_vbridge(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len)
> +{
> + /* PCI_SEC_STATUS_RCV_SYSTEM_ERROR */
> + if (ranges_overlap(addr, len, PCI_STATUS, 2)) {
> + pcie_w1c_word(dev, PCI_SEC_STATUS, PCI_SEC_STATUS_RCV_SYSTEM_ERROR,
> + addr, val);
> + }
> +}
> +
> +static inline void pcie_aer_errmsg(PCIDevice *dev,
> + const struct pcie_aer_err_msg *msg)
> +{
> + assert(dev->exp);
> + assert(dev->exp->aer_errmsg);
> + dev->exp->aer_errmsg(dev, msg);
> +}
> +
> +static AER_ERR_MSG_RESULT
> +pcie_aer_errmsg_alldev(PCIDevice *dev, const struct pcie_aer_err_msg *msg)
> +{
> + uint16_t cmd = pci_get_word(dev->config + PCI_COMMAND);
> + bool transmit1 =
> + pcie_aer_err_msg_is_uncor(msg) && (cmd & PCI_COMMAND_SERR);
> + uint32_t pos = pci_pcie_cap(dev);
> + uint32_t devctl = pci_get_word(dev->config + pos + PCI_EXP_DEVCTL);
> + bool transmit2 = msg->severity & devctl;
> + PCIDevice *parent_port;
> +
> + if (transmit1) {
> + if (pcie_aer_err_msg_is_uncor(msg)) {
> + /* Signaled System Error */
> + uint8_t *status = dev->config + PCI_STATUS;
> + pci_set_word(status,
> + pci_get_word(status) | PCI_STATUS_SIG_SYSTEM_ERROR);
> + }
> + }
> +
> + if (!(transmit1 || transmit2)) {
> + return AER_ERR_MSG_MASKED;
> + }
> +
> + /* send up error message */
> + if (pci_is_express(dev) &&
> + pcie_cap_get_type(dev) == PCI_EXP_TYPE_ROOT_PORT) {
> + /* Root port notify system itself,
> + or send the error message to root complex event collector. */
> + /*
> + * if root port is associated to event collector, set
> + * parent_port = root complex event collector
> + * For now root complex event collector isn't supported.
> + */
> + parent_port = NULL;
> + } else {
> + parent_port = pci_bridge_get_device(dev->bus);
> + }
> + if (parent_port) {
> + if (!pci_is_express(parent_port)) {
> + /* What to do? */
> + return AER_ERR_MSG_MASKED;
> + }
> + pcie_aer_errmsg(parent_port, msg);
> + }
> + return AER_ERR_MSG_SENT;
> +}
> +
> +static AER_ERR_MSG_RESULT
> +pcie_aer_errmsg_vbridge(PCIDevice *dev, const struct pcie_aer_err_msg *msg)
> +{
> + uint16_t bridge_control = pci_get_word(dev->config + PCI_BRIDGE_CONTROL);
> +
> + if (pcie_aer_err_msg_is_uncor(msg)) {
> + /* Received System Error */
> + uint8_t *sec_status = dev->config + PCI_SEC_STATUS;
> + pci_set_word(sec_status,
> + pci_get_word(sec_status) |
> + PCI_SEC_STATUS_RCV_SYSTEM_ERROR);
> + }
> +
> + if (!(bridge_control & PCI_BRIDGE_CTL_SERR)) {
> + return AER_ERR_MSG_MASKED;
> + }
> + return pcie_aer_errmsg_alldev(dev, msg);
> +}
> +
> +static AER_ERR_MSG_RESULT
> +pcie_aer_errmsg_root_port(PCIDevice *dev, const struct pcie_aer_err_msg *msg)
> +{
> + AER_ERR_MSG_RESULT ret;
> + uint16_t cmd;
> + uint8_t *aer_cap;
> + uint32_t root_cmd;
> + uint32_t root_sta;
> + bool trigger;
> +
> + ret = pcie_aer_errmsg_vbridge(dev, msg);
> + if (ret != AER_ERR_MSG_SENT) {
> + return ret;
> + }
> +
> + ret = AER_ERR_MSG_MASKED;
> + cmd = pci_get_word(dev->config + PCI_COMMAND);
> + aer_cap = dev->config + pcie_aer_cap(dev);
> + root_cmd = pci_get_long(aer_cap + PCI_ERR_ROOT_COMMAND);
> + root_sta = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
> + trigger = false;
> +
> + if (cmd & PCI_COMMAND_SERR) {
> + /* System Error. Platform Specific */
> + /* ret = AER_ERR_MSG_SENT; */
> + }
> +
> + /* Errro Message Received: Root Error Status register */
> + switch (msg->severity) {
> + case AER_ERR_COR:
> + if (root_sta & PCI_ERR_ROOT_COR_RCV) {
> + root_sta |= PCI_ERR_ROOT_MULTI_COR_RCV;
> + } else {
> + if (root_cmd & PCI_ERR_ROOT_CMD_COR_EN) {
> + trigger = true;
> + }
> + pci_set_word(aer_cap + PCI_ERR_ROOT_COR_SRC, msg->source_id);
> + }
> + root_sta |= PCI_ERR_ROOT_COR_RCV;
> + break;
> + case AER_ERR_NONFATAL:
> + if (!(root_sta & PCI_ERR_ROOT_NONFATAL_RCV) &&
> + root_cmd & PCI_ERR_ROOT_CMD_NONFATAL_EN) {
> + trigger = true;
> + }
> + root_sta |= PCI_ERR_ROOT_NONFATAL_RCV;
> + break;
> + case AER_ERR_FATAL:
> + if (!(root_sta & PCI_ERR_ROOT_FATAL_RCV) &&
> + root_cmd & PCI_ERR_ROOT_CMD_FATAL_EN) {
> + trigger = true;
> + }
> + if (!(root_sta & PCI_ERR_ROOT_UNCOR_RCV)) {
> + root_sta |= PCI_ERR_ROOT_FIRST_FATAL;
> + }
> + root_sta |= PCI_ERR_ROOT_FATAL_RCV;
> + break;
> + }
> + if (pcie_aer_err_msg_is_uncor(msg)) {
> + if (root_sta & PCI_ERR_ROOT_UNCOR_RCV) {
> + root_sta |= PCI_ERR_ROOT_MULTI_UNCOR_RCV;
> + } else {
> + pci_set_word(aer_cap + PCI_ERR_ROOT_SRC, msg->source_id);
> + }
> + root_sta |= PCI_ERR_ROOT_UNCOR_RCV;
> + }
> + pci_set_long(aer_cap + PCI_ERR_ROOT_STATUS, root_sta);
> +
> + if (root_cmd & msg->severity) {
> + /* Error Interrupt(INTx or MSI) */
> + pcie_aer_root_notify(dev, trigger, 1);
> + ret = AER_ERR_MSG_SENT;
> + }
> + return ret;
> +}
> +
> +static void pcie_aer_update_log(PCIDevice *dev, const struct pcie_aer_err *err)
> +{
> + uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
> + uint8_t first_bit = ffsl(err->status) - 1;
> + uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
> + int i;
> + uint32_t dw;
> +
> + errcap &= ~(PCI_ERR_CAP_FEP_MASK | PCI_ERR_CAP_TLP);
> + errcap |= PCI_ERR_CAP_FEP(first_bit);
> +
> + if (err->flags & PCIE_AER_ERR_HEADER_VALID) {
> + for (i = 0; i < ARRAY_SIZE(err->header); ++i) {
> + /* 7.10.8 Header Log Register */
> + cpu_to_be32wu(&dw, err->header[i]);
> + memcpy(aer_cap + PCI_ERR_HEADER_LOG + sizeof(err->header[0]) * i,
> + &dw, sizeof(dw));
> + }
> + } else {
> + assert(!(err->flags & PCIE_AER_ERR_TLP_PRESENT));
> + memset(aer_cap + PCI_ERR_HEADER_LOG, 0, sizeof(err->header));
> + }
> +
> + if ((err->flags & PCIE_AER_ERR_TLP_PRESENT) &&
> + (pci_get_long(dev->config + pci_pcie_cap(dev) + PCI_EXP_DEVCTL2) &
> + PCI_EXP_DEVCAP2_EETLPP)) {
> + for (i = 0; i < ARRAY_SIZE(err->prefix); ++i) {
> + /* 7.10.12 tlp prefix log register */
> + cpu_to_be32wu(&dw, err->prefix[i]);
> + memcpy(aer_cap + PCI_ERR_TLP_PREFIX_LOG +
> + sizeof(err->prefix[0]) * i, &dw, sizeof(dw));
> + }
> + errcap |= PCI_ERR_CAP_TLP;
> + } else {
> + memset(aer_cap + PCI_ERR_TLP_PREFIX_LOG, 0, sizeof(err->prefix));
> + }
> + pci_set_long(aer_cap + PCI_ERR_CAP, errcap);
> +}
> +
> +static void pcie_aer_clear_log(PCIDevice *dev)
> +{
> + struct pcie_aer_err *err;
> + uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
> + uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
> +
> + errcap &= ~(PCI_ERR_CAP_FEP_MASK | PCI_ERR_CAP_TLP);
> + pci_set_long(aer_cap + PCI_ERR_CAP, errcap);
> +
> + memset(aer_cap + PCI_ERR_HEADER_LOG, 0, sizeof(err->header));
> + memset(aer_cap + PCI_ERR_TLP_PREFIX_LOG, 0, sizeof(err->prefix));
> +}
> +
> +static int pcie_aer_record_error(PCIDevice *dev,
> + const struct pcie_aer_err *err)
> +{
> + uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
> + uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
> + int fep = PCI_ERR_CAP_FEP(errcap);
> +
> + if (errcap & PCI_ERR_CAP_MHRE &&
> + (pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) & (1ULL << fep))) {
> + /* Not first error. queue error */
> + if (pcie_aer_log_add_err(&dev->aer_log, err) < 0) {
> + /* overflow */
> + return -1;
> + }
> + return 0;
> + }
> +
> + pcie_aer_update_log(dev, err);
> + return 0;
> +}
> +
> +static void pcie_aer_clear_error(PCIDevice *dev)
> +{
> + uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
> + uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
> + uint32_t old_err = (1UL << PCI_ERR_CAP_FEP(errcap));
> + struct pcie_aer_log *aer_log = &dev->aer_log;
> + const struct pcie_aer_err *err;
> + uint32_t consumer;
> +
> + if (!(errcap & PCI_ERR_CAP_MHRE) || pcie_aer_log_empty(aer_log)) {
> + pcie_aer_clear_log(dev);
> + pci_set_long(aer_cap + PCI_ERR_UNCOR_STATUS,
> + pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) & ~old_err);
> + return;
> + }
> +
> + /* if no same error is queued, clear bit in uncorrectable error status */
> + for (consumer = dev->aer_log.consumer;
> + !pcie_aer_log_empty_index(dev->aer_log.producer, consumer);
> + consumer = pcie_aer_log_next(consumer, dev->aer_log.log_max)) {
> + if (dev->aer_log.log[consumer].status & old_err) {
> + old_err = 0;
> + break;
> + }
> + }
> + if (old_err) {
> + pci_set_long(aer_cap + PCI_ERR_UNCOR_STATUS,
> + pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) & ~old_err);
> + }
> +
> + err = pcie_aer_log_del_err(aer_log);
> + pcie_aer_update_log(dev, err);
> +}
> +
> +/*
> + * non-Function specific error must be recorded in all functions.
> + * It is the responsibility of the caller of this function.
> + * It is also caller's responsiblity to determine which function should
> + * report the rerror.
> + *
> + * 6.2.4 Error Logging
> + * 6.2.5 Sqeucne of Device Error Signaling and Logging Operations
> + * table 6-2: Flowchard Showing Sequence of Device Error Signaling and Logging
> + * Operations
> + *
> + * Although this implementation can be shortened/optimized, this is kept
> + * parallel to table 6-2.
> + */
> +void pcie_aer_inject_error(PCIDevice *dev, const struct pcie_aer_err *err)
> +{
> + uint8_t *exp_cap;
> + uint8_t *aer_cap = NULL;
> + uint32_t devctl = 0;
> + uint32_t devsta = 0;
> + uint32_t status = err->status;
> + uint32_t mask;
> + bool is_unsupported_request =
> + (!(err->flags & PCIE_AER_ERR_IS_CORRECTABLE) &&
> + err->status == PCI_ERR_UNC_UNSUP);
> + bool is_advisory_nonfatal = false; /* for advisory non-fatal error */
> + uint32_t uncor_status = 0; /* for advisory non-fatal error */
> + struct pcie_aer_err_msg msg;
> + int is_header_log_overflowed = 0;
> +
> + if (!pci_is_express(dev)) {
> + /* What to do? */
> + return;
> + }
> +
> + if (err->flags & PCIE_AER_ERR_IS_CORRECTABLE) {
> + status &= PCI_ERR_COR_SUPPORTED;
> + } else {
> + status &= PCI_ERR_UNC_SUPPORTED;
> + }
> + if (!status || status & (status - 1)) {
> + /* invalid status bit. one and only one bit must be set */
> + return;
> + }
> +
> + exp_cap = dev->config + pci_pcie_cap(dev);
> + if (dev->exp->aer_cap) {
> + aer_cap = dev->config + pcie_aer_cap(dev);
> + devctl = pci_get_long(exp_cap + PCI_EXP_DEVCTL);
> + devsta = pci_get_long(exp_cap + PCI_EXP_DEVSTA);
> + }
> + if (err->flags & PCIE_AER_ERR_IS_CORRECTABLE) {
> + correctable_error:
> + devsta |= PCI_EXP_DEVSTA_CED;
> + if (is_unsupported_request) {
> + devsta |= PCI_EXP_DEVSTA_URD;
> + }
> + pci_set_word(exp_cap + PCI_EXP_DEVSTA, devsta);
> +
> + if (aer_cap) {
> + pci_set_long(aer_cap + PCI_ERR_COR_STATUS,
> + pci_get_long(aer_cap + PCI_ERR_COR_STATUS) | status);
> + mask = pci_get_long(aer_cap + PCI_ERR_COR_MASK);
> + if (mask & status) {
> + return;
> + }
> + if (is_advisory_nonfatal) {
> + uint32_t uncor_mask =
> + pci_get_long(aer_cap + PCI_ERR_UNCOR_MASK);
> + if (!(uncor_mask & uncor_status)) {
> + is_header_log_overflowed = pcie_aer_record_error(dev, err);
> + }
> + pci_set_long(aer_cap + PCI_ERR_UNCOR_STATUS,
> + pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) |
> + uncor_status);
> + }
> + }
> +
> + if (is_unsupported_request && !(devctl & PCI_EXP_DEVCTL_URRE)) {
> + return;
> + }
> + if (!(devctl & PCI_EXP_DEVCTL_CERE)) {
> + return;
> + }
> + msg.severity = AER_ERR_COR;
> + } else {
> + bool is_fatal =
> + (pcie_aer_uncor_default_severity(status) == AER_ERR_FATAL);
> + uint16_t cmd;
> +
> + if (aer_cap) {
> + is_fatal = status & pci_get_long(aer_cap + PCI_ERR_UNCOR_SEVER);
> + }
> + if (!is_fatal && (err->flags & PCIE_AER_ERR_MAYBE_ADVISORY)) {
> + is_advisory_nonfatal = true;
> + uncor_status = status;
> + status = PCI_ERR_COR_ADV_NONFATAL;
> + goto correctable_error;
> + }
> + if (is_fatal) {
> + devsta |= PCI_EXP_DEVSTA_FED;
> + } else {
> + devsta |= PCI_EXP_DEVSTA_NFED;
> + }
> + if (is_unsupported_request) {
> + devsta |= PCI_EXP_DEVSTA_URD;
> + }
> + pci_set_long(exp_cap + PCI_EXP_DEVSTA, devsta);
> +
> + if (aer_cap) {
> + mask = pci_get_long(aer_cap + PCI_ERR_UNCOR_MASK);
> + if (mask & status) {
> + pci_set_long(aer_cap + PCI_ERR_UNCOR_STATUS,
> + pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) |
> + status);
> + return;
> + }
> +
> + is_header_log_overflowed = pcie_aer_record_error(dev, err);
> + pci_set_long(aer_cap + PCI_ERR_UNCOR_STATUS,
> + pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) |
> + status);
> + }
> +
> + cmd = pci_get_word(dev->config + PCI_COMMAND);
> + if (is_unsupported_request &&
> + !(devctl & PCI_EXP_DEVCTL_URRE) && !(cmd & PCI_COMMAND_SERR)) {
> + return;
> + }
> + if (is_fatal) {
> + if (!((cmd & PCI_COMMAND_SERR) ||
> + (devctl & PCI_EXP_DEVCTL_FERE))) {
> + return;
> + }
> + msg.severity = AER_ERR_FATAL;
> + } else {
> + if (!((cmd & PCI_COMMAND_SERR) ||
> + (devctl & PCI_EXP_DEVCTL_NFERE))) {
> + return;
> + }
> + msg.severity = AER_ERR_NONFATAL;
> + }
> + }
> +
> + /* send up error message */
> + msg.source_id = err->source_id;
> + pcie_aer_errmsg(dev, &msg);
> +
> + if (is_header_log_overflowed) {
> + struct pcie_aer_err header_log_overflow = {
> + .status = PCI_ERR_COR_HL_OVERFLOW,
> + .flags = PCIE_AER_ERR_IS_CORRECTABLE,
> + .header = {0, 0, 0, 0},
> + .prefix = {0, 0, 0, 0},
> + };
> + pcie_aer_inject_error(dev, &header_log_overflow);
> + }
> +}
> +
> +/* aer root error command/status */
> +#define PCI_ERR_ROOT_CMD_EN_MASK (PCI_ERR_ROOT_CMD_COR_EN | \
> + PCI_ERR_ROOT_CMD_NONFATAL_EN | \
> + PCI_ERR_ROOT_CMD_FATAL_EN)
> +
> +#define PCI_ERR_ROOT_IRQ_SHIFT 26
> +#define PCI_ERR_ROOT_IRQ 0xf8000000
> +#define PCI_ERR_ROOT_STATUS_REPORT_MASK (PCI_ERR_ROOT_COR_RCV | \
> + PCI_ERR_ROOT_MULTI_COR_RCV | \
> + PCI_ERR_ROOT_UNCOR_RCV | \
> + PCI_ERR_ROOT_MULTI_UNCOR_RCV | \
> + PCI_ERR_ROOT_FIRST_FATAL | \
> + PCI_ERR_ROOT_NONFATAL_RCV | \
> + PCI_ERR_ROOT_FATAL_RCV)
> +
> +void pcie_aer_root_set_vector(PCIDevice *dev, uint8_t vector)
> +{
> + uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
> + uint32_t root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
> + root_status &= ~PCI_ERR_ROOT_IRQ;
> + root_status |=
> + (((uint32_t)vector) << PCI_ERR_ROOT_IRQ_SHIFT) & PCI_ERR_ROOT_IRQ;
> + pci_set_long(aer_cap + PCI_ERR_ROOT_STATUS, root_status);
> +}
> +
> +static uint8_t pcie_aer_root_get_vector(PCIDevice *dev)
> +{
> + uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
> + uint32_t root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
> + return (root_status & PCI_ERR_ROOT_IRQ) >> PCI_ERR_ROOT_IRQ_SHIFT;
> +}
> +
> +static void pcie_aer_root_notify(PCIDevice *dev, bool trigger, int level)
> +{
> + pcie_notify(dev, pcie_aer_root_get_vector(dev), trigger, level);
> +}
> +
> +void pcie_aer_root_init(PCIDevice *dev)
> +{
> + uint16_t pos = pcie_aer_cap(dev);
> +
> + pci_set_long(dev->wmask + pos + PCI_ERR_ROOT_COMMAND,
> + PCI_ERR_ROOT_CMD_EN_MASK);
> + dev->exp->aer_errmsg = pcie_aer_errmsg_root_port;
> +}
> +
> +void pcie_aer_root_reset(PCIDevice *dev)
> +{
> + uint8_t* aer_cap = dev->config + pcie_aer_cap(dev);
> +
> + pci_set_long(aer_cap + PCI_ERR_ROOT_COMMAND, 0);
> +
> + /*
> + * Advanced Error Interrupt Message Number in Root Error Status Register
> + * must be updated by chip dependent code.
> + */
> +}
> +
> +static bool pcie_aer_root_does_trigger(uint32_t cmd, uint32_t sta)
> +{
> + return
> + ((cmd & PCI_ERR_ROOT_CMD_COR_EN) && (sta & PCI_ERR_ROOT_COR_RCV)) ||
> + ((cmd & PCI_ERR_ROOT_CMD_NONFATAL_EN) &&
> + (sta & PCI_ERR_ROOT_NONFATAL_RCV)) ||
> + ((cmd & PCI_ERR_ROOT_CMD_FATAL_EN) && (sta & PCI_ERR_ROOT_FATAL_RCV));
> +}
> +
> +void pcie_aer_root_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len,
> + uint32_t root_cmd_prev)
> +{
> + uint16_t pos = pcie_aer_cap(dev);
> + uint8_t *aer_cap = dev->config + pos;
> + uint32_t root_status;
> +
> + if (ranges_overlap(addr, len, pos + PCI_ERR_ROOT_STATUS, 4)) {
> + /* RW1CS */
> + pcie_w1c_long(dev, pos + PCI_ERR_ROOT_STATUS,
> + PCI_ERR_ROOT_STATUS_REPORT_MASK, addr, val);
> + }
> +
> + /* root command */
> + if (ranges_overlap(addr, len, pos + PCI_ERR_ROOT_COMMAND, 4)) {
> + uint32_t root_cmd = pci_get_long(aer_cap + PCI_ERR_ROOT_COMMAND);
> + if (root_cmd & PCI_ERR_ROOT_CMD_EN_MASK) {
> + bool trigger = false;
> + int level = 0;
> + uint32_t root_cmd_set = (root_cmd_prev ^ root_cmd) & root_cmd;
> +
> + /* 0 -> 1 */
> + root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
> + if (pcie_aer_root_does_trigger(root_cmd_set, root_status)) {
> + trigger = true;
> + }
> + if (pcie_aer_root_does_trigger(root_cmd, root_status)) {
> + level = 1;
> + }
> + pcie_aer_root_notify(dev, trigger, level);
> + }
> + }
> +}
> +
> +static const VMStateDescription vmstate_pcie_aer_err = {
> + .name = "PCIE_AER_ERROR",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .minimum_version_id_old = 1,
> + .fields = (VMStateField[]) {
> + VMSTATE_UINT32(status, struct pcie_aer_err),
> + VMSTATE_UINT16(source_id, struct pcie_aer_err),
> + VMSTATE_UINT16(flags, struct pcie_aer_err),
> + VMSTATE_UINT32_ARRAY(header, struct pcie_aer_err, 4),
> + VMSTATE_UINT32_ARRAY(prefix, struct pcie_aer_err, 4),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +#define VMSTATE_PCIE_AER_ERRS(_field, _state, _field_num, _vmsd, _type) { \
> + .name = (stringify(_field)), \
> + .version_id = 0, \
> + .num_offset = vmstate_offset_value(_state, _field_num, uint16_t), \
> + .size = sizeof(_type), \
> + .vmsd = &(_vmsd), \
> + .flags = VMS_POINTER | VMS_VARRAY_UINT16 | VMS_STRUCT, \
> + .offset = vmstate_offset_pointer(_state, _field, _type), \
> +}
> +
> +const VMStateDescription vmstate_pcie_aer_log = {
> + .name = "PCIE_AER_ERROR_LOG",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .minimum_version_id_old = 1,
> + .fields = (VMStateField[]) {
> + VMSTATE_UINT32(producer, struct pcie_aer_log),
> + VMSTATE_UINT32(consumer, struct pcie_aer_log),
> + VMSTATE_UINT16(log_max, struct pcie_aer_log),
> + VMSTATE_PCIE_AER_ERRS(log, struct pcie_aer_log, log_max,
> + vmstate_pcie_aer_err, struct pcie_aer_err),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> diff --git a/hw/pcie.h b/hw/pcie.h
> new file mode 100644
> index 0000000..07f42c6
> --- /dev/null
> +++ b/hw/pcie.h
> @@ -0,0 +1,186 @@
> +/*
> + * pcie.h
> + *
> + * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
> + * VA Linux Systems Japan K.K.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef QEMU_PCIE_H
> +#define QEMU_PCIE_H
> +
> +#include "hw.h"
> +
> +enum PCIExpressIndicator {
> + /* for attention and power indicator */
> + PCI_EXP_HP_IND_RESERVED = 0b00,
> + PCI_EXP_HP_IND_ON = 0b01,
> + PCI_EXP_HP_IND_BLINK = 0b10,
> + PCI_EXP_HP_IND_OFF = 0b11,
> +};
> +
> +enum PCIExpressHotPlugEvent {
> + /* the bits match the bits in Slot Control/Status registers.
> + * PCI_EXP_HP_EV_xxx = PCI_EXP_SLTCTL_xxxE = PCI_EXP_SLTSTA_xxx
> + */
> + PCI_EXP_HP_EV_ABP = 0b00001, /* attention button preseed */
> + PCI_EXP_HP_EV_PDC = 0b01000, /* presence detect changed */
> + PCI_EXP_HP_EV_CCI = 0b10000, /* command completed */
> +
> + PCI_EXP_HP_EV_SUPPORTED = 0b11001, /* supported event mask */
> + /* events not listed aren't supported */
> +};
> +
> +typedef void (*pcie_flr_fn)(PCIDevice *dev);
> +
> +struct pcie_aer_err_msg;
> +enum AER_ERR_MSG_RESULT {
> + AER_ERR_MSG_MASKED,
> + AER_ERR_MSG_SENT,
> +};
> +typedef enum AER_ERR_MSG_RESULT AER_ERR_MSG_RESULT;
> +typedef AER_ERR_MSG_RESULT (*pcie_aer_errmsg_fn)(PCIDevice *dev, const struct pcie_aer_err_msg *msg);
> +
> +struct PCIExpressDevice {
> + /* Offset of express capability in config space */
> + uint8_t exp_cap;
> +
> + /* FLR */
> + pcie_flr_fn flr;
> +
> + /* AER */
> + uint16_t aer_cap;
> + pcie_aer_errmsg_fn aer_errmsg;
> +};
> +
> +struct pcie_aer_log {
> + uint32_t producer;
> + uint32_t consumer;
> +
> +#define PCIE_AER_LOG_MAX_DEFAULT 8
> +#define PCIE_AER_LOG_MAX_MAX 128 /* what is appropriate? */
> +#define PCIE_AER_LOG_MAX_UNSET (~(uint16_t)0)
> + uint16_t log_max;
> +
> + struct pcie_aer_err *log;
> +};
This is not how we are supposed to name the types in qemu, is it?
Either all lower case type names without a typedef
(violates CodingStyle but a lot of code does this)
or a mixed case with a typedef like CodingStyle wants,
please.
> +
> +extern const VMStateDescription vmstate_pcie_aer_log;
> +
> +/* PCI express capability helper functions */
> +int pci_pcie_cap_init(PCIDevice *dev,
> + uint8_t offset, uint8_t type, uint8_t port);
> +int pci_pcie_cap_exit(PCIDevice *dev);
> +uint8_t pcie_cap_get_type(const PCIDevice *dev);
> +void pcie_cap_flags_set_vector(PCIDevice *dev, uint8_t vector);
> +uint8_t pcie_cap_flags_get_vector(PCIDevice *dev);
> +
> +void pcie_cap_deverr_init(PCIDevice *dev);
> +void pcie_cap_deverr_reset(PCIDevice *dev);
> +void pcie_cap_deverr_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len);
> +
> +void pcie_cap_slot_init(PCIDevice *dev, uint16_t slot);
> +void pcie_cap_slot_reset(PCIDevice *dev);
> +void pcie_cap_slot_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len,
> + uint16_t sltctl_prev);
> +void pcie_cap_slot_push_attention_button(PCIDevice *dev);
> +
> +void pcie_cap_root_init(PCIDevice *dev);
> +void pcie_cap_root_reset(PCIDevice *dev);
> +
> +void pcie_cap_flr_init(PCIDevice *dev, pcie_flr_fn flr);
> +void pcie_cap_flr_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len);
> +
> +void pcie_cap_ari_init(PCIDevice *dev);
> +void pcie_cap_ari_reset(PCIDevice *dev);
> +bool pcie_cap_is_ari_enabled(const PCIDevice *dev);
> +
> +/* PCI express extended capability helper functions */
> +uint16_t pcie_find_ext_capability(PCIDevice *dev, uint16_t cap_id);
> +int pcie_add_ext_capability(PCIDevice *dev,
> + uint16_t cap_id, uint8_t cap_ver, uint16_t size);
> +int pcie_append_ext_capability(PCIDevice *dev,
> + uint16_t cap_id, uint8_t cap_ver,
> + uint16_t offset, uint16_t size);
> +void pcie_del_ext_capability(PCIDevice *dev, uint16_t cap_id, uint16_t size);
> +void pcie_reserve_ext_capability(PCIDevice *dev,
> + uint16_t offset, uint16_t size);
> +
> +int pcie_ari_init(PCIDevice *dev, uint16_t offset, uint16_t nextfn);
> +
> +/* PCI express extended capabilities */
> +
> +/* AER */
> +/* aer error severity */
> +enum PCIE_AER_SEVERITY {
> + /* those value are same as
> + * Root error command register in aer extended cap and
> + * root control register in pci express cap.
> + */
> + AER_ERR_COR = 0x1,
> + AER_ERR_NONFATAL = 0x2,
> + AER_ERR_FATAL = 0x4,
> +};
> +
> +/* aer error message: error signaling message has only error sevirity and
> + source id. See 2.2.8.3 error signaling messages */
> +struct pcie_aer_err_msg {
> + enum PCIE_AER_SEVERITY severity;
> + uint16_t source_id; /* bdf */
> +};
> +
> +static inline bool
> +pcie_aer_err_msg_is_uncor(const struct pcie_aer_err_msg *msg)
> +{
> + return msg->severity == AER_ERR_NONFATAL || msg->severity == AER_ERR_FATAL;
> +}
> +
> +/* error */
> +struct pcie_aer_err {
> + uint32_t status; /* error status bits */
> + uint16_t source_id; /* bdf */
> +
> +#define PCIE_AER_ERR_IS_CORRECTABLE 0x1 /* correctable/uncorrectable */
> +#define PCIE_AER_ERR_MAYBE_ADVISORY 0x2 /* maybe advisory non-fatal */
> +#define PCIE_AER_ERR_HEADER_VALID 0x4 /* TLP header is logged */
> +#define PCIE_AER_ERR_TLP_PRESENT 0x8 /* TLP Prefix is logged */
> + uint16_t flags;
> +
> + uint32_t header[4]; /* TLP header */
> + uint32_t prefix[4]; /* TLP header prefix */
> +};
> +
> +int pcie_aer_init(PCIDevice *dev, uint16_t offset);
> +void pcie_aer_exit(PCIDevice *dev);
> +void pcie_aer_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len);
> +void pcie_aer_write_config_vbridge(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len);
> +
> +/* aer root port */
> +void pcie_aer_root_set_vector(PCIDevice *dev, uint8_t vector);
> +void pcie_aer_root_init(PCIDevice *dev);
> +void pcie_aer_root_reset(PCIDevice *dev);
> +void pcie_aer_root_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len,
> + uint32_t root_cmd_prev);
> +
> +/* error injection */
> +void pcie_aer_inject_error(PCIDevice *dev, const struct pcie_aer_err *err);
> +
> +#endif /* QEMU_PCIE_H */
> diff --git a/qemu-common.h b/qemu-common.h
> index d735235..6d9ee26 100644
> --- a/qemu-common.h
> +++ b/qemu-common.h
> @@ -219,6 +219,7 @@ typedef struct PCIHostState PCIHostState;
> typedef struct PCIExpressHost PCIExpressHost;
> typedef struct PCIBus PCIBus;
> typedef struct PCIDevice PCIDevice;
> +typedef struct PCIExpressDevice PCIExpressDevice;
> typedef struct PCIBridge PCIBridge;
> typedef struct SerialState SerialState;
> typedef struct IRQState *qemu_irq;
> --
> 1.7.1.1
^ permalink raw reply [flat|nested] 25+ messages in thread
* [Qemu-devel] Re: [PATCH v2 9/9] msix: clear not only INTA, but all INTx when MSI-X is enabled.
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 9/9] msix: clear not only INTA, but all INTx when MSI-X is enabled Isaku Yamahata
@ 2010-09-08 10:33 ` Michael S. Tsirkin
0 siblings, 0 replies; 25+ messages in thread
From: Michael S. Tsirkin @ 2010-09-08 10:33 UTC (permalink / raw)
To: Isaku Yamahata; +Cc: skandasa, adhyas, etmartin, qemu-devel, wexu2
On Wed, Sep 08, 2010 at 04:39:42PM +0900, Isaku Yamahata wrote:
> clear not only INTA, but all INTx when MSI-X is enabled.
>
> Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
Nod. This makes sense.
> ---
> hw/msix.c | 5 ++++-
> 1 files changed, 4 insertions(+), 1 deletions(-)
>
> diff --git a/hw/msix.c b/hw/msix.c
> index 7ce63eb..b202ff7 100644
> --- a/hw/msix.c
> +++ b/hw/msix.c
> @@ -158,6 +158,7 @@ void msix_write_config(PCIDevice *dev, uint32_t addr,
> {
> unsigned enable_pos = dev->msix_cap + MSIX_CONTROL_OFFSET;
> int vector;
> + int i;
>
> if (!range_covers_byte(addr, len, enable_pos)) {
> return;
> @@ -167,7 +168,9 @@ void msix_write_config(PCIDevice *dev, uint32_t addr,
> return;
> }
>
> - qemu_set_irq(dev->irq[0], 0);
> + for (i = 0; i < PCI_NUM_PINS; ++i) {
> + qemu_set_irq(dev->irq[i], 0);
> + }
>
> if (msix_function_masked(dev)) {
> return;
> --
> 1.7.1.1
^ permalink raw reply [flat|nested] 25+ messages in thread
* [Qemu-devel] Re: [PATCH v2 7/9] pcie/hotplug: glue pushing attention button command. pcie_abp
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 7/9] pcie/hotplug: glue pushing attention button command. pcie_abp Isaku Yamahata
@ 2010-09-08 10:34 ` Michael S. Tsirkin
2010-09-09 3:43 ` Isaku Yamahata
0 siblings, 1 reply; 25+ messages in thread
From: Michael S. Tsirkin @ 2010-09-08 10:34 UTC (permalink / raw)
To: Isaku Yamahata; +Cc: skandasa, adhyas, etmartin, qemu-devel, wexu2
On Wed, Sep 08, 2010 at 04:39:40PM +0900, Isaku Yamahata wrote:
> glue to pcie_abp monitor command.
>
> Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
We also need to glue the LED so it's status both is reported
and can be polled from monitor?
> ---
> hw/pcie_port.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
> qemu-monitor.hx | 14 +++++++++
> sysemu.h | 4 +++
> 3 files changed, 100 insertions(+), 0 deletions(-)
>
> diff --git a/hw/pcie_port.c b/hw/pcie_port.c
> index d2b1f38..0aa8c53 100644
> --- a/hw/pcie_port.c
> +++ b/hw/pcie_port.c
> @@ -18,6 +18,10 @@
> * with this program; if not, see <http://www.gnu.org/licenses/>.
> */
>
> +#include "qemu-objects.h"
> +#include "sysemu.h"
> +#include "monitor.h"
> +#include "pcie.h"
> #include "pcie_port.h"
>
> void pcie_port_init_reg(PCIDevice *d)
> @@ -104,3 +108,81 @@ void pcie_chassis_del_slot(PCIESlot *s)
> {
> QLIST_REMOVE(s, next);
> }
> +
> +/**************************************************************************
> + * glue for qemu monitor
> + */
> +
> +/* Parse [<chassis>.]<slot>, return -1 on error */
> +static int pcie_parse_slot_addr(const char* slot_addr,
> + uint8_t *chassisp, uint16_t *slotp)
> +{
> + const char *p;
> + char *e;
> + unsigned long val;
> + unsigned long chassis = 0;
> + unsigned long slot;
> +
> + p = slot_addr;
> + val = strtoul(p, &e, 0);
> + if (e == p) {
> + return -1;
> + }
> + if (*e == '.') {
> + chassis = val;
> + p = e + 1;
> + val = strtoul(p, &e, 0);
> + if (e == p) {
> + return -1;
> + }
> + }
> + slot = val;
> +
> + if (*e) {
> + return -1;
> + }
> +
> + if (chassis > 0xff || slot > 0xffff) {
> + return -1;
> + }
> +
> + *chassisp = chassis;
> + *slotp = slot;
> + return 0;
> +}
> +
> +void pcie_attention_button_push_print(Monitor *mon, const QObject *data)
> +{
> + QDict *qdict;
> +
> + assert(qobject_type(data) == QTYPE_QDICT);
> + qdict = qobject_to_qdict(data);
> +
> + monitor_printf(mon, "OK chassis %d, slot %d\n",
> + (int) qdict_get_int(qdict, "chassis"),
> + (int) qdict_get_int(qdict, "slot"));
> +}
> +
> +int pcie_attention_button_push(Monitor *mon, const QDict *qdict,
> + QObject **ret_data)
> +{
> + const char* pcie_slot = qdict_get_str(qdict, "pcie_slot");
> + uint8_t chassis;
> + uint16_t slot;
> + PCIESlot *s;
> +
> + if (pcie_parse_slot_addr(pcie_slot, &chassis, &slot) < 0) {
> + monitor_printf(mon, "invalid pcie slot address %s\n", pcie_slot);
> + return -1;
> + }
> + s = pcie_chassis_find_slot(chassis, slot);
> + if (!s) {
> + monitor_printf(mon, "slot is not found. %s\n", pcie_slot);
> + return -1;
> + }
> + pcie_cap_slot_push_attention_button(&s->port.br.dev);
> + *ret_data = qobject_from_jsonf("{ 'chassis': %d, 'slot': %d}",
> + chassis, slot);
> + assert(*ret_data);
> + return 0;
> +}
> diff --git a/qemu-monitor.hx b/qemu-monitor.hx
> index 2af3de6..02fbda1 100644
> --- a/qemu-monitor.hx
> +++ b/qemu-monitor.hx
> @@ -1154,6 +1154,20 @@ Hot remove PCI device.
> ETEXI
>
> {
> + .name = "pcie_abp",
> + .args_type = "pcie_slot:s",
> + .params = "[<chassis>.]<slot>",
> + .help = "push pci express attention button",
> + .user_print = pcie_attention_button_push_print,
> + .mhandler.cmd_new = pcie_attention_button_push,
> + },
> +
> +STEXI
> +@item pcie_abp
> +Push PCI express attention button
> +ETEXI
> +
> + {
> .name = "host_net_add",
> .args_type = "device:s,opts:s?",
> .params = "tap|user|socket|vde|dump [options]",
> diff --git a/sysemu.h b/sysemu.h
> index 9c988bb..cca411d 100644
> --- a/sysemu.h
> +++ b/sysemu.h
> @@ -150,6 +150,10 @@ extern unsigned int nb_prom_envs;
> void pci_device_hot_add(Monitor *mon, const QDict *qdict);
> void drive_hot_add(Monitor *mon, const QDict *qdict);
> void do_pci_device_hot_remove(Monitor *mon, const QDict *qdict);
> +/* pcie hotplug */
> +void pcie_attention_button_push_print(Monitor *mon, const QObject *data);
> +int pcie_attention_button_push(Monitor *mon, const QDict *qdict,
> + QObject **ret_data);
>
> /* serial ports */
>
> --
> 1.7.1.1
^ permalink raw reply [flat|nested] 25+ messages in thread
* [Qemu-devel] Re: [PATCH v2 2/9] pcie: helper functions for pcie extended capability.
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 2/9] pcie: helper functions for pcie extended capability Isaku Yamahata
2010-09-08 10:31 ` [Qemu-devel] " Michael S. Tsirkin
@ 2010-09-08 17:38 ` Wei Xu
2010-09-12 7:49 ` Blue Swirl
2010-09-12 13:26 ` Michael S. Tsirkin
2 siblings, 1 reply; 25+ messages in thread
From: Wei Xu @ 2010-09-08 17:38 UTC (permalink / raw)
To: Isaku Yamahata, qemu-devel; +Cc: skandasa, adhyas, etmartin, mst
Isaku:
For binary constants below, to achieve max compatibility with gcc versions,
I recommend to change to hex (0x...):
> +
> +enum PCIExpressIndicator {
> + /* for attention and power indicator */
> + PCI_EXP_HP_IND_RESERVED = 0b00,
> + PCI_EXP_HP_IND_ON = 0b01,
> + PCI_EXP_HP_IND_BLINK = 0b10,
> + PCI_EXP_HP_IND_OFF = 0b11,
> +};
> +
> +enum PCIExpressHotPlugEvent {
> + /* the bits match the bits in Slot Control/Status registers.
> + * PCI_EXP_HP_EV_xxx = PCI_EXP_SLTCTL_xxxE = PCI_EXP_SLTSTA_xxx
> + */
> + PCI_EXP_HP_EV_ABP = 0b00001, /* attention button preseed */
> + PCI_EXP_HP_EV_PDC = 0b01000, /* presence detect changed */
> + PCI_EXP_HP_EV_CCI = 0b10000, /* command completed */
> +
Wei Xu
On 9/8/10 12:39 AM, "Isaku Yamahata" <yamahata@valinux.co.jp> wrote:
> This patch implements helper functions for pci express extended capability.
> NOTE: presence detection depends on pci_qdev_init() change.
> PCIExpressDevice::aer_log_max is in PCIDevice for device property.
>
> Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
> ---
> Makefile.objs | 1 +
> hw/pci.h | 24 +
> hw/pcie.c | 1668
> +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> hw/pcie.h | 186 +++++++
> qemu-common.h | 1 +
> 5 files changed, 1880 insertions(+), 0 deletions(-)
> create mode 100644 hw/pcie.c
> create mode 100644 hw/pcie.h
>
> diff --git a/Makefile.objs b/Makefile.objs
> index 5f5a4c5..eeb5134 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -186,6 +186,7 @@ hw-obj-$(CONFIG_PIIX4) += piix4.o
> # PCI watchdog devies
> hw-obj-y += wdt_i6300esb.o
>
> +hw-obj-y += pcie.o
> hw-obj-y += msix.o msi.o
>
> # PCI network cards
> diff --git a/hw/pci.h b/hw/pci.h
> index 296c7ba..bccab3a 100644
> --- a/hw/pci.h
> +++ b/hw/pci.h
> @@ -9,6 +9,8 @@
> /* PCI includes legacy ISA access. */
> #include "isa.h"
>
> +#include "pcie.h"
> +
> /* PCI bus */
>
> #define PCI_DEVFN(slot, func) ((((slot) & 0x1f) << 3) | ((func) & 0x07))
> @@ -172,6 +174,12 @@ struct PCIDevice {
> /* Offset of MSI capability in config space */
> uint8_t msi_cap;
>
> + /* PCI Express */
> + PCIExpressDevice *exp;
> + /* Theoretically this belongs to PCIExpressDevice.
> + However it is here for property and save/load */
> + struct pcie_aer_log aer_log;
> +
> /* Location of option rom */
> char *romfile;
> ram_addr_t rom_offset;
> @@ -367,6 +375,22 @@ static inline uint32_t pci_config_size(const PCIDevice
> *d)
> return pci_is_express(d) ? PCIE_CONFIG_SPACE_SIZE :
> PCI_CONFIG_SPACE_SIZE;
> }
>
> +
> +/* These are pci express specific, so should belong to pcie.h.
> + they're here to avoid header inclusion error. */
> +static inline uint8_t pci_pcie_cap(const PCIDevice *d)
> +{
> + return d->exp ? d->exp->exp_cap : 0;
> +}
> +
> +/* AER */
> +static inline uint16_t pcie_aer_cap(const PCIDevice *d)
> +{
> + assert(d->exp);
> + return d->exp->aer_cap;
> +}
> +
> +
> /* These are not pci specific. Should move into a separate header.
> * Only pci.c uses them, so keep them here for now.
> */
> diff --git a/hw/pcie.c b/hw/pcie.c
> new file mode 100644
> index 0000000..1f24c2a
> --- /dev/null
> +++ b/hw/pcie.c
> @@ -0,0 +1,1668 @@
> +/*
> + * pcie.c
> + *
> + * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
> + * VA Linux Systems Japan K.K.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "sysemu.h"
> +#include "pci_bridge.h"
> +#include "pcie.h"
> +#include "msix.h"
> +#include "msi.h"
> +#include "pci_internals.h"
> +
> +//#define DEBUG_PCIE
> +#ifdef DEBUG_PCIE
> +# define PCIE_DPRINTF(fmt, ...) \
> + fprintf(stderr, "%s:%d " fmt, __func__, __LINE__, ## __VA_ARGS__)
> +#else
> +# define PCIE_DPRINTF(fmt, ...) do {} while (0)
> +#endif
> +#define PCIE_DEV_PRINTF(dev, fmt, ...) \
> + PCIE_DPRINTF("%s:%x "fmt, (dev)->name, (dev)->devfn, ## __VA_ARGS__)
> +
> +static inline const char *pcie_hp_event_name(enum PCIExpressHotPlugEvent
> event)
> +{
> + switch (event) {
> + case PCI_EXP_HP_EV_ABP:
> + return "attention button pushed";
> + case PCI_EXP_HP_EV_PDC:
> + return "present detection changed";
> + case PCI_EXP_HP_EV_CCI:
> + return "command completed";
> + default:
> + break;
> + }
> + return "Unknown event";
> +}
> +
> +static void pcie_aer_clear_error(PCIDevice *dev);
> static void pcie_aer_root_notify(PCIDevice *dev, bool trigger, int level);
> +static AER_ERR_MSG_RESULT
> +pcie_aer_errmsg_alldev(PCIDevice *dev, const struct pcie_aer_err_msg *msg);
> +static AER_ERR_MSG_RESULT
> +pcie_aer_errmsg_vbridgePCIDevice *dev, const struct pcie_aer_err_msg *msg);
> +
> +/**************************************************************************
> + * pci express capability helper functions
> + */
> +
> +#define PCI_EXP_VER2_SIZEOF 0x3c /* express capability of version 2 */
> +
> +/* PCI_EXP_FLAGS */
> +#define PCI_EXP_FLAGS_VER2 2 /* for now, supports only version 2
> */
> +#define PCI_EXP_FLAGS_IRQ_SHIFT 9
> +#define PCI_EXP_FLAGS_IRQ_REG(irq) (((irq) << PCI_EXP_FLAGS_IRQ_SHIFT) &
> PCI_EXP_FLAGS_IRQ)
> +#define PCI_EXP_FLAGS_TYPE_SHIFT 4
> +
> +/* PCI_EXP_LINK{CAP, STA} */
> +/* link speed */
> +#define PCI_EXP_LNK_LS_25 1
> +
> +#define PCI_EXP_LNK_MLW_SHIFT 4
> +#define PCI_EXP_LNK_MLW_1 (1 << PCI_EXP_LNK_MLW_SHIFT)
> +
> +/* PCI_EXP_LINKCAP */
> +#define PCI_EXP_LNKCAP_ASPMS_SHIFT 10
> +#define PCI_EXP_LNKCAP_ASPMS_0S (1 << PCI_EXP_LNKCAP_ASPMS_SHIFT)
> +
> +#define PCI_EXP_LNKCAP_PN_SHIFT 24
> +#define PCI_EXP_LNKCAP_PN_REG(pn) (((pn) << PCI_EXP_LNKCAP_PN_SHIFT) &
> PCI_EXP_LNKCAP_PN)
> +
> +#define PCI_EXP_SLTCAP_PSN_SHIFT 19
> +#define PCI_EXP_SLTCAP_PSN_REG(slot) (((slot) << PCI_EXP_SLTCAP_PSN_SHIFT)
> & PCI_EXP_SLTCAP_PSN)
> +
> +#define PCI_EXP_SLTCTL_AIC_SHIFT 6
> +#define PCI_EXP_SLTCTL_AIC_ON (PCI_EXP_HP_IND_ON <<
> PCI_EXP_SLTCTL_AIC_SHIFT)
> +#define PCI_EXP_SLTCTL_AIC_BLINK (PCI_EXP_HP_IND_BLINK <<
> PCI_EXP_SLTCTL_AIC_SHIFT)
> +#define PCI_EXP_SLTCTL_AIC_OFF (PCI_EXP_HP_IND_OFF <<
> PCI_EXP_SLTCTL_AIC_SHIFT)
> +
> +#define PCI_EXP_SLTCTL_PIC_SHIFT 8
> +#define PCI_EXP_SLTCTL_PIC_ON (PCI_EXP_HP_IND_ON <<
> PCI_EXP_SLTCTL_PIC_SHIFT)
> +#define PCI_EXP_SLTCTL_PIC_BLINK (PCI_EXP_HP_IND_BLINK <<
> PCI_EXP_SLTCTL_PIC_SHIFT)
> +#define PCI_EXP_SLTCTL_PIC_OFF (PCI_EXP_HP_IND_OFF <<
> PCI_EXP_SLTCTL_PIC_SHIFT)
> +
> +#define PCI_EXP_DEVCAP2_EFF 0x100000
> +#define PCI_EXP_DEVCAP2_EETLPP 0x200000
> +
> +#define PCI_EXP_DEVCTL2_EETLPPB 0x80
> +
> +static void pcie_notify(PCIDevice *dev, uint16_t vector,
> + bool trigger, int level)
> +{
> + /* masking/masking interrupt is handled by upper layer.
> + * i.e. msix_notify() for MSI-X
> + * msi_notify() for MSI
> + * pci_set_irq() for INTx
> + */
> + PCIE_DEV_PRINTF(dev, "noitfy vector %d tirgger:%d level:%d\n",
> + vector, trigger, level);
> + if (msix_enabled(dev)) {
> + if (trigger) {
> + msix_notify(dev, vector);
> + }
> + } else if (msi_enabled(dev)) {
> + if (trigger){
> + msi_notify(dev, vector);
> + }
> + } else {
> + qemu_set_irq(dev->irq[0], level);
> + }
> +}
> +
> +static inline uint32_t pcie_written_val_long(uint32_t addr, uint32_t val,
> + uint32_t pos)
> +{
> + if (addr >= pos) {
> + val <<= addr - pos;
> + } else {
> + val >>= pos - addr;
> + }
> + return val;
> +}
> +
> +static inline uint16_t pcie_written_val_word(uint32_t addr, uint32_t val,
> + uint32_t pos)
> +{
> + return pcie_written_val_long(addr, val, pos) & 0xffff;
> +}
> +
> +/*
> + * RW1C: Write-1-to-clear
> + * regiger written val result
> + * 0 0 => 0
> + * 1 0 => 1
> + * 0 1 => 0
> + * 1 1 => 0
> + */
> +static inline void pcie_w1c_long(PCIDevice *d, uint32_t pos, uint32_t mask,
> + uint32_t addr, uint32_t val)
> +{
> + uint32_t written = pcie_written_val_long(addr, val, pos) & mask;
> + uint32_t reg = pci_get_long(d->config + pos);
> + reg &= ~written;
> + pci_set_long(d->config + pos, reg);
> +}
> +
> +static inline void pcie_w1c_word(PCIDevice *d, uint32_t pos, uint16_t mask,
> + uint32_t addr, uint32_t val)
> +{
> + uint16_t written = pcie_written_val_word(addr, val, pos) & mask;
> + uint16_t reg = pci_get_word(d->config + pos);
> + reg &= ~written;
> + pci_set_word(d->config + pos, reg);
> +}
> +
> +int pci_pcie_cap_init(PCIDevice *dev,
> + uint8_t offset, uint8_t type, uint8_t port)
> +{
> + int exp_cap;
> + uint8_t *pcie_cap;
> +
> + assert(pci_is_express(dev));
> + dev->exp = qemu_mallocz(sizeof(*dev->exp));
> +
> + exp_cap = pci_add_capability(dev, PCI_CAP_ID_EXP, offset,
> + PCI_EXP_VER2_SIZEOF);
> + if (exp_cap < 0) {
> + qemu_free(dev->exp);
> + dev->exp = NULL;
> + return exp_cap;
> + }
> + dev->exp->exp_cap = exp_cap;
> + /* dev->cap_present |= QEMU_PCI_CAP_EXPRESS; */ /* already done in
> pci_qdev_init() */
> +
> + pcie_cap = dev->config + pci_pcie_cap(dev);
> +
> + /* capability register
> + interrupt message number defaults to 0 */
> + pci_set_word(pcie_cap + PCI_EXP_FLAGS,
> + ((type << PCI_EXP_FLAGS_TYPE_SHIFT) & PCI_EXP_FLAGS_TYPE) |
> + PCI_EXP_FLAGS_VER2);
> +
> + /* device capability register
> + * table 7-12:
> + * roll based error reporting bit must be set by all
> + * Functions conforming to the ECN, PCI Express Base
> + * Specification, Revision 1.1., or subsequent PCI Express Base
> + * Specification revisions.
> + */
> + pci_set_long(pcie_cap + PCI_EXP_DEVCAP, PCI_EXP_DEVCAP_RBER);
> +
> + pci_set_long(pcie_cap + PCI_EXP_LNKCAP,
> + PCI_EXP_LNKCAP_PN_REG(port) |
> + PCI_EXP_LNKCAP_ASPMS_0S |
> + PCI_EXP_LNK_MLW_1 |
> + PCI_EXP_LNK_LS_25);
> +
> + pci_set_word(pcie_cap + PCI_EXP_LNKSTA,
> + PCI_EXP_LNK_MLW_1 | PCI_EXP_LNK_LS_25);
> +
> + pci_set_long(pcie_cap + PCI_EXP_DEVCAP2,
> + PCI_EXP_DEVCAP2_EFF | PCI_EXP_DEVCAP2_EETLPP);
> +
> + pci_set_word(dev->wmask + exp_cap, PCI_EXP_DEVCTL2_EETLPPB);
> + return exp_cap;
> +}
> +
> +int pci_pcie_cap_exit(PCIDevice *dev)
> +{
> + /* pci_del_capability(dev, PCI_CAP_ID_EXP, PCI_EXP_VER2_SIZEOF); */
> + qemu_free(dev->exp);
> + return 0;
> +}
> +
> +uint8_t pcie_cap_get_type(const PCIDevice *dev)
> +{
> + uint32_t pos = pci_pcie_cap(dev);
> + assert(pos > 0);
> + return (pci_get_word(dev->config + pos + PCI_EXP_FLAGS) &
> + PCI_EXP_FLAGS_TYPE) >> PCI_EXP_FLAGS_TYPE_SHIFT;
> +}
> +
> +/* MSI/MSI-X */
> +/* pci express interrupt message number */
> +void pcie_cap_flags_set_vector(PCIDevice *dev, uint8_t vector)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + uint16_t tmp;
> +
> + assert(vector <= 32);
> + tmp = pci_get_word(pcie_cap + PCI_EXP_FLAGS);
> + tmp &= ~PCI_EXP_FLAGS_IRQ;
> + tmp |= PCI_EXP_FLAGS_IRQ_REG(vector);
> + pci_set_word(pcie_cap + PCI_EXP_FLAGS, tmp);
> +}
> +
> +uint8_t pcie_cap_flags_get_vector(PCIDevice *dev)
> +{
> + return (pci_get_word(dev->config + pci_pcie_cap(dev) + PCI_EXP_FLAGS) &
> + PCI_EXP_FLAGS_IRQ) >> PCI_EXP_FLAGS_IRQ_SHIFT;
> +}
> +
> +static void pcie_cap_notify(PCIDevice *dev, bool trigger, int level)
> +{
> + pcie_notify(dev, pcie_cap_flags_get_vector(dev), trigger, level);
> +}
> +
> +void pcie_cap_deverr_init(PCIDevice *dev)
> +{
> + uint32_t pos = pci_pcie_cap(dev);
> + uint8_t *pcie_cap = dev->config + pos;
> + uint8_t *pcie_wmask = dev->wmask + pos;
> +
> + pci_set_long(pcie_cap + PCI_EXP_DEVCAP,
> + pci_get_long(pcie_cap + PCI_EXP_DEVCAP) |
> + PCI_EXP_DEVCAP_RBER);
> +
> + pci_set_long(pcie_wmask + PCI_EXP_DEVCTL,
> + pci_get_long(pcie_wmask + PCI_EXP_DEVCTL) |
> + PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE |
> + PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE);
> +}
> +
> +void pcie_cap_deverr_reset(PCIDevice *dev)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + pci_set_long(pcie_cap + PCI_EXP_DEVCTL,
> + pci_get_long(pcie_cap + PCI_EXP_DEVCTL) &
> + ~(PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE |
> + PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE));
> +}
> +
> +void pcie_cap_deverr_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len)
> +{
> + uint32_t pos = pci_pcie_cap(dev);
> + if (ranges_overlap(addr, len, pos + PCI_EXP_DEVSTA, 4)) {
> + /* RW1C */
> + pcie_w1c_long(dev, pos + PCI_EXP_DEVSTA,
> + PCI_EXP_DEVSTA_CED | PCI_EXP_DEVSTA_NFED |
> + PCI_EXP_DEVSTA_URD | PCI_EXP_DEVSTA_URD,
> + addr, val);
> + }
> +}
> +
> +/*
> + * events: PCI_EXP_HP_EV_xxx
> + * status: bit or of PCI_EXP_SLTSTA_xxx
> + */
> +static void pcie_cap_slot_event(PCIDevice *dev,
> + enum PCIExpressHotPlugEvent events,
> + uint16_t status)
> +{
> + bool trigger = false;
> + int level = 0;
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + uint16_t sltctl = pci_get_word(pcie_cap + PCI_EXP_SLTCTL);
> + uint16_t sltsta = pci_get_word(pcie_cap + PCI_EXP_SLTSTA);
> +
> + PCIE_DEV_PRINTF(dev,
> + "sltctl: 0x%0x2 sltsta: 0x%02x event:%x %s status:%d\n",
> + sltctl, sltsta,
> + events, pcie_hp_event_name(events), status);
> + events &= PCI_EXP_HP_EV_SUPPORTED;
> + if ((sltctl & PCI_EXP_SLTCTL_HPIE) && (sltctl & events) &&
> + ((sltsta ^ events) & events) /* 0 -> 1 */) {
> + trigger = true;
> + }
> +
> + if (events & PCI_EXP_HP_EV_PDC) {
> + sltsta &= ~PCI_EXP_SLTSTA_PDS;
> + sltsta |= (status & PCI_EXP_SLTSTA_PDS);
> + }
> + sltsta |= events;
> + pci_set_word(pcie_cap + PCI_EXP_SLTSTA, sltsta);
> + PCIE_DEV_PRINTF(dev, "sltsta -> %02xn", sltsta);
> +
> + if ((sltctl & PCI_EXP_SLTCTL_HPIE) && (sltsta & PCI_EXP_HP_EV_SUPPORTED))
> {
> + level = 1;
> + }
> +
> + pcie_cap_notify(dev, trigger, level);
> +}
> +
> +static int pcie_cap_slot_hotplug(DeviceState *qdev,
> + PCIDevice *pci_dev, int state)
> +{
> + PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev);
> + uint8_t *pcie_cap = d->config + pci_pcie_cap(d);
> + uint16_t sltsta = pci_get_word(pcie_cap + PCI_EXP_SLTSTA);
> +
> + if (!pci_dev->qdev.hotplugged) {
> + assert(state); /* this case only happens machine creation. */
> + sltsta |= PCI_EXP_SLTSTA_PDS;
> + pci_set_word(pcie_cap + PCI_EXP_SLTSTA, sltsta);
> + return 0;
> + }
> +
> + PCIE_DEV_PRINTF(pci_dev, "hotplug state: %d\n", state);
> + if (sltsta & PCI_EXP_SLTSTA_EIS) {
> + /* the slot is electromechanically locked. */
> + return -EBUSY;
> + }
> +
> + if (state) {
> + if (PCI_FUNC(pci_dev->devfn) == 0) {
> + /* event is per slot. Not per function
> + * only generates event for function = 0.
> + * When hot plug, populate functions > 0
> + * and then add function = 0 last.
> + */
> + pcie_cap_slot_event(d, PCI_EXP_HP_EV_PDC, PCI_EXP_SLTSTA_PDS);
> + }
> + } else {
> + PCIBridge *br;
> + PCIBus *bus;
> + DeviceState *next;
> + if (PCI_FUNC(pci_dev->devfn) != 0) {
> + /* event is per slot. Not per function.
> + accepts function = 0 only. */
> + return -EINVAL;
> + }
> +
> + /* zap all functions. */
> + br = DO_UPCAST(PCIBridge, dev, d);
> + bus = pci_bridge_get_sec_bus(br);
> + QLIST_FOREACH_SAFE(qdev, &bus->qbus.children, sibling, next) {
> + qdev_free(qdev);
> + }
> +
> + pcie_cap_slot_event(d, PCI_EXP_HP_EV_PDC, 0);
> + }
> + return 0;
> +}
> +
> +/* pci express slot for pci express root/downstream port
> + PCI express capability slot registers */
> +void pcie_cap_slot_init(PCIDevice *dev, uint16_t slot)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + uint8_t *pcie_wmask = dev->wmask + pci_pcie_cap(dev);
> + uint32_t tmp;
> +
> + pci_set_word(pcie_cap + PCI_EXP_FLAGS,
> + pci_get_word(pcie_cap + PCI_EXP_FLAGS) |
> PCI_EXP_FLAGS_SLOT);
> +
> + tmp = pci_get_long(pcie_cap + PCI_EXP_SLTCAP);
> + tmp &= PCI_EXP_SLTCAP_PSN;
> + tmp |=
> + PCI_EXP_SLTCAP_PSN_REG(slot) |
> + PCI_EXP_SLTCAP_EIP |
> + PCI_EXP_SLTCAP_HPS |
> + PCI_EXP_SLTCAP_HPC |
> + PCI_EXP_SLTCAP_PIP |
> + PCI_EXP_SLTCAP_AIP |
> + PCI_EXP_SLTCAP_ABP;
> + pci_set_long(pcie_cap + PCI_EXP_SLTCAP, tmp);
> +
> + tmp = pci_get_word(pcie_cap + PCI_EXP_SLTCTL);
> + tmp &= ~(PCI_EXP_SLTCTL_PIC | PCI_EXP_SLTCTL_AIC);
> + tmp |= PCI_EXP_SLTCTL_PIC_OFF | PCI_EXP_SLTCTL_AIC_OFF;
> + pci_set_word(pcie_cap + PCI_EXP_SLTCTL, tmp);
> + pci_set_word(pcie_wmask + PCI_EXP_SLTCTL,
> + pci_get_word(pcie_wmask + PCI_EXP_SLTCTL) |
> + PCI_EXP_SLTCTL_PIC |
> + PCI_EXP_SLTCTL_AIC |
> + PCI_EXP_SLTCTL_HPIE |
> + PCI_EXP_SLTCTL_CCIE |
> + PCI_EXP_SLTCTL_PDCE |
> + PCI_EXP_SLTCTL_ABPE);
> +
> + pci_bus_hotplug(pci_bridge_get_sec_bus(DO_UPCAST(PCIBridge, dev, dev)),
> + pcie_cap_slot_hotplug, &dev->qdev);
> +}
> +
> +void pcie_cap_slot_reset(PCIDevice *dev)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + uint32_t tmp;
> +
> + PCIE_DEV_PRINTF(dev, "reset\n");
> +
> + tmp = pci_get_word(pcie_cap + PCI_EXP_SLTCTL);
> + tmp &= ~(PCI_EXP_SLTCTL_EIC |
> + PCI_EXP_SLTCTL_PIC |
> + PCI_EXP_SLTCTL_AIC |
> + PCI_EXP_SLTCTL_HPIE |
> + PCI_EXP_SLTCTL_CCIE |
> + PCI_EXP_SLTCTL_PDCE |
> + PCI_EXP_SLTCTL_ABPE);
> + tmp |= PCI_EXP_SLTCTL_PIC_OFF | PCI_EXP_SLTCTL_AIC_OFF;
> + pci_set_word(pcie_cap + PCI_EXP_SLTCTL, tmp);
> +
> + tmp = pci_get_word(pcie_cap + PCI_EXP_SLTSTA);
> + tmp &= ~(PCI_EXP_SLTSTA_EIS | /* by reset, the lock is released */
> + PCI_EXP_SLTSTA_CC |
> + PCI_EXP_SLTSTA_PDC |
> + PCI_EXP_SLTSTA_ABP);
> + pci_set_word(pcie_cap + PCI_EXP_SLTSTA, tmp);
> +}
> +
> +void pcie_cap_slot_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len,
> + uint16_t sltctl_prev)
> +{
> + uint32_t pos = pci_pcie_cap(dev);
> + uint8_t *pcie_cap = dev->config + pos;
> + uint16_t sltctl = pci_get_word(pcie_cap + PCI_EXP_SLTCTL);
> + uint16_t sltsta = pci_get_word(pcie_cap + PCI_EXP_SLTSTA);
> +
> + PCIE_DEV_PRINTF(dev,
> + "addr: 0x%x val: 0x%x len: %d\n"
> + "\tsltctl_prev: 0x%02x sltctl: 0x%02x sltsta 0x%02x\n",
> + addr, val, len, sltctl_prev, sltctl, sltsta);
> + /* SLTSTA: process SLTSTA before SLTCTL to avoid spurious interrupt */
> + if (ranges_overlap(addr, len, pos + PCI_EXP_SLTSTA, 2)) {
> + /* RW1C */
> + pcie_w1c_word(dev, pos + PCI_EXP_SLTSTA, PCI_EXP_HP_EV_SUPPORTED,
> + addr, val);
> + sltsta = pci_get_word(pcie_cap + PCI_EXP_SLTSTA);
> +
> + /* write to stlsta results in clearing bits,
> + so new interrupts won't be generated. */
> + PCIE_DEV_PRINTF(dev, "sltsta -> 0x%02x\n", sltsta);
> + }
> +
> + /* SLTCTL */
> + if (ranges_overlap(addr, len, pos + PCI_EXP_SLTCTL, 2)) {
> + PCIE_DEV_PRINTF(dev, "sltctl: 0x%02x -> 0x%02x\n",
> + sltctl_prev, sltctl);
> + if (pcie_written_val_word(addr, val, pos + PCI_EXP_SLTCTL) &
> + PCI_EXP_SLTCTL_EIC) {
> + /* toggle PCI_EXP_SLTSTA_EIS */
> + sltsta = (sltsta & ~PCI_EXP_SLTSTA_EIS) |
> + ((sltsta ^ PCI_EXP_SLTSTA_EIS) & PCI_EXP_SLTSTA_EIS);
> + pci_set_word(pcie_cap + PCI_EXP_SLTSTA, sltsta);
> + PCIE_DEV_PRINTF(dev, "PCI_EXP_SLTCTL_EIC: sltsta -> 0x%02x\n",
> + sltsta);
> + }
> +
> + if (sltctl & PCI_EXP_SLTCTL_HPIE) {
> + bool trigger = false;
> + int level = 0;
> +
> + if (((sltctl_prev ^ sltctl) & sltctl) & PCI_EXP_HP_EV_SUPPORTED)
> {
> + /* 0 -> 1 */
> + trigger = true;
> + }
> + if ((sltctl & sltsta) & PCI_EXP_HP_EV_SUPPORTED) {
> + level = 1;
> + }
> + pcie_cap_notify(dev, trigger, level);
> + }
> +
> + /* command completed.
> + unlike real hardware, command completes instantaneously */
> +#define PCI_EXP_SLTCTL_SUPPORTED \
> + (PCI_EXP_SLTCTL_ABPE | \
> + PCI_EXP_SLTCTL_PDCE | \
> + PCI_EXP_SLTCTL_CCIE | \
> + PCI_EXP_SLTCTL_HPIE | \
> + PCI_EXP_SLTCTL_AIC | \
> + PCI_EXP_SLTCTL_PCC | \
> + PCI_EXP_SLTCTL_EIC)
> + if ( 1 /* (sltctl_prev ^ sltctl) & PCI_EXP_SLTCTL_SUPPORTED */ ) {
> + /* set command completed bit */
> + pcie_cap_slot_event(dev, PCI_EXP_HP_EV_CCI, 0);
> + }
> + }
> +}
> +
> +void pcie_cap_slot_push_attention_button(PCIDevice *dev)
> +{
> + pcie_cap_slot_event(dev, PCI_EXP_HP_EV_ABP, 0);
> +}
> +
> +/* root control/capabilities/status. PME isn't emulated for now */
> +void pcie_cap_root_init(PCIDevice *dev)
> +{
> + uint8_t pos = pci_pcie_cap(dev);
> + pci_set_word(dev->wmask + pos + PCI_EXP_RTCTL,
> + PCI_EXP_RTCTL_SECEE | PCI_EXP_RTCTL_SENFEE |
> + PCI_EXP_RTCTL_SEFEE);
> +}
> +
> +void pcie_cap_root_reset(PCIDevice *dev)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + pci_set_word(pcie_cap + PCI_EXP_RTCTL, 0);
> +}
> +
> +/* function level reset(FLR) */
> +void pcie_cap_flr_init(PCIDevice *dev, pcie_flr_fn flr)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + pci_set_word(pcie_cap + PCI_EXP_DEVCAP,
> + pci_get_word(pcie_cap + PCI_EXP_DEVCAP) |
> PCI_EXP_DEVCAP_FLR);
> + dev->exp->flr = flr;
> +}
> +
> +void pcie_cap_flr_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len)
> +{
> + uint32_t pos = pci_pcie_cap(dev);
> + if (ranges_overlap(addr, len, pos + PCI_EXP_DEVCTL, 2)) {
> + uint16_t val16 = pcie_written_val_word(addr, val,
> + pos + PCI_EXP_DEVCTL);
> + if ((val16 & PCI_EXP_DEVCTL_BCR_FLR) && dev->exp->flr) {
> + dev->exp->flr(dev);
> + }
> + }
> +}
> +
> +
> +/* Alternative Routing-ID Interpretation (ARI) */
> +/* ari forwarding support for down stream port */
> +void pcie_cap_ari_init(PCIDevice *dev)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> + uint8_t *pcie_wmask = dev->wmask + pci_pcie_cap(dev);
> +
> + pci_set_long(pcie_cap + PCI_EXP_DEVCAP2,
> + pci_get_long(pcie_cap + PCI_EXP_DEVCAP2) |
> + PCI_EXP_DEVCAP2_ARI);
> +
> + pci_set_long(pcie_wmask + PCI_EXP_DEVCTL2,
> + pci_get_long(pcie_wmask + PCI_EXP_DEVCTL2) |
> + PCI_EXP_DEVCTL2_ARI);
> +}
> +
> +void pcie_cap_ari_reset(PCIDevice *dev)
> +{
> + uint8_t *pcie_cap = dev->config + pci_pcie_cap(dev);
> +
> + pci_set_long(pcie_cap + PCI_EXP_DEVCTL2,
> + pci_get_long(pcie_cap + PCI_EXP_DEVCTL2) &
> + ~PCI_EXP_DEVCTL2_ARI);
> +}
> +
> +bool pcie_cap_is_ari_enabled(const PCIDevice *dev)
> +{
> + if (!pci_is_express(dev)) {
> + return false;
> + }
> + if (!pci_pcie_cap(dev)) {
> + return false;
> + }
> +
> + return pci_get_long(dev->config + pci_pcie_cap(dev) + PCI_EXP_DEVCTL2) &
> + PCI_EXP_DEVCTL2_ARI;
> +}
> +
> +/**************************************************************************
> + * pci express extended capability allocation functions
> + * uint16_t ext_cap_id (16 bit)
> + * uint8_t cap_ver (4 bit)
> + * uint16_t cap_offset (12 bit)
> + * uint16_t ext_cap_size
> + */
> +
> +#define PCI_EXT_CAP_VER_SHIFT 16
> +#define PCI_EXT_CAP_NEXT_MASK 0xfff00000
> +#define PCI_EXT_CAP_NEXT_SHIFT 20
> +
> +#define PCI_EXT_CAP(id, ver, next) ((id) | ((ver) << PCI_EXT_CAP_VER_SHIFT) |
> ((next) << PCI_EXT_CAP_NEXT_SHIFT))
> +
> +#define PCI_EXT_CAP_ALIGN 4
> +#define PCI_EXT_CAP_ALIGNUP(x) (((x) + PCI_EXT_CAP_ALIGN - 1) &
> ~(PCI_EXT_CAP_ALIGN - 1))
> +
> +static int16_t pcie_ext_cap_find_space(PCIDevice *dev, uint16_t size)
> +{
> + uint16_t offset = PCI_CONFIG_SPACE_SIZE;
> + uint16_t i = offset;
> +
> + while (i < PCIE_CONFIG_SPACE_SIZE - size) {
> + if (dev->used[i]) {
> + offset = PCI_EXT_CAP_ALIGNUP(i + 1);
> + i = offset;
> + continue;
> + } else if (i - offset + 1 == size) {
> + return offset;
> + }
> +
> + ++i;
> + }
> +
> + return 0;
> +}
> +
> +static uint16_t pcie_find_ext_capability_list(PCIDevice *dev, uint16_t
> cap_id,
> + uint16_t *prev_p)
> +{
> + int ttl;
> +
> + uint16_t prev = 0;
> + uint16_t next = PCI_CONFIG_SPACE_SIZE;
> + uint32_t header = pci_get_long(dev->config + next);
> +
> + if (!header) {
> + return 0;
> + }
> +
> + /* minimum 8 bytes per capability */
> + ttl = (PCIE_CONFIG_SPACE_SIZE - PCI_CONFIG_SPACE_SIZE) / 8;
> +
> + while (ttl-- > 0) {
> + if (PCI_EXT_CAP_ID(header) == cap_id) {
> + break;
> + }
> +
> + prev = next;
> + next = PCI_EXT_CAP_NEXT(header);
> + if (next < PCI_CONFIG_SPACE_SIZE) {
> + return 0;
> + }
> + header = pci_get_long(dev->config + prev);
> + }
> +
> + if (!ttl) {
> + return 0;
> + }
> + if (prev_p) {
> + *prev_p = prev;
> + }
> + return next;
> +}
> +
> +uint16_t pcie_find_ext_capability(PCIDevice *dev, uint16_t cap_id)
> +{
> + return pcie_find_ext_capability_list(dev, cap_id, NULL);
> +}
> +
> +static void pcie_ext_cap_set_next(PCIDevice *dev, uint16_t pos, uint16_t
> next)
> +{
> + uint16_t header = pci_get_long(dev->config + pos);
> + assert(!(next & (PCI_EXT_CAP_ALIGN - 1)));
> + header = (header & ~PCI_EXT_CAP_NEXT_MASK) |
> + ((next << PCI_EXT_CAP_NEXT_SHIFT) & PCI_EXT_CAP_NEXT_MASK);
> + pci_set_long(dev->config + pos, header);
> +}
> +
> +static void pcie_allocate_ext_capability(PCIDevice *dev,
> + uint16_t cap_id, uint8_t cap_ver,
> + uint16_t offset, uint16_t size)
> +{
> + uint32_t header;
> + uint16_t next;
> +
> + assert(offset < offset + size);
> + assert(offset + size < PCIE_CONFIG_SPACE_SIZE);
> + assert(size >= 8);
> +
> + if (offset == PCI_CONFIG_SPACE_SIZE) {
> + header = pci_get_long(dev->config + offset);
> + next = PCI_EXT_CAP_NEXT(header);
> + } else {
> + /* find last ext cap */
> + int ttl = (PCIE_CONFIG_SPACE_SIZE - PCI_CONFIG_SPACE_SIZE) / 8;
> + uint16_t pos = PCI_CONFIG_SPACE_SIZE;
> + while (ttl-- > 0) {
> + header = pci_get_long(dev->config + pos);
> + if (PCI_EXT_CAP_NEXT(header) < PCI_CONFIG_SPACE_SIZE) {
> + break;
> + }
> +
> + pos = PCI_EXT_CAP_NEXT(header);
> + }
> +
> + assert(ttl > 0); /* since it is known that [offset, offset + size]
> + is unused, so ttl shouldn't be zero */
> + pcie_ext_cap_set_next(dev, pos, offset);
> + next = 0;
> + }
> + pci_set_long(dev->config + offset, PCI_EXT_CAP(cap_id, cap_ver, next));
> +
> + memset(dev->used + offset, 0xFF, size);
> + /* Make capability read-only by default */
> + memset(dev->wmask + offset, 0, size);
> + /* Check capability by default */
> + memset(dev->cmask + offset, 0xFF, size);
> +}
> +
> +int pcie_add_ext_capability(PCIDevice *dev,
> + uint16_t cap_id, uint8_t cap_ver, uint16_t size)
> +{
> + uint16_t offset = pcie_ext_cap_find_space(dev, size);
> +
> + if (!offset) {
> + return -ENOSPC;
> + }
> +
> + pcie_allocate_ext_capability(dev, cap_id, cap_ver, offset, size);
> + return offset;
> +}
> +
> +int pcie_append_ext_capability(PCIDevice *dev,
> + uint16_t cap_id, uint8_t cap_ver,
> + uint16_t offset, uint16_t size)
> +{
> + uint16_t i;
> +
> + if (!offset) {
> + return pcie_add_ext_capability(dev, cap_id, cap_ver, size);
> + }
> +
> + assert(offset < offset + size);
> + assert(offset + size < PCIE_CONFIG_SPACE_SIZE);
> + assert(size >= 8);
> +
> + for (i = offset; i < offset + size; ++i) {
> + if (dev->used[i]) {
> + return -EBUSY;
> + }
> + }
> +
> + pcie_allocate_ext_capability(dev, cap_id, cap_ver, offset, size);
> + return offset;
> +}
> +
> +void pcie_del_ext_capability(PCIDevice *dev, uint16_t cap_id, uint16_t size)
> +{
> + uint16_t prev;
> + uint16_t offset = pcie_find_ext_capability_list(dev, cap_id, &prev);
> + uint32_t header;
> +
> + if (!offset) {
> + return;
> + }
> +
> + header = pci_get_long(dev->config + offset);
> + if (prev) {
> + pcie_ext_cap_set_next(dev, prev, PCI_EXT_CAP_NEXT(header));
> + } else {
> + /* move up next ext cap to PCI_CONFIG_SPACE_SIZE? */
> + assert(offset == PCI_CONFIG_SPACE_SIZE);
> + pci_set_long(dev->config + offset,
> + PCI_EXT_CAP(0, 0, PCI_EXT_CAP_NEXT(header)));
> + }
> +
> + /* Make capability writeable again */
> + memset(dev->wmask + offset, 0xff, size);
> + /* Clear cmask as device-specific registers can't be checked */
> + memset(dev->cmask + offset, 0, size);
> + memset(dev->used + offset, 0, size);
> +}
> +
> +void pcie_reserve_ext_capability(PCIDevice *dev,
> + uint16_t offset, uint16_t size)
> +{
> + memset(dev->used + offset, 0xff, size);
> +}
> +
> +/**************************************************************************
> + * pci express extended capability helper functions
> + */
> +
> +/* ARI */
> +#define PCI_ARI_VER 1
> +#define PCI_ARI_SIZEOF 8
> +
> +int pcie_ari_init(PCIDevice *dev, uint16_t offset, uint16_t nextfn)
> +{
> + int pos;
> + pos = pcie_append_ext_capability(dev, PCI_EXT_CAP_ID_ARI, PCI_ARI_VER,
> + offset, PCI_ARI_SIZEOF);
> + if (pos < 0) {
> + return pos;
> + }
> +
> + pci_set_long(dev->config + pos + PCI_ARI_CAP, PCI_ARI_CAP_NFN(nextfn));
> + return pos;
> +}
> +
> +/* AER */
> +#define PCI_ERR_VER 2
> +#define PCI_ERR_SIZEOF 0x48
> +
> +#define PCI_ERR_UNC_SDN 0x00000020 /* surprise down */
> +#define PCI_ERR_UNC_ACSV 0x00200000 /* ACS Violation */
> +#define PCI_ERR_UNC_INTN 0x00400000 /* Internal Error */
> +#define PCI_ERR_UNC_MCBTLP 0x00800000 /* MC Blcoked TLP */
> +#define PCI_ERR_UNC_ATOP_EBLOCKED 0x01000000 /* atomic op egress
> blocked */
> +#define PCI_ERR_UNC_TLP_PRF_BLOCKED 0x02000000 /* TLP Prefix Blocked
> */
> +#define PCI_ERR_UNC_SUPPORTED (PCI_ERR_UNC_DLP | \
> + PCI_ERR_UNC_SDN | \
> + PCI_ERR_UNC_POISON_TLP | \
> + PCI_ERR_UNC_FCP | \
> + PCI_ERR_UNC_COMP_TIME | \
> + PCI_ERR_UNC_COMP_ABORT | \
> + PCI_ERR_UNC_UNX_COMP | \
> + PCI_ERR_UNC_RX_OVER | \
> + PCI_ERR_UNC_MALF_TLP | \
> + PCI_ERR_UNC_ECRC | \
> + PCI_ERR_UNC_UNSUP | \
> + PCI_ERR_UNC_ACSV | \
> + PCI_ERR_UNC_INTN | \
> + PCI_ERR_UNC_MCBTLP | \
> + PCI_ERR_UNC_ATOP_EBLOCKED | \
> + PCI_ERR_UNC_TLP_PRF_BLOCKED)
> +
> +#define PCI_ERR_UNC_SEVERITY_DEFAULT (PCI_ERR_UNC_DLP | \
> + PCI_ERR_UNC_SDN | \
> + PCI_ERR_UNC_FCP | \
> + PCI_ERR_UNC_RX_OVER | \
> + PCI_ERR_UNC_MALF_TLP | \
> + PCI_ERR_UNC_INTN)
> +
> +#define PCI_ERR_COR_ADV_NONFATAL 0x00002000 /* Advisory Non-Fatal
> */
> +#define PCI_ERR_COR_INTERNAL 0x00004000 /* Corrected Internal
> */
> +#define PCI_ERR_COR_HL_OVERFLOW 0x00008000 /* Header Long
> Overflow */
> +#define PCI_ERR_COR_SUPPORTED (PCI_ERR_COR_RCVR | \
> + PCI_ERR_COR_BAD_TLP | \
> + PCI_ERR_COR_BAD_DLLP | \
> + PCI_ERR_COR_REP_ROLL | \
> + PCI_ERR_COR_REP_TIMER | \
> + PCI_ERR_COR_ADV_NONFATAL | \
> + PCI_ERR_COR_INTERNAL | \
> + PCI_ERR_COR_HL_OVERFLOW)
> +#define PCI_ERR_COR_MASK_DEFAULT (PCI_ERR_COR_ADV_NONFATAL | \
> + PCI_ERR_COR_INTERNAL | \
> + PCI_ERR_COR_HL_OVERFLOW)
> +
> +
> +#define PCI_ERR_CAP_FEP_MASK 0x0000001f
> +#define PCI_ERR_CAP_MHRC 0x00000200
> +#define PCI_ERR_CAP_MHRE 0x00000400
> +#define PCI_ERR_CAP_TLP 0x00000800
> +
> +#define PCI_ERR_TLP_PREFIX_LOG 0x38
> +
> +/* From 6.2.7 Error Listing and Rules. Table 6-2, 6-3 and 6-4 */
> +static enum PCIE_AER_SEVERITY pcie_aer_uncor_default_severity(uint32_t
> status)
> +{
> + switch (status) {
> + case PCI_ERR_UNC_INTN:
> + case PCI_ERR_UNC_DLP:
> + case PCI_ERR_UNC_SDN:
> + case PCI_ERR_UNC_RX_OVER:
> + case PCI_ERR_UNC_FCP:
> + case PCI_ERR_UNC_MALF_TLP:
> + return AER_ERR_FATAL;
> + case PCI_ERR_UNC_POISON_TLP:
> + case PCI_ERR_UNC_ECRC:
> + case PCI_ERR_UNC_UNSUP:
> + case PCI_ERR_UNC_COMP_TIME:
> + case PCI_ERR_UNC_COMP_ABORT:
> + case PCI_ERR_UNC_UNX_COMP:
> + case PCI_ERR_UNC_ACSV:
> + case PCI_ERR_UNC_MCBTLP:
> + case PCI_ERR_UNC_ATOP_EBLOCKED:
> + case PCI_ERR_UNC_TLP_PRF_BLOCKED:
> + return AER_ERR_NONFATAL;
> + default:
> + break;
> + }
> + abort();
> + return AER_ERR_FATAL;
> +}
> +
> +static uint32_t pcie_aer_log_next(uint32_t i, uint32_t max)
> +{
> + return (i + 1) % max;
> +}
> +
> +static bool pcie_aer_log_empty_index(uint32_t producer, uint32_t consumer)
> +{
> + return producer == consumer;
> +}
> +
> +static bool pcie_aer_log_empty(struct pcie_aer_log *aer_log)
> +{
> + return pcie_aer_log_empty_index(aer_log->producer, aer_log->consumer);
> +}
> +
> +static bool pcie_aer_log_full(struct pcie_aer_log *aer_log)
> +{
> + return pcie_aer_log_next(aer_log->producer, aer_log->log_max) ==
> + aer_log->consumer;
> +}
> +
> +static uint32_t pcie_aer_log_add(struct pcie_aer_log *aer_log)
> +{
> + uint32_t i = aer_log->producer;
> + aer_log->producer = pcie_aer_log_next(aer_log->producer,
> aer_log->log_max);
> + return i;
> +}
> +
> +static uint32_t pcie_aer_log_del(struct pcie_aer_log *aer_log)
> +{
> + uint32_t i = aer_log->consumer;
> + aer_log->consumer = pcie_aer_log_next(aer_log->consumer,
> aer_log->log_max);
> + return i;
> +}
> +
> +static int pcie_aer_log_add_err(struct pcie_aer_log *aer_log,
> + const struct pcie_aer_err *err)
> +{
> + uint32_t i;
> + if (pcie_aer_log_full(aer_log)) {
> + return -1;
> + }
> + i = pcie_aer_log_add(aer_log);
> + memcpy(&aer_log->log[i], err, sizeof(*err));
> + return 0;
> +}
> +
> +static const struct pcie_aer_err*
> +pcie_aer_log_del_err(struct pcie_aer_log *aer_log)
> +{
> + uint32_t i;
> + assert(!pcie_aer_log_empty(aer_log));
> + i = pcie_aer_log_del(aer_log);
> + return &aer_log->log[i];
> +}
> +
> +static void pcie_aer_log_clear_all_err(struct pcie_aer_log *aer_log)
> +{
> + aer_log->producer = 0;
> + aer_log->consumer = 0;
> +}
> +
> +int pcie_aer_init(PCIDevice *dev, uint16_t offset)
> +{
> + int pos;
> + PCIExpressDevice *exp;
> +
> + pci_set_word(dev->wmask + PCI_COMMAND,
> + pci_get_word(dev->wmask + PCI_COMMAND) | PCI_COMMAND_SERR);
> +
> + pos = pcie_append_ext_capability(dev, PCI_EXT_CAP_ID_ERR, PCI_ERR_VER,
> + offset, PCI_ERR_SIZEOF);
> + if (pos < 0) {
> + return pos;
> + }
> + exp = dev->exp;
> + exp->aer_cap = pos;
> + if (dev->aer_log.log_max == PCIE_AER_LOG_MAX_UNSET) {
> + dev->aer_log.log_max = PCIE_AER_LOG_MAX_DEFAULT;
> + }
> + if (dev->aer_log.log_max > PCIE_AER_LOG_MAX_MAX) {
> + dev->aer_log.log_max = PCIE_AER_LOG_MAX_MAX;
> + }
> + dev->aer_log.log =
> + qemu_mallocz(sizeof(dev->aer_log.log[0]) * dev->aer_log.log_max);
> +
> + pci_set_long(dev->wmask + pos + PCI_ERR_UNCOR_MASK,
> + PCI_ERR_UNC_SUPPORTED);
> +
> + pci_set_long(dev->config + pos + PCI_ERR_UNCOR_SEVER,
> + PCI_ERR_UNC_SEVERITY_DEFAULT);
> + pci_set_long(dev->wmask + pos + PCI_ERR_UNCOR_SEVER,
> + PCI_ERR_UNC_SUPPORTED);
> +
> + pci_set_long(dev->config + pos + PCI_ERR_COR_MASK,
> + PCI_ERR_COR_MASK_DEFAULT);
> + pci_set_long(dev->wmask + pos + PCI_ERR_COR_MASK,
> + PCI_ERR_COR_SUPPORTED);
> +
> + /* capabilities and control. multiple header logging is supported */
> + if (dev->aer_log.log_max > 0) {
> + pci_set_long(dev->config + pos + PCI_ERR_CAP,
> + PCI_ERR_CAP_ECRC_GENC | PCI_ERR_CAP_ECRC_CHKC |
> + PCI_ERR_CAP_MHRC);
> + pci_set_long(dev->wmask + pos + PCI_ERR_CAP,
> + PCI_ERR_CAP_ECRC_GENE | PCI_ERR_CAP_ECRC_CHKE |
> + PCI_ERR_CAP_MHRE);
> + } else {
> + pci_set_long(dev->config + pos + PCI_ERR_CAP,
> + PCI_ERR_CAP_ECRC_GENC | PCI_ERR_CAP_ECRC_CHKC);
> + pci_set_long(dev->wmask + pos + PCI_ERR_CAP,
> + PCI_ERR_CAP_ECRC_GENE | PCI_ERR_CAP_ECRC_CHKE);
> + }
> +
> + switch (pcie_cap_get_type(dev)) {
> + case PCI_EXP_TYPE_ROOT_PORT:
> + /* this case will be set by pcie_aer_root_init() */
> + /* fallthrough */
> + case PCI_EXP_TYPE_DOWNSTREAM:
> + case PCI_EXP_TYPE_UPSTREAM:
> + pci_set_word(dev->wmask + PCI_BRIDGE_CONTROL,
> + pci_get_word(dev->wmask + PCI_BRIDGE_CONTROL) |
> + PCI_BRIDGE_CTL_SERR);
> + exp->aer_errmsg = pcie_aer_errmsg_vbridge;
> + break;
> + default:
> + exp->aer_errmsg = pcie_aer_errmsg_alldev;
> + break;
> + }
> + return pos;
> +}
> +
> +void pcie_aer_exit(PCIDevice *dev)
> +{
> + pci_del_capability(dev, PCI_EXT_CAP_ID_ERR, PCI_ERR_SIZEOF);
> + qemu_free(dev->aer_log.log);
> +}
> +
> +/* Multiple Header recording isn't implemented. Is it wanted? */
> +void pcie_aer_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len)
> +{
> + uint32_t pos = dev->exp->aer_cap;
> +
> + /* PCI_STATUS_SIG_SYSTEM_ERROR */
> + if (ranges_overlap(addr, len, PCI_STATUS, 2)) {
> + pcie_w1c_word(dev, PCI_STATUS, PCI_STATUS_SIG_SYSTEM_ERROR, addr,
> val);
> + }
> +
> + /* uncorrectable */
> + if (ranges_overlap(addr, len, pos + PCI_ERR_UNCOR_STATUS, 4)) {
> + uint32_t written =
> + pcie_written_val_long(addr, val, pos + PCI_ERR_UNCOR_STATUS) &
> + PCI_ERR_UNC_SUPPORTED;
> + uint32_t uncorsta =
> + pci_get_long(dev->config + pos + PCI_ERR_UNCOR_STATUS);
> + uint32_t errcap = pci_get_long(dev->config + pos + PCI_ERR_CAP);
> + uint32_t first_error = (1 << PCI_ERR_CAP_FEP(errcap));
> +
> + if ((uncorsta & first_error) && (written & first_error)) {
> + pcie_aer_clear_error(dev);
> + }
> + if (!(errcap & PCI_ERR_CAP_MHRE)) {
> + /* RW1CS */
> + pcie_w1c_long(dev, pos + PCI_ERR_UNCOR_STATUS,
> + PCI_ERR_UNC_SUPPORTED, addr, val);
> + }
> + }
> +
> + /* correctable */
> + if (ranges_overlap(addr, len, pos + PCI_ERR_COR_STATUS, 4)) {
> + /* RW1CS */
> + pcie_w1c_long(dev, pos + PCI_ERR_COR_STATUS, PCI_ERR_COR_SUPPORTED,
> + addr, val);
> + }
> +
> + /* capability & control */
> + if (ranges_overlap(addr, len, pos + PCI_ERR_CAP, 4)) {
> + uint32_t err_cap = pci_get_long(dev->config + pos + PCI_ERR_CAP);
> + if (!(err_cap & PCI_ERR_CAP_MHRE)) {
> + pcie_aer_log_clear_all_err(&dev->aer_log);
> + }
> + }
> +}
> +
> +#define PCI_SEC_STATUS_RCV_SYSTEM_ERROR 0x4000
> +
> +void pcie_aer_write_config_vbridge(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len)
> +{
> + /* PCI_SEC_STATUS_RCV_SYSTEM_ERROR */
> + if (ranges_overlap(addr, len, PCI_STATUS, 2)) {
> + pcie_w1c_word(dev, PCI_SEC_STATUS, PCI_SEC_STATUS_RCV_SYSTEM_ERROR,
> + addr, val);
> + }
> +}
> +
> +static inline void pcie_aer_errmsg(PCIDevice *dev,
> + const struct pcie_aer_err_msg *msg)
> +{
> + assert(dev->exp);
> + assert(dev->exp->aer_errmsg);
> + dev->exp->aer_errmsg(dev, msg);
> +}
> +
> +static AER_ERR_MSG_RESULT
> +pcie_aer_errmsg_alldev(PCIDevice *dev, const struct pcie_aer_err_msg *msg)
> +{
> + uint16_t cmd = pci_get_word(dev->config + PCI_COMMAND);
> + bool transmit1 =
> + pcie_aer_err_msg_is_uncor(msg) && (cmd & PCI_COMMAND_SERR);
> + uint32_t pos = pci_pcie_cap(dev);
> + uint32_t devctl = pci_get_word(dev->config + pos + PCI_EXP_DEVCTL);
> + bool transmit2 = msg->severity & devctl;
> + PCIDevice *parent_port;
> +
> + if (transmit1) {
> + if (pcie_aer_err_msg_is_uncor(msg)) {
> + /* Signaled System Error */
> + uint8_t *status = dev->config + PCI_STATUS;
> + pci_set_word(status,
> + pci_get_word(status) | PCI_STATUS_SIG_SYSTEM_ERROR);
> + }
> + }
> +
> + if (!(transmit1 || transmit2)) {
> + return AER_ERR_MSG_MASKED;
> + }
> +
> + /* send up error message */
> + if (pci_is_express(dev) &&
> + pcie_cap_get_type(dev) == PCI_EXP_TYPE_ROOT_PORT) {
> + /* Root port notify system itself,
> + or send the error message to root complex event collector. */
> + /*
> + * if root port is associated to event collector, set
> + * parent_port = root complex event collector
> + * For now root complex event collector isn't supported.
> + */
> + parent_port = NULL;
> + } else {
> + parent_port = pci_bridge_get_device(dev->bus);
> + }
> + if (parent_port) {
> + if (!pci_is_express(parent_port)) {
> + /* What to do? */
> + return AER_ERR_MSG_MASKED;
> + }
> + pcie_aer_errmsg(parent_port, msg);
> + }
> + return AER_ERR_MSG_SENT;
> +}
> +
> +static AER_ERR_MSG_RESULT
> +pcie_aer_errmsg_vbridge(PCIDevice *dev, const struct pcie_aer_err_msg *msg)
> +{
> + uint16_t bridge_control = pci_get_word(dev->config + PCI_BRIDGE_CONTROL);
> +
> + if (pcie_aer_err_msg_is_uncor(msg)) {
> + /* Received System Error */
> + uint8_t *sec_status = dev->config + PCI_SEC_STATUS;
> + pci_set_word(sec_status,
> + pci_get_word(sec_status) |
> + PCI_SEC_STATUS_RCV_SYSTEM_ERROR);
> + }
> +
> + if (!(bridge_control & PCI_BRIDGE_CTL_SERR)) {
> + return AER_ERR_MSG_MASKED;
> + }
> + return pcie_aer_errmsg_alldev(dev, msg);
> +}
> +
> +static AER_ERR_MSG_RESULT
> +pcie_aer_errmsg_root_port(PCIDevice *dev, const struct pcie_aer_err_msg *msg)
> +{
> + AER_ERR_MSG_RESULT ret;
> + uint16_t cmd;
> + uint8_t *aer_cap;
> + uint32_t root_cmd;
> + uint32_t root_sta;
> + bool trigger;
> +
> + ret = pcie_aer_errmsg_vbridge(dev, msg);
> + if (ret != AER_ERR_MSG_SENT) {
> + return ret;
> + }
> +
> + ret = AER_ERR_MSG_MASKED;
> + cmd = pci_get_word(dev->config + PCI_COMMAND);
> + aer_cap = dev->config + pcie_aer_cap(dev);
> + root_cmd = pci_get_long(aer_cap + PCI_ERR_ROOT_COMMAND);
> + root_sta = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
> + trigger = false;
> +
> + if (cmd & PCI_COMMAND_SERR) {
> + /* System Error. Platform Specific */
> + /* ret = AER_ERR_MSG_SENT; */
> + }
> +
> + /* Errro Message Received: Root Error Status register */
> + switch (msg->severity) {
> + case AER_ERR_COR:
> + if (root_sta & PCI_ERR_ROOT_COR_RCV) {
> + root_sta |= PCI_ERR_ROOT_MULTI_COR_RCV;
> + } else {
> + if (root_cmd & PCI_ERR_ROOT_CMD_COR_EN) {
> + trigger = true;
> + }
> + pci_set_word(aer_cap + PCI_ERR_ROOT_COR_SRC, msg->source_id);
> + }
> + root_sta |= PCI_ERR_ROOT_COR_RCV;
> + break;
> + case AER_ERR_NONFATAL:
> + if (!(root_sta & PCI_ERR_ROOT_NONFATAL_RCV) &&
> + root_cmd & PCI_ERR_ROOT_CMD_NONFATAL_EN) {
> + trigger = true;
> + }
> + root_sta |= PCI_ERR_ROOT_NONFATAL_RCV;
> + break;
> + case AER_ERR_FATAL:
> + if (!(root_sta & PCI_ERR_ROOT_FATAL_RCV) &&
> + root_cmd & PCI_ERR_ROOT_CMD_FATAL_EN) {
> + trigger = true;
> + }
> + if (!(root_sta & PCI_ERR_ROOT_UNCOR_RCV)) {
> + root_sta |= PCI_ERR_ROOT_FIRST_FATAL;
> + }
> + root_sta |= PCI_ERR_ROOT_FATAL_RCV;
> + break;
> + }
> + if (pcie_aer_err_msg_is_uncor(msg)) {
> + if (root_sta & PCI_ERR_ROOT_UNCOR_RCV) {
> + root_sta |= PCI_ERR_ROOT_MULTI_UNCOR_RCV;
> + } else {
> + pci_set_word(aer_cap + PCI_ERR_ROOT_SRC, msg->source_id);
> + }
> + root_sta |= PCI_ERR_ROOT_UNCOR_RCV;
> + }
> + pci_set_long(aer_cap + PCI_ERR_ROOT_STATUS, root_sta);
> +
> + if (root_cmd & msg->severity) {
> + /* Error Interrupt(INTx or MSI) */
> + pcie_aer_root_notify(dev, trigger, 1);
> + ret = AER_ERR_MSG_SENT;
> + }
> + return ret;
> +}
> +
> +static void pcie_aer_update_log(PCIDevice *dev, const struct pcie_aer_err
> *err)
> +{
> + uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
> + uint8_t first_bit = ffsl(err->status) - 1;
> + uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
> + int i;
> + uint32_t dw;
> +
> + errcap &= ~(PCI_ERR_CAP_FEP_MASK | PCI_ERR_CAP_TLP);
> + errcap |= PCI_ERR_CAP_FEP(first_bit);
> +
> + if (err->flags & PCIE_AER_ERR_HEADER_VALID) {
> + for (i = 0; i < ARRAY_SIZE(err->header); ++i) {
> + /* 7.10.8 Header Log Register */
> + cpu_to_be32wu(&dw, err->header[i]);
> + memcpy(aer_cap + PCI_ERR_HEADER_LOG + sizeof(err->header[0]) * i,
> + &dw, sizeof(dw));
> + }
> + } else {
> + assert(!(err->flags & PCIE_AER_ERR_TLP_PRESENT));
> + memset(aer_cap + PCI_ERR_HEADER_LOG, 0, sizeof(err->header));
> + }
> +
> + if ((err->flags & PCIE_AER_ERR_TLP_PRESENT) &&
> + (pci_get_long(dev->config + pci_pcie_cap(dev) + PCI_EXP_DEVCTL2) &
> + PCI_EXP_DEVCAP2_EETLPP)) {
> + for (i = 0; i < ARRAY_SIZE(err->prefix); ++i) {
> + /* 7.10.12 tlp prefix log register */
> + cpu_to_be32wu(&dw, err->prefix[i]);
> + memcpy(aer_cap + PCI_ERR_TLP_PREFIX_LOG +
> + sizeof(err->prefix[0]) * i, &dw, sizeof(dw));
> + }
> + errcap |= PCI_ERR_CAP_TLP;
> + } else {
> + memset(aer_cap + PCI_ERR_TLP_PREFIX_LOG, 0, sizeof(err->prefix));
> + }
> + pci_set_long(aer_cap + PCI_ERR_CAP, errcap);
> +}
> +
> +static void pcie_aer_clear_log(PCIDevice *dev)
> +{
> + struct pcie_aer_err *err;
> + uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
> + uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
> +
> + errcap &= ~(PCI_ERR_CAP_FEP_MASK | PCI_ERR_CAP_TLP);
> + pci_set_long(aer_cap + PCI_ERR_CAP, errcap);
> +
> + memset(aer_cap + PCI_ERR_HEADER_LOG, 0, sizeof(err->header));
> + memset(aer_cap + PCI_ERR_TLP_PREFIX_LOG, 0, sizeof(err->prefix));
> +}
> +
> +static int pcie_aer_record_error(PCIDevice *dev,
> + const struct pcie_aer_err *err)
> +{
> + uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
> + uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
> + int fep = PCI_ERR_CAP_FEP(errcap);
> +
> + if (errcap & PCI_ERR_CAP_MHRE &&
> + (pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) & (1ULL << fep))) {
> + /* Not first error. queue error */
> + if (pcie_aer_log_add_err(&dev->aer_log, err) < 0) {
> + /* overflow */
> + return -1;
> + }
> + return 0;
> + }
> +
> + pcie_aer_update_log(dev, err);
> + return 0;
> +}
> +
> +static void pcie_aer_clear_error(PCIDevice *dev)
> +{
> + uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
> + uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
> + uint32_t old_err = (1UL << PCI_ERR_CAP_FEP(errcap));
> + struct pcie_aer_log *aer_log = &dev->aer_log;
> + const struct pcie_aer_err *err;
> + uint32_t consumer;
> +
> + if (!(errcap & PCI_ERR_CAP_MHRE) || pcie_aer_log_empty(aer_log)) {
> + pcie_aer_clear_log(dev);
> + pci_set_long(aer_cap + PCI_ERR_UNCOR_STATUS,
> + pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) &
> ~old_err);
> + return;
> + }
> +
> + /* if no same error is queued, clear bit in uncorrectable error status */
> + for (consumer = dev->aer_log.consumer;
> + !pcie_aer_log_empty_index(dev->aer_log.producer, consumer);
> + consumer = pcie_aer_log_next(consumer, dev->aer_log.log_max)) {
> + if (dev->aer_log.log[consumer].status & old_err) {
> + old_err = 0;
> + break;
> + }
> + }
> + if (old_err) {
> + pci_set_long(aer_cap + PCI_ERR_UNCOR_STATUS,
> + pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) &
> ~old_err);
> + }
> +
> + err = pcie_aer_log_del_err(aer_log);
> + pcie_aer_update_log(dev, err);
> +}
> +
> +/*
> + * non-Function specific error must be recorded in all functions.
> + * It is the responsibility of the caller of this function.
> + * It is also caller's responsiblity to determine which function should
> + * report the rerror.
> + *
> + * 6.2.4 Error Logging
> + * 6.2.5 Sqeucne of Device Error Signaling and Logging Operations
> + * table 6-2: Flowchard Showing Sequence of Device Error Signaling and
> Logging
> + * Operations
> + *
> + * Although this implementation can be shortened/optimized, this is kept
> + * parallel to table 6-2.
> + */
> +void pcie_aer_inject_error(PCIDevice *dev, const struct pcie_aer_err *err)
> +{
> + uint8_t *exp_cap;
> + uint8_t *aer_cap = NULL;
> + uint32_t devctl = 0;
> + uint32_t devsta = 0;
> + uint32_t status = err->status;
> + uint32_t mask;
> + bool is_unsupported_request =
> + (!(err->flags & PCIE_AER_ERR_IS_CORRECTABLE) &&
> + err->status == PCI_ERR_UNC_UNSUP);
> + bool is_advisory_nonfatal = false; /* for advisory non-fatal error */
> + uint32_t uncor_status = 0; /* for advisory non-fatal error */
> + struct pcie_aer_err_msg msg;
> + int is_header_log_overflowed = 0;
> +
> + if (!pci_is_express(dev)) {
> + /* What to do? */
> + return;
> + }
> +
> + if (err->flags & PCIE_AER_ERR_IS_CORRECTABLE) {
> + status &= PCI_ERR_COR_SUPPORTED;
> + } else {
> + status &= PCI_ERR_UNC_SUPPORTED;
> + }
> + if (!status || status & (status - 1)) {
> + /* invalid status bit. one and only one bit must be set */
> + return;
> + }
> +
> + exp_cap = dev->config + pci_pcie_cap(dev);
> + if (dev->exp->aer_cap) {
> + aer_cap = dev->config + pcie_aer_cap(dev);
> + devctl = pci_get_long(exp_cap + PCI_EXP_DEVCTL);
> + devsta = pci_get_long(exp_cap + PCI_EXP_DEVSTA);
> + }
> + if (err->flags & PCIE_AER_ERR_IS_CORRECTABLE) {
> + correctable_error:
> + devsta |= PCI_EXP_DEVSTA_CED;
> + if (is_unsupported_request) {
> + devsta |= PCI_EXP_DEVSTA_URD;
> + }
> + pci_set_word(exp_cap + PCI_EXP_DEVSTA, devsta);
> +
> + if (aer_cap) {
> + pci_set_long(aer_cap + PCI_ERR_COR_STATUS,
> + pci_get_long(aer_cap + PCI_ERR_COR_STATUS) |
> status);
> + mask = pci_get_long(aer_cap + PCI_ERR_COR_MASK);
> + if (mask & status) {
> + return;
> + }
> + if (is_advisory_nonfatal) {
> + uint32_t uncor_mask =
> + pci_get_long(aer_cap + PCI_ERR_UNCOR_MASK);
> + if (!(uncor_mask & uncor_status)) {
> + is_header_log_overflowed = pcie_aer_record_error(dev,
> err);
> + }
> + pci_set_long(aer_cap + PCI_ERR_UNCOR_STATUS,
> + pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) |
> + uncor_status);
> + }
> + }
> +
> + if (is_unsupported_request && !(devctl & PCI_EXP_DEVCTL_URRE)) {
> + return;
> + }
> + if (!(devctl & PCI_EXP_DEVCTL_CERE)) {
> + return;
> + }
> + msg.severity = AER_ERR_COR;
> + } else {
> + bool is_fatal =
> + (pcie_aer_uncor_default_severity(status) == AER_ERR_FATAL);
> + uint16_t cmd;
> +
> + if (aer_cap) {
> + is_fatal = status & pci_get_long(aer_cap + PCI_ERR_UNCOR_SEVER);
> + }
> + if (!is_fatal && (err->flags & PCIE_AER_ERR_MAYBE_ADVISORY)) {
> + is_advisory_nonfatal = true;
> + uncor_status = status;
> + status = PCI_ERR_COR_ADV_NONFATAL;
> + goto correctable_error;
> + }
> + if (is_fatal) {
> + devsta |= PCI_EXP_DEVSTA_FED;
> + } else {
> + devsta |= PCI_EXP_DEVSTA_NFED;
> + }
> + if (is_unsupported_request) {
> + devsta |= PCI_EXP_DEVSTA_URD;
> + }
> + pci_set_long(exp_cap + PCI_EXP_DEVSTA, devsta);
> +
> + if (aer_cap) {
> + mask = pci_get_long(aer_cap + PCI_ERR_UNCOR_MASK);
> + if (mask & status) {
> + pci_set_long(aer_cap + PCI_ERR_UNCOR_STATUS,
> + pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) |
> + status);
> + return;
> + }
> +
> + is_header_log_overflowed = pcie_aer_record_error(dev, err);
> + pci_set_long(aer_cap + PCI_ERR_UNCOR_STATUS,
> + pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) |
> + status);
> + }
> +
> + cmd = pci_get_word(dev->config + PCI_COMMAND);
> + if (is_unsupported_request &&
> + !(devctl & PCI_EXP_DEVCTL_URRE) && !(cmd & PCI_COMMAND_SERR)) {
> + return;
> + }
> + if (is_fatal) {
> + if (!((cmd & PCI_COMMAND_SERR) ||
> + (devctl & PCI_EXP_DEVCTL_FERE))) {
> + return;
> + }
> + msg.severity = AER_ERR_FATAL;
> + } else {
> + if (!((cmd & PCI_COMMAND_SERR) ||
> + (devctl & PCI_EXP_DEVCTL_NFERE))) {
> + return;
> + }
> + msg.severity = AER_ERR_NONFATAL;
> + }
> + }
> +
> + /* send up error message */
> + msg.source_id = err->source_id;
> + pcie_aer_errmsg(dev, &msg);
> +
> + if (is_header_log_overflowed) {
> + struct pcie_aer_err header_log_overflow = {
> + .status = PCI_ERR_COR_HL_OVERFLOW,
> + .flags = PCIE_AER_ERR_IS_CORRECTABLE,
> + .header = {0, 0, 0, 0},
> + .prefix = {0, 0, 0, 0},
> + };
> + pcie_aer_inject_error(dev, &header_log_overflow);
> + }
> +}
> +
> +/* aer root error command/status */
> +#define PCI_ERR_ROOT_CMD_EN_MASK (PCI_ERR_ROOT_CMD_COR_EN | \
> + PCI_ERR_ROOT_CMD_NONFATAL_EN | \
> + PCI_ERR_ROOT_CMD_FATAL_EN)
> +
> +#define PCI_ERR_ROOT_IRQ_SHIFT 26
> +#define PCI_ERR_ROOT_IRQ 0xf8000000
> +#define PCI_ERR_ROOT_STATUS_REPORT_MASK (PCI_ERR_ROOT_COR_RCV | \
> + PCI_ERR_ROOT_MULTI_COR_RCV | \
> + PCI_ERR_ROOT_UNCOR_RCV | \
> + PCI_ERR_ROOT_MULTI_UNCOR_RCV | \
> + PCI_ERR_ROOT_FIRST_FATAL | \
> + PCI_ERR_ROOT_NONFATAL_RCV | \
> + PCI_ERR_ROOT_FATAL_RCV)
> +
> +void pcie_aer_root_set_vector(PCIDevice *dev, uint8_t vector)
> +{
> + uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
> + uint32_t root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
> + root_status &= ~PCI_ERR_ROOT_IRQ;
> + root_status |=
> + (((uint32_t)vector) << PCI_ERR_ROOT_IRQ_SHIFT) & PCI_ERR_ROOT_IRQ;
> + pci_set_long(aer_cap + PCI_ERR_ROOT_STATUS, root_status);
> +}
> +
> +static uint8_t pcie_aer_root_get_vector(PCIDevice *dev)
> +{
> + uint8_t *aer_cap = dev->config + pcie_aer_cap(dev);
> + uint32_t root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
> + return (root_status & PCI_ERR_ROOT_IRQ) >> PCI_ERR_ROOT_IRQ_SHIFT;
> +}
> +
> +static void pcie_aer_root_notify(PCIDevice *dev, bool trigger, int level)
> +{
> + pcie_notify(dev, pcie_aer_root_get_vector(dev), trigger, level);
> +}
> +
> +void pcie_aer_root_init(PCIDevice *dev)
> +{
> + uint16_t pos = pcie_aer_cap(dev);
> +
> + pci_set_long(dev->wmask + pos + PCI_ERR_ROOT_COMMAND,
> + PCI_ERR_ROOT_CMD_EN_MASK);
> + dev->exp->aer_errmsg = pcie_aer_errmsg_root_port;
> +}
> +
> +void pcie_aer_root_reset(PCIDevice *dev)
> +{
> + uint8_t* aer_cap = dev->config + pcie_aer_cap(dev);
> +
> + pci_set_long(aer_cap + PCI_ERR_ROOT_COMMAND, 0);
> +
> + /*
> + * Advanced Error Interrupt Message Number in Root Error Status Register
> + * must be updated by chip dependent code.
> + */
> +}
> +
> +static bool pcie_aer_root_does_trigger(uint32_t cmd, uint32_t sta)
> +{
> + return
> + ((cmd & PCI_ERR_ROOT_CMD_COR_EN) && (sta & PCI_ERR_ROOT_COR_RCV)) ||
> + ((cmd & PCI_ERR_ROOT_CMD_NONFATAL_EN) &&
> + (sta & PCI_ERR_ROOT_NONFATAL_RCV)) ||
> + ((cmd & PCI_ERR_ROOT_CMD_FATAL_EN) && (sta &
> PCI_ERR_ROOT_FATAL_RCV));
> +}
> +
> +void pcie_aer_root_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len,
> + uint32_t root_cmd_prev)
> +{
> + uint16_t pos = pcie_aer_cap(dev);
> + uint8_t *aer_cap = dev->config + pos;
> + uint32_t root_status;
> +
> + if (ranges_overlap(addr, len, pos + PCI_ERR_ROOT_STATUS, 4)) {
> + /* RW1CS */
> + pcie_w1c_long(dev, pos + PCI_ERR_ROOT_STATUS,
> + PCI_ERR_ROOT_STATUS_REPORT_MASK, addr, val);
> + }
> +
> + /* root command */
> + if (ranges_overlap(addr, len, pos + PCI_ERR_ROOT_COMMAND, 4)) {
> + uint32_t root_cmd = pci_get_long(aer_cap + PCI_ERR_ROOT_COMMAND);
> + if (root_cmd & PCI_ERR_ROOT_CMD_EN_MASK) {
> + bool trigger = false;
> + int level = 0;
> + uint32_t root_cmd_set = (root_cmd_prev ^ root_cmd) & root_cmd;
> +
> + /* 0 -> 1 */
> + root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
> + if (pcie_aer_root_does_trigger(root_cmd_set, root_status)) {
> + trigger = true;
> + }
> + if (pcie_aer_root_does_trigger(root_cmd, root_status)) {
> + level = 1;
> + }
> + pcie_aer_root_notify(dev, trigger, level);
> + }
> + }
> +}
> +
> +static const VMStateDescription vmstate_pcie_aer_err = {
> + .name = "PCIE_AER_ERROR",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .minimum_version_id_old = 1,
> + .fields = (VMStateField[]) {
> + VMSTATE_UINT32(status, struct pcie_aer_err),
> + VMSTATE_UINT16(source_id, struct pcie_aer_err),
> + VMSTATE_UINT16(flags, struct pcie_aer_err),
> + VMSTATE_UINT32_ARRAY(header, struct pcie_aer_err, 4),
> + VMSTATE_UINT32_ARRAY(prefix, struct pcie_aer_err, 4),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +#define VMSTATE_PCIE_AER_ERRS(_field, _state, _field_num, _vmsd, _type) { \
> + .name = (stringify(_field)), \
> + .version_id = 0, \
> + .num_offset = vmstate_offset_value(_state, _field_num, uint16_t), \
> + .size = sizeof(_type), \
> + .vmsd = &(_vmsd), \
> + .flags = VMS_POINTER | VMS_VARRAY_UINT16 | VMS_STRUCT, \
> + .offset = vmstate_offset_pointer(_state, _field, _type), \
> +}
> +
> +const VMStateDescription vmstate_pcie_aer_log = {
> + .name = "PCIE_AER_ERROR_LOG",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .minimum_version_id_old = 1,
> + .fields = (VMStateField[]) {
> + VMSTATE_UINT32(producer, struct pcie_aer_log),
> + VMSTATE_UINT32(consumer, struct pcie_aer_log),
> + VMSTATE_UINT16(log_max, struct pcie_aer_log),
> + VMSTATE_PCIE_AER_ERRS(log, struct pcie_aer_log, log_max,
> + vmstate_pcie_aer_err, struct pcie_aer_err),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> diff --git a/hw/pcie.h b/hw/pcie.h
> new file mode 100644
> index 0000000..07f42c6
> --- /dev/null
> +++ b/hw/pcie.h
> @@ -0,0 +1,186 @@
> +/*
> + * pcie.h
> + *
> + * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
> + * VA Linux Systems Japan K.K.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef QEMU_PCIE_H
> +#define QEMU_PCIE_H
> +
> +#include "hw.h"
> +
> +enum PCIExpressIndicator {
> + /* for attention and power indicator */
> + PCI_EXP_HP_IND_RESERVED = 0b00,
> + PCI_EXP_HP_IND_ON = 0b01,
> + PCI_EXP_HP_IND_BLINK = 0b10,
> + PCI_EXP_HP_IND_OFF = 0b11,
> +};
> +
> +enum PCIExpressHotPlugEvent {
> + /* the bits match the bits in Slot Control/Status registers.
> + * PCI_EXP_HP_EV_xxx = PCI_EXP_SLTCTL_xxxE = PCI_EXP_SLTSTA_xxx
> + */
> + PCI_EXP_HP_EV_ABP = 0b00001, /* attention button preseed */
> + PCI_EXP_HP_EV_PDC = 0b01000, /* presence detect changed */
> + PCI_EXP_HP_EV_CCI = 0b10000, /* command completed */
> +
> + PCI_EXP_HP_EV_SUPPORTED = 0b11001, /* supported event mask */
> + /* events not listed aren't supported */
> +};
> +
> +typedef void (*pcie_flr_fn)(PCIDevice *dev);
> +
> +struct pcie_aer_err_msg;
> +enum AER_ERR_MSG_RESULT {
> + AER_ERR_MSG_MASKED,
> + AER_ERR_MSG_SENT,
> +};
> +typedef enum AER_ERR_MSG_RESULT AER_ERR_MSG_RESULT;
> +typedef AER_ERR_MSG_RESULT (*pcie_aer_errmsg_fn)(PCIDevice *dev, const struct
> pcie_aer_err_msg *msg);
> +
> +struct PCIExpressDevice {
> + /* Offset of express capability in config space */
> + uint8_t exp_cap;
> +
> + /* FLR */
> + pcie_flr_fn flr;
> +
> + /* AER */
> + uint16_t aer_cap;
> + pcie_aer_errmsg_fn aer_errmsg;
> +};
> +
> +struct pcie_aer_log {
> + uint32_t producer;
> + uint32_t consumer;
> +
> +#define PCIE_AER_LOG_MAX_DEFAULT 8
> +#define PCIE_AER_LOG_MAX_MAX 128 /* what is appropriate? */
> +#define PCIE_AER_LOG_MAX_UNSET (~(uint16_t)0)
> + uint16_t log_max;
> +
> + struct pcie_aer_err *log;
> +};
> +
> +extern const VMStateDescription vmstate_pcie_aer_log;
> +
> +/* PCI express capability helper functions */
> +int pci_pcie_cap_init(PCIDevice *dev,
> + uint8_t offset, uint8_t type, uint8_t port);
> +int pci_pcie_cap_exit(PCIDevice *dev);
> +uint8_t pcie_cap_get_type(const PCIDevice *dev);
> +void pcie_cap_flags_set_vector(PCIDevice *dev, uint8_t vector);
> +uint8_t pcie_cap_flags_get_vector(PCIDevice *dev);
> +
> +void pcie_cap_deverr_init(PCIDevice *dev);
> +void pcie_cap_deverr_reset(PCIDevice *dev);
> +void pcie_cap_deverr_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len);
> +
> +void pcie_cap_slot_init(PCIDevice *dev, uint16_t slot);
> +void pcie_cap_slot_reset(PCIDevice *dev);
> +void pcie_cap_slot_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len,
> + uint16_t sltctl_prev);
> +void pcie_cap_slot_push_attention_button(PCIDevice *dev);
> +
> +void pcie_cap_root_init(PCIDevice *dev);
> +void pcie_cap_root_reset(PCIDevice *dev);
> +
> +void pcie_cap_flr_init(PCIDevice *dev, pcie_flr_fn flr);
> +void pcie_cap_flr_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len);
> +
> +void pcie_cap_ari_init(PCIDevice *dev);
> +void pcie_cap_ari_reset(PCIDevice *dev);
> +bool pcie_cap_is_ari_enabled(const PCIDevice *dev);
> +
> +/* PCI express extended capability helper functions */
> +uint16_t pcie_find_ext_capability(PCIDevice *dev, uint16_t cap_id);
> +int pcie_add_ext_capability(PCIDevice *dev,
> + uint16_t cap_id, uint8_t cap_ver, uint16_t size);
> +int pcie_append_ext_capability(PCIDevice *dev,
> + uint16_t cap_id, uint8_t cap_ver,
> + uint16_t offset, uint16_t size);
> +void pcie_del_ext_capability(PCIDevice *dev, uint16_t cap_id, uint16_t size);
> +void pcie_reserve_ext_capability(PCIDevice *dev,
> + uint16_t offset, uint16_t size);
> +
> +int pcie_ari_init(PCIDevice *dev, uint16_t offset, uint16_t nextfn);
> +
> +/* PCI express extended capabilities */
> +
> +/* AER */
> +/* aer error severity */
> +enum PCIE_AER_SEVERITY {
> + /* those value are same as
> + * Root error command register in aer extended cap and
> + * root control register in pci express cap.
> + */
> + AER_ERR_COR = 0x1,
> + AER_ERR_NONFATAL = 0x2,
> + AER_ERR_FATAL = 0x4,
> +};
> +
> +/* aer error message: error signaling message has only error sevirity and
> + source id. See 2.2.8.3 error signaling messages */
> +struct pcie_aer_err_msg {
> + enum PCIE_AER_SEVERITY severity;
> + uint16_t source_id; /* bdf */
> +};
> +
> +static inline bool
> +pcie_aer_err_msg_is_uncor(const struct pcie_aer_err_msg *msg)
> +{
> + return msg->severity == AER_ERR_NONFATAL || msg->severity ==
> AER_ERR_FATAL;
> +}
> +
> +/* error */
> +struct pcie_aer_err {
> + uint32_t status; /* error status bits */
> + uint16_t source_id; /* bdf */
> +
> +#define PCIE_AER_ERR_IS_CORRECTABLE 0x1 /* correctable/uncorrectable
> */
> +#define PCIE_AER_ERR_MAYBE_ADVISORY 0x2 /* maybe advisory non-fatal
> */
> +#define PCIE_AER_ERR_HEADER_VALID 0x4 /* TLP header is logged */
> +#define PCIE_AER_ERR_TLP_PRESENT 0x8 /* TLP Prefix is logged */
> + uint16_t flags;
> +
> + uint32_t header[4]; /* TLP header */
> + uint32_t prefix[4]; /* TLP header prefix */
> +};
> +
> +int pcie_aer_init(PCIDevice *dev, uint16_t offset);
> +void pcie_aer_exit(PCIDevice *dev);
> +void pcie_aer_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len);
> +void pcie_aer_write_config_vbridge(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len);
> +
> +/* aer root port */
> +void pcie_aer_root_set_vector(PCIDevice *dev, uint8_t vector);
> +void pcie_aer_root_init(PCIDevice *dev);
> +void pcie_aer_root_reset(PCIDevice *dev);
> +void pcie_aer_root_write_config(PCIDevice *dev,
> + uint32_t addr, uint32_t val, int len,
> + uint32_t root_cmd_prev);
> +
> +/* error injection */
> +void pcie_aer_inject_error(PCIDevice *dev, const struct pcie_aer_err *err);
> +
> +#endif /* QEMU_PCIE_H */
> diff --git a/qemu-common.h b/qemu-common.h
> index d735235..6d9ee26 100644
> --- a/qemu-common.h
> +++ b/qemu-common.h
> @@ -219,6 +219,7 @@ typedef struct PCIHostState PCIHostState;
> typedef struct PCIExpressHost PCIExpressHost;
> typedef struct PCIBus PCIBus;
> typedef struct PCIDevice PCIDevice;
> +typedef struct PCIExpressDevice PCIExpressDevice;
> typedef struct PCIBridge PCIBridge;
> typedef struct SerialState SerialState;
> typedef struct IRQState *qemu_irq;
^ permalink raw reply [flat|nested] 25+ messages in thread
* [Qemu-devel] Re: [PATCH v2 7/9] pcie/hotplug: glue pushing attention button command. pcie_abp
2010-09-08 10:34 ` [Qemu-devel] " Michael S. Tsirkin
@ 2010-09-09 3:43 ` Isaku Yamahata
0 siblings, 0 replies; 25+ messages in thread
From: Isaku Yamahata @ 2010-09-09 3:43 UTC (permalink / raw)
To: Michael S. Tsirkin; +Cc: skandasa, adhyas, etmartin, qemu-devel, wexu2
On Wed, Sep 08, 2010 at 01:34:23PM +0300, Michael S. Tsirkin wrote:
> On Wed, Sep 08, 2010 at 04:39:40PM +0900, Isaku Yamahata wrote:
> > glue to pcie_abp monitor command.
> >
> > Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
>
> We also need to glue the LED so it's status both is reported
> and can be polled from monitor?
Yes, it's on my TODO list. But low priority.
QMP event would be desirable on LED status change.
>
> > ---
> > hw/pcie_port.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
> > qemu-monitor.hx | 14 +++++++++
> > sysemu.h | 4 +++
> > 3 files changed, 100 insertions(+), 0 deletions(-)
> >
> > diff --git a/hw/pcie_port.c b/hw/pcie_port.c
> > index d2b1f38..0aa8c53 100644
> > --- a/hw/pcie_port.c
> > +++ b/hw/pcie_port.c
> > @@ -18,6 +18,10 @@
> > * with this program; if not, see <http://www.gnu.org/licenses/>.
> > */
> >
> > +#include "qemu-objects.h"
> > +#include "sysemu.h"
> > +#include "monitor.h"
> > +#include "pcie.h"
> > #include "pcie_port.h"
> >
> > void pcie_port_init_reg(PCIDevice *d)
> > @@ -104,3 +108,81 @@ void pcie_chassis_del_slot(PCIESlot *s)
> > {
> > QLIST_REMOVE(s, next);
> > }
> > +
> > +/**************************************************************************
> > + * glue for qemu monitor
> > + */
> > +
> > +/* Parse [<chassis>.]<slot>, return -1 on error */
> > +static int pcie_parse_slot_addr(const char* slot_addr,
> > + uint8_t *chassisp, uint16_t *slotp)
> > +{
> > + const char *p;
> > + char *e;
> > + unsigned long val;
> > + unsigned long chassis = 0;
> > + unsigned long slot;
> > +
> > + p = slot_addr;
> > + val = strtoul(p, &e, 0);
> > + if (e == p) {
> > + return -1;
> > + }
> > + if (*e == '.') {
> > + chassis = val;
> > + p = e + 1;
> > + val = strtoul(p, &e, 0);
> > + if (e == p) {
> > + return -1;
> > + }
> > + }
> > + slot = val;
> > +
> > + if (*e) {
> > + return -1;
> > + }
> > +
> > + if (chassis > 0xff || slot > 0xffff) {
> > + return -1;
> > + }
> > +
> > + *chassisp = chassis;
> > + *slotp = slot;
> > + return 0;
> > +}
> > +
> > +void pcie_attention_button_push_print(Monitor *mon, const QObject *data)
> > +{
> > + QDict *qdict;
> > +
> > + assert(qobject_type(data) == QTYPE_QDICT);
> > + qdict = qobject_to_qdict(data);
> > +
> > + monitor_printf(mon, "OK chassis %d, slot %d\n",
> > + (int) qdict_get_int(qdict, "chassis"),
> > + (int) qdict_get_int(qdict, "slot"));
> > +}
> > +
> > +int pcie_attention_button_push(Monitor *mon, const QDict *qdict,
> > + QObject **ret_data)
> > +{
> > + const char* pcie_slot = qdict_get_str(qdict, "pcie_slot");
> > + uint8_t chassis;
> > + uint16_t slot;
> > + PCIESlot *s;
> > +
> > + if (pcie_parse_slot_addr(pcie_slot, &chassis, &slot) < 0) {
> > + monitor_printf(mon, "invalid pcie slot address %s\n", pcie_slot);
> > + return -1;
> > + }
> > + s = pcie_chassis_find_slot(chassis, slot);
> > + if (!s) {
> > + monitor_printf(mon, "slot is not found. %s\n", pcie_slot);
> > + return -1;
> > + }
> > + pcie_cap_slot_push_attention_button(&s->port.br.dev);
> > + *ret_data = qobject_from_jsonf("{ 'chassis': %d, 'slot': %d}",
> > + chassis, slot);
> > + assert(*ret_data);
> > + return 0;
> > +}
> > diff --git a/qemu-monitor.hx b/qemu-monitor.hx
> > index 2af3de6..02fbda1 100644
> > --- a/qemu-monitor.hx
> > +++ b/qemu-monitor.hx
> > @@ -1154,6 +1154,20 @@ Hot remove PCI device.
> > ETEXI
> >
> > {
> > + .name = "pcie_abp",
> > + .args_type = "pcie_slot:s",
> > + .params = "[<chassis>.]<slot>",
> > + .help = "push pci express attention button",
> > + .user_print = pcie_attention_button_push_print,
> > + .mhandler.cmd_new = pcie_attention_button_push,
> > + },
> > +
> > +STEXI
> > +@item pcie_abp
> > +Push PCI express attention button
> > +ETEXI
> > +
> > + {
> > .name = "host_net_add",
> > .args_type = "device:s,opts:s?",
> > .params = "tap|user|socket|vde|dump [options]",
> > diff --git a/sysemu.h b/sysemu.h
> > index 9c988bb..cca411d 100644
> > --- a/sysemu.h
> > +++ b/sysemu.h
> > @@ -150,6 +150,10 @@ extern unsigned int nb_prom_envs;
> > void pci_device_hot_add(Monitor *mon, const QDict *qdict);
> > void drive_hot_add(Monitor *mon, const QDict *qdict);
> > void do_pci_device_hot_remove(Monitor *mon, const QDict *qdict);
> > +/* pcie hotplug */
> > +void pcie_attention_button_push_print(Monitor *mon, const QObject *data);
> > +int pcie_attention_button_push(Monitor *mon, const QDict *qdict,
> > + QObject **ret_data);
> >
> > /* serial ports */
> >
> > --
> > 1.7.1.1
>
--
yamahata
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [Qemu-devel] Re: [PATCH v2 2/9] pcie: helper functions for pcie extended capability.
2010-09-08 17:38 ` Wei Xu
@ 2010-09-12 7:49 ` Blue Swirl
0 siblings, 0 replies; 25+ messages in thread
From: Blue Swirl @ 2010-09-12 7:49 UTC (permalink / raw)
To: Wei Xu; +Cc: skandasa, etmartin, mst, adhyas, qemu-devel, Isaku Yamahata
On Wed, Sep 8, 2010 at 5:38 PM, Wei Xu <wexu2@cisco.com> wrote:
> Isaku:
>
> For binary constants below, to achieve max compatibility with gcc versions,
> I recommend to change to hex (0x...):
Yes, binary constants were only added to GCC 4.3.x. Since they are
also GCC extensions with no obvious way to circumvent their use (as
with GCC attributes), they shouldn't be used.
^ permalink raw reply [flat|nested] 25+ messages in thread
* [Qemu-devel] Re: [PATCH v2 2/9] pcie: helper functions for pcie extended capability.
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 2/9] pcie: helper functions for pcie extended capability Isaku Yamahata
2010-09-08 10:31 ` [Qemu-devel] " Michael S. Tsirkin
2010-09-08 17:38 ` Wei Xu
@ 2010-09-12 13:26 ` Michael S. Tsirkin
2 siblings, 0 replies; 25+ messages in thread
From: Michael S. Tsirkin @ 2010-09-12 13:26 UTC (permalink / raw)
To: Isaku Yamahata; +Cc: skandasa, adhyas, etmartin, qemu-devel, wexu2
On Wed, Sep 08, 2010 at 04:39:35PM +0900, Isaku Yamahata wrote:
> +#define PCI_EXP_SLTCTL_AIC_SHIFT 6
> +#define PCI_EXP_SLTCTL_AIC_ON (PCI_EXP_HP_IND_ON << PCI_EXP_SLTCTL_AIC_SHIFT)
> +#define PCI_EXP_SLTCTL_AIC_BLINK (PCI_EXP_HP_IND_BLINK << PCI_EXP_SLTCTL_AIC_SHIFT)
> +#define PCI_EXP_SLTCTL_AIC_OFF (PCI_EXP_HP_IND_OFF << PCI_EXP_SLTCTL_AIC_SHIFT)
> +
> +#define PCI_EXP_SLTCTL_PIC_SHIFT 8
> +#define PCI_EXP_SLTCTL_PIC_ON (PCI_EXP_HP_IND_ON << PCI_EXP_SLTCTL_PIC_SHIFT)
> +#define PCI_EXP_SLTCTL_PIC_BLINK (PCI_EXP_HP_IND_BLINK << PCI_EXP_SLTCTL_PIC_SHIFT)
> +#define PCI_EXP_SLTCTL_PIC_OFF (PCI_EXP_HP_IND_OFF << PCI_EXP_SLTCTL_PIC_SHIFT)
It might be better to simply define the 6 macros we are using directly.
The duplication here is minimal, and I guess it will be easier get
them into linux this way.
--
MST
^ permalink raw reply [flat|nested] 25+ messages in thread
* [Qemu-devel] Re: [PATCH v2 2/9] pcie: helper functions for pcie extended capability.
2010-09-08 10:31 ` [Qemu-devel] " Michael S. Tsirkin
@ 2010-09-14 10:29 ` Isaku Yamahata
2010-09-14 12:23 ` Michael S. Tsirkin
2010-09-15 5:50 ` Isaku Yamahata
1 sibling, 1 reply; 25+ messages in thread
From: Isaku Yamahata @ 2010-09-14 10:29 UTC (permalink / raw)
To: Michael S. Tsirkin; +Cc: skandasa, adhyas, etmartin, qemu-devel, wexu2
On Wed, Sep 08, 2010 at 01:31:22PM +0300, Michael S. Tsirkin wrote:
> > +/*
> > + * RW1C: Write-1-to-clear
> > + * regiger written val result
> > + * 0 0 => 0
> > + * 1 0 => 1
> > + * 0 1 => 0
> > + * 1 1 => 0
> > + */
> > +static inline void pcie_w1c_long(PCIDevice *d, uint32_t pos, uint32_t mask,
> > + uint32_t addr, uint32_t val)
> > +{
> > + uint32_t written = pcie_written_val_long(addr, val, pos) & mask;
> > + uint32_t reg = pci_get_long(d->config + pos);
> > + reg &= ~written;
> > + pci_set_long(d->config + pos, reg);
> > +}
> > +
> > +static inline void pcie_w1c_word(PCIDevice *d, uint32_t pos, uint16_t mask,
> > + uint32_t addr, uint32_t val)
> > +{
> > + uint16_t written = pcie_written_val_word(addr, val, pos) & mask;
> > + uint16_t reg = pci_get_word(d->config + pos);
> > + reg &= ~written;
> > + pci_set_word(d->config + pos, reg);
> > +}
> > +
>
> So the SERR bit support IMO belongs in pci. And this means the W1C
> inline functions need to move there.
>
> pci.c implemented this in a simpler way, by shifting
> val by 8 bytes each time. Can we find a way to do it
> in a similar way? I'll try to think about it.
How about the following?
diff --git a/hw/pci.c b/hw/pci.c
index ceee291..6f1ce48 100644
--- a/hw/pci.c
+++ b/hw/pci.c
@@ -627,6 +627,7 @@ static void pci_config_alloc(PCIDevice *pci_dev)
pci_dev->config = qemu_mallocz(config_size);
pci_dev->cmask = qemu_mallocz(config_size);
pci_dev->wmask = qemu_mallocz(config_size);
+ pci_dev->w1cmask = qemu_mallocz(config_size);
pci_dev->used = qemu_mallocz(config_size);
}
@@ -635,6 +636,7 @@ static void pci_config_free(PCIDevice *pci_dev)
qemu_free(pci_dev->config);
qemu_free(pci_dev->cmask);
qemu_free(pci_dev->wmask);
+ qemu_free(pci_dev->w1cmask);
qemu_free(pci_dev->used);
}
@@ -997,7 +999,10 @@ void pci_default_write_config(PCIDevice *d, uint32_t addr, uint32_t val, int l)
for (i = 0; i < l && addr + i < config_size; val >>= 8, ++i) {
uint8_t wmask = d->wmask[addr + i];
+ uint8_t w1cmask = d->w1cmask[addr + i];
+ assert(!(wmask & w1cmask));
d->config[addr + i] = (d->config[addr + i] & ~wmask) | (val & wmask);
+ d->config[addr + i] &= ~(val & w1cmask);
}
if (ranges_overlap(addr, l, PCI_BASE_ADDRESS_0, 24) ||
ranges_overlap(addr, l, PCI_ROM_ADDRESS, 4) ||
diff --git a/hw/pci.h b/hw/pci.h
index e001f92..3509459 100644
--- a/hw/pci.h
+++ b/hw/pci.h
@@ -132,6 +132,15 @@ struct PCIDevice {
/* Used to implement R/W bytes */
uint8_t *wmask;
+ /* Used to implement RW1C bytes
+ * regiger written val result
+ * 0 0 => 0
+ * 1 0 => 1
+ * 0 1 => 0
+ * 1 1 => 0
+ */
+ uint8_t *w1cmask;
+
/* Used to allocate config space for capabilities. */
uint8_t *used;
> > +int pci_pcie_cap_init(PCIDevice *dev,
> > + uint8_t offset, uint8_t type, uint8_t port)
>
> I think we should have
> pcie_init
> that would init everything and be external,
> and this one should be static, and this one
> should be static.
Ah, please take a look at Figure 7-10 if possible.
Express capability structure is too complex to initialize
by single function.
So I introduce functions that applies to all devices,
functions that applies to root complex,
function that applies, ...
--
yamahata
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] Re: [PATCH v2 2/9] pcie: helper functions for pcie extended capability.
2010-09-14 10:29 ` Isaku Yamahata
@ 2010-09-14 12:23 ` Michael S. Tsirkin
0 siblings, 0 replies; 25+ messages in thread
From: Michael S. Tsirkin @ 2010-09-14 12:23 UTC (permalink / raw)
To: Isaku Yamahata; +Cc: skandasa, adhyas, etmartin, qemu-devel, wexu2
On Tue, Sep 14, 2010 at 07:29:15PM +0900, Isaku Yamahata wrote:
> On Wed, Sep 08, 2010 at 01:31:22PM +0300, Michael S. Tsirkin wrote:
> > > +/*
> > > + * RW1C: Write-1-to-clear
> > > + * regiger written val result
> > > + * 0 0 => 0
> > > + * 1 0 => 1
> > > + * 0 1 => 0
> > > + * 1 1 => 0
> > > + */
> > > +static inline void pcie_w1c_long(PCIDevice *d, uint32_t pos, uint32_t mask,
> > > + uint32_t addr, uint32_t val)
> > > +{
> > > + uint32_t written = pcie_written_val_long(addr, val, pos) & mask;
> > > + uint32_t reg = pci_get_long(d->config + pos);
> > > + reg &= ~written;
> > > + pci_set_long(d->config + pos, reg);
> > > +}
> > > +
> > > +static inline void pcie_w1c_word(PCIDevice *d, uint32_t pos, uint16_t mask,
> > > + uint32_t addr, uint32_t val)
> > > +{
> > > + uint16_t written = pcie_written_val_word(addr, val, pos) & mask;
> > > + uint16_t reg = pci_get_word(d->config + pos);
> > > + reg &= ~written;
> > > + pci_set_word(d->config + pos, reg);
> > > +}
> > > +
> >
> > So the SERR bit support IMO belongs in pci. And this means the W1C
> > inline functions need to move there.
> >
> > pci.c implemented this in a simpler way, by shifting
> > val by 8 bytes each time. Can we find a way to do it
> > in a similar way? I'll try to think about it.
>
> How about the following?
>
> diff --git a/hw/pci.c b/hw/pci.c
> index ceee291..6f1ce48 100644
> --- a/hw/pci.c
> +++ b/hw/pci.c
> @@ -627,6 +627,7 @@ static void pci_config_alloc(PCIDevice *pci_dev)
> pci_dev->config = qemu_mallocz(config_size);
> pci_dev->cmask = qemu_mallocz(config_size);
> pci_dev->wmask = qemu_mallocz(config_size);
> + pci_dev->w1cmask = qemu_mallocz(config_size);
> pci_dev->used = qemu_mallocz(config_size);
> }
>
> @@ -635,6 +636,7 @@ static void pci_config_free(PCIDevice *pci_dev)
> qemu_free(pci_dev->config);
> qemu_free(pci_dev->cmask);
> qemu_free(pci_dev->wmask);
> + qemu_free(pci_dev->w1cmask);
> qemu_free(pci_dev->used);
> }
>
> @@ -997,7 +999,10 @@ void pci_default_write_config(PCIDevice *d, uint32_t addr, uint32_t val, int l)
>
> for (i = 0; i < l && addr + i < config_size; val >>= 8, ++i) {
> uint8_t wmask = d->wmask[addr + i];
> + uint8_t w1cmask = d->w1cmask[addr + i];
> + assert(!(wmask & w1cmask));
> d->config[addr + i] = (d->config[addr + i] & ~wmask) | (val & wmask);
> + d->config[addr + i] &= ~(val & w1cmask);
> }
> if (ranges_overlap(addr, l, PCI_BASE_ADDRESS_0, 24) ||
> ranges_overlap(addr, l, PCI_ROM_ADDRESS, 4) ||
> diff --git a/hw/pci.h b/hw/pci.h
> index e001f92..3509459 100644
> --- a/hw/pci.h
> +++ b/hw/pci.h
> @@ -132,6 +132,15 @@ struct PCIDevice {
> /* Used to implement R/W bytes */
> uint8_t *wmask;
>
> + /* Used to implement RW1C bytes
Just add (Write 1 to Clear) and that's clear enough
I think, the table in the comment is not really helpful.
> + * regiger written val result
regiger -> register :)
> + * 0 0 => 0
> + * 1 0 => 1
> + * 0 1 => 0
> + * 1 1 => 0
> + */
> + uint8_t *w1cmask;
> +
> /* Used to allocate config space for capabilities. */
> uint8_t *used;
It will definitely work. Does this help PCIE code as well?
If yes it's probably worth it. If no we could
do it just for SERR in a simple way like this:
for (i = 0; i < l && addr + i < config_size; val >>= 8, ++i) {
.... (existing code) ...
if (addr + i == PCI_COMMAND + 1 && (val & (PCI_COMMAND_SERR >> 8))) {
d->config[addr + i] &= ~(PCI_COMMAND_SERR >> 8);
}
}
> > > +int pci_pcie_cap_init(PCIDevice *dev,
> > > + uint8_t offset, uint8_t type, uint8_t port)
> >
> > I think we should have
> > pcie_init
> > that would init everything and be external,
> > and this one should be static, and this one
> > should be static.
>
> Ah, please take a look at Figure 7-10 if possible.
> Express capability structure is too complex to initialize
> by single function.
>
> So I introduce functions that applies to all devices,
> functions that applies to root complex,
> function that applies, ...
Sure, it's good to split to smaller functions,
but all these can be static so this would not affect
the external API, it can still be clean and simple.
--
MST
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [Qemu-devel] Re: [PATCH v2 2/9] pcie: helper functions for pcie extended capability.
2010-09-08 10:31 ` [Qemu-devel] " Michael S. Tsirkin
2010-09-14 10:29 ` Isaku Yamahata
@ 2010-09-15 5:50 ` Isaku Yamahata
2010-09-15 13:05 ` Michael S. Tsirkin
1 sibling, 1 reply; 25+ messages in thread
From: Isaku Yamahata @ 2010-09-15 5:50 UTC (permalink / raw)
To: Michael S. Tsirkin; +Cc: skandasa, adhyas, etmartin, qemu-devel, wexu2
On Wed, Sep 08, 2010 at 01:31:22PM +0300, Michael S. Tsirkin wrote:
> > +
> > +static void pcie_notify(PCIDevice *dev, uint16_t vector,
> > + bool trigger, int level)
> > +{
> > + /* masking/masking interrupt is handled by upper layer.
> > + * i.e. msix_notify() for MSI-X
> > + * msi_notify() for MSI
> > + * pci_set_irq() for INTx
> > + */
>
> So this will send another interrupt when level is 0?
Yes. The condition that triggers MSI-X/MSI can be different from
the one that asserts INTx as you can see it in the following code.
trigger and level are set independently.
--
yamahata
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [Qemu-devel] Re: [PATCH v2 2/9] pcie: helper functions for pcie extended capability.
2010-09-15 5:50 ` Isaku Yamahata
@ 2010-09-15 13:05 ` Michael S. Tsirkin
2010-09-19 4:11 ` Isaku Yamahata
0 siblings, 1 reply; 25+ messages in thread
From: Michael S. Tsirkin @ 2010-09-15 13:05 UTC (permalink / raw)
To: Isaku Yamahata; +Cc: skandasa, adhyas, etmartin, qemu-devel, wexu2
On Wed, Sep 15, 2010 at 02:50:01PM +0900, Isaku Yamahata wrote:
> On Wed, Sep 08, 2010 at 01:31:22PM +0300, Michael S. Tsirkin wrote:
> > > +
> > > +static void pcie_notify(PCIDevice *dev, uint16_t vector,
> > > + bool trigger, int level)
> > > +{
> > > + /* masking/masking interrupt is handled by upper layer.
> > > + * i.e. msix_notify() for MSI-X
> > > + * msi_notify() for MSI
> > > + * pci_set_irq() for INTx
> > > + */
> >
> > So this will send another interrupt when level is 0?
>
> Yes. The condition that triggers MSI-X/MSI can be different from
> the one that asserts INTx as you can see it in the following code.
> trigger and level are set independently.
Looks like a bug ... but we are better off splitting this
to assert/deassert case as I suggested separately, anyway.
> --
> yamahata
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [Qemu-devel] Re: [PATCH v2 2/9] pcie: helper functions for pcie extended capability.
2010-09-15 13:05 ` Michael S. Tsirkin
@ 2010-09-19 4:11 ` Isaku Yamahata
2010-09-19 11:51 ` Michael S. Tsirkin
0 siblings, 1 reply; 25+ messages in thread
From: Isaku Yamahata @ 2010-09-19 4:11 UTC (permalink / raw)
To: Michael S. Tsirkin; +Cc: skandasa, adhyas, etmartin, qemu-devel, wexu2
On Wed, Sep 15, 2010 at 03:05:13PM +0200, Michael S. Tsirkin wrote:
> On Wed, Sep 15, 2010 at 02:50:01PM +0900, Isaku Yamahata wrote:
> > On Wed, Sep 08, 2010 at 01:31:22PM +0300, Michael S. Tsirkin wrote:
> > > > +
> > > > +static void pcie_notify(PCIDevice *dev, uint16_t vector,
> > > > + bool trigger, int level)
> > > > +{
> > > > + /* masking/masking interrupt is handled by upper layer.
> > > > + * i.e. msix_notify() for MSI-X
> > > > + * msi_notify() for MSI
> > > > + * pci_set_irq() for INTx
> > > > + */
> > >
> > > So this will send another interrupt when level is 0?
> >
> > Yes. The condition that triggers MSI-X/MSI can be different from
> > the one that asserts INTx as you can see it in the following code.
> > trigger and level are set independently.
>
> Looks like a bug ...
No. It can and the spec requires it. The mode of INTx and MSI is exclusive.
I think that it's quite reasonable to assume the basic knowledge
of express. For example
>From 6.7.3.4. Software Notification of Hot-Plug Events
> If the Port is enabled for level-triggered interrupt signaling using
> the INTx messages, the virtualization INTx wire must be asserted whenever
> and as long as the following conditions are satisfied:
and the list of conditions..
> If the Port is enabled for edge-triggered interrupt signaling using
> MSI or MSI-X, an interrupt message must be sent every time the logical
> AND of the following conditions transitions from FALSE to TRUE:
and the list of conditions.
--
yamahata
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [Qemu-devel] Re: [PATCH v2 2/9] pcie: helper functions for pcie extended capability.
2010-09-19 4:11 ` Isaku Yamahata
@ 2010-09-19 11:51 ` Michael S. Tsirkin
0 siblings, 0 replies; 25+ messages in thread
From: Michael S. Tsirkin @ 2010-09-19 11:51 UTC (permalink / raw)
To: Isaku Yamahata; +Cc: skandasa, adhyas, etmartin, qemu-devel, wexu2
On Sun, Sep 19, 2010 at 01:11:21PM +0900, Isaku Yamahata wrote:
> On Wed, Sep 15, 2010 at 03:05:13PM +0200, Michael S. Tsirkin wrote:
> > On Wed, Sep 15, 2010 at 02:50:01PM +0900, Isaku Yamahata wrote:
> > > On Wed, Sep 08, 2010 at 01:31:22PM +0300, Michael S. Tsirkin wrote:
> > > > > +
> > > > > +static void pcie_notify(PCIDevice *dev, uint16_t vector,
> > > > > + bool trigger, int level)
> > > > > +{
> > > > > + /* masking/masking interrupt is handled by upper layer.
> > > > > + * i.e. msix_notify() for MSI-X
> > > > > + * msi_notify() for MSI
> > > > > + * pci_set_irq() for INTx
> > > > > + */
> > > >
> > > > So this will send another interrupt when level is 0?
> > >
> > > Yes. The condition that triggers MSI-X/MSI can be different from
> > > the one that asserts INTx as you can see it in the following code.
> > > trigger and level are set independently.
> >
> > Looks like a bug ...
>
> No. It can and the spec requires it. The mode of INTx and MSI is exclusive.
> I think that it's quite reasonable to assume the basic knowledge
> of express. For example
>
> >From 6.7.3.4. Software Notification of Hot-Plug Events
>
> > If the Port is enabled for level-triggered interrupt signaling using
> > the INTx messages, the virtualization INTx wire must be asserted whenever
> > and as long as the following conditions are satisfied:
>
> and the list of conditions..
>
> > If the Port is enabled for edge-triggered interrupt signaling using
> > MSI or MSI-X, an interrupt message must be sent every time the logical
> > AND of the following conditions transitions from FALSE to TRUE:
>
> and the list of conditions.
I guess I just don't seem to be able to map the code to spec.
I don't understand what trigger and level are.
I think it would become clearer if we have two functions:
assert and dessert - instead of attempting to encode
it all as level and trigger.
Deassert would simply do nothing for msi/msix.
> yamahata
^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2010-09-19 11:58 UTC | newest]
Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-09-08 7:39 [Qemu-devel] [PATCH v2 0/9] pcie port switch emulators Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 1/9] msi: implemented msi Isaku Yamahata
2010-09-08 9:13 ` [Qemu-devel] " Michael S. Tsirkin
2010-09-08 9:49 ` Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 2/9] pcie: helper functions for pcie extended capability Isaku Yamahata
2010-09-08 10:31 ` [Qemu-devel] " Michael S. Tsirkin
2010-09-14 10:29 ` Isaku Yamahata
2010-09-14 12:23 ` Michael S. Tsirkin
2010-09-15 5:50 ` Isaku Yamahata
2010-09-15 13:05 ` Michael S. Tsirkin
2010-09-19 4:11 ` Isaku Yamahata
2010-09-19 11:51 ` Michael S. Tsirkin
2010-09-08 17:38 ` Wei Xu
2010-09-12 7:49 ` Blue Swirl
2010-09-12 13:26 ` Michael S. Tsirkin
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 3/9] pcie port: define struct PCIEPort/PCIESlot and helper functions Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 4/9] pcie root port: implement pcie root port Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 5/9] pcie upstream port: pci express switch upstream port Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 6/9] pcie downstream port: pci express switch downstream port Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 7/9] pcie/hotplug: glue pushing attention button command. pcie_abp Isaku Yamahata
2010-09-08 10:34 ` [Qemu-devel] " Michael S. Tsirkin
2010-09-09 3:43 ` Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 8/9] pcie/aer: glue aer error injection into qemu monitor Isaku Yamahata
2010-09-08 7:39 ` [Qemu-devel] [PATCH v2 9/9] msix: clear not only INTA, but all INTx when MSI-X is enabled Isaku Yamahata
2010-09-08 10:33 ` [Qemu-devel] " Michael S. Tsirkin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).