System Programming/Device Driver

Linux Device Driver 기초 #8 PCI 드라이버

uzguns 2024. 11. 15. 11:15

PCI(Peripheral Component Interconnect) 드라이버

버스는 전기적 인터페이스와 프로그래밍 인터페이스로 구성된다.

주로 현대 데스크탑 및 대형 컴퓨터에서는 PCI 버스 가 많이 사용된다.

 

PCI 장치가 시스템의 하드웨어를 찾고 접근하는 방법

특정 드라이버가 하드웨어를 탐지하고 접근할 수 있도록 지원

 

  • 32비트 데이터 버스를 기본으로 사용하며, 64비트 확장도 포함되어 있음
  • PCI 버스는 ISA보다 높은 클럭 속도로 더 나은 성능을 달성
  • 플랫폼 독립성을 고려한 설계로, 다양한 프로세서 아키텍처(IA-32, Alpha, PowerPC, SPARC64, IA-64 등)에서 사용됨
  • 특히 중요한 점은 PCI가 인터페이스 보드의 자동 감지 및 구성(auto-detection)을 지원한다는 점
  • 이는 점퍼가 필요 없고 부팅 시 자동으로 설정되며, 드라이버는 탐색 없이 구성 정보를 얻어 초기화를 진행할 수 있음

PCI 주소 지정

  • 각 PCI 주변 장치는 버스 번호, 장치 번호, 함수 번호로 식별됨
  • 하나의 시스템은 최대 256개의 버스를 가질 수 있지만, 이 한계를 넘어서는 경우 Linux에서는 PCI 도메인 기능을 지원
  • 각 PCI 도메인은 최대 256개의 버스를, 각 버스는 최대 32개의 장치를, 각 장치는 최대 8개의 기능을 포함할 수 있음

PCI 시스템 레이아웃

다음은 일반적인 PCI 시스템의 구조이다. RAM, CPU가 있고, 여러 브리지(Host Bridge, ISA Bridge, CardBus Bridge, PCI Bridge)가 연결되어 있다.

  • Host Bridge는 CPU와 PCI 버스를 연결
  • PCI Bridge는 여러 개의 PCI 버스를 서로 연결
  • ISA Bridge는 ISA 장치와 연결
  • CardBus Bridge는 노트북에서 사용되는 확장 슬롯(CardBus)와 연결

PCI 주소 포맷

  • PCI 장치는 보통 버스 번호, 장치 번호, 기능 번호를 기준으로 식별됨
  • 이 주소 값들은 일반적으로 16진수로 표시됨
  • 예를 들어, /proc/bus/pci/devices 파일은 단일 16비트 필드로 주소를 표시하여 구문 분석과 정렬을 쉽게함
  • 반면, /proc/bus/busnumber 파일은 주소를 세 가지 필드(버스, 장치, 기능)로 나눔
  • lspci 명령을 사용하면 PCI 장치의 주소와 장치 종류를 확인할 수 있음
    • lspci 명령에서 나오는 정보는 /proc 파일 시스템에서 가져옴
    • 예를 들어, VGA 비디오 컨트롤러 주소 0x00a0는 0000:00:14.0로 해석
    • 여기서 도메인(16비트), 버스(8비트), 장치(5비트), 기능(3비트)로 구성
$ lspci
00:00.0 Host bridge: Intel Corporation Device 191f (rev 07)
00:02.0 VGA compatible controller: Intel Corporation Device 1912 (rev 06)
00:14.0 USB controller: Intel Corporation Device a12f (rev 31)
00:14.2 Signal processing controller: Intel Corporation Device a131 (rev 31)
00:16.0 Communication controller: Intel Corporation Device a13a (rev 31)
00:17.0 SATA controller: Intel Corporation Device a102 (rev 31)
00:1c.0 PCI bridge: Intel Corporation Device a114 (rev f1)
00:1c.5 PCI bridge: Intel Corporation Device a115 (rev f1)
00:1d.0 PCI bridge: Intel Corporation Device a118 (rev f1)
00:1f.0 ISA bridge: Intel Corporation Device a143 (rev 31)
00:1f.2 Memory controller: Intel Corporation Device a121 (rev 31)
00:1f.3 Audio device: Intel Corporation Device a170 (rev 31)
00:1f.4 SMBus: Intel Corporation Device a123 (rev 31)
01:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller (rev 0c)
02:00.0 PCI bridge: Integrated Technology Express, Inc. Device 8892 (rev 41)
03:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8169 PCI Gigabit Ethernet Controller (rev 10)

 

