[리눅스 인사이드] 2019년 3월 스터디 모집공고입니다.

안녕하세요? 문c 블로그의 문영일입니다.

 

리눅스 인사이드 제 2기 arm64  리눅스 커널 스터디할 멤버를 모집합니다.

 

스터디 요약

  • 참여하실 분들은 arm 커널에 대해 최소한 일부 지식이 필요합니다.
  • 처음 커널을 스터디하고자 하시는 분들은 iamroot 사이트에서 지원하는 스터디를 이용하는 것이 더 쉬울 것입니다.
  • 약 2년을 목표로 끈기와 정성이 필요합니다.

 

스터디 참여 접수 기간

  • 현재 ~ 2019년 2월 26일까지

 

스터디 시작

  • 2019년 3월 2일 예정
  • 매주 토요일 오후 3시 ~ 오후 10시까지

 

스터디 공간

  • 강남 지역 사설 스터디룸(장소는 추후 공지)
  • 매주 약 1만원 이내의 비용 발생

 

주최 사이트

  • 사이트명: 리눅스 인사이드 (Linux Inside)
  • 사이트 URL: www.linuxinside.kr
  • 대표 관리자: 윤창호

 

참여하실 분은 위 사이트에 가입하시고,  다음 내용으로 게시판에 참여 글을 올리시기 바랍니다. (게시된 글은 본인만 확인 가능합니다)

1. 이름: 홍길동

2. 연락처: 010-1111-2222

3. 리눅스 관련 경험/경럭 서술

 

감사합니다.

MTD(Memory Technology Device) -1-

 

MTD(Memory Technology Device) -1-

MTD는 플래시 메모리를 상위 application 계층과 연결시켜 소통하기 위한 별도 계층의 디바이스 파일이다. 

  • 고전(Traditinal) 리눅스의 블럭 디바이스나 캐릭터 디바이스만으로는 플래시 메모리와 communication하는 것이 어울리지 않는다.
    • 캐릭터 디바이스는 seek를 지원하지 않는다.
    • 블럭 디바이스는 write와 erase를 구분하지 않는다.
  • 플래시 메모리를 위해 별도의 디바이스인 MTD 디바이스가 필요해졌다.
    • UBIFS 파일 시스템은 UBI를 통해 MTD 디바이스와 연결된다.
    • /dev/mtdN
  • Block layer 기반으로 동작하는 JFFS2, YAFFS 등과 같은 플래시 파일시스템에 연결시킬 수도 있다.
    • 블럭디바이스와 연결할 때 사용되는 디바이스 파일: /dev/mtdblockN
  • removal이 가능한 MMC, SD Flash 등도 플래시 메모리 유형에 들어가지만  MTD에 속하지 않고 다른 레이어인 FTL(Flash Translantion Layer)를 사용한다.

 

MTD 지원 디바이스

  • NAND 플래시
    • 일반 NAND로 다른 기술을 가진 NAND와 분류하기 위해 Bare NAND, Raw NAND 또는 Pure NAND로 불리기도 한다.
    • ECC는 부트 로더 또는 리눅스 커널 드라이버 등의 software로 처리하지만, ECC 기능이 내장된 NAND controller가 사용된 경우 hardware 기능을 사용할 수도 있다.
  • NOR 플래시
    • ECC가 없는 NOR 플래시
      • 희귀하지만 ECC가 있는 NOR 플래시 제품도 있다.
  • ECC Free NAND
    • One NAND
      • Nand 플래시 셀에 일부를 Nor 플래시와 연결하여 동작시키고, SRAM과 로직(ECC Controller)을 추가하여 하나의 칩으로 만든 퓨전 메모리이다.
        • 삼성이 개발하였으며 도시바 및 몇 개 기업에 라이센스 하였다.
    • Flex-One NAND
      • One NAND 기능은 동일하고, NAND 플래시의 셀 부분을 MLC를 사용하고 외부에 연결된 부분만 SLC로 구성하여 대용량화 시킨 제품이다.
    • ClearNAND
      • Micron이 개발하였으며 BA NAND 종류로 ECC 처리를 위해 NAND 칩에 내장된 ECC controller가 ECC 처리를 직접 수행한다.
    • LBA NAND
      • Toshiba가 개발하였으며 FTL 로직을 NAND 칩에 모두 내장시켜 점점 복잡해지는 호스트 컨트롤러 및 software의 처리를 단순화시켜주는 장범이 있다.

 

참고: Micron 일부 플래시 칩에 ECC 기능이 내장된 칩들을 위해 리눅스 커널의 메인 라인 v4.13-rc1에서는 On-die NAND 기능을 추가하였다. 삼성은 One NAND 처리를 위해 별도의 서브 트리를 사용한다.

 

플래시 표준 규격

  • JEDEC(Joint Electron Device Engineering Council) 솔리드 스테이트 기술 협회
    • 플래시 업계의 1위 및 2위인 삼성과 도시바가 참가한다.
  • ONFI(Open Nand Flash Interface)
    • 삼성과 도시바를 제외한 나머지 플래시 제조사가 참가한다.
  • 기타
    • 규격이 정해지기 전에 생산된 제품들(Old none-CFI)

 

Nor Flash vs Nand Flash

비교

  • Bus
    • CPU에서 직접 주소가 가능한 방식이라 Bus라고 불린다.
    • Nand는 페이지별로 읽기/쓰기 동작을 별도로 해야 한다.
  • Cell Size
    • 1개의 셀 사이즈가 작게만들 수록 집적화가 커 제한된 면적에서 더 큰 용량을 만들 수 있다.
    • NOR 플래시 용량은 2019년 현재 1Mb ~ 1Gb 이다.
    • NAND 플래시 용량은 SLC Nand 칩 기준 2019년 현재 1Mb ~ 16Gb 이다.

 

Parellel vs Serial 방식

처음 NOR 플래시 및 NAND 플래시는 Pararell 방식으로 만들어졌지만 Serial 방식을 쓰는 SPI 버스에 연결할 수 있는 방식도 사용한다. SPI-NAND 및 SPI-NOR 플래시는 Throughput은  약간 떨어지지만 핀 수가 줄어들어 소형으로 만들 수 있는 장점이 있다.

 

Parallel NAND Flash

블럭 다이어그램

Nand 플래시의 주소를 지정할 때에는 총 5번의 주소를 전송하여 지정할 수 있다.

  • 2번은 Column 주소
  • 3번은 Row 주소

 

플래시의 용량 표기 시 8Gb라고 할 때 데이터를 8Gbit에 저장할 수 있고, ECC등의 정보를 담는 Spare 영역은 8Gbit에 포함되지 않고 별도의 영역을 가진다.

  • 아래 Micron 8Gb 플래시도 별도의 256Mbit의 Spare 영역을 가지고 있다.

 

x8 vs x16

  • 한 번에 8bit 또는 16bit 데이터 입출력을 전송할 수 있는 능력을 가진다. x8보다는 x16이 더 빠른 처리를 수행할 수 있다.

 

8Gb/x16 Micron NAND Flash (MT29F4G16BxB)

 

4Gb/x8 Samsung NAND Flash (K9K8G08U0M)

 

읽기 시그널

Micron(MT29F4G16BxB)

Samsung(K9K8G08U0M)

 

지원 명령

제조사 및 칩마다 지원 명령들이 다르다.

Micron(MT29F4G16BxB)

Samsung(K9K8G08U0M)

 

제품 코드 분류

주로 Micron SLC 1~4세대 제품들을 보여주고 있다.

  • MLC 및 TLC NAND 들은 생략하였다.
Micron-SLC-1세대 (Feture Set A)

 

Micron-SLC-2세대 (Feture Set B)

 

Micron-SLC-3세대 (Feture Set C)

 

Micron-SLC-4세대 (Feture Set D)

 

Samsung(K9K8G08U0M)

 

블럭 및 페이지 사이즈

같은 회사의 동일한 용량이라도 generation에 따라 페이지 사이즈와 블럭수 및 OOB 사이즈 등이 다르다.

  • 주로 Micron SLC 1~4세대 제품들을 보여주고 있다.
    • MLC 및 TLC NAND 들은 생략하였다.
  • 주의: 버스폭에 따라 사이즈(페이지 크기, OOB 사이즈) 값은 바이트(8bit)와 워드(16bit) 표기를 사용한다.
    • x8: Bytes (8bits)
    • x16: Words (16bits) 
  • Plain

    • 처리 속도를 높이기 위해 디바이스를 여러 개의 플레인으로 구성하여 사용한다.

 

Micron 1세대 SLC

MT29F4G08AAA & MT29F8G08DAA

  • MT29F8G08DAA의 경우 두 개의 Chip Enable(CE#, CE2#)을 사용하여 4G x 2= 8G 용량을 사용할 수 있다.

 

MT29F8G08BAA & MT29F16G08FAA

  • MT29F16G08FAA의 경우 두 개의 Chip Enable(CE#, CE2#)을 사용하여  8G x 2= 16G 용량을 사용할 수 있다.

 

Micron 2세대 SLC

MT29F2G08AxB

MT29F2G16AxB

 

MT29F4G08BxB & MT29F8G08FxB

  • MT29F8G08FxB의 경우 두 개의 Chip Enable(CE#, CE2#)을 사용하여 4G x 2= 8G 용량을 사용할 수 있다.

 

Micron(MT29F4G16BxB)

 

Micron 3세대 SLC

MT29F8G8xxxC

 

MT29F8G16xxxC

 

MT29F16G08xxxC

  

 

MT29F16G16xxxC

 

Micron 4세대 SLC

MT29F4G08xxxD

 

MT29F4G16xxxD

 

MT29F8G08xxxD & MT29F16G08xxxD

  • MT29F16G08xxxD의 경우 두 개의 Chip Enable(CE#, CE2#)을 사용하여 8G x 2= 16G 용량을 사용할 수 있다.

 

MT29F8G16xxxD

 

Samsung

Samsung(K9K8G08U0M)

 

 

주소 지정

각 사의 NAND 플래시마다 주소 지정방법이 조금씩 다르다. 

Micron(MT29F4G08BxB)

첫 블럭만 살펴보면

  • 첫 블럭 영역
    • 0x0000_0000 ~ 0x0003_ffff (미사용 영역 포함: 256KB)
  • 첫 블럭의 첫 페이지 영역
    • 0x0000_0000 ~ 0x0000_0fff (미사용 영역 포함: 4KB)
  • 첫 페이지 데이터 영역
    • 0x0000_0000 ~ 0x0000_07ff (2KB)
  • 첫 페이지 Spare 영역
    • 0x0000_0800 ~ 0x0000_083f (64B)

 

Micron(MT29F4G16BxB)

첫 블럭만 살펴보면

  • 첫 블럭 영역
    • 0x0000_0000 ~ 0x0001_ffff (미사용 영역 포함: 128KB)
  • 첫 블럭의 첫 페이지 영역
    • 0x0000_0000 ~ 0x0000_07ff (미사용 영역 포함: 2KB)
  • 첫 페이지 데이터 영역
    • 0x0000_0000 ~ 0x0000_03ff (1KB)
  • 첫 페이지 Spare 영역
    • 0x0000_0400 ~ 0x0000_041f (32B)

 

Spare Area 또는 OOB(Out Of Block)

 

NAND의 최소 단위는 페이지인데 1개의 페이지 크기는 칩 마다 다양한 크기를 갖는다.

  • 512 바이트
  • 1024 바이트
  • 2048 바이트
  • 4096 바이트
  • 8192 바이트
  • …. 

 

ECC를 만들기 위해 사용되는 사이즈 범위를 스텝(Step)이라 하고 이 스텝의 사이즈는 다음과 같이 2 가지가 있다.

  • 512 바이트
  • 1024 바이트

 

매 데이터 페이지마다 Spare Area 또는 OOB(Out Of Block)이라고 불리는 추가 영역을 필요로한다. 이 곳에는 다음과 같은 정보가 담기며 각 제조사 NAND 플래시와 칩 마다 다르다.

  • BI(Bad block Info) 또는 BBM(Bad Block Marker) 정보
    • 1 바이트로 구성된다.
    • 0xff인 경우 정상이고, 그 외의 값은 Bad Block을 의미한다.
    • 2개가 있는 경우는 버스가 x16인 경우에 사용된다.
  • ECC 정보
    • 1 비트 Hamming의 경우 3바이트
    • BCH-4의 경우 7바이트를 사용하며 OOB 내에서의 위치는 제조사 및 칩마다 다르다.

 

OOB 영역의 경우 1개의 페이지 뒤에 모여 있는 경우도 있고 1개 페이지 중간 스텝마다 위치하는 경우도 있다. OOB 영역의 크기는 다음과 같이 다양하다.

  • 8, 16, 32, 64,  128, 218, 224, 400, 436, 448, 512, 640, 1024, 16764, 2048 bytes
    • 16 * 4 스텝 = 64 bytes
    • 16 * 8 스텝 = 128 bytes
    • 28 * 4 스텝 = 112 bytes
    • 28 * 8 스텝 = 224 bytes

  • BBM(Bad Block Marker)  위치
    • 보통 OOB(Out Of Block, Spare Area)의 첫 번째 바이트 또는 여섯 번째 위치
  • ECC 알고리즘 종류
    • Hamming
      • NAND 플래시에 적용 시 512바이트당 최대 1비트 부호를 정정할 수 있다. 
      • 16바이트 단위로 일곱번째 바이트에 위치하고 3바이트 사용
    • BCH-4
      • NAND 플래시에 적용 시 512바이트당 최대 4비트 부호를 정정할 수 있다.
      • 16바이트 단위로 열번째 바이트에 위치하고 7바이트 사용
    • BCH-8
      • NAND 플래시에 적용 시 512바이트당 최대 8비트 부호를 정정할 수 있다.
      • 생략
    • BCH-12
      • NAND 플래시에 적용 시 512바이트당 최대 12비트 부호를 정정할 수 있다. 
      • 생략

 

1 bit Hamming

Case 1) 16바이트 OOB – for 512B 페이지
  • 0,0,0,0,0,B,E,E-E,0,0,0,0,0,0,0

 

Case 2) 64바이트 OOB – for 2KB 페이지
  • B,B,0,0,0,0,E,E-E,0,0,0,0,0,0,0-0,0,0,0,0,0,E,E-E,0,0,0,0,0,0,0
  • 0,0,0,0,0,0,E,E-E,0,0,0,0,0,0,0-0,0,0,0,0,0,E,E-E,0,0,0,0,0,0,0
    • x16 버스를 사용하는 경우 워드(2 Byte) 단위로 처리되므로 첫 번째 워드의 각각의 바이트에 Bad Block 표기가 기재된다.

 

Case 3) 128바이트 OOB – for 4KB  페이지
  • B,B,0,0,0,0,E,E-E,0,0,0,0,0,0,0-0,0,0,0,0,0,E,E-E,0,0,0,0,0,0,0
  • 0,0,0,0,0,0,E,E-E,0,0,0,0,0,0,0-0,0,0,0,0,0,E,E-E,0,0,0,0,0,0,0
  • 0,0,0,0,0,0,E,E-E,0,0,0,0,0,0,0-0,0,0,0,0,0,E,E-E,0,0,0,0,0,0,0
  • 0,0,0,0,0,0,E,E-E,0,0,0,0,0,0,0-0,0,0,0,0,0,E,E-E,0,0,0,0,0,0,0

 

BCH-4

Case 1) 16바이트 OOB – for 512B 페이지
  • 0,0,0,0,0,B,0,0-0,E,E,E,E,E,E,E

 

