@@ -723,7 +723,7 @@ Available alternatives, with their meaning, are:
### dbgp
> `= ehci[ <integer> | @pci<bus>:<slot>.<func> ]`
-> `= xhci[ <integer> | @pci<bus>:<slot>.<func> ]`
+> `= xhci[ <integer> | @pci<bus>:<slot>.<func> ][,share=<bool>|hwdom]`
Specify the USB controller to use, either by instance number (when going
over the PCI busses sequentially) or by PCI device (must be on segment 0).
@@ -731,6 +731,18 @@ over the PCI busses sequentially) or by PCI device (must be on segment 0).
Use `ehci` for EHCI debug port, use `xhci` for XHCI debug capability.
XHCI driver will wait indefinitely for the debug host to connect - make sure
the cable is connected.
+The `share` option for xhci controls who else can use the controller:
+* `no`: use the controller exclusively for console, even hardware domain
+ (dom0) cannot use it
+* `hwdom`: hardware domain may use the controller too, ports not used for debug
+ console will be available for normal devices; this is the default
+* `yes`: the controller can be assigned to any domain; it is not safe to assign
+ the controller to untrusted domain
+
+Choosing `share=hwdom` (the default) or `share=yes` allows a domain to reset the
+controller, which may cause small portion of the console output to be lost.
+
+The `share=yes` configuration is not security supported.
### debug_stack_lines
> `= <integer>`
@@ -23,6 +23,7 @@
#include <xen/iommu.h>
#include <xen/mm.h>
#include <xen/param.h>
+#include <xen/rangeset.h>
#include <xen/serial.h>
#include <xen/timer.h>
#include <xen/types.h>
@@ -232,6 +233,12 @@ struct dbc_work_ring {
uint64_t dma;
};
+enum xhci_share {
+ XHCI_SHARE_HWDOM = 0,
+ XHCI_SHARE_NONE,
+ XHCI_SHARE_ANY
+};
+
struct dbc {
struct dbc_reg __iomem *dbc_reg;
struct xhci_dbc_ctx *dbc_ctx;
@@ -250,6 +257,7 @@ struct dbc {
bool enable; /* whether dbgp=xhci was set at all */
bool open;
+ enum xhci_share share;
unsigned int xhc_num; /* look for n-th xhc */
};
@@ -961,13 +969,56 @@ static bool __init dbc_open(struct dbc *dbc)
}
/*
- * Ensure DbC is still running, handle events, and possibly re-enable if cable
- * was re-plugged. Returns true if DbC is operational.
+ * Ensure DbC is still running, handle events, and possibly
+ * re-enable/re-configure if cable was re-plugged or controller was reset.
+ * Returns true if DbC is operational.
*/
static bool dbc_ensure_running(struct dbc *dbc)
{
struct dbc_reg *reg = dbc->dbc_reg;
uint32_t ctrl;
+ uint16_t cmd;
+
+ if ( dbc->share != XHCI_SHARE_NONE )
+ {
+ /*
+ * Re-enable memory decoding and later bus mastering, if dom0 (or
+ * other) disabled it in the meantime.
+ */
+ cmd = pci_conf_read16(dbc->sbdf, PCI_COMMAND);
+ if ( !(cmd & PCI_COMMAND_MEMORY) )
+ {
+ cmd |= PCI_COMMAND_MEMORY;
+ pci_conf_write16(dbc->sbdf, PCI_COMMAND, cmd);
+ }
+
+ /*
+ * FIXME: Make Linux coordinate XHCI reset, so the DbC driver can
+ * prepare for it properly, instead of only detecting it after the
+ * fact. See EHCI driver for similar handling.
+ */
+ if ( dbc->open && !(readl(®->ctrl) & (1U << DBC_CTRL_DCE)) )
+ {
+ if ( !dbc_init_dbc(dbc) )
+ return false;
+
+ dbc_init_work_ring(dbc, &dbc->dbc_owork);
+ dbc_enable_dbc(dbc);
+ }
+ else
+ {
+ /*
+ * dbc_init_dbc() takes care about it, so check only if it wasn't
+ * called.
+ */
+ cmd = pci_conf_read16(dbc->sbdf, PCI_COMMAND);
+ if ( !(cmd & PCI_COMMAND_MASTER) )
+ {
+ cmd |= PCI_COMMAND_MASTER;
+ pci_conf_write16(dbc->sbdf, PCI_COMMAND, cmd);
+ }
+ }
+ }
dbc_pop_events(dbc);
@@ -1136,10 +1187,38 @@ static void __init cf_check dbc_uart_init_postirq(struct serial_port *port)
init_timer(&uart->timer, dbc_uart_poll, port, 0);
set_timer(&uart->timer, NOW() + MILLISECS(1));
- if ( pci_ro_device(0, uart->dbc.sbdf.bus, uart->dbc.sbdf.devfn) )
- printk(XENLOG_WARNING
- "Failed to mark read-only %pp used for XHCI console\n",
- &uart->dbc.sbdf);
+ switch ( uart->dbc.share )
+ {
+ case XHCI_SHARE_NONE:
+ if ( pci_ro_device(0, uart->dbc.sbdf.bus, uart->dbc.sbdf.devfn) )
+ printk(XENLOG_WARNING
+ "Failed to mark read-only %pp used for XHCI console\n",
+ &uart->dbc.sbdf);
+ break;
+ case XHCI_SHARE_HWDOM:
+ if ( pci_hide_device(0, uart->dbc.sbdf.bus, uart->dbc.sbdf.devfn) )
+ printk(XENLOG_WARNING
+ "Failed to hide %pp used for XHCI console\n",
+ &uart->dbc.sbdf);
+ break;
+ case XHCI_SHARE_ANY:
+ /* Do not hide. */
+ break;
+ }
+#ifdef CONFIG_X86
+ /*
+ * This marks the whole page as R/O, which may include other registers
+ * unrelated to DbC. Xen needs only DbC area protected, but it seems
+ * Linux's XHCI driver (as of 5.18) works without writting to the whole
+ * page, so keep it simple.
+ */
+ if ( rangeset_add_range(mmio_ro_ranges,
+ PFN_DOWN(uart->dbc.xhc_mmio_phys + uart->dbc.xhc_dbc_offset),
+ PFN_UP(uart->dbc.xhc_mmio_phys + uart->dbc.xhc_dbc_offset +
+ sizeof(*uart->dbc.dbc_reg)) - 1) )
+ printk(XENLOG_INFO
+ "Error while adding MMIO range of device to mmio_ro_ranges\n");
+#endif
}
static int cf_check dbc_uart_tx_ready(struct serial_port *port)
@@ -1207,13 +1286,15 @@ static int __init xhci_parse_dbgp(const char *opt_dbgp)
{
struct dbc_uart *uart = &dbc_uart;
struct dbc *dbc = &uart->dbc;
- const char *e;
+ const char *e, *opt;
+ int val;
if ( strncmp(opt_dbgp, "xhci", 4) )
return 0;
memset(dbc, 0, sizeof(*dbc));
+ e = &opt_dbgp[4];
if ( isdigit(opt_dbgp[4]) )
{
dbc->xhc_num = simple_strtoul(opt_dbgp + 4, &e, 10);
@@ -1223,7 +1304,7 @@ static int __init xhci_parse_dbgp(const char *opt_dbgp)
unsigned int bus, slot, func;
e = parse_pci(opt_dbgp + 8, NULL, &bus, &slot, &func);
- if ( !e || *e )
+ if ( !e || (*e && *e != ',') )
{
printk(XENLOG_ERR
"Invalid dbgp= PCI device spec: '%s'\n",
@@ -1233,6 +1314,38 @@ static int __init xhci_parse_dbgp(const char *opt_dbgp)
dbc->sbdf = PCI_SBDF(0, bus, slot, func);
}
+ opt = e;
+
+ /* other options */
+ while ( *opt == ',' )
+ {
+ opt++;
+ e = strchr(opt, ',');
+ if ( !e )
+ e = strchr(opt, '\0');
+
+ if ( (val = parse_boolean("share", opt, e)) != -1 )
+ {
+ if ( val == -2 && !cmdline_strcmp(opt + 6, "hwdom") )
+ dbc->share = XHCI_SHARE_HWDOM;
+ else if ( val == 0 )
+ dbc->share = XHCI_SHARE_NONE;
+ else if ( val == 1 )
+ dbc->share = XHCI_SHARE_ANY;
+ else
+ break;
+ }
+ else
+ break;
+
+ opt = e;
+ }
+
+ if ( *opt )
+ {
+ printk(XENLOG_ERR "Invalid dbgp= parameters: '%s'\n", opt);
+ return -EINVAL;
+ }
dbc->enable = true;