PCI 주소 공간

  • PCI 장치는 세 가지 주소 공간을 사용
    • 메모리 위치
    • I/O 포트
    • 구성 레지스터(Configuration Registers)
  • 메모리와 I/O 주소는 동일한 PCI 버스에 있는 모든 장치가 동시에 접근할 수 있음
  • 구성 공간은 지리적 주소 지정 방식을 사용하여 한 번에 하나의 장치만 접근하도록 함

메모리 및 I/O 공간 처리

  • 드라이버 관점에서 메모리 및 I/O 영역은 일반적으로 readb, readw, readl 등으로 접근
  • 구성 트랜잭션은 특정 장치의 구성 레지스터에 접근하는 방식으로 수행
  • PCI 사양은 인터럽트 라인을 공유할 수 있도록 하여, 제한된 IRQ 라인을 가진 시스템에서도 여러 PCI 장치를 연결할 수 있게함
  • 예를 들어, x86 시스템은 제한된 IRQ 라인을 가지지만, 각 인터럽트에 네 개의 핀을 연결할 수 있어 여러 PCI 인터페이스 보드를 처리할 수 있음

I/O 공간과 메모리 공간

  • PCI 버스의 I/O 공간은 32비트 주소 버스를 사용하여 4GB의 I/O 포트를 지원
  • 메모리 공간은 32비트 또는 64비트 주소를 사용할 수 있으며, 최신 플랫폼에서는 64비트를 지원
  • 각 장치의 메모리와 I/O 주소가 재배치될 수 있으며, 이 과정은 충돌을 방지하기 위해 시스템 부팅 시 펌웨어가 처리
  • 구성 공간에서 주소 정보를 읽은 후, Linux 드라이버는 장치에 안전하게 접근할 수 있음

 

구성 공간(Configuration Space)

**PCI 구성 공간 레지스터 맵:**

      |--MSB                                     LSB--|
      |     +03H     |      +02H     |       +01H     |      +00H     |
  00H |       디바이스 ID            |         벤더 ID                |
  04H |       스테이터스             |         커맨드                 |
  08H |                    클래스 코드                |  revision ID  |
  0CH |  자기진단(BIST)  |    헤더타입  | 레이턴시타이머 |  캐시라인크기 |
  10H |                기준주소 레지스터 0                             |
  14H |                기준주소 레지스터 1                             |
  18H |                기준주소 레지스터 2                             |
  1CH |                기준주소 레지스터 3                             |
  20H |                기준주소 레지스터 4                             |
  24H |                기준주소 레지스터 5                             |
  28H |                       예약                                    |
  2CH |                       예약                                    |
  30H |                기준주소 레지스터 5                             |
  34H |                       예약                                    |
  38H |                       예약                                    |
  3CH |  최대 레이턴시  | 최소 그란트 |   인터럽트 핀   | 인터럽트 라인 |
  • 각 장치 함수는 256바이트의 구성 공간을 가지며, PCI Express 장치는 4KB의 구성 공간을 가짐
  • 구성 레지스터의 레이아웃은 표준화되어 있음
  • 구성 공간의 첫 4바이트는 장치의 고유한 ID를 포함하고 있어, 드라이버가 이를 통해 장치를 식별할 수 있음
  • PCI 인터페이스의 주요 혁신 중 하나는 구성 주소 공간의 존재
  • 이를 통해 드라이버는 장치 탐색 과정에서 위험한 작업을 피하고 안전하게 장치에 접근할 수 있음

 

 

부팅 (Boot Time) 후 초기화

  • PCI 장치 설정은 시스템 부팅 시 이루어짐
  • 부팅 시점에서 장치가 설정되며, 드라이버는 장치의 구성 공간을 통해 장치를 식별하고 접근할 수 있음

