/* * Copyright (c) 2011-2017 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * pci.cpp * Functions for handling PCI devices. */ #include #include #include #include #include #include #include #include #include namespace Sortix { namespace PCI { static kthread_mutex_t pci_lock = KTHREAD_MUTEX_INITIALIZER; static const uint16_t CONFIG_ADDRESS = 0xCF8; static const uint16_t CONFIG_DATA = 0xCFC; uint32_t MakeDevAddr(uint8_t bus, uint8_t slot, uint8_t func) { //assert(bus < 1UL<<8UL); // bus is 8 bit anyways. assert(slot < 1UL<<5UL); assert(func < 1UL<<3UL); return func << 8U | slot << 11U | bus << 16U | 1 << 31U; } void SplitDevAddr(uint32_t devaddr, uint8_t* vals /* bus, slot, func */) { vals[0] = devaddr >> 16U & ((1UL<<8UL)-1); vals[1] = devaddr >> 11U & ((1UL<<3UL)-1); vals[2] = devaddr >> 8U & ((1UL<<5UL)-1); } uint32_t ReadRaw32(uint32_t devaddr, uint8_t off) { assert((off & 0x3) == 0); outport32(CONFIG_ADDRESS, devaddr + off); return inport32(CONFIG_DATA); } void WriteRaw32(uint32_t devaddr, uint8_t off, uint32_t val) { assert((off & 0x3) == 0); outport32(CONFIG_ADDRESS, devaddr + off); outport32(CONFIG_DATA, val); } uint32_t Read32(uint32_t devaddr, uint8_t off) { return le32toh(ReadRaw32(devaddr, off)); } void Write32(uint32_t devaddr, uint8_t off, uint32_t val) { WriteRaw32(devaddr, off, htole32(val)); } void Write16(uint32_t devaddr, uint8_t off, uint16_t val) { assert((off & 0x1) == 0); uint8_t alignedoff = off & ~0x3; union { uint8_t val8[4]; uint32_t val32; }; val32 = ReadRaw32(devaddr, alignedoff); val8[(off & 0x3) + 0] = val >> 0 & 0xFF; val8[(off & 0x3) + 1] = val >> 8 & 0xFF; WriteRaw32(devaddr, alignedoff, val32); } uint16_t Read16(uint32_t devaddr, uint8_t off) { assert((off & 0x1) == 0); uint8_t alignedoff = off & ~0x3; union { uint8_t val8[4]; uint32_t val32; }; val32 = ReadRaw32(devaddr, alignedoff); return (uint16_t) val8[(off & 0x3) + 0] << 0 | (uint16_t) val8[(off & 0x3) + 1] << 8; } void Write8(uint32_t devaddr, uint8_t off, uint8_t val) { uint8_t alignedoff = off & ~0x3; union { uint8_t val8[4]; uint32_t val32; }; val32 = ReadRaw32(devaddr, alignedoff); val8[(off & 0x3)] = val; WriteRaw32(devaddr, alignedoff, val32); } uint8_t Read8(uint32_t devaddr, uint8_t off) { uint8_t alignedoff = off & ~0x3; union { uint8_t val8[4]; uint32_t val32; }; val32 = ReadRaw32(devaddr, alignedoff); return val8[(off & 0x3)]; } uint32_t CheckDevice(uint8_t bus, uint8_t slot, uint8_t func) { return Read32(MakeDevAddr(bus, slot, func), 0x0); } pciid_t GetDeviceId(uint32_t devaddr) { pciid_t ret; ret.deviceid = Read16(devaddr, PCIFIELD_DEVICE_ID); ret.vendorid = Read16(devaddr, PCIFIELD_VENDOR_ID); return ret; } pcitype_t GetDeviceType(uint32_t devaddr) { pcitype_t ret; ret.classid = Read8(devaddr, PCIFIELD_CLASS); ret.subclassid = Read8(devaddr, PCIFIELD_SUBCLASS); ret.progif = Read8(devaddr, PCIFIELD_PROG_IF); ret.revid = Read8(devaddr, PCIFIELD_REVISION_ID); return ret; } static void MakeCoarsePattern(pcifind_t* coarse, const pcifind_t* patterns, size_t pattern_count) { if ( pattern_count < 1 ) { memset(coarse, 255, sizeof(*coarse)); return; } const pcifind_t* first = patterns; coarse->vendorid = first->vendorid; coarse->deviceid = first->deviceid; coarse->classid = first->classid; coarse->subclassid = first->subclassid; coarse->progif = first->progif; coarse->revid = first->revid; for ( size_t i = 1; i < pattern_count; i++ ) { const pcifind_t* pattern = patterns + i; if ( coarse->vendorid != pattern->vendorid ) coarse->vendorid = 0xffff; if ( coarse->deviceid != pattern->deviceid ) coarse->deviceid = 0xffff; if ( coarse->classid != pattern->classid ) coarse->classid = 0xff; if ( coarse->subclassid != pattern->subclassid ) coarse->subclassid = 0xff; if ( coarse->progif != pattern->progif ) coarse->progif = 0xff; if ( coarse->revid != pattern->revid ) coarse->revid = 0xff; } } static bool MatchesPattern(const pciid_t* id, const pcitype_t* type, const pcifind_t* pattern) { if ( id->vendorid == 0xFFFF && id->deviceid == 0xFFFF ) return false; if ( pattern->vendorid != 0xFFFF && id->vendorid != pattern->vendorid ) return false; if ( pattern->deviceid != 0xFFFF && id->deviceid != pattern->deviceid ) return false; if ( pattern->classid != 0xFF && type->classid != pattern->classid ) return false; if ( pattern->subclassid != 0xFF && type->subclassid != pattern->subclassid ) return false; if ( pattern->progif != 0xFF && type->progif != pattern->progif ) return false; if ( pattern->revid != 0xFF && type->revid != pattern->revid ) return false; return true; } static const pcifind_t* MatchesPatterns(const pciid_t* id, const pcitype_t* type, const pcifind_t* patterns, size_t pattern_count) { if ( id->vendorid == 0xFFFF || id->deviceid == 0xFFFF ) return NULL; for ( size_t i = 0; i < pattern_count; i++ ) { const pcifind_t* pattern = &patterns[i]; if ( MatchesPattern(id, type, pattern) ) return pattern; } return NULL; } static bool SearchBus(bool (*callback)(uint32_t, const pciid_t*, const pcitype_t*, void*, void*), void* context, const pcifind_t* coarse_pattern, const pcifind_t* patterns, size_t pattern_count, uint8_t bus) { for ( unsigned int slot = 0; slot < 32; slot++ ) { unsigned int num_functions = 1; for ( unsigned int function = 0; function < num_functions; function++ ) { uint32_t devaddr = MakeDevAddr(bus, slot, function); pciid_t id = GetDeviceId(devaddr); pcitype_t type = GetDeviceType(devaddr); uint8_t header = Read8(devaddr, PCIFIELD_HEADER_TYPE); if ( header & 0x80 ) // Multi function device. num_functions = 8; if ( (header & 0x7F) == 0x01 ) // PCI to PCI bus. { uint8_t subbusid = Read8(devaddr, PCIFIELD_SECONDARY_BUS_NUMBER); bool search = SearchBus(callback, context, coarse_pattern, patterns, pattern_count, subbusid); if ( !search ) return false; } // Do a coarse pattern before the more detailed one to save time. if ( 1 < pattern_count && !MatchesPattern(&id, &type, coarse_pattern) ) continue; const pcifind_t* pattern = MatchesPatterns(&id, &type, patterns, pattern_count); if ( !pattern ) continue; // Unlock PCI in this scope to allow the callback to lock and change // settings. Stop the search if the callback fails. kthread_mutex_unlock(&pci_lock); bool continue_search = callback(devaddr, &id, &type, context, pattern->context); kthread_mutex_lock(&pci_lock); if ( !continue_search ) return false; } } return true; } void Search(bool (*callback)(uint32_t, const pciid_t*, const pcitype_t*, void*, void*), void* context, const pcifind_t* patterns, size_t pattern_count) { pcifind_t coarse_pattern; MakeCoarsePattern(&coarse_pattern, patterns, pattern_count); ScopedLock lock(&pci_lock); SearchBus(callback, context, &coarse_pattern, patterns, pattern_count, 0); } static bool MatchesPatternByDevAddr(uint32_t devaddr, const pcifind_t* pcifind) { pciid_t id = GetDeviceId(devaddr); if ( id.vendorid == 0xFFFF && id.deviceid == 0xFFFF ) return false; pcitype_t type = GetDeviceType(devaddr); return MatchesPattern(&id, &type, pcifind); } // TODO: This iterates the whole PCI device tree on each call! Transition the // callers to use the new callback API and delete this API. static uint32_t SearchForDevicesOnBus(uint8_t bus, pcifind_t pcifind, uint32_t last = 0) { bool found_any_device = false; uint32_t next_device = 0; for ( unsigned int slot = 0; slot < 32; slot++ ) { unsigned int num_functions = 1; for ( unsigned int function = 0; function < num_functions; function++ ) { uint32_t devaddr = MakeDevAddr(bus, slot, function); if ( last < devaddr && (!found_any_device || devaddr < next_device) && MatchesPatternByDevAddr(devaddr, &pcifind) ) next_device = devaddr, found_any_device = true; uint8_t header = Read8(devaddr, PCIFIELD_HEADER_TYPE); if ( header & 0x80 ) // Multi function device. num_functions = 8; if ( (header & 0x7F) == 0x01 ) // PCI to PCI bus. { uint8_t subbusid = Read8(devaddr, PCIFIELD_SECONDARY_BUS_NUMBER); uint32_t recret = SearchForDevicesOnBus(subbusid, pcifind, last); if ( last < recret && (!found_any_device || recret < next_device) ) next_device = recret, found_any_device = true; } } } if ( !found_any_device ) return 0; return next_device; } uint32_t SearchForDevices(pcifind_t pcifind, uint32_t last) { // Search on bus 0 and recurse on other detected busses. return SearchForDevicesOnBus(0, pcifind, last); } pcibar_t GetBAR(uint32_t devaddr, uint8_t bar) { ScopedLock lock(&pci_lock); uint32_t low = PCI::Read32(devaddr, 0x10 + 4 * (bar+0)); pcibar_t result; result.addr_raw = low; result.size_raw = 0; if ( result.is_64bit() ) { uint32_t high = PCI::Read32(devaddr, 0x10 + 4 * (bar+1)); result.addr_raw |= (uint64_t) high << 32; PCI::Write32(devaddr, 0x10 + 4 * (bar+0), 0xFFFFFFFF); PCI::Write32(devaddr, 0x10 + 4 * (bar+1), 0xFFFFFFFF); uint32_t size_low = PCI::Read32(devaddr, 0x10 + 4 * (bar+0)); uint32_t size_high = PCI::Read32(devaddr, 0x10 + 4 * (bar+1)); PCI::Write32(devaddr, 0x10 + 4 * (bar+0), low); PCI::Write32(devaddr, 0x10 + 4 * (bar+1), high); result.size_raw = (uint64_t) size_high << 32 | (uint64_t) size_low << 0; result.size_raw = ~(result.size_raw & 0xFFFFFFFFFFFFFFF0) + 1; } else if ( result.is_32bit() ) { PCI::Write32(devaddr, 0x10 + 4 * (bar+0), 0xFFFFFFFF); uint32_t size_low = PCI::Read32(devaddr, 0x10 + 4 * (bar+0)); PCI::Write32(devaddr, 0x10 + 4 * (bar+0), low); result.size_raw = (uint64_t) size_low << 0; result.size_raw = ~(result.size_raw & 0xFFFFFFF0) + 1; result.size_raw &= 0xFFFFFFFF; } else if ( result.is_iospace() ) { PCI::Write32(devaddr, 0x10 + 4 * (bar+0), 0xFFFFFFFF); uint32_t size_low = PCI::Read32(devaddr, 0x10 + 4 * (bar+0)); PCI::Write32(devaddr, 0x10 + 4 * (bar+0), low); result.size_raw = (uint64_t) size_low << 0; result.size_raw = ~(result.size_raw & 0xFFFFFFFC) + 1; result.size_raw &= 0xFFFFFFFF; } return result; } pcibar_t GetExpansionROM(uint32_t devaddr) { const uint32_t ROM_ADDRESS_MASK = ~UINT32_C(0x7FF); ScopedLock lock(&pci_lock); uint32_t low = PCI::Read32(devaddr, 0x30); PCI::Write32(devaddr, 0x30, ROM_ADDRESS_MASK | low); uint32_t size_low = PCI::Read32(devaddr, 0x30); PCI::Write32(devaddr, 0x30, low); pcibar_t result; result.addr_raw = (low & ROM_ADDRESS_MASK) | PCIBAR_TYPE_32BIT; result.size_raw = ~(size_low & ROM_ADDRESS_MASK) + 1; return result; } void EnableExpansionROM(uint32_t devaddr) { ScopedLock lock(&pci_lock); PCI::Write32(devaddr, 0x30, PCI::Read32(devaddr, 0x30) | 0x1); } void DisableExpansionROM(uint32_t devaddr) { ScopedLock lock(&pci_lock); PCI::Write32(devaddr, 0x30, PCI::Read32(devaddr, 0x30) & ~UINT32_C(0x1)); } bool IsExpansionROMEnabled(uint32_t devaddr) { ScopedLock lock(&pci_lock); return PCI::Read32(devaddr, 0x30) & 0x1; } static bool IsOkayInterruptLine(uint8_t line) { if ( line == 0 ) return false; // Conflict with PIT. if ( line == 2 ) return false; // Cascade, can't be received. if ( 16 <= line ) return false; // Not in set of valid IRQs. return true; } uint8_t SetupInterruptLine(uint32_t devaddr) { ScopedLock lock(&pci_lock); uint8_t line = Read8(devaddr, PCIFIELD_INTERRUPT_LINE); if ( !IsOkayInterruptLine(line) ) return 0; return Interrupt::IRQ0 + line; } void EnableBusMaster(uint32_t devaddr) { ScopedLock lock(&pci_lock); uint16_t command = PCI::Read16(devaddr, PCIFIELD_COMMAND); PCI::Write16(devaddr, PCIFIELD_COMMAND, command | PCIFIELD_COMMAND_BUS_MASTER); } void DisableBusMaster(uint32_t devaddr) { ScopedLock lock(&pci_lock); uint16_t command = PCI::Read16(devaddr, PCIFIELD_COMMAND); PCI::Write16(devaddr, PCIFIELD_COMMAND, command & ~PCIFIELD_COMMAND_BUS_MASTER); } void EnableMemoryWrite(uint32_t devaddr) { ScopedLock lock(&pci_lock); uint16_t command = PCI::Read16(devaddr, PCIFIELD_COMMAND); PCI::Write16(devaddr, PCIFIELD_COMMAND, command | PCIFIELD_COMMAND_MEMORY_WRITE_AND_INVALIDATE); } void DisableMemoryWrite(uint32_t devaddr) { ScopedLock lock(&pci_lock); uint16_t command = PCI::Read16(devaddr, PCIFIELD_COMMAND); PCI::Write16(devaddr, PCIFIELD_COMMAND, command & ~PCIFIELD_COMMAND_MEMORY_WRITE_AND_INVALIDATE); } void EnableInterruptLine(uint32_t devaddr) { ScopedLock lock(&pci_lock); uint16_t command = PCI::Read16(devaddr, PCIFIELD_COMMAND); PCI::Write16(devaddr, PCIFIELD_COMMAND, command & ~PCIFIELD_COMMAND_INTERRUPT_DISABLE); } void DisableInterruptLine(uint32_t devaddr) { ScopedLock lock(&pci_lock); uint16_t command = PCI::Read16(devaddr, PCIFIELD_COMMAND); PCI::Write16(devaddr, PCIFIELD_COMMAND, command | PCIFIELD_COMMAND_INTERRUPT_DISABLE); } uint8_t GetInterruptIndex(uint32_t devaddr) { ScopedLock lock(&pci_lock); uint32_t line = PCI::Read8(devaddr, PCIFIELD_INTERRUPT_LINE) & 0xf; return Interrupt::IRQ0 + line; } void Init() { } } // namespace PCI } // namespace Sortix