hw/usb/hcd-xhci-pci: Adds property for disabling mapping in IRQ mode

This change addresses an edge case that trips up macOS guest drivers
for PCI based XHCI controllers. The guest driver would attempt to
schedule events to XHCI event rings 1 and 2 even when using PCI
pin-based interrupts. Interrupts would therefore be dropped, and events
only handled on timeout.

So, in addition to disabling interrupter mapping if numintrs is 1, a
callback is added to xhci to check whether interrupter mapping should be
enabled. The PCI XHCI device type now provides an implementation of
this callback if the new "conditional-intr-mapping" property is enabled.
(default: disabled) When enabled, interrupter mapping is only enabled
when MSI-X or MSI is active.

This means that when using pin-based interrupts, events are only
submitted to interrupter 0 regardless of selected target. This allows
the macOS guest drivers to work with the device in those configurations.

Signed-off-by: Phil Dennis-Jordan <phil@philjordan.eu>
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2705
Message-ID: <20241227121336.25838-6-phil@philjordan.eu>
Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org>
This commit is contained in:
Phil Dennis-Jordan 2024-12-08 20:16:44 +01:00 committed by Philippe Mathieu-Daudé
parent ee241d79bb
commit 9422a5acf2
4 changed files with 32 additions and 1 deletions

View file

@ -82,6 +82,21 @@ static bool xhci_pci_intr_raise(XHCIState *xhci, int n, bool level)
return false; return false;
} }
static bool xhci_pci_intr_mapping_conditional(XHCIState *xhci)
{
XHCIPciState *s = container_of(xhci, XHCIPciState, xhci);
PCIDevice *pci_dev = PCI_DEVICE(s);
/*
* Implementation of the "conditional-intr-mapping" property, which only
* enables interrupter mapping if MSI or MSI-X is available and active.
* Forces all events onto interrupter/event ring 0 in pin-based IRQ mode.
* Provides compatibility with macOS guests on machine types where MSI(-X)
* is not available.
*/
return msix_enabled(pci_dev) || msi_enabled(pci_dev);
}
static void xhci_pci_reset(DeviceState *dev) static void xhci_pci_reset(DeviceState *dev)
{ {
XHCIPciState *s = XHCI_PCI(dev); XHCIPciState *s = XHCI_PCI(dev);
@ -119,6 +134,9 @@ static void usb_xhci_pci_realize(struct PCIDevice *dev, Error **errp)
object_property_set_link(OBJECT(&s->xhci), "host", OBJECT(s), NULL); object_property_set_link(OBJECT(&s->xhci), "host", OBJECT(s), NULL);
s->xhci.intr_update = xhci_pci_intr_update; s->xhci.intr_update = xhci_pci_intr_update;
s->xhci.intr_raise = xhci_pci_intr_raise; s->xhci.intr_raise = xhci_pci_intr_raise;
if (s->conditional_intr_mapping) {
s->xhci.intr_mapping_supported = xhci_pci_intr_mapping_conditional;
}
if (!qdev_realize(DEVICE(&s->xhci), NULL, errp)) { if (!qdev_realize(DEVICE(&s->xhci), NULL, errp)) {
return; return;
} }
@ -201,6 +219,8 @@ static void xhci_instance_init(Object *obj)
static const Property xhci_pci_properties[] = { static const Property xhci_pci_properties[] = {
DEFINE_PROP_ON_OFF_AUTO("msi", XHCIPciState, msi, ON_OFF_AUTO_AUTO), DEFINE_PROP_ON_OFF_AUTO("msi", XHCIPciState, msi, ON_OFF_AUTO_AUTO),
DEFINE_PROP_ON_OFF_AUTO("msix", XHCIPciState, msix, ON_OFF_AUTO_AUTO), DEFINE_PROP_ON_OFF_AUTO("msix", XHCIPciState, msix, ON_OFF_AUTO_AUTO),
DEFINE_PROP_BOOL("conditional-intr-mapping", XHCIPciState,
conditional_intr_mapping, false),
}; };
static void xhci_class_init(ObjectClass *klass, void *data) static void xhci_class_init(ObjectClass *klass, void *data)
@ -215,6 +235,10 @@ static void xhci_class_init(ObjectClass *klass, void *data)
k->exit = usb_xhci_pci_exit; k->exit = usb_xhci_pci_exit;
k->class_id = PCI_CLASS_SERIAL_USB; k->class_id = PCI_CLASS_SERIAL_USB;
device_class_set_props(dc, xhci_pci_properties); device_class_set_props(dc, xhci_pci_properties);
object_class_property_set_description(klass, "conditional-intr-mapping",
"When true, disables interrupter mapping for pin-based IRQ mode. "
"Intended to be used with guest drivers with questionable behaviour, "
"such as macOS's.");
} }
static const TypeInfo xhci_pci_info = { static const TypeInfo xhci_pci_info = {

View file

@ -40,6 +40,7 @@ typedef struct XHCIPciState {
XHCIState xhci; XHCIState xhci;
OnOffAuto msi; OnOffAuto msi;
OnOffAuto msix; OnOffAuto msix;
bool conditional_intr_mapping;
} XHCIPciState; } XHCIPciState;
#endif #endif

View file

@ -644,7 +644,8 @@ static void xhci_event(XHCIState *xhci, XHCIEvent *event, int v)
dma_addr_t erdp; dma_addr_t erdp;
unsigned int dp_idx; unsigned int dp_idx;
if (xhci->numintrs == 1) { if (xhci->numintrs == 1 ||
(xhci->intr_mapping_supported && !xhci->intr_mapping_supported(xhci))) {
v = 0; v = 0;
} }

View file

@ -193,6 +193,11 @@ typedef struct XHCIState {
uint32_t max_pstreams_mask; uint32_t max_pstreams_mask;
void (*intr_update)(XHCIState *s, int n, bool enable); void (*intr_update)(XHCIState *s, int n, bool enable);
bool (*intr_raise)(XHCIState *s, int n, bool level); bool (*intr_raise)(XHCIState *s, int n, bool level);
/*
* Callback for special-casing interrupter mapping support. NULL for most
* implementations, for defaulting to enabled mapping unless numintrs == 1.
*/
bool (*intr_mapping_supported)(XHCIState *s);
DeviceState *hostOpaque; DeviceState *hostOpaque;
/* Operational Registers */ /* Operational Registers */