/* * Copyright 2022, Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. */ extern "C" { #include "device.h" #include #include #include } #include //#define DEBUG_PCI #ifdef DEBUG_PCI # define TRACE_PCI(dev, format, args...) device_printf(dev, format , ##args) #else # define TRACE_PCI(dev, format, args...) do { } while (0) #endif pci_module_info *gPci; status_t init_pci() { if (gPci != NULL) return B_OK; status_t status = get_module(B_PCI_MODULE_NAME, (module_info **)&gPci); if (status != B_OK) return status; return B_OK; } void uninit_pci() { if (gPci != NULL) put_module(B_PCI_MODULE_NAME); } pci_info* get_device_pci_info(device_t device) { struct root_device_softc* root_softc = (struct root_device_softc*)device->root->softc; if (root_softc->bus != root_device_softc::BUS_pci) return NULL; return &root_softc->pci_info; } uint32_t pci_read_config(device_t dev, int offset, int size) { pci_info* info = get_device_pci_info(dev); uint32_t value = gPci->read_pci_config(info->bus, info->device, info->function, offset, size); TRACE_PCI(dev, "pci_read_config(%i, %i) = 0x%x\n", offset, size, value); return value; } void pci_write_config(device_t dev, int offset, uint32_t value, int size) { pci_info* info = get_device_pci_info(dev); TRACE_PCI(dev, "pci_write_config(%i, 0x%x, %i)\n", offset, value, size); gPci->write_pci_config(info->bus, info->device, info->function, offset, size, value); } uint16_t pci_get_vendor(device_t dev) { return pci_read_config(dev, PCI_vendor_id, 2); } uint16_t pci_get_device(device_t dev) { return pci_read_config(dev, PCI_device_id, 2); } uint16_t pci_get_subvendor(device_t dev) { return pci_read_config(dev, PCI_subsystem_vendor_id, 2); } uint16_t pci_get_subdevice(device_t dev) { return pci_read_config(dev, PCI_subsystem_id, 2); } uint8_t pci_get_revid(device_t dev) { return pci_read_config(dev, PCI_revision, 1); } uint32_t pci_get_domain(device_t dev) { return 0; } uint32_t pci_get_devid(device_t dev) { return pci_read_config(dev, PCI_device_id, 2) << 16 | pci_read_config(dev, PCI_vendor_id, 2); } uint8_t pci_get_cachelnsz(device_t dev) { return pci_read_config(dev, PCI_line_size, 1); } uint8_t * pci_get_ether(device_t dev) { /* used in if_dc to get the MAC from CardBus CIS for Xircom card */ return NULL; /* NULL is handled in the caller correctly */ } uint8_t pci_get_bus(device_t dev) { pci_info *info = &((struct root_device_softc *)dev->root->softc)->pci_info; return info->bus; } uint8_t pci_get_slot(device_t dev) { pci_info *info = &((struct root_device_softc *)dev->root->softc)->pci_info; return info->device; } uint8_t pci_get_function(device_t dev) { pci_info* info = get_device_pci_info(dev); return info->function; } device_t pci_find_dbsf(uint32_t domain, uint8_t bus, uint8_t slot, uint8_t func) { // We don't support that yet - if we want to support the multi port // feature of the Broadcom BCM 570x driver, we would have to change // that. return NULL; } static void pci_set_command_bit(device_t dev, uint16_t bit) { uint16_t command = pci_read_config(dev, PCI_command, 2); pci_write_config(dev, PCI_command, command | bit, 2); } int pci_enable_busmaster(device_t dev) { // We do this a bit later than FreeBSD does. if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) pci_set_powerstate(dev, PCI_POWERSTATE_D0); pci_set_command_bit(dev, PCI_command_master); return 0; } int pci_enable_io(device_t dev, int space) { /* adapted from FreeBSD's pci_enable_io_method */ int bit = 0; switch (space) { case SYS_RES_IOPORT: bit = PCI_command_io; break; case SYS_RES_MEMORY: bit = PCI_command_memory; break; default: return EINVAL; } pci_set_command_bit(dev, bit); if (pci_read_config(dev, PCI_command, 2) & bit) return 0; device_printf(dev, "pci_enable_io(%d) failed.\n", space); return ENXIO; } int pci_find_cap(device_t dev, int capability, int *capreg) { pci_info* info = get_device_pci_info(dev); uint8 offset; status_t status; status = gPci->find_pci_capability(info->bus, info->device, info->function, capability, &offset); *capreg = offset; return status; } int pci_find_extcap(device_t dev, int capability, int *capreg) { pci_info* info = get_device_pci_info(dev); uint16 offset; status_t status; status = gPci->find_pci_extended_capability(info->bus, info->device, info->function, capability, &offset); *capreg = offset; return status; } int pci_msi_count(device_t dev) { pci_info* info = get_device_pci_info(dev); return gPci->get_msi_count(info->bus, info->device, info->function); } int pci_alloc_msi(device_t dev, int *count) { pci_info* info = get_device_pci_info(dev); uint32 startVector = 0; if (gPci->configure_msi(info->bus, info->device, info->function, *count, &startVector) != B_OK) { return ENODEV; } ((struct root_device_softc *)dev->root->softc)->is_msi = true; info->u.h0.interrupt_line = startVector; return EOK; } int pci_release_msi(device_t dev) { pci_info* info = get_device_pci_info(dev); gPci->unconfigure_msi(info->bus, info->device, info->function); ((struct root_device_softc *)dev->root->softc)->is_msi = false; ((struct root_device_softc *)dev->root->softc)->is_msix = false; return EOK; } int pci_msix_table_bar(device_t dev) { pci_info* info = get_device_pci_info(dev); uint8 capability_offset; if (gPci->find_pci_capability(info->bus, info->device, info->function, PCI_cap_id_msix, &capability_offset) != B_OK) return -1; uint32 table_value = gPci->read_pci_config(info->bus, info->device, info->function, capability_offset + PCI_msix_table, 4); uint32 bar = table_value & PCI_msix_bir_mask; return PCIR_BAR(bar); } int pci_msix_count(device_t dev) { pci_info* info = get_device_pci_info(dev); return gPci->get_msix_count(info->bus, info->device, info->function); } int pci_alloc_msix(device_t dev, int *count) { pci_info* info = get_device_pci_info(dev); uint32 startVector = 0; if (gPci->configure_msix(info->bus, info->device, info->function, *count, &startVector) != B_OK) { return ENODEV; } ((struct root_device_softc *)dev->root->softc)->is_msix = true; info->u.h0.interrupt_line = startVector; return EOK; } int pci_get_max_read_req(device_t dev) { int cap; uint16_t val; if (pci_find_extcap(dev, PCIY_EXPRESS, &cap) != 0) return (0); val = pci_read_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, 2); val &= PCIM_EXP_CTL_MAX_READ_REQUEST; val >>= 12; return (1 << (val + 7)); } int pci_set_max_read_req(device_t dev, int size) { int cap; uint16_t val; if (pci_find_extcap(dev, PCIY_EXPRESS, &cap) != 0) return (0); if (size < 128) size = 128; if (size > 4096) size = 4096; size = (1 << (fls(size) - 1)); val = pci_read_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, 2); val &= ~PCIM_EXP_CTL_MAX_READ_REQUEST; val |= (fls(size) - 8) << 12; pci_write_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, val, 2); return (size); } int pci_get_powerstate(device_t dev) { int capabilityRegister; uint16 status; int powerState = PCI_POWERSTATE_D0; if (pci_find_extcap(dev, PCIY_PMG, &capabilityRegister) != EOK) return powerState; status = pci_read_config(dev, capabilityRegister + PCIR_POWER_STATUS, 2); switch (status & PCI_pm_mask) { case PCI_pm_state_d0: break; case PCI_pm_state_d1: powerState = PCI_POWERSTATE_D1; break; case PCI_pm_state_d2: powerState = PCI_POWERSTATE_D2; break; case PCI_pm_state_d3: powerState = PCI_POWERSTATE_D3; break; default: powerState = PCI_POWERSTATE_UNKNOWN; break; } TRACE_PCI(dev, "%s: D%i\n", __func__, powerState); return powerState; } int pci_set_powerstate(device_t dev, int newPowerState) { int capabilityRegister; int oldPowerState; uint8 currentPowerManagementStatus; uint8 newPowerManagementStatus; uint16 powerManagementCapabilities; bigtime_t stateTransitionDelayInUs = 0; if (pci_find_extcap(dev, PCIY_PMG, &capabilityRegister) != EOK) return EOPNOTSUPP; oldPowerState = pci_get_powerstate(dev); if (oldPowerState == newPowerState) return EOK; switch (max_c(oldPowerState, newPowerState)) { case PCI_POWERSTATE_D2: stateTransitionDelayInUs = 200; break; case PCI_POWERSTATE_D3: stateTransitionDelayInUs = 10000; break; } currentPowerManagementStatus = pci_read_config(dev, capabilityRegister + PCIR_POWER_STATUS, 2); newPowerManagementStatus = currentPowerManagementStatus & ~PCI_pm_mask; powerManagementCapabilities = pci_read_config(dev, capabilityRegister + PCIR_POWER_CAP, 2); switch (newPowerState) { case PCI_POWERSTATE_D0: newPowerManagementStatus |= PCIM_PSTAT_D0; break; case PCI_POWERSTATE_D1: if ((powerManagementCapabilities & PCI_pm_d1supp) == 0) return EOPNOTSUPP; newPowerManagementStatus |= PCIM_PSTAT_D1; break; case PCI_POWERSTATE_D2: if ((powerManagementCapabilities & PCI_pm_d2supp) == 0) return EOPNOTSUPP; newPowerManagementStatus |= PCIM_PSTAT_D2; break; case PCI_POWERSTATE_D3: newPowerManagementStatus |= PCIM_PSTAT_D3; break; default: return EINVAL; } TRACE_PCI(dev, "%s: D%i -> D%i\n", __func__, oldPowerState, newPowerState); pci_write_config(dev, capabilityRegister + PCIR_POWER_STATUS, newPowerManagementStatus, 2); if (stateTransitionDelayInUs != 0) snooze(stateTransitionDelayInUs); return EOK; }