Case 2) 64 바이트 OOB – for 2KB  페이지
  • B,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E-0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E
  • 0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E-0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E

 

Case 3) 128 바이트 OOB – for 4KB  페이지
  • B,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E-0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E
  • 0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E-0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E
  • 0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E-0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E
  • 0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E-0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E

 

Case 4) 256 바이트 OOB – for 8KB  페이지
  • B,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E-0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E
  • 0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E-0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E
  • 0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E-0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E
  • 0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E-0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E
  • 0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E-0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E
  • 0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E-0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E
  • 0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E-0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E
  • 0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E-0,0,0,0,0,0,0,0-0,E,E,E,E,E,E,E

 

BBT(Bad Block Table)

두 개의 블럭을 사용하여 배드 블럭을 관리한다. 

  • 처음 부트로더가 Nand 전체를 Erase 한 후 마지막 블럭부터 검색하여 정상인 두 개의 블럭에 Primary 및 Secondary(Mirror) BBT 정보를 기록한다.
  • BBT 블럭 내용에는 4개의 비트마다 하나의 블럭 정보를 담당하여 기록한다.
    • 0x0000
      • Factory Bad
      • 커널에서는 2비트로 0x11
    • 0x0001 ~ 0x1110
      • Bad Block
      • 0x1101: Reserved
        • 커널에서는 2비트로 0x10
      • 0x1110: Warn Out
        • 커널에서는 2비트로 0x01
    • 0x1111
      • 정상(Good)
      • 커널에서는 2비트로 0x00
  • BBT가 있는 블럭의 첫 페이지에 해당하는 OOB 영역에는 BBT 식별 부호가 담긴다.
    • Primary BBT
      • “Bbt0”
    • Secondary BBT
      • “1tbB”
  • BBT에 Bad Block 정보 기록할 때
    • 부트 로더에서 Nand Erase 시
    • mtd-utils를 사용하여 수동으로 배드 블럭 마킹 시
    • UBI를 사용하는 파티션의 경우 커널을 통해 Read/Write 에러 발생 시 자동으로

 

다음 그림은 BBT를 스캔하는 방법 3가지를 보여준다.

 

다음 그림은 BBT 식별부호가 저장되는 위치 2가지를 보여준다.

 

다음 그림은 커널에서 관리하는 BBT와 NAND에 위치한 BBT를 보여준다.

  • 커널 BBT는 항상 사용되지만 NAND에서 BBT를 운영할지의 여부는 NAND_BBT_USE_FLASH 옵션의 사용 유무로 판단한다.
    • NAND_BBT_CREATE 옵션
      • BBT 가 없을 때 생성한다.
    • NAND_BBT_WRITE 옵션
      • NAND에 BBT 갱신을 가능하게 한다.
  • NAND에 저장되는 Bad Block 상태값은 4가지 비트 타입 중 하나를 사용한다.
    • 대부분 2비트를 사용한다. 일부는 8비트를 사용하는 경우도 있다.
  • Reserved 상태는 커널 BBT에서만 관리하고 NAND에는 저장하지 않는다.

 

SPI를 위한 프레임워크

SPI-NOR 및 SPI-NAND를 위한 프레임워크도 준비되어 있다.

 

MTD & FTL Subsystem

MTD 서브시스템을 사용하는 인터페이스와 FTL/NFTL 서브시스템을 사용하는 분리하여 보여주고 있다.

  • 리눅스 커널의 MTD subsystem에도 FTL 및 NFTL 코드가 있지만 라이센스 문제가 있어 상용 목적으로 사용할 수 없다.
  • 보통 MTD 서브시스템의 FTL/NFTL을 사용하지 않고 대부분 제조사 컨트롤러 HW에 내장된 FTL을 사용한다.

 

 

MTD vs FTL

리눅스 커널에 FTL 및 NFTL 코드가 있고 소프트웨어만으로도 구현을 할 수는 있지만 라이센스 문제로 상용 목적으로 사용할 수는 없다

  • 참고로 UBIFS의 경우 대부분의 FTL 기능을 담고 있다.

 

다음 MTD와 H/W로 구현된 FTL의 차이점을 간단히 알아본다.

  • MTD
    • 부트 로더 및 리눅스 커널이 배드 블럭 관리를 해야 한다.
    • 부트 로더 및 리눅스 커널이 블럭별 레벨 관리를 해야 한다.
      • 레벨 관리: 특정 블럭에 write가 집중되지 않게 한다.
    • 부트 로더 및 리눅스 커널이 ECC 정보를 추가로 기록해야 한다.
    • UBIFS를 사용하는 경우 FTL의 대부분 기능을 처리해준다.
  • FTL
    • 컨트롤러 H/W가 배드 블럭 관리를 자동으로한다.
      • 일정 공간을 여분으로 두어 하드웨어가 배드 블럭 교체에 사용한다.
    • 컨트롤러 H/W가 블럭별 레벨 관리를 자동으로 한다.
    • 컨트롤러 H/W가 ECC 정보를 자동으로 기록하여 관리한다.

 

FTL의 주요 기능

  • Logical Block Mapping
  • Wear Levelling
  • Garbage Collection
  • Write Amplification

 

Case A) NAND Flash

예를 들어 MTD 레이어를 사용하고, MTD chip 드라이버에 NAND Flash를 사용하는 경우는 다음과 같이 동작함을 알 수 있다.

 

Case B) SPI Flash

예를 들어 MTD 레이어를 사용하고, MTD chip 드라이버에 SPI Flash를 사용하는 경우는 다음과 같이 동작함을 알 수 있다.

 

MTD를 사용하는 파일시스템간 비교

 

ECC(Error Checking & Correction)

낸드에 에러율이 높으므로 이를 보완할 수 있도록 기본적으로 ECC를 사용한다. 칩 선폭이 큰 SLC 같은 경우 Hamming이나 BCH 알고리즘으로 충분하다. MLC나 TLC의 경우는 더욱 높은 수준의 ECC가 필요해졌다. 참고로 1 비트 Hamming은 한 스텝(512 또는 1024 바이트) 당 에러 정정 비트는 1비트만 가능하다. 즉 한 스텝에서 동시에 2개 비트가 에러가 발생하는 경우 교정하지 못한다. 더 높은 수준의 ECC를 위해 BCH-4, BCH-8, BCH-12, … 등을 사용하는데 BCH 뒤에 붙는 숫자는 보정 가능한 비트 수를 의미한다.

 

1 bit Hamming

다음은 가장 간단한 ECC 알고리즘 중 하나인 Hamming으로 한 스텝(512 또는 1024 바이트) 데이터를 위해 필요한 ECC 비트를 산출하였다. 

  • X축에 필요한 ECC=6bits
  • Y축에 필요한 ECC=18bits
  • 총 24bits(3 bytes)

 

다음 그림은 512바이트(x8) 데이터의 hamming 알고리즘을 사용하여 ECC를 만드는 방법을 보여준다.

  • X(column)축의 첫 번째 ECC 비트를 만드는 방법은 다음과 같다.
    • 0x000 ~ 0x1ff의 bit7..bit4를 모두 Exclusive OR한 값
  • Y(row)축의 첫 번째 ECC 비트를 만드는 방법은 다음과 같다.
    • 0x000 ~ 0x0ff의 bit7..bit0을 모두 Exclusive OR한 값

 

NAND 셀(SLC, MLC, TLC)

NAND 플래시에서 1 셀당 저장할 수 있는 비트가 다음과 같다.

  • SLC
    • 1 셀당 1비트
    • 블럭당 약 100,000회 erase 가능
    • 고가
  • MLC
    • 1셀당 2비트
    • 블럭당 약 10,000회 erase 가능
    • 중간
  • TLC
    • 1셀당 3비트
    • 블럭당 약 1,000회 erase 가능
    • 저가

SLC에 비해 MLC 및 TLC의 경우 에러 메니지먼트가 더욱 중요해서 더 복잡한 ECC를 채용하였음을 보여준다.

 

NAND Error Management Trends

NAND 플래시에서 점점 진보된 ECC 알고리즘으로 변화되고 있는 트렌드를 보여준다.

 

MTD Subsystem

MTD 오퍼레이션 명령 단위

플래시 메모리를 읽기 위해 다음과 같이 동작한다.

  • 명령(Cmd) 전송 –> 주소들 전송 –> 명령(Cmd) 전송 –> 대기 –> 데이터 수신

 

기존 커널 오퍼레이션

NAND 컨트롤러 드라이버 구현 시 nand를 읽기 위해 여러 개의 operation을 구현해야 했다.

 

바뀐 새 커널(v4.16) 오퍼레이션

변경된 커널 v4.16에서는 NAND 컨트롤러 드라이버 구현 시 nand를 읽기 위해 단순화된 operation을 구현하면 된다. 당분간 기존 operation도 호환 목적으로 같이 병행하여 사용한다.

  • (*exec_op)
    • 추가된 후크 함수로 다음 명령들을 대체한다.
      • ->cmdfunc()
      • ->{read,write}_{buf,byte,word}()
      • ->dev_ready()
      • ->waifunc()

 

참고

 

 

SPI Subsystem -1- (Basic)

 

SPI(Serial Peripheral Interface)

1980년대 중반에 모토롤라가 처음 개발한 동기 시리얼 통신 인터페이스로 간략히 소개하면 다음과 같다.

  • SPI(Serial Peripheral Interface)는 마이크로컨트롤러를 센서, 메모리(SPI-NAND, MMC 및 SD Card, …), 주변장치에 연결하는 데 사용되는 동기식 4선 직렬 링크이다. 이것은 단순한 “사실상의(de facto)” 표준이다.
  • SPI는 마스터/슬레이브 구성을 사용하고, 싱글 마스터만을 지원하며 슬레이브는 1개 이상과 연결할 수 있다.
  • 디폴트 구성에서 4 wire를 사용하며 Full Duplex를 지원한다.
    • 컨트롤러에 따라 Half Duplex를 지원하는 여러 모드가 사용된다.
  • 연속 전송을 위해 DMA Engine을 사용하여 Queue를 지원하는 컨트롤러도 있다.
  • 다음 2개의 설정 조합으로 4개의 클럭 모드를 사용한다.
    • CPOL(Clock Polarity)={0, 1}
    • CPHA(Clock PHAse)={0, 1}
  • 최대 클럭이 제한되지 않아 속도 제한이 없다.
  • 구현이 간단하므로 저속 SPI Master 또는 SPI Slave는 GPIO pin을 사용하여 구현할 수도 있다.

 

기본 4개의 핀 구성

  • SCLK
    • 클럭
    • SCK, CLK, SCL로 부르기도 한다.
  • MOSI
    • Master Output Slave Input
    • SIMO, SDI, DI, SDA로 부르기도 한다.
  • MISO
    • Master Output Slave Input
    • SOMI, SDO, DO, SDA로 부르기도 한다.
  • SS#
    • Slave Select (Low Active)

 

여러 SPI 전송 모드

다음과 같이 Half Duplex를 사용하는 여러 종류의 SPI 모드들을 지원하는 컨트롤러도 있다.

  • 3 wire SPI
    • Half Duplex 입출력 wire를 1개 지원한다.
    • 저속 EEPROM 및 센서류에서 사용된다.
  • Dual SPI
    • Half Duplex 입출력 wire를 2개 지원한다. (단방향 속도가 2배)
  • Quad SPI
    • Half Duplex 입출력 wire를 4개 지원한다. (단방향 속도가 4배)
    • SPI-Nor 플래시 및 SPI-Nand 플래시에 자주 사용된다.

 

연결 구성 방식

SPI 버스를 구성하는 방법으로 다음과 같이 두 가지 방식을 사용한다.

  • 독립 slave 구성 방식
    • SPI 디바이스를 선택하여 사용하는 방식이다.
  • Daisy-chain slave 구성 방식
    • 데이터 비트를 서로 밀어내는 방식이다.

 

SPI 장점

  • Push-Pull 출력(Open Drain이 아닌)을 사용하여 상호간에 같은 전압을 사용하여 시그널 정합성과 고속을 지원한다.
  • I2C 보다 낮은 소비 전력
  • Arbitration이 없다.
  • 슬레이브는 마스터가 보내주는 클럭만을 사용하고 정확성이 떨어져도 문제 없다.

 

SPI 단점

  • 인밴드(디폴트 SPI wire)를 통해 주소가 지원되지 않아 다 수의 슬레이브를 사용 시 별도의 아웃밴드(칩 셀렉트 라인)를 통해 슬레이브를 선택해야 한다.
  • HW Flow-control이 없다.
  • 하나의 마스터만 지원한다.
  • 에러 체킹을 지원하지 않는다.
  • Hot 플러그를 지원하지 않는다.
  • Dual, Quad, 3-wire를 지원하는 경우 Half-Duplex만 지원한다.

 

Applications

  • EEPROM, 플래시 메모리
  • MMC or SD Card
  • LCD
  • RTC(Real Time Clock)
  • 각종 센서류

 

클럭 트리거 모드

SPI 컨트롤러들은 다음 테이블과 같이 4가지 모드 중 하나 이상을 지원한다.

CPOL:

  • 0-클럭 idle이 low에서 출발
  • 1-클럭 idle이 high에서 출발

CPHA:

  • 0-시작 pulse에서 동작
  • 1-종료 pulse에서 동작

 

모드에 따라 트리거되는 시점이 다음과 같이 다름을 알 수 있다.

 

Shift Register

SPI 장치들은 Shift Register를 가지고 있다.

  • 기본적으로 MSB부터 전송되는데 특정 컨트롤러는 LSB부터 전송을 수행시키는 방법도 지원한다.

 

간단한 SPI의 Write 및 Read 동작

 

Quad SPI에서의 입출력 동작

 

디바이스 트리

Case 1) Broadcom Northstar SPI

arch/arm64/boot/dts/broadcom/northstar2$/ns2.dtsi

qspi: spi@66470200 {
        compatible = "brcm,spi-bcm-qspi", "brcm,spi-ns2-qspi";
        reg = <0x66470200 0x184>,
              <0x66470000 0x124>,
              <0x67017408 0x004>,
              <0x664703a0 0x01c>;
        reg-names = "mspi", "bspi", "intr_regs",
                    "intr_status_reg";
        interrupts = <GIC_SPI 419 IRQ_TYPE_LEVEL_HIGH>;
        interrupt-names = "spi_l1_intr";
        clocks = <&iprocmed>;
        clock-names = "iprocmed";
        num-cs = <2>;
        #address-cells = <1>;
        #size-cells = <0>;
};
  • num-cs
    • 최대 2개의 슬레이브 셀렉트 기능을 갖고 있다.

 

arch/arm64/boot/dts/broadcom/northstar2$/ns2-svk.dts

&qspi {
        bspi-sel = <0>;
        flash: m25p80@0 {
                #address-cells = <1>;
                #size-cells = <1>;
                compatible = "m25p80";
                reg = <0x0>;
                spi-max-frequency = <12500000>;
                m25p,fast-read;
                spi-cpol;
                spi-cpha;

                partition@0 {
                        label = "boot";
                        reg = <0x00000000 0x000a0000>;
                };

                partition@a0000 {
                        label = "env";
                        reg = <0x000a0000 0x00060000>;
                };

                partition@100000 {
                        label = "system";
                        reg = <0x00100000 0x00600000>;
                };

                partition@700000 {
                        label = "rootfs";
                        reg = <0x00700000 0x01900000>;
                };
        };
};

