PCI 의 세계는 광대하고 (별로 즐겁지 않겠지만) 놀라움으로 가득하다. 각각의 PCI 장치들은 서로 다른 요구사항과 버그들을 가진다. 이때문에 리눅스 커널의 PCI 지원 레이어는 우리가 바라는 만큼 간단하지가 않다. 이 짧은 글은 잠재적인 모든 PCI 드라이버 개발자들에게 PCI handling 이라는 깊은 숲속에서 자신 만의 길을 찾아내도록 도와주려고 한다.




1 PCI 드라이버의 구조 


2가지 종류의 PCI 드라이버가 존재한다: 신식 (new-style) 드라이버는 장치를 검색 (probe) 하는 대부분의 과정을 PCI 레이어에게 넘겨주었고 장치의 온라인 삽입과 제거를 지원한다. (즉 PCI, hot-pluggable PCI,CardBus 를 하나의 드라이버로 지원한다) 다른 하나는 구식 (old-style) 드라이버인데 모든 장치 검색 과정을 스스로 처리한다. 만약 꼭 그래야 할 이유가 없다면, 새로 작성하는 코드에서 구식의 방식을 이용하여 장치 검색을 수행하지 않도록 하자. 장치가 검색되고 나면 드라이버는 (구식이던 신식이던 간에) 장치를 동작시키고 싶어한다. 그러기 위해서는 다음 과정이 필요하다:

  • 장치를 활성화한다
  • 장치의 설정 공간에 접근한다
  • 장치가 제공하는 자원 (주소와 IRQ 번호) 를 알아낸다
  • 이 자원들을 할당한다
  • 장치와 통신한다 

이들은 대부분 다음 절에서 설명한다. 나머지 부분은 주석이 잘 달려있는 <linux/pci.h> 코드를 보게 될 것이다.

만약 PCI subsystem 이 설정되지 않았다면 (CONFIG_PCI 가 n 값을 가짐) 다음에 소개하는 대부분의 함수들은 내용이 없는 인라인 함수 혹은 드라이버 상의 많은 ifdef 들을 피하기 위한 적절한 에러코드를 리턴하는 함수로 정의될 것이다.

2 신식 (New-style) 드라이버 


신식 드라이버는 초기화 과정에서 단지 다음과 같은 멤버를 포함하는 드라이버를 표현하기 위한 구조체 (struct pci_driver) 의 포인터를 인수로 pci_register_driver 함수를 호출한다.

name 드라이버 이름
id_table 드라이버가 처리하는 디바이스 ID 의 테이블의 포인터. 대부분의 드라이버에서는 이 테이블을 MODULE_DEVICE_TABLE(pci,...) 매크로를 이용해서 공개 (export) 해야 한다. 시스템이 알고있는 모든 PCI 장치들의 probe() 함수를 호출할 때는 NULL 로 설정하라..?
probe ID 테이블과 매치되고 아직 다른 드라이버에 의해 처리되지 않은 모든 장치들에 대한 장치 검색 함수의 포인터 (기존에 존재하던 장치나 이후에 새롭게 추가된 장치에 대하여 pci_register_driver 함수가 실행되는 과정에서 호출됨). 이 함수는 장치를 나타내는 pci_dev 구조체의 포인터와 장치와 매치되는 ID 테이블의 엔트리의 포인터를 인자로 받는다. 장치가 드라이버를 수락하면 (accepted) 0 을 반환하고 그렇지 않으면 (음수값인) 에러 코드를 반환한다. 이 함수는 언제나 process context 에서 호출되기 때문에 sleep 할 수 있다.
remove 이 드라이버에 의해 처리되던 장치가 제거될 때에 호출되는 함수의 포인터 (드라이버의 등록해제 (deregistration) 과정이나 hot-pluggable 슬롯에서 기계적으로 제거한 경우에 호출됨). 이 함수는 언제나 process context 에서 호출되기 때문에 sleep 할 수 있다.
save_state 장치가 대기 모드 (suspend) 로 들어가기 전에 상태 정보를 저장한다.
suspend 장치를 절전 상태 (low-power state) 로 만든다.
resume 장치를 절전 상태에서 깨어나게 한다
enable_wake wake event 를 만들어서 장치를 절전 상태로부터 활성화 시킨다.
(PCI 전원 관리와 그에 관련된 함수들은 Documentation/power/pci.txt 를 참고하기 바란다)

ID 테이블은 마지막이 모두 0 인 엔트리로 끝나는 struct pci_device_id 의 배열이다. 이 구조체는 다음과 같은 멤버들을 가진다:

