/*- * Copyright (c) 1997, Stefan Esser * Copyright (c) 2000, Michael Smith * Copyright (c) 2000, BSDi * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD: src/sys/amd64/pci/pci_cfgreg.c,v 1.108 2005/01/21 05:56:41 peter Exp $"); #include #include #include #include #include #include #include #include static int cfgmech; static int devmax; static int pcireg_cfgread(int bus, int slot, int func, int reg, int bytes); static void pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes); static int pcireg_cfgopen(void); static struct mtx pcicfg_mtx; static uint8_t pci_mech1_getb(int bus, int dev, int func, int reg); static uint16_t pci_mech1_getw(int bus, int dev, int func, int reg); static uint32_t pci_mech1_getl(int bus, int dev, int func, int reg); static void pci_mech1_putb(int bus, int dev, int func, int reg, uint8_t val); static void pci_mech1_putw(int bus, int dev, int func, int reg, uint16_t val); static void pci_mech1_putl(int bus, int dev, int func, int reg, uint32_t val); /* * Initialise access to PCI configuration space */ int pci_cfgregopen(void) { static int opened = 0; if (opened) return (1); if (pcireg_cfgopen() == 0) return (0); mtx_init(&pcicfg_mtx, "pcicfg", NULL, MTX_SPIN); opened = 1; return (1); } /* * Read configuration space register */ u_int32_t pci_cfgregread(int bus, int slot, int func, int reg, int bytes) { uint32_t line; /* * Some BIOS writers seem to want to ignore the spec and put * 0 in the intline rather than 255 to indicate none. Some use * numbers in the range 128-254 to indicate something strange and * apparently undocumented anywhere. Assume these are completely bogus * and map them to 255, which the rest of the PCI code recognizes as * as an invalid IRQ. */ if (reg == PCIR_INTLINE && bytes == 1) { line = pcireg_cfgread(bus, slot, func, PCIR_INTLINE, 1); if (line == 0 || line >= 128) line = PCI_INVALID_IRQ; return (line); } return (pcireg_cfgread(bus, slot, func, reg, bytes)); } /* * Write configuration space register */ void pci_cfgregwrite(int bus, int slot, int func, int reg, u_int32_t data, int bytes) { pcireg_cfgwrite(bus, slot, func, reg, data, bytes); } /* * Configuration space access using direct register operations */ /* enable configuration space accesses and return data port address */ static int pci_cfgenable(unsigned bus, unsigned slot, unsigned func, int reg, int bytes) { int dataport = 0; if (bus <= PCI_BUSMAX && slot < devmax && func <= PCI_FUNCMAX && reg <= PCI_REGMAX && bytes != 3 && (unsigned) bytes <= 4 && (reg & (bytes - 1)) == 0) { switch (cfgmech) { case 1: outl(CONF1_ADDR_PORT, (1 << 31) | (bus << 16) | (slot << 11) | (func << 8) | (reg & ~0x03)); dataport = CONF1_DATA_PORT + (reg & 0x03); break; case 2: outb(CONF2_ENABLE_PORT, 0xf0 | (func << 1)); outb(CONF2_FORWARD_PORT, bus); dataport = 0xc000 | (slot << 8) | reg; break; } } return (dataport); } /* disable configuration space accesses */ static void pci_cfgdisable(void) { switch (cfgmech) { case 1: outl(CONF1_ADDR_PORT, 0); break; case 2: outb(CONF2_ENABLE_PORT, 0); outb(CONF2_FORWARD_PORT, 0); break; } } static int pcireg_cfgread(int bus, int slot, int func, int reg, int bytes) { int data = -1; #ifdef notdef int port; mtx_lock_spin(&pcicfg_mtx); port = pci_cfgenable(bus, slot, func, reg, bytes); if (port != 0) { switch (bytes) { case 1: data = inb(port); break; case 2: data = inw(port); break; case 4: data = inl(port); break; } pci_cfgdisable(); } mtx_unlock_spin(&pcicfg_mtx); #endif switch(bytes) { case 1: data = pci_mech1_getb(bus, slot, func, reg); break; case 2: data = pci_mech1_getw(bus, slot, func, reg); break; case 4: data = pci_mech1_getl(bus, slot, func, reg); break; default: panic("bad PCI access width"); break; } return (data); } static void pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes) { #ifdef notdef int port; mtx_lock_spin(&pcicfg_mtx); port = pci_cfgenable(bus, slot, func, reg, bytes); if (port != 0) { switch (bytes) { case 1: outb(port, data); break; case 2: outw(port, data); break; case 4: outl(port, data); break; } pci_cfgdisable(); } mtx_unlock_spin(&pcicfg_mtx); #endif switch (bytes) { case 1: pci_mech1_putb(bus, slot, func, reg, (uint8_t)data); break; case 2: pci_mech1_putw(bus, slot, func, reg, (uint16_t)data); break; case 4: pci_mech1_putl(bus, slot, func, reg, (uint32_t)data); break; default: panic("bad PCI access width"); break; } return; } /* check whether the configuration mechanism has been correctly identified */ static int pci_cfgcheck(int maxdev) { uint32_t id, class; uint8_t header; uint8_t device; int port; if (bootverbose) printf("pci_cfgcheck:\tdevice "); for (device = 0; device < maxdev; device++) { if (bootverbose) printf("%d ", device); port = pci_cfgenable(0, device, 0, 0, 4); id = inl(port); if (id == 0 || id == 0xffffffff) continue; port = pci_cfgenable(0, device, 0, 8, 4); class = inl(port) >> 8; if (bootverbose) printf("[class=%06x] ", class); if (class == 0 || (class & 0xf870ff) != 0) continue; port = pci_cfgenable(0, device, 0, 14, 1); header = inb(port); if (bootverbose) printf("[hdr=%02x] ", header); if ((header & 0x7e) != 0) continue; if (bootverbose) printf("is there (id=%08x)\n", id); pci_cfgdisable(); return (1); } if (bootverbose) printf("-- nothing found\n"); pci_cfgdisable(); return (0); } static int pcireg_cfgopen(void) { uint32_t mode1res, oldval1; uint8_t mode2res, oldval2; oldval1 = inl(CONF1_ADDR_PORT); if (bootverbose) { printf("pci_open(1):\tmode 1 addr port (0x0cf8) is 0x%08x\n", oldval1); } if ((oldval1 & CONF1_ENABLE_MSK) == 0) { cfgmech = 1; devmax = 32; outl(CONF1_ADDR_PORT, CONF1_ENABLE_CHK); DELAY(1); mode1res = inl(CONF1_ADDR_PORT); outl(CONF1_ADDR_PORT, oldval1); if (bootverbose) printf("pci_open(1a):\tmode1res=0x%08x (0x%08lx)\n", mode1res, CONF1_ENABLE_CHK); if (mode1res) { if (pci_cfgcheck(32)) return (cfgmech); } outl(CONF1_ADDR_PORT, CONF1_ENABLE_CHK1); mode1res = inl(CONF1_ADDR_PORT); outl(CONF1_ADDR_PORT, oldval1); if (bootverbose) printf("pci_open(1b):\tmode1res=0x%08x (0x%08lx)\n", mode1res, CONF1_ENABLE_CHK1); if ((mode1res & CONF1_ENABLE_MSK1) == CONF1_ENABLE_RES1) { if (pci_cfgcheck(32)) return (cfgmech); } } oldval2 = inb(CONF2_ENABLE_PORT); if (bootverbose) { printf("pci_open(2):\tmode 2 enable port (0x0cf8) is 0x%02x\n", oldval2); } if ((oldval2 & 0xf0) == 0) { cfgmech = 2; devmax = 16; outb(CONF2_ENABLE_PORT, CONF2_ENABLE_CHK); mode2res = inb(CONF2_ENABLE_PORT); outb(CONF2_ENABLE_PORT, oldval2); if (bootverbose) printf("pci_open(2a):\tmode2res=0x%02x (0x%02x)\n", mode2res, CONF2_ENABLE_CHK); if (mode2res == CONF2_ENABLE_RES) { if (bootverbose) printf("pci_open(2a):\tnow trying mechanism 2\n"); if (pci_cfgcheck(16)) return (cfgmech); } } cfgmech = 0; devmax = 0; return (cfgmech); } #define PCI_CONFADD 0xcf8 #define PCI_PMC 0xcfb #define PCI_CONFDATA 0xcfc #define PCI_CONE 0x80000000 #define PCI_CADDR1(bus, device, function, reg) \ (PCI_CONE | (((bus) & 0xff) << 16) | (((device & 0x1f)) << 11) \ | (((function) & 0x7) << 8) | ((reg) & 0xfc)) #define PCI_MECH1_SPEC_CYCLE_DEV 0x1f /* dev to request spec cyc */ #define PCI_MECH1_SPEC_CYCLE_FUNC 0x07 /* func to request spec cyc */ static uint8_t pci_mech1_getb(int bus, int device, int function, int reg) { uint8_t val; if (device == PCI_MECH1_SPEC_CYCLE_DEV && function == PCI_MECH1_SPEC_CYCLE_FUNC) { return (0xff); } mtx_lock_spin(&pcicfg_mtx); outl(PCI_CONFADD, PCI_CADDR1(bus, device, function, reg)); val = inb(PCI_CONFDATA | (reg & 0x3)); mtx_unlock_spin(&pcicfg_mtx); return (val); } static uint16_t pci_mech1_getw(int bus, int device, int function, int reg) { uint16_t val; if (device == PCI_MECH1_SPEC_CYCLE_DEV && function == PCI_MECH1_SPEC_CYCLE_FUNC) { return (0xff); } mtx_lock_spin(&pcicfg_mtx); outl(PCI_CONFADD, PCI_CADDR1(bus, device, function, reg)); val = inw(PCI_CONFDATA | (reg & 0x2)); mtx_unlock_spin(&pcicfg_mtx); return (val); } static uint32_t pci_mech1_getl(int bus, int device, int function, int reg) { uint32_t val; if (device == PCI_MECH1_SPEC_CYCLE_DEV && function == PCI_MECH1_SPEC_CYCLE_FUNC) { return (0xff); } mtx_lock_spin(&pcicfg_mtx); outl(PCI_CONFADD, PCI_CADDR1(bus, device, function, reg)); val = inl(PCI_CONFDATA); mtx_unlock_spin(&pcicfg_mtx); return (val); } void pci_mech1_putb(int bus, int device, int function, int reg, uint8_t val) { if (device == PCI_MECH1_SPEC_CYCLE_DEV && function == PCI_MECH1_SPEC_CYCLE_FUNC) { return; } mtx_lock_spin(&pcicfg_mtx); outl(PCI_CONFADD, PCI_CADDR1(bus, device, function, reg)); outb(PCI_CONFDATA | (reg & 0x3), val); mtx_unlock_spin(&pcicfg_mtx); return; } void pci_mech1_putw(int bus, int device, int function, int reg, uint16_t val) { if (device == PCI_MECH1_SPEC_CYCLE_DEV && function == PCI_MECH1_SPEC_CYCLE_FUNC) { return; } mtx_lock_spin(&pcicfg_mtx); outl(PCI_CONFADD, PCI_CADDR1(bus, device, function, reg)); outw(PCI_CONFDATA | (reg & 0x2), val); mtx_unlock_spin(&pcicfg_mtx); return; } void pci_mech1_putl(int bus, int device, int function, int reg, uint32_t val) { if (device == PCI_MECH1_SPEC_CYCLE_DEV && function == PCI_MECH1_SPEC_CYCLE_FUNC) { return; } mtx_lock_spin(&pcicfg_mtx); outl(PCI_CONFADD, PCI_CADDR1(bus, device, function, reg)); outl(PCI_CONFDATA, val); mtx_unlock_spin(&pcicfg_mtx); return; }