PCI 소개
PCI bus경우에는 data 전송을 266MB/sec까지 낼 수 있다. PCI Bus는 새로운 Standard로 alpha및 intel에서도 동작한다.
PCI는 다음과 같은 3개의 다른 bus system을 가진다.
Processor / Memory Bus
PCI Bus
Expansion Bus
<CPU>
|
-------------------------------------------------------[PCI Bus 0]
| | |
<PCI-ISA Bridge> <Video> <PCI-PCI Bridge>
| |
--------------- ------------------------[PCI Bus 1]
| | |
<Super I/O Controller> <SCSI> <Ethernet>
다음으로 명심해서 보아야 할 부분은 PCI Configuration memory space이다.
이곳에는 우리가 원하는 PCI device specific한 정보를 담을 수 있다.
여기서 우리는 우리가 만드는 driver가 제대로 맞는 device를 찾을 수 있도록 하는 정보와 Device의 register의 base address와 같은 driver의 I/O가 필요로 하는 address를 찾게 된다.
- struct pci_config_addr_space {
-
unsigned short device_id;
-
unsigned short vendor_id;
-
-
unsigned short status;
-
unsigned short command;
-
-
char class_code[3];
-
char rev;
-
-
char bist;
-
char hdr;
-
char latency;
-
char cache;
-
unsigned int base_addr_0;
-
unsigned int base_addr_1;
-
unsigned int base_addr_2;
-
unsigned int base_addr_3;
-
unsigned int base_addr_4;
-
unsigned int base_addr_5;
-
unsigned int cardbus_cis_pointer;
-
unsigned short subsystem_id;
-
unsigned short subsys_vendor_id;
-
-
unsigned int expansion_bios_rom_addr;
-
unsigned int reserved[2];
-
char maxlat;
-
char mingnt;
-
char int_pin;
-
char int_line;
- };
[PCI Device의 configuration address space]
Vendor Identification : PCI장치의 고유 제작자를 나타내는 고유번호이다. 이것은 Windows NT같은 곳에서 Device에 맞는 드라이버를 설정하는데 필요한 정보를 제공한다. 즉, 이것과 함께 장치 식별자를 묶어서 하나의 ID를 만들어 이것이 올바른 Driver를 선택하는데 필요한 정보를 제공하게 되는 것이다.
Device Identification : 장치 자체를 나타내는 고유번호이다. 이것은 위에서 살펴본 것과 같은 형태로 사용된다. 리눅스에서는 device의 configuration을 읽어서 driver가 사용하고자 하는 device가 있는지를 확인하는 데 사용하게 된다.
Status : 장치의 상태를 나타낸다.
Class Code : 장치의 유형을 나타낸다.
Base Address Register : PCI Device의 register들에 대한 기본적인 주소를 표시한다. 즉, 이것과 offset을 더해서 사용하고자 하는 register의 주소를 알 수 있게 된다.
Interrupt Pin : PCI card로부터 PCI Bus로 interrupt를 전송하는 역할을 한다.
Interrupt Line : 인터럽트 핸들러가 PCI장치로부터 온 Interrupt를 Linux에 있는 올바른 디바이스 드라이버의 인터럽트 처리 코드로 인터럽트를 전달할 수 있도록 한다.
이러한 PCI configuration space에 대한 접근은 운영체제가 정의하고 있는 BIOS function에 의해 접근가능하다. 이 부분을 읽어들여 device driver에서 사용하고자 하는 정보들을 얻어야 한다. 이것은 device driver의 load초기에 해주어야 할 일 중의 일부이다.
DMA를 할 수 있는 PCI Card는 Bus Master이다. 이것은 자기자신만의 control register를 가지고 있으며, DMA같은 데이터의 전송을 책임진다.
DMA transfer에는 반드시 두개의 PCI Device가 있게 되며, 하나는 Initiator가 되고 하나는 Target이 된다.
Initiator가 되는 PCI Device는 Bus master가 되는 것이다.
Target이 되는 PCI Device는 Initiator에 의해 address가 되며, Initiator에 의해 요구된 data의 주고받기를 하게 된다.
PCI Bridge
PCI Bridge는 Bus와 Bus간을 연결하는 역할을 한다. 이러한 역할을 하는 것으로는 PCI-ISA Bridge, PCI-PCI Bridge가 있다. 이러한 bridge를 넘어갈 경우에는 device의 주소공간을 mapping하는 절차가 필요하게 된다.
PCI에서 사용하게 될 기본 함수들
이상에서 우리는 우리가 작성하고자 하는 device driver가 사용하게 될 주소에 대해 알게 되었다. 즉, 기본 주소(base address)가 결정되면, 그것을 기준으로 해서 device의 register에 대한 접근이 가능하게 됨을 의미한다. 방법은 기본주소 더하기 offset이 될 것이다. 이렇게 결정된 register에 대한 operation을 통해 데이터의 전송을 control하게 되는 것이다.
-커널 버전 2.4에서는 이러한 방법을 사용하지 않고 있다. device driver에서 PCI device의 table에 들어갈 항목을 정의해서 이를 pci_driver구조체로 두고 이를 pci_register_driver()함수를 이용해서 등록하는 방법을 사용한다.
#include <linux/config.h> - CONFIG_PCI : 이 macro는 PCI와 관련된 code부분을 조건적으로 compile하게 될 때 사용한다.
#include <linux/pci.h> - PCI register들에 대한 이름과 여러개의 vendor와 deviceID를 나타내는 상수값들이 정의되어 있다. 이러한 값들을 이용해서 PCI function을 사용하게 된다. 물론 새로이 만들려는 device의 경우에는 vendor와 deviceID가 없으므로 단순히 program내에서 정의해서 사용하면 될 것이다.
#include <linux/bios32.h> - 모든 pcibios_function들의 정의가 나와 있다. 하지만 이것은 현재의 kernle_version에서는 include해서 compile할경우에는 warning message를 화면에 뿌리게 될 것이다. kernel documentation을 읽어보면 새로운 PCI bios function을 쓰는 것을 권장하고 있다. 이 헤더 파일에 정의된 함수들을 살펴보도록 하자.
int pcibios_find_devices(unsigned short vendor , unsigned short id , unsigned short index,unsigned char* bus , unsigned char* function );
int pcibios_find_class(unsigned int class_node , unsigned short index , unsigned char* bus , unsigned char* function );
위 함수들은 성공했을 경우 pointer로 넘어간 변수에 값이 할당되어 돌아온다 다음으로 살펴볼 것은 PCI device configuration register를 읽고 쓰는데 사용된다.
pcibios_read_config_byte( unsigned char bus , unsigned char function , unsigned char where , unsigned char* ptr);
pcibios_read_config_word( unsigned char bus , unsigned char function , unsigned char where , unsigned char* ptr );
pcibios_read_config_dword(unsigned char bus , unsigned char function unsigned char where , unsigned char* ptr );
pcibios_write_config_byte(unsigned char bus , unsigned char function , unsigned char where , unsigned char val);
pcibios_write_config_word( unsigned char bus , unsigned char funcion , unsigned char where , unsigned char val );
pcibios_write_config_dword(unsigned char bus , unsigned char function , unsigned char where , unsigned char val );
여기서 한가지 유념할 것은 PCI bus는 little endian이라는 것이다. 따라서 multi byte로 된 값을 접근할 때는 byte ordering에 신경을 써야 한다.
그럼 위에서 보인 PCI BIOS Function을 이용해서 PCI slot에 있는 device를 검출하는 routine은 다음과 같이 작성될 수 있다. 이 코드는 ~/drivers/net/eepro100.c에서 PCI를 쓰는 card에 대한 검출에서 가져온 것이다. 이 코드는 커널 버전 2.2.X에 해당한다.
- if( !pcibios_present() ) //PCI BIOS존재 여부 확인
-
return cards_found;
- /*만약 카드가 있다면 이하의 부분을 수행하게 되며, pci_tbl[]배열을 검색해서 해당하는 카드를 찾는다. */
- for( chip_idx = 0 ; pci_tbl[chip_idx].name;chip_idx++){
-
for(;pci_tbl[chip_idx].pci_index < 8 ; pci_tbl(chip_idx].pci_index++) {
-
unsigned char pci_bus , pci_device_fn , pci_latency;
-
unsigned long pciaddr;
-
long ioaddr;
-
int irq;
-
u16 pci_command , new_command;
-
/*pcibios_find_device()함수는 하드웨어 vendor id(vendor_id), device id(device_id), 그리고 pci 디바이스의 index값과 찾은 디바이스의 PCI BUS(pci_bus)및 PCI 디바이스 기능(function)을 알려주는 필드를 파라미터로 받는다. 돌 려주는 값은 해당 카드를 찾았을 경우에는 0이 아닌 값이 될 것이며, pci_bus와 pci_device_fn을 설정할 것이다.
-
*/
-
if(pcibios_find_device(pci_tbl[chip_idx].vendor_id,
-
pci_tbl[chip_idx].device_id,
-
pci_tbl[chip_idx].pci_index , &pci_bus ,
-
&pci_device_fn))
- break;
- {
- /*찾은 카드에 대해 slot번호를 구한다.*/
- pdev = pci_find_slot(pci_bus , pci_device_fn)
- #ifdef USE_ID
- /*이젠 pdev값으로 pci_base_address()를 호출해서 해당 디바이스가
- 사용하는 기본 주소(base address)를 구해온다. 이 값은
- I/O mapped I/O를 사용하는지, 아니면 memory-mapped I/O를 사용하는지에
- 따라서 pci_base_address()에 넘겨주는 값에 따라서 달라질 것이다.*/
- pciaddr = pci_base_address(pdev , 1); /*use [0] to mem-map */
- #else
- pciaddr = pci_base_address(pdev , 0);
- #endif
- irq = pdev->irq; //irq값은 보관하자
- }
[PCI 카드의 검출]
가장 먼저 PCI BIOS의 존재 여부를 묻는 pcibios_present()함수를 호출한다.
아무것도 없다면, 현재 검색된 카드의 수를 나타내는 card_found(=0)를 돌려준다. 만약 카드가 있다면 이하의 부분을 수행하게 되며, pci_tbl[]배열을 검색해서 해당하는 카드를 찾는다.
pcibios_find_device()함수는 하드웨어 vendor id(vendor_id), device id(device_id), 그리고 pci 디바이스의 index값과 찾은 디바이스의 PCI BUS(pci_bus)및 PCI 디바이스 기능(function)을 알려주는 필드를 파라미터로 받는다. 돌려주는 값은 해당 카드를 찾았을 경우에는 0이 아닌 값이 될 것이며, pci_bus와 pci_device_fn을 설정할 것이다.
이젠 찾은 카드에 대해 slot번호를 구한다(pci_find_slot()). 넘겨주는 값은 앞에서 찾은 pci_bus와 pci_device_fn 값이다. 이것을 이용해서 pdev를 알게되며, 이때 설정된 인터럽트 번호도 알게된다.
이젠 pdev값으로 pci_base_address()를 호출해서 해당 디바이스가 사용하는 기본 주소(base address)를 구해온다. 이 값은 I/O mapped I/O를 사용하는지, 아니면 memory-mapped I/O를 사용하는지에 따라서 pci_base_address()에 넘겨주는 값에 따라서 달라질 것이다.
해당 디바이스에 할당된 IRQ는 나중에 인터럽트 핸들러를 설치하고자 할 때 사용할 것이므로 보관하도록 하자(irq).
- /* Remove I/O space marker in bit 0. */
- if (pciaddr & 1) {
- ioaddr = pciaddr & ~3UL;
- if (check_region(ioaddr, 32))
- continue;
- }
- else {
- #ifdef __sparc__
- /* ioremap is hosed in 2.2.x on Sparc. */
ioaddr = pciaddr & ~0xfUL; - #else
if ((ioaddr = (long)ioremap(pciaddr & ~0xfUL, 0x1000)) == 0) {
printk(KERN_INFO "Failed to map PCI address %#lx.₩n",pciaddr); - continue;
- }
- #endif
- }
[PCI 카드의 검출 - continued]
얻어온 기본 주소에서 bit 0에 값이 있는지를 확인해서, LSB 2bit을 지우고, 이 값으로 ioaddr을 설정해서 올바른 영역을 나타내는지 확인한다 (check_region()). 만약 bit 0에 아무 값도 가지지 않는다면, __sparc__ 정의가 있는 경우에는 구해온 주소에서 완전히 한 byte를 지워주고, __sparc__정의가 없다면 ioremap()함수에 pciaddr에서 한 byte를 지운값과 페이지 크기(0x1000)만큼을 넘겨서, 새로운 PCI 기본 주소(base address)에 대한 메모리 맵핑을 한다.
- /* Get and check the bus-master and latency values. */
- pcibios_read_config_word(pci_bus, pci_device_fn,PCI_COMMAND, &pci_command);
- new_command= pci_command | PCI_COMMAND_MASTER|PCI_COMMAND_IO;
- if (pci_command != new_command) {
- printk(KERN_INFO " The PCI BIOS has not enabled this"" device! Updating PCI command %4.4x->%4.4x.₩n",pci_command, new_command);
- pcibios_write_config_word(pci_bus, pci_device_fn,PCI_COMMAND, new_command);
- }
- pcibios_read_config_byte(pci_bus, pci_device_fn,PCI_LATENCY_TIMER, &pci_latency);
- if (pci_latency < 32) {
- printk(" PCI latency timer (CFLT) is unreasonably low at %d."" Setting to 32 clocks.₩n", pci_latency);
- pcibios_write_config_byte(pci_bus, pci_device_fn,PCI_LATENCY_TIMER, 32);
- } else if (speedo_debug > 1)
- printk(" PCI latency timer (CFLT) is %#x.₩n", pci_latency);
- /* 하드웨어에 의존적인 카드 찾기 */
- }
- }
[PCI 카드의 검출 - continued]
이젠 기타 등등의 설정이 남았다.
직접적으로 PCI 디바이스의 configuration 메모리 영역에 대해서 읽기와 쓰기를 한다. 해주는 일은 BUS master와 latency 설정이다(pci_write_config_word()).
이것을 끝내면 PCI 디바이스를 사용하기 위한 기본적인 설정이 끝났다. 물론 이것은 디바이스에 의존적이며, BUS master로 동작하지 않는 경우에는 해줄 필요가 없다.
리눅스 커널 버전 2.4.X에서는 이러한 방법을 좀더 자동적으로 해주려는 노력이 엿보인다. 가령, pci_driver구조체를 정의해서 위에서 해주었던 일과 비슷한 일을 하는 것을 등록시키고, 커널에서 직접적으로 찾는 일을 해주도록 만들고 있다. 가령 예를 들어서 커널 버전 2.4.0의 ~/drivers/net/eepro100.c를 보면 아래와 같은 것을 찾을 수 있을 것이다.
- static struct pci_device_id eepro100_pci_tbl[] __devinitdata = {
-
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82557, PCI_ANY_ID, PCI_ANY_ID, },
-
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82559ER,PCI_ANY_ID, PCI_ANY_ID, },
-
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ID1029,PCI_ANY_ID, PCI_ANY_ID, },
-
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ID1030,PCI_ANY_ID, PCI_ANY_ID, },
-
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82820FW_4,PCI_ANY_ID, PCI_ANY_ID, },
-
{ 0,}
- };
- MODULE_DEVICE_TABLE(pci, eepro100_pci_tbl);
static struct pci_driver eepro100_driver = {
name: "eepro100",
id_table: eepro100_pci_tbl,
probe: eepro100_init_one,
remove: eepro100_remove_one,
#ifdef CONFIG_EEPRO100_PM
suspend: eepro100_suspend,
resume: eepro100_resume,
#endif
};
[eepro100.c에서 PCI 드라이버 등록하기]
즉, 등록하려는 PCI 디바이스가 가지는 vendor ID와 device ID등을 정의한 pci_device_id를 만들고, 이를 pci_driver구조체의 id_table에 놓는다.
테이블에 들어갈 디바아스의 name은 eepro100을, 검침(probe) 함수는 eepro100_init_one, 제거(remove) 함수는 eepro100_remove_one을 둔다. 만약 전원 관리(power management)까지 하고 싶다면, suspend에 eepro100_suspend를 resume에는 eepro100_resume을 선언한다.
이렇게 하고 아래와 같이 호출한다(여기서 선언된 부분은 __devinitdata의 섹션을 차지하게 된다. 즉, 초기화시에 쓰일 데이터에 PCI ID들의 테이블 선언이 들어간다.)
- #if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,48)
- static int pci_module_init(struct pci_driver *pdev){
-
int rc;
-
rc = pci_register_driver(pdev);
-
if (rc <= 0) {
-
printk(KERN_INFO "%s: No cards found, driver not installed.\n",pdev->name);
-
pci_unregister_driver(pdev);
-
return -ENODEV;
-
}
-
return 0;
- }
- #endif
- static int __init eepro100_init_module(void){
-
if (debug >= 0 && speedo_debug != debug)
-
printk(KERN_INFO "eepro100.c: Debug level is %d.\n", debug);
-
if (debug >= 0)
-
speedo_debug = debug;
-
return pci_module_init(&eepro100_driver);
- }
- static void __exit eepro100_cleanup_module(void){
-
pci_unregister_driver(&eepro100_driver);
- }
- module_init(eepro100_init_module);
- module_exit(eepro100_cleanup_module);
[PCI 드라이버의 등록과 해지 예(eepro100.c)]
모듈에 대해서는 아직 보지 않았지만, module_init()와 module_exit()가 모듈의 적재와 해지 함수를 표시한다.
적재는 eepro100._init_module()이, 해지는 eepro100_cleanup_module()이 해준다. 다시 eepro100_init_module()함수를 보면 pci_module_init()를 호출 하는 부분을 찾을 수 있을 것이다. 이 함수에서 PCI 디바이스 드라이버로 등록이 일어나게 되며, 해당하는 함수가 pci_register_driver() 라는 것을 알수 있을 것이다. 따라서, 우린 pci_driver 구조체를 만드는 것에만 전념하면 된다. 나머지는 커널이 해줄 일이다.
따라서, 위에서 정의한 pci_driver 구조체의 probe()함수가 디바이스 드라이버가 사용하게 되는 디바이스를 찾는 함수가 될 것이다. eepro100_init_one() 함수가 해당될 것이다. 여기서 이 코드를 다 보기 보다는 이 함수가 하는 일만 간단히 요약한다면,
1. 먼저 PCI 디바이스를 위한 메모리 영역을 할당 받고,이를 예약한다(request_region(), request_mem_region()),
2. 이젠 PCI 드라이버가 이 영역을 사용한다고나타내주고(pci_resource_start()),
3. 만약 주소 공간의 remaping이 필요하다면 ioremap()함수를 호출한다.
4. 전원 관리를 지원하는지 확인하고(pci_find_capability()),
5. PCI 디바이스를 사용가능하게 만든다 (pci_enable_device()).
6. 마지막으로 함수가 복귀하기 전에 pci_set_master()을 호출해서 PCI master로 만든다.
보면 알수 있듯이 2.2.X버전의 커널보다 2.4.X버전의 커널은 디바이스 드라이버의 구현에서 좀더 디바이스 드라이버의 본연의 임부만을 강조하도록 바뀌었음을 확인할 수 있다.
PCI를 bus를 나타내는 구조체
~/include/linux/pci.h를 참조하라
- struct pci_bus {
-
struct list_head node; /* 버스 리스트의 노드 */
-
struct pci_bus *parent; /* PCI 버스가 맞물린 parent버스 */
-
struct list_head children; /* 버스의 child 버스의 리스트 */
-
struct list_head devices; /* 이 버스에 맞물린 디바이스의 리스트 */
-
struct pci_dev *self; /* 부모에게 보여지는 PCI bridge 디바이스 */
-
struct resource *resource[4]; /* 이 버스로 보내지게 되도록 설정된 주소공간 */
-
struct pci_ops *ops; /* 제어 접근 함수들에 대한 포인터 */
-
void *sysdata; /* 특정 시스템에 대한 데이터 저장공간 */
-
struct proc_dir_entry *procdir; /* /proc파일 시스템에 대한 entry */
-
unsigned char number; /* 버스의 번호 */
- unsigned char primary; /* 주(primary) bridge의 수 */
-
unsigned char secondary; /* 이차(secondary) bridge의 수 */
-
unsigned char subordinate; /* 최대 하위 버스의 수 */
-
char name[48]; /* 디바이스의 이름 */
-
unsigned short vendor; /* 디바이스의 공급자(vendor)명 */
-
unsigned short device; /* 디바이스 ID */
-
unsigned int serial; /* 시리얼 번호 */
-
unsigned char pnpver; /* 플러그 & 플래이 버전 번호 */
-
unsigned char productver; /* 생산 버전 번호 */
-
unsigned char checksum; /* checksum이 있는지를 표시 0 - checksum passed */
-
unsigned char pad1; /* Padding */
- };
[PCI Bus 구조체의 정의]
PCI 버스 구조체의 정의 중에서 pci_ops는 다시 아래와 같이 정의된다. 이것은 특정 시스템에 의존적인 연산이다.
- struct pci_ops {
-
int (*read_byte)(struct pci_dev *, int where, u8 *val);
-
int (*read_word)(struct pci_dev *, int where, u16 *val);
-
int (*read_dword)(struct pci_dev *, int where, u32 *val);
-
int (*write_byte)(struct pci_dev *, int where, u8 val);
-
int (*write_word)(struct pci_dev *, int where, u16 val);
-
int (*write_dword)(struct pci_dev *, int where, u32 val);
- };
이 함수들은 해당하는 PCI Device로부터 byte,word,doubleword를 읽거나 쓰기 위한 연산이다. 주로 configuration주소 공간에 대한 read나 write연산에 사용한다.
PCI 버스상의 한 디바이스는 PCI Device구조체로 나타내지며, 아래와 같이 정의된다.
- struct pci_dev {
- struct list_head global_list; /* 모든 PCI 디바이스에서의 리스트 */
- struct list_head bus_list; /* 특정 PCI bus에서의 리스트 */
- struct pci_bus *bus; /* 디바이스가 맞물린 버스의 포인터 */
- struct pci_bus *subordinate; /* 이 디바이스가 bridge로서의 역할을 할때 하위의 버스*/
- void *sysdata; /* 특정 시스템을 위한 데이터 포인터 */
- struct proc_dir_entry *procent; /* /proc 파일 시스템의 entry */
- unsigned int devfn; /* 기호화된(encoded) 디바이스나 function의 인덱스 값 */
- unsigned short vendor; /* 제품 공급자 명 */
- unsigned short device; /* 디바이스 ID */
- unsigned short subsystem_vendor; /* Subvendor 명 */
- unsigned short subsystem_device; /* Subdevice ID */
- unsigned int class; /* 디바이스 클래스 : 3 bytes(base,sub,prog-if) */
- u8 hdr_type; /* PCI헤더 타입 */
- u8 rom_base_reg; /* Config 레지스터의 base주소 */
- struct pci_driver *driver; /* 드라이버를 가르키는 포인터 */
- void *driver_data; /* 드라이버에서 사용할 데이터에 대한 포인터 */
- dma_addr_t dma_mask; /* 버스 주소에 대한 bit mask */
- /* 다음의 공급자와 디바이스 ID에 대해서 호환성을 가진다. */
- unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE];
- unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE];
- unsigned int irq; /* 할당된 interrupt번호 */
- struct resource resource[DEVICE_COUNT_RESOURCE]; /* I/O와 확장(expansion) ROM을 위한 메모리
- 공간*/
- struct resource dma_resource[DEVICE_COUNT_DMA]; /* DMA(Direct Memory Access)를 위한 메모리
- 공간 */
- struct resource irq_resource[DEVICE_COUNT_IRQ]; /* 디바이스의 Interrupt를 위한 메모리 공간 */
- char name[80]; /* 디바이스의 이름 */
- char slot_name[8]; /* 설치된 slot의 이름 */
- int active; /* ISAPnP: 디바이스가 사용중이다. */
- int ro; /* ISAPnP: 디바이스가 Read Only로 동작한다. */
- unsigned short regs; /* ISAPnP: 제공되는 레지스터들 */
- int (*prepare)(struct pci_dev *dev); /* ISAPnP를 위한 함수 포인터들 */
- int (*activate)(struct pci_dev *dev);
- int (*deactivate)(struct pci_dev *dev);
- };
[PCI Device 구조체의 정의]
이젠 PCI 버스와 디바이스의 구조체를 보았으니 이를 사용하기 위한 드라이버 구조체를 볼 시간이다.
PCI Driver 구조체는 아래와 같이 정의된다.
- struct pci_driver {
- struct list_head node; /* PCI driver 리스트 */
- char *name; /* driver의 이름 */
- const struct pci_device_id *id_table; /* PCI 디바이스의 ID 테이블 */
- int (*probe)(struct pci_dev *dev, const struct pci_device_id *id); /* PCI 디바이스 probe 함수*/
- void (*remove)(struct pci_dev *dev); /* PCI 디바이스 제거 함수(NULL- not hot pluggable */
- void (*suspend)(struct pci_dev *dev); /* PCI 디바이스 suspend함수 : 잠시 멈춤 */
- void (*resume)(struct pci_dev *dev); /* PCI 디바이스 resume함수 : 계속 진행 */
- };
[PCI Driver 구조체의 정의]
PCI Bus는 시스템 상에 여러개가 존재할 수 있으며, 각각은 bridge로 연결된다. 또한 bus상에는 여러 PCI Device가 있을 수 있으며 각각은 해당하는 디바이스 드라이버를 가지고 있다.
pci_driver는 driver를 device와 관련짓기 위해 사용된다.
Driver를 표시하는 name field와
PCI device의 configuration 공간에 들어가 있는 ID들에 대한 table,
driver 자신이 제어할 수 있는 device를 찾는 probe routine에 대한 pointer와
remove, suspend, resume을 수행하는 routine에 대한 pointer들이 들어간다.
이러한 routine들은 나중에 PCI device에 대한 연산이 주어진 경우, kernel에서 해당하는 function을 불러주게 된다.
이중에서 probe routine을 살펴보면, 이는 새로운 PCI device 있는지를 검색하는데 사용되는데, 이 routine에서 해주어야 하는 일은 I/O를 위해서 사용될 resource에 대한 예약과 device가 사용할 memory공간에 대한 예약, 그리고, 할당된 IRQ를 알아내는 일이 있다.
또한 device가 Power management를 지원하는지를 알아보아야 하며, PCI master로서 동작하는지도 확인해야 한다.
PCI에 대한 초기화는 ~/init/main.c의 start_kernel()에서 do_basic_setup()함수내에서 CONFIG_PCI가 설정된 경우에 pci_init()를 호출하면서 시작된다. 아래와 같다.
- void __init pci_init(void)
- {
-
struct pci_dev *dev;
-
pcibios_init();
-
pci_for_each_dev(dev) {
-
pci_fixup_device(PCI_FIXUP_FINAL, dev);
-
}
- #ifdef CONFIG_PM
-
pm_register(PM_PCI_DEV, 0, pci_pm_callback);
- #endif
- }
[pci_init()함수]
여기서는 PCI bios에 대한 초기화와 각각의 PCI 디바이스에 대한 초기화가 이루어진다. 먼저 PCI bios에 대한 초기화부터 보도록 하자.
- void __init pcibios_init(void)
- {
-
struct pci_ops *bios = NULL;
-
struct pci_ops *dir = NULL;
- #ifdef CONFIG_PCI_BIOS
-
if ((pci_probe & PCI_PROBE_BIOS) && ((bios = pci_find_bios()))) {
-
pci_probe |= PCI_BIOS_SORT;
-
pci_bios_present = 1;
-
}
- #endif
- #ifdef CONFIG_PCI_DIRECT
-
if (pci_probe & (PCI_PROBE_CONF1 | PCI_PROBE_CONF2))
-
dir = pci_check_direct();
- #endif
-
if (dir)
-
pci_root_ops = dir;
-
else if (bios)
-
pci_root_ops = bios;
-
else {
-
printk("PCI: No PCI bus detected\n");
-
return;
-
}
-
printk("PCI: Probing PCI hardware\n");
-
pci_root_bus = pci_scan_bus(0, pci_root_ops, NULL);
-
pcibios_irq_init();
-
pcibios_fixup_peer_bridges();
-
pcibios_fixup_irqs();
-
pcibios_resource_survey();
- #ifdef CONFIG_PCI_BIOS
-
if ((pci_probe & PCI_BIOS_SORT) && !(pci_probe & PCI_NO_SORT))
-
pcibios_sort();
- #endif
- }
[PCI BIOS의 초기화 (pcibios_init()함수)]
pcibios_init()함수는 ~/arch/i386/pci-pc.c에 정의되어 있다.
이 함수가 하는 일은 pci_probe에 설정된 사항을 가지고, 해당하는 PCI버스에 대한 연산(pci_ops)를 초기화하고, PCI root bus(pci_root_bus)를 만드는 일이다.
만약 PCI가 디바이스에 대한 직접접근(direct access)가 가능하다면, dir연산으로 pci_root_ops를 초기화하고, PCI bios가 존재하고, 직접접근이 가능하지 않다면, bios로 pci_root_ops를 초기화 한다.
pci_scan_bus()함수는 모든 PCI버스에 대한 초기화를 담당하며, pcibios_irq_init()함수는 인터럽트에 대한 초기화를, pci_bios_fixup_peer_bridges()함수는 나머지 PCI bridge들에 대한 검색과 초기화를 담당하며, pcibios_fixup_irqs()함수는 현재 설정된 PCI 디바이스에 대한 인터럽트 번호를 재 설정하게되며,pcibios_resource_survey()함수는 커널 자원(resource)의 할당을 맡고 있다.
'리눅스' 카테고리의 다른 글
가상머신 없이 리눅스 우분투 설치 (0) | 2019.08.22 |
---|---|
wrapper 래퍼 (0) | 2010.09.20 |
vi, vim 명령어 (0) | 2010.05.31 |
네트워크 디바이스 드라이버2 (0) | 2010.05.18 |
Snull 디바이스 드라이버 (0) | 2010.05.18 |