vendor, device 매치시킬 제품 공급자와 장치 ID (혹은 PCI_ANI_ID 값을 가짐)
subvendor, subdevice 매치시킬 subsystem 의 제품 공급자와 장치 ID (혹은 PCI_ANI_ID 값을 가짐)
class, class_mask 매치시킬 장치의 클래스. class_mask 는 비교시 어떤 비트들을 검사해야 하는지를 나타냄
driver_data 드라이버마다 자체적으로 사용하는 데이터
대부분의 드라이버에서는 driver_data 를 사용할 필요가 없다. driver_data 를 사용하는 가장 좋은 예로는 동일한 장치 타입의 정적인 리스트의 인덱스로 사용하는 것이다 (포인터로 사용하는 것이 아니다).

시스템이 알고있는 모든 PCI 장치에게 probe() 가 호출되도록 하려면 테이블의 엔트리를 {PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID} 로 만들면 된다.

/sys/bus/pci/drivers/{driver}/new_id 파일에 쓰는 것으로 런타임에 디바이스 드라이버에게 새로운 PCI ID 가 추가될 수 있다. 일단 추가되면, 드라이버는 이것을 지원하는 모든 장치를 검색 (probe) 할 것이다.

echo "vendor device subvendor subdevice class class_mask driver_data" > \ 
   /sys/bus/pci/drivers/{driver}/new_id 
  


(위의 모든 필드들은 (0x 을 생략한 형태의) 16 진수로 넘겨진다.)

사용자는 단지 필요한 필드만을 넘길 필요가 있다; vendor, device, subvendor, subdevice 필드는 기본값으로 PCI_ANY_ID (FFFFFFFF) 를 가지고 class 와 classmask 는 0 을, driver_data 필드는 OUL 을 각각 기본값으로 가진다. 디바이스 드라이버는 인자로 넘겨진 driver_data 필드를 위해서 다음 함수를 반드시 호출해야 한다.

pci_dynids_set_use_driver_data(pci_driver *, 1) 
  


그렇지 않으면 해당 필드로는 단지 0 이 넘겨진다.

드라이버가 제거될 (exit) 때는, 단지 pci_unregister_driver() 함수를 호출하고 PCI 레이어는 자동적으로 이 드라이버에게 처리되고 있던 모든 장치에 대해서 remove hook 함수를 호출한다.

초기화 (initialization) 함수와 제거 (clean-up) 함수는 (<linux/init.h> 에 정의된 매크로 들을 이용하여) 적절히 표시해 주기 바란다:

__init 초기화 코드. 드라이버가 초기화 된 이후에는 사라진다.
__exit 종료 코드. 모듈로 컴파일 되지 않은 드라이버에서는 무시된다.
__devinit 장치 초기화 코드. 커널이 CONFIG_HOTPLUG 가 설정되지 않은 상태로 컴파일 되었다면 __init 와 동일하다. 그렇지 않으면 일반 함수이다.
devexit exit 와 동일하다.
팁: 

module_init()/module_exit() 함수들 (그리고 오직 여기서만 호출되는 모든 초기화 함수들) 은 __init/exit 로 표시 (mark) 되어야 한다. struct pci_driver 는 이러한 태그들로 표시되면 안된다. ID 테이블 배열은 __devinitdata 로 표시되어야 한다. probe() 와 remove() 함수들 (그리고 오직 여기서만 호출되는 모든 초기화 함수들) 은 __devinit/exit 로 표시되어야 한다. 만약 드라이버가 hotplug 를 지원하는 드라이버가 아니라고 확신한다면 그냥 __init/exit 와 __initdata/exitdata 만을 사용하라.

__devexit 로 표시된 함수에 대한 포인터는 반드시 __devexit_p(함수이름) 을 사용하여 생성되어야 한다. 이 매크로는 함수이름을 생성하거나 __devexit 로 표시된 함수가 버려질 (discard) 경우 NULL 을 반환한다.

3 수동으로 PCI 장치를 검색하는 방법 (구식 드라이버) 


pci_register_driver() 인터페이스를 이용하지 않는 PCI 드라이버의 경우에는 다음과 같은 방식으로 수동으로 (manually) PCI 장치를 검색할 수 있다:

제품 공급자와 장치 ID 검색:

struct pci_dev *dev = NULL; 
while (dev = pci_find_device(VENDOR_ID, DEVICE_ID, dev)) 
    configure_device(dev); 
  