PCI 장치 초기화

 

1. PCI 장치는 전원이 공급될 때 메모리와 I/O 포트가 비활성화된 상태로 시작한다.

  • 이유는 PCI 버스에 연결된 장치들은 기본적으로 버스와 연결이 끊어진 상태이다.
  • 이는 장치가 초기에는 동작하지 않는 상태임을 의미하며, 장치의 모든 기능이 비활성화되어 있다.
  • 즉, 장치는 설정 트랜잭션에만 응답하며, 전원이 켜진 후에는 인터럽트 보고와 같은 장치 고유의 기능이 모두 비활성화된다.
  • 하지만 구성 공간(Configuration Space), 즉 장치의 환경 설정 영역만은 시스템 프로세서(CPU)가 접근할 수 있다.
  • 대부분의 마더보드는 BIOS, NVRAM 또는 PROM과 같은 PCI 지원 펌웨어를 갖추고 있으며, 이를 통해 설정 공간을 읽고 쓸 수 있게한다.
  • 구성 공간은 운영체제나 CPU가 장치와 통신할 수 있도록 설정 값을 저장하는 곳
  • 이 영역을 통해 장치에 관한 기본 정보(장치 ID, 벤더 ID 등)를 확인할 수 있다.

2. 운영체제가 장치 정보를 수집

  • 운영체제는 부팅 과정에서 PCI 버스에 연결된 모든 장치의 구성 공간을 차례로 검사한다.
  • 이때 각 장치가 필요로 하는 I/O 주소 공간과 인터럽트 번호와 같은 자원을 확인하고, 장치의 종류와 기능에 관한 기타 필요한 정보를 수집한다.
  • 이를 통해 운영체제는 장치가 어떤 기능을 수행할 수 있는지 파악하게 된다.

3. 운영체제가 내부 정보 정리 및 자원 할당

  • 운영체제는 수집한 정보를 바탕으로 각 PCI 장치를 관리하기 위한 내부적인 데이터 구조를 구성한다.
  • 그리고 각 장치가 사용할 I/O 주소와 인터럽트 번호를 할당한다.
  • 이렇게 할당된 자원은 시스템 내에서 다른 장치와 겹치지 않도록 관리된다.

4. 환경 설정 영역에 자원 정보 기록

  • 운영체제는 할당한 I/O 주소와 인터럽트 번호를 각 장치의 구성 공간(Configuration Space)에 기록한다.
  • 이 과정을 통해 장치가 운영체제에서 설정한 자원 정보를 알 수 있게된다.

5. 장치 활성화

  • 프로세서가 I/O 주소와 인터럽트 번호를 장치의 구성 공간에 쓰는 순간, 장치는 설정된 I/O 주소와 인터럽트 번호를 사용하여 작동을 시작할 수 있다.
  • 이때부터 프로세서는 해당 I/O 주소를 통해 장치에 접근할 수 있으며, 장치는 운영체제에서 할당된 인터럽트 번호를 사용해 인터럽트를 발생시킬 수 있다.

 

  • 시스템 부팅 시 펌웨어 또는 Linux 커널이 PCI 주변 장치와의 설정 트랜잭션을 수행하여 각 주소 영역에 안전한 위치를 할당한다.
  • 드라이버가 장치에 접근할 때는 이미 메모리와 I/O 영역이 프로세서 주소 공간에 매핑된 상태이다.

