diff --git a/src/drivers/bus/pci.c b/src/drivers/bus/pci.c index 06b36a770e..e6260ca6ca 100644 --- a/src/drivers/bus/pci.c +++ b/src/drivers/bus/pci.c @@ -143,29 +143,67 @@ static void pci_read_bases ( struct pci_device *pci ) { * * @v pci PCI device * - * Set device to be a busmaster in case BIOS neglected to do so. Also - * adjust PCI latency timer to a reasonable value, 32. + * Set device to be a bus master in case BIOS neglected to do so. Enable + * both memory and I/O space access. Also adjust PCI latency timer to a + * reasonable value, 32. */ void adjust_pci_device ( struct pci_device *pci ) { + pci_enable_device ( pci, + PCI_COMMAND_MASTER | PCI_COMMAND_MEM | + PCI_COMMAND_IO, + 32 ); +} + +/** + * Enable PCI device + * + * @v pci PCI device + * @v flags PCI command flags to enable + * @v latency Minimum desired PCI latency timer or 0 to not adjust it + * @ret old_flags Original PCI command flags + * + * Enable device by setting bits in the command register in case BIOS neglected + * to do so. Also optionally adjust the PCI latency timer. + */ +unsigned pci_enable_device ( struct pci_device *pci, unsigned flags, + unsigned char latency ) { unsigned short new_command, pci_command; unsigned char pci_latency; pci_read_config_word ( pci, PCI_COMMAND, &pci_command ); - new_command = ( pci_command | PCI_COMMAND_MASTER | - PCI_COMMAND_MEM | PCI_COMMAND_IO ); + new_command = ( pci_command | flags ); if ( pci_command != new_command ) { - DBGC ( pci, PCI_FMT " device not enabled by BIOS! Updating " + DBGC ( pci, PCI_FMT " device not fully enabled by BIOS! Updating " "PCI command %04x->%04x\n", PCI_ARGS ( pci ), pci_command, new_command ); pci_write_config_word ( pci, PCI_COMMAND, new_command ); } - pci_read_config_byte ( pci, PCI_LATENCY_TIMER, &pci_latency); - if ( pci_latency < 32 ) { - DBGC ( pci, PCI_FMT " latency timer is unreasonably low at " - "%d. Setting to 32.\n", PCI_ARGS ( pci ), pci_latency ); - pci_write_config_byte ( pci, PCI_LATENCY_TIMER, 32); + if ( latency ) { + pci_read_config_byte ( pci, PCI_LATENCY_TIMER, &pci_latency ); + if ( pci_latency < latency ) { + DBGC ( pci, PCI_FMT " latency timer is unreasonably low at " + "%d. Setting to %d.\n", PCI_ARGS ( pci ), + pci_latency, latency ); + pci_write_config_byte ( pci, PCI_LATENCY_TIMER, latency ); + } } + return pci_command; +} + +/** + * Restore PCI device + * + * @v pci PCI device + * @v old_flags Flags to restore, returned from pci_enable_device + * + * Restore the command register of a PCI device to state it had before we + * changed it. + */ +void pci_restore_device ( struct pci_device *pci, unsigned old_flags ) { + DBGC ( pci, PCI_FMT " restoring PCI command to %04x\n", + PCI_ARGS ( pci ), old_flags ); + pci_write_config_word ( pci, PCI_COMMAND, old_flags ); } /** diff --git a/src/drivers/bus/virtio-pci.c b/src/drivers/bus/virtio-pci.c index 3311595fb2..cb7b5e1c04 100644 --- a/src/drivers/bus/virtio-pci.c +++ b/src/drivers/bus/virtio-pci.c @@ -387,6 +387,16 @@ int vpm_find_vqs(struct virtio_pci_modern_device *vdev, if (err) { goto err_map_notify; } + + /* enable memory or I/O access if not already enabled */ + switch (vq->notification.flags & VIRTIO_PCI_REGION_TYPE_MASK) { + case VIRTIO_PCI_REGION_PORT: + pci_enable_device(vdev->pci, PCI_COMMAND_IO, 0); + break; + case VIRTIO_PCI_REGION_MEMORY: + pci_enable_device(vdev->pci, PCI_COMMAND_MEM, 0); + break; + } } /* Select and activate all queues. Has to be done last: once we do diff --git a/src/drivers/net/virtio-net.c b/src/drivers/net/virtio-net.c index fe0fd4b860..5b15836d63 100644 --- a/src/drivers/net/virtio-net.c +++ b/src/drivers/net/virtio-net.c @@ -92,6 +92,9 @@ struct virtnet_nic { /** 0 for legacy, 1 for virtio 1.0 */ int virtio_version; + /** Content of the command register before we initialized the device */ + unsigned old_pci_command; + /** Virtio 1.0 device data */ struct virtio_pci_modern_device vdev; @@ -463,7 +466,8 @@ static int virtnet_probe_legacy ( struct pci_device *pci ) { virtnet, pci->dev.name, ioaddr, pci->irq ); /* Enable PCI bus master and reset NIC */ - adjust_pci_device ( pci ); + virtnet->old_pci_command = pci_enable_device ( pci, + PCI_COMMAND_MASTER | PCI_COMMAND_IO, 0 ); vp_reset ( ioaddr ); /* Load MAC address */ @@ -487,11 +491,33 @@ static int virtnet_probe_legacy ( struct pci_device *pci ) { unregister_netdev ( netdev ); err_register_netdev: vp_reset ( ioaddr ); + pci_restore_device ( pci, virtnet->old_pci_command ); netdev_nullify ( netdev ); netdev_put ( netdev ); return rc; } +/** + * Determine if the device uses at least one region of the given type + * + * @v vdev Virtio-net device data + * @v type The region type to test + * @ret result Non-zero if the type is used, zero otherwise + */ +static int virtnet_uses_region_type ( struct virtio_pci_modern_device *vdev, + unsigned type ) { + if ( ( vdev->common.flags & VIRTIO_PCI_REGION_TYPE_MASK ) == type ) { + return 1; + } + if ( ( vdev->isr.flags & VIRTIO_PCI_REGION_TYPE_MASK ) == type ) { + return 1; + } + if ( ( vdev->device.flags & VIRTIO_PCI_REGION_TYPE_MASK ) == type ) { + return 1; + } + return 0; +} + /** * Probe PCI device, modern virtio 1.0 * @@ -504,6 +530,7 @@ static int virtnet_probe_modern ( struct pci_device *pci, int *found_dev ) { struct virtnet_nic *virtnet; u64 features; int rc, common, isr, notify, config, device; + unsigned pci_command; common = virtio_pci_find_capability ( pci, VIRTIO_PCI_CAP_COMMON_CFG ); if ( ! common ) { @@ -562,7 +589,14 @@ static int virtnet_probe_modern ( struct pci_device *pci, int *found_dev ) { } /* Enable the PCI device */ - adjust_pci_device ( pci ); + pci_command = PCI_COMMAND_MASTER; + if ( virtnet_uses_region_type ( &virtnet->vdev, VIRTIO_PCI_REGION_PORT ) ) { + pci_command |= PCI_COMMAND_IO; + } + if ( virtnet_uses_region_type ( &virtnet->vdev, VIRTIO_PCI_REGION_MEMORY ) ) { + pci_command |= PCI_COMMAND_MEM; + } + virtnet->old_pci_command = pci_enable_device ( pci, pci_command, 0 ); /* Reset the device and set initial status bits */ vpm_reset ( &virtnet->vdev ); @@ -601,6 +635,7 @@ static int virtnet_probe_modern ( struct pci_device *pci, int *found_dev ) { err_register_netdev: err_mac_address: vpm_reset ( &virtnet->vdev ); + pci_restore_device ( pci, virtnet->old_pci_command ); netdev_nullify ( netdev ); netdev_put ( netdev ); @@ -638,6 +673,8 @@ static void virtnet_remove ( struct pci_device *pci ) { struct net_device *netdev = pci_get_drvdata ( pci ); struct virtnet_nic *virtnet = netdev->priv; + pci_restore_device ( pci, virtnet->old_pci_command ); + virtio_pci_unmap_capability ( &virtnet->vdev.device ); virtio_pci_unmap_capability ( &virtnet->vdev.isr ); virtio_pci_unmap_capability ( &virtnet->vdev.common ); diff --git a/src/include/ipxe/pci.h b/src/include/ipxe/pci.h index ddd8c8d1e2..d39d28a0f4 100644 --- a/src/include/ipxe/pci.h +++ b/src/include/ipxe/pci.h @@ -281,6 +281,9 @@ struct pci_driver { PCI_SLOT ( (pci)->busdevfn ), PCI_FUNC ( (pci)->busdevfn ) extern void adjust_pci_device ( struct pci_device *pci ); +extern unsigned pci_enable_device ( struct pci_device *pci, unsigned flags, + unsigned char latency ); +extern void pci_restore_device ( struct pci_device *pci, unsigned old_flags ); extern unsigned long pci_bar_start ( struct pci_device *pci, unsigned int reg ); extern int pci_read_config ( struct pci_device *pci );