Colors of Ray+Hue'



PCI Configuration Space

original source: http://egloos.zum.com/nimhaplz/v/5314763

PCI 버스에는 여러가지 장치가 물리는데 그 장치를 사용하려면, 각 장치가 어떤 것이고(identification), 어떻게 장치와 통신해야 하는지(protocol)를 알아야 하는데, PCI 버스에서는 장치를 인식하고, 그 장치의 기본적인 정보를 얻어오기 위해 configuration space를 사용한다.

PCI configuration space 대략 다음과 같은 정보가 담긴 데이터 스트럭쳐 이다.
Device ID,Vendor ID,Status,Class code,.. 등

디바이스의 PCI configuration space 정보를 읽으면 디바이스와 통신을 하기 위한 기본적인 정보를 알 수 있는 것이다. 그러면 예를 들어, 내 PC에 달린 네트웍 카드(NIC)의 configuration space는 어떻게 읽어올 수 있을까? 직접적으로 읽어올 수는 없고, 모든 디바이스를 검색 해야 한다. PCI디바이스는 물리적으로 모든 디바이스에 bus, device, function 이라는 번호가 부여된다. 이 번호는 PCI slot에 따라 부여되는 것이기 때문에, 모든 bus, 모든 device, 모든 function을 스캔 해 보면, 컴퓨터에 달린 모든 디바이스 정보를 알 수 있다.

bus, device, function은 총 16bit이므로, 여걸 다 스캔하면 65536개를 스캔해야 하는 것이다. 실제로 Linux는 부팅과정에서 이걸 다 스캔해서 디바이스를 인식하며, 이 과정을 enumeration이라 한다. 실제 Linux 디바이스 드라이버를 살펴 보자. 살펴 볼 드라이버는 Realtek의 네트웍카드인 r8169이다. Linux source의 drivers/net/r8169.c 파일이다.

(구글에서 r8169.c라고 검색하면 바로 볼 수 있다)
이 파일의 맨 끝부분을 보면, 다음과 같이 디바이스 드라이버를 등록하는 코드가 있다.


static struct pci_driver rtl8169_pci_driver = {
    .name       = MODULENAME,
    .id_table   = rtl8169_pci_tbl,
    .probe      = rtl8169_init_one,
    .remove     = __devexit_p(rtl8169_remove_one),
#ifdef CONFIG_PM
    .suspend    = rtl8169_suspend,
    .resume     = rtl8169_resume,
#endif
};

이 디바이스 드라이버가 처리할 수 있는 ID는 id_table에 저장돼 있다. 그걸 따라가 보면 이렇다.

static struct pci_device_id rtl8169_pci_tbl[] = {
    { PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8129), 0, 0, RTL_CFG_0 },
    { PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8136), 0, 0, RTL_CFG_2 },
    { PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8167), 0, 0, RTL_CFG_0 },


여기서 PCI_VENDOR_ID_REALTEK이 realtek의 vendor ID이고,(0x10ec)Device ID가 각각 0x8129, 8136, 8167이다. 이 vendor ID, Device ID가 PCI Configuration space에 적혀 있어서,이 VID(Vendor ID), DID(Device ID)를 가진 하드웨어가 검색되면 이 8169.c를 쓸 수 있는 것이다.디바이스 드라이버를 등록하는 곳으로 다시 돌아가서, 이번에는 probe함수를 살펴보자.이 함수는 하드웨어를 사용하기 위해 실제로 준비를 하는 함수이다.

probe함수의 이름은 rtl8169_init_one 이다.이 함수 중간에 보면, pci_set_master라는 함수를 부르는 곳이 있다.pci set master는 busmaster DMA를 켜주는 함수로, 다음과 같이 정의된다.

void pci_set_master(struct pci_dev *dev){
    u16 cmd;
    pci_read_config_word(dev, PCI_COMMAND, &cmd);
    if (! (cmd & PCI_COMMAND_MASTER)) {
        pr_debug("PCI: Enabling bus mastering for device %s\n", pci_name(dev));
        cmd |= PCI_COMMAND_MASTER;
        pci_write_config_word(dev, PCI_COMMAND, cmd);
    }
    dev->is_busmaster = 1;
    pcibios_set_master(dev);
}

여기서 굵은 글씨로 표시한 두 줄이 바로 PCI configuration space를 읽고 쓰는 부분이다.