PCI 장치 정보 조회

  • 사용자는 /proc/bus/pci/devices 및 /proc/bus/pci/* 파일을 통해 PCI 장치 목록과 구성 레지스터 정보를 확인할 수 있다.
$ ls /proc/bus/pci
00  01  02  03  devices
$ ls /proc/bus/pci/01
00.0
  • /proc/bus/pci
    • 커널이 제공하는 가상 파일 시스템으로, 시스템의 PCI 버스와 장치 정보를 가지고 있음
    • 00, 01, 02, 03과 같은 디렉토리는 각각의 PCI 버스를 나타내며, 숫자는 버스 번호
    • 예를 들어, /proc/bus/pci/01/00.0 파일은 버스 번호 01에 있는 장치 00.0에 대한 정보를 포함
    • devices 파일은 시스템에 있는 모든 PCI 장치에 대한 요약 정보를 제공하는 파일

 

  • sysfs 트리(/sys/bus/pci/devices/)에서 개별 PCI 장치 디렉터리를 찾아볼 수 있다.
$ ls /sys/bus/pci/devices/
0000:00:00.0  0000:00:02.0  0000:00:14.0  0000:00:14.2  0000:00:16.0  0000:00:17.0  0000:00:1c.0  0000:00:1c.5  0000:00:1d.0  0000:00:1f.0  0000:00:1f.2  0000:00:1f.3  0000:00:1f.4  0000:01:00.0  0000:02:00.0  0000:03:00.0
  • /sys/bus/pci/devices
    • sysfs를 통해 PCI 장치의 정보를 제공
    • 0000:00:00.0, 0000:00:02.0 등은 PCI 장치를 식별하는 고유한 경로
      • 0000은 PCI 도메인
      • 00은 버스 번호
      • 00.0은 장치 및 함수 번호
    • 이 경로에서 각각의 장치 디렉토리는 해당 장치의 상태, 리소스 매핑, 구성 레지스터 등 다양한 정보를 담고 있는 파일들로 구성되어 있음
$ tree /sys/bus/pci/devices/0000:00:00.0
/sys/bus/pci/devices/0000:00:00.0
├── broken_parity_status
├── class
├── config
├── consistent_dma_mask_bits
├── d3cold_allowed
├── device
├── dma_mask_bits
├── enabled
├── irq
├── local_cpulist
├── local_cpus
├── modalias
├── msi_bus
├── numa_node
├── power
│   ├── async
│   ├── autosuspend_delay_ms
│   ├── control
│   ├── runtime_active_kids
│   ├── runtime_active_time
│   ├── runtime_enabled
│   ├── runtime_status
│   ├── runtime_suspended_time
│   └── runtime_usage
├── remove
├── rescan
├── resource
├── subsystem -> ../../../bus/pci
├── subsystem_device
├── subsystem_vendor
├── uevent
└── vendor
  • config: PCI 구성 정보를 담고 있는 바이너리 파일로, /proc/bus/pci/*에서 제공하는 내용과 동일
  • vendor, device, subsystem_device, subsystem_vendor, class: 장치의 특정 값을 나타내는 파일들로, 각각의 PCI 장치에 대해 고유한 정보를 제공
  • irq: 현재 PCI 장치에 할당된 IRQ
  • resource: 이 장치가 할당받은 메모리 리소스


PCI 장치의 구성 레지스터(Configuration Registers)

  • 모든 PCI 장치는 최소 256바이트의 구성 공간을 가지며, 이 중 처음 64바이트는 표준화되어 있다. 나머지 부분은 장치에 따라 달라짐
  • 필수 레지스터(Required Register)는 모든 PCI 장치에서 반드시 의미 있는 값을 가져야 하며, 선택적 레지스터(Optional Register)는 장치의 기능에 따라 달라짐

구성 레지스터 필드 설명

  • Vendor IDDevice ID: 장치 제조사와 장치 고유 ID를 식별
  • Command RegisterStatus Register: 장치 제어와 상태 정보
  • Base Address Registers: 장치의 메모리 및 I/O 주소를 지정
  • Interrupt LineInterrupt Pin: 장치에 할당된 인터럽트 정보를 포함
  • Subsystem Vendor IDSubsystem Device ID: 서브시스템(예: 특정 제조사의 하드웨어)에 대한 추가 정보를 제공

레지스터의 엔디안 형식

  • PCI 레지스터는 항상 리틀 엔디안 형식을 사용 (여러 플랫폼에서 사용할 경우 바이트 순서를 주의해야함)
  • Linux 개발자는 바이트 순서 변환 문제를 해결하기 위해 asm/byteorder.h에 정의된 함수를 사용할 수 있음

레지스터 설명

  • vendorID: 16비트 레지스터로, 하드웨어 제조사를 식별한다. 예를 들어, Intel의 모든 장치는 0x8086이라는 고유 번호를 가진다. 모든 제조사는 PCI Special Interest Group에 의해 관리되는 전역 레지스트리를 통해 고유 번호를 부여받는다.
  • deviceID: 제조사가 지정한 16비트 레지스터로, 특정 장치를 식별한다. 이 값은 vendorID와 함께 사용되어 장치의 고유 ID가된다.
  • class: 16비트 값으로, 장치의 기본 그룹 또는 클래스(예: 네트워크, 통신 등)를 식별한다. 드라이버는 이 값을 사용하여 유사한 기능의 장치들을 식별할 수 있다.

하위 시스템 식별자

  • subsystem vendorIDsubsystem deviceID: 특정 장치의 세부 구분을 위해 사용한다.
  • 예를 들어, 칩셋이 여러 역할을 할 수 있는 경우 이 필드를 통해 특정 장치로 식별할 수 있다.

 

pci_device_id 배열을 정의하여, 드라이버가 지원하는 장치 유형을 설정한다. 

여기에는 PCI_DEVICE_CLASS 매크로를 사용하여 특정 클래스의 USB 2.0 EHCI 컨트롤러를 처리하는 드라이버 설정이 포함되어 있다.

/* PCI driver selection metadata; PCI hotplugging uses this */
static const struct pci_device_id pci_ids [] = { {
	/* handle any USB 2.0 EHCI controller */
	PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_EHCI, ~0),
	}, {
	PCI_VDEVICE(STMICRO, PCI_DEVICE_ID_STMICRO_USB_HOST),
	},
	{ /* end: all zeroes */ }
};
MODULE_DEVICE_TABLE(pci, pci_ids);

 

