diff options
author | Rusty Russell <rusty@rustcorp.com.au> | 2015-02-10 23:54:01 -0500 |
---|---|---|
committer | Rusty Russell <rusty@rustcorp.com.au> | 2015-02-11 01:17:43 -0500 |
commit | 59eba788db298c3597728774dc3d0f16bdc8a1a4 (patch) | |
tree | 90013065e783dade54db4877efef5e2ad68a7644 | |
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>
-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 { | |||
156 | struct virtio_pci_notify_cap notify; | 156 | struct virtio_pci_notify_cap notify; |
157 | struct virtio_pci_cap isr; | 157 | struct virtio_pci_cap isr; |
158 | struct virtio_pci_cap device; | 158 | struct virtio_pci_cap device; |
159 | /* FIXME: Implement this! */ | ||
160 | struct virtio_pci_cfg_cap cfg_access; | 159 | struct virtio_pci_cfg_cap cfg_access; |
161 | }; | 160 | }; |
162 | 161 | ||
@@ -1184,6 +1183,36 @@ static struct device *dev_and_reg(u32 *reg) | |||
1184 | return find_pci_device(pci_config_addr.bits.devnum); | 1183 | return find_pci_device(pci_config_addr.bits.devnum); |
1185 | } | 1184 | } |
1186 | 1185 | ||
1186 | /* | ||
1187 | * We can get invalid combinations of values while they're writing, so we | ||
1188 | * only fault if they try to write with some invalid bar/offset/length. | ||
1189 | */ | ||
1190 | static bool valid_bar_access(struct device *d, | ||
1191 | struct virtio_pci_cfg_cap *cfg_access) | ||
1192 | { | ||
1193 | /* We only have 1 bar (BAR0) */ | ||
1194 | if (cfg_access->cap.bar != 0) | ||
1195 | return false; | ||
1196 | |||
1197 | /* Check it's within BAR0. */ | ||
1198 | if (cfg_access->cap.offset >= d->mmio_size | ||
1199 | || cfg_access->cap.offset + cfg_access->cap.length > d->mmio_size) | ||
1200 | return false; | ||
1201 | |||
1202 | /* Check length is 1, 2 or 4. */ | ||
1203 | if (cfg_access->cap.length != 1 | ||
1204 | && cfg_access->cap.length != 2 | ||
1205 | && cfg_access->cap.length != 4) | ||
1206 | return false; | ||
1207 | |||
1208 | /* Offset must be multiple of length */ | ||
1209 | if (cfg_access->cap.offset % cfg_access->cap.length != 0) | ||
1210 | return false; | ||
1211 | |||
1212 | /* Return pointer into word in BAR0. */ | ||
1213 | return true; | ||
1214 | } | ||
1215 | |||
1187 | /* Is this accessing the PCI config address port?. */ | 1216 | /* Is this accessing the PCI config address port?. */ |
1188 | static bool is_pci_addr_port(u16 port) | 1217 | static bool is_pci_addr_port(u16 port) |
1189 | { | 1218 | { |
@@ -1215,6 +1244,8 @@ static bool is_pci_data_port(u16 port) | |||
1215 | return port >= PCI_CONFIG_DATA && port < PCI_CONFIG_DATA + 4; | 1244 | return port >= PCI_CONFIG_DATA && port < PCI_CONFIG_DATA + 4; |
1216 | } | 1245 | } |
1217 | 1246 | ||
1247 | static void emulate_mmio_write(struct device *d, u32 off, u32 val, u32 mask); | ||
1248 | |||
1218 | static bool pci_data_iowrite(u16 port, u32 mask, u32 val) | 1249 | static bool pci_data_iowrite(u16 port, u32 mask, u32 val) |
1219 | { | 1250 | { |
1220 | u32 reg, portoff; | 1251 | u32 reg, portoff; |
@@ -1255,12 +1286,53 @@ static bool pci_data_iowrite(u16 port, u32 mask, u32 val) | |||
1255 | && mask == 0xFFFF) { | 1286 | && mask == 0xFFFF) { |
1256 | /* Ignore command writes. */ | 1287 | /* Ignore command writes. */ |
1257 | return true; | 1288 | return true; |
1289 | } else if (&d->config_words[reg] | ||
1290 | == (void *)&d->config.cfg_access.cap.bar | ||
1291 | || &d->config_words[reg] | ||
1292 | == &d->config.cfg_access.cap.length | ||
1293 | || &d->config_words[reg] | ||
1294 | == &d->config.cfg_access.cap.offset) { | ||
1295 | |||
1296 | /* | ||
1297 | * The VIRTIO_PCI_CAP_PCI_CFG capability | ||
1298 | * provides a backdoor to access the MMIO | ||
1299 | * regions without mapping them. Weird, but | ||
1300 | * useful. | ||
1301 | */ | ||
1302 | iowrite(portoff, val, mask, &d->config_words[reg]); | ||
1303 | return true; | ||
1304 | } else if (&d->config_words[reg] == &d->config.cfg_access.window) { | ||
1305 | u32 write_mask; | ||
1306 | |||
1307 | /* Must be bar 0 */ | ||
1308 | if (!valid_bar_access(d, &d->config.cfg_access)) | ||
1309 | return false; | ||
1310 | |||
1311 | /* First copy what they wrote into the window */ | ||
1312 | iowrite(portoff, val, mask, &d->config.cfg_access.window); | ||
1313 | |||
1314 | /* | ||
1315 | * Now emulate a write. The mask we use is set by | ||
1316 | * len, *not* this write! | ||
1317 | */ | ||
1318 | write_mask = (1ULL<<(8*d->config.cfg_access.cap.length)) - 1; | ||
1319 | verbose("Window writing %#x/%#x to bar %u, offset %u len %u\n", | ||
1320 | d->config.cfg_access.window, write_mask, | ||
1321 | d->config.cfg_access.cap.bar, | ||
1322 | d->config.cfg_access.cap.offset, | ||
1323 | d->config.cfg_access.cap.length); | ||
1324 | |||
1325 | emulate_mmio_write(d, d->config.cfg_access.cap.offset, | ||
1326 | d->config.cfg_access.window, write_mask); | ||
1327 | return true; | ||
1258 | } | 1328 | } |
1259 | 1329 | ||
1260 | /* Complain about other writes. */ | 1330 | /* Complain about other writes. */ |
1261 | return false; | 1331 | return false; |
1262 | } | 1332 | } |
1263 | 1333 | ||
1334 | static u32 emulate_mmio_read(struct device *d, u32 off, u32 mask); | ||
1335 | |||
1264 | static void pci_data_ioread(u16 port, u32 mask, u32 *val) | 1336 | static void pci_data_ioread(u16 port, u32 mask, u32 *val) |
1265 | { | 1337 | { |
1266 | u32 reg; | 1338 | u32 reg; |
@@ -1268,6 +1340,33 @@ static void pci_data_ioread(u16 port, u32 mask, u32 *val) | |||
1268 | 1340 | ||
1269 | if (!d) | 1341 | if (!d) |
1270 | return; | 1342 | return; |
1343 | |||
1344 | /* Read through the PCI MMIO access window is special */ | ||
1345 | if (&d->config_words[reg] == &d->config.cfg_access.window) { | ||
1346 | u32 read_mask; | ||
1347 | |||
1348 | /* Must be bar 0 */ | ||
1349 | if (!valid_bar_access(d, &d->config.cfg_access)) | ||
1350 | errx(1, "Invalid cfg_access to bar%u, offset %u len %u", | ||
1351 | d->config.cfg_access.cap.bar, | ||
1352 | d->config.cfg_access.cap.offset, | ||
1353 | d->config.cfg_access.cap.length); | ||
1354 | |||
1355 | /* | ||
1356 | * Read into the window. The mask we use is set by | ||
1357 | * len, *not* this read! | ||
1358 | */ | ||
1359 | read_mask = (1ULL<<(8*d->config.cfg_access.cap.length))-1; | ||
1360 | d->config.cfg_access.window | ||
1361 | = emulate_mmio_read(d, | ||
1362 | d->config.cfg_access.cap.offset, | ||
1363 | read_mask); | ||
1364 | verbose("Window read %#x/%#x from bar %u, offset %u len %u\n", | ||
1365 | d->config.cfg_access.window, read_mask, | ||
1366 | d->config.cfg_access.cap.bar, | ||
1367 | d->config.cfg_access.cap.offset, | ||
1368 | d->config.cfg_access.cap.length); | ||
1369 | } | ||
1271 | ioread(port - PCI_CONFIG_DATA, d->config_words[reg], mask, val); | 1370 | ioread(port - PCI_CONFIG_DATA, d->config_words[reg], mask, val); |
1272 | } | 1371 | } |
1273 | 1372 | ||