3V 전압으로 동작하는 Micron M25P80 시리얼 Nor 플래시 임베디드 메모리로 8Mbit 용량을 가지고 있다.

  • spi-max-frequency
    • frequency를 제한한다.
    • 위의 예에서는 12.5Mhz
  •  m25p,fast-read
    • fast-read를 지원한다.
  • spi-cpol
    • 클럭이 high부터 출발해야하는 경우 설정되는 속성이다.
  • spi-cpha
    • 클럭이 faling edge 방향에서 데이터와 동기되어야 하는 속성이다.

 

Case 2) Rockchip SPI

arch/arm64/boot/dts/rockchips/rk3399.dtsi

spi1: spi@ff1d0000 {
        compatible = "rockchip,rk3399-spi", "rockchip,rk3066-spi";
        reg = <0x0 0xff1d0000 0x0 0x1000>;
        clocks = <&cru SCLK_SPI1>, <&cru PCLK_SPI1>;
        clock-names = "spiclk", "apb_pclk";
        interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH 0>;
        pinctrl-names = "default";
        pinctrl-0 = <&spi1_clk &spi1_tx &spi1_rx &spi1_cs0>;
        #address-cells = <1>;
        #size-cells = <0>;
        status = "disabled";
};

 

arch/arm64/boot/dts/rockchips/rk3399-gru.dtsi

&spi1 {
        status = "okay";

        pinctrl-names = "default", "sleep";
        pinctrl-1 = <&spi1_sleep>;

        spiflash@0 {
                compatible = "jedec,spi-nor";
                reg = <0>;

                /* May run faster once verified. */
                spi-max-frequency = <10000000>;
        };
};

 

참고

 

PCI Subsystem -3- (Host Controller)

 

PCI 호스트 컨트롤러 디바이스 트리 노드

다음 PCI 노드를 알아본다.

pci {
    compatible = "company,foo"
    device_type = "pci";        /* always be "pci" */
    #address-cells   = <3>;     /* always be 3 */
    #size-cells      = <2>;     /* 32bit=1, 64bit=2 */
    #interrupt-cells = <1>;     /* always be 1 for legacy pci irq */

    bus-range = <0x0 0x1>;

    /*    CPU_PHYSICAL(P#a)     SIZE(P#s) */
    reg = <0x0 0x40000000    0x0 0x1000000>;

    /*             PCI_PHYSICAL(#a)      CPU_PHYSICAL(P#a)   SIZE(#s) */
    ranges = <0x81000000 0x0 0x00000000   0x0 0x48000000    0x0 0x00010000>,  /* non-relocatable IO */
             <0x82000000 0x0 0x40000000   0x0 0x40000000    0x0 0x40000000>;  /* non-relocatable MEM */

    /*               PCI_DEVICE(#a)  INT#(#i)   IRQ-Controller   IRQ-UNIT#   IRQ_ARG(PI#i) */
    interrupt-map = <  0x0 0x0 0x0    0x0           &gic            0x0       0x4 0x1
                     0x800 0x0 0x0    0x1           &gic            0x0       0x5 0x1
                    0x1000 0x0 0x0    0x2           &gic            0x0       0x6 0x1
                    0x1800 0x0 0x0    0x4           &gic            0x0       0x7 0x1>;

    /*                    PCI_DEVICE(#a)    INT#(#i) */
    interrupt-map-mask = <0xf800 0x0 0x0     0x7>;
}
  • reg
    • PCI 호스트 컨트롤러의 조작에 사용될 레지스터들의 액세스에 사용될 물리 주소와 사이즈가 지정된다.
  • device_type
    • 항상 “pci”를 대입하여야 한다.
  • #address-cells
    • pci 주소에 사용하므로 반드시 3이어야 한다.
  • #size-cells
    • PCI 및 물리 주소에서 사용되는 메모리 사이즈
      • 1=32비트 메모리 사이즈
      • 2=64비트 메모리 사이즈
  • interrupt-cells
    • 레거시 인터럽트를 사용하는 경우 사용되며, 이 때에는 반드시 1이어야 한다.
  • bus-range
    • 사용할 시작 버스 번호 ~ 끝 버스 번호
  • ranges
    • pci 주소 체계가 cpu에서 사용하는 물리 주소 체계로 변환되어야 하는 범위가 지정된다.
  • interrrupt-map
    • 레거시 인터럽트를 사용하는 PCI 디바이스를 위해 최대 4개의 irq 핀을 위해 irq 매핑 정보를 구성한다.
  • interrupt-map-mask
    • 위의 레거시 인터럽트 값의 유효 값을 필터하기 위해 사용하는 마스크 정보이다.

 

각 속성에서 사용한 셀

주석에서 사용된 기호는 셀 수를 알아오는 곳을 의미한다.

  • #a
    • PCI 노드의 #address-cells 값
  • #s
    • PCI 노드의 #size-cells 값
  • #i
    • PCI 노드의 #interrupt-cells 값
  • P#a
    • 부모 노드의 #address-cells 값
  • P#s
    • 부모 노드의 #size-cells 값
  • PI#i
    • 부모 인터럽트 컨트롤러 노드의 #interrupt-cells 값
pci {
    compatible = "company,foo"
    device_type = "pci";        /* always be "pci" */
    #address-cells   = <3>;     /* always be 3 */
    #size-cells      = <2>;     /* 32bit=1, 64bit=2 */
    #interrupt-cells = <1>;     /* always be 1 for legacy pci irq */

    bus-range = <0x0 0x1>;

    /*    CPU_PHYSICAL(P#a)     SIZE(P#s) */
    reg = <0x0 0x40000000    0x0 0x1000000>;

    /*             PCI_PHYSICAL(#a)      CPU_PHYSICAL(P#a)   SIZE(#s) */
    ranges = <0x81000000 0x0 0x00000000   0x0 0x48000000    0x0 0x00010000>,  /* non-relocatable IO */
             <0x82000000 0x0 0x40000000   0x0 0x40000000    0x0 0x40000000>;  /* non-relocatable MEM */

    /*               PCI_DEVICE(#a)  INT#(#i)   IRQ-Controller   IRQ-UNIT#   IRQ_ARG(PI#i) */
    interrupt-map = <  0x0 0x0 0x0    0x0           &gic            0x0       0x4 0x1
                     0x800 0x0 0x0    0x1           &gic            0x0       0x5 0x1
                    0x1000 0x0 0x0    0x2           &gic            0x0       0x6 0x1
                    0x1800 0x0 0x0    0x4           &gic            0x0       0x7 0x1>;

    /*                    PCI_DEVICE(#a)    INT#(#i) */
    interrupt-map-mask = <0xf800 0x0 0x0     0x7>;
}

 

PCI 주소 공간 매핑

PCI 장치와 CPU가 서로의 영역에 접근하려면 각각의 접근 방향에 따른 매핑이 필요하다.

  • outbound mapping
    • PCI 장치에 붙어 있는 메모리를 cpu가 접근하기 위한 매핑
  • inbound mapping
    • PCI 장치가 DMA를 통해 호스트 메모리에 접근하기 위한 매핑

a) 아웃바운드 매핑

PCI 디바이스에서 사용하는 1개 이상의 PCI 주소(I/O, MEM)는 cpu가 곧 바로 접근하여 사용하지 못한다. 먼저 PCI 호스트 컨트롤러 내부에 존재하는 IOMMU 장치를 사용하여 PCI 메모리의 영역을 CPU 물리 주소의 빈 공간으로 아웃바운드 매핑을 해야한다. 이렇게 CPU 물리 주소로 매핑된 메모리는 해당 PCI 디바이스 드라이버에서 이 영역을 다시 CPU의 가상 주소에 매핑하여 cpu가 이 영역에 접근할 수 있게 할 수 있다.

  • PCI 호스트 컨트롤러가 outbound mapping하는 주소 범위 등의 정보는 디바이스 트리에서 PCI 노드의 ranges 속성으로 표현한다.

 

Ranges 속성

호스트 컨트롤러가 IO 주소 공간, MEM32 공간 및 MEM64 공간을 outbound 매핑하는 영역에 대한 기술을 한다. pre-fetchable 영역의 MEM32 및 MEM64가 있는 경우 별도의 영역으로 구성해야 한다. 영역을 구성하는 셀 값은 크게 다음과 같이 3 부분으로 구성된다.

  • PCI 주소(source)
    • 변환할 PCI 주소 공간에 대한 타입과 주소를 기재한다.
    • PCI 호스트 컨트롤러 노드의 #address-cells 값이 지정한 값 만큼의 셀 수를 사용한다. (이 항목은 항상 3자리 셀을 사용한다)
  • cpu 물리 주소(destination)
    • PCI 호스트 컨트롤러로 변환되는 dest 영역의 시작 물리 주소를 기재한다.
    • 부모 노드에서 기재한 #address-size 값이 지정한 값 만큼의 셀 수를 사용한다.
  • 사이즈
    • 호스트 컨트롤러가 변환시키는 영역의 사이즈
    • PCI 호스트 컨트롤러 노드의 #size-cells 값이 지정한 값 만큼의 셀 수를 사용한다. (32bit=1, 64bit=2)

 

다음 ranges 속성은 2개의 PCI 메모리 영역을 물리 주소로 변환된다는 것을 커널에 알려주게 된다. 이 들 값 중 PCI 주소는 3자리 셀로 구성되며 이를 해석하는 방법도 이어서 알아본다.

 

PCI 주소

PCI 주소는 항상 3개의 셀로 구성된다.

phys.hi 셀
  • n
    • Non-relocatable region flag
    • 비-재배치 여부 플래그 (1=non-relocatable0=relocatable)
  • p
    • Prefetchable (cacheable) 영역 플래그(1=prefetchable, 0=non-prefetchable)
  • t
    • Ten bits aliased address flag (1=Ten bits aliased, 0=otherwise)
    • I/O 및 Memory 영역에 대해 하위 10비트를 제거한 Hard-Decoding을 사용할 수 있게 허용한다.
    • 즉 BAR를 통해 재배치할 필요 없다.
  • ss
    • 주소 영역 타입
      • 00: Configuration space
      • 01: I/O space
      • 10: 32 bit Memory space
      • 11: 64 bit Memory space
  • bbbbbbbb
    • PCI 버스 번호
  • ddddd
    • PCI 디바이스 번호 (또는 슬롯 번호)
  • fff
    • PCI 펑션 번호
  • rrrrrrrr
    • 레지스터 offset
phys.mid 셀
  • 64비트 PCI 주소 중 상위 32비트
phys.low 셀
  • 64비트 PCI 주소 중 하위 32비트

 

ss 타입에 따른 사용법

Configuration, I/O 및 메모리 장치 타입에 따른 PCI 주소 구성을 알아본다.

  • ss=00: Configuration space
    • n, p, t 모두 0이어야 한다.
    • bbbbbbbb,ddddd,fff,rrrrrrrrr에서 Configuration 영역 주소를 지정한다.
    • h[31:0]은 모두 0이어야 한다.
    • l[31:0]은 모두 0이어야 한다.
  • ss=01: I/O space
    • p는 0이어야 한다.
    • 10bit aliasing(t=1)은 10비트만을 사용한 초기 pc를 위해 사용하는데 arm, arm64 에서는 사용하지 않는다.
    • bbbbbbbb,ddddd,fff,rrrrrrrr가 지정하는 즉, BAR가 가리키는 주소를 사용한다.
      • rrrrrrrr=0x10, 0x14, 0x18, 0x1c, 0x20, 0x24
      • rrrrrrrr=0x00 (non-relocatable)
    • h[31:0]은 모두 0이어야 한다.
    • n
      • relocatable(n=0) 인 경우 l[31:0]에 I/O 영역에서 relocatable 시작 영역의 32비트 offset을 지정한다.
      • non-relocatable(n=1) 인 경우 l[31:0]에 32비트 IO 영역 주소를 지정한다.
    • arm64 사용사례: 0x01, 0x81
  • ss=10: 32 bit memory space
    • p는 0 또는 1을 사용할 수 있다.
    • 10bit aliasing(t=1)은 1M 메모리까지의 메모리 영역을 사용한 초기 pc를 위해 사용하는데 arm, arm64 에서는 사용하지 않는다.
    • bbbbbbbb,ddddd,fff,rrrrrrrr가 지정하는 즉, BAR가 가리키는 주소를 사용한다.
      • rrrrrrrr=0x10, 0x14, 0x18, 0x1c, 0x20, 0x24, 0x30
      • rrrrrrrr=0x00 (non-relocatable)
    • h[31:0]은 모두 0이어야 한다.
    • n
      • relocatable(n=0) 인 경우 l[31:0]에 상대 offset을 지정한다.
        • 32bit PCI Momory  시작 주소 = BAR + offset(l[31:0])
      • non-relocatable(n=1)인 경우 32비트 메모리 주소를 지정한다.
        • 32bit PCI Momory  시작 주소 = l[31:0]
    • arm64 사용사례: 0x02, 0x42, 0x82
  • ss=11: 64 bit memory space
    • ss=10과 유사하며 상위 PCI 주소를 사용하기 위해 h[31:0]도 사용한다.
    • arm64 사용사례: 0x03, 0x43, 0x83

 

다음은 PCI 디바이스에서 I/O 주소 공간과 32비트 Memory 주소 공간에 대한 Ranges 속성 사례를 보여준다.

 

b) inbound mapping

outbound와 반대 방향의 inbound mapping을 구성하는 경우 PCI 디바이스가 CPU를 통하지 않고 DMA 장치를 통해 직접 호스트 메모리에 접근할 수 있게된다.

  • PCI 호스트 컨트롤러가 inbound mapping하는 주소 범위 등의 정보는 디바이스 트리에서 PCI 노드의 dma-ranges 속성으로 표현한다
  • outbound 매핑에서 사용하는 ranges 속성과 동일한 구성을 사용한다.

 

다음 예를 알아보자.

  • 예) dma-ranges = <0x43000000 0x0 0x0 0x0 0x0 0x100 0x0>;
    • relocatable, pre-fetchable Mem64 영역으로 PCI 주소와 호스트 CPU 물리 주소를 동일한 공간으로 0x100_0000_0000 사이즈만큼(512G 영역) inbound 매핑한다.

 

Root Complex PCIe 브리지 포트의 PCI 주소 영역 및 제한

Root Complex PCIe 브리지 포트의 configuration space에서 3 가지 타입의 메모리 매핑 영역에 대한 시작 주소 및 제한 범위가 지정되는데 이를 알아본다.

  • 아래 그림에서 1번에 해당하는 값은 하위 4비트로 32bit 및 64bit 여부를 가린다.
    • 0b0000=32bit, 0b0001=64bit
  • Base 주소를 구성할 때 각 타입에 따라 1~2 개의 값을 연결하여 구성하고 수 비트의 하위 비트들은 지정되지 않는데 이들은 모두 0으로 채운다. (always 0)
    • 예) Pre-Fetchable Memory Base=0x4561, Pre-Fetchable Base Upper 32 bits=0x0000_1234인 경우
      • Base=0x0000_1234_4560_0000 (64bit)
  • Limit 주소를 구성할 때 각 타입에 따라 1~2 개의 값을 연결하여 구성하고 수 비트의 하위 비트들은 지정되지 않는데 이들은 모두 0으로 채운다. (always 1)
    • 예) Pre-Fetchable Memory Limit=0x4561, Pre-Fetchable Limit Upper 32 bits=0x0000_1234인 경우
      • Limit=0x0000_1234_456F_FFFF (64bit)

 

PCI 레거시 인터럽트 매핑