* pci_read_config_word  : dev의 PCI configuration space에서 PCI_COMMAND field를 읽어와서, cmd에 저장한다.
* pci_write_config_word  : dev의 PCI configuration space의 PCI_COMMAND field에 cmd값을 쓴다.

따라서 위 코드는, PCI_COMMAND field에다가 PCI_COMMAND_MASTER라는 bit을 켜는 역할을 수행하는 것이다.여기까지 PCI configuration space가 무엇인지, 어떻게 쓰는지를 살펴봤다.그 외 field에 대해 좀 더 자세히 살펴보자면 이렇다.

* Class Code
이 PCI device의 분류를 나타낸다. 예를 들어 이 디바이스가 network card인지, USB controller인지 구별하는 field이다.class code는 include/linux/pci_ids.h 파일에 보면 적혀있다.상위 16 bits가 저 파일에 적혀 있으며, 예를 들면 다음과 같다.

#define PCI_CLASS_STORAGE_IDE       0x0101      // Legacy IDE controller
#define PCI_CLASS_NETWORK_ETHERNET  0x0200     // Ethernet Interface

따라서, class code만 봐도 디바이스의 종류를 알 수 있는것이다.입출력 표준이 정해 져 있는 class가 있는데, 그런 경우 class마다 디바이스 드라이버가 있다.예를 들어, IDE같은 경우(class code 0x0101), PCI IDE controller Specification을 보고디바이스 드라이버를 작성하면, device ID나 vendor ID에 상관없이 동작한다.이러한 표준이 없는 디바이스 드라이버의 경우, device ID와 vendor ID에 따라 디바이스 드라이버가 필요하다.

* Base Address Register (BAR)
PCI configuration space는 장치를 찾을 때만 이용할 수 있는 매우 좁은 공간이다.실제로 디바이스 드라이버를 이용하기 위해서는 좀 더 넓은 영역이 필요할 것이다.예를 들어, 비디오 카드의 경우, Memory-mapped I/O (MMIO)를 하기 위해서는넓은 공간이 필요하다. (2000x1000 pixels에서 32bits color를 쓰면, 8MB정도가 필요하다) 이러한 공간을 따로 잡아서, 디바이스와 OS가 "우리 저길 쓰자"고 합의해야 하는데,이 때, Base Address Register (BAR)에 그러한 내용을 쓰는 것이다.부팅 과정에서 BIOS는 기본적으로 각 디바이스 별로 이용할 address space를 할당 해 주는데,BAR를 읽어보면 그 값을 알 수 있고, BAR를 통해서 그 값을 설정할 수도 있다.

* Decoding PCI data and lspci output on Linux Hosts

$ ls -la /sys/bus/pci/devices

위의 명령을 실행하면 다음과 같은 예제를 얻을 수 있는데, 

lrwxrwxrwx 1 root root 0 2009-08-03 10:38 0000:04:00.0 -> ../../../devices/pci0000:00/0000:00:0b.0/0000:04:00.0

디바이스의 문자열 "0000:04:00.0" 의 의미는 다음과 같다.

0000 : PCI domain (각 도메인은 256개의 PCI 버스를 가질 수 있음) 04 : the bus number the device is attached to 00 : the device number .0 : PCI device function

해당 디바이스에 대한 좀더 구체적인 정보를 얻기 위해서는 0000:04:00.0 디렉토리로 들어가서, 파일을 보면 된다.


참고할 만한 사이트
http://en.wikipedia.org/wiki/PCI_configuration_space
위키 페이지는 항상 자세하고 친절한 설명이 있으니 여기에도 좋은 말씀이 많을 것이다.나는 보통 configuration space 모양을 보는데 참고한다.

http://wiki.osdev.org/PCI
PCI configuration space 사용법에 대해 자세히 나와있다.reference manual용도로 사용하면 되겠다.

http://lwn.net/Kernel/LDD3/
불후의 명저 Linux Device Driver 3rd edition 사이트이다.책으로도 파는데 온라인으로도 공개 돼 있다.Linux Device Driver전반에 대한 자세하고(?) 친절한 설명이 있다.처음 보기엔 좀 어려울 수 있다.

'Linux Kernel' 카테고리의 다른 글

Block Device Open  (0) 2016.09.01
volatile keyword  (0) 2016.08.30
glibc Malloc  (0) 2016.08.27
LD_PRELOAD example  (0) 2016.08.27
MMIO in PCIe  (0) 2016.08.24