qemu/hw/pci/shpc.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

790 lines
26 KiB
C
Raw Normal View History

#include "qemu/osdep.h"
2016-03-14 11:01:28 +03:00
#include "qapi/error.h"
#include "qemu/host-utils.h"
#include "qemu/range.h"
#include "qemu/error-report.h"
#include "hw/pci/shpc.h"
#include "migration/qemu-file-types.h"
#include "hw/pci/pci.h"
#include "hw/pci/pci_bus.h"
#include "hw/pci/msi.h"
#include "trace.h"
/* TODO: model power only and disabled slot states. */
/* TODO: handle SERR and wakeups */
/* TODO: consider enabling 66MHz support */
/* TODO: remove fully only on state DISABLED and LED off.
* track state to properly record this. */
/* SHPC Working Register Set */
#define SHPC_BASE_OFFSET 0x00 /* 4 bytes */
#define SHPC_SLOTS_33 0x04 /* 4 bytes. Also encodes PCI-X slots. */
#define SHPC_SLOTS_66 0x08 /* 4 bytes. */
#define SHPC_NSLOTS 0x0C /* 1 byte */
#define SHPC_FIRST_DEV 0x0D /* 1 byte */
#define SHPC_PHYS_SLOT 0x0E /* 2 byte */
#define SHPC_PHYS_NUM_MAX 0x7ff
#define SHPC_PHYS_NUM_UP 0x2000
#define SHPC_PHYS_MRL 0x4000
#define SHPC_PHYS_BUTTON 0x8000
#define SHPC_SEC_BUS 0x10 /* 2 bytes */
#define SHPC_SEC_BUS_33 0x0
#define SHPC_SEC_BUS_66 0x1 /* Unused */
#define SHPC_SEC_BUS_MASK 0x7
#define SHPC_MSI_CTL 0x12 /* 1 byte */
#define SHPC_PROG_IFC 0x13 /* 1 byte */
#define SHPC_PROG_IFC_1_0 0x1
#define SHPC_CMD_CODE 0x14 /* 1 byte */
#define SHPC_CMD_TRGT 0x15 /* 1 byte */
#define SHPC_CMD_TRGT_MIN 0x1
#define SHPC_CMD_TRGT_MAX 0x1f
#define SHPC_CMD_STATUS 0x16 /* 2 bytes */
#define SHPC_CMD_STATUS_BUSY 0x1
#define SHPC_CMD_STATUS_MRL_OPEN 0x2
#define SHPC_CMD_STATUS_INVALID_CMD 0x4
#define SHPC_CMD_STATUS_INVALID_MODE 0x8
#define SHPC_INT_LOCATOR 0x18 /* 4 bytes */
#define SHPC_INT_COMMAND 0x1
#define SHPC_SERR_LOCATOR 0x1C /* 4 bytes */
#define SHPC_SERR_INT 0x20 /* 4 bytes */
#define SHPC_INT_DIS 0x1
#define SHPC_SERR_DIS 0x2
#define SHPC_CMD_INT_DIS 0x4
#define SHPC_ARB_SERR_DIS 0x8
#define SHPC_CMD_DETECTED 0x10000
#define SHPC_ARB_DETECTED 0x20000
/* 4 bytes * slot # (start from 0) */
#define SHPC_SLOT_REG(s) (0x24 + (s) * 4)
/* 2 bytes */
#define SHPC_SLOT_STATUS(s) (0x0 + SHPC_SLOT_REG(s))
/* Same slot state masks are used for command and status registers */
#define SHPC_SLOT_STATE_MASK 0x03
#define SHPC_SLOT_STATE_SHIFT \
ctz32(SHPC_SLOT_STATE_MASK)
#define SHPC_STATE_NO 0x0
#define SHPC_STATE_PWRONLY 0x1
#define SHPC_STATE_ENABLED 0x2
#define SHPC_STATE_DISABLED 0x3
#define SHPC_SLOT_PWR_LED_MASK 0xC
#define SHPC_SLOT_PWR_LED_SHIFT \
ctz32(SHPC_SLOT_PWR_LED_MASK)
#define SHPC_SLOT_ATTN_LED_MASK 0x30
#define SHPC_SLOT_ATTN_LED_SHIFT \
ctz32(SHPC_SLOT_ATTN_LED_MASK)
#define SHPC_LED_NO 0x0
#define SHPC_LED_ON 0x1
#define SHPC_LED_BLINK 0x2
#define SHPC_LED_OFF 0x3
#define SHPC_SLOT_STATUS_PWR_FAULT 0x40
#define SHPC_SLOT_STATUS_BUTTON 0x80
#define SHPC_SLOT_STATUS_MRL_OPEN 0x100
#define SHPC_SLOT_STATUS_66 0x200
#define SHPC_SLOT_STATUS_PRSNT_MASK 0xC00
#define SHPC_SLOT_STATUS_PRSNT_EMPTY 0x3
#define SHPC_SLOT_STATUS_PRSNT_25W 0x1
#define SHPC_SLOT_STATUS_PRSNT_15W 0x2
#define SHPC_SLOT_STATUS_PRSNT_7_5W 0x0
#define SHPC_SLOT_STATUS_PRSNT_PCIX 0x3000
/* 1 byte */
#define SHPC_SLOT_EVENT_LATCH(s) (0x2 + SHPC_SLOT_REG(s))
/* 1 byte */
#define SHPC_SLOT_EVENT_SERR_INT_DIS(d, s) (0x3 + SHPC_SLOT_REG(s))
#define SHPC_SLOT_EVENT_PRESENCE 0x01
#define SHPC_SLOT_EVENT_ISOLATED_FAULT 0x02
#define SHPC_SLOT_EVENT_BUTTON 0x04
#define SHPC_SLOT_EVENT_MRL 0x08
#define SHPC_SLOT_EVENT_CONNECTED_FAULT 0x10
/* Bits below are used for Serr/Int disable only */
#define SHPC_SLOT_EVENT_MRL_SERR_DIS 0x20
#define SHPC_SLOT_EVENT_CONNECTED_FAULT_SERR_DIS 0x40
#define SHPC_MIN_SLOTS 1
#define SHPC_MAX_SLOTS 31
#define SHPC_SIZEOF(d) SHPC_SLOT_REG((d)->shpc->nslots)
/* SHPC Slot identifiers */
/* Hotplug supported at 31 slots out of the total 32. We reserve slot 0,
and give the rest of them physical *and* pci numbers starting from 1, so
they match logical numbers. Note: this means that multiple slots must have
different chassis number values, to make chassis+physical slot unique.
TODO: make this configurable? */
#define SHPC_IDX_TO_LOGICAL(slot) ((slot) + 1)
#define SHPC_LOGICAL_TO_IDX(target) ((target) - 1)
#define SHPC_IDX_TO_PCI(slot) ((slot) + 1)
#define SHPC_PCI_TO_IDX(pci_slot) ((pci_slot) - 1)
#define SHPC_IDX_TO_PHYSICAL(slot) ((slot) + 1)
static const char *shpc_led_state_to_str(uint8_t value)
{
switch (value) {
case SHPC_LED_ON:
return "on";
case SHPC_LED_BLINK:
return "blink";
case SHPC_LED_OFF:
return "off";
default:
return "invalid";
}
}
static const char *shpc_slot_state_to_str(uint8_t value)
{
switch (value) {
case SHPC_STATE_PWRONLY:
return "power-only";
case SHPC_STATE_ENABLED:
return "enabled";
case SHPC_STATE_DISABLED:
return "disabled";
default:
return "invalid";
}
}
static uint8_t shpc_get_status(SHPCDevice *shpc, int slot, uint16_t msk)
{
uint8_t *status = shpc->config + SHPC_SLOT_STATUS(slot);
uint16_t result = (pci_get_word(status) & msk) >> ctz32(msk);
assert(result <= UINT8_MAX);
return result;
}
static void shpc_set_status(SHPCDevice *shpc,
int slot, uint8_t value, uint16_t msk)
{
uint8_t *status = shpc->config + SHPC_SLOT_STATUS(slot);
pci_word_test_and_clear_mask(status, msk);
pci_word_test_and_set_mask(status, value << ctz32(msk));
}
static void shpc_interrupt_update(PCIDevice *d)
{
SHPCDevice *shpc = d->shpc;
int slot;
int level = 0;
uint32_t serr_int;
uint32_t int_locator = 0;
/* Update interrupt locator register */
for (slot = 0; slot < shpc->nslots; ++slot) {
uint8_t event = shpc->config[SHPC_SLOT_EVENT_LATCH(slot)];
uint8_t disable = shpc->config[SHPC_SLOT_EVENT_SERR_INT_DIS(d, slot)];
uint32_t mask = 1U << SHPC_IDX_TO_LOGICAL(slot);
if (event & ~disable) {
int_locator |= mask;
}
}
serr_int = pci_get_long(shpc->config + SHPC_SERR_INT);
if ((serr_int & SHPC_CMD_DETECTED) && !(serr_int & SHPC_CMD_INT_DIS)) {
int_locator |= SHPC_INT_COMMAND;
}
pci_set_long(shpc->config + SHPC_INT_LOCATOR, int_locator);
level = (!(serr_int & SHPC_INT_DIS) && int_locator) ? 1 : 0;
if (msi_enabled(d) && shpc->msi_requested != level)
msi_notify(d, 0);
else
pci_set_irq(d, level);
shpc->msi_requested = level;
}
static void shpc_set_sec_bus_speed(SHPCDevice *shpc, uint8_t speed)
{
switch (speed) {
case SHPC_SEC_BUS_33:
shpc->config[SHPC_SEC_BUS] &= ~SHPC_SEC_BUS_MASK;
shpc->config[SHPC_SEC_BUS] |= speed;
break;
default:
pci_word_test_and_set_mask(shpc->config + SHPC_CMD_STATUS,
SHPC_CMD_STATUS_INVALID_MODE);
}
}
void shpc_reset(PCIDevice *d)
{
SHPCDevice *shpc = d->shpc;
int nslots = shpc->nslots;
int i;
memset(shpc->config, 0, SHPC_SIZEOF(d));
pci_set_byte(shpc->config + SHPC_NSLOTS, nslots);
pci_set_long(shpc->config + SHPC_SLOTS_33, nslots);
pci_set_long(shpc->config + SHPC_SLOTS_66, 0);
pci_set_byte(shpc->config + SHPC_FIRST_DEV, SHPC_IDX_TO_PCI(0));
pci_set_word(shpc->config + SHPC_PHYS_SLOT,
SHPC_IDX_TO_PHYSICAL(0) |
SHPC_PHYS_NUM_UP |
SHPC_PHYS_MRL |
SHPC_PHYS_BUTTON);
pci_set_long(shpc->config + SHPC_SERR_INT, SHPC_INT_DIS |
SHPC_SERR_DIS |
SHPC_CMD_INT_DIS |
SHPC_ARB_SERR_DIS);
pci_set_byte(shpc->config + SHPC_PROG_IFC, SHPC_PROG_IFC_1_0);
pci_set_word(shpc->config + SHPC_SEC_BUS, SHPC_SEC_BUS_33);
for (i = 0; i < shpc->nslots; ++i) {
pci_set_byte(shpc->config + SHPC_SLOT_EVENT_SERR_INT_DIS(d, i),
SHPC_SLOT_EVENT_PRESENCE |
SHPC_SLOT_EVENT_ISOLATED_FAULT |
SHPC_SLOT_EVENT_BUTTON |
SHPC_SLOT_EVENT_MRL |
SHPC_SLOT_EVENT_CONNECTED_FAULT |
SHPC_SLOT_EVENT_MRL_SERR_DIS |
SHPC_SLOT_EVENT_CONNECTED_FAULT_SERR_DIS);
if (shpc->sec_bus->devices[PCI_DEVFN(SHPC_IDX_TO_PCI(i), 0)]) {
shpc_set_status(shpc, i, SHPC_STATE_ENABLED, SHPC_SLOT_STATE_MASK);
shpc_set_status(shpc, i, 0, SHPC_SLOT_STATUS_MRL_OPEN);
shpc_set_status(shpc, i, SHPC_SLOT_STATUS_PRSNT_7_5W,
SHPC_SLOT_STATUS_PRSNT_MASK);
shpc_set_status(shpc, i, SHPC_LED_ON, SHPC_SLOT_PWR_LED_MASK);
} else {
shpc_set_status(shpc, i, SHPC_STATE_DISABLED, SHPC_SLOT_STATE_MASK);
shpc_set_status(shpc, i, 1, SHPC_SLOT_STATUS_MRL_OPEN);
shpc_set_status(shpc, i, SHPC_SLOT_STATUS_PRSNT_EMPTY,
SHPC_SLOT_STATUS_PRSNT_MASK);
shpc_set_status(shpc, i, SHPC_LED_OFF, SHPC_SLOT_PWR_LED_MASK);
}
shpc_set_status(shpc, i, SHPC_LED_OFF, SHPC_SLOT_ATTN_LED_MASK);
shpc_set_status(shpc, i, 0, SHPC_SLOT_STATUS_66);
}
shpc_set_sec_bus_speed(shpc, SHPC_SEC_BUS_33);
shpc->msi_requested = 0;
shpc_interrupt_update(d);
}
static void shpc_invalid_command(SHPCDevice *shpc)
{
pci_word_test_and_set_mask(shpc->config + SHPC_CMD_STATUS,
SHPC_CMD_STATUS_INVALID_CMD);
}
static void shpc_free_devices_in_slot(SHPCDevice *shpc, int slot)
{
HotplugHandler *hotplug_ctrl;
int devfn;
int pci_slot = SHPC_IDX_TO_PCI(slot);
for (devfn = PCI_DEVFN(pci_slot, 0);
devfn <= PCI_DEVFN(pci_slot, PCI_FUNC_MAX - 1);
++devfn) {
PCIDevice *affected_dev = shpc->sec_bus->devices[devfn];
if (affected_dev) {
hotplug_ctrl = qdev_get_hotplug_handler(DEVICE(affected_dev));
hotplug_handler_unplug(hotplug_ctrl, DEVICE(affected_dev),
&error_abort);
qdev: Let the hotplug_handler_unplug() caller delete the device When unplugging a device, at one point the device will be destroyed via object_unparent(). This will, one the one hand, unrealize the removed device hierarchy, and on the other hand, destroy/free the device hierarchy. When chaining hotplug handlers, we want to overwrite a bus hotplug handler by the machine hotplug handler, to be able to perform some part of the plug/unplug and to forward the calls to the bus hotplug handler. For now, the bus hotplug handler would trigger an object_unparent(), not allowing us to perform some unplug action on a device after we forwarded the call to the bus hotplug handler. The device would be gone at that point. machine_unplug_handler(dev) /* eventually do unplug stuff */ bus_unplug_handler(dev) /* dev is gone, we can't do more unplug stuff */ So move the object_unparent() to the original caller of the unplug. For now, keep the unrealize() at the original places of the object_unparent(). For implicitly chained hotplug handlers (e.g. pc code calling acpi hotplug handlers), the object_unparent() has to be done by the outermost caller. So when calling hotplug_handler_unplug() from inside an unplug handler, nothing is to be done. hotplug_handler_unplug(dev) -> calls machine_unplug_handler() machine_unplug_handler(dev) { /* eventually do unplug stuff */ bus_unplug_handler(dev) -> calls unrealize(dev) /* we can do more unplug stuff but device already unrealized */ } object_unparent(dev) In the long run, every unplug action should be factored out of the unrealize() function into the unplug handler (especially for PCI). Then we can get rid of the additonal unrealize() calls and object_unparent() will properly unrealize the device hierarchy after the device has been unplugged. hotplug_handler_unplug(dev) -> calls machine_unplug_handler() machine_unplug_handler(dev) { /* eventually do unplug stuff */ bus_unplug_handler(dev) -> only unplugs, does not unrealize /* we can do more unplug stuff */ } object_unparent(dev) -> will unrealize The original approach was suggested by Igor Mammedov for the PCI part, but I extended it to all hotplug handlers. I consider this one step into the right direction. To summarize: - object_unparent() on synchronous unplugs is done by common code -- "Caller of hotplug_handler_unplug" - object_unparent() on asynchronous unplugs ("unplug requests") has to be done manually -- "Caller of hotplug_handler_unplug" Reviewed-by: Igor Mammedov <imammedo@redhat.com> Acked-by: Cornelia Huck <cohuck@redhat.com> Signed-off-by: David Hildenbrand <david@redhat.com> Message-Id: <20190228122849.4296-2-david@redhat.com> Reviewed-by: Greg Kurz <groug@kaod.org> Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
2019-02-28 15:28:47 +03:00
object_unparent(OBJECT(affected_dev));
}
}
}
static bool shpc_slot_is_off(uint8_t state, uint8_t power, uint8_t attn)
{
return state == SHPC_STATE_DISABLED && power == SHPC_LED_OFF;
}
static void shpc_slot_command(PCIDevice *d, uint8_t target,
uint8_t state, uint8_t power, uint8_t attn)
{
SHPCDevice *shpc = d->shpc;
int slot = SHPC_LOGICAL_TO_IDX(target);
uint8_t old_state = shpc_get_status(shpc, slot, SHPC_SLOT_STATE_MASK);
uint8_t old_power = shpc_get_status(shpc, slot, SHPC_SLOT_PWR_LED_MASK);
uint8_t old_attn = shpc_get_status(shpc, slot, SHPC_SLOT_ATTN_LED_MASK);
if (target < SHPC_CMD_TRGT_MIN || slot >= shpc->nslots) {
shpc_invalid_command(shpc);
return;
}
if (old_state == SHPC_STATE_ENABLED && state == SHPC_STATE_PWRONLY) {
shpc_invalid_command(shpc);
return;
}
if (power == SHPC_LED_NO) {
power = old_power;
} else {
/* TODO: send event to monitor */
shpc_set_status(shpc, slot, power, SHPC_SLOT_PWR_LED_MASK);
}
if (attn == SHPC_LED_NO) {
attn = old_attn;
} else {
/* TODO: send event to monitor */
shpc_set_status(shpc, slot, attn, SHPC_SLOT_ATTN_LED_MASK);
}
if (state == SHPC_STATE_NO) {
state = old_state;
} else {
shpc_set_status(shpc, slot, state, SHPC_SLOT_STATE_MASK);
}
if (trace_event_get_state_backends(TRACE_SHPC_SLOT_COMMAND)) {
DeviceState *parent = DEVICE(d);
int pci_slot = SHPC_IDX_TO_PCI(slot);
DeviceState *child =
DEVICE(shpc->sec_bus->devices[PCI_DEVFN(pci_slot, 0)]);
trace_shpc_slot_command(
parent->canonical_path, pci_slot,
child ? child->canonical_path : "no-child",
shpc_led_state_to_str(old_power),
shpc_led_state_to_str(power),
shpc_led_state_to_str(old_attn),
shpc_led_state_to_str(attn),
shpc_slot_state_to_str(old_state),
shpc_slot_state_to_str(state));
}
if (!shpc_slot_is_off(old_state, old_power, old_attn) &&
shpc_slot_is_off(state, power, attn))
{
shpc_free_devices_in_slot(shpc, slot);
shpc_set_status(shpc, slot, 1, SHPC_SLOT_STATUS_MRL_OPEN);
shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_EMPTY,
SHPC_SLOT_STATUS_PRSNT_MASK);
shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
SHPC_SLOT_EVENT_MRL |
SHPC_SLOT_EVENT_PRESENCE;
}
}
static void shpc_command(PCIDevice *d)
{
SHPCDevice *shpc = d->shpc;
uint8_t code = pci_get_byte(shpc->config + SHPC_CMD_CODE);
uint8_t speed;
uint8_t target;
uint8_t attn;
uint8_t power;
uint8_t state;
int i;
/* Clear status from the previous command. */
pci_word_test_and_clear_mask(shpc->config + SHPC_CMD_STATUS,
SHPC_CMD_STATUS_BUSY |
SHPC_CMD_STATUS_MRL_OPEN |
SHPC_CMD_STATUS_INVALID_CMD |
SHPC_CMD_STATUS_INVALID_MODE);
switch (code) {
case 0x00 ... 0x3f:
target = shpc->config[SHPC_CMD_TRGT] & SHPC_CMD_TRGT_MAX;
state = (code & SHPC_SLOT_STATE_MASK) >> SHPC_SLOT_STATE_SHIFT;
power = (code & SHPC_SLOT_PWR_LED_MASK) >> SHPC_SLOT_PWR_LED_SHIFT;
attn = (code & SHPC_SLOT_ATTN_LED_MASK) >> SHPC_SLOT_ATTN_LED_SHIFT;
shpc_slot_command(d, target, state, power, attn);
break;
case 0x40 ... 0x47:
speed = code & SHPC_SEC_BUS_MASK;
shpc_set_sec_bus_speed(shpc, speed);
break;
case 0x48:
/* Power only all slots */
/* first verify no slots are enabled */
for (i = 0; i < shpc->nslots; ++i) {
state = shpc_get_status(shpc, i, SHPC_SLOT_STATE_MASK);
if (state == SHPC_STATE_ENABLED) {
shpc_invalid_command(shpc);
goto done;
}
}
for (i = 0; i < shpc->nslots; ++i) {
if (!(shpc_get_status(shpc, i, SHPC_SLOT_STATUS_MRL_OPEN))) {
shpc_slot_command(d, i + SHPC_CMD_TRGT_MIN,
SHPC_STATE_PWRONLY, SHPC_LED_ON, SHPC_LED_NO);
} else {
shpc_slot_command(d, i + SHPC_CMD_TRGT_MIN,
SHPC_STATE_NO, SHPC_LED_OFF, SHPC_LED_NO);
}
}
break;
case 0x49:
/* Enable all slots */
/* TODO: Spec says this shall fail if some are already enabled.
* This doesn't make sense - why not? a spec bug? */
for (i = 0; i < shpc->nslots; ++i) {
state = shpc_get_status(shpc, i, SHPC_SLOT_STATE_MASK);
if (state == SHPC_STATE_ENABLED) {
shpc_invalid_command(shpc);
goto done;
}
}
for (i = 0; i < shpc->nslots; ++i) {
if (!(shpc_get_status(shpc, i, SHPC_SLOT_STATUS_MRL_OPEN))) {
shpc_slot_command(d, i + SHPC_CMD_TRGT_MIN,
SHPC_STATE_ENABLED, SHPC_LED_ON, SHPC_LED_NO);
} else {
shpc_slot_command(d, i + SHPC_CMD_TRGT_MIN,
SHPC_STATE_NO, SHPC_LED_OFF, SHPC_LED_NO);
}
}
break;
default:
shpc_invalid_command(shpc);
break;
}
done:
pci_long_test_and_set_mask(shpc->config + SHPC_SERR_INT, SHPC_CMD_DETECTED);
}
static void shpc_write(PCIDevice *d, unsigned addr, uint64_t val, int l)
{
SHPCDevice *shpc = d->shpc;
int i;
if (addr >= SHPC_SIZEOF(d)) {
return;
}
l = MIN(l, SHPC_SIZEOF(d) - addr);
/* TODO: code duplicated from pci.c */
for (i = 0; i < l; val >>= 8, ++i) {
unsigned a = addr + i;
uint8_t wmask = shpc->wmask[a];
uint8_t w1cmask = shpc->w1cmask[a];
assert(!(wmask & w1cmask));
shpc->config[a] = (shpc->config[a] & ~wmask) | (val & wmask);
shpc->config[a] &= ~(val & w1cmask); /* W1C: Write 1 to Clear */
}
if (ranges_overlap(addr, l, SHPC_CMD_CODE, 2)) {
shpc_command(d);
}
shpc_interrupt_update(d);
}
static uint64_t shpc_read(PCIDevice *d, unsigned addr, int l)
{
uint64_t val = 0x0;
if (addr >= SHPC_SIZEOF(d)) {
return val;
}
l = MIN(l, SHPC_SIZEOF(d) - addr);
memcpy(&val, d->shpc->config + addr, l);
return val;
}
/* SHPC Bridge Capability */
#define SHPC_CAP_LENGTH 0x08
#define SHPC_CAP_DWORD_SELECT 0x2 /* 1 byte */
#define SHPC_CAP_CxP 0x3 /* 1 byte: CSP, CIP */
#define SHPC_CAP_DWORD_DATA 0x4 /* 4 bytes */
#define SHPC_CAP_CSP_MASK 0x4
#define SHPC_CAP_CIP_MASK 0x8
static uint8_t shpc_cap_dword(PCIDevice *d)
{
return pci_get_byte(d->config + d->shpc->cap + SHPC_CAP_DWORD_SELECT);
}
/* Update dword data capability register */
static void shpc_cap_update_dword(PCIDevice *d)
{
unsigned data;
data = shpc_read(d, shpc_cap_dword(d) * 4, 4);
pci_set_long(d->config + d->shpc->cap + SHPC_CAP_DWORD_DATA, data);
}
/* Add SHPC capability to the config space for the device. */
static int shpc_cap_add_config(PCIDevice *d, Error **errp)
{
uint8_t *config;
int config_offset;
config_offset = pci_add_capability(d, PCI_CAP_ID_SHPC,
0, SHPC_CAP_LENGTH,
errp);
if (config_offset < 0) {
return config_offset;
}
config = d->config + config_offset;
pci_set_byte(config + SHPC_CAP_DWORD_SELECT, 0);
pci_set_byte(config + SHPC_CAP_CxP, 0);
pci_set_long(config + SHPC_CAP_DWORD_DATA, 0);
d->shpc->cap = config_offset;
/* Make dword select and data writable. */
pci_set_byte(d->wmask + config_offset + SHPC_CAP_DWORD_SELECT, 0xff);
pci_set_long(d->wmask + config_offset + SHPC_CAP_DWORD_DATA, 0xffffffff);
return 0;
}
static uint64_t shpc_mmio_read(void *opaque, hwaddr addr,
unsigned size)
{
return shpc_read(opaque, addr, size);
}
static void shpc_mmio_write(void *opaque, hwaddr addr,
uint64_t val, unsigned size)
{
shpc_write(opaque, addr, val, size);
}
static const MemoryRegionOps shpc_mmio_ops = {
.read = shpc_mmio_read,
.write = shpc_mmio_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
/* SHPC ECN requires dword accesses, but the original 1.0 spec doesn't.
* It's easier to support all sizes than worry about it.
*/
.min_access_size = 1,
.max_access_size = 4,
},
};
static bool shpc_device_get_slot(PCIDevice *affected_dev, int *slot,
SHPCDevice *shpc, Error **errp)
{
int pci_slot = PCI_SLOT(affected_dev->devfn);
*slot = SHPC_PCI_TO_IDX(pci_slot);
if (pci_slot < SHPC_IDX_TO_PCI(0) || *slot >= shpc->nslots) {
error_setg(errp, "Unsupported PCI slot %d for standard hotplug "
"controller. Valid slots are between %d and %d.",
pci_slot, SHPC_IDX_TO_PCI(0),
SHPC_IDX_TO_PCI(shpc->nslots) - 1);
return false;
}
return true;
}
void shpc_device_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
Error **errp)
{
PCIDevice *pci_hotplug_dev = PCI_DEVICE(hotplug_dev);
SHPCDevice *shpc = pci_hotplug_dev->shpc;
int slot;
if (!shpc_device_get_slot(PCI_DEVICE(dev), &slot, shpc, errp)) {
return;
}
/* Don't send event when device is enabled during qemu machine creation:
* it is present on boot, no hotplug event is necessary. We do send an
* event when the device is disabled later. */
if (!dev->hotplugged) {
shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_MRL_OPEN);
shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_7_5W,
SHPC_SLOT_STATUS_PRSNT_MASK);
return;
}
/* This could be a cancellation of the previous removal.
* We check MRL state to figure out. */
if (shpc_get_status(shpc, slot, SHPC_SLOT_STATUS_MRL_OPEN)) {
shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_MRL_OPEN);
shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_7_5W,
SHPC_SLOT_STATUS_PRSNT_MASK);
shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
SHPC_SLOT_EVENT_BUTTON |
SHPC_SLOT_EVENT_MRL |
SHPC_SLOT_EVENT_PRESENCE;
} else {
/* Press attention button to cancel removal */
shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
SHPC_SLOT_EVENT_BUTTON;
}
shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_66);
shpc_interrupt_update(pci_hotplug_dev);
}
void shpc_device_unplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
Error **errp)
{
qdev_unrealize(dev);
}
void shpc_device_unplug_request_cb(HotplugHandler *hotplug_dev,
DeviceState *dev, Error **errp)
{
PCIDevice *pci_hotplug_dev = PCI_DEVICE(hotplug_dev);
SHPCDevice *shpc = pci_hotplug_dev->shpc;
uint8_t state;
uint8_t led;
int slot;
if (!shpc_device_get_slot(PCI_DEVICE(dev), &slot, shpc, errp)) {
return;
}
state = shpc_get_status(shpc, slot, SHPC_SLOT_STATE_MASK);
led = shpc_get_status(shpc, slot, SHPC_SLOT_PWR_LED_MASK);
if (led == SHPC_LED_BLINK) {
error_setg(errp, "Hot-unplug failed: "
"guest is busy (power indicator blinking)");
return;
}
if (state == SHPC_STATE_DISABLED && led == SHPC_LED_OFF) {
shpc_free_devices_in_slot(shpc, slot);
shpc_set_status(shpc, slot, 1, SHPC_SLOT_STATUS_MRL_OPEN);
shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_EMPTY,
SHPC_SLOT_STATUS_PRSNT_MASK);
shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
SHPC_SLOT_EVENT_MRL |
SHPC_SLOT_EVENT_PRESENCE;
pci/shpc: don't push attention button when ejecting powered-off device When the slot is in steady powered-off state and the device is being removed, there's no need to press the attention button. Nor is it mandated by the Standard Hot-Plug Controller Specification, Rev. 1.0. Moreover it confuses the guest, Linux in particular, as it assumes that the attention button pressed in this state indicates that the device has been inserted and will need to be powered on. Therefore it transitions the slot into BLINKING_ON state for 5 seconds, and discovers at the end that no device is actually inserted: ... unplug request [12685.451329] shpchp 0000:01:00.0: Button pressed on Slot(2) [12685.455478] shpchp 0000:01:00.0: PCI slot #2 - powering off due to button press ... in 5 seconds OS powers off the slot, QEMU ejects the device [12690.632282] shpchp 0000:01:00.0: Latch open on Slot(2) ... excessive button press in steady powered-off state [12690.634267] shpchp 0000:01:00.0: Button pressed on Slot(2) [12690.636256] shpchp 0000:01:00.0: Card not present on Slot(2) ... the last button press spawns powering on the slot [12690.638909] shpchp 0000:01:00.0: PCI slot #2 - powering on due to button press ... in 5 more seconds attempt to power on discovers empty slot [12695.735986] shpchp 0000:01:00.0: No adapter on slot(2) Worse, if the real device insertion happens within 5 seconds from the apparent completion of the previous device removal (signaled via DEVICE_DELETED event), the new button press will be interpreted as the cancellation of that misguided powering on: [13448.965295] shpchp 0000:01:00.0: Button pressed on Slot(2) [13448.969430] shpchp 0000:01:00.0: PCI slot #2 - powering off due to button press [13454.025107] shpchp 0000:01:00.0: Latch open on Slot(2) [13454.027101] shpchp 0000:01:00.0: Button pressed on Slot(2) [13454.029165] shpchp 0000:01:00.0: Card not present on Slot(2) ... the excessive button press spawns powering on the slot ... device has already been ejected by QEMU [13454.031949] shpchp 0000:01:00.0: PCI slot #2 - powering on due to button press ... new device is inserted in the slot [13456.861545] shpchp 0000:01:00.0: Latch close on Slot(2) ... valid button press arrives before 5 s since the wrong one [13456.864894] shpchp 0000:01:00.0: Button pressed on Slot(2) [13456.869211] shpchp 0000:01:00.0: Card present on Slot(2) ... the valid button press is counted as cancellation of the wrong one [13456.873173] shpchp 0000:01:00.0: Button cancel on Slot(2) [13456.877101] shpchp 0000:01:00.0: PCI slot #2 - action canceled due to button press As a result, the newly inserted device isn't brought up by the guest. Avoid this situation by not pushing the attention button when the device in the slot is in powered-off state and is being ejected. FWIW pcie implementation doesn't suffer from this problem. Signed-off-by: Roman Kagan <rvkagan@yandex-team.ru> Message-Id: <20201102053750.2281818-1-rvkagan@yandex-team.ru> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
2020-11-02 08:37:50 +03:00
} else {
shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |= SHPC_SLOT_EVENT_BUTTON;
}
shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_66);
shpc_interrupt_update(pci_hotplug_dev);
}
/* Initialize the SHPC structure in bridge's BAR. */
int shpc_init(PCIDevice *d, PCIBus *sec_bus, MemoryRegion *bar,
unsigned offset, Error **errp)
{
int i, ret;
int nslots = SHPC_MAX_SLOTS; /* TODO: qdev property? */
SHPCDevice *shpc = d->shpc = g_malloc0(sizeof(*d->shpc));
shpc->sec_bus = sec_bus;
ret = shpc_cap_add_config(d, errp);
if (ret) {
g_free(d->shpc);
return ret;
}
if (nslots < SHPC_MIN_SLOTS) {
return 0;
}
if (nslots > SHPC_MAX_SLOTS ||
SHPC_IDX_TO_PCI(nslots) > PCI_SLOT_MAX) {
/* TODO: report an error message that makes sense. */
return -EINVAL;
}
shpc->nslots = nslots;
shpc->config = g_malloc0(SHPC_SIZEOF(d));
shpc->cmask = g_malloc0(SHPC_SIZEOF(d));
shpc->wmask = g_malloc0(SHPC_SIZEOF(d));
shpc->w1cmask = g_malloc0(SHPC_SIZEOF(d));
shpc_reset(d);
pci_set_long(shpc->config + SHPC_BASE_OFFSET, offset);
pci_set_byte(shpc->wmask + SHPC_CMD_CODE, 0xff);
pci_set_byte(shpc->wmask + SHPC_CMD_TRGT, SHPC_CMD_TRGT_MAX);
pci_set_byte(shpc->wmask + SHPC_CMD_TRGT, SHPC_CMD_TRGT_MAX);
pci_set_long(shpc->wmask + SHPC_SERR_INT,
SHPC_INT_DIS |
SHPC_SERR_DIS |
SHPC_CMD_INT_DIS |
SHPC_ARB_SERR_DIS);
pci_set_long(shpc->w1cmask + SHPC_SERR_INT,
SHPC_CMD_DETECTED |
SHPC_ARB_DETECTED);
for (i = 0; i < nslots; ++i) {
pci_set_byte(shpc->wmask +
SHPC_SLOT_EVENT_SERR_INT_DIS(d, i),
SHPC_SLOT_EVENT_PRESENCE |
SHPC_SLOT_EVENT_ISOLATED_FAULT |
SHPC_SLOT_EVENT_BUTTON |
SHPC_SLOT_EVENT_MRL |
SHPC_SLOT_EVENT_CONNECTED_FAULT |
SHPC_SLOT_EVENT_MRL_SERR_DIS |
SHPC_SLOT_EVENT_CONNECTED_FAULT_SERR_DIS);
pci_set_byte(shpc->w1cmask +
SHPC_SLOT_EVENT_LATCH(i),
SHPC_SLOT_EVENT_PRESENCE |
SHPC_SLOT_EVENT_ISOLATED_FAULT |
SHPC_SLOT_EVENT_BUTTON |
SHPC_SLOT_EVENT_MRL |
SHPC_SLOT_EVENT_CONNECTED_FAULT);
}
/* TODO: init cmask */
memory_region_init_io(&shpc->mmio, OBJECT(d), &shpc_mmio_ops,
d, "shpc-mmio", SHPC_SIZEOF(d));
shpc_cap_update_dword(d);
memory_region_add_subregion(bar, offset, &shpc->mmio);
qbus_set_hotplug_handler(BUS(sec_bus), OBJECT(d));
d->cap_present |= QEMU_PCI_CAP_SHPC;
return 0;
}
int shpc_bar_size(PCIDevice *d)
{
return pow2roundup32(SHPC_SLOT_REG(SHPC_MAX_SLOTS));
}
void shpc_cleanup(PCIDevice *d, MemoryRegion *bar)
{
SHPCDevice *shpc = d->shpc;
d->cap_present &= ~QEMU_PCI_CAP_SHPC;
memory_region_del_subregion(bar, &shpc->mmio);
/* TODO: cleanup config space changes? */
}
void shpc_free(PCIDevice *d)
{
SHPCDevice *shpc = d->shpc;
if (!shpc) {
return;
}
object_unparent(OBJECT(&shpc->mmio));
g_free(shpc->config);
g_free(shpc->cmask);
g_free(shpc->wmask);
g_free(shpc->w1cmask);
g_free(shpc);
d->shpc = NULL;
}
void shpc_cap_write_config(PCIDevice *d, uint32_t addr, uint32_t val, int l)
{
if (!ranges_overlap(addr, l, d->shpc->cap, SHPC_CAP_LENGTH)) {
return;
}
if (ranges_overlap(addr, l, d->shpc->cap + SHPC_CAP_DWORD_DATA, 4)) {
unsigned dword_data;
dword_data = pci_get_long(d->shpc->config + d->shpc->cap
+ SHPC_CAP_DWORD_DATA);
shpc_write(d, shpc_cap_dword(d) * 4, dword_data, 4);
}
/* Update cap dword data in case guest is going to read it. */
shpc_cap_update_dword(d);
}
static int shpc_save(QEMUFile *f, void *pv, size_t size,
const VMStateField *field, JSONWriter *vmdesc)
{
PCIDevice *d = container_of(pv, PCIDevice, shpc);
qemu_put_buffer(f, d->shpc->config, SHPC_SIZEOF(d));
return 0;
}
static int shpc_load(QEMUFile *f, void *pv, size_t size,
const VMStateField *field)
{
PCIDevice *d = container_of(pv, PCIDevice, shpc);
int ret = qemu_get_buffer(f, d->shpc->config, SHPC_SIZEOF(d));
if (ret != SHPC_SIZEOF(d)) {
return -EINVAL;
}
/* Make sure we don't lose notifications. An extra interrupt is harmless. */
d->shpc->msi_requested = 0;
shpc_interrupt_update(d);
return 0;
}
const VMStateInfo shpc_vmstate_info = {
.name = "shpc",
.get = shpc_load,
.put = shpc_save,
};