PCI 버스에 연결된 PCI 장치에서 사용하는 레거시 인터럽트의 매핑 구성 정보를 표현한다. 이 정보를 통해 각 PCI 버스의 INTA ~ INTD 까지의 인터럽트 시그널이 상위 인터럽트 컨트롤러의 어떠한 인터럽트 번호에 매핑되었는지 알아낼 수 있다. 참고로 디바이스 트리를 사용하지 않는 PC의 경우 ACPI 바이오스 정보를 읽어 매핑한다. 또한 PCI express의 경우 레거시 IRQ가 없지만 PCI express 포트에 PCI 브릿지를 연결하면 PCI 디바이스를 연결할 수도 있으므로 레거시 인터럽트 매핑 정보를 구성해야 하는 경우도 있다.

 

interrupt-map 속성

다음과 같이 5개 부분으로 구성된다.

  • child unit 주소
    • #address-cells 값이 지정한 셀 수만큼의 32비트 주소를 사용한다.
    • 아래 예에서는 3개의 32비트 주소 값을 사용한다.
  • child 인터럽트 지정 번호
    • 인터럽트 핀 번호인 1~4를 지정한다.
  • interrupt parent
    • 부모 인터럽트 컨트롤러 노드에 해당하는 phandle 값
  • parent unit 주소
    • 부모 인터럽트 노드에 있는 #address-cells 값이 지정한 셀 수 만큼 32bit 주소를 사용한다.
      • 아래는 1개의 unit 주소를 사용하였다.
  • parent interrupt 번호 및 트리거 타입
    • 부모 인터럽트 노드의 #interrupt-cells 값이 지정한 셀 수 만큼의 32bit 값을 사용한다. (보통 1 ~ 4 셀)
    • 아래는 2개의 셀을 사용하였다.
      • generic하게 2 개의 셀을 사용하는 경우 처음 셀은 인터럽트 번호, 두 번째는 인터럽트 타입을 지정한다.
        • pci에서는 인터럽트 타입에 IRQ_TYPE_LEVEL_HIGH를 사용한다.

interrupt-map-mask 속성

위의 레거시 인터럽트 값의 유효 값을 필터하기 위해 사용하는 마스크 정보로 다음과 같이 2개 부분으로 구성된다.

  • child unit 주소에 대한 마스크
  • child 인터럽트 지정 번호에 대한 마스크

 

아래 interrupt-map 속성의 4번째와 5번째 박스의 셀은 부모 인터럽트 컨트롤러의 #interrupt-cells 속성 값 만큼의 셀 수로 구성된다.

  • 참고로 arm64의 gic 컨트롤러는 3개의 셀로 구성된다.
    • 첫 번째 셀은 irq-unit 번호로 GIC_SPI(0) 또는 GIC_PPI(1)이 사용된다.
    • 두 번째 셀은 인터럽트 번호로 첫 번째 인자 값에 따라 hwirq 번호가 산출된다.
      • GIC_PPI(1)를 사용하는 경우에는 이 값에 + 16을 한 값이 hwirq 번호이다.
      • GIC_SPI(0)를 사용하는 경우에는 이 값에 + 32를 한 값이 hwirq 번호이다.
    • 세 번째 셀은 인터럽트 트리거 타입을 지정한다.
      • IRQ_TYPE_NONE(0)
      • IRQ_TYPE_EDGE_RISING(1)
      • IRQ_TYPE_EDGE_FALLING(2)
      • IRQ_TYPE_LEVEL_HIGH(4)
      • IRQ_TYPE_LEVEL_LOW(8)

 

PCI Host Controller(PCIe RC) 구현

다음과 같이 플랫폼 드라이버를 준비한다. 특별히 다른 버스 드라이버와 차이점은 없다.

static struct platform_driver foo_pci_driver = {
        .driver = {
                .name = "foo-pci",
                .of_match_table = foo_pci_of_match,
        },
        .probe = foo_pci_probe,
        .remove = foo_pci_remove,
};
module_platform_driver(foo_pci_driver);

 

디바이스 트리 노드와 매치 될 수 있도록 compatible 속성값을 정한다.

static const struct of_device_id foo_pci_of_match[] = {
        { .compatible = "company,foo" },
        { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, foo_pci_of_match);

 

foo_pcie 구조체

다음과 같이 pcie host controller를 관리할 수 있는 구조체 샘플을 보여준다.

struct foo_pcie {
        struct device *dev;
        void __iomem *base;
        phys_addr_t base_addr;
        struct resource mem;
        struct pci_bus *root_bus;
        struct phy *phy;
        int (*map_irq)(const struct pci_dev *, u8, u8);
        bool need_ib_cfg;
        int nr_ib_regions;
};
  • *dev
    • pcie 호스트 컨트롤러를 가리키는 디바이스
  • *base
    • PCIe 호스트 컨트롤러 레지스터 가상 주소 base
  • base_addr
    • PCIe 호스트 컨트롤러 레지스터 물리 주소 base
  • mem
    • 메모리 리소스
  • *root_bus
    • pci 루트 버스
  • *phy
    • Serdes를 컨트롤하는 PHY 디바이스
  • (*map_irq)
    • 인터럽트 번호를 알아올 후크 함수
  • *msi
    • msi 데이터

 

foo_pci_probe()

다음 샘플 드라이버 코드와 의미를 알아본다.

static int foo_pci_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        struct foo_pcie *pcie;
        struct device_node *np = dev->of_node;
        struct resource reg;
        resource_size_t iobase = 0;
        LIST_HEAD(resources);
        struct pci_host_bridge *bridge;
        int ret;

        bridge = devm_pci_alloc_host_bridge(dev, sizeof(*pcie));
        if (!bridge)
                return -ENOMEM;

        pcie = pci_host_bridge_priv(bridge);
        platform_set_drvdata(pdev, pcie);
        pcie->dev = dev;

        ret = of_address_to_resource(np, 0, &reg);
        if (ret < 0) { 
                dev_err(dev, "unable to obtain controller resources\n"); 
                return ret; 
        } 
        pcie->base = devm_pci_remap_cfgspace(dev, reg.start,
                                             resource_size(&reg));
        if (!pcie->base) {
                dev_err(dev, "unable to map controller registers\n");
                return -ENOMEM;
        }
        pcie->base_addr = reg.start;

        pcie->need_ib_cfg = of_property_read_bool(np, "dma-ranges");

        /* PHY use is optional */
        pcie->phy = devm_phy_get(dev, "pcie-phy");
        if (IS_ERR(pcie->phy)) {
                if (PTR_ERR(pcie->phy) == -EPROBE_DEFER)
                        return -EPROBE_DEFER;
                pcie->phy = NULL;
        }

        ret = of_pci_get_host_bridge_resources(np, 0, 0xff, &resources,
                                               &iobase);
        if (ret) {
                dev_err(dev, "unable to get PCI host bridge resources\n");
                return ret;
        }

        /* for legacy irq */
        pcie->map_irq = of_irq_parse_and_map_pci;

        ret = foo_pcie_setup(pcie, &resources);
        if (ret) {
                dev_err(dev, "PCIe controller setup failed\n");
                pci_free_resource_list(&resources);
                return ret;
        }

        return 0;
}

이 probe 함수는 pci 호스트 컨트롤러 드라이버를 구동한다.

  • 코드 라인 12~14에서 관리할 드라이버에 대한 구조체를 생성한다. devm_으로 시작하는 함수는 드라이버 언로드 시 자동으로 release 처리한다.
    • 예) struct pci_host_bridge 와 struct foo_pci 두 구조체가 생성된다.
  • 코드 라인 16~17에서 위에서 생성한 브리지가 가리키는 struct foo_pci 주소를 알아와서 private 드라이버 데이터로 활용하기 위해 저장한다.
  • 코드 라인 20~24에서 “reg” 속성을 읽어 리소스로 알아온다.
  • 코드 라인 25~31에서 pcie 호스트 컨트롤러 레지스터 리소스를 가상 주소에 매핑하고 가상 주소를 base 멤버 변수에, 그리고 물리 주소 base_addr 멤버 변수에 저장한다.
  • 코드 라인 33에서 인바운드 매핑이 필요한 지 여부를 “dma-ranges” 속성 값이 있는지 확인한다.
  • 코드 라인 36~41에서 phy-names 속성 값이 “pcie-phy”인 경우 phys 속성의 phandle 값이 가리키는 노드에 해당하는 phy 핸들을 알아온다.
  • 코드 라인 43~48에서 호스트 브리지 리소스를 알아온다.
  • 코드 라인 51에서 pci 디바이스에서 사용하는 legacy irq를 할당하는 후크 함수를 지정한다.
    • of_property_match_string() 함수는 다음과 같은 동작을 수행한다.
      • pci configuration space의 PCI_INTERRUPT_PIN 레지스터(1 바이트)를 읽어 pci 핀 번호(0, 1~4)를 알아온다.
      • 디바이스 트리 노드의 “interrupt-map” 속성과 “interrupt-mask” 속성을 사용하여 pci 핀 번호에 해당하는 hwirq를 알아온다.
      • irq domain에서 hwirq와 virq를 매핑한다.
      • pci configuration space의 PCI_INTERRUPT_LINE 레지스터(1 바이트)에 virq를 기록한다.
      • virq를 반환한다.
  • 코드 라인 53~58에서 pcie 호스트 컨트롤러를 사용하기 위해 pcie hw 설정을 수행한다.

 

foo_pcie_setup()

static int foo_pcie_setup(struct foo_pcie *pcie, struct list_head *res)
{
        struct device *dev;
        int ret;
        void *sysdata;
        struct pci_bus *child;
        struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie);

        dev = pcie->dev;

        ret = devm_request_pci_bus_resources(dev, res);
        if (ret)
                return ret;

        ret = phy_init(pcie->phy);
        if (ret) {
                dev_err(dev, "unable to initialize PCIe PHY\n");
                return ret;
        }

        ret = phy_power_on(pcie->phy);
        if (ret) {
                dev_err(dev, "unable to power on PCIe PHY\n");
                goto err_exit_phy;
        }

        /* pcie hw clock & regulator control */
        /* ...(ignore)... */

        /* pcie hw outbound map(ranges) control */
        /* ...(ignore)... */ 

        /* pcie hw inbound map(dma-ranges) control */
        /* ...(ignore)... */ 
        sysdata = pcie;

        ret = foo_pcie_check_link(pcie);
        if (ret) {
                dev_err(dev, "no PCIe EP device detected\n");
                goto err_power_off_phy;
        }

        /* pcie hw enable */
        /* ...(ignore)... */ 

        /* pcie msi related */
        /* ...(ignore)... */
 
        list_splice_init(res, &host->windows);
        host->busnr = 0;
        host->dev.parent = dev;
        host->ops = &foo_pcie_ops;
        host->sysdata = sysdata;
        host->map_irq = pcie->map_irq;
        host->swizzle_irq = pci_common_swizzle;

        ret = pci_scan_root_bus_bridge(host);
        if (ret < 0) {
                dev_err(dev, "failed to scan host: %d\n", ret);
                goto err_power_off_phy;
        }

        pci_assign_unassigned_bus_resources(host->bus);

        pcie->root_bus = host->bus;

        list_for_each_entry(child, &host->bus->children, node)
                pcie_bus_configure_settings(child);

        pci_bus_add_devices(host->bus);

        return 0;

err_power_off_phy:
        phy_power_off(pcie->phy);
err_exit_phy:
        phy_exit(pcie->phy);
        return ret;
}

pcie 호스트 컨트롤러(Root Complex)를 사용하기 위해 pcie hw 설정을 수행한다.

  • 코드 라인 11~13에서 pci 리소스를 사용한다고 요청한다. 물리 영역에 이미 사용 중인 리소스 영역이 존재하면 에러를 반환한다.
    • 요청된 리소스의 주소 범위 확인은 cat /proc/iomem 으로 확인할 수 있다.
    • 리소스를 매핑(ioremap)하기 전에 request 하여 중복 영역 체크를 하는 것이 좋다. 또한 /proc/iomem 을 통해 관리하기 좋다.
  • 코드 라인 15~19에서 pci 디바이스의 전송 라인에 연결된 phy 장치를 초기화한다.
    • phy는 전송 장치의 물리 layer 끝에 위치하는 장치이고 다음 두 부분을 포함한다.
      • 코드화를 담당하는 Physical Coding Sublayer (PCS) 부분
      • 광 케이블 또는 cooper 케이블과 연결되는 Physical Medium Dependent (PMD) 부분
    • 바로 옆에 연결된 chip들과의 통신에는 phy가 사용되지 않지만, 수십센티 이상 조금이라도 거리가 먼 장치와의 통신을 위해 몇 가지 기술들을 사용한다.
      • layer시그널 전압 및 전류를 높이고 변조 및 코드화 시켜 출력한다.
      • 고속 전송 시 노이즈 제거를 위해 differential 등의 기술을  사용하기도 한다.
      • 슬롯, 커넥터 등을 통해 연결되는 거의 모든 장치와의 통신에 대부분 사용된다.
        • SDRAM, Flash Memory, SATA, PCIe, USB, Ethernet, Wifi, Wimax, …
    • drivers/phy 디렉토리에 phy 관련 드라이버들이 존재한다.
  • 코드 라인 21~25에서 phy 전원을 on 시킨다.
  • 코드 라인 28에서 pci 호스트 컨트롤러의 클럭과 전원 공급 장치(레귤레이터)와 관련한 hw 설정을 수행한다.
    • pci 장치마다 레지스터를 조작하는 규격이 다르므로 해당 칩의 데이터 시트 및 프로그래머스 매뉴얼을 참고해야 한다.
  • 코드 라인 31에서 pci 호스트 컨트롤러의 outbound 매핑 관련 레지스터를 설정한다.
    • outbound 매핑이 고정되어 제공되는 경우 제어를 할 수 없다.
  • 코드 라인 34에서 pci 호스트 컨트롤러에서 dma 장치를 사용하는 pci 디바이스의 연결을 위해 inbound 매핑 관련 레지스터를 설정한다.
    • inbound 매핑이 고정되어 제공되는 경우 제어를 할 수 없다.
  • 코드 라인 37~41에서 pcie root complex와 연결된 PCIe end point 디바이스와의 링크 연결 여부를 확인한다. (구현 함수는 생략)
    • 참고: iproc_pcie_check_link()
  • 코드 라인 44에서 pci 호스트 컨트롤러를 enable 하는 레지스터를 설정한다.
  • 코드 라인 47에서 pci 호스트 컨트롤러에서 msi를 준비한다.
  • 코드 라인 49~61에서 pcie 오퍼레이션을 준비한 후 루트 버스 브리지를 스캔한다.
    • 이 버스에 연결된 하위 버스 및 디바이스를 스캔한다.
  • 코드 라인 63에서
  • 코드 라인 67~68에서
  • 코드 라인 70에서 버스에 연결된 pci 디바이스들을 등록한다.

 

PCI operation 지정

static struct pci_ops foo_pcie_ops = {
        .map_bus = foo_pcie_bus_map_cfg_bus,
        .read = foo_pcie_config_read32,
        .write = foo_pcie_config_write32,
};
  • (*map_bus)
    • 요청하는 버스 및 devfn에 해당하는 pci configuraton space에서 접근할 수 있도록 pci 호스트 컨트롤러의 관련 레지스터 설정을 하는 후크 함수를 지정한다.
  • (*read)
    • 요청하는 버스 및 devfn에 해당하는 pci configuraton space에 원하는 위치의 데이터를 읽어오는 후크 함수를 지정한다.
    • 1, 2, 4 바이트
  • (*write)
    • 요청하는 버스 및 devfn에 해당하는 pci configuraton space의 원하는 위치에 데이터를 기록하는 후크 함수를 지정한다.
    • 1, 2, 4 바이트

 

