diff options
author | Rusty Russell | 2015-02-11 15:24:01 +1030 |
---|---|---|
committer | Rusty Russell | 2015-02-11 16:47:43 +1030 |
commit | 59eba788db298c3597728774dc3d0f16bdc8a1a4 (patch) | |
tree | 90013065e783dade54db4877efef5e2ad68a7644 /tools | |
parent | e8330d9bc1f7af7737500aebd3fc1f488e3dbb71 (diff) |
lguest: support backdoor window.
The VIRTIO_PCI_CAP_PCI_CFG in the PCI virtio 1.0 spec allows access to
the BAR registers without mapping them. This is a compulsory feature,
and we implement it here.
There are some subtleties involving access widths which we should
note:
4.1.4.7.1 Device Requirements: PCI configuration access capability
...
Upon detecting driver write access to pci_cfg_data, the device MUST
execute a write access at offset cap.offset at BAR selected by
cap.bar using the first cap.length bytes from pci_cfg_data.
Upon detecting driver read access to pci_cfg_data, the device MUST
execute a read access of length cap.length at offset cap.offset at
BAR selected by cap.bar and store the first cap.length bytes in
pci_cfg_data.
So, for a write, we copy into the pci_cfg_data window, then write from
there out to the BAR. This works correctly if cap.length != width of
write. Similarly, for a read, we read into window from the BAR then
read the value from there.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Diffstat (limited to 'tools')
-rw-r--r-- | tools/lguest/lguest.c | 101 |
1 files changed, 100 insertions, 1 deletions
diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c index 8959ac246668..e3c4d3d7dc2a 100644 --- a/tools/lguest/lguest.c +++ b/tools/lguest/lguest.c @@ -156,7 +156,6 @@ struct pci_config { struct virtio_pci_notify_cap notify; struct virtio_pci_cap isr; struct virtio_pci_cap device; - /* FIXME: Implement this! */ struct virtio_pci_cfg_cap cfg_access; }; @@ -1184,6 +1183,36 @@ static struct device *dev_and_reg(u32 *reg) return find_pci_device(pci_config_addr.bits.devnum); } +/* + * We can get invalid combinations of values while they're writing, so we + * only fault if they try to write with some invalid bar/offset/length. + */ +static bool valid_bar_access(struct device *d, + struct virtio_pci_cfg_cap *cfg_access) +{ + /* We only have 1 bar (BAR0) */ + if (cfg_access->cap.bar != 0) + return false; + + /* Check it's within BAR0. */ + if (cfg_access->cap.offset >= d->mmio_size + || cfg_access->cap.offset + cfg_access->cap.length > d->mmio_size) + return false; + + /* Check length is 1, 2 or 4. */ + if (cfg_access->cap.length != 1 + && cfg_access->cap.length != 2 + && cfg_access->cap.length != 4) + return false; + + /* Offset must be multiple of length */ + if (cfg_access->cap.offset % cfg_access->cap.length != 0) + return false; + + /* Return pointer into word in BAR0. */ + return true; +} + /* Is this accessing the PCI config address port?. */ static bool is_pci_addr_port(u16 port) { @@ -1215,6 +1244,8 @@ static bool is_pci_data_port(u16 port) return port >= PCI_CONFIG_DATA && port < PCI_CONFIG_DATA + 4; } +static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask); + static bool pci_data_iowrite(u16 port, u32 mask, u32 val) { u32 reg, portoff; @@ -1255,12 +1286,53 @@ static bool pci_data_iowrite(u16 port, u32 mask, u32 val) && mask == 0xFFFF) { /* Ignore command writes. */ return true; + } else if (&d->config_words[reg] + == (void *)&d->config.cfg_access.cap.bar + || &d->config_words[reg] + == &d->config.cfg_access.cap.length + || &d->config_words[reg] + == &d->config.cfg_access.cap.offset) { + + /* + * The VIRTIO_PCI_CAP_PCI_CFG capability + * provides a backdoor to access the MMIO + * regions without mapping them. Weird, but + * useful. + */ + iowrite(portoff, val, mask, &d->config_words[reg]); + return true; + } else if (&d->config_words[reg] == &d->config.cfg_access.window) { + u32 write_mask; + + /* Must be bar 0 */ + if (!valid_bar_access(d, &d->config.cfg_access)) + return false; + + /* First copy what they wrote into the window */ + iowrite(portoff, val, mask, &d->config.cfg_access.window); + + /* + * Now emulate a write. The mask we use is set by + * len, *not* this write! + */ + write_mask = (1ULL<<(8*d->config.cfg_access.cap.length)) - 1; + verbose("Window writing %#x/%#x to bar %u, offset %u len %u\n", + d->config.cfg_access.window, write_mask, + d->config.cfg_access.cap.bar, + d->config.cfg_access.cap.offset, + d->config.cfg_access.cap.length); + + emulate_mmio_write(d, d->config.cfg_access.cap.offset, + d->config.cfg_access.window, write_mask); + return true; } /* Complain about other writes. */ return false; } +static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask); + static void pci_data_ioread(u16 port, u32 mask, u32 *val) { u32 reg; @@ -1268,6 +1340,33 @@ static void pci_data_ioread(u16 port, u32 mask, u32 *val) if (!d) return; + + /* Read through the PCI MMIO access window is special */ + if (&d->config_words[reg] == &d->config.cfg_access.window) { + u32 read_mask; + + /* Must be bar 0 */ + if (!valid_bar_access(d, &d->config.cfg_access)) + errx(1, "Invalid cfg_access to bar%u, offset %u len %u", + d->config.cfg_access.cap.bar, + d->config.cfg_access.cap.offset, + d->config.cfg_access.cap.length); + + /* + * Read into the window. The mask we use is set by + * len, *not* this read! + */ + read_mask = (1ULL<<(8*d->config.cfg_access.cap.length))-1; + d->config.cfg_access.window + = emulate_mmio_read(d, + d->config.cfg_access.cap.offset, + read_mask); + verbose("Window read %#x/%#x from bar %u, offset %u len %u\n", + d->config.cfg_access.window, read_mask, + d->config.cfg_access.cap.bar, + d->config.cfg_access.cap.offset, + d->config.cfg_access.cap.length); + } ioread(port - PCI_CONFIG_DATA, d->config_words[reg], mask, val); } |