<kernel v4.14>
PCI 서브시스템 초기화
pci_driver_init()
drivers/pci/pci-driver.c
static int __init pci_driver_init(void)
{
return bus_register(&pci_bus_type);
}
postcore_initcall(pci_driver_init);
아래 pci 버스 타입을 등록한다.
drivers/pci/pci-driver.c
struct bus_type pci_bus_type = {
.name = "pci",
.match = pci_bus_match,
.uevent = pci_uevent,
.probe = pci_device_probe,
.remove = pci_device_remove,
.shutdown = pci_device_shutdown,
.dev_groups = pci_dev_groups,
.bus_groups = pci_bus_groups,
.drv_groups = pci_drv_groups,
.pm = PCI_PM_OPS_PTR,
.num_vf = pci_bus_num_vf,
};
EXPORT_SYMBOL(pci_bus_type);
PCI 디바이스 & 드라이버 매치
pci_bus_match()
drivers/pci/pci-driver.c
/**
* pci_bus_match - Tell if a PCI device structure has a matching PCI device id structure
* @dev: the PCI device structure to match against
* @drv: the device driver to search for matching PCI device id structures
*
* Used by a driver to check whether a PCI device present in the
* system is in its list of supported devices. Returns the matching
* pci_device_id structure or %NULL if there is no match.
*/
static int pci_bus_match(struct device *dev, struct device_driver *drv)
{
struct pci_dev *pci_dev = to_pci_dev(dev);
struct pci_driver *pci_drv;
const struct pci_device_id *found_id;
if (!pci_dev->match_driver)
return 0;
pci_drv = to_pci_driver(drv);
found_id = pci_match_device(pci_drv, pci_dev);
if (found_id)
return 1;
return 0;
}
pci 버스에 디바이스나 드라이버가 추가될 때 서로 매치여부를 확인하기 위한 함수이다.
pci_match_device()
drivers/pci/pci-driver.c
/**
* pci_match_device - Tell if a PCI device structure has a matching PCI device id structure
* @drv: the PCI driver to match against
* @dev: the PCI device structure to match against
*
* Used by a driver to check whether a PCI device present in the
* system is in its list of supported devices. Returns the matching
* pci_device_id structure or %NULL if there is no match.
*/
static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
struct pci_dev *dev)
{
struct pci_dynid *dynid;
const struct pci_device_id *found_id = NULL;
/* When driver_override is set, only bind to the matching driver */
if (dev->driver_override && strcmp(dev->driver_override, drv->name))
return NULL;
/* Look at the dynamic ids first, before the static ones */
spin_lock(&drv->dynids.lock);
list_for_each_entry(dynid, &drv->dynids.list, node) {
if (pci_match_one_device(&dynid->id, dev)) {
found_id = &dynid->id;
break;
}
}
spin_unlock(&drv->dynids.lock);
if (!found_id)
found_id = pci_match_id(drv->id_table, dev);
/* driver_override will always match, send a dummy id */
if (!found_id && dev->driver_override)
found_id = &pci_device_id_any;
return found_id;
}
pci 버스에 등록된 pci 디바이스와 드라이버를 서로 매치시켜 매치된 pci_device_id 포인터를 반환한다. 만일 매치되지 않은 경우 null을 반환한다.
- 코드 라인 17~18에서 디바이스에 사용할 드라이버명을 지정한 경우이다. 디바이스에 오버드라이브된 드라이버명이 아닌경우 null을 반환한다.
- 코드 라인 21~28에서 dynids 리스트에 등록된 디바이스들부터 먼저 매치 확인을 한다.
- 코드 라인 30~31에서 매치되지 않은 경우 드라이버에 지정된 id_table에서 매치 확인을 한다.
- 코드 라인 34~35에서 여전히 매치되지 않은 경우이면서 드라이버 오버라이드 설정이 있는 경우 pci_device_id_any를 반환한다.
매치 우선 순위
- 다이나믹 pci 디바이스
- id_table을 사용하는 스태틱 pci 디바이스
- 오버드라이브 지정된 경우
pci 디바이스에 드라이버명으로 지정하여 오버드라이브 설정한 경우 해당 드라이버만 매치될 수 있다.
pci_match_one_device()
drivers/pci/pci.h
/**
* pci_match_one_device - Tell if a PCI device structure has a matching
* PCI device id structure
* @id: single PCI device id structure to match
* @dev: the PCI device structure to match against
*
* Returns the matching pci_device_id structure or %NULL if there is no match.
*/
static inline const struct pci_device_id *
pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev)
{
if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) &&
(id->device == PCI_ANY_ID || id->device == dev->device) &&
(id->subvendor == PCI_ANY_ID || id->subvendor == dev->subsystem_vendor) &&
(id->subdevice == PCI_ANY_ID || id->subdevice == dev->subsystem_device) &&
!((id->class ^ dev->class) & id->class_mask))
return id;
return NULL;
}
다음 다음 매치 조건에 대해 모두 만족한 경우 pci_device_id를 그대로 반환하고, 매치되지 않은 경우 null을 반환한다.
- 벤더명, 디바이스명, 서브벤더명 및 서브디바이스명을 지정하거나 PCI_ANY_ID를 기재한 경우 true이다.
- 클래스도 동일해야 한다.
pci_match_id()
drivers/pci/pci-driver.c
/**
* pci_match_id - See if a pci device matches a given pci_id table
* @ids: array of PCI device id structures to search in
* @dev: the PCI device structure to match against.
*
* Used by a driver to check whether a PCI device present in the
* system is in its list of supported devices. Returns the matching
* pci_device_id structure or %NULL if there is no match.
*
* Deprecated, don't use this as it will not catch any dynamic ids
* that a driver might want to check for.
*/
const struct pci_device_id *pci_match_id(const struct pci_device_id *ids,
struct pci_dev *dev)
{
if (ids) {
while (ids->vendor || ids->subvendor || ids->class_mask) {
if (pci_match_one_device(ids, dev))
return ids;
ids++;
}
}
return NULL;
}
EXPORT_SYMBOL(pci_match_id);
지정한 pci_device_id들 중 벤더, 서브벤더, 클래스 마스크 중 하나라도 지정된 경우 매치 여부를 확인한다. 매치가 확인되지 않는 경우 null을 반환한다.
PCI 드라이버 Probe
pci_device_probe()
drivers/pci/pci-driver.c
static int pci_device_probe(struct device *dev)
{
int error;
struct pci_dev *pci_dev = to_pci_dev(dev);
struct pci_driver *drv = to_pci_driver(dev->driver);
pci_assign_irq(pci_dev);
error = pcibios_alloc_irq(pci_dev);
if (error < 0)
return error;
pci_dev_get(pci_dev);
if (pci_device_can_probe(pci_dev)) {
error = __pci_device_probe(drv, pci_dev);
if (error) {
pcibios_free_irq(pci_dev);
pci_dev_put(pci_dev);
}
}
return error;
}
pci 디바이스의 legacy 인터럽트를 설정하고 probe를 진행한다.
- 코드 라인 7에서 pci 디바이스가 사용할 legacy 인터럽트를 pci configuration 정보를 읽어 배정한다.
- 코드 라인 9~11에서 pci 디바이스가 사용할 인터럽트를 ACPI를 사용하는 pc로부터 알아와 배정한다.
- 코드 라인 13~20에서 pci 디바이스의 참조 카운터를 1 증가시킨 후 probe를 진행한다.
pci_device_can_probe()
drivers/pci/pci-driver.c
#ifdef CONFIG_PCI_IOV
static inline bool pci_device_can_probe(struct pci_dev *pdev)
{
return (!pdev->is_virtfn || pdev->physfn->sriov->drivers_autoprobe);
}
#else
static inline bool pci_device_can_probe(struct pci_dev *pdev)
{
return true;
}
#endif
pci_device_can_probe() 함수는 PCI IO Virtualization을 사용하지 않는 경우 항상 1이다.
- PCI IO Virtualization을 사용하는 경우 drivers_autoprobe가 설정된 경우에만 1을 반환한다.
__pci_device_probe()
drivers/pci/pci-driver.c
/**
* __pci_device_probe - check if a driver wants to claim a specific PCI device
* @drv: driver to call to check if it wants the PCI device
* @pci_dev: PCI device being probed
*
* returns 0 on success, else error.
* side-effect: pci_dev->driver is set to drv when drv claims pci_dev.
*/
static int __pci_device_probe(struct pci_driver *drv, struct pci_dev *pci_dev)
{
const struct pci_device_id *id;
int error = 0;
if (!pci_dev->driver && drv->probe) {
error = -ENODEV;
id = pci_match_device(drv, pci_dev);
if (id)
error = pci_call_probe(drv, pci_dev, id);
}
return error;
}
pci 디바이스에 대해 드라이버가 아직 지정되지 않았고 드라이버의 probe 후크 함수가 있으면 매치 id를 알아온 후 probe를 진행한다.
- 드라이버가 이미 지정되어 있는 경우 매치나 probe 과정을 진행하지 않고 성공(0)을 반환한다.
pci_call_probe()
drivers/pci/pci-driver.c
static int pci_call_probe(struct pci_driver *drv, struct pci_dev *dev,
const struct pci_device_id *id)
{
int error, node, cpu;
struct drv_dev_and_id ddi = { drv, dev, id };
/*
* Execute driver initialization on node where the device is
* attached. This way the driver likely allocates its local memory
* on the right node.
*/
node = dev_to_node(&dev->dev);
dev->is_probed = 1;
cpu_hotplug_disable();
/*
* Prevent nesting work_on_cpu() for the case where a Virtual Function
* device is probed from work_on_cpu() of the Physical device.
*/
if (node < 0 || node >= MAX_NUMNODES || !node_online(node) ||
pci_physfn_is_probed(dev))
cpu = nr_cpu_ids;
else
cpu = cpumask_any_and(cpumask_of_node(node), cpu_online_mask);
if (cpu < nr_cpu_ids)
error = work_on_cpu(cpu, local_pci_probe, &ddi);
else
error = local_pci_probe(&ddi);
dev->is_probed = 0;
cpu_hotplug_enable();
return error;
}
pci 디바이스가 있는 노드의 cpu의 워크큐를 사용하여 pci 드라이버의 probe를 호출한다.
- 코드 라인 12에서 pci 디바이스에 해당하는 노드 번호를 알아온다.
- 코드 라인 13에서 pci 디바이스가 probe 진행 중임을 마크한다.
- 코드 라인 15에서 다른 cpu hotplug 작업이 수행 중인 경우 완료될 때까지 기다린 후 락을 획득한다.
- 코드 라인 21~25에서 노드에서 동작하는 online cpu 중 하나의 cpu 번호를 알아온다. 만일 pci io virtualization이 동작하는 경우 현재 cpu를 사용한다.
- 코드 라인 27~30에서 해당 cpu의 워크큐에서 local_pci_probe 함수를 호출하게 한다.
- 코드 라인 32에서 pci 디바이스의 probe 진행이 완료되었으므로 클리어한다.
- 코드 라인 33에서 cpu hotplug 락을 해제한다.
local_pci_probe()
drivers/pci/pci-driver.c
static long local_pci_probe(void *_ddi)
{
struct drv_dev_and_id *ddi = _ddi;
struct pci_dev *pci_dev = ddi->dev;
struct pci_driver *pci_drv = ddi->drv;
struct device *dev = &pci_dev->dev;
int rc;
/*
* Unbound PCI devices are always put in D0, regardless of
* runtime PM status. During probe, the device is set to
* active and the usage count is incremented. If the driver
* supports runtime PM, it should call pm_runtime_put_noidle(),
* or any other runtime PM helper function decrementing the usage
* count, in its probe routine and pm_runtime_get_noresume() in
* its remove routine.
*/
pm_runtime_get_sync(dev);
pci_dev->driver = pci_drv;
rc = pci_drv->probe(pci_dev, ddi->id);
if (!rc)
return rc;
if (rc < 0) {
pci_dev->driver = NULL;
pm_runtime_put_sync(dev);
return rc;
}
/*
* Probe function should return < 0 for failure, 0 for success
* Treat values > 0 as success, but warn.
*/
dev_warn(dev, "Driver probe function unexpectedly returned %d\n", rc);
return 0;
}
pci 디바이스를 절전 모드에서 복구시킨 후 pm 참조 카운터를 증가시킨다. 그런 후 해당 pci 드라이버의 probe 함수를 호출한다.
Legacy IRQ 할당
pci_assign_irq()
drivers/pci/setup-irq.c
void pci_assign_irq(struct pci_dev *dev)
{
u8 pin;
u8 slot = -1;
int irq = 0;
struct pci_host_bridge *hbrg = pci_find_host_bridge(dev->bus);
if (!(hbrg->map_irq)) {
dev_dbg(&dev->dev, "runtime IRQ mapping not provided by arch\n");
return;
}
/* If this device is not on the primary bus, we need to figure out
which interrupt pin it will come in on. We know which slot it
will come in on 'cos that slot is where the bridge is. Each
time the interrupt line passes through a PCI-PCI bridge we must
apply the swizzle function. */
pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);
/* Cope with illegal. */
if (pin > 4)
pin = 1;
if (pin) {
/* Follow the chain of bridges, swizzling as we go. */
if (hbrg->swizzle_irq)
slot = (*(hbrg->swizzle_irq))(dev, &pin);
/*
* If a swizzling function is not used map_irq must
* ignore slot
*/
irq = (*(hbrg->map_irq))(dev, slot, pin);
if (irq == -1)
irq = 0;
}
dev->irq = irq;
dev_dbg(&dev->dev, "assign IRQ: got %d\n", dev->irq);
/* Always tell the device, so the driver knows what is
the real IRQ to use; the device does not use it. */
pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq);
}
pci 디바이스가 사용할 legacy 인터럽트를 pci configuration 정보를 읽어 배정한다. 자세한 동작은 다음과 같다.
- 코드 라인 6에서 pci 버스에 대한 호스트 브리지를 구해온다.
- 코드 라인 19~22에서 pci 디바이스의 기본 configuration 헤더 정보에서 인터럽트 핀(1~4)을 읽어온다. 단 인터럽트를 사용하지 않는 pci 디바이스는 0을 읽어온다.
- 코드 라인 26~27에서 소스트 브리지에 구현된 (*swizzle_irq) 후크 함수를 통해 슬롯 번호를 알아온다.
- 코드 라인 33~35에서 읽어온 핀 번호에 대해 호스트 브리지에 구현된 (*map_irq) 후크 함수를 통해 슬롯과 핀 인자를 전달하고 ACPI 또는 디바이스 트리의 hwirq를 읽어온 후 이에 매핑된 virq를 알아온다.
- 코드 라인 37~39에서 dev->irq에 설정하고 로그 메시지를 출력한다.
- 코드 라인 43에서 pci 디바이스의 기본 configuratio 헤더 정보 중 인터럽트 라인에 인터럽트 번호를 기록한다.
pci 드라이버 등록
pci_register_driver()
include/linux/pci.h
/*
* pci_register_driver must be a macro so that KBUILD_MODNAME can be expanded
*/
#define pci_register_driver(driver) \
__pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)
pci 드라이버의 등록은 위의 매크로 함수를 통해 등록할 수 있다.
__pci_register_driver()
drivers/pci/pci-driver.c
/**
* __pci_register_driver - register a new pci driver
* @drv: the driver structure to register
* @owner: owner module of drv
* @mod_name: module name string
*
* Adds the driver structure to the list of registered drivers.
* Returns a negative value on error, otherwise 0.
* If no error occurred, the driver remains registered even if
* no device was claimed during registration.
*/
int __pci_register_driver(struct pci_driver *drv, struct module *owner,
const char *mod_name)
{
/* initialize common driver fields */
drv->driver.name = drv->name;
drv->driver.bus = &pci_bus_type;
drv->driver.owner = owner;
drv->driver.mod_name = mod_name;
drv->driver.groups = drv->groups;
spin_lock_init(&drv->dynids.lock);
INIT_LIST_HEAD(&drv->dynids.list);
/* register with core */
return driver_register(&drv->driver);
}
EXPORT_SYMBOL(__pci_register_driver);
pci 버스에 요청한 pci 드라이버를 등록한다.
- 다이나믹하게 pci 디바이스의 id를 관리하기 위해 dynids를 사용한다.
pci_driver 구조체
pci 드라이버를 등록할 때 사용하는 구조체이다.
include/linux/pci.h
struct pci_driver {
struct list_head node;
const char *name;
const struct pci_device_id *id_table; /* must be non-NULL for probe to be called */
int (*probe) (struct pci_dev *dev, const struct pci_device_id *id); /* New device inserted */
void (*remove) (struct pci_dev *dev); /* Device removed (NULL if not a hot-plug capable driver) */
int (*suspend) (struct pci_dev *dev, pm_message_t state); /* Device suspended */
int (*suspend_late) (struct pci_dev *dev, pm_message_t state);
int (*resume_early) (struct pci_dev *dev);
int (*resume) (struct pci_dev *dev); /* Device woken up */
void (*shutdown) (struct pci_dev *dev);
int (*sriov_configure) (struct pci_dev *dev, int num_vfs); /* PF pdev */
const struct pci_error_handlers *err_handler;
const struct attribute_group **groups;
struct device_driver driver;
struct pci_dynids dynids;
};
- *name
- *id_table
- 매치하여 사용할 pci 디바이스의 id 테이블을 지정한다.
- (*probe)
- 새로운 디바이스가 추가될 때 probe 후크 함수를 지정한다.
- (*remove)
- 디바이스가 제거될 때 호출할 후크 함수를 지정한다.
- (*suspend),
- (*suspend_late),
- (*resume_early),
- (*resume),
- (*shutdown)
- 절전 관련 후크 함수들이며 legacy pci 드라이버 호환을 위해 사용된다.
- 최근 인터페이스는 pci 드라이버에 임베드 되어 있는 driver->pm을 사용한다.
- (*sriov_configure)
- 동작 시킬 VF(가상 기능) 수를 지정할 수 있는 싱글 루트 I/O 가상화 설정용 후크 함수이다.
- *err_handler
- pci 버스 에러 이벤트를 처리할 콜백들이 담긴 pci_error_handlers 구조체를 지정한다.
- **groups
- driver
- dynids
PCI 장치 스캔
루트 버스 브리지 스캔
pci_scan_root_bus_bridge()
drivers/pci/probe.c
int pci_scan_root_bus_bridge(struct pci_host_bridge *bridge)
{
struct resource_entry *window;
bool found = false;
struct pci_bus *b;
int max, bus, ret;
if (!bridge)
return -EINVAL;
resource_list_for_each_entry(window, &bridge->windows)
if (window->res->flags & IORESOURCE_BUS) {
found = true;
break;
}
ret = pci_register_host_bridge(bridge);
if (ret < 0)
return ret;
b = bridge->bus;
bus = bridge->busnr;
if (!found) {
dev_info(&b->dev,
"No busn resource found for root bus, will use [bus %02x-ff]\n",
bus);
pci_bus_insert_busn_res(b, bus, 255);
}
max = pci_scan_child_bus(b);
if (!found)
pci_bus_update_busn_res_end(b, max);
return 0;
}
EXPORT_SYMBOL(pci_scan_root_bus_bridge);
루트 버스 브리지를 스캔한다.
- 코드 라인 8~9에서 브릿지가 null인 경우 -EINVAL 에러를 반환한다.
- 코드 라인 11~15에서 플랫폼 리소스 중 bus 리소스가 있는 경우에만 found 값을 true로 설정하고 루프를 벗어난다.
- 코드 라인 17~19에서 호스트 브릿지를 등록한다.
- 코드 라인 21~29에서 버스 리소스가 발견되지 않은 경우 인자로 요청한 버스의 버스 번호 부터 0xff 까지의 버스 번호를 사용하도록 버스 리소스를 생성하여 추가한다.
- 만일 처음 호스트 브릿지가 등록되는 경우라면 버스 번호 0번 부터 255번까지 리소스가 등록된다.
- 코드 라인 31에서 child 버스를 스캔한다.
- 코드 라인 33~34에서 처음 버스 리소스가 발견되지 않아 생성하여 추가한 경우 child 버스의 버스 번호까지 버스 리소스를 갱신한다.
차일드 버스 스캔
pci_scan_child_bus()
drivers/pci/probe.c
unsigned int pci_scan_child_bus(struct pci_bus *bus)
{
unsigned int devfn, pass, max = bus->busn_res.start;
struct pci_dev *dev;
dev_dbg(&bus->dev, "scanning bus\n");
/* Go find them, Rover! */
for (devfn = 0; devfn < 0x100; devfn += 8)
pci_scan_slot(bus, devfn);
/* Reserve buses for SR-IOV capability. */
max += pci_iov_bus_range(bus);
/*
* After performing arch-dependent fixup of the bus, look behind
* all PCI-to-PCI bridges on this bus.
*/
if (!bus->is_added) {
dev_dbg(&bus->dev, "fixups for bus\n");
pcibios_fixup_bus(bus);
bus->is_added = 1;
}
for (pass = 0; pass < 2; pass++)
list_for_each_entry(dev, &bus->devices, bus_list) {
if (pci_is_bridge(dev))
max = pci_scan_bridge(bus, dev, max, pass);
}
/*
* Make sure a hotplug bridge has at least the minimum requested
* number of buses.
*/
if (bus->self && bus->self->is_hotplug_bridge && pci_hotplug_bus_size) {
if (max - bus->busn_res.start < pci_hotplug_bus_size - 1)
max = bus->busn_res.start + pci_hotplug_bus_size - 1;
/* Do not allocate more buses than we have room left */
if (max > bus->busn_res.end)
max = bus->busn_res.end;
}
/*
* We've scanned the bus and so we know all about what's on
* the other side of any bridges that may be on this bus plus
* any devices.
*
* Return how far we've got finding sub-buses.
*/
dev_dbg(&bus->dev, "bus scan returning with max=%02x\n", max);
return max;
}
EXPORT_SYMBOL_GPL(pci_scan_child_bus);
child pci 버스를 스캔한다.
- 코드 라인 9~10에서 전체 슬롯을 스캔한다.
- 코드 라인 13에서 virtual function이 사용하는 버스 번호를 reserve하기 위해 max 값을 그 만큼 건너뛴다.
- 코드 라인 19~23에서 버스가 처음 추가된 경우 pcibios fixup을 수행한다.
- 아키텍처마다 코드가 준비되어 있다.
- arm의 경우 arm/kernel/bios32.c – pcibios_fixup_bus() 함수를 사용한다.
- arm64의 경우 fixup 코드를 수행하지 않는다.
- 코드 라인 25~29에서 현재 버스에 등록된 모든 pci 브리지 디바이스를 대상으로 2번씩 스캔한다.
- 코드 라인 35~42에서 hot plug 버스를 위해 버스 번호를 남겨둔다.
PCI 슬롯 스캔
pci_scan_slot()
drivers/pci/probe.c
/**
* pci_scan_slot - scan a PCI slot on a bus for devices.
* @bus: PCI bus to scan
* @devfn: slot number to scan (must have zero function.)
*
* Scan a PCI slot on the specified PCI bus for devices, adding
* discovered devices to the @bus->devices list. New devices
* will not have is_added set.
*
* Returns the number of new devices found.
*/
int pci_scan_slot(struct pci_bus *bus, int devfn)
{
unsigned fn, nr = 0;
struct pci_dev *dev;
if (only_one_child(bus) && (devfn > 0))
return 0; /* Already scanned the entire slot */
dev = pci_scan_single_device(bus, devfn);
if (!dev)
return 0;
if (!dev->is_added)
nr++;
for (fn = next_fn(bus, dev, 0); fn > 0; fn = next_fn(bus, dev, fn)) {
dev = pci_scan_single_device(bus, devfn + fn);
if (dev) {
if (!dev->is_added)
nr++;
dev->multifunction = 1;
}
}
/* only one slot has pcie device */
if (bus->self && nr)
pcie_aspm_init_link_state(bus->self);
return nr;
}
EXPORT_SYMBOL(pci_scan_slot);
pci 슬롯을 스캔한다.
- 코드 라인 17~18에서 pcie 포트에 이미 디바이스가 연결된 경우 또는 스캔하지 못하도록 한 경우 0을 반환한다.
- 코드 라인 20~22에서 하나의 디바이스를 스캔한다. 스캔된 디바이스가 없으면 0을 반환한다.
- 코드 라인 23~24에서 이미 추가된 디바이스의 경우가 아니면 nr을 증가시킨다.
- 코드 라인 26~33에서 다음 펑션에 대한 하나의 디바이스를 스캔한다. 디바이스가 추가된 경우 multifunction 설정을 한다.
- 코드 라인 36~37에서 PCIe 루트 포트 디바이스에 디바이스가 추가된 경우 link state를 초기화한다.
싱글 디바이스 스캔
pci_scan_single_device()
drivers/pci/probe.c
struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn)
{
struct pci_dev *dev;
dev = pci_get_slot(bus, devfn);
if (dev) {
pci_dev_put(dev);
return dev;
}
dev = pci_scan_device(bus, devfn);
if (!dev)
return NULL;
pci_device_add(dev, bus);
return dev;
}
EXPORT_SYMBOL(pci_scan_single_device);
하나의 디바이스를 스캔하여 pci 디바이스로 추가한다.
- 코드 라인 5~9에서 버스에 등록된 디바이스들 중 devfn에 해당하는 디바이스를 가져온다.
- 코드 라인 11~13에서 해당 devfn 디바이스를 스캔한다.
- 코드 라인 15에서 스캔 성공한 디바이스를 pci 디바이스로 추가한다.
PCI 디바이스 스캔
pci_scan_device()
drivers/pci/probe.c
/*
* Read the config data for a PCI device, sanity-check it
* and fill in the dev structure...
*/
static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn)
{
struct pci_dev *dev;
u32 l;
if (!pci_bus_read_dev_vendor_id(bus, devfn, &l, 60*1000))
return NULL;
dev = pci_alloc_dev(bus);
if (!dev)
return NULL;
dev->devfn = devfn;
dev->vendor = l & 0xffff;
dev->device = (l >> 16) & 0xffff;
pci_set_of_node(dev);
if (pci_setup_device(dev)) {
pci_bus_put(dev->bus);
kfree(dev);
return NULL;
}
return dev;
}
하나의 devfn에 해당하는 pci 디바이스를 스캔한 후 셋업한다. 실패한 경우 null을 반환한다.
- 코드 라인 10~11에서 최대 60초 이내에 요청한 버스 디바이스 펑션 번호의 pci 디바이스의 디바이스 id와 벤더 id를 읽어온다.
- 코드 라인 13~15에서 pci 디바이스를 할당한다. 그리고 타입에 pci_dev_type을 대입하고 버스 참조 카운터를 1 증가시킨다.
- 코드 라인 17~19에서 pci 디바이스에 devfn을 지정하고 알아온 16비트 벤더와 디바이스 id를 대입한다.
- 코드 라인 21에서 디바이스가 소속된 호스트 컨트롤러의 디바이스 트리의 자식 노드에서 해당 디바이스에 대한 노드를 검색 후 of_node에 대입한다.
- “reg” 속성 값의 bits[15:8] 값이 디바이스(슬롯) 및 펑션 번호이다.
- 코드 라인 23~27에서 configuration space 헤더 정보를 읽어 pci 디바이스를 셋업한다.
pci_bus_read_dev_vendor_id()
drivers/pci/probe.c
bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *l,
int timeout)
{
if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, l))
return false;
/* some broken boards return 0 or ~0 if a slot is empty: */
if (*l == 0xffffffff || *l == 0x00000000 ||
*l == 0x0000ffff || *l == 0xffff0000)
return false;
if (pci_bus_crs_vendor_id(*l))
return pci_bus_wait_crs(bus, devfn, l, timeout);
return true;
}
EXPORT_SYMBOL(pci_bus_read_dev_vendor_id);
요청한 버스 디바이스 펑션의 configuration space에서 디바이스 id와 벤더 id를 읽어 인자 l에 출력하고 true를 반환한다.
- 코드 라인 4~5에서 요청한 버스 디바이스 펑션의 configuration space에서 벤더 id를 읽어 인자 l에 알아온다. 만일 읽어올 수 없으면 false를 반환한다.
- 코드 라인 8~10에서 슬롯이 비어있는 경우 false를 반환한다.
- 코드 라인 12~13에서 벤더 id를 읽을 때 지연될 수 있다. 그러한 경우 timeout 범위 내에서 1초 부터 2배 단위로 지연 후 다시 읽기를 시도한다.
PCI 디바이스 셋업
pci_setup_device()
drivers/pci/probe.c -1/2-
/**
* pci_setup_device - fill in class and map information of a device
* @dev: the device structure to fill
*
* Initialize the device structure with information about the device's
* vendor,class,memory and IO-space addresses,IRQ lines etc.
* Called at initialisation of the PCI subsystem and by CardBus services.
* Returns 0 on success and negative if unknown type of device (not normal,
* bridge or CardBus).
*/
int pci_setup_device(struct pci_dev *dev)
{
u32 class;
u16 cmd;
u8 hdr_type;
int pos = 0;
struct pci_bus_region region;
struct resource *res;
if (pci_read_config_byte(dev, PCI_HEADER_TYPE, &hdr_type))
return -EIO;
dev->sysdata = dev->bus->sysdata;
dev->dev.parent = dev->bus->bridge;
dev->dev.bus = &pci_bus_type;
dev->hdr_type = hdr_type & 0x7f;
dev->multifunction = !!(hdr_type & 0x80);
dev->error_state = pci_channel_io_normal;
set_pcie_port_type(dev);
pci_dev_assign_slot(dev);
/* Assume 32-bit PCI; let 64-bit PCI cards (which are far rarer)
set this higher, assuming the system even supports it. */
dev->dma_mask = 0xffffffff;
dev_set_name(&dev->dev, "%04x:%02x:%02x.%d", pci_domain_nr(dev->bus),
dev->bus->number, PCI_SLOT(dev->devfn),
PCI_FUNC(dev->devfn));
pci_read_config_dword(dev, PCI_CLASS_REVISION, &class);
dev->revision = class & 0xff;
dev->class = class >> 8; /* upper 3 bytes */
dev_printk(KERN_DEBUG, &dev->dev, "[%04x:%04x] type %02x class %#08x\n",
dev->vendor, dev->device, dev->hdr_type, dev->class);
/* need to have dev->class ready */
dev->cfg_size = pci_cfg_space_size(dev);
/* need to have dev->cfg_size ready */
set_pcie_thunderbolt(dev);
/* "Unknown power state" */
dev->current_state = PCI_UNKNOWN;
/* Early fixups, before probing the BARs */
pci_fixup_device(pci_fixup_early, dev);
/* device class may be changed after fixup */
class = dev->class >> 8;
if (dev->non_compliant_bars) {
pci_read_config_word(dev, PCI_COMMAND, &cmd);
if (cmd & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) {
dev_info(&dev->dev, "device has non-compliant BARs; disabling IO/MEM decoding\n");
cmd &= ~PCI_COMMAND_IO;
cmd &= ~PCI_COMMAND_MEMORY;
pci_write_config_word(dev, PCI_COMMAND, cmd);
}
}
dev->broken_intx_masking = pci_intx_mask_broken(dev);
configuration space header 정보를 읽어 pci_dev 정보를 구성한다.
- 코드 라인 20~21에서 디바이스의 pci configuration space에서 헤더 타입을 읽어온다.
- 코드 라인 23~28에서 pci 디바이스의 정보를 채운다.
- 헤더 타입의 bit15가 1인 경우 mmultifunction 디바이스이다.
- 코드 라인 29에서 pcie 디바이스 정보를 설정한다.
- configuration space에서 pcie capability를 읽어 pcie_cap, pcie_flags_reg, pcie_mpss, has_secondary_link 정보를 알아온다.
- 코드 라인 31에서 버스내 슬롯 번호를 지정한다.
- 코드 라인 34에서 32bit PCI의 dma 영역을 모두 허용한다.
- 코드 라인 36~38에서 디바이스 명을 지정한다.
- “<4자리 도메인번호>:<2자리 버스 번호>:<2자리 슬롯번호>.<1자리 펑션 번호>”
- 코드 라인 40~45에서 configuration space에서 class_revision에 해당하는 더블 워드(4 byte) 값을 읽어 하위 8비트를 디바이스의 revision 값에 대입하고, 나머지 값은 class에 대입한다. 그런 후 이 정보를 디버그 출력한다.
- 코드 라인 48에서 configaration space의 사이즈를 알아온다.
- pci: 기본 사이즈 256
- pcie: 기본 사이즈 256, 확장 정보인 경우 4096
- pcix: 기본 사이즈 256, 266M 또는 533Mhz인 경우 확장 정보를 지원하여 4096
- 코드 라인 51에서 PCI_EXT_CAP_ID_VNDR cap에서 벤더가 인텔이고, 벤더 헤더 id가 썬더볼트인 경우 is_thunderbolt를 1로 설정한다.
- 코드 라인 55에서처음 pm 상태를 unknown으로 설정한다.
- 코드 라인 58~60에서 부팅 시에 동작시킬 pci fixup 후크가 있는 경우 수행한다.
- DECLARE_PCI_FIXUP_EARLY() 매크로 함수로 지정한다.
- pci_fixup 구조체를 생성한 후 .pci_fixup_early 섹션에 저장한다.
- 특정 아키텍처 및 드라이버 루틴에 존재한다.
- arm64 아키텍처에는 존재하지 않고 arm 아키텍처에는 PCI_VENDOR_ID_PLX 벤더에서 3개의 디바이스에 대해 fixup_early 루틴이 있다.
- drivers/pci/quirks.c에 특정 벤더와 디바이스에 대한 fixup_early 루틴이 존재 한다.
- 예) PCI_VENDOR_ID_BROADCOM, 0x16cd 와 0x16f0 디바이스인 경우 quirk_paxc_bridge() 후크 함수를 호출한다.
- 코드 라인 62~70에서 host/pcie-tango.c 드라이버에서 fake BAR를 사용하는 경우만 적용되는 코드이다. 생략.
- 코드 라인 72에서 pci command 레지스터에 INTX_DISABLE(bit10) 비트 기록 테스트를 하여 변경 불가능 여부를 알아온다.
drivers/pci/probe.c -2/2-
switch (dev->hdr_type) { /* header type */
case PCI_HEADER_TYPE_NORMAL: /* standard header */
if (class == PCI_CLASS_BRIDGE_PCI)
goto bad;
pci_read_irq(dev);
pci_read_bases(dev, 6, PCI_ROM_ADDRESS);
pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);
pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device);
/*
* Do the ugly legacy mode stuff here rather than broken chip
* quirk code. Legacy mode ATA controllers have fixed
* addresses. These are not always echoed in BAR0-3, and
* BAR0-3 in a few cases contain junk!
*/
if (class == PCI_CLASS_STORAGE_IDE) {
u8 progif;
pci_read_config_byte(dev, PCI_CLASS_PROG, &progif);
if ((progif & 1) == 0) {
region.start = 0x1F0;
region.end = 0x1F7;
res = &dev->resource[0];
res->flags = LEGACY_IO_RESOURCE;
pcibios_bus_to_resource(dev->bus, res, ®ion);
dev_info(&dev->dev, "legacy IDE quirk: reg 0x10: %pR\n",
res);
region.start = 0x3F6;
region.end = 0x3F6;
res = &dev->resource[1];
res->flags = LEGACY_IO_RESOURCE;
pcibios_bus_to_resource(dev->bus, res, ®ion);
dev_info(&dev->dev, "legacy IDE quirk: reg 0x14: %pR\n",
res);
}
if ((progif & 4) == 0) {
region.start = 0x170;
region.end = 0x177;
res = &dev->resource[2];
res->flags = LEGACY_IO_RESOURCE;
pcibios_bus_to_resource(dev->bus, res, ®ion);
dev_info(&dev->dev, "legacy IDE quirk: reg 0x18: %pR\n",
res);
region.start = 0x376;
region.end = 0x376;
res = &dev->resource[3];
res->flags = LEGACY_IO_RESOURCE;
pcibios_bus_to_resource(dev->bus, res, ®ion);
dev_info(&dev->dev, "legacy IDE quirk: reg 0x1c: %pR\n",
res);
}
}
break;
case PCI_HEADER_TYPE_BRIDGE: /* bridge header */
if (class != PCI_CLASS_BRIDGE_PCI)
goto bad;
/* The PCI-to-PCI bridge spec requires that subtractive
decoding (i.e. transparent) bridge must have programming
interface code of 0x01. */
pci_read_irq(dev);
dev->transparent = ((dev->class & 0xff) == 1);
pci_read_bases(dev, 2, PCI_ROM_ADDRESS1);
set_pcie_hotplug_bridge(dev);
pos = pci_find_capability(dev, PCI_CAP_ID_SSVID);
if (pos) {
pci_read_config_word(dev, pos + PCI_SSVID_VENDOR_ID, &dev->subsystem_vendor);
pci_read_config_word(dev, pos + PCI_SSVID_DEVICE_ID, &dev->subsystem_device);
}
break;
case PCI_HEADER_TYPE_CARDBUS: /* CardBus bridge header */
if (class != PCI_CLASS_BRIDGE_CARDBUS)
goto bad;
pci_read_irq(dev);
pci_read_bases(dev, 1, 0);
pci_read_config_word(dev, PCI_CB_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);
pci_read_config_word(dev, PCI_CB_SUBSYSTEM_ID, &dev->subsystem_device);
break;
default: /* unknown header */
dev_err(&dev->dev, "unknown header type %02x, ignoring device\n",
dev->hdr_type);
return -EIO;
bad:
dev_err(&dev->dev, "ignoring class %#08x (doesn't match header type %02x)\n",
dev->class, dev->hdr_type);
dev->class = PCI_CLASS_NOT_DEFINED << 8;
}
/* We found a fine healthy device, go go go... */
return 0;
}
- 코드 라인 1~8에서 pci 디바이스의 헤더 타입이 normal 디바이스인 경우이다.
- irq 라인과(0~5) irq 번호를 읽고,
- BAR0~5 주소와 BAR6-ROM 주소를 읽어온다.
- 서브시스템 벤더 id와 디바이스 id를 읽어온다.
- 코드 라인 16~52에서 ide 타입 스토리지인 경우에 대한 코드는 생략
- 코드 라인 54~69에서 pci 디바이스의 헤더 타입이 브리지인 경우이다.
- irq 라인과(0~5) irq 번호를 읽고,
- BAR0~1 주소와 BAR6-ROM 주소를 읽어온다. 핫 플러그 지원 여부를 읽어 is_hotplug_bridge에 대입한다.
- 서브시스템 벤터 cap이 존재하는 경우 이데 대한 서브시스템 벤더 id와 디바이스 id를 읽어온다.
- 코드 라인 71~78에서 pci 디바이스의 헤더 타입이 카드버스인 경우이다.
- irq 라인과(0~5) irq 번호를 읽고,
- BAR0 주소와 BAR6-ROM 주소를 읽어온다. 핫 플러그 지원 여부를 읽어 is_hotplug_bridge에 대입한다.
- 서브시스템 벤터 cap이 존재하는 경우 이데 대한 서브시스템 벤더 id와 디바이스 id를 읽어온다.
BAR(Base Address Register) Setup
PCI 호스트 컨트롤러가 버스를 스캔하여 PCI 디바이스를 검출한 경우 PCI 디바이스를 셋업하는데 이 과정에서 PCI 디바이스의 BAR 레지스터들을 읽어 PCI 디바이스가 가지고 있는 IO 또는 메모리의 사이즈와 타입을 읽은 후 호스트 CPU의 물리 메모리 주소로 기록한다.
Mem32 Type BAR
PCI 디바이스의 BAR 값은 공장 출고 시 BAR 레지스터의 일부를 사이즈와 타입 정보를 기록하고 Read Only로 제공한다.
- Size의 표기는 BAR 레지스터의 모든 비트를 1로 기록한 후 타입 4비트를 제외하고 1로 설정된 lowest 비트의 포지션 값으로 사이즈를 알아낸다.
- 아래 bit26에서 1이 발견되었으므로 사이즈는 0x400_0000 (64MB) 임을 알아낼 수 있다.
- 마지막으로 디바이스 트리의 ranges 에서 지정하는 메모리 타입의 호스트 CPU 물리 주소부터 배치된 리소스의 주소를 기록하는 것으로 완료한다.
- 예) ranges = <0x82000000 0 0 0 0x20000000 0 0x400_0000>;
- 타입: Pre-fetchable Mem32
- pci 주소: 0x0
- 호스트 cpu 물리 주소: 0x20000000
- 사이즈: 0x4000000 (64MB)
Mem64 Type BAR
다음 그림은 2개의 BAR를 사용하여 64비트 메모리 주소를 알아오는 모습을 보여준다.
- Size의 표기는 BAR1 및 BAR2 레지스터의 모든 비트를 1로 기록한 후 타입 4비트를 제외하고 1로 설정된 lowest 비트의 포지션 값으로 사이즈를 알아낸다.
- 아래 bit30에서 1이 발견되었으므로 사이즈는 0x4000_0000 (1GB) 임을 알아낼 수 있다.
- 마지막으로 디바이스 트리의 ranges 에서 지정하는 메모리 타입의 호스트 CPU 물리 주소부터 배치된 리소스의 주소를 기록하는 것으로 완료한다.
- 예) ranges = <0x83000000 0 0 4 0x80000000 0 0x4000_0000>;
- 타입: Pre-fetchable Mem64
- pci 주소: 0x0
- 호스트 cpu 물리 주소: 0x4_80000000
- 사이즈: 0x40000000 (1GB)
I/O Type BAR
다음 그림은 IO 주소를 표현하는 BAR3를 보여준다.
- Size의 표기는 BAR3 레지스터의 모든 비트를 1로 기록한 후 타입 2비트를 제외하고 1로 설정된 lowest 비트의 포지션 값으로 사이즈를 알아낸다.
- 아래 bit12에서 1이 발견되었으므로 사이즈는 0x1000 (4KB) 임을 알아낼 수 있다.
- 마지막으로 디바이스 트리의 ranges 에서 지정하는 IO 타입의 호스트 CPU 물리 주소부터 배치된 리소스의 주소를 기록하는 것으로 완료한다.
- 예) ranges = <0x81000000 0 0 0 0x24000000 0 0x1000>;
- 타입: I/O
- pci 주소: 0x0
- 호스트 cpu 물리 주소: 0x24000000
- 사이즈: 0x1000 (4KB)
PCI 디바이스 설정 및 추가
pci_device_add()
drivers/pci/probe.c
void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
{
int ret;
pci_configure_device(dev);
device_initialize(&dev->dev);
dev->dev.release = pci_release_dev;
set_dev_node(&dev->dev, pcibus_to_node(bus));
dev->dev.dma_mask = &dev->dma_mask;
dev->dev.dma_parms = &dev->dma_parms;
dev->dev.coherent_dma_mask = 0xffffffffull;
pci_set_dma_max_seg_size(dev, 65536);
pci_set_dma_seg_boundary(dev, 0xffffffff);
/* Fix up broken headers */
pci_fixup_device(pci_fixup_header, dev);
/* moved out from quirk header fixup code */
pci_reassigndev_resource_alignment(dev);
/* Clear the state_saved flag. */
dev->state_saved = false;
/* Initialize various capabilities */
pci_init_capabilities(dev);
/*
* Add the device to our list of discovered devices
* and the bus list for fixup functions, etc.
*/
down_write(&pci_bus_sem);
list_add_tail(&dev->bus_list, &bus->devices);
up_write(&pci_bus_sem);
ret = pcibios_add_device(dev);
WARN_ON(ret < 0);
/* Setup MSI irq domain */
pci_set_msi_domain(dev);
/* Notifier could use PCI capabilities */
dev->match_driver = false;
ret = device_add(&dev->dev);
WARN_ON(ret < 0);
}
- 코드 라인 5에서 pci 디바이스의 mps, extended tag 여부, relaxed ordering, hot plug parameter 정보 등을 설정한다.
- 코드 라인 6에서 pci 디바이스의 (*release) 후크에 pci_release_dev() 함수를 대입한다.
- 코드 라인 8~14에서 버스가 사용하는 노드 번호로 디바이스 노드를 설정하고, 디바이스의 dma 관련 설정등을 최대치로 설정해둔다.
- 코드 라인 17에서 특정 장치에 대해 pci_fixup_header 후쿠가 있는 경우 이를 수행한다.
- DECLARE_PCI_FIXUP_HEADER() 매크로 함수로 지정한다.
- pci_fixup 구조체를 생성한 후 .pci_fixup_header 섹션에 저장한다.
- 코드 라인 20에서 resource_alignment 버스 속성을 지정한 경우 리소스 alignment를 수행한다. (powerpc만 지원한다)
- resource_alignment=
- [<order of align>@][<domain>:]<bus>:<slot>.<func>
- [:noresize][; …]
- 코드 라인 23에서 pm state save 여부를 false로 한다.
- 코드 라인 26에서 각종 cap을 초기화한다.
- 코드 라인 32~34에서 버스에 디바이스를 추가한다.
- 코드 라인 36에서 pcibios 설정에 맞춰 디바이스의 매핑을 수행한다.
- x86, s390, powerpc, sparc 등 바이오스가 있는 경우에 한해 설정한다.
- arm, arm64는 해당 사항 없다.
- 코드 라인 40에서 msi용 irq 도메인을 지정한다.
- 코드 라인 43~44에서 디바이스를 추가하되 match 동작을 수행하지 않게한다.
pci_configure_device()
drivers/pci/probe.c
static void pci_configure_device(struct pci_dev *dev)
{
struct hotplug_params hpp;
int ret;
pci_configure_mps(dev);
pci_configure_extended_tags(dev, NULL);
pci_configure_relaxed_ordering(dev);
memset(&hpp, 0, sizeof(hpp));
ret = pci_get_hp_params(dev, &hpp);
if (ret)
return;
program_hpp_type2(dev, hpp.t2);
program_hpp_type1(dev, hpp.t1);
program_hpp_type0(dev, hpp.t0);
}
pci 디바이스의 mps, extended tag 여부, relaxed ordering, hot plug parameter 정보 등을 설정한다.
- 코드 라인 6에서 pci upstream 브리지의 mps(Maximum Payload Size) 값으로 pci 디바이스의 mps를 설정한다.
- pci 디바이스와 pci upstream 브리지의 mps가 같은 경우 변경이 필요 없다.
- 128, 256, 512, 1024, 2048, 4096 값 중 하나를 사용할 수 있다.
- pci 버스가 아직 설정되지 않은 경우 나중에 처리할 수도 있다.
- 코드 라인 7에서 pci 호스트의 extended tag가 설정된 경우 pci 디바이스도 설정한다.
- pcie EXP_DEVCAP에서 extended tag 비트가 설정된 경우 호스트의 extended tag 처리가 가능 여부에 따라 디바이스의 설정을 변경한다.
- “disabling Extended Tags” 또는 “enabling Extended Tags” 메시지가 출력된다.
- 코드 라인 8에서 pci 루트 포트가 relaxed ordering을 허용하지 않으면 pci 디바이스의 설정도 클리어한다.
- PCI_EXP_DEVCTL 레지스터의 PCI_EXP_DEVCTL_RELAX_EN 비트가 설정되었고, 루트 포트에 PCI_DEV_FLAGS_NO_RELAXED_ORDERING 플래그의 설정이 있으면 pci 디바이스의 PCI_EXP_DEVCTL 레지스터의 PCI_EXP_DEVCTL_RELAX_EN을 클리어한다.
- “”Disable Relaxed Ordering because the Root Port didn’t support it” 메시지가 출력된다.
- 코드 라인 10~13에서 acpi를 사용할 때 pci hot plug 파라메터를 가져온 후 pci 디바이스에 설정한다.
참고