본 글은 The Linux Kernel 을 정리한 것이며, 출처를 밝히지 않은 모든 이미지는 원글에 속한 것입니다.
PCI 주소 공간
- PCI(Peripheral Component Interconnect) 시스템에 있는 여러 주변장치들을 구조화하고, 연결하여 효율적으로 관리하는 방법을 정의하는 표준
- 위 그림에서 CPU는 PCI 버스 0번에 연결되어 있고, PCI-PCI 브릿지는 PCI 버스 0번(1차 버스, primary)와 PCI 버스 1번(2차 버스, secondary)를 연결. 여기서 PCI-PCI 브릿지에서 CPU에 가깝게 연결된 버스를 upstream 이라 하며, CPU에 멀게 연결된 버스를 downstream 이라고 함. PCI-ISA 브릿지는 오래 전부터 사용되어온 ISA 장치(키보드, 마우스, 플로피 디스크를 제어하는 슈퍼 I/O 컨트롤러 칩 등)를 연결하기 위한 ISA 버스와 PCI 버스를 연결하는 장치
- 디바이스 드라이버(커널 모듈)는 메인 보드에 있는 PCI 카드를 제어하고 PCI 장치와 서로 정보를 교환하기 위해 공유 메모리를 사용. 이 메모리에는 장치의 컨트롤러가 사용하는 제어 레지스터(control register)와 상태 레지스터(status register)가 존재
- 예를 들어, PCI SCSI 디바이스 드라이버는 SCSI 디스크로 데이터를 쓰려고 할 때, 장치의 상태 레지스터를 읽어와 쓰기 작업이 가능한지 파악하며, 제어 레지스터에 값을 써서 장치를 동작시킴
- 주변장치들은 각자 자신만의 메모리 공간(I/O 공간)을 가지며, CPU는 이 영역에 자유롭게 접근할 수 있으나, 장치가 시스템 메모리 공간에 접근하는 것은 직접 메모리 접근(Direct Memory Access, DMA) 채널을 이용해야 함
- ISA 장치는 ISA I/O와 ISA 메모리를 가짐
- PCI 장치는 PCI I/O, PCI 메모리, PCI 설정 공간(configuration space)를 가짐
- 디바이스 드라이버는 PCI I/O와 PCI 메모리 주소공간을 사용하며, PCI 설정 공간의 경우 커널의 PCI 초기화 코드에서 사용
- 장치들이 사용하는 메모리 공간은 가상 주소 공간에서 일부를 PCI 주소 공간으로 매핑하는 희소 주소 매핑(sparse address mapping) 방법으로 생성됨
- PCI I/O와 PCI 메모리 주소
- 장치가 리눅스 커널에서 실행되는 디바이스 드라이버와 통신하기 위해 사용하는 메모리 공간
- PCI 시스템이 설정되고, PCI 설정 헤더에 있는 명령 항목에서 이들 주소 공간에 대한 접근을 허용할 때까지 아무도 이 공간에 접근할 수 없음.
- 장치는 자신의 내부 레지스터를 PCI I/O 공간에 매핑하며, 해당 디바이스 드라이버는 장치를 제어하기 위해 레지스터를 읽고 씀
- 비디오/오디오 드라이버는 데이터를 저장하기 위해 큰 용량의 PCI 메모리 공간을 사용
PCI 설정 헤더
- 모든 PCI 장치(PCI-PCI 브릿지 포함)는 PCI 설정 공간에서 PCI 설정 헤더(모든 헤더의 크기는 256바이트)를 가지는데, 이 자료구조는 시스템이 장치를 구별하고 제어하는데 사용되며, 헤더가 있는 정확한 위치는 PCI 배치도에서 장치가 위치한 곳에 따라 결정됨
- 메인 보드에 있는 여러 PCI 슬롯 중 하나에 꽂을 때, 어떤 슬롯에 꽂느냐에 따라 PCI 설정 공간에서 각기 다른 위치에 헤더가 위치
- PCI 설정 헤더에 있는 설정 레지스터와 상태 레지스터를 이용해 장치를 설정
- 첫번째 슬롯의 PCI 설정 헤더가 오프셋 0에 위치한다면, 두번째 슬롯의 헤더는 오프셋 256에 위치
- 시스템별로 PCI 설정 공간에 접근하는 하드웨어 메커니즘이 다르게 정의되어 있으며, 이에 따라 PCI 설정 코드는 주어진 PCI 버스에 있어서 가능한 모든 PCI 설정 헤더를 검사하여 장치의 유무를 파악
- 장치 식별자(Device Identification) 장치 자체를 나타내는 고유번호(고속 이더넷 장치의 경우 0x0009)
- 제작자 식별자(Vendor Identification) PCI 장치의 제작자를 나타내는 고유번호(인텔의 경우 0x8086)
- 상태(Status) 장치의 상태
- 명령(Command) 시스템은 이 항목에 값을 써서 장치의 PCI I/O 공간에 접근할 수 있고, 이를 이용해 장치를 제어
- 분류 코드(Class Code) 이 장치가 속한 장치의 유형을 구별
- 베이스 주소 레지스터(Base Address Register) PCI I/O, PCI 메모리 공간의 유형과 크기, 위치를 지정하는데 사용
- 인터럽트 핀(Interrupt Pin) PCI 장치가 사용하고 있는 인터럽트 핀
- PCI 카드에 있는 4개의 핀(표준으로 각각 A, B, C, D라고 명명)은 PCI 카드로부터 PCI 버스로 인터럽트를 전달
- 보통 특정 장치에 있어서 인터럽트 핀은 직접 배선되어 부팅 시 그 장치는 그 핀을 사용
- 인터럽트 라인(Interrupt Line) PCI 초기화 코드와 디바이스 드라이버, 리눅스의 이넡럽트 처리 서브시스템 사이에 인터럽트 핸들을 전달하기 위해 사용되며, 이 핸들러가 PCI 장치로부터 전달된 인터럽트를 리눅스 커널에 있는 올바른 디바이스 드라이버의 인터럽트 핸들러로 전달
PCI-ISA 브릿지
- PCI I/O와 PCI 메모리 공간으로의 접근을 ISA I/O와 ISA 메모리 공간으로의 접근으로 변환해주는 장치
- 오래전부터 사용해온 ISA 장치를 지원하는 역할
- PCI 규약에서는 PCI I/O와 PCI 메모리 공간에서 아래 영역을 ISA 시스템의 주변장치가 사용하도록 하며, 하나의 PCI-ISA 브릿지를 통해 PCI 메모리읮 주소로 접근하면 이 아래 영역의 주소로 변환
PCI-PCI 브릿지
- 하나의 PCI 버스가 지원가능한 PCI 장치 개수는 전기적인 제한이 있어, 더 많은 PCI 장치를 지원하려면 PCI-PCI 브릿지를 이용하여 PCI 버스를 추가
- PCI 설정 사이클(Configuration Cycle) PCI 초기화 전에 PCI 장치들을 설정하기 위해 사용하는 특별한 주소
- PCI 초기화 코드가 메인 PCI 버스(0번)에 있지 않는 장치들에 접근하려면 브릿지가 자신의 1차 PCI 버스에서 2차 PCI 버스로 설정 사이클을 넘길지 결정하는 메커니즘이 필요
- PCI 규약에서는 2가지 형식의 PCI 설정 주소를 정의
- 0번 타입 PCI 설정 사이클 PCI 버스 번호가 없으며, 모든 장치는 같은 PCI 버스에 대한 PCI 설정 주소로 해석
- 1번 타입 PCI 설정 사이클 PCI 버스 번호가 있으며, 이 주소는 PCI-PCI 브릿지만 사용
- 각 PCI-PCI 브릿지는 종속 버스(subordinate bus) 번호를 가지는데, 이 번호는 2차 PCI 버스 너머 다운스트림으로 연결된 모든 PCI 버스들 중 최대 버스 번호를 의미
- 모든 PCI-PCI 브릿지는 1번 타입 설정 사이클을 받으면, 다운스트림으로 보낼 지 결정
- 지정된 버스 번호가 [브릿지의 2차 버스 번호, 종속 버스 번호] 사이에 있지 않으면, 1차 버스의 것으로 간주
- 지정된 버스 번호가 2차 버스 번호와 일치하면 0번 타입 설정 명령으로 변환
- 지정된 버스 번호가 (브릿지의 2차 버스 번호, 종속 버스 번호] 사이에 있으면, 주소를 바꾸지 않고 2차 버스로 전달
- 예를 들어, 4개의 버스(0~3번)가 있고, 브릿지1은 0번과 1번, 브릿지2는 1번과 2번, 브릿지3은 1번과 3번 버스를 각각 연결한다고 가정. (2번과 3번 버스는 연결되어 있지 않은 상태) 이 때 3번 버스에 있는 장치A를 지정하려면
- CPU로부터 1번 타입 설정 사이클을 계산해서 0번 버스를 이용해 브릿지1로 전달
- 브릿지1은 이를 바꾸지 않고 1번 버스로 전달
- 브릿지2는 해당되지 않으므로 무시
- 브릿지3은 0번 타입 설정 사이클로 변환한 뒤 3번 버스로 전달 --> 장치 응답
- PCI-PCI 브릿지 너머에 있는 모든 PCI 버스의 번호는 브릿지의 2차 버스 번호와 종속 버스 번호 사이에 있어야 함. 만약 그렇지 않다면, 브릿지는 1번 타입 설정 사이클을 변환하지 못하거나 제대로 통과시키지 못하게 되고, 시스템은 PCI 장치를 찾지 못하거나 초기화할 수 없음
PCI 초기화
- PCI 디바이스 드라이버 0번 버스부터 PCI 시스템을 탐색하면서 시스템에 있는 브릿지 포함 모든 PCI 장치를 초기화
- 찾은 모든 브릿지에는 순서대로 번호가 부여되며, 해당 자료구조의 연결 리스트를 만들어서 시스템의 배치도를 저장
- 유사 디바이스 드라이버(pseudo device driver)로, 실제 디바이스 드라이버 프로그램이 아니라 초기화 과정에서 호출되는 리눅스 커널 내부의 한 함수
- PCI BIOS CPU가 모든 PCI 주소 공간을 제어할 수 있도록 함 (커널 코드와 디바이스 드라이버만 사용)
- BIOS 서비스가 없는 64 비트 프로세서의 경우 동일한 일을 하는 코드가 커널 내부에 존재
- 리눅스 커널 PCI 자료구조
- pci_dev 구조체 (브릿지 포함) 각 PCI 장치를 나타냄
- pci_bus 구조체 PCI 버스를 나타냄
- 최종 결과는 버스들의 트리 구조이며, 각 노드는 해당 버스에 연결된 여러 PCI 장치들을 가리킴
- PCI 버스는 PCI-PCI 브릿지를 통해서만 도달 가능 (첫번째 PCI 버스인 0번 버스는 제외). 따라서, 각 pci_bus 구조체는 접근할 때 거쳐야 하는 PCI-PCI 브릿지에 대한 포인터를 포함
- PCI-PCI 브릿지 구조체는 연결된 PCI 버스와 부모 PCI 버스의 자식 노드
- pci_devices 변수 시스템에 있는 모든 PCI 장치 리스트를 가리키는 포인터
- 시스템에 있는 모든 PCI 장치는 자신의 pci_dev 구조체를 이 리스트에 추가함
- PCI 초기화 코드 깊이 우선 탐색(Depth First Search, DFS)으로 구현되어 있음
- 시스템에 있는 모든 PCI 장치(브릿지 포함)들을 탐색하기 위해 PCI BIOS 코드를 이용하여 현재 조사중인 PCI 버스의 모든 가능한 슬롯 각각이 점유되었는지 파악
- PCI 슬롯이 점유되었으면, 그 장치를 기술하는 pci_dev 구조체를 생성해서 pci_devices 가 가리키는 PCI 장치 리스트에 추가
- 찾은 PCI 장치가 PCI-PCI 브릿지이면, pci_bus 구조체를 생성해서 브릿지에 대한 pci_dev 구조체를 참조하도록 하고, 브릿지에 연결된 다른 버스와도 연결
- 장치 분류 코드(0x060400 고정)로 브릿지의 유무를 파악
- 커널은 자신이 찾은 PCI-PCI 브릿지의 다운스트림 방향으로 PCI 버스를 설정, 깊이 우선 탐색이 끝나면 너비 우선 탐색으로 진행
- PCI-PCI 브릿지가 PCI I/O와 PCI 메모리 공간 및 PCI 설정 공간에 읽고 쓰려는 작업을 통과시키려면 4가지 정보가 필요
- 1차 버스(primary bus) 번호 PCI-PCI 브릿지에 upstream으로 연결된 버스 번호
- 2차 버스(secondary bus) 번호 PCI-PCI 브릿지에 downstream으로 연결된 버스 번호
- 종속 버스(subordinate bus) 번호 PCI-PCI 브릿지의 downstream 도달 가능한 버스들 중 최대 버스 번호
- PCI I/O와 PCI 메모리 윈도우 PCI-PCI 브릿지의 모든 downstream으로 연결된 장치들이 사용하는 PCI I/O와 PCI 메모리 공간의 윈도우에 대한 베이스 주소 및 크기
- PCI-PCI 브릿지 초기화 단계에서 종속 버스 번호는 바로 알 수 없기 때문에, 처음에는 DFS로 브릿지를 찾을 때마다 각 2차 버스 번호를 부여하고 종속 버스 번호에는 임시로 0xFF를 지정. CPU에서 가장 멀리 떨어진(downstream의 마지막) 브릿지로부터 되돌아올 때(백트래킹 과정에서) 종속 버스 번호에 도달가능한 최대 버스 번호를 부여
PCI Fixup
- PCI 확정(Fixup) 시스템별로 다른 PCI 초기화의 종료 부분을 마무리짓는 작업
- 인텔 기반 시스템은 부팅 시 실행되는 시스템 BIOS에서 이 작업을 진행하며 그 외 시스템에서는 커널에서 작업
- 각 장치에 PCI I/O와 PCI 메모리 공간을 할당
- 장치가 사용할 메모리 공간의 크기를 탐색하기 위해, 각 장치의 베이스 주소 레지스터에 1을 써서 전달, 장치는 무관한 주소 비트를 0으로 설정해서 응답하는데 커널이 받은 값은 주소공간의 크기를 나타냄
- 모든 주소 공간의 크기는 2의 배수이며, 이에 맞춰 자연스럽게 메모리 공간은 정렬되어 장치에 할당됨
- 커널은 어떤 주소 공간에 장치의 레지스터가 있어야 하는지를 알려주는데, 현재 버스의 모든 장치에 대해 각각 필요한 크기만큼 전역 PCI I/O와 메모리 베이스 주소를 이동시킴
- 현재 버스의 모든 downstream 으로 연결된 버스들을 재귀적으로 찾아 공간을 할당
- 전역 PCI I/O와 PCI 메모리 공간의 베이스 주소를 변경
- 장치들의 주소 공간이 모든 upstream 으로 연결된 PCI-PCI 브릿지의 메모리 공간에 위치하게 됨
- 시스템에 있는 각 PCI-PCI 브릿지의 PCI I/O와 PCI 메모리 주소 윈도우를 설정
- 현재 있는 전역 PCI I/O와 PCI 메모리 베이스 주소를 4K 단위로 상대적 정렬 (1Mbytes 경계를 가짐)
- 이 과정에서 현재 PCI-PCI 브릿지가 필요로 하는 윈도우의 베이스 주소 및 크기를 계산
- 이 버스에 연결된 PCI-PCI 브릿지를 계속 탐색해서 베이스 주소와 크기를 지정
- PCI-PCI 브릿지에 대한 PCI I/O와 PCI 메모리 공간에 접근할 수 있도록 브릿지 기능 설정
- 브릿지의 1차 PCI 버스에서 보이는 PCI I/O와 PCI 메모리 주소 중에서 윈도우 안에 있는 PCI I/O와 PCI 메모리 주소는 2차 버스가 사용하는 것
- 장치에 인터럽트 라인 값을 지정하여 그 장치의 인터럽트 처리를 제어할 수 있게 함
'Operating System > Linux' 카테고리의 다른 글
리눅스 커널 - 디바이스 드라이버 1 (0) | 2021.06.05 |
---|---|
리눅스 커널 - 인터럽트와 인터럽트 처리 (0) | 2021.06.05 |
리눅스 커널 - 프로세스간 통신(IPC) 메커니즘 (0) | 2021.06.03 |
리눅스 커널 - 프로세스와 쓰레드 (0) | 2021.06.02 |
리눅스 커널 - 메모리 관리 2 (0) | 2021.06.01 |