디바이스 트리 사례 (for arm64)

1) arm/juno-base.dtsi
ranges = <0x01000000 0x00 0x00000000 0x00 0x5f800000 0x0 0x00800000>,  /* relocatable I/O */
         <0x02000000 0x00 0x50000000 0x00 0x50000000 0x0 0x08000000>,  /* relocatable MEM */
         <0x42000000 0x40 0x00000000 0x40 0x00000000 0x1 0x00000000>;  /* relocatable, pre-fetchable MEM */
interrupt-map-mask = <0 0 0 7>;
interrupt-map = <0 0 0 1 &gic 0 0 0 136 4>,
                <0 0 0 2 &gic 0 0 0 137 4>,
                <0 0 0 3 &gic 0 0 0 138 4>,
                <0 0 0 4 &gic 0 0 0 139 4>;

 

2) marvell/armada-37xx.dtsi
ranges = <0x82000000 0 0xe8000000   0 0xe8000000 0 0x1000000 /* non-relocatable MEM */
          0x81000000 0 0xe9000000   0 0xe9000000 0 0x10000>; /* non-relocatable I/O */
interrupt-map-mask = <0 0 0 7>;
interrupt-map = <0 0 0 1 &pcie_intc 0>,
                <0 0 0 2 &pcie_intc 1>,
                <0 0 0 3 &pcie_intc 2>,
                <0 0 0 4 &pcie_intc 3>;

 

3) marvell/armada-cp110-master.dtsi
ranges = <0x81000000 0 0xf9000000 0 0xf9000000 0 0x10000     /* non-relocatable I/O */
          0x82000000 0 0xf6000000 0 0xf6000000 0 0xf00000>;  /* non-relocatable, MEM */
interrupt-map-mask = <0 0 0 0>;
interrupt-map = <0 0 0 0 &cpm_icu ICU_GRP_NSR 22 IRQ_TYPE_LEVEL_HIGH>;

ranges = <0x81000000 0 0xf9010000 0 0xf9010000 0 0x10000     /* non-relocatable I/O */
          0x82000000 0 0xf7000000 0 0xf7000000 0 0xf00000>;  /* non-relocatable MEM */
interrupt-map-mask = <0 0 0 0>;
interrupt-map = <0 0 0 0 &cpm_icu ICU_GRP_NSR 24 IRQ_TYPE_LEVEL_HIGH>;

 

4) amd/amd-seattle-soc.dtsi
ranges = <0x01000000 0x00 0x00000000 0x00 0xefff0000 0x00 0x00010000>,   /* relocatable I/O (size=64K) */
         <0x02000000 0x00 0x40000000 0x00 0x40000000 0x00 0x80000000>,   /* relocatable MEM (size=2G) */
         <0x03000000 0x01 0x00000000 0x01 0x00000000 0x7f 0x00000000>;   /* relocatable MEM64 (size= 124G) */
interrupt-map-mask = <0xf800 0x0 0x0 0x7>;
interrupt-map = <0x1000 0x0 0x0 0x1 &gic0 0x0 0x0 0x0 0x120 0x1>,
                <0x1000 0x0 0x0 0x2 &gic0 0x0 0x0 0x0 0x121 0x1>,
                <0x1000 0x0 0x0 0x3 &gic0 0x0 0x0 0x0 0x122 0x1>,
                <0x1000 0x0 0x0 0x4 &gic0 0x0 0x0 0x0 0x123 0x1>;
dma-ranges = <0x43000000 0x0 0x0 0x0 0x0 0x100 0x0>;

 

5) freescale/fsl-ls1043a.dtsi
ranges = <0x81000000 0x0 0x00000000 0x40 0x00010000 0x0 0x00010000   /* non-relocatable I/O */
          0x82000000 0x0 0x40000000 0x40 0x40000000 0x0 0x40000000>; /* non-relocatable MEM */
interrupt-map-mask = <0 0 0 7>;
interrupt-map = <0000 0 0 1 &gic 0 110 0x4>,
                <0000 0 0 2 &gic 0 111 0x4>,
                <0000 0 0 3 &gic 0 112 0x4>,
                <0000 0 0 4 &gic 0 113 0x4>;

 

6) renesas/r8a7795.dtsi
ranges = <0x01000000 0 0x00000000 0 0xfe100000 0 0x00100000    /* relocatable I/O */
          0x02000000 0 0xfe200000 0 0xfe200000 0 0x00200000    /* relocatable MEM */
          0x02000000 0 0x30000000 0 0x30000000 0 0x08000000    /* relocatable MEM */
          0x42000000 0 0x38000000 0 0x38000000 0 0x08000000>;  /* relocatable, pre-fetchable MEM */
interrupt-map-mask = <0 0 0 0>;
interrupt-map = <0 0 0 0 &gic GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>;
dma-ranges = <0x42000000 0 0x40000000 0 0x40000000 0 0x40000000>;

 

7) broadcom/northstar2/ns2.dtsi
ranges = <0x83000000 0 0x00000000 0 0x30000000 0 0x20000000>;   /* non-relocatable MEM64 */
interrupt-map-mask = <0 0 0 0>;
interrupt-map = <0 0 0 0 &gic 0 GIC_SPI 305 IRQ_TYPE_NONE>;

brcm,pcie-ob;
brcm,pcie-ob-oarr-size;
brcm,pcie-ob-axi-offset = <0x30000000>;
brcm,pcie-ob-window-size = <256>;

northstar2 전용 속성 4개를 사용하여 pci 주소를 axi 버스에 outbound 매핑 주소를 일부 변경할 수 있다.

  • pcie-ob
    • ns2의 PCIe Root Complex 의 디폴트 아웃바운드 매핑을 변경할 수 있게한다.
    • ns2 디폴트 Oubound 매핑:
      • PCI 주소: 0x0 ~ 0x1fffffff -> 호스트 CPU 물리 주소: 0x30000000 ~ 0x4fffffff (사이즈: 512MB)
  • pcie-ob-oarr-size
    • ns2의 PCIe Root Complex 의 디폴트 아웃바운드 매핑 사이즈를 변경할 수 있게한다.
    • ns2 디폴트 Oubound 매핑 사이즈: 512MB
  • pcie-ob-axi-offset
    • ns2의 PCIe Root Complex 의 호스트 CPU 물리 주소의 offset을 지정한다.
    • 예에서는 PCI 주소 0x0에 해당하는 AXI 물리 주소와의 간격(offset)은 0x30000000이다.
    • 참고로 ranges 속성 값 중 CPU 물리 주소에서 이 값을 뺀 차이 값이 OARR 레지스터에 기록한다.
      • CPU 물리 주소(0x30000000) – pcie-ob-axi-offset 속성값(0x30000000) = 0x0 (OARR 레지스터에 반영)
    • 참고로 ranges 속성 값 중 PCI 주소 값은 OMAP 레지스터에 기록한다.
      • PCI 주소 값(0x0)을 OMAP 레지스터에 반영
  • pcie-ob-window-size
    • ns2의 PCIe Root Complex가 아웃바운드 매핑을 변경할 때 사용할 사이즈를 지정한다.
      • 128, 256, 512 만 사용할 수 있다. (단위: MB)

 

다음 실제 사례를 참고한다.

  • NS2는 외부 PCIe 장치를 위해 PCIe Root Complex를 상황에 따라 기본 2개에서 부터 최대 8개로 변경할 수 있다.
    • A 포트 및 B 포트를 각각 1~4x 레인을 사용하는 1개의 RC로 구성할 수 있다. (디폴트)
    • A 포트 및 B 포트를 각각 1~2x 레인만 사용하는 2개의 RC로 구성할 수 있다.
    • A 포트 및 B 포트를 각각 1~2x 레인을 사용하는 1개의 RC와 1x 레인을 사용하는 2개의 RC로 구성할 수 있다.
    • A 포트 및 B 포트를 각각 1x 레인만 사용하는 4개의 RC로 구성할 수 있다.

 

아래 예는 B 포트를 1x 레인만을 사용하는 4개의 RC 구성으로 사용한 디바이스 트리 설정 사례를 보여준다.

  • 디폴트 Outbound 매핑은 다음과 같다. (1~4x 레인을 사용)
    • ranges = <0x83000000 0 0x0 0 0x30000000 0 0x20000000>;
    • PCI 주소: 0x0 ~ 0x1fffffff -> 호스트 CPU 물리 주소: 0x30000000 ~ 0x4fffffff (사이즈: 512MB)
  • B 포트를 4개의 RC로 사용하는 경우 아래 구성과 같이 128MB 씩 영역을 나누어 outbound 매핑할 수 있다.

 

실제 ns2의 Root Complex 각 포트의 주소 영역은 어떻게 제한되어 있을 까 알아본다.

  • 참고로 128M 단위로 영역을 나눈 사이즈를 지정하지만 실제 RC의 PCI 주소 영역은 20MB 및 28M의 영역으로 제한되어 있음을 알 수 있다.

 

8) cavium/thunder2-99xx.dtsi
ranges = <0x02000000    0 0x40000000    0 0x40000000    0 0x20000000       /* relocatable MEM */
          0x43000000 0x40 0x00000000 0x40 0x00000000 0x20 0x00000000>;     /* relocatable, pre-fetchable MEM64 */
interrupt-map-mask = <0 0 0 7>;
interrupt-map = /* addr  pin  ic   icaddr            icintr */
                  <0 0 0  1  &gic   0 0    GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH
                   0 0 0  2  &gic   0 0    GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH
                   0 0 0  3  &gic   0 0    GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH
                   0 0 0  4  &gic   0 0    GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>;

 

9) nvidia/tegra132.dtsi
ranges = <0x82000000 0 0x01000000 0x0 0x01000000 0 0x00001000   /* non-relocatable MEM (port 0 cfg space) */
          0x82000000 0 0x01001000 0x0 0x01001000 0 0x00001000   /* non-relocatable MEM (port 1 cfg space) */
          0x81000000 0 0x0        0x0 0x12000000 0 0x00010000   /* non-relocatable I/O (64 KiB) */
          0x82000000 0 0x13000000 0x0 0x13000000 0 0x0d000000   /* non-relocatable MEM (208 MiB) */
          0xc2000000 0 0x20000000 0x0 0x20000000 0 0x20000000>; /* non-relocatable, pre-fetchable MEM (512 MiB) */
interrupt-map-mask = <0 0 0 0>;
interrupt-map = <0 0 0 0 &gic GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;

pci@1,0 {
        assigned-addresses = <0x82000800 0 0x01000000 0 0x1000>; /* non-relocatable MEM (4K) */
        ...
};

pci@2,0 {
        assigned-addresses = <0x82001000 0 0x01001000 0 0x1000>; /* non-relocatable MEM (4K) */
        ... 
};

하위 버스에 대해 range를 부여할 수 있다.

 

참고

 

  • PCI Bus Binding to: IEEE Std 1275-1994 | www.devicetree.org – 다운로드 pdf
  • Open Firmware Recommended Practice: Interrupt Mapping | www.devicetree.org – 다운로드 pdf

PCI Subsystem -2- (Core)

<kernel v4.14>

 

PCI 서브시스템 초기화

pci_driver_init()

drivers/pci/pci-driver.c

static int __init pci_driver_init(void)
{
	return bus_register(&pci_bus_type);
}
postcore_initcall(pci_driver_init);

아래 pci 버스 타입을 등록한다.

 

drivers/pci/pci-driver.c

struct bus_type pci_bus_type = {
	.name		= "pci",
	.match		= pci_bus_match,
	.uevent		= pci_uevent,
	.probe		= pci_device_probe,
	.remove		= pci_device_remove,
	.shutdown	= pci_device_shutdown,
	.dev_groups	= pci_dev_groups,
	.bus_groups	= pci_bus_groups,
	.drv_groups	= pci_drv_groups,
	.pm		= PCI_PM_OPS_PTR,
	.num_vf		= pci_bus_num_vf,
};
EXPORT_SYMBOL(pci_bus_type);

 

PCI 디바이스 & 드라이버 매치

pci_bus_match()

drivers/pci/pci-driver.c

/**
 * pci_bus_match - Tell if a PCI device structure has a matching PCI device id structure
 * @dev: the PCI device structure to match against
 * @drv: the device driver to search for matching PCI device id structures
 *
 * Used by a driver to check whether a PCI device present in the
 * system is in its list of supported devices. Returns the matching
 * pci_device_id structure or %NULL if there is no match.
 */
static int pci_bus_match(struct device *dev, struct device_driver *drv)
{
	struct pci_dev *pci_dev = to_pci_dev(dev);
	struct pci_driver *pci_drv;
	const struct pci_device_id *found_id;

	if (!pci_dev->match_driver)
		return 0;

	pci_drv = to_pci_driver(drv);
	found_id = pci_match_device(pci_drv, pci_dev);
	if (found_id)
		return 1;

	return 0;
}

pci 버스에 디바이스나 드라이버가 추가될 때 서로 매치여부를 확인하기 위한 함수이다.

 

pci_match_device()

drivers/pci/pci-driver.c

/**
 * pci_match_device - Tell if a PCI device structure has a matching PCI device id structure
 * @drv: the PCI driver to match against
 * @dev: the PCI device structure to match against
 *
 * Used by a driver to check whether a PCI device present in the
 * system is in its list of supported devices.  Returns the matching
 * pci_device_id structure or %NULL if there is no match.
 */
static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
						    struct pci_dev *dev)
{
	struct pci_dynid *dynid;
	const struct pci_device_id *found_id = NULL;

	/* When driver_override is set, only bind to the matching driver */
	if (dev->driver_override && strcmp(dev->driver_override, drv->name))
		return NULL;

	/* Look at the dynamic ids first, before the static ones */
	spin_lock(&drv->dynids.lock);
	list_for_each_entry(dynid, &drv->dynids.list, node) {
		if (pci_match_one_device(&dynid->id, dev)) {
			found_id = &dynid->id;
			break;
		}
	}
	spin_unlock(&drv->dynids.lock);

	if (!found_id)
		found_id = pci_match_id(drv->id_table, dev);

	/* driver_override will always match, send a dummy id */
	if (!found_id && dev->driver_override)
		found_id = &pci_device_id_any;

	return found_id;
}

pci 버스에 등록된 pci 디바이스와 드라이버를 서로 매치시켜 매치된 pci_device_id 포인터를 반환한다. 만일 매치되지 않은 경우 null을 반환한다.

  • 코드 라인 17~18에서 디바이스에 사용할 드라이버명을 지정한 경우이다. 디바이스에 오버드라이브된 드라이버명이 아닌경우 null을 반환한다.
  • 코드 라인 21~28에서 dynids 리스트에 등록된 디바이스들부터 먼저 매치 확인을 한다.
  • 코드 라인 30~31에서 매치되지 않은 경우 드라이버에 지정된 id_table에서 매치 확인을 한다.
  • 코드 라인 34~35에서 여전히 매치되지 않은 경우이면서 드라이버 오버라이드 설정이 있는 경우 pci_device_id_any를 반환한다.

 

매치 우선 순위

  1. 다이나믹 pci 디바이스
  2. id_table을 사용하는 스태틱 pci 디바이스
  3. 오버드라이브 지정된 경우

pci 디바이스에 드라이버명으로 지정하여 오버드라이브 설정한 경우 해당 드라이버만 매치될 수 있다.

 

