blob: 3da1b8664193fd62b8b5df4159e24bcfa4449add [file] [log] [blame]
/* Copyright (c) 2009, 2010 The Regents of the University of California
* See LICENSE for details.
*
* Barret Rhoden <brho@cs.berkeley.edu>
* Original by Paul Pearce <pearce@eecs.berkeley.edu> */
#include <arch/x86.h>
#include <arch/pci.h>
#include <trap.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <kmalloc.h>
#include <mm.h>
#include <arch/pci_defs.h>
#include <ros/errno.h>
/* List of all discovered devices */
struct pcidev_stailq pci_devices = STAILQ_HEAD_INITIALIZER(pci_devices);
/* PCI accesses are two-stage PIO, which need to complete atomically */
spinlock_t pci_lock = SPINLOCK_INITIALIZER_IRQSAVE;
static char STD_PCI_DEV[] = "Standard PCI Device";
static char PCI2PCI[] = "PCI-to-PCI Bridge";
static char PCI2CARDBUS[] = "PCI-Cardbus Bridge";
/* Gets any old raw bar, with some catches based on type. */
static uint32_t pci_getbar(struct pci_device *pcidev, unsigned int bar)
{
uint8_t type;
if (bar >= MAX_PCI_BAR)
panic("Nonexistant bar requested!");
type = pcidev_read8(pcidev, PCI_HEADER_REG);
type &= ~0x80; /* drop the MF bit */
/* Only types 0 and 1 have BARS */
if ((type != 0x00) && (type != 0x01))
return 0;
/* Only type 0 has BAR2 - BAR5 */
if ((bar > 1) && (type != 0x00))
return 0;
return pcidev_read32(pcidev, PCI_BAR0_STD + bar * PCI_BAR_OFF);
}
/* Determines if a given bar is IO (o/w, it's mem) */
static bool pci_is_iobar(uint32_t bar)
{
return bar & PCI_BAR_IO;
}
static bool pci_is_membar32(uint32_t bar)
{
if (pci_is_iobar(bar))
return FALSE;
return (bar & PCI_MEMBAR_TYPE) == PCI_MEMBAR_32BIT;
}
static bool pci_is_membar64(uint32_t bar)
{
if (pci_is_iobar(bar))
return FALSE;
return (bar & PCI_MEMBAR_TYPE) == PCI_MEMBAR_64BIT;
}
/* Helper to get the address from a membar. Check the type beforehand */
static uint32_t pci_getmembar32(uint32_t bar)
{
uint8_t type = bar & PCI_MEMBAR_TYPE;
if (type != PCI_MEMBAR_32BIT) {
warn("Unhandled PCI membar type: %02p\n", type >> 1);
return 0;
}
return bar & 0xfffffff0;
}
/* Helper to get the address from an IObar. Check the type beforehand */
static uint32_t pci_getiobar32(uint32_t bar)
{
return bar & 0xfffffffc;
}
/* memory bars have a little dance you go through to detect what the size of the
* memory region is. for 64 bit bars, i'm assuming you only need to do this to
* the lower part (no device will need > 4GB, right?).
*
* Hold the dev's lock, or o/w avoid sync issues. */
static uint32_t __pci_membar_get_sz(struct pci_device *pcidev, int bar)
{
/* save the old value, write all 1s, invert, add 1, restore.
* http://wiki.osdev.org/PCI for details. */
uint32_t bar_off = PCI_BAR0_STD + bar * PCI_BAR_OFF;
uint32_t old_val = pcidev_read32(pcidev, bar_off);
uint32_t retval;
pcidev_write32(pcidev, bar_off, 0xffffffff);
/* Don't forget to mask the lower 3 bits! */
retval = pcidev_read32(pcidev, bar_off) & PCI_BAR_MEM_MASK;
retval = ~retval + 1;
pcidev_write32(pcidev, bar_off, old_val);
return retval;
}
/* process the bars. these will tell us what address space (PIO or memory) and
* where the base is. fills results into pcidev. i don't know if you can have
* multiple bars with conflicting/different regions (like two separate PIO
* ranges). I'm assuming you don't, and will warn if we see one. */
static void __pci_handle_bars(struct pci_device *pcidev)
{
uint32_t bar_val;
int max_bars;
if (pcidev->header_type == STD_PCI_DEV)
max_bars = MAX_PCI_BAR;
else if (pcidev->header_type == PCI2PCI)
max_bars = 2;
else
max_bars = 0;
/* TODO: consider aborting for classes 00, 05 (memory ctlr), 06 (bridge)
*/
for (int i = 0; i < max_bars; i++) {
bar_val = pci_getbar(pcidev, i);
pcidev->bar[i].raw_bar = bar_val;
if (!bar_val) /* (0 denotes no valid data) */
continue;
if (pci_is_iobar(bar_val)) {
pcidev->bar[i].pio_base = pci_getiobar32(bar_val);
} else {
if (pci_is_membar32(bar_val)) {
pcidev->bar[i].mmio_base32 =
bar_val & PCI_BAR_MEM_MASK;
pcidev->bar[i].mmio_sz =
__pci_membar_get_sz(pcidev, i);
} else if (pci_is_membar64(bar_val)) {
/* 64 bit, the lower 32 are in this bar, the
* upper are in the next bar */
pcidev->bar[i].mmio_base64 =
bar_val & PCI_BAR_MEM_MASK;
assert(i < max_bars - 1);
/* read next bar */
bar_val = pci_getbar(pcidev, i + 1);
/* note we don't check for IO or memsize. the
* entire next bar is supposed to be for the
* upper 32 bits. */
pcidev->bar[i].mmio_base64 |=
(uint64_t)bar_val << 32;
pcidev->bar[i].mmio_sz =
__pci_membar_get_sz(pcidev, i);
i++;
}
}
/* this will track the maximum bar we've had. it'll include the
* 64 bit uppers, as well as devices that have only higher
* numbered bars. */
pcidev->nr_bars = i + 1;
}
}
static void __pci_parse_caps(struct pci_device *pcidev)
{
uint32_t cap_off; /* not sure if this can be extended from u8 */
uint8_t cap_id;
if (!(pcidev_read16(pcidev, PCI_STATUS_REG) & (1 << 4)))
return;
switch (pcidev_read8(pcidev, PCI_HEADER_REG) & 0x7f) {
case 0: /* etc */
case 1: /* pci to pci bridge */
cap_off = 0x34;
break;
case 2: /* cardbus bridge */
cap_off = 0x14;
break;
default:
return;
}
/* initial offset points to the addr of the first cap */
cap_off = pcidev_read8(pcidev, cap_off);
cap_off &= ~0x3; /* osdev says the lower 2 bits are reserved */
while (cap_off) {
cap_id = pcidev_read8(pcidev, cap_off);
if (cap_id > PCI_CAP_ID_MAX) {
printk("PCI %x:%x:%x had bad cap 0x%x\n", pcidev->bus,
pcidev->dev, pcidev->func, cap_id);
return;
}
pcidev->caps[cap_id] = cap_off;
cap_off = pcidev_read8(pcidev, cap_off + 1);
/* not sure if subsequent caps must be aligned or not */
if (cap_off & 0x3)
printk("PCI %x:%x:%x had unaligned cap offset 0x%x\n",
pcidev->bus, pcidev->dev, pcidev->func, cap_off);
}
}
/* Scans the PCI bus. Won't actually work for anything other than bus 0, til we
* sort out how to handle bridge devices. */
void pci_init(void)
{
uint32_t result = 0;
uint16_t dev_id, ven_id;
struct pci_device *pcidev;
int max_nr_func;
/* In earlier days bus address 0xff caused problems so we only iterated
* to PCI_MAX_BUS - 1, but this should no longer be an issue. Old
* comment: phantoms at 0xff */
for (int i = 0; i < PCI_MAX_BUS; i++) {
for (int j = 0; j < PCI_MAX_DEV; j++) {
max_nr_func = 1;
for (int k = 0; k < max_nr_func; k++) {
result = pci_read32(i, j, k, PCI_DEV_VEND_REG);
dev_id = result >> 16;
ven_id = result & 0xffff;
/* Skip invalid IDs (not a device)
* If the first function doesn't exist then no
* device is connected, but there can be gaps in
* the other function numbers. Eg. 0,2,3 is ok.
* */
if (ven_id == INVALID_VENDOR_ID) {
if (k == 0)
break;
continue;
}
pcidev = kzmalloc(sizeof(struct pci_device), 0);
/* we don't need to lock it til we post the
* pcidev to the list*/
spinlock_init_irqsave(&pcidev->lock);
pcidev->bus = i;
pcidev->dev = j;
pcidev->func = k;
snprintf(pcidev->name, sizeof(pcidev->name),
"%02x:%02x.%x", pcidev->bus,
pcidev->dev, pcidev->func);
pcidev->dev_id = dev_id;
pcidev->ven_id = ven_id;
/* Get the Class/subclass */
pcidev->class =
pcidev_read8(pcidev, PCI_CLASS_REG);
pcidev->subclass =
pcidev_read8(pcidev, PCI_SUBCLASS_REG);
pcidev->progif =
pcidev_read8(pcidev, PCI_PROGIF_REG);
/* All device types (0, 1, 2) have the IRQ in
* the same place */
/* This is the PIC IRQ the device is wired to */
pcidev->irqline =
pcidev_read8(pcidev, PCI_IRQLINE_STD);
/* This is the interrupt pin the device uses
* (INTA# - INTD#) */
pcidev->irqpin =
pcidev_read8(pcidev, PCI_IRQPIN_STD);
/* bottom 7 bits are header type */
switch (pcidev_read8(pcidev, PCI_HEADER_REG)
& 0x7c) {
case 0x00:
pcidev->header_type = STD_PCI_DEV;
break;
case 0x01:
pcidev->header_type = PCI2PCI;
break;
case 0x02:
pcidev->header_type = PCI2CARDBUS;
break;
default:
pcidev->header_type =
"Unknown Header Type";
}
__pci_handle_bars(pcidev);
__pci_parse_caps(pcidev);
/* we're the only writer at this point in the
* boot process */
STAILQ_INSERT_TAIL(&pci_devices, pcidev,
all_dev);
#ifdef CONFIG_PCI_VERBOSE
pcidev_print_info(pcidev, 4);
#else
pcidev_print_info(pcidev, 0);
#endif /* CONFIG_PCI_VERBOSE */
/* Top bit determines if we have multiple
* functions on this device. We can't just
* check for more functions, since
* non-multifunction devices exist that respond
* to different functions with the same
* underlying device (same bars etc). Note that
* this style allows for devices that only
* report multifunction in the first function's
* header. */
if (pcidev_read8(pcidev, PCI_HEADER_REG) & 0x80)
max_nr_func = PCI_MAX_FUNC;
}
}
}
}
uint32_t pci_config_addr(uint8_t bus, uint8_t dev, uint8_t func, uint32_t reg)
{
return (uint32_t)(((uint32_t)bus << 16) |
((uint32_t)dev << 11) |
((uint32_t)func << 8) |
(reg & 0xfc) |
((reg & 0xf00) << 16) |/* extended PCI CFG space... */
0x80000000);
}
/* Helper to read 32 bits from the config space of B:D:F. 'Offset' is how far
* into the config space we offset before reading, aka: where we are reading. */
uint32_t pci_read32(uint8_t bus, uint8_t dev, uint8_t func, uint32_t offset)
{
uint32_t ret;
spin_lock_irqsave(&pci_lock);
outl(PCI_CONFIG_ADDR, pci_config_addr(bus, dev, func, offset));
ret = inl(PCI_CONFIG_DATA);
spin_unlock_irqsave(&pci_lock);
return ret;
}
/* Same, but writes (doing 32bit at a time). Never actually tested (not sure if
* PCI lets you write back). */
void pci_write32(uint8_t bus, uint8_t dev, uint8_t func, uint32_t offset,
uint32_t value)
{
spin_lock_irqsave(&pci_lock);
outl(PCI_CONFIG_ADDR, pci_config_addr(bus, dev, func, offset));
outl(PCI_CONFIG_DATA, value);
spin_unlock_irqsave(&pci_lock);
}
uint16_t pci_read16(uint8_t bus, uint8_t dev, uint8_t func, uint32_t offset)
{
uint16_t ret;
spin_lock_irqsave(&pci_lock);
outl(PCI_CONFIG_ADDR, pci_config_addr(bus, dev, func, offset));
ret = inw(PCI_CONFIG_DATA + (offset & 2));
spin_unlock_irqsave(&pci_lock);
return ret;
}
void pci_write16(uint8_t bus, uint8_t dev, uint8_t func, uint32_t offset,
uint16_t value)
{
spin_lock_irqsave(&pci_lock);
outl(PCI_CONFIG_ADDR, pci_config_addr(bus, dev, func, offset));
outw(PCI_CONFIG_DATA + (offset & 2), value);
spin_unlock_irqsave(&pci_lock);
}
uint8_t pci_read8(uint8_t bus, uint8_t dev, uint8_t func, uint32_t offset)
{
uint8_t ret;
spin_lock_irqsave(&pci_lock);
outl(PCI_CONFIG_ADDR, pci_config_addr(bus, dev, func, offset));
ret = inb(PCI_CONFIG_DATA + (offset & 3));
spin_unlock_irqsave(&pci_lock);
return ret;
}
void pci_write8(uint8_t bus, uint8_t dev, uint8_t func, uint32_t offset,
uint8_t value)
{
spin_lock_irqsave(&pci_lock);
outl(PCI_CONFIG_ADDR, pci_config_addr(bus, dev, func, offset));
outb(PCI_CONFIG_DATA + (offset & 3), value);
spin_unlock_irqsave(&pci_lock);
}
uint32_t pcidev_read32(struct pci_device *pcidev, uint32_t offset)
{
return pci_read32(pcidev->bus, pcidev->dev, pcidev->func, offset);
}
void pcidev_write32(struct pci_device *pcidev, uint32_t offset, uint32_t value)
{
pci_write32(pcidev->bus, pcidev->dev, pcidev->func, offset, value);
}
uint16_t pcidev_read16(struct pci_device *pcidev, uint32_t offset)
{
return pci_read16(pcidev->bus, pcidev->dev, pcidev->func, offset);
}
void pcidev_write16(struct pci_device *pcidev, uint32_t offset, uint16_t value)
{
pci_write16(pcidev->bus, pcidev->dev, pcidev->func, offset, value);
}
uint8_t pcidev_read8(struct pci_device *pcidev, uint32_t offset)
{
return pci_read8(pcidev->bus, pcidev->dev, pcidev->func, offset);
}
void pcidev_write8(struct pci_device *pcidev, uint32_t offset, uint8_t value)
{
pci_write8(pcidev->bus, pcidev->dev, pcidev->func, offset, value);
}
/* Helper to get the class description strings. Adapted from
* http://www.pcidatabase.com/reports.php?type=c-header */
static void pcidev_get_cldesc(struct pci_device *pcidev, char **class,
char **subclass, char **progif)
{
int i;
*class = *subclass = *progif = "";
for (i = 0; i < PCI_CLASSCODETABLE_LEN; i++) {
if (PciClassCodeTable[i].BaseClass == pcidev->class) {
if (!(**class))
*class = PciClassCodeTable[i].BaseDesc;
if (PciClassCodeTable[i].SubClass == pcidev->subclass) {
if (!(**subclass))
*subclass =
PciClassCodeTable[i].SubDesc;
if (PciClassCodeTable[i].ProgIf ==
pcidev->progif) {
*progif = PciClassCodeTable[i].ProgDesc;
break ;
}
}
}
}
}
/* Helper to get the vendor and device description strings */
static void pcidev_get_devdesc(struct pci_device *pcidev, char **vend_short,
char **vend_full, char **chip, char **chip_desc)
{
int i;
*vend_short = *vend_full = *chip = *chip_desc = "";
for (i = 0; i < PCI_VENTABLE_LEN; i++) {
if (PciVenTable[i].VenId == pcidev->ven_id) {
*vend_short = PciVenTable[i].VenShort;
*vend_full = PciVenTable[i].VenFull;
break ;
}
}
for (i = 0; i < PCI_DEVTABLE_LEN; i++) {
if ((PciDevTable[i].VenId == pcidev->ven_id) &&
(PciDevTable[i].DevId == pcidev->dev_id)) {
*chip = PciDevTable[i].Chip;
*chip_desc = PciDevTable[i].ChipDesc;
break ;
}
}
}
/* Prints info (like lspci) for a device */
void pcidev_print_info(struct pci_device *pcidev, int verbosity)
{
char *ven_sht, *ven_fl, *chip, *chip_txt, *class, *subcl, *progif;
pcidev_get_cldesc(pcidev, &class, &subcl, &progif);
pcidev_get_devdesc(pcidev, &ven_sht, &ven_fl, &chip, &chip_txt);
printk("%02x:%02x.%x %s: %s %s %s: %s\n",
pcidev->bus,
pcidev->dev,
pcidev->func,
subcl,
ven_sht,
chip,
chip_txt,
pcidev->header_type);
if (verbosity < 1) /* whatever */
return;
printk("\tIRQ: %02d IRQ pin: 0x%02x\n",
pcidev->irqline,
pcidev->irqpin);
printk("\tVendor Id: 0x%04x Device Id: 0x%04x\n",
pcidev->ven_id,
pcidev->dev_id);
printk("\t%s %s %s\n",
class,
progif,
ven_fl);
for (int i = 0; i < pcidev->nr_bars; i++) {
if (pcidev->bar[i].raw_bar == 0)
continue;
printk("\tBAR %d: ", i);
if (pci_is_iobar(pcidev->bar[i].raw_bar)) {
assert(pcidev->bar[i].pio_base);
printk("IO port 0x%04x\n", pcidev->bar[i].pio_base);
} else {
bool bar_is_64 =
pci_is_membar64(pcidev->bar[i].raw_bar);
printk("MMIO Base%s %p, MMIO Size %p\n",
bar_is_64 ? "64" : "32",
bar_is_64 ? pcidev->bar[i].mmio_base64 :
pcidev->bar[i].mmio_base32,
pcidev->bar[i].mmio_sz);
/* Takes up two bars */
if (bar_is_64) {
assert(!pcidev->bar[i].mmio_base32);
i++;
}
}
}
printk("\tCapabilities:");
for (int i = 0; i < PCI_CAP_ID_MAX + 1; i++) {
if (pcidev->caps[i])
printk(" 0x%02x", i);
}
printk("\n");
}
void pci_set_bus_master(struct pci_device *pcidev)
{
spin_lock_irqsave(&pcidev->lock);
pcidev_write16(pcidev, PCI_CMD_REG, pcidev_read16(pcidev, PCI_CMD_REG) |
PCI_CMD_BUS_MAS);
spin_unlock_irqsave(&pcidev->lock);
}
void pci_clr_bus_master(struct pci_device *pcidev)
{
uint16_t reg;
spin_lock_irqsave(&pcidev->lock);
reg = pcidev_read16(pcidev, PCI_CMD_REG);
reg &= ~PCI_CMD_BUS_MAS;
pcidev_write16(pcidev, PCI_CMD_REG, reg);
spin_unlock_irqsave(&pcidev->lock);
}
struct pci_device *pci_match_tbdf(int tbdf)
{
struct pci_device *search;
int bus, dev, func;
bus = BUSBNO(tbdf);
dev = BUSDNO(tbdf);
func = BUSFNO(tbdf);
STAILQ_FOREACH(search, &pci_devices, all_dev) {
if ((search->bus == bus) &&
(search->dev == dev) &&
(search->func == func))
return search;
}
return NULL;
}
/* Helper to get the membar value for BAR index bir */
uintptr_t pci_get_membar(struct pci_device *pcidev, int bir)
{
if (bir >= pcidev->nr_bars)
return 0;
if (pcidev->bar[bir].mmio_base64) {
assert(pci_is_membar64(pcidev->bar[bir].raw_bar));
return pcidev->bar[bir].mmio_base64;
}
/* we can just return mmio_base32, even if it's 0. but i'd like to do
* the assert too. */
if (pcidev->bar[bir].mmio_base32) {
assert(pci_is_membar32(pcidev->bar[bir].raw_bar));
return pcidev->bar[bir].mmio_base32;
}
return 0;
}
uintptr_t pci_get_iobar(struct pci_device *pcidev, int bir)
{
if (bir >= pcidev->nr_bars)
return 0;
/* we can just return pio_base, even if it's 0. but i'd like to do the
* assert too. */
if (pcidev->bar[bir].pio_base) {
assert(pci_is_iobar(pcidev->bar[bir].raw_bar));
return pcidev->bar[bir].pio_base;
}
return 0;
}
uint32_t pci_get_membar_sz(struct pci_device *pcidev, int bir)
{
if (bir >= pcidev->nr_bars)
return 0;
return pcidev->bar[bir].mmio_sz;
}
uint16_t pci_get_vendor(struct pci_device *pcidev)
{
return pcidev->ven_id;
}
uint16_t pci_get_device(struct pci_device *pcidev)
{
return pcidev->dev_id;
}
uint16_t pci_get_subvendor(struct pci_device *pcidev)
{
uint8_t header_type = pcidev_read8(pcidev, PCI_HEADER_REG) & 0x7c;
switch (header_type) {
case 0x00: /* STD_PCI_DEV */
return pcidev_read16(pcidev, PCI_SUBSYSVEN_STD);
case 0x01: /* PCI2PCI */
return -1;
case 0x02: /* PCI2CARDBUS */
return pcidev_read16(pcidev, PCI_SUBVENID_CB);
default:
warn("Unknown Header Type, %d", header_type);
}
return -1;
}
uint16_t pci_get_subdevice(struct pci_device *pcidev)
{
uint8_t header_type = pcidev_read8(pcidev, PCI_HEADER_REG) & 0x7c;
switch (header_type) {
case 0x00: /* STD_PCI_DEV */
return pcidev_read16(pcidev, PCI_SUBSYSID_STD);
case 0x01: /* PCI2PCI */
return -1;
case 0x02: /* PCI2CARDBUS */
return pcidev_read16(pcidev, PCI_SUBDEVID_CB);
default:
warn("Unknown Header Type, %d", header_type);
}
return -1;
}
void pci_dump_config(struct pci_device *pcidev, size_t len)
{
if (len > 256)
printk("FYI, printing more than 256 bytes of PCI space\n");
printk("PCI Config space for %02x:%02x:%02x\n---------------------\n",
pcidev->bus, pcidev->dev, pcidev->func);
for (int i = 0; i < len; i += 4)
printk("0x%03x | %08x\n", i, pcidev_read32(pcidev, i));
}
int pci_find_cap(struct pci_device *pcidev, uint8_t cap_id, uint32_t *cap_reg)
{
if (cap_id > PCI_CAP_ID_MAX)
return -EINVAL;
if (!pcidev->caps[cap_id])
return -ENOENT;
/* The actual value at caps[id] is the offset in the PCI config space
* where that ID was stored. That's needed for accessing the
* capability. */
if (cap_reg)
*cap_reg = pcidev->caps[cap_id];
return 0;
}
unsigned int pci_to_tbdf(struct pci_device *pcidev)
{
return MKBUS(BusPCI, pcidev->bus, pcidev->dev, pcidev->func);
}
uintptr_t pci_map_membar(struct pci_device *dev, int bir)
{
uintptr_t paddr = pci_get_membar(dev, bir);
size_t sz = pci_get_membar_sz(dev, bir);
if (!paddr || !sz)
return 0;
return vmap_pmem_nocache(paddr, sz);
}
/* The following were ported from Linux:
*
* pci_set_cacheline_size
* pci_set_mwi
* pci_clear_mwi
*/
int pci_set_cacheline_size(struct pci_device *dev)
{
uint8_t cl_sz;
uint8_t pci_cache_line_size = ARCH_CL_SIZE >> 2;
cl_sz = pcidev_read8(dev, PCI_CACHE_LINE_SIZE);
/* Validate current setting: the PCI_CACHE_LINE_SIZE must be equal to or
* multiple of the right value. */
if (cl_sz >= pci_cache_line_size && (cl_sz % pci_cache_line_size) == 0)
return 0;
pcidev_write8(dev, PCI_CACHE_LINE_SIZE, pci_cache_line_size);
cl_sz = pcidev_read8(dev, PCI_CACHE_LINE_SIZE);
if (cl_sz == pci_cache_line_size)
return 0;
printk("PCI device %s does not support cache line size of %d\n",
dev->name, pci_cache_line_size << 2);
return -EINVAL;
}
int pci_set_mwi(struct pci_device *dev)
{
int rc;
uint16_t cmd;
rc = pci_set_cacheline_size(dev);
if (rc)
return rc;
cmd = pcidev_read16(dev, PCI_COMMAND);
if (!(cmd & PCI_COMMAND_INVALIDATE)) {
cmd |= PCI_COMMAND_INVALIDATE;
pcidev_write16(dev, PCI_COMMAND, cmd);
}
return 0;
}
void pci_clear_mwi(struct pci_device *dev)
{
uint16_t cmd;
cmd = pcidev_read16(dev, PCI_COMMAND);
if (cmd & PCI_COMMAND_INVALIDATE) {
cmd &= ~PCI_COMMAND_INVALIDATE;
pcidev_write16(dev, PCI_COMMAND, cmd);
}
}