struct pci_device_id

struct pci_device_id {
	__u32 vendor, device;         /* Vendor and device ID or PCI_ANY_ID*/
	__u32 subvendor, subdevice;   /* Subsystem ID's or PCI_ANY_ID */
	__u32 class, class_mask;      /* (class,subclass,prog-if) triplet */
	kernel_ulong_t driver_data;   /* Data private to the driver */
};
  • vendordevice:
    • 장치의 제조사와 장치 ID를 지정
    • 드라이버가 모든 제조사 또는 장치를 처리할 수 있는 경우, PCI_ANY_ID 값을 사용할 수 있음
  • subvendorsubdevice:
    • 서브시스템 제조사 및 장치 ID를 지정
    • 모든 서브시스템 ID를 처리할 수 있는 경우 PCI_ANY_ID를 사용할 수 있음
  • classclass_mask:
    • 드라이버가 지원하는 PCI 클래스 유형을 지정
    • 예를 들어, VGA 컨트롤러와 같은 특정 장치 클래스가 PCI 사양에 정의되어 있음
    • 모든 서브시스템 ID를 처리할 수 있는 경우 PCI_ANY_ID를 사용할 수 있음
  • driver_data
    • 장치를 매칭하는 데 사용되지 않지만, 드라이버가 다양한 장치를 구분하고자 할 때 사용할 수 있는 정보 저장 용도로 사용

PCI_DEVICE(vendor, device) 매크로

  • 특정 vendor 및 device ID에 맞는 struct pci_device_id를 생성
  • 이 매크로는 subvendor와 subdevice 필드를 PCI_ANY_ID로 설정

PCI_DEVICE_CLASS(device_class, device_class_mask) 매크로

  • 특정 PCI 클래스에 맞는 struct pci_device_id를 생성

pci_device_id

  • static struct pci_device_id i810_ids[] 배열은 Intel 장치에 대한 여러 pci_device_id 구조체를 정의
  • 마지막 값으로 { 0, }를 설정하여 배열이 끝났음을 표시
static struct pci_device_id i810_ids[] __devinitdata = {
	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG1) },
	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG3) },
	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810E_IG) },
	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82815_CGC) },
	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82845G_IG) },
	{ 0, },
};

 

 

 

 

References