임베디드 개발/리눅스 디바이스 드라이버

LDD ] PCIe 디바이스 드라이버 작성하기 - (1)

eteo 2024. 12. 25. 22:24

 

 

 

PCIe 카드 구입

 

 

이제부터 PCIe 리눅스 디바이스 드라이버를 공부해보려고 PCIe 카드 하나를 구입했다. 학습용으로 사용할거기 때문에 네이버에서 낮은 가격순으로 정렬해서 가장 저렴한걸 구입해봤다.

 

COMS 사의 SW562 제품으로 CH382L 칩셋을 탑재한 x1 슬롯 타입 PCIe 페러럴 포트 카드이며 윈도우 디바이스 드라이버CD와 함께 제공된다.

 

 

 

📝 페러럴 포트란?

 

Parallel Port는 예전에는 프린터, 스캐너, 산업용 장비 등 다양한 외부 장치를 연결하는 데 사용되었지만, 최근에는 USB나 네트워크 연결 방식으로 대체되어 PC에서 기본적으로 제공되지 않는 경우가 많다. 때문에 레거시 장비와 호환을 위해서 PCIe 슬롯에 꼽으면 PC에 Parallel Port를 추가해주는 이런 확장 카드가 존재한다.

 

 

 

DSUB Female 25 핀아웃은 다음과 같다.

 

 

 

 

 

CH382L에 대해서

 

 

 

CH382라는 칩셋 자체는 PCIe 기반 듀얼 채널 UART와 양방향 페러럴포트(프린터 모드)를 동시에 지원하는 다목적 칩셋이다. 하지만 SW562라는 PCIe 카드는 CH382의 기능 중 페러럴 포트만 사용하게끔 설계가 되어있고 DB25 핀아웃도 해당 페러럴 포트 신호만 나와있으므로 이 카드를 가지고 UART 기능을 사용할 수는 없고 한 채널의 페러럴 포트만 사용할 수 있다.

 

내가 해보려는 거는 리눅스 디바이스 드라이버를 작성하여 패러럴 포트의 데이터 라인과 프린터에 사용되는 제어 신호를 GPIO처럼 다뤄보려는 것이다.

 

 

데이터시트 :

CH382DS1.PDF
0.20MB

 

 

 

 

lspci

구입한 PCIe 카드를 메인보드의 PCIe 슬롯에 연결한 뒤 부팅한다.

 

pciutils의 lspci 명령을 사용하면 부팅 시 Enumeration 된 PCIe 장치들의 리스트를 확인할 수 있다.

 

장치 목록 앞부분에 나타나는 XX:YY.Z 형식의 숫자는 버스, 디바이스, 펑션 번호인데 이 중에서 내가 추가한 PCIe 장치의 번호는04:00.0 이다.

 

  • Bus Number : RC(Root Complex)에는 최대 256개의 버스가 존재할 수 있다.
  • Device Number : 각 버스는 최대 32개의 장치를 연결할 수 있다.
  • Function Number : 각 장치는 최대 8 개의 Function을 가질 수 있다. 하나의 장치가 여러 기능을 수행할 수 있게 설정된 경우 각 기능이 별도의 함수로 분리된다.

 

일반적으로 동일한 슬롯에 카드가 장착되어 있고 시스템에 별다른 변경사항이 없을 경우에별는 PCIe Bus:Device.Function 번호는 재부팅하더라도 유지된다.

 

 

 

sudo lspci -s 04:00.0 -vvv 명령을 사용하면 좀 더 자세한 정보를 확인할 수 있다.

 

 

 

이 때 출력되는 정보들은 운영체제가 PCIe 장치의 Configuration space에서 읽어온 정보를 사용자에게 표시해주는 것이라 할 수 있다.

 

PCI 레거시 장치는 256바이트 크기의 Configuration space를 가진다. 한편 PCIe장치 Configuration space는 4096바이트의 확장된 크기를 가지는데 앞부분 256 바이트는 PCI Configuration space와 동일한 구조로 하위호환성을 제공한다.

 

그리고 PCI와 PCIe 모두 Configuration space의 첫 64바이트는 장치의 기본 정보를 제공하는 공통 헤더로 구성된다.

 

 

 

먼저 PCIe Header에 대해 알아보자.

 

 

 

 

PCIe Header

PCIE 헤더는 두가지 타입으로 EP장치에서 사용하는 Type 0 헤더와 RC 장치에서 사용하는 Type 1 헤더로 나뉜다.

 