pci_match_one_device()

drivers/pci/pci.h

/**
 * pci_match_one_device - Tell if a PCI device structure has a matching
 *                        PCI device id structure
 * @id: single PCI device id structure to match
 * @dev: the PCI device structure to match against
 *
 * Returns the matching pci_device_id structure or %NULL if there is no match.
 */
static inline const struct pci_device_id *
pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev)
{
	if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) &&
	    (id->device == PCI_ANY_ID || id->device == dev->device) &&
	    (id->subvendor == PCI_ANY_ID || id->subvendor == dev->subsystem_vendor) &&
	    (id->subdevice == PCI_ANY_ID || id->subdevice == dev->subsystem_device) &&
	    !((id->class ^ dev->class) & id->class_mask))
		return id;
	return NULL;
}

다음 다음 매치 조건에 대해 모두 만족한 경우 pci_device_id를 그대로 반환하고, 매치되지 않은 경우 null을 반환한다.

  • 벤더명, 디바이스명, 서브벤더명 및 서브디바이스명을 지정하거나 PCI_ANY_ID를 기재한 경우 true이다.
  • 클래스도 동일해야 한다.

 

pci_match_id()

drivers/pci/pci-driver.c

/**
 * pci_match_id - See if a pci device matches a given pci_id table
 * @ids: array of PCI device id structures to search in
 * @dev: the PCI device structure to match against.
 *
 * Used by a driver to check whether a PCI device present in the
 * system is in its list of supported devices.  Returns the matching
 * pci_device_id structure or %NULL if there is no match.
 *
 * Deprecated, don't use this as it will not catch any dynamic ids
 * that a driver might want to check for.
 */
const struct pci_device_id *pci_match_id(const struct pci_device_id *ids,
					 struct pci_dev *dev)
{
	if (ids) {
		while (ids->vendor || ids->subvendor || ids->class_mask) {
			if (pci_match_one_device(ids, dev))
				return ids;
			ids++;
		}
	}
	return NULL;
}
EXPORT_SYMBOL(pci_match_id);

지정한 pci_device_id들 중 벤더, 서브벤더, 클래스 마스크 중 하나라도 지정된 경우 매치 여부를 확인한다. 매치가 확인되지 않는 경우 null을 반환한다.

 

PCI 드라이버 Probe

pci_device_probe()

drivers/pci/pci-driver.c

static int pci_device_probe(struct device *dev)
{
	int error;
	struct pci_dev *pci_dev = to_pci_dev(dev);
	struct pci_driver *drv = to_pci_driver(dev->driver);

	pci_assign_irq(pci_dev);

	error = pcibios_alloc_irq(pci_dev);
	if (error < 0)
		return error;

	pci_dev_get(pci_dev);
	if (pci_device_can_probe(pci_dev)) {
		error = __pci_device_probe(drv, pci_dev);
		if (error) {
			pcibios_free_irq(pci_dev);
			pci_dev_put(pci_dev);
		}
	}

	return error;
}

pci 디바이스의 legacy 인터럽트를 설정하고 probe를 진행한다.

  • 코드 라인 7에서 pci 디바이스가 사용할 legacy 인터럽트를 pci configuration 정보를 읽어 배정한다.
  • 코드 라인 9~11에서 pci 디바이스가 사용할 인터럽트를 ACPI를 사용하는 pc로부터 알아와 배정한다.
  • 코드 라인 13~20에서 pci 디바이스의 참조 카운터를 1 증가시킨 후 probe를 진행한다.

 

pci_device_can_probe()

drivers/pci/pci-driver.c

#ifdef CONFIG_PCI_IOV
static inline bool pci_device_can_probe(struct pci_dev *pdev)
{
        return (!pdev->is_virtfn || pdev->physfn->sriov->drivers_autoprobe);
}
#else
static inline bool pci_device_can_probe(struct pci_dev *pdev)
{
        return true;
}
#endif

pci_device_can_probe() 함수는 PCI IO Virtualization을 사용하지 않는 경우 항상 1이다.

  • PCI IO Virtualization을 사용하는 경우 drivers_autoprobe가 설정된 경우에만 1을 반환한다.

 

__pci_device_probe()

drivers/pci/pci-driver.c

/**
 * __pci_device_probe - check if a driver wants to claim a specific PCI device
 * @drv: driver to call to check if it wants the PCI device
 * @pci_dev: PCI device being probed
 *
 * returns 0 on success, else error.
 * side-effect: pci_dev->driver is set to drv when drv claims pci_dev.
 */
static int __pci_device_probe(struct pci_driver *drv, struct pci_dev *pci_dev)
{
	const struct pci_device_id *id;
	int error = 0;

	if (!pci_dev->driver && drv->probe) {
		error = -ENODEV;

		id = pci_match_device(drv, pci_dev);
		if (id)
			error = pci_call_probe(drv, pci_dev, id);
	}
	return error;
}

pci 디바이스에 대해 드라이버가 아직 지정되지 않았고 드라이버의 probe 후크 함수가 있으면 매치 id를 알아온 후 probe를 진행한다.

  • 드라이버가 이미 지정되어 있는 경우 매치나 probe 과정을 진행하지 않고 성공(0)을 반환한다.

 

pci_call_probe()

drivers/pci/pci-driver.c

static int pci_call_probe(struct pci_driver *drv, struct pci_dev *dev,
			  const struct pci_device_id *id)
{
	int error, node, cpu;
	struct drv_dev_and_id ddi = { drv, dev, id };

	/*
	 * Execute driver initialization on node where the device is
	 * attached.  This way the driver likely allocates its local memory
	 * on the right node.
	 */
	node = dev_to_node(&dev->dev);
	dev->is_probed = 1;

	cpu_hotplug_disable();

	/*
	 * Prevent nesting work_on_cpu() for the case where a Virtual Function
	 * device is probed from work_on_cpu() of the Physical device.
	 */
	if (node < 0 || node >= MAX_NUMNODES || !node_online(node) ||
	    pci_physfn_is_probed(dev))
		cpu = nr_cpu_ids;
	else
		cpu = cpumask_any_and(cpumask_of_node(node), cpu_online_mask);

	if (cpu < nr_cpu_ids)
		error = work_on_cpu(cpu, local_pci_probe, &ddi);
	else
		error = local_pci_probe(&ddi);

	dev->is_probed = 0;
	cpu_hotplug_enable();
	return error;
}

pci 디바이스가 있는 노드의 cpu의 워크큐를 사용하여 pci 드라이버의 probe를 호출한다.

  • 코드 라인 12에서 pci 디바이스에 해당하는 노드 번호를 알아온다.
  • 코드 라인 13에서 pci 디바이스가 probe 진행 중임을 마크한다.
  • 코드 라인 15에서 다른 cpu hotplug 작업이 수행 중인 경우 완료될 때까지 기다린 후 락을 획득한다.
  • 코드 라인 21~25에서 노드에서 동작하는 online cpu 중 하나의 cpu 번호를 알아온다. 만일 pci io virtualization이 동작하는 경우 현재 cpu를 사용한다.
  • 코드 라인 27~30에서 해당 cpu의 워크큐에서 local_pci_probe 함수를 호출하게 한다.
  • 코드 라인 32에서 pci 디바이스의 probe 진행이 완료되었으므로 클리어한다.
  • 코드 라인 33에서 cpu hotplug 락을 해제한다.

 

local_pci_probe()

drivers/pci/pci-driver.c

static long local_pci_probe(void *_ddi)
{
	struct drv_dev_and_id *ddi = _ddi;
	struct pci_dev *pci_dev = ddi->dev;
	struct pci_driver *pci_drv = ddi->drv;
	struct device *dev = &pci_dev->dev;
	int rc;

	/*
	 * Unbound PCI devices are always put in D0, regardless of
	 * runtime PM status.  During probe, the device is set to
	 * active and the usage count is incremented.  If the driver
	 * supports runtime PM, it should call pm_runtime_put_noidle(),
	 * or any other runtime PM helper function decrementing the usage
	 * count, in its probe routine and pm_runtime_get_noresume() in
	 * its remove routine.
	 */
	pm_runtime_get_sync(dev);
	pci_dev->driver = pci_drv;
	rc = pci_drv->probe(pci_dev, ddi->id);
	if (!rc)
		return rc;
	if (rc < 0) {
		pci_dev->driver = NULL;
		pm_runtime_put_sync(dev);
		return rc;
	}
	/*
	 * Probe function should return < 0 for failure, 0 for success
	 * Treat values > 0 as success, but warn.
	 */
	dev_warn(dev, "Driver probe function unexpectedly returned %d\n", rc);
	return 0;
}

pci 디바이스를 절전 모드에서 복구시킨 후 pm 참조 카운터를 증가시킨다. 그런 후 해당 pci 드라이버의 probe 함수를 호출한다.

 

Legacy IRQ 할당

pci_assign_irq()

drivers/pci/setup-irq.c

void pci_assign_irq(struct pci_dev *dev)
{
        u8 pin;
        u8 slot = -1;
        int irq = 0;
        struct pci_host_bridge *hbrg = pci_find_host_bridge(dev->bus);

        if (!(hbrg->map_irq)) {
                dev_dbg(&dev->dev, "runtime IRQ mapping not provided by arch\n");
                return;
        }

        /* If this device is not on the primary bus, we need to figure out
           which interrupt pin it will come in on.   We know which slot it
           will come in on 'cos that slot is where the bridge is.   Each
           time the interrupt line passes through a PCI-PCI bridge we must
           apply the swizzle function.  */

        pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);
        /* Cope with illegal. */
        if (pin > 4)
                pin = 1;

        if (pin) {
                /* Follow the chain of bridges, swizzling as we go.  */
                if (hbrg->swizzle_irq)
                        slot = (*(hbrg->swizzle_irq))(dev, &pin);

                /*
                 * If a swizzling function is not used map_irq must
                 * ignore slot
                 */
                irq = (*(hbrg->map_irq))(dev, slot, pin);
                if (irq == -1)
                        irq = 0;
        }
        dev->irq = irq;

        dev_dbg(&dev->dev, "assign IRQ: got %d\n", dev->irq);

        /* Always tell the device, so the driver knows what is
           the real IRQ to use; the device does not use it. */
        pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq);
}

pci 디바이스가 사용할 legacy 인터럽트를 pci configuration 정보를 읽어 배정한다. 자세한 동작은 다음과 같다.

  • 코드 라인 6에서 pci 버스에 대한 호스트 브리지를 구해온다.
  • 코드 라인 19~22에서 pci 디바이스의 기본 configuration 헤더 정보에서 인터럽트 핀(1~4)을 읽어온다. 단 인터럽트를 사용하지 않는 pci 디바이스는 0을 읽어온다.
    • PCI_INTERRUPT_PIN(0x3d)
  • 코드 라인 26~27에서 소스트 브리지에 구현된 (*swizzle_irq) 후크 함수를 통해 슬롯 번호를 알아온다.
  • 코드 라인 33~35에서 읽어온 핀 번호에 대해 호스트 브리지에 구현된 (*map_irq) 후크 함수를 통해 슬롯과 핀 인자를 전달하고 ACPI 또는 디바이스 트리의 hwirq를 읽어온 후 이에 매핑된 virq를 알아온다.
  • 코드 라인 37~39에서 dev->irq에 설정하고 로그 메시지를 출력한다.
  • 코드 라인 43에서 pci 디바이스의 기본 configuratio 헤더 정보 중 인터럽트 라인에 인터럽트 번호를 기록한다.
    • PCI_INTERRUPT_LINE(0x3c)

 

pci 드라이버 등록

pci_register_driver()

include/linux/pci.h

/*
 * pci_register_driver must be a macro so that KBUILD_MODNAME can be expanded
 */
#define pci_register_driver(driver)		\
	__pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)

pci 드라이버의 등록은 위의 매크로 함수를 통해 등록할 수 있다.

 

__pci_register_driver()

drivers/pci/pci-driver.c

/**
 * __pci_register_driver - register a new pci driver
 * @drv: the driver structure to register
 * @owner: owner module of drv
 * @mod_name: module name string
 *
 * Adds the driver structure to the list of registered drivers.
 * Returns a negative value on error, otherwise 0.
 * If no error occurred, the driver remains registered even if
 * no device was claimed during registration.
 */
int __pci_register_driver(struct pci_driver *drv, struct module *owner,
                          const char *mod_name)
{
        /* initialize common driver fields */
        drv->driver.name = drv->name;
        drv->driver.bus = &pci_bus_type;
        drv->driver.owner = owner;
        drv->driver.mod_name = mod_name;
        drv->driver.groups = drv->groups;

        spin_lock_init(&drv->dynids.lock);
        INIT_LIST_HEAD(&drv->dynids.list);

        /* register with core */
        return driver_register(&drv->driver);
}
EXPORT_SYMBOL(__pci_register_driver);

pci 버스에 요청한 pci 드라이버를 등록한다.

  • 다이나믹하게 pci 디바이스의 id를 관리하기 위해 dynids를 사용한다.

 

pci_driver 구조체

pci 드라이버를 등록할 때 사용하는 구조체이다.

include/linux/pci.h

struct pci_driver {
	struct list_head node;
	const char *name;
	const struct pci_device_id *id_table;	/* must be non-NULL for probe to be called */
	int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id);	/* New device inserted */
	void (*remove) (struct pci_dev *dev);	/* Device removed (NULL if not a hot-plug capable driver) */
	int  (*suspend) (struct pci_dev *dev, pm_message_t state);	/* Device suspended */
	int  (*suspend_late) (struct pci_dev *dev, pm_message_t state);
	int  (*resume_early) (struct pci_dev *dev);
	int  (*resume) (struct pci_dev *dev);	                /* Device woken up */
	void (*shutdown) (struct pci_dev *dev);
	int (*sriov_configure) (struct pci_dev *dev, int num_vfs); /* PF pdev */
	const struct pci_error_handlers *err_handler;
	const struct attribute_group **groups;
	struct device_driver	driver;
	struct pci_dynids dynids;
};
  • *name
    • 드라이버 이름
  • *id_table
    • 매치하여 사용할 pci 디바이스의 id 테이블을 지정한다.
  • (*probe)
    • 새로운 디바이스가 추가될 때 probe 후크 함수를 지정한다.
  • (*remove)
    • 디바이스가 제거될 때 호출할 후크 함수를 지정한다.
  • (*suspend),
  • (*suspend_late),
  • (*resume_early),
  • (*resume),
  • (*shutdown)
    • 절전 관련 후크 함수들이며 legacy pci 드라이버 호환을 위해 사용된다.
    • 최근 인터페이스는 pci 드라이버에 임베드 되어 있는 driver->pm을 사용한다.
  • (*sriov_configure)
    • 동작 시킬 VF(가상 기능) 수를 지정할 수 있는 싱글 루트 I/O 가상화 설정용 후크 함수이다.
  • *err_handler
    • pci 버스 에러 이벤트를 처리할 콜백들이 담긴 pci_error_handlers 구조체를 지정한다.
  • **groups
    • 드라이버용 속성 그룹 배열을 지정한다.
  • driver
    • 임베드된 드라이버이다.
  • dynids
    • 다이나믹 id를 관리할 리스트이다.

 

PCI 장치 스캔

루트 버스 브리지 스캔

pci_scan_root_bus_bridge()

drivers/pci/probe.c