클래스 ID 검색 (동일한 방식으로 루프를 이용):

    pci_find_class(CLASS_ID, dev) 
  


제품 공급자/장치 ID 및 subsystem 의 제품 공급자/장치 ID 검색:

    pci_find_subsys(VENDOR_ID, DEVICE_ID, SUBSYS_VENDOR_ID, SUBSYS_DEVICE_ID, dev) 
  


VENDOR_ID 나 DEVICE_ID 위치에는 와일드 카드로 PCI_ANY_ID 라는 상수를 이용할 수 있다. 이 경우에는 예를 들어 '특정 공급자가 제공하는 모든 장치' 와 같은 검색이 가능하게 된다.

이 함수들은 hotplug 에 대해서 안전하지 않다는 (not hotplug-safe) 것을 주의하기 바란다. 위의 각 함수들에 대해서 hotplug 를 안전하게 지원하는 버전은 pci_get_device(), pci_get_class(), pci_get_subsys() 이다. 이 함수들은 자신이 반환하는 장치에 해당하는 pci_dev 구조체의 참조 횟수 (reference count) 를 증가시킨다. 해당 장치를 사용하지 않을 경우에는 (아마도 모듈 언로드시) pci_dev_put() 함수를 이용하여 반드시 이 장치의 참조 횟수를 감소시켜야 한다.

4 장치 활성화하기 


검색한 장치를 이용해서 어떤 작업을 수행하기 전에, pci_enable_device() 함수를 이용해 장치를 활성화 시킬 필요가 있다. 이 함수는 I/O 와 메모리 영역을 활성화시키고, 필요한 경우 잃은 자원들을 할당하며, 장치가 절전 상태에 있었던 경우에는 깨어나도록 한다. 이 함수가 실패할 수도 있다는 사실에 주의하기 바란다.

만약 장치를 buf mastering mode 로 사용하고 싶은 경우에는, pci_set_master() 함수를 호출해서 PCI_COMMAND 레지스터의 bus master 비트를 활성화하고, BIOS 에 의해 어떤 bogus 로 설정된 경우?? 레이턴시 타이머 값을 수정한다.

만약 PCI Memory-Write-Invalidate 트랜잭션을 사용하고 싶은 경우에는 pci_set_mwi() 함수를 호출하라. 이 함수는 Mem-Wr-Inval 의 PCI_COMMAND 비트를 활성화하고, cache inline size 레지스터가 적절하게 설정되도록 해준다. 모든 아키텍처에서 Memory-Write-Invalidate 를 지원하지는 않기 때문에, pci_set_mwi() 의 리턴값을 체크해야 한다.

5 PCI 설정 공간에 접근하는 방법 


struct pci_dev * 로 표현되는 장치의 설정 공간에 접근하기 위해서 pci_(read/write)_config_(byte|word|dword) 함수를 사용할 수 있다. 이 함수들은 모두 성공인 경우에 0 을 리턴하고, 실패인 경우 pcibios_strerror 에 의해 문자열로 변환될 수 있는 에러 코드 (PCIBIOS_... 의 형태) 를 리턴한다. 대부분의 드라이버에서 올바른 PCI 장치에 접근하는 경우에는 실패하지 않을 것이다.

만약 가능한 struct pci_dev 가 없는 경우에는 pci_bus(read|write)_config(byte|word|dword) 함수를 이용해서 해당 버스상의 장치와 function ? 에 접근할 수 있다.

만약 config header 의 standard portion 의 필드에 접근하는 경우에는 <linux/pci.h> 에 선언된 심볼들을 이용하기 바란다.

만약 Extended PCI Capability 레지스터에 접근할 필요가 있는 경우에는 단지 특정한 capability 를 위해 pci_find_capability() 를 호출하라. 그러면 이 함수는 해당하는 레지스터 블럭을 찾아줄 것이다.

6 주소와 인터럽트 


메모리와 포트 주소와 인터럽트 번호는 설정 공간으로 부터 읽혀져서는 안된다. 대신에 커널에 의해 매핑된 struct pci_dev 의 값을 사용해야 한다.

장치의 메모리에 접근하는 방법은 Documentation/IO-mapping.txt 를 참조하기 바란다.

다른 누군가가 같은 장치를 사용하지 않음을 나타내기 위해 I/O 영역에 대해서 request_region() 함수를, 메모리 영역에 대해서 request_mem_region() 함수를 호출할 필요가 있다.