그 중에서도 Type 0 Header는 다음과 같이 구성된다.

 

 

 

 

다음은 04:00.0 장치의 PCI 헤더를 출력한 것이다.

 

 

데이터시트와 같이 비교해보자. Vendor ID가 0x1C00이고 Device ID가 0x3050이니 Single paraller port 모드로 설정된 것을 할 수 있다.

 

 

 

 

 

 

 

PCI(e) Capabilities

 

그럼 헤더를 제외한 나머지 Configuration space에는 어떤 정보들이 있을까? 바로 PCI(e) Capabilities들이 나열되어 있다.

 

PCI(e) Capabilities는 PCI와 PCIe 장치가 제공하는 추가 기능(PCI Express 지원 여부, MSI/MSI-X 지원 여부, 전원 관리 기능, 고급 에러 보고 기능 등)과 그 속성을 시스템에 제공하는 구조이다.

 

Capabilities는 크게 0x34 오프셋에서 시작하는 PCI Capabilities와 0x100 오프셋에서 시작하는 PCI Express Extended Capabilities로 나뉜다.

 

 

 

먼저 PCI Capabilities는 0x34 Capabilities Pointer가 가리키는 위치에서 부터 시작하는데 각 Capability는 링크드 리스트 형태로 저장되며 1바이트의 Capability ID 다음에 등장하는 포인터가 다음 Capability를 가리키는 구조이다.

 

 

 

 

다음은 PCI Capabilities의 ID 테이블이다.

 

이 중에서도 0x10 ID를 가지는 PCI Express Capability는 PCIe 장치에서 제공되는 Capability로 장치의 세부 정보, 링크 상태, 버스 폭, PCIe 특유의 기능 속성 등이 포함되어 있다.

 

 

 

다음 PCI Express Extended Capabilities는 PCI Configuration space가 끝나는 다음 위치인 0x100 위치에서 시작하며 일반 PCI Capability와 달리 2바이트의 ID를 가지고 4bit의 버전 넘버 다음에 12비트의 Next Capability를 가리키는 포인터가 오는 것을 알 수 있다.

 

 

 

 

 

구입한 SW562 PCIe 카드의 경우 ID 0x1, 0x5, 0x10의 PCI Capabilities가 있고 ID 0x1의 PCI Express Extended Capabilities가 있는 것을 확인할 수 있었다.

 

 

 

 

 

 

 

 

Base Address Register (BAR)

 

- BAR란?

 

시스템 메모리 영역을 매핑하는데 사용되는 레지스터로 configuration space의 offset 0x10부터 시작하여 6개의 BAR가 위치한다.

 

이 레지스터는 PCI/PCIe 장치가 시스템 자원을 요청하고 할당받기 위한 핵심 구성 요소로서, 장치는 필요한 메모리 공간 또는 I/O공간의 요구사항을 BAR를 통해 운영체제에 알리며, 운영체제는 이를 인식하여 적절한 메모리 공간을 할당하고 해당 주소를 BAR에 기록한다.

 

각 BAR는 32비트 크기로 BAR[0]부터 BAR[5]까지 존재하며, 이 중 장치의 요구사항에 따라 필요한 BAR만 사용되고 그렇지 않은 BAR는 0으로 채워진다.

 

 

 

 

 

 

다음은 BAR의 비트필드 구조이다.

 

 

- Memory BAR 비트 필드 구조

  • Bit 0 (Region Type) :
    • 0 (Memory) : 이 BAR는 메모리 공간으로 사용된다.
  • Bit 1-2 (Locatable) :
    • 00 (any 32-bit) : 이 BAR는 32-bit 메모리 주소 공간에 위치한다.
    • 01 (< 1MB) : 이 BAR는 1MB 이하의 주소 공간에 위치한다는 뜻으로 특별한 메모리 제약이 있는 경우 사용한다.
    • 10 (any 64-bit) : 이 BAR는 64-bit 메모리 주소 공간에 위치한다. 두 개의 연속된 BAR를 사용하여 하위 BAR가 Lower 32 bit, 상위 BAR가 Upper 32 bit 주소를 구성한다. 또한 이 때 상위 BAR의 하위 4비트는 항상 0으로 설정된다.
  • Bit 3 (Prefechable) :
    • 0 (no) : Non-Prefechable 메모리로 캐시를 사용하지 않고 CPU가 이 메모리 주소에서 읽을 때마다 메모리 컨트롤러에 접근해야 한다.
    • 1 (yes) : Prefechable 메모리로 CPU가 한번에 많은 데이터를 미리 읽어와서 나중에 빠르게 접근할 수 있도록 캐시에 저장할 수 있다.
  • Bit 4-31 (Base Address) :
    • Memory BAR의 기본 주소를 나타내며 이 주소는 16바이트 단위로 정렬된다. 즉, 0x10 단위로 주소가 설정될 수 있고 하위 4비트가 0으로 고정된다.

 