int pci_scan_root_bus_bridge(struct pci_host_bridge *bridge)
{
        struct resource_entry *window;
        bool found = false;
        struct pci_bus *b;
        int max, bus, ret;

        if (!bridge)
                return -EINVAL;

        resource_list_for_each_entry(window, &bridge->windows)
                if (window->res->flags & IORESOURCE_BUS) {
                        found = true;
                        break;
                }

        ret = pci_register_host_bridge(bridge);
        if (ret < 0)
                return ret;

        b = bridge->bus;
        bus = bridge->busnr;

        if (!found) {
                dev_info(&b->dev,
                 "No busn resource found for root bus, will use [bus %02x-ff]\n",
                        bus);
                pci_bus_insert_busn_res(b, bus, 255);
        }

        max = pci_scan_child_bus(b);

        if (!found)
                pci_bus_update_busn_res_end(b, max);

        return 0;
}
EXPORT_SYMBOL(pci_scan_root_bus_bridge);

루트 버스 브리지를 스캔한다.

  • 코드 라인 8~9에서 브릿지가 null인 경우 -EINVAL 에러를 반환한다.
  • 코드 라인 11~15에서 플랫폼 리소스 중 bus 리소스가 있는 경우에만 found 값을 true로 설정하고 루프를 벗어난다.
  • 코드 라인 17~19에서 호스트 브릿지를 등록한다.
  • 코드 라인 21~29에서 버스 리소스가 발견되지 않은 경우 인자로 요청한 버스의 버스 번호 부터 0xff 까지의 버스 번호를 사용하도록 버스 리소스를 생성하여 추가한다.
    • 만일 처음 호스트 브릿지가 등록되는 경우라면 버스 번호 0번 부터 255번까지 리소스가 등록된다.
  • 코드 라인 31에서 child 버스를 스캔한다.
  • 코드 라인 33~34에서 처음 버스 리소스가 발견되지 않아 생성하여 추가한 경우 child 버스의 버스 번호까지 버스 리소스를 갱신한다.

 

차일드 버스 스캔

pci_scan_child_bus()

drivers/pci/probe.c

unsigned int pci_scan_child_bus(struct pci_bus *bus)
{
        unsigned int devfn, pass, max = bus->busn_res.start;
        struct pci_dev *dev;

        dev_dbg(&bus->dev, "scanning bus\n");

        /* Go find them, Rover! */
        for (devfn = 0; devfn < 0x100; devfn += 8)
                pci_scan_slot(bus, devfn);

        /* Reserve buses for SR-IOV capability. */
        max += pci_iov_bus_range(bus);

        /*
         * After performing arch-dependent fixup of the bus, look behind
         * all PCI-to-PCI bridges on this bus.
         */
        if (!bus->is_added) {
                dev_dbg(&bus->dev, "fixups for bus\n");
                pcibios_fixup_bus(bus);
                bus->is_added = 1;
        }

        for (pass = 0; pass < 2; pass++)
                list_for_each_entry(dev, &bus->devices, bus_list) {
                        if (pci_is_bridge(dev))
                                max = pci_scan_bridge(bus, dev, max, pass);
                }

        /*
         * Make sure a hotplug bridge has at least the minimum requested
         * number of buses.
         */
        if (bus->self && bus->self->is_hotplug_bridge && pci_hotplug_bus_size) {
                if (max - bus->busn_res.start < pci_hotplug_bus_size - 1)
                        max = bus->busn_res.start + pci_hotplug_bus_size - 1;

                /* Do not allocate more buses than we have room left */
                if (max > bus->busn_res.end)
                        max = bus->busn_res.end;
        }

        /*
         * We've scanned the bus and so we know all about what's on
         * the other side of any bridges that may be on this bus plus
         * any devices.
         *
         * Return how far we've got finding sub-buses.
         */
        dev_dbg(&bus->dev, "bus scan returning with max=%02x\n", max);
        return max;
}
EXPORT_SYMBOL_GPL(pci_scan_child_bus);

child pci 버스를 스캔한다.

  • 코드 라인 9~10에서 전체 슬롯을 스캔한다.
  • 코드 라인 13에서 virtual function이 사용하는 버스 번호를 reserve하기 위해 max 값을 그 만큼 건너뛴다.
  • 코드 라인 19~23에서 버스가 처음 추가된 경우 pcibios fixup을 수행한다.
    • 아키텍처마다 코드가 준비되어 있다.
      • arm의 경우 arm/kernel/bios32.c – pcibios_fixup_bus() 함수를 사용한다.
      • arm64의 경우 fixup 코드를 수행하지 않는다.
  • 코드 라인 25~29에서 현재 버스에 등록된 모든 pci 브리지 디바이스를 대상으로 2번씩 스캔한다.
  • 코드 라인 35~42에서 hot plug 버스를 위해 버스 번호를 남겨둔다.

 

PCI 슬롯 스캔

pci_scan_slot()

drivers/pci/probe.c

/**
 * pci_scan_slot - scan a PCI slot on a bus for devices.
 * @bus: PCI bus to scan
 * @devfn: slot number to scan (must have zero function.)
 *
 * Scan a PCI slot on the specified PCI bus for devices, adding
 * discovered devices to the @bus->devices list.  New devices
 * will not have is_added set.
 *
 * Returns the number of new devices found.
 */
int pci_scan_slot(struct pci_bus *bus, int devfn)
{
        unsigned fn, nr = 0;
        struct pci_dev *dev;

        if (only_one_child(bus) && (devfn > 0))
                return 0; /* Already scanned the entire slot */

        dev = pci_scan_single_device(bus, devfn);
        if (!dev)
                return 0;
        if (!dev->is_added)
                nr++;

        for (fn = next_fn(bus, dev, 0); fn > 0; fn = next_fn(bus, dev, fn)) {
                dev = pci_scan_single_device(bus, devfn + fn);
                if (dev) {
                        if (!dev->is_added)
                                nr++;
                        dev->multifunction = 1;
                }
        }

        /* only one slot has pcie device */
        if (bus->self && nr)
                pcie_aspm_init_link_state(bus->self);

        return nr;
}
EXPORT_SYMBOL(pci_scan_slot);

pci 슬롯을 스캔한다.

  • 코드 라인 17~18에서 pcie 포트에 이미 디바이스가 연결된 경우 또는 스캔하지 못하도록 한 경우 0을 반환한다.
  • 코드 라인 20~22에서 하나의 디바이스를 스캔한다. 스캔된 디바이스가 없으면 0을 반환한다.
  • 코드 라인 23~24에서 이미 추가된 디바이스의 경우가 아니면 nr을 증가시킨다.
  • 코드 라인 26~33에서 다음 펑션에 대한 하나의 디바이스를 스캔한다. 디바이스가 추가된 경우 multifunction 설정을 한다.
  • 코드 라인 36~37에서 PCIe 루트 포트 디바이스에 디바이스가 추가된 경우 link state를 초기화한다.

 

싱글 디바이스 스캔

pci_scan_single_device()

drivers/pci/probe.c

struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn)
{
        struct pci_dev *dev;

        dev = pci_get_slot(bus, devfn);
        if (dev) {
                pci_dev_put(dev);
                return dev;
        }

        dev = pci_scan_device(bus, devfn);
        if (!dev)
                return NULL;

        pci_device_add(dev, bus);

        return dev;
}
EXPORT_SYMBOL(pci_scan_single_device);

하나의 디바이스를 스캔하여 pci 디바이스로 추가한다.

  • 코드 라인 5~9에서 버스에 등록된 디바이스들 중 devfn에 해당하는 디바이스를 가져온다.
  • 코드 라인 11~13에서 해당 devfn 디바이스를 스캔한다.
  • 코드 라인 15에서 스캔 성공한 디바이스를 pci 디바이스로 추가한다.

 

PCI 디바이스 스캔

pci_scan_device()

drivers/pci/probe.c

/*
 * Read the config data for a PCI device, sanity-check it
 * and fill in the dev structure...
 */
static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn)
{
        struct pci_dev *dev;
        u32 l;

        if (!pci_bus_read_dev_vendor_id(bus, devfn, &l, 60*1000))
                return NULL;

        dev = pci_alloc_dev(bus);
        if (!dev)
                return NULL;

        dev->devfn = devfn;
        dev->vendor = l & 0xffff;
        dev->device = (l >> 16) & 0xffff;

        pci_set_of_node(dev);

        if (pci_setup_device(dev)) {
                pci_bus_put(dev->bus);
                kfree(dev);
                return NULL;
        }

        return dev;
}

하나의 devfn에 해당하는 pci 디바이스를 스캔한 후 셋업한다. 실패한 경우 null을 반환한다.

  • 코드 라인 10~11에서 최대 60초 이내에 요청한 버스 디바이스 펑션 번호의 pci 디바이스의 디바이스 id와 벤더 id를 읽어온다.
  • 코드 라인 13~15에서 pci 디바이스를 할당한다. 그리고 타입에 pci_dev_type을 대입하고 버스 참조 카운터를 1 증가시킨다.
  • 코드 라인 17~19에서 pci 디바이스에 devfn을 지정하고 알아온 16비트 벤더와 디바이스 id를 대입한다.
  • 코드 라인 21에서 디바이스가 소속된 호스트 컨트롤러의 디바이스 트리의 자식 노드에서 해당 디바이스에  대한 노드를 검색 후 of_node에 대입한다.
    • “reg” 속성 값의 bits[15:8] 값이 디바이스(슬롯) 및 펑션 번호이다.
  • 코드 라인 23~27에서 configuration space 헤더 정보를 읽어 pci 디바이스를 셋업한다.

 

pci_bus_read_dev_vendor_id()

drivers/pci/probe.c

bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *l,
                                int timeout)
{
        if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, l))
                return false;

        /* some broken boards return 0 or ~0 if a slot is empty: */
        if (*l == 0xffffffff || *l == 0x00000000 ||
            *l == 0x0000ffff || *l == 0xffff0000)
                return false;

        if (pci_bus_crs_vendor_id(*l))
                return pci_bus_wait_crs(bus, devfn, l, timeout);

        return true;
}
EXPORT_SYMBOL(pci_bus_read_dev_vendor_id);

요청한 버스 디바이스 펑션의 configuration space에서 디바이스 id와 벤더 id를 읽어 인자 l에 출력하고 true를 반환한다.

  • 코드 라인 4~5에서 요청한 버스 디바이스 펑션의 configuration space에서 벤더 id를 읽어 인자 l에 알아온다. 만일 읽어올 수 없으면 false를 반환한다.
  • 코드 라인 8~10에서 슬롯이 비어있는 경우 false를 반환한다.
  • 코드 라인 12~13에서 벤더 id를 읽을 때 지연될 수 있다. 그러한 경우 timeout 범위 내에서 1초 부터 2배 단위로 지연 후 다시 읽기를 시도한다.

 

PCI 디바이스 셋업

pci_setup_device()

drivers/pci/probe.c -1/2-

/**
 * pci_setup_device - fill in class and map information of a device
 * @dev: the device structure to fill
 *
 * Initialize the device structure with information about the device's
 * vendor,class,memory and IO-space addresses,IRQ lines etc.
 * Called at initialisation of the PCI subsystem and by CardBus services.
 * Returns 0 on success and negative if unknown type of device (not normal,
 * bridge or CardBus).
 */
int pci_setup_device(struct pci_dev *dev)
{
        u32 class;
        u16 cmd;
        u8 hdr_type;
        int pos = 0;
        struct pci_bus_region region;
        struct resource *res;

        if (pci_read_config_byte(dev, PCI_HEADER_TYPE, &hdr_type))
                return -EIO;

        dev->sysdata = dev->bus->sysdata;
        dev->dev.parent = dev->bus->bridge;
        dev->dev.bus = &pci_bus_type;
        dev->hdr_type = hdr_type & 0x7f;
        dev->multifunction = !!(hdr_type & 0x80);
        dev->error_state = pci_channel_io_normal;
        set_pcie_port_type(dev);

        pci_dev_assign_slot(dev);
        /* Assume 32-bit PCI; let 64-bit PCI cards (which are far rarer)
           set this higher, assuming the system even supports it.  */
        dev->dma_mask = 0xffffffff;

        dev_set_name(&dev->dev, "%04x:%02x:%02x.%d", pci_domain_nr(dev->bus),
                     dev->bus->number, PCI_SLOT(dev->devfn),
                     PCI_FUNC(dev->devfn));

        pci_read_config_dword(dev, PCI_CLASS_REVISION, &class);
        dev->revision = class & 0xff;
        dev->class = class >> 8;                    /* upper 3 bytes */

        dev_printk(KERN_DEBUG, &dev->dev, "[%04x:%04x] type %02x class %#08x\n",
                   dev->vendor, dev->device, dev->hdr_type, dev->class);

        /* need to have dev->class ready */
        dev->cfg_size = pci_cfg_space_size(dev);

        /* need to have dev->cfg_size ready */
        set_pcie_thunderbolt(dev);

        /* "Unknown power state" */
        dev->current_state = PCI_UNKNOWN;

        /* Early fixups, before probing the BARs */
        pci_fixup_device(pci_fixup_early, dev);
        /* device class may be changed after fixup */
        class = dev->class >> 8;

        if (dev->non_compliant_bars) {
                pci_read_config_word(dev, PCI_COMMAND, &cmd);
                if (cmd & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) {
                        dev_info(&dev->dev, "device has non-compliant BARs; disabling IO/MEM decoding\n");
                        cmd &= ~PCI_COMMAND_IO;
                        cmd &= ~PCI_COMMAND_MEMORY;
                        pci_write_config_word(dev, PCI_COMMAND, cmd);
                }
        }

        dev->broken_intx_masking = pci_intx_mask_broken(dev);

configuration space header 정보를 읽어 pci_dev 정보를 구성한다.

  • 코드 라인 20~21에서 디바이스의 pci configuration space에서 헤더 타입을 읽어온다.
  • 코드 라인 23~28에서 pci 디바이스의 정보를 채운다.
    • 헤더 타입의 bit15가 1인 경우 mmultifunction 디바이스이다.
  • 코드 라인 29에서 pcie 디바이스 정보를 설정한다.
    • configuration space에서 pcie capability를 읽어 pcie_cap, pcie_flags_reg, pcie_mpss, has_secondary_link 정보를 알아온다.
  • 코드 라인 31에서 버스내 슬롯 번호를 지정한다.
  • 코드 라인 34에서 32bit PCI의 dma 영역을 모두 허용한다.
  • 코드 라인 36~38에서 디바이스 명을 지정한다.
    • “<4자리 도메인번호>:<2자리 버스 번호>:<2자리 슬롯번호>.<1자리 펑션 번호>”
  • 코드 라인 40~45에서 configuration space에서 class_revision에 해당하는 더블 워드(4 byte) 값을 읽어 하위 8비트를 디바이스의 revision 값에 대입하고, 나머지 값은 class에 대입한다. 그런 후 이 정보를 디버그 출력한다.
  • 코드 라인 48에서 configaration space의 사이즈를 알아온다.
    • pci: 기본 사이즈 256
    • pcie: 기본 사이즈 256, 확장 정보인 경우 4096
    • pcix: 기본 사이즈 256, 266M 또는 533Mhz인 경우 확장 정보를 지원하여 4096
  • 코드 라인 51에서 PCI_EXT_CAP_ID_VNDR cap에서 벤더가 인텔이고, 벤더 헤더 id가 썬더볼트인 경우 is_thunderbolt를 1로 설정한다.
  • 코드 라인 55에서처음 pm 상태를 unknown으로 설정한다.
  • 코드 라인 58~60에서 부팅 시에 동작시킬 pci fixup 후크가 있는 경우 수행한다.
    • DECLARE_PCI_FIXUP_EARLY() 매크로 함수로 지정한다.
    • pci_fixup 구조체를 생성한 후 .pci_fixup_early 섹션에 저장한다.
    • 특정 아키텍처 및 드라이버 루틴에 존재한다.
      • arm64 아키텍처에는 존재하지 않고 arm 아키텍처에는 PCI_VENDOR_ID_PLX 벤더에서 3개의 디바이스에 대해 fixup_early 루틴이 있다.
      • drivers/pci/quirks.c에 특정 벤더와 디바이스에 대한 fixup_early 루틴이 존재 한다.
        • 예) PCI_VENDOR_ID_BROADCOM, 0x16cd 와 0x16f0 디바이스인 경우 quirk_paxc_bridge() 후크 함수를 호출한다.
  • 코드 라인 62~70에서 host/pcie-tango.c 드라이버에서 fake BAR를 사용하는 경우만 적용되는 코드이다. 생략.
  • 코드 라인 72에서 pci command 레지스터에 INTX_DISABLE(bit10) 비트 기록 테스트를 하여 변경 불가능 여부를 알아온다.

 

