PCI 설정 공간과 인터럽트 처리 학술

PCI는 메모리와 I/O 포트 주소 공간에 대응하는 2개의 분리된 주소 공간을 가지고 있습니다. 그리고 3번째 주소 공간으로, PCI 설정 공간이 존재합니다. 이 주소 공간은 BDF라 불리는 8비트 PCI 버스, 5비트 장치, 3비트 함수 번호의 조합으로 접근할 수 있습니다. 그러니 최대 256개의 버스마다 최대 32개의 장치, 그리고 각각 최대 8개의 함수를 연결할 수 있는 것이지요. PCI 호환 카드는 무조건 0번 함수를 구현해야 인식이 됩니다.

설정 공간은 256바이트로 구성되어 있는데, PCI-X 2.0과 PCI Express의 경우에는 4096 바이트까지 확장되었습니다. 소프트웨어는 이 설정 공간을 통해 장치가 얼마만큼의 메모리나 I/O 주소 공간이 필요한지 알아낼 수 있습니다. 각 장치는 최대 6개까지 메모리 공간 또는 I/O 포트 공간을 요청할 수 있습니다.

PCI 설정 공간 헤더는 아래와 같이 생겼습니다.
여기서 벤더 ID가 있고 또 서브시스템 벤더 ID가 있는데, 벤더 ID는 칩 제조사의 것이고, 서브시스템 벤더 ID는 카드 제조사의 것입니다. 장치 ID도 마찬가지로 구분되어 있습니다.

일반적으로 펌웨나 운영체제는 시작하는 시점에 모든 PCI 버스를 돌아가면서 어떤 장치가 있는지, 각 장치는 어떤 시스템 리소스 (메모리 공간, I/O 공간, 인터럽트 라인 등)를 필요로 하는지 조사합니다. 그런 다음 리소스를 할당하고 각 장치에게 할당 정보를 알려줍니다. PCI 설정 공간은 장치 유형 정보를 포함하고 있는데, 운영체제는 이를 이용하여 디바이스 드라이버를 선택하는데 필요한 도움을 받을 수 있습니다. 매번 시스템 부팅할 때마다 물려있는 장치를 조사하는 방법으로 플러그 앤 플레이를 구현한 것입니다.

각 장치는 인터럽트 라인을 공유할 수 있으므로 정해진 프로토콜을 따라야 합니다. APIC (Advanced Programmable Interrupt Controller)를 통합한 최근의 x86 시스템은 APIC 하나에 물리적으로 최대 255개까지 하드웨어 IRQ를 사용할 수 있다고 하지만, 그 이전에는 16개의 IRQ가 일반적으로 사용되었습니다. 여기서 이미 용도가 정해진 IRQ를 이것저것 빼고 나면 실제로 쓸 수 있는 IRQ는 4개 밖에 안 남습니다.

예전에는 아래와 같았는데 요새는 어떨지 다시 찾아봐야겠네요. LPT나 COM, PS/2 포트 다 없어지고 있으니.

마스터 PIC
IRQ 0 - 시스템 타이머
IRQ 1 - 키보드
IRQ 2 - IRQ 8~15에서 캐스캐이드 된 신호.
IRQ 3 - COM2, COM4 시리얼 포트
IRQ 4 - COM1, COM3 시리얼 포트
IRQ 5 - LPT2 또는 사운드 카드
IRQ 6 - 플로피
IRQ 7 - LPT1

슬레이브 PIC
IRQ 8 - 실시간 클럭 (RTC)
IRQ 9 - 여분
IRQ 10 - 여분
IRQ 11 - 여분
IRQ 12 - PS/2 커넥터 마우스
IRQ 13 - ISA
IRQ 14 - Primary IDE
IRQ 15 - Secondary IDE

하여튼 4개 남는다고 치면, 실제 연결하는 장치는 이보다 많을 수 있는데 어쩔 수 없이 같은 인터럽트 라인을 공유하게 됩니다. 그런데 대부분 장치가 인터럽트 라인으로 INT A 하나만을 사용할테니까, A에 다 몰리는 것을 막기 위해 PCI 버스와 슬롯 사이를 연결할 때 인터럽트 라인을 돌려가면서 물립니다. 가령 첫번째 슬롯에 인터럽트 라인이 (a b c d)로 물려있었다면, 두번째 슬롯에는 인터럽트 라인이 (b c d a)로 물려있는 것이죠. 그러면 가급적 특정 인터럽트 라인만 붐비지 않도록 적당히 분배할 수 있게 됩니다.

문제는 이렇게 해서 잘 나눠도 인터럽트 라인을 공유할 수 밖에 없다는 것이고, 그러면 인터럽트가 발생했을 때 어떤 장치가 발생시킨 것인지 알 수가 없게 됩니다. 이러면 짤없이 특정 IRQ에 달려 있는 IRQ Handler를 순서대로 호출합니다. 각 IRQ Handler는 자기 장치가 인터럽트를 낸 것인지 확인합니다. 이렇게 IRQ를 공유해서 쓰려면 리눅스에서는 request_irq()를 쓰면서 옵션을 주어야 한다는군요.

같은 인터럽트 라인을 여러 장치가 공유하는 상황에서 프로그래밍 인터페이스가 잘못 설계된 장치의 경우에는 자기가 인터럽트를 보냈는지 안 보냈는지도 모르는 경우가 있습니다. 이런 장치들은 공유했다간 동작이 완전 이상해지겠지요. ISA 카드 같은 경우 싸게 대충 구현하다보니 이런 문제가 많다고 합니다.

물리적으로 인터럽트 라인을 확장하는 방법도 있지만 PCI Express 같은 경우 MSI (Message-signaled Interrupt)를 사용할 수 있습니다. 이것은 물리적으로 인터럽트 라인을 쓰지 않고 버스를 통해서 특정 비트 패턴의 메시지를 보내는 것으로 인터럽트를 거는 방법입니다. 원래 인터럽트 트리거 방식은 유실될 위험 때문에 레벨 트리거를 쓰는데, MSI 같은 경우 사실상 엣지 트리거 같은 방법으로 다루게 됩니다. 여러 개 인터럽트가 오면 하나로 합쳐버릴 수 있습니다. MSI에 사용되는 벡터 또한 공유될 수 있습니다.

트랙백

이 글과 관련된 글 쓰기 (트랙백 보내기)
TrackbackURL : http://xeraph.com/tb/4451329 [도움말]

핑백

  • Xeraph beyond the Great Firewall : 2008년 회고록 (작성 중) 2008-12-18 01:42:46 #

    ... 도 Copper, Fiber, SerDes 따라 제각기 다르구요. NDIS도 직접 구현하면서 이제 하나씩 감이 잡히고 있습니다. 지금까지 정리했던 글들을 아래 붙입니다. PCI 설정 공간과 인터럽트 처리Relaxed OrderingSnoop Not RequiredNDIS 인터럽트 흐름SerDes : Serializer/DeserializerGBIC : Gigabi ... more

덧글

댓글 입력 영역