모든 인터럽트 핸들러는 SA_SHIRQ 플래그와 함께 등록되어 있어야 하며, IRQ 와 장치를 매핑하기 위해서 devid 를 사용해야 한다 (모든 PCI 인터럽트는 공유된다는 것을 기억하라).

7 다른 흥미로운 함수들 


pci_fiind_slot() 주어진 버스와 슬롯 번호에 대응하는 pci_dev 를 찾는다.
pci_set_power_state() PCI Power Management state (0=D0 ... 3=D3) 를 설정한다.
pci_find_capability() 장치의 capability 리스트로 부터 특정한 capability 를 찾는다.
pci_module_init() 올바른 pci_driver 의 초기화와 에러 처리를 도와주는 인라인 함수이다.
pci_resource_start() 주어진 PCI 영역에 해당하는 버스의 시작 주소를 리턴한다.
pci_resource_end() 주어진 PCI 영역에 해당하는 버스의 마지막 주소를 리턴한다.
pci_resource_len() PCI 영역의 바이트 길이를 리턴한다.
pci_set_drvdata() pci_dev 에 대한 private driver data 포인터를 설정한다.
pci_get_drvdata() pci_dev 에 대한 private driver data 포인터를 얻어온다.
pci_set_mwi() Memory-Write-Invalidate 트랜잭션을 활성화한다.
pci_clear_mwi() Memory-Write-Invalidate 트랜잭션을 비활성화한다.

8 그 밖의 힌트들 


(드라이버에서 사용자에게 어떤 카드가 발견되었는지를 보여주고 싶은 경우와 같이) 사용자에게 PCI 슬롯 네임을 보여줄 때에는 pci_name(pci_dev) 를 사용하기 바란다.

PCI 장치를 참조할 경우에는 항상 struct pci_dev 의 포인터를 이용하라. 모든 PCI 레이어 함수들은 이 정보를 사용하며, 이것이 오직 올바른 것이다. (semantics 가 상당히 복잡한 multiple primary bus 를 사용하는 시스템에서와 같이) 아주 특별한 경우가 아니라면 bus/slot/function 번호를 사용하지 말자.

만약 PCI bus mastering DMA 를 사용하고자 할 경우에는, Documentation/DMA-mapping.txt 를 읽어보기 바란다.

당신이 사용하는 장치에서 Fast Back to Back write 를 켜려고 (turn on) 시도하지 마라. 버스상의 모든 장치들이 이를 지원해야 하므로, 이것은 플랫폼과 일반적인 코드 (generic code) 로 처리할 필요가 있는 것이지 하나의 드라이버 차원에서 다루어 져서는 안된다.

9 사용되지 않는 함수들 


오래된 드라이버를 새로운 PCI 인터페이스로 포팅하려고 할 때 주의해야 할 몇가지 함수들이 있따. 이들은 hotplug 나 PCI domain 이나 locking 에 호환되지 않기 때문에 더이상 커널에 존재하지 않는 것들이다.

pcibios_present(), pci_present() 예전부터, PCI subsystem 과 통신하려고 할 때 그것이 존재하는지 테스트할 필요가 없었다. 만약 존재하지 않는다면 PCI 장치의 리스트는 비어 있을 것이고 장치를 검색하는 모든 함수들은 단지 NULL 을 리턴할 것이다.
pcibios_(read|write)_* 대응되는 pci_(read|write)_* 함수들로 대체되었다.
pcibios_find_* 대응되는 pci_find_* 함수들로 대체되었다.
pci_for_each_dev() pci_find_device() 로 대체되었다.
pci_for_each_dev_reverse() pci_find_device_reverse() 로 대체되었다.
pci_for_each_bus() pci_find_next_bus() 로 대체되었다.
pci_find_device() pci_get_device() 로 대체되었다.
pci_find_subsys() pci_get_subsys() 로 대체되었다.
pcibios_find_class() pci_find_class() 로 대체되었다.
pci_(read|write)_*_nodev() pci_bus_(read|write)_*() 로 대체되었다.



 출처 : http://wiki.kldp.org/wiki.php
 

'Developer's Infos > Linux Programming' 카테고리의 다른 글

df 소스  (0) 2011.11.06
PCI 드라이버(PCI-Driver)  (0) 2011.10.12
add_wait_queue()/remove_wait_queue()/schedule() 설명 및 사용법  (0) 2011.10.10
__attribute__  (0) 2011.10.10
[Linux] asmlinkage  (0) 2011.09.30
Posted by 삼성동고양이