drivers/pci/probe.c -2/2-

        switch (dev->hdr_type) {                    /* header type */
        case PCI_HEADER_TYPE_NORMAL:                /* standard header */
                if (class == PCI_CLASS_BRIDGE_PCI)
                        goto bad;
                pci_read_irq(dev);
                pci_read_bases(dev, 6, PCI_ROM_ADDRESS);
                pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);
                pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device);

                /*
                 * Do the ugly legacy mode stuff here rather than broken chip
                 * quirk code. Legacy mode ATA controllers have fixed
                 * addresses. These are not always echoed in BAR0-3, and
                 * BAR0-3 in a few cases contain junk!
                 */
                if (class == PCI_CLASS_STORAGE_IDE) {
                        u8 progif;
                        pci_read_config_byte(dev, PCI_CLASS_PROG, &progif);
                        if ((progif & 1) == 0) {
                                region.start = 0x1F0;
                                region.end = 0x1F7;
                                res = &dev->resource[0];
                                res->flags = LEGACY_IO_RESOURCE;
                                pcibios_bus_to_resource(dev->bus, res, &region);
                                dev_info(&dev->dev, "legacy IDE quirk: reg 0x10: %pR\n",
                                         res);
                                region.start = 0x3F6;
                                region.end = 0x3F6;
                                res = &dev->resource[1];
                                res->flags = LEGACY_IO_RESOURCE;
                                pcibios_bus_to_resource(dev->bus, res, &region);
                                dev_info(&dev->dev, "legacy IDE quirk: reg 0x14: %pR\n",
                                         res);
                        }
                        if ((progif & 4) == 0) {
                                region.start = 0x170;
                                region.end = 0x177;
                                res = &dev->resource[2];
                                res->flags = LEGACY_IO_RESOURCE;
                                pcibios_bus_to_resource(dev->bus, res, &region);
                                dev_info(&dev->dev, "legacy IDE quirk: reg 0x18: %pR\n",
                                         res);
                                region.start = 0x376;
                                region.end = 0x376;
                                res = &dev->resource[3];
                                res->flags = LEGACY_IO_RESOURCE;
                                pcibios_bus_to_resource(dev->bus, res, &region);
                                dev_info(&dev->dev, "legacy IDE quirk: reg 0x1c: %pR\n",
                                         res);
                        }
                }
                break;

        case PCI_HEADER_TYPE_BRIDGE:                /* bridge header */
                if (class != PCI_CLASS_BRIDGE_PCI)
                        goto bad;
                /* The PCI-to-PCI bridge spec requires that subtractive
                   decoding (i.e. transparent) bridge must have programming
                   interface code of 0x01. */
                pci_read_irq(dev);
                dev->transparent = ((dev->class & 0xff) == 1);
                pci_read_bases(dev, 2, PCI_ROM_ADDRESS1);
                set_pcie_hotplug_bridge(dev);
                pos = pci_find_capability(dev, PCI_CAP_ID_SSVID);
                if (pos) {
                        pci_read_config_word(dev, pos + PCI_SSVID_VENDOR_ID, &dev->subsystem_vendor);
                        pci_read_config_word(dev, pos + PCI_SSVID_DEVICE_ID, &dev->subsystem_device);
                }
                break;

        case PCI_HEADER_TYPE_CARDBUS:               /* CardBus bridge header */
                if (class != PCI_CLASS_BRIDGE_CARDBUS)
                        goto bad;
                pci_read_irq(dev);
                pci_read_bases(dev, 1, 0);
                pci_read_config_word(dev, PCI_CB_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);
                pci_read_config_word(dev, PCI_CB_SUBSYSTEM_ID, &dev->subsystem_device);
                break;

        default:                                    /* unknown header */
                dev_err(&dev->dev, "unknown header type %02x, ignoring device\n",
                        dev->hdr_type);
                return -EIO;

        bad:
                dev_err(&dev->dev, "ignoring class %#08x (doesn't match header type %02x)\n",
                        dev->class, dev->hdr_type);
                dev->class = PCI_CLASS_NOT_DEFINED << 8;
        }

        /* We found a fine healthy device, go go go... */
        return 0;
}
  • 코드 라인 1~8에서 pci 디바이스의 헤더 타입이 normal 디바이스인 경우이다.
    • irq 라인과(0~5) irq 번호를 읽고,
    • BAR0~5 주소와 BAR6-ROM 주소를 읽어온다.
    • 서브시스템 벤더 id와 디바이스 id를 읽어온다.
  • 코드 라인 16~52에서 ide 타입 스토리지인 경우에 대한 코드는 생략
  • 코드 라인 54~69에서 pci 디바이스의 헤더 타입이 브리지인 경우이다.
    • irq 라인과(0~5) irq 번호를 읽고,
    • BAR0~1 주소와 BAR6-ROM 주소를 읽어온다. 핫 플러그 지원 여부를 읽어 is_hotplug_bridge에 대입한다.
    • 서브시스템 벤터 cap이 존재하는 경우 이데 대한 서브시스템 벤더 id와 디바이스 id를 읽어온다.
  • 코드 라인 71~78에서 pci 디바이스의 헤더 타입이 카드버스인 경우이다.
    • irq 라인과(0~5) irq 번호를 읽고,
    • BAR0 주소와 BAR6-ROM 주소를 읽어온다. 핫 플러그 지원 여부를 읽어 is_hotplug_bridge에 대입한다.
    • 서브시스템 벤터 cap이 존재하는 경우 이데 대한 서브시스템 벤더 id와 디바이스 id를 읽어온다.

 

BAR(Base Address Register) Setup

PCI 호스트 컨트롤러가 버스를 스캔하여 PCI 디바이스를 검출한 경우 PCI 디바이스를 셋업하는데 이 과정에서 PCI 디바이스의 BAR 레지스터들을 읽어 PCI 디바이스가 가지고 있는 IO 또는 메모리의 사이즈와 타입을 읽은 후 호스트 CPU의 물리 메모리 주소로 기록한다.

 

Mem32 Type BAR

PCI 디바이스의 BAR 값은 공장 출고 시 BAR 레지스터의 일부를 사이즈와 타입 정보를 기록하고 Read Only로 제공한다.

  • Size의 표기는 BAR 레지스터의 모든 비트를 1로 기록한 후 타입 4비트를 제외하고 1로 설정된 lowest 비트의 포지션 값으로 사이즈를 알아낸다.
    • 아래 bit26에서 1이 발견되었으므로 사이즈는 0x400_0000 (64MB) 임을 알아낼 수 있다.
  • 마지막으로 디바이스 트리의 ranges 에서 지정하는 메모리 타입의 호스트 CPU 물리 주소부터 배치된 리소스의 주소를 기록하는 것으로 완료한다.
    • 예) ranges = <0x82000000 0 0 0 0x20000000 0 0x400_0000>;
      • 타입: Pre-fetchable Mem32
      • pci 주소: 0x0
      • 호스트 cpu 물리 주소: 0x20000000
      • 사이즈: 0x4000000 (64MB)

 

Mem64 Type BAR

다음 그림은 2개의 BAR를 사용하여 64비트 메모리 주소를 알아오는 모습을 보여준다.

  • Size의 표기는 BAR1 및 BAR2 레지스터의 모든 비트를 1로 기록한 후 타입 4비트를 제외하고 1로 설정된 lowest 비트의 포지션 값으로 사이즈를 알아낸다.
    • 아래 bit30에서 1이 발견되었으므로 사이즈는 0x4000_0000 (1GB) 임을 알아낼 수 있다.
  • 마지막으로 디바이스 트리의 ranges 에서 지정하는 메모리 타입의 호스트 CPU 물리 주소부터 배치된 리소스의 주소를 기록하는 것으로 완료한다.
    • 예) ranges = <0x83000000 0 0 4 0x80000000 0 0x4000_0000>;
      • 타입: Pre-fetchable Mem64
      • pci 주소: 0x0
      • 호스트 cpu 물리 주소: 0x4_80000000
      • 사이즈: 0x40000000 (1GB)

 

I/O Type BAR

다음 그림은 IO 주소를 표현하는 BAR3를 보여준다.

  • Size의 표기는 BAR3 레지스터의 모든 비트를 1로 기록한 후 타입 2비트를 제외하고 1로 설정된 lowest 비트의 포지션 값으로 사이즈를 알아낸다.
    • 아래 bit12에서 1이 발견되었으므로 사이즈는 0x1000 (4KB) 임을 알아낼 수 있다.
  • 마지막으로 디바이스 트리의 ranges 에서 지정하는 IO 타입의 호스트 CPU 물리 주소부터 배치된 리소스의 주소를 기록하는 것으로 완료한다.
    • 예) ranges = <0x81000000 0 0 0 0x24000000 0 0x1000>;
      • 타입: I/O
      • pci 주소: 0x0
      • 호스트 cpu 물리 주소: 0x24000000
      • 사이즈: 0x1000 (4KB)

 

PCI 디바이스 설정 및 추가

pci_device_add()

drivers/pci/probe.c

void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
{
        int ret;

        pci_configure_device(dev);

        device_initialize(&dev->dev);
        dev->dev.release = pci_release_dev;

        set_dev_node(&dev->dev, pcibus_to_node(bus));
        dev->dev.dma_mask = &dev->dma_mask;
        dev->dev.dma_parms = &dev->dma_parms;
        dev->dev.coherent_dma_mask = 0xffffffffull;

        pci_set_dma_max_seg_size(dev, 65536);
        pci_set_dma_seg_boundary(dev, 0xffffffff);

        /* Fix up broken headers */
        pci_fixup_device(pci_fixup_header, dev);

        /* moved out from quirk header fixup code */
        pci_reassigndev_resource_alignment(dev);

        /* Clear the state_saved flag. */
        dev->state_saved = false;

        /* Initialize various capabilities */
        pci_init_capabilities(dev);

        /*
         * Add the device to our list of discovered devices
         * and the bus list for fixup functions, etc.
         */
        down_write(&pci_bus_sem);
        list_add_tail(&dev->bus_list, &bus->devices);
        up_write(&pci_bus_sem);

        ret = pcibios_add_device(dev);
        WARN_ON(ret < 0);

        /* Setup MSI irq domain */
        pci_set_msi_domain(dev);

        /* Notifier could use PCI capabilities */
        dev->match_driver = false;
        ret = device_add(&dev->dev);
        WARN_ON(ret < 0);
}
  • 코드 라인 5에서 pci 디바이스의 mps, extended tag 여부, relaxed ordering, hot plug parameter 정보 등을 설정한다.
  • 코드 라인 6에서 pci 디바이스의 (*release) 후크에 pci_release_dev() 함수를 대입한다.
  • 코드 라인 8~14에서 버스가 사용하는 노드 번호로 디바이스 노드를 설정하고, 디바이스의 dma 관련 설정등을 최대치로 설정해둔다.
  • 코드 라인 17에서 특정 장치에 대해 pci_fixup_header 후쿠가 있는 경우 이를 수행한다.
    • DECLARE_PCI_FIXUP_HEADER() 매크로 함수로 지정한다.
    • pci_fixup 구조체를 생성한 후 .pci_fixup_header 섹션에 저장한다.
  • 코드 라인 20에서 resource_alignment 버스 속성을 지정한 경우 리소스 alignment를 수행한다. (powerpc만 지원한다)
    • resource_alignment=
      • [<order of align>@][<domain>:]<bus>:<slot>.<func>
      • [:noresize][; …]
  • 코드 라인 23에서 pm state save 여부를 false로 한다.
  • 코드 라인 26에서 각종 cap을 초기화한다.
  • 코드 라인 32~34에서 버스에 디바이스를 추가한다.
  • 코드 라인 36에서 pcibios 설정에 맞춰 디바이스의 매핑을 수행한다.
    • x86, s390, powerpc, sparc 등 바이오스가 있는 경우에 한해 설정한다.
    • arm, arm64는 해당 사항 없다.
  • 코드 라인 40에서 msi용 irq 도메인을 지정한다.
  • 코드 라인 43~44에서 디바이스를 추가하되 match 동작을 수행하지 않게한다.

 

pci_configure_device()

drivers/pci/probe.c

static void pci_configure_device(struct pci_dev *dev)
{
        struct hotplug_params hpp;
        int ret;

        pci_configure_mps(dev);
        pci_configure_extended_tags(dev, NULL);
        pci_configure_relaxed_ordering(dev);

        memset(&hpp, 0, sizeof(hpp));
        ret = pci_get_hp_params(dev, &hpp);
        if (ret)
                return;

        program_hpp_type2(dev, hpp.t2);
        program_hpp_type1(dev, hpp.t1);
        program_hpp_type0(dev, hpp.t0);
}

pci 디바이스의 mps, extended tag 여부, relaxed ordering, hot plug parameter 정보 등을 설정한다.

  • 코드 라인  6에서 pci upstream 브리지의 mps(Maximum Payload Size) 값으로 pci 디바이스의 mps를 설정한다.
    • pci 디바이스와 pci  upstream 브리지의 mps가 같은 경우 변경이 필요 없다.
    • 128, 256, 512, 1024, 2048, 4096 값 중 하나를 사용할 수 있다.
    • pci 버스가 아직 설정되지 않은 경우 나중에 처리할 수도 있다.
  • 코드 라인 7에서 pci 호스트의 extended tag가 설정된 경우 pci 디바이스도 설정한다.
    • pcie EXP_DEVCAP에서 extended tag 비트가 설정된 경우 호스트의 extended tag 처리가 가능 여부에 따라 디바이스의 설정을 변경한다.
    • “disabling Extended Tags” 또는 “enabling Extended Tags” 메시지가 출력된다.
  • 코드 라인 8에서 pci 루트 포트가 relaxed ordering을 허용하지 않으면 pci 디바이스의 설정도 클리어한다.
    • PCI_EXP_DEVCTL 레지스터의 PCI_EXP_DEVCTL_RELAX_EN 비트가 설정되었고, 루트 포트에 PCI_DEV_FLAGS_NO_RELAXED_ORDERING 플래그의 설정이 있으면 pci 디바이스의  PCI_EXP_DEVCTL 레지스터의 PCI_EXP_DEVCTL_RELAX_EN을 클리어한다.
    • “”Disable Relaxed Ordering because the Root Port didn’t support it” 메시지가 출력된다.
  • 코드 라인 10~13에서 acpi를 사용할 때 pci hot plug 파라메터를 가져온 후 pci 디바이스에 설정한다.

 

참고