- I/O BAR 비트 필드 구조

  • Bit 0 (Region Type) :
    •  1 (I/O) : 이 BAR는 I/O 공간으로 사용된다.
  • Bit 2-31 (Base Address) :
    • I/O Bar의 기본 주소를 나타내며 이 주소는 4바이트 단위로 정렬된다. 즉, 0x4 단위로 주소가 설정될 수 있고 하위 2비트가 0으로 고정된다.

 

 

 

 

📝 I/O Port 란?

x86 아키텍처에서는 메모리 주소 공간과 별개로 I/O 포트를 위한 독립된 주소 공간이 존재한다. 보통 낮은 주소 영역을 차지하며, 특수한 목적으로 사용된다. CPU가 메모리 공간에 접근할 때는 MOV와 같은 어셈블리 명령어를 통해 접근하는데, I/O 포트에 접근 할 때는 IN과 OUT이라는 특수한 명령어를 사용하여 접근해야 한다. 보통 커널 모드에서는 inb, inw, inl, outb, outw, outl과 같은 함수를 사용해서 I/O 포트에 접근한다.

 

 

 

 

 

 

- 운영체제가 BAR의 정보를 읽어 PCIe 장치가 요구하는 공간의 사이즈를 확인하는 방법

 

BAR 주소 할당 운영체제가 부팅될 때 PCIe Enumeration 시 수행 되고, 아래의 과정을 통해 운영체제는 PCIe 장치가 필요한 공간의 사이즈를 확인해 적절한 시스템 메모리 주소를 할당할 수 있다.

 

 

예를 들어 BAR가 다음과 같다고 해보자. Bit 0이 1이니 이 BAR는 I/O BAR임을 의미한다.

 31             24                16          11    8                 0
| W W W W W W W W | W W W W W W W W | W W W W R R R R | R R R R R R R R |
|                 |                 |         0 0 0 0 | 0 0 0 0 0 0 0 1 |

 

그리고 이 BAR의 하위 12 비트는 Hardwired 비트로 제조사에 의해 고정된 0x001이라는 값을 가지며 Read Only 비트이다.

 

  1. OS는 BAR의 모든 비트를 1로 set하기 위해 0xFFFFFFFF를 쓴다.
  2. 그리고 다시 BAR를 읽는데 하위 12비트가 Read Only 속성이기 때문에 0xFFFFF001을 읽는다.
  3. 읽은 값에서 Bit 0을 클리어한다. (Memory BAR인 경우에는 Bit 0-3을 클리어한다.) 클리어한 결과는 0xFFFFF000이다.
  4. 이를 반전하면 0x00000FFF 이고 여기에 1을 더하면 0x1000이다.
  5. 즉, 해당 I/O BAR가 요청하는 사이즈는 4096 바이트임을 알 수 있다.

 

 

 

 

 

- SW562 카드의 BAR의 경우

 

데이터 시트를 보면 BAR0, BAR1, BAR2 만 사용되고, 그 중에서도 Serial Port 제어에 사용되는 I/O base address 0 는 I/O space로 256 btyes 사이즈의 공간을 사용하고, Parallel Port 제어에 사용되는 I/O base address 2 는 I/O space로 4 btyes 사이즈의 공간을 사용하는 것을 알 수 있다.

 

 

 

그런데 실제로 BAR값을 임시로 저장해두고 0xFFFFFFFF 값을 쓴 다음에 다시 읽어 BAR가 hardwired 방식으로 요구하는 사이즈를 확인해보니 BAR1과 BAR2의 경우에는 데이터 시트에 나와있는 W/R 속성과 약간 달랐다.

 

어쨌든 중요한건 메모리 정렬에 따라 하위 비트를 무시하고 시작 메모리 주소를 확인해봤을 때 BAR0의 경우 I/O Port 0xE000 주소에 256바이트가 할당 되었고, BAR2의 경우 I/O Port 0xE100 주소에 4바이트가 할당된 것을 확인할 수 있었다. (아래 참고)

 

 

 

 

 

 

 

 

Reference : Johannes 4GNU_Linux (Youtube)