I2C Subsystem -1- (Basic)

 

I2C(Inter-Integrated Circuit)

I2C는 필립스에서 개발한 직렬 컴퓨터 버스이며 여러 개의 저속의 주변 기기를 연결하기 위해 사용된다. 시리얼 통신에 사용되는 UART 또는 SPI들보다 더 단순하여 심플한 회로 구성이 가능하다.

 

I2C 통신에 사용되는 2 개의 라인은 다음과 같은 기능을 수행한다.

  • SDA (Serial DAta)
    • 데이터의 직렬 전송에 사용된다.
  • SCL (Serial CLock)
    • 디바이스간 신호 동기화에 사용되는 클럭이다.

 

다음 그림은 I2C 버스에 연결되어 있는 디바이스들을 보여준다.

 

i2c 버스에 연결되는 디바이스 종류들은 수백~수천 가지로 매우 다양하다. 다음 그림은 몇 가지 종류의 디바이스들을 i2c 버스에 연결하여 사용할 때의 모습을 보여준다.

 

I2C Specification History

i2c 스펙은 다음과 같이 발전을 해왔다. 아래 노란 박스는 새 버전이 나올 때 마다 변경된 주요 항목이다.

  • 필립스에서 처음 I2C가 개발되었을 때 다음과 같은 specification으로 동작하였다.
    • 가장 널리 사용되는 스탠다드 모드가 100Khz로 사용되고, 저속 모드로 운영시에는 10Khz의 속도로 동작한다.
    • 7비트 주소를 사용하여 총 128개의 주소를 사용할 수 있지만 그 중 16개는 reserve되어 있고, 나머지 112개를 사용할 수 있다. 따라서 하나의 I2C 버스에는 최대 112개의 디바이스가 연결될 수 있다.
  • 그 후 추가 버전들은 속도가 증가되었고, 주소가 10비트로 확장되었다.
    • 속도 증가
      • Fast 모드에서 400Khz, High 모드에서 3.4Mhz로 속도가 증가됨을 알 수 있다.
    • 주소 확대
      • 10비트 모드가 사용되면서 최대 1008개의 디바이스를 연결할 수 있게 되었다.

  • (1) 3.4Mhz High-speed-mode를 사용하기 위해서는 SDAH SLCH를 사용하는 디바이스를 사용하고 arbitration 및 클럭 동기화를 사용하지 않아야 한다.
  • (2) 5Mhz Ultra Fast-speed-mode를 위해서는 USDA, USCL 라인이 추가되어야하고 Master → Slave 단방향만 지원된다.

 

멀티 마스터 지원

I2C는 다음 그림과 같이 1개의 버스에 2개 이상의 컨트롤러가 경쟁할 수 있다. 이 때 두 컨트롤러는 버스 Master 역할을 하기 위해 경쟁하게된다.

  • Multi-master
    • 2개 이상의 마스터가 메시지 손상 없이 동시에 버스를 제어하는 경우.
  • Arbitration
    • 2개 이상의 마스터가 버스를 동시에 통제할 때 오직 하나만이 허용되며 선택된 마스터의 메시지는 손상되지 않는다.
  • Synchronization
    • 두 개 이상의 디바이스의 클럭들을 동기화

 

I2C-Mux

다음과 같이 i2c 스위치 및 i2c 멀티플레서 두 종류로 나뉜다. 각각의 기능은 다음과 같다.

  • i2c 스위치
    • 1:N으로 구성 시 하위 채널 각각에 대해 select  및 deselect 할 수 있다. 즉 1개 이상을 select할 수 있다.
  • i2c 멀티플렉서
    • 1:N 또는 N:1로 구성 시 하위 채널 하나를 select 및 deselect 할 수 있다.  select 시 한 번에 하나의 채널만 select할 수 있다.

 

 

제품에 따라 다음과 같은 기능이 지원될 수도 있다.

  • HW reset 핀(option)
    • i2c 스위치에서 외부 HW reset(option) 핀이 지원되는 경우 버스 리커버리를 수행할 수 있다.
    • i2c 스위치 및 i2c 멀티플렉서 둘 다 내부에서 POR(Power On Reset) 기능을 지원한다.
  • 인터럽트(option)
    • 인터럽트를 받아 mux하여 전송할 수 있다.
  • HW addr 설정 핀
    • i2c-mux 디바이스를 하나의 버스에 여러 개를 사용할 경우 중복되지 않게 주소 하위 비트 몇 개(3~4)를 설정할 수 있는 핀이 패키지 외부로 제공된다.

 

다음 그림은 외부 reset, 인터럽트, HW 주소 설정핀 등이 지원되는 모습을 보여준다.

 

아래 그림은 2개의 i2c 컨트롤러와 그 중 하나에 연결된 4채널 i2c 멀티플렉서를 보여준다.

 

I2C 통신 프로토콜

2 개의 라인만을 사용하는 I2C 버스에서 I2C 디바이스들이 사용하는 통신 프로토콜을 알아보기로 하자.

 

Feature 리스트

다음 표와 같이 I2C 통신 프로토콜에 사용되는 Feature들이다. 디바이스들은 싱글 마스터, 멀티 마스터 또는 슬래이브 등의 각 구성(Configuration)에 따라 사용되는 Feature들이 다르다.

 

주요 Feature

i2c 버스에 어떠한 통신도 없는 경우 SDA 및 SCL 라인은 high 상태에 있다. 마스터나 디바이스가 통신을 시도할 때 전류가 흐르며 low 상태로 바뀐다.

 

Start & Stop condition

다음 그림과 같이 데이터를 송수신하기 위한 시작과 끝 조건을 알아본다.

  • Start condition
    • 클럭(SCL)이 high 시그널일 때 데이터(SDA)가 low 시그널로 변경될 때 Start condition으로 인식한다.
    • 표기법: S
  • Stop condition
    • 클럭(SCL)이 high 시그널일 때 데이터(SDA)가 high 시그널로 변경될 때 Stop condition으로 인식한다.
    • 표기법: P

 

i2c 데이터 통신 중에는 클럭이 high 시그널일 때에는 데이터(SDA) 시그널이 변경되면 안된다. 이러한 규칙으로 인해 클럭이 high인 상태에서는 데이터를 안정적으로 읽는 것을 보장한다.

 

그런데 아래 그림과 같이 클럭(SCL)이 high 상태일 때 데이터(SDA)가 변경되는 특수한 상태가 있다. 이러한 경우 데이터(SDA)가 변화한 방향에 따라 low 상태로 변경되면 Start condition이고 high 상태로 변경되면 Stop condition이라 한다.

 

1 바이트 전송 및 Acknowledge 처리

Start condition이 발생한 후 Stop condition이 나오기 전까지 디바이스들 간에 연속으로 바이트 단위의 통신을 할 수 있다. 1 바이트를 수신할 때마다 수신측 디바이스는 전송측을 위해 응답(Acknowledge) 비트를 전송해야 한다.

  • 전송측 디바이스는 1 바이트를 MSB부터 전송한다.
  • 수신측 디바이스는 1 바이트 수신 완료 후 1 비트의 Ack(low)를 전송한다.
  • 표기법: A 또는 ACK

 

아래 그림을 보면 클럭(SCL)을 마스터가 주기적으로 보내고 있음을 알 수 있다. 데이터(SDA)는 전송 디바이스측에서 8비트를 보낸 후 9번째에 high 상태를 유지하고 있다. 그리고 수신 디바이스측에서 9번째 클럭에 해당하는 위치가 오기전에 ACK(low) 상태를 유지하는 것을 알 수 있다.

Synchronization

하나의 i2c 버스에서 두 개의 마스터가 동작할 때 각각의 클럭을 and 하여 동기화 시키는 것으로 하나의 클럭을 만들어낸다.

 

Arbtration

하나의 i2c 버스가 free 상태일 때 두 개의 마스터가 동시에 버스를 차지하려고 각각 Start condition을 보낸 경우이다. 그 이후로 운좋게(?) 서로 데이터를 동일하게 보내다가 어느 시점에서 각자의 전송 비트가 달라지게 되는데 그 시점에서 데이터(SDA)를 high 상태로 바꾼 마스터가 arbtration의 패자가 된다. 즉 데이터(SDA)를 low 상태로 물고 있는 마스터가 승자이다.

 

아래 그림은 두 개의 마스터가 거의 동시에 start condtion을 보냈고, 첫 번째 마스터가 데이터를 high로 변경하였음에도 SDA 라인은 low 상태를 유지하고 있다. 이 때 첫 번째 마스터가 자신이 arbitration의 패자가 되었음을 인식한다.

 

Clock Streach (Clock low extending)

i2c 마스터가 규칙적으로 clock을 발생하여야 하고 이에 대해 i2c 디바이스는 클럭에 맞춰 데이터를 송/수신해야한다. 그러나 요청을 받은 후에 클럭 타임을 초과하는 처리 시간이 필요한 경우 slave 디바이스가 데이터를 준비하기 위해 클럭을 강제로 low로 설정해버린다. 그런 후 데이터가 준비되어 응답을 할 수 있으면 다시 클럭을 풀어준다. 그런 경우 마스터가 계속 하여 클럭을 진행할 수 있다.

  • 예) 주소 0x45를 갖는 EEPROM 디바이스에 offset 08번에 해당하는 데이터를 요청하였다. eeprom 장치에서 데이터를 가져오는 시간이 많이 걸릴 때가 있으므로 이러한 경우 잠시 클럭을 low로 묶어놓고 데이터가 다 준비된 경우 이를 응답하는 형식으로 처리를 하는 것을 clock streaching이라 한다.
  • ACK를 수신하면 인터럽트가 발생하고 이 인터럽트의 처리가 완료되기 전까지 클럭(SCL) 시그널은 low 상태로 고정되어 대기한다.
  • clock streach 기능을 사용하면 adapter가 I2C_FUNC_SLAVE 기능이 지원되어야 한다.
  • 클럭을 최대 100us까지 지연시킬 수 있다.
  • 두 가지 지연 방법이 있다.
    • Periodic clock stretching by slave
      • 주기적으로 매 번 클럭을 늘려 smbus 통신 속도를 10Khz로 줄일 수 있다.
    • Random clock stretching by master or slave
      • ISR 루틴에서 데이터 준비를 위해 지연을 시키는 경우이다.

 

다음 그림은 특정 디바이스로부터 무조건 1 바이트 값을 읽어오는 시나리오에서 디바이스가 clock streaching을 사용하는 유무에 대해 비교하였다.

  • 보통 주소를 전송하고 요청 offset 및 size를 요청하는데 이렇게 하면 길어지므로 아래 그림에서는 간단히 주소와 데이터를 수신하는 것으로 보여준다.
  • slave 디바이스가 scl을 low로 강제함으로써 마스터 디바이스가 계속하여 클럭을 발생시키지 못하게 홀드시킨다.

 

7-bit slave addresss (슬레이브 디바이스 선택 후 데이터 전송 또는 수신)

먼저 상대 디바이스를 선택한 후 데이터를 송/수신하는데 방법은 다음과 같다.

  • 7bit 주소를 전송하는 것으로 디바이스를 선택한다. 그 후 전송측을 결정하는 R/W 비트를 전송한다.
    • R/W 비트
      • 1=마스터가 수신
      • 0=마스터가 송신
  • 송수신 비트가 0인 경우 마스터가 슬레이브에 8 비트 데이터를 전송한다.
  • 송수신 비트가 1인 경우 슬레이브가 마스터에 8비트 데이터를 수신한다.
  • 그 후 수신측에서 응답(Ack) 1 비트를 전송한다.
  • 표기법:  Addr + R/W

 

특별 주소(Special Addresses)

다음의 16개 주소들은 다음과 같이 특별한 목적으로 사용된다.

  • R/W 비트 상태여부와 관계 없이 Address만을 보면 16개의 주소가 특별 주소로 사용된다.

  • smbus의 경우 다음 주소의 디바이스 용도는 다음과 같다.
    • 0001-000 (0x08)
      • SMBus host
      • SMBus를 이용할 때 host controller의 address이다.
      • 디바이스는 이 주소로 Host Notify 이벤트를 보낼 수 있다.
    • 0001-001 (0x09)
      • Smart Battery Charger
    • 0001-010
      • Smart Battery Selector
      • Smart Battery System Manager
    • 0001-011
      • Smart Battery
    • 0001-100
      • SMBus
    • 0001-100 (0x0c)
      • ARA(Alert Response Address)
      • SMBus alert 응답 주소
    • 0101-000 (0x28)
      • ACCESS.bus host
    • 0101-100 (0x2c)
      • LCD Contrast Controller로 이전에 예약되어 있었지만 미래에 다시 설정할 예정이다.
    • 0101-101 (0x2d)
      • CCFL Backlight Driver로 이전에 예약되어 있었지만 미래에 다시 설정할 예정이다.
    • 0110-111 (0x37)
      • ACCESS.bus 디폴트 주소
    • 1000-0XX (0x40 ~ 0x43)
      • PCMCIA Socket Controller로 이전에 예약되어 있었지만 미래에 다시 설정할 예정이다.
    • 1000-100 (0x44)
      • VGA Graphics Controller로 이전에 예약되어 있었지만 미래에 다시 설정할 예정이다.
    • 1001-0XX (0x48 ~ 0x4b)
      • 제한없이 사용할 수 있는 주소
    • 1100-001 (0x61)
      • SMBus 디바이스의 디폴트 주소

 

참고로 ACCESS.bus는 2-wire를 사용하여 외부 장치들을 연결하여 나갈 수 있도록 설계한 시도이다.

  • 전원까지 4개의 pin을 사용한다.
  • RS-232C 인터페이스를 대체할 목적이다.

 

10-bit addressing

10비트 주소를 사용하는 경우 다음과 같이 2 바이트를 사용하여 디바이스의 주소를 지정할 수 있다.

  • 첫 번째 바이트
    • 10비트 중 2비트 주소
  • 두 번째 바이트
    • 10비트 중 8비트 주소

 

 

General Call address

General call 주소가 사용되면 디바이스에 몇 가지 특별한 요청을 할 수 있다. 이 기능은 아래 Start BYTE와 같이 대부분의 시스템에서 잘 사용되지 않는 기능이다.  이 기능은 주로 i2c 디바이스들 중 EEPROM등에서 프로그래밍등이 요구될 때 사용되는 특수 기능이다.

  • 첫 번째 바이트 구성을 General call(0x0)로 사용한다.
  • 두 번째 바이트에 기능 코드 또는 마스터 주소를 부여할 수 있다.
    • LSB가 0일 때 다음과 같은 기능 코드가 있다.
      • 0x04: Write programmable part of slave address by hardware
      • 0x06: Reset and write programmable part of slave address by hardware
    • LSB가 1인 경우 마스터 주소를 사용하는 경우 Hardware Generall Call을 사용할 수 있다.

 

 

Start BYTE

Srart BYTE feature는 0x00 데이터와 dummy NACK(1)을 전송한다. 두 개 이상의 호스트 컨트롤러가 버스 Master를 경쟁하고 있는 경우 stop condition을 detect한 경우 두 호스트 컨트롤러 중 하나가 버스 마스터가 된다. 특별한 경우 어떤 호스트 컨트롤러가 계속하여 버스를 점유하여 명령(reset, EEPROM 기록 등)을 처리해야 하는 경우 stop condition을 사용하지 않아야 한다. stop condition을 하는 경우 버스를 빼앗길 염려가 있다. 이러한 경우에 stop condition 대신 START BYTE를 보내고 다시 start condition을 사용하는 방식으로 반복하여 계속 명령을 보낼 수 있다. 잘 사용되지 않는 기능이지만 드믈게 특정 시스템에서 사용된다.

 

Device ID

디바이스에 대한 제조사 id와 파트 id 및 리비전 정보를 알아올 수 있다.

 

디바이스를 알아오는 경우 다음과 같은 제조사 id를 읽어낼 수 있다.

 

i2c 전송 포맷

i2c 전송 포맷은 다음과 같이 3가지 방법이 있다.

  • 마스터 송신
    • 슬레이브로 데이터를 송신 중에 방향이 바뀌지 않는다.
  • 마스터 수신
    • 마스터가 주소 송신 후 슬레이브로 부터 데이터를 수신한다. 역시 방향이 바뀌지 않는다.
  • Combined 포맷
    • 마스터와 슬레이브의 복합 송수신이 가능하다.

 

마스터 송신

다음 그림은 마스터 디바이스가 슬레이브 디바이스로 N 바이트를  전송하는 모습이다.

  • 슬레이브가 바이트를 받을 때 마다 ACK를 보내는데 더 이상 받을 수 없는 상황인 경우 NACK를 보낸다.

 

마스터 수신

다음 그림은 마스터 디바이스가 슬레이브 디바이스로 1 바이트를 수신하는 모습이다.

  • 마지막 바이트를 수신한 후 마스터가 NACK를 보내야한다.

 

Combination 전송

다음 그림은 마스터 디바이스가 슬레이브 디바이스로 여러 개의 메시지를 송신 또는 수신하는 모습이다.

  • 마스터가 버스를 계속 점유한 채로 전송 및 수신들의 combination을 완료해야 하는 경우 사용한다.
  • 메시지의 전송이 끝난 후 반복 시작하고자 할 때 중간에 repeated start condition 을 전송한다.
    • 표기법: Sr

 

 

SMBus(System Management Bus)

필립스의 i2c를 기반으로 동작하도록 인텔과 듀라셀이 1994년 정의한 버스이다. 특징은 다음과 같다.

  • 주로 PC 마더보드에 연결된 전력 제어 장치(power on/off) 및 온도 센서등이 smbus에 연결되어 사용된다.
  • 참고: System Management Bus (SMBus) Specification Version 2.0 August 3, 2000 | smbus.org – 다운로드 pdf

 

SMBus가 I2C와의 다른 점

타이밍
  • Speed
    • smbus
      • 10~100Khz를 사용한다.
        • 실제 대부분의 구현은 50~100Khz를 사용한다.
      • PMBus로 발전되면서 400Khz 속도도 사용할 수 있다.
    • i2c
      • 최저 속도는 100Khz이다.
  • 클럭(scl) 라인 low 타임아웃
    • smbus
      • 타임아웃을 설정할 수 있다.
      • Slave 디바이스의 경우 최대 25ms 까지 low 시간을 연장할 수 있다. (Clock Streaching)
      • Master 디바이스의 경우 최대 10ms 까지 low 시간을 연장할 수 있다. (Clock Streaching)
    • i2c
      • 타임아웃을 설정할 수 없다.
  • 데이터(sda) 라인 홀드 시간
    • smbus
      • 최대 300ns까지 홀드 시킬 수 있다.
    • i2c
      • 홀드할 수 없다.
  • Power-On-Reset 후 디바이스 준비 시간
    • smbus
      • 최대 0.5초까지 대기해야 한다.
ACK 및 NACK
  • addr 전송 후 ACK 용도
    • smbus
      • 탈부착 smbus 디바이스가 버스에 연결되었는지 여부를 detect 하는 비트 용도로 항상 ACK를 요구한다.
      • 슬레이브 디바이스는 자신의 주소(Addr) 비트를 인식하면 ACK를 회신한다.
      • S | Addr + R/W | ACK | …
    • i2c
      • ACK가 없어도 된다.
  • NACK 의미
    • smbus
      • N잘못된 명령이나 데이터의 수신을 의미한다.
    • i2c
      • 더이상 데이터를 전송하지 않는다는 것을 의미한다.
Protocol
  • 탈부착 디바이스 연결
    • smbus
      • 통신 중 smbus 디바이스가 smbus에 연결될 수 있다.
    • i2c
      • 통신 중 i2c 디바이스가 i2c 버스에 연결될 수 없다.
  • ARP(Address Resolution Protocol)
    • smbus
      • ARP를 지원한다.
    • i2c
      • ARP를 지원하지 않는다.
  • PEC(Packet Error Checking)
    • smbus
      • PEC를 지원한다.
      • 에러 체킹을 위해 CRC-8 규격으로 1 바이트를 추가 사용한다.
    • i2c
      • PEC를 지원하지 않는다.
Alert
  • smbus에 연결된 디바이스들이 alert라는 옵션 신호를 사용하여 host에 전달할 수 있게 하였다.
전기 규격
  • 전기적인 특성이 I2C와 일부 다르다.

 

SMBus protocol

SMBus 프로토콜에 사용되는 명령들을 알아본다. 참고로 그림에서 사용하는 기호는 다음과 같다.

  • S
    • Start condition
  • P
    • Stop condition
  • Sr
    • Repeted Start condition
  • Addr
    • 7비트 주소
  • R/W
    • 1비트 (R=1, W=0)
  • A
    • Ack (0)
  • PEC
    • 8비트 Packet Error Checking

 

Quick, Send & Receive 명령

Quick

슬레이브 디바이스로 R/W 1 비트를 전달할 수 있고, Ack 1비트를 수신하여 detect 여부를 확인할 수 있다.

  • R/W 비트를 수신하여 장치의 on/off를 제어하거나, low-power 스탠바이 모드를 enable/disable하는데 사용할 수 있다.
  • 데이터 바이트의 송신 또는 수신은 없다.

 

Send byte

슬레이브 디바이스에 1 바이트를 송신한다. Write Byte와 다른 점은 command를 전송하지 않는다는 점이다.

  • pure smbus의 ACK는 디바이스에서의 처리 결과를 호스트에 반환하는 기능을 가진다.
  • pure i2c-bus의 ACK는 디바이스가 항상 1을 응답해야한다.

 

Receive byte

슬레이브 디바이스로부터 1 바이트를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

Write & Read 명령

Write byte

슬레이브 디바이스에 1 바이트 명령 + 1 바이트를 송신한다.

 

Write word

슬레이브 디바이스에 1 바이트 명령 + 워드(2 바이트)를 송신한다.

  • smbus에서 워드는 2바이트이다.

 

Read byte

슬레이브 디바이스에 1 바이트 명령을 송신하고 1 바이트를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

Read word

슬레이브 디바이스에 1 바이트 명령을 송신하고 워드(2 바이트)를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

Block 명령

Block write

슬레이브 디바이스에 명령을 전송한다. 그런 후 다시 슬레이브 디바이스에 길이 + 블럭(최대 32바이트) 데이터를 송신한다.

 

Block read

슬레이브 디바이스에 명령을 전송한다. 그런 후 다시 슬레이브 디바이스로부터 길이 + 블럭(최대 32바이트) 데이터를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

Process Call 명령

Process call

슬레이브 디바이스에 명령 + 워드(2 바이트)를 전송한다. 그런 후 다시 슬레이브 디바이스로부터 워드(2 바이트)를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

Block Process Call

슬레이브 디바이스에 명령 + 길이와 블럭 데이터(32 바이트까지)를 전송한다. 그런 후 다시 슬레이브 디바이스로부터 길이와 블럭(최대 32 바이트) 데이터를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

이벤트

Host notify

슬레이브 디바이스가 호스트에 워드 데이터를 전달하고자 할 때 사용한다. 호스트는 워드 데이터를 수신하여 호스트에서 슬레이브를 담당하는 드라이버에 이벤트를 전달한다.

  • pure smbus에서 host notify는 반드시 구현되어야 한다.

 

smbalert#

슬레이브 디바이스가 smbalert# 인터럽트를 호스트에 발생시킨다. 발생 시킨 디바이스를 확인하기 위해 아래와 같이 ARA(Alert Response Address)로부터 디바이스 주소를 알아올 수 있다.

  • 만일 HW 구성 시 별도의 smbalert# 라인이 호스트에 연결되어 구성되지 않은 경우 호스트가 alert를 주기적으로 폴링하여 알아내는 방법도 사용할 수 있다.
  • pure smbus에서 alert는 option 사항이다.
  • alert callback을 위해 i2c_setup_smbus_alert() 함수를 사용하여 callback용 함수를 등록하여 사용한다.

 

I2C 서브시스템

I2C 디바이스와 드라이버를 등록하면 I2C 서브시스템은 유저 스페이스에 다양한 인터페이스를 제공한다. (i2c-N은 i2c adapter로 N은 특별히 지정하지 않으면 0번부터 시작된다.)

  • i2c-core가 크게 두 가지를 관리한다.
    • i2c adapter 디바이스
      • /sys/devices/…/<i2c 호스트 디바이스>/i2c-N
        • i2c 어댑터 디바이스 인터페이스
      • /sys/class/i2c-adapter
        • i2c-adapter 클래스 디바이스 인터페이스
      • /sys/bus/i2c/devices/i2c-N
        • 버스에 등록된 i2c 어댑터 디바이스 인터페이스
    • i2c client 디바이스 및 드라이버
      • /sys/devices/…/<i2c 호스트 디바이스>/i2c-N/<client device>
        • i2c 클라이언트 디바이스 인터페이스
        • <client device>의 기본 포맷은 <버스번호>-<4자리의 addr 숫자>
          • 예) 0x3f 주소를 가진 디바이스가 1번 i2c 버스에 연결된 경우 -> 1-003f
      • /sys/bus/i2c/devices/<client device>
  • i2c-dev는 i2c 클라이언트 디바이스를 위해 2 개의 인터페이스를 제공한다.
    • /dev/i2c-N
      • 캐릭터 디바이스 인터페이스
      • i2cdetect, i2cdump, i2cset, i2cget 등의 i2c-tools를 사용하려면 i2c 캐릭터디바이스가 동작해야 한다.
    • /sys/class/i2c-dev/i2c-N
      • i2c-dev 클래스 디바이스 인터페이스
      • i2c adapter에 대한 character 디바이스를 사용할 때 생성된다.(CONFIG_I2C_CHARDEV)

 

SoC 외부에 위치한 i2c 호스트 컨트롤러

임베디드 시스템에서 i2c 호스트 컨트롤러들은 보통 SoC 내부에 위치하여 플랫폼 디바이스로 곧바로 등록할 수 있다. 그러나 이를 사용하지 못하는 경우에는 속도가 빠른 버스에 연결하여 사용할 수 있다. 일반적인 PC에는 i2c 호스트 컨트롤러가 내장되지 않았다. 이러한 PC에는 pci 버스와 usb 버스가 연결되어 있는데 pci 버스 또는 usb 버스에 i2c 호스트 컨트롤러를 연결하여 사용하는 경우가 많다. 다음 그림은 usb 버스에 i2c 버스가 연결된 경우를 보여준다. 좌측 그림이 H/W 연결 구성이고 우측 그림이 이에 대한 각 버스 서브 시스템과 core 및 드라이버 위치를 보여준다.

  • 예) PCI 버스  —–>  USB 버스  —–>  I2C 버스

 

i2c sysfs

다음 그림은 i2c adapater 디바이스 및 드라이버가 등록된 모습을 sysfs 디렉토리로 보여준다.

  • 2 개의 i2c adapter가 플랫폼 디바이스로 등록되어있고, 2 개의 드라이버 역시 바인딩 되어 동작중인 상태이다.

 

다음 그림은 여러 개의 i2c client 디바이스 및 드라이버가 추가 등록된 모습을 sysfs 디렉토리로 보여준다.

  • 여러 가지 종류의 디바이스 및 드라이버가 등록되어 있는 모습을 보여준다.

 

i2c 호스트 컨트롤러(Adapter) 및 i2 디바이스(Client) 등록

 

i2c 디바이스는 버스 마스터 기능 유무에 따라 i2c 호스트 컨트롤러와 i2c 클라이언트의 역할을 한다.

  • i2c 호스트 컨트롤러(included i2c adapter)는 상위 버스와 i2c 버스간의 브릿지 기능을 수행한다.
  • i2c 클라이언트는 i2c 버스에 연결되는 consumer 디바이스이다.

 

i2c 버스는 pci나 usb 버스와 다르게 i2c 버스를 스캔하여 디바이스를 찾는 기능이 없다. 따라서 사전에 연결하고자 하는 디바이스의 주소를 미리 등록해두고 이 주소를 사용하여 디바이스가 있는지 HW detect를 시도할 수 있다.

 

다음 그림은 상위 버스에 i2c 호스트 컨트롤러(included i2c adapter)를 등록시키고 i2c 클라이언트 디바이스가 등록된 모습을 보여준다.

  • i2c 클라이언트 디바이스
    • 하드웨어 센서 모니터링 디바이스인 lm75를 예를 들었다.

 

i2c 호스트 컨트롤러 등록

i2c 호스트 컨트롤러 디바이스와 드라이버를 등록하기 위해 다음과 같은 API들을 사용한다.

예) 플랫폼 버스에 등록

  • 디바이스
    • platform_device_register() 함수를 사용하여 등록시키거나 디바이스 트리를 파싱하여 등록을 한다.
      • of_platform_device_create_pdata()
  • 드라이버
    • platform_register_driver() 함수를 사용하여 등록시킨다.
    • 드라이버 내부 probe 함수에서 i2c_add_adapter() 함수를 사용하여 i2c 버스를 제어할 i2c_adapter 구조체를 등록시킨다.

 

다음 그림은 broadcom 사의 i2c 호스트 컨트롤러 드라이버가 등록되는 주요 코드 과정을 보여준다.

 

i2c 클라이언트 등록

  • 디바이스 부분
    • i2c_new_device() 함수를 사용하여 등록시키거나 디바이스 트리를 파싱하여 등록을 한다.
      • of_i2c_register_device()
    • 유저 스페이스에서 sysfs를 사용하여 등록하는 방법도 있다.
      • 예) echo <device> 0x71 > /sys/bus/i2c/devices/i2c-0/new_device
  • 드라이버 부분
    • i2c_add_driver() 또는 i2c_register_driver() 함수를 사용하여 등록시킨다.

 

다음 그림은 wm8750 디바이스가 i2c 클라이언트 디바이스 및 드라이버로 등록되는 과정에서의 주요 코드 부분을 보여준다.

  • i2c 노드에 포함된 wm8750@1a 노드가 파싱되어 i2c_client 구조체에 포함된 후 i2c 버스에 디바이스로 이미 포함된 상태이다.
  • i2c_driver 구조체가 wm8750@1a 디바이스에 대응하는

 

유저 스페이스 바인딩

드라이버를 i2c 디바이스와 attach하는 방법

  • 기존(old) 방법
    • modprobe <driver> probe=1,0×71
    • modprobe <driver> force=1,0×71
    • modprobe <driver> force_<device>=1,0×71
  • 새로운(new) 방법
    • echo <device> 0x71 > /sys/bus/i2c/devices/i2c-0/new_device

 

드라이버를 i2c 디바이스와 attach하지 못하게 하는 방법

  • 기존(old) 방법
    • modprobe <driver> ignore=1,0×71
  • 새로운(new) 방법
    • 드라이버를 dummy 디바이스와 연결해두는 방법을 사용한다.
      • echo dummy 0x2f > /sys/bus/i2c/devices/i2c-1/new_device
      • modprobe <driver>

 

I2C Tools

i2c 툴을 설치하기 위해서는 다음과 같이 i2c-tools를 설치한다.

  • sudo apt-get install i2c-tools

 

i2cdetect

명령어 사용법

$ i2cdetect
Error: No i2c-bus specified!
Usage: i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]
i2cdetect -F I2CBUS
i2cdetect -l
I2CBUS is an integer or an I2C bus name
If provided, FIRST and LAST limit the probing range.

 

i2c 버스 및 i2c Adapter명 확인

x86 서버

다음과 같이 pc server는 두 개의 i2c adapter가 서로 다름을 알 수 있다.

$ i2cdetect -l
i2c-0   unknown         AST i2c bit bus                         N/A
i2c-1   unknown         SMBus I801 adapter at 0580              N/A

 

broadcom ns2(Cortex-A57)
$ i2cdetect -l
i2c-0 i2c Broadcom iProc I2C adapter I2C adapter
i2c-1 i2c Broadcom iProc I2C adapter I2C adapter

 

rpi3(Cortex-A53)
$ i2cdetect -l
i2c-1 i2c 3f804000.i2c I2C adapter

 

i2c 버스별 Functionalities 확인

x86 서버

0번 버스는 i2c  전송이 가능하고 smbus 전송도 에뮬레이션 처리가 가능하다. 반면에 1번 버스는 smbus 전송만 가능하다.

$ i2cdetect -F 0
Functionalities implemented by /dev/i2c-0:
I2C yes
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call yes
SMBus Block Write yes
SMBus Block Read yes
SMBus Block Process Call yes
SMBus PEC yes
I2C Block Write yes
I2C Block Read yes

$ sudo i2cdetect -F 1
Functionalities implemented by /dev/i2c-1:
I2C no
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call no
SMBus Block Write yes
SMBus Block Read yes
SMBus Block Process Call no
SMBus PEC yes
I2C Block Write yes
I2C Block Read yes

 

broadcom ns2(Cortex-A57)
$ i2cdetect -F 0
Functionalities implemented by /dev/i2c/0:
I2C yes
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call yes
SMBus Block Write yes
SMBus Block Read no
SMBus Block Process Call no
SMBus PEC yes
I2C Block Write yes
I2C Block Read yes

$ i2cdetect -F 0
  (상동)

 

rpi3(Cortex-A53)
$ i2cdetect -F 1
Functionalities implemented by /dev/i2c-1:
I2C yes
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call yes
SMBus Block Write yes
SMBus Block Read no
SMBus Block Process Call no
SMBus PEC yes
I2C Block Write yes
I2C Block Read yes

 

i2c 버스별 디바이스 스캔

x86 서버
$ sudo i2cdetect -y -r 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

$ sudo i2cdetect -y -r 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- 44 -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

 

broadcom ns2(Cortex-A57)
  • HW 스캔된 디바이스는 숫자로 표기되고, UU로 표기된 디바이스는 이미 바인딩(dummy 포함)된 디바이스이다.
$ i2cdetect -y -r 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- UU -- -- --
50: 50 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- --
70: UU -- UU -- -- -- -- --

# i2cdetect -y -r 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- UU
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

 

rpi3(Cortex-A53)
$ i2cdetect -y -r 0
Error: Could not open file `/dev/i2c-0' or `/dev/i2c/0': No such file or directory

$ i2cdetect -y -r 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

 

i2cdump

명령어 사용법
$ i2cdump
Error: No i2c-bus specified!
Usage: i2cdump [-f] [-o] [-y] [-r first-last] I2CBUS ADDRESS [MODE [BANK [BANKREG]]]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
b (byte, default)
w (word)
W (word on even register addresses)
s (SMBus block)
i (I2C block)
c (consecutive byte)

 

데이터 dump

1번 버스의 0x4f 디바이스 덤프

$ i2cdump -f -y 1 0x4f
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 22 11 ff 00 00 ff 00 00 ff 00 ff 0f 00 00 00 00 "?.........?....
10: 00 ff 04 00 00 00 00 00 00 00 00 00 00 00 00 00 ..?.............
…
e0: 22 11 ff 00 00 ff 00 00 ff 00 ff 0f 00 00 00 00 "?.........?....
f0: 00 ff 04 00 00 00 00 00 00 00 00 00 00 00 00 00 ..?.............

 

i2cget

명령어 사용법
$ i2cget 
Usage: i2cget [-f] [-y] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
b (read byte data, default)
w (read word data)
c (write byte/read byte)
Append p for SMBus PEC

 

1 바이트 읽기

0번 버스의 0x4f 디바이스에서 offset 0x0b에 해당하는 바이트를 읽어온다.

$ i2cget -f -y 0 0x4c 0x0b
0x0f

 

i2cset

명령어 사용법
$ i2cset
Usage: i2cset [-f] [-y] [-m MASK] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
c (byte, no value)
b (byte data, default)
w (word data)
i (I2C block data)
s (SMBus block data)
Append p for SMBus PEC

 

1바이트 쓰기

1번 버스의 0x72 디바이스 0 offset에 0x01을 기록한다. (i2c 스위치에 0번 채널을 선택하게 한다.)

$ i2cset -f -y 1 0x72 0 0x01

 

i2c trace

i2c를 trace하기 위해 ftrace를 사용해본다. ftrace를 사용하기 위해 다음의 커널 옵션이 사용된다.

  • CONFIG_FTRACE
  • CONFIG_ENABLE_DEFAULT_TRACERS

 

먼저 tracefs를 /sys/kernel/tracing 디렉토리에 마운트한다.

$ mount -t tracefs nodev /sys/kernel/tracing

 

마운트한  후 해당 디렉토리를 살펴보면 다음과 같다.

# cd /sys/kernel/tracing/
# ls -la
total 0
drwx------    6 root     root             0 Jan  1 09:00 .
drwxr-xr-x    8 root     root             0 Jan  1 09:31 ..
-r--r--r--    1 root     root             0 Jan  1 09:00 README
-r--r--r--    1 root     root             0 Jan  1 09:00 available_events
-r--r--r--    1 root     root             0 Jan  1 09:00 available_tracers
-rw-r--r--    1 root     root             0 Jan  1 09:00 buffer_size_kb
-r--r--r--    1 root     root             0 Jan  1 09:00 buffer_total_size_kb
-rw-r--r--    1 root     root             0 Jan  1 09:00 current_tracer
drwxr-xr-x   45 root     root             0 Jan  1 09:00 events
--w-------    1 root     root             0 Jan  1 09:00 free_buffer
drwxr-xr-x    2 root     root             0 Jan  1 09:00 instances
drwxr-xr-x    2 root     root             0 Jan  1 09:00 options
drwxr-xr-x    6 root     root             0 Jan  1 09:00 per_cpu
-r--r--r--    1 root     root             0 Jan  1 09:00 printk_formats
-r--r--r--    1 root     root             0 Jan  1 09:00 saved_cmdlines
-rw-r--r--    1 root     root             0 Jan  1 09:00 saved_cmdlines_size
-rw-r--r--    1 root     root             0 Jan  1 09:00 set_event
-rw-r--r--    1 root     root             0 Jan  1 09:00 set_event_pid
-rw-r--r--    1 root     root             0 Jan  1 09:00 trace
-rw-r--r--    1 root     root             0 Jan  1 09:00 trace_clock
--w--w----    1 root     root             0 Jan  1 09:00 trace_marker
-rw-r--r--    1 root     root             0 Jan  1 09:00 trace_options
-r--r--r--    1 root     root             0 Jan  1 09:00 trace_pipe
-rw-r--r--    1 root     root             0 Jan  1 09:00 tracing_cpumask
-rw-r--r--    1 root     root             0 Jan  1 09:00 tracing_on
-rw-r--r--    1 root     root             0 Jan  1 09:00 tracing_thresh

 

사용할 수 있는 i2c 이벤트를 확인해본다.

$ cat available_events | grep i2c
i2c:smbus_result
i2c:smbus_reply
i2c:smbus_read
i2c:smbus_write
i2c:i2c_result
i2c:i2c_reply
i2c:i2c_read
i2c:i2c_write

 

다음 명령을 통해 i2c 이벤트 전체를 trace 하도록 enable 한다.

$ echo 1 > events/i2c/enable

 

만일 i2c-adapter(i2c-bus 호스트 컨트롤러)가 여러 개 준비되어 있는 경우 그 중 하나만 선택하려면 다음과 같은 명령을 사용할 수 있다.

$ echo adapter_nr==0 > events/i2c/filter

 

다음은 0x72 주소를 사용하는 i2c 스위치 디바이스에 0x80 데이터를 기록하여 총 8개 채널 중 마지막 채널을 연결한다.

  • trace에 0 값을 기록하는 경우 기존 trace 로그를 삭제한다.
$ echo 0 > trace; i2cset -f -y 0 0x72 0x80; cat trace
# tracer: nop
#
# entries-in-buffer/entries-written: 4/4   #P:2
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
          i2cset-168   [000] ....  2027.045809: smbus_write: i2c-0 a=072 f=0000 c=80 BYTE l=0 []
          i2cset-168   [000] ....  2027.045814: i2c_write: i2c-0 #0 a=072 f=0000 l=1 [80]
          i2cset-168   [000] ....  2027.046032: i2c_result: i2c-0 n=1 ret=1
          i2cset-168   [000] ....  2027.046033: smbus_result: i2c-0 a=072 f=0000 c=80 BYTE wr res=0

 

기록된 값을 확인해본다.

$ echo 0 > trace; i2cget -f -y 0 0x72; cat trace
0x80
# tracer: nop
#
# entries-in-buffer/entries-written: 6/6   #P:2
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
          i2cget-176   [000] ....  2326.033639: smbus_read: i2c-0 a=072 f=0000 c=0 BYTE
          i2cget-176   [000] ....  2326.033641: i2c_read: i2c-0 #0 a=072 f=0001 l=1
          i2cget-176   [000] ....  2326.033856: i2c_reply: i2c-0 #0 a=072 f=0001 l=1 [80]
          i2cget-176   [000] ....  2326.033857: i2c_result: i2c-0 n=1 ret=1
          i2cget-176   [000] ....  2326.033857: smbus_reply: i2c-0 a=072 f=0000 c=0 BYTE l=1 [80]
          i2cget-176   [000] ....  2326.033858: smbus_result: i2c-0 a=072 f=0000 c=0 BYTE rd res=0

 

위의 방법과 유사하지만 tracefs를 마운트하지 않고 /sys/kernel/debug/tracing 디렉토리에서 동일한 동작을 할 수도 있다.

 

U-Boot에서 I2C 제어

명령어 헬프
u-boot> i2c
i2c - I2C sub-system

Usage:
i2c crc32 chip address[.0, .1, .2] count - compute CRC32 checksum
i2c dev [dev] - show or set current I2C bus
i2c loop chip address[.0, .1, .2] [# of objects] - looping read of device
i2c md chip address[.0, .1, .2] [# of objects] - read from I2C device
i2c mm chip address[.0, .1, .2] - write to I2C device (auto-incrementing)
i2c mw chip address[.0, .1, .2] value [count] - write to I2C device (fill)
i2c nm chip address[.0, .1, .2] - write to I2C device (constant address)
i2c probe [address] - test for and show device(s) on the I2C bus
i2c read chip address[.0, .1, .2] length memaddress - read to memory
i2c write memaddress chip address[.0, .1, .2] length [-s] - write memory
          to I2C; the -s option selects bulk write in a single transaction
i2c reset - re-init the I2C Controller
i2c speed [speed] - show or set I2C bus speed

 

버스 선택 및 probe
u-boot> i2c dev 0
Setting bus to 0
u-boot> i2c probe
Valid chip addresses: 00 4C 68 70 72

 

u-boot> i2c dev 1
Setting bus to 1
u-boot> i2c probe
Valid chip addresses: 4F

 

데이터 덤프
u-boot> i2c md 0x4f 0
0000: 22 11 ff 00 00 ff 00 00 ff 00 ff 0f 00 00 00 00 "............…

u-boot> i2c md 0x4f 2 1
0002: ff

 

데이터 기록(0x4f 디바이스 2 offset에 0xf7 기록)
u-boot> i2c mw 0x4f 2 f7

 

참고

 

 

Device & Driver -3- (Platform Device)

<kernel v4.14>

플랫폼 디바이스

리눅스 커널에서 기능 별로 분류한 디바이스와 전혀 다르게 플랫폼 디바이스라고 불리는 디바이스가 있다. 플랫폼은 anonymous(virtual) 버스와 비슷한 개념이다. 이러한 플랫폼에 연결되는 디바이스가 플랫폼 디바이스인데 일반 디바이스와 다른 것이 무엇인지 알아보자.

플랫폼 버스와 연결된 플랫폼 디바이스와 해당 드라이버들이 있다.  플랫폼 디바이스가 일반 디바이스와 다르게 불리는 이유를 알아보자.

  • 플랫폼 디바이스는 SoC 내부에 임베드된 디바이스 또는 버스 디바이스로 cpu로 부터 직접 주소 지정이 가능한 특성을 가진다.
    • 플랫폼 디바이스의 레지스터를 가상 주소 공간에 매핑하여 직접 조작할 수 있다.
  • 플랫폼 디바이스는 디바이스의 시작(probe) 시점이 정해지지 않은 디바이스이다.
    • 자동 발견되지 않으므로 누군가 지정해줘야 하는 디바이스가 플랫폼 디바이스이다.
    • 누가 플랫폼 디바이스를 등록시킬까?
      • 디바이스 트리를 사용하기 전에는 보드 특정(board, machine 또는 architect specific) 셋업 코드에서 플랫폼 디바이스를 등록시켰다.
      • 디바이스 트리를 사용하는 최신 리눅스 커널은 대부분 디바이스 트리를 통해서 등록된다.
  • 플러그 가능한 PCI 및 USB 버스에 연결되는 디바이스는 플랫폼 디바이스가 아니다.
    • 참고로 pci 디바이스의 인식 및 가동 정보는 pci 자체적으로 파악할 수 있으므로 반드시 디바이스 트리를 사용할 필요가 없다.
  • 플랫폼 디바이스의 등록은 커널 부팅 시 매우 빠른(early) 시간에 해야한다.
  • 플랫폼 드라이버의 등록은 In-kernel 드라이버인 경우 커널 부팅 후에 하고 모듈 방식인 경우 모듈 로딩 시에 수행된다.

 

다음 그림과 같이 4개의 플랫폼 디바이스의 위치를 확인할 수 있다.

  • 다음과 같이 가정할 때 두 개의 i2c bus controller 중 A는 플랫폼 디바이스로, 그리고 C는 pci 디바이스로 등록해야한다.
    • bus controller A가 ic2 bus controller
    • bus controller B가 pci bus controller
    • bus controller C가 i2c bus controller

 

platform_device 구조체

플랫폼 디바이스를 등록하기 위해 아래의 platform_device 구조체를 등록한다. 디바이스 구조체를 임베드한 platform_device 구조체를 알아보자.

include/linux/platform_device.h

struct platform_device {                                                        
    const char  *name;                                                          
    int     id;                                                                 
    bool        id_auto;                                                        
    struct device   dev;                                                        
    u32     num_resources;                                                      
    struct resource *resource;                                                  
                                                                                
    const struct platform_device_id *id_entry;                                  
    char *driver_override; /* Driver name to force a match */                   
                                                                                
    /* MFD cell pointer */                                                      
    struct mfd_cell *mfd_cell;                                                  
                                                                                
    /* arch specific additions */                                               
    struct pdev_archdata    archdata;                                           
};

플랫폼 디바이스를 정의하기 위해 기본적으로 이름과 id 및 리소스들과 리소스의 수가 주어질 수 있다.

  • *name
    • 플랫폼 디바이스 이름
  • id
    • 전체 플랫폼이 아닌 해당 플랫폼 디바이스에서 유니크한 값
    • -1인 경우 하나밖에 없는 경우이다.
    • 두 개 이상의 디바이스 인스턴스가 주어진 경우 0부터 시작한다.
      • 예) serial/0 또는 serial.0
  • id_auto
    • id 발급을 자동으로할지 여부
  • dev
    • 디바이스 구조체가 임베드된다.
  • num_resources
    • 포함된 리소스의 수
  • *resource
    • 플랫폼 디바이스 고유의 리소스 데이터로 irq 또는 시작 주소와 사이즈를 갖는 리소스 값들

 

플랫폼 디바이스 등록

디바이스 드라이버 모듈을 따른 플랫폼 디바이스의 등록은 이전 32bit arm에서 많은 수의 보드 특정(board, machine 또는 architect specific) 셋업 코드에 아래의 플랫폼 디바이스 등록 API 코드들을 사용해왔다. 현재 디바이스 트리를 사용하는 arm64 커널은 arch-specific 셋업 코드를 사용하지 않고 디바이스 트리가 제공하는 정보를 파싱하여 각 성격에 맞는 디바이스로 등록된다. 대부분의 플랫폼 디바이스들은 일부 특수 노드를 제외하고 루트 노드의 자식 노드들이다. 그리고 플랫폼 디바이스 노드를 확장할 수 있는데, compatible=”simple-bus” 속성이 추가된 노드들(예: soc)에 포함된 자식 디렉토리들도 플랫폼 디바이스로 등록하여 사용한다.

  • int platform_device_register(struct platform_device *pdev);
    • 플랫폼 디바이스를 등록한다.
  • int platform_add_devices(struct platform_device **pdevs, int ndev);
    • 다수의 플랫폼 디바이스를 등록한다.

 

Legacy 플랫폼 디바이스 등록

디바이스 드라이버 모듈을 따르지 않는 기존 플랫폼 디바이스는 모듈(핫플러그)로 등록될 수 없다. Legacy 드라이버에서 다음 API의 사용을 피하고 두 번째 API를 사용하는 것을 권장한다.

    • 직접 사용을 피해야 할 API
      • platform_device_alloc()
  • 권장 API
    • platform_device_register_simple()

 

다음과 같이 시스템에 등록된 플랫폼 디바이스들을 알아보려면 “/sys/devices/platform” 디렉토리를 확인한다.

$ ls /sys/devices/platform -la
drwxr-xr-x  3 root root    0 May 29 13:07 0.flash
drwxr-xr-x  3 root root    0 May 29 13:07 0.gpio
drwxr-xr-x  4 root root    0 May 29 13:07 3f000000.pcie
drwxr-xr-x  4 root root    0 May 29 13:07 9000000.pl011
drwxr-xr-x  4 root root    0 May 29 13:07 9010000.pl031
drwxr-xr-x  3 root root    0 May 29 13:07 9020000.fw-cfg
drwxr-xr-x  4 root root    0 May 29 13:07 Fixed MDIO bus.0
drwxr-xr-x  3 root root    0 May 29 13:07 a000000.virtio_mmio
drwxr-xr-x  3 root root    0 May 29 13:07 a000200.virtio_mmio
...
drwxr-xr-x  4 root root    0 May 29 13:07 a003e00.virtio_mmio
drwxr-xr-x  3 root root    0 May 29 13:07 alarmtimer
drwxr-xr-x  3 root root    0 May 29 13:07 gpio-keys
drwxr-xr-x  3 root root    0 May 29 13:07 platform@c000000
drwxr-xr-x  3 root root    0 May 29 13:07 pmu
drwxr-xr-x  2 root root    0 May 29 13:16 power
drwxr-xr-x  3 root root    0 May 29 13:07 psci
drwxr-xr-x  4 root root    0 May 29 13:07 reg-dummy
drwxr-xr-x  4 root root    0 May 29 13:07 serial8250
drwxr-xr-x  3 root root    0 May 29 13:07 snd-soc-dummy
drwxr-xr-x  3 root root    0 May 29 13:07 timer

 

플랫폼 리소스 정보

플랫폼 디바이스를 등록할 때 저장할 플랫폼 리소스 데이터가 주어질 수 있다.  추후 플랫폼 드라이버가 probe되어 호출될 때 이 정보를 사용하여 HW 설정을 한다. 플랫폼 디바이스를 위해 저장될 수 있는 리소스 정보는 다음과 같다.

  • io
    • io 시작 포트 및 끝 포트
    • 디바이스 트리 노드의 reg 속성에서 읽는 값들
  • mem
    • memory 시작 주소 및 끝 주소
  • irq
    • irq 번호
    • 디바이스 트리 노드의 interrupts 및 interrupt-names 속성에서 읽는 값들
  • dma
    • dma 채널 번호
  • bus
    • 버스 시작 주소 및 끝 주소

 

플랫폼 드라이버

표준 드라이버 모델은 드라이버 외부로부터 발견(discovery) 및 열거(enumeration)가 가능하므로 플랫폼 드라이버 역시 (*probe) 후크 함수와 (*remove) 후크 함수등이 구현되어야 한다. 추가로 디바이스에 절전 기능이  있는 경우 해당 후크업 함수들도 구현되어야 한다.

 

platfrom_driver 구조체

include/linux/platform_device.h

struct platform_driver {                                                        
    int (*probe)(struct platform_device *);                                     
    int (*remove)(struct platform_device *);                                    
    void (*shutdown)(struct platform_device *);                                 
    int (*suspend)(struct platform_device *, pm_message_t state);               
    int (*resume)(struct platform_device *);                                    
    struct device_driver driver;                                                
    const struct platform_device_id *id_table;                                  
    bool prevent_deferred_probe;                                                
};
  • (*probe)
    • HW 디바이스 존재 유무를 판단하기 위해 호출되고 해당 디바이스를 사용하기 위해 디바이스 드라이버와 바인드한다.
    • 디바이스 리소스를 사용하여 irq, 레지스터 매핑등을 할 수 있다.
  • (*remove)
    • 디바이스의 사용을 완료시킨 경우 호출된다.
  • (*shutdown)
    • 디바이스의 전원을 끄려고할 때 호출된다.
  • (*suspend)
    • 디바이스가 절전 모드로 진입할 때 호출된다.
  • (*resume)
    • 디바이스가 절전 모드로부터 정상 모드로 돌아올 때 호출된다.
  • driver
    • 디바이스 드라이버 구조체가 임베드된다.
  • *id_table
    • 식별용 디바이스 id
  • prevent_deferred_probe
    • probe 유예 금지

 

플랫폼 드라이버의 등록

플랫폼 드라이버를 등록하는 방법은 다음과 같이 몇 가지가 있다.

  • int platform_driver_register(struct platform_driver *drv);
    • 일반적으로 사용하는 API이다.
  • int platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *))
    • 모듈(핫플러그) 형태의 디바이스 드라이버가 아닌 경우 이 API를 사용하면 메모리 foot-print를 줄일 수 있다.
    • 이 함수 코드가 .init 섹션에 등록시키므로 커널이 부트업한 후 .init 섹션을 모두 할당해제하여 메모리 사용량을 줄인다.
  • #define platform_register_drivers(drivers, count)
    • int __platform_register_drivers(struct platform_driver * const *drivers, unsigned int count, struct module *owner);
    • 복수의 디바이스 드라이버들을 등록하는 API이다. 만일 하나라도 디바이스 드라이버의 등록이 실패하면 한꺼번에 해제시킨다.

 

Early 플랫폼 디바이스 및 드라이버

예를 들어 “earlyprintk”를 지원하기 위해 early 플랫폼 디바이스로 early 시리얼 콘솔을 사용할 수 있도록 커널은 6 단계의 스텝을 수행하였다.

  1. eraly 플랫폼 디바이스 데이터 등록
  2. 커널 커멘드 라인 파싱
  3. 해당 클래스로의 early 플랫픔 드라이버 설치
  4. early 플랫폼 드라이버 등록
  5. 해당 클래스에서 early 플랫폼 드라이버의 probing
  6. early 플랫폼 드라이버 probe 내부

 

플랫폼 디바이스 등록

 

다음 그림은 플랫폼 디바이스를 등록하는 과정을 보여준다.

  • 커널 부트업 시 몇 개의 등록된 initcall 함수를 통해 플랫폼 디바이스가 등록된다.
  • of_platform_default_populate_init() 함수를 통해 디바이스 트리를 통해서 등록되는 플랫폼 디바이스가 더 많다.

 

다음 그림은 플랫폼 디바이스와 플랫폼 드라이버의 등록 및 바인딩하는 모습을 보여준다.

 

platform_device_register()

drivers/base/platform.c

/**                                                                             
 * platform_device_register - add a platform-level device                       
 * @pdev: platform device we're adding                                          
 */                                                                             
int platform_device_register(struct platform_device *pdev)                      
{                                                                               
    device_initialize(&pdev->dev);                                              
    arch_setup_pdev_archdata(pdev);                                             
    return platform_device_add(pdev);                                           
}                                                                               
EXPORT_SYMBOL_GPL(platform_device_register)

인자로 주어진 플랫폼 디바이스를 등록한다.

  • 코드 라인 7에서 플랫폼 디바이스 내부의 디바이스 구조체의 멤버들을 초기화한다.
  • 코드 라인 8에서 플랫폼 디바이스의 archdata 조작이 필요한 경우 호출하는 함수이다.
    • 현재는 ppc 아키텍처에서만 사용하고 있다.
  • 코드 라인 9에서 플랫폼 디바이스를 추가한다.

 

device_initialize()

drivers/base/core.c

/**                                                                             
 * device_initialize - init device structure.                                   
 * @dev: device.                                                                
 *                                                                              
 * This prepares the device for use by other layers by initializing             
 * its fields.                                                                  
 * It is the first half of device_register(), if called by                      
 * that function, though it can also be called separately, so one               
 * may use @dev's fields. In particular, get_device()/put_device()              
 * may be used for reference counting of @dev after calling this                
 * function.                                                                    
 *                                                                              
 * All fields in @dev must be initialized by the caller to 0, except            
 * for those explicitly set to some other value.  The simplest                  
 * approach is to use kzalloc() to allocate the structure containing            
 * @dev.                                                                        
 *                                                                              
 * NOTE: Use put_device() to give up your reference instead of freeing          
 * @dev directly once you have called this function.                            
 */                                                                             
void device_initialize(struct device *dev)                                      
{                                                                               
    dev->kobj.kset = devices_kset;                                              
    kobject_init(&dev->kobj, &device_ktype);                                    
    INIT_LIST_HEAD(&dev->dma_pools);                                            
    mutex_init(&dev->mutex);                                                    
    lockdep_set_novalidate_class(&dev->mutex);                                  
    spin_lock_init(&dev->devres_lock);                                          
    INIT_LIST_HEAD(&dev->devres_head);                                          
    device_pm_init(dev);                                                        
    set_dev_node(dev, -1);                                                      
#ifdef CONFIG_GENERIC_MSI_IRQ                                                   
    INIT_LIST_HEAD(&dev->msi_list);                                             
#endif                                                                          
    INIT_LIST_HEAD(&dev->links.consumers);                                      
    INIT_LIST_HEAD(&dev->links.suppliers);                                      
    dev->links.status = DL_DEV_NO_DRIVER;                                       
}                                                                               
EXPORT_SYMBOL_GPL(device_initialize);

디바이스 구조체의 멤버들을 초기화한다.

 

platform_device_add()

drivers/base/platform.c

/**                                                                             
 * platform_device_add - add a platform device to device hierarchy              
 * @pdev: platform device we're adding                                          
 *                                                                              
 * This is part 2 of platform_device_register(), though may be called           
 * separately _iff_ pdev was allocated by platform_device_alloc().              
 */                                                                             
int platform_device_add(struct platform_device *pdev)                           
{                                                                               
    int i, ret;                                                                 
                                                                                
    if (!pdev)                                                                  
        return -EINVAL;                                                         
                                                                                
    if (!pdev->dev.parent)                                                      
        pdev->dev.parent = &platform_bus;                                       
                                                                                
    pdev->dev.bus = &platform_bus_type;                                         
                                                                                
    switch (pdev->id) {                                                         
    default:                                                                    
        dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);               
        break;                                                                  
    case PLATFORM_DEVID_NONE:                                                   
        dev_set_name(&pdev->dev, "%s", pdev->name);                             
        break;                                                                  
    case PLATFORM_DEVID_AUTO:                                                   
        /*                                                                      
         * Automatically allocated device ID. We mark it as such so             
         * that we remember it must be freed, and we append a suffix            
         * to avoid namespace collision with explicit IDs.                      
         */                                                                     
        ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);            
        if (ret < 0)                                                            
            goto err_out;                                                       
        pdev->id = ret;                                                         
        pdev->id_auto = true;                                                   
        dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);           
        break;                                                                  
    }

플랫폼 디바이스를 등록한다.

  • 코드 라인 15~16에서 부모 디바이스가 지정되지 않은 경우 플랫폼 버스를 지정한다.
  • 코드 라인 18에서 디바이스에 플랫폼 버스타입을 지정한다.
  • 코드 라인 20~40에서 디바이스 id를 포함하여 디바이스명을 지정한다.
    • pdev->id가 주어진 경우
      • 예) foo.1
    • id가 없는 경우, pdev->id = PLATFORM_DEVID_NONE
      • 예) foo
    • id를 자동 생성하는 경우, pdev->id = PLATFORM_DEVID_AUTO
      • 예) foo.1.auto

 

    for (i = 0; i < pdev->num_resources; i++) {                                 
        struct resource *p, *r = &pdev->resource[i];                            
                                                                                
        if (r->name == NULL)                                                    
            r->name = dev_name(&pdev->dev);                                     
                                                                                
        p = r->parent;                                                          
        if (!p) {                                                               
            if (resource_type(r) == IORESOURCE_MEM)                             
                p = &iomem_resource;                                            
            else if (resource_type(r) == IORESOURCE_IO)                         
                p = &ioport_resource;                                           
        }                                                                       
                                                                                
        if (p && insert_resource(p, r)) {                                       
            dev_err(&pdev->dev, "failed to claim resource %d: %pR\n", i, r);    
            ret = -EBUSY;                                                       
            goto failed;                                                        
        }                                                                       
    }                                                                           
                                                                                
    pr_debug("Registering platform device '%s'. Parent at %s\n",                
         dev_name(&pdev->dev), dev_name(pdev->dev.parent));                     
                                                                                
    ret = device_add(&pdev->dev);                                               
    if (ret == 0)                                                               
        return ret;                                                             
                                                                                
 failed:                                                                        
    if (pdev->id_auto) {                                                        
        ida_simple_remove(&platform_devid_ida, pdev->id);                       
        pdev->id = PLATFORM_DEVID_AUTO;                                         
    }                                                                           
                                                                                
    while (--i >= 0) {                                                          
        struct resource *r = &pdev->resource[i];                                
        if (r->parent)                                                          
            release_resource(r);                                                
    }                                                                           
                                                                                
 err_out:                                                                       
    return ret;                                                                 
}                                                                               
EXPORT_SYMBOL_GPL(platform_device_add);
  • 코드 라인 1~5에서 플랫폼 디바이스에 지정된 리소스를 대상으로 손회하며 리소스 명이 없는 경우 플랫폼 디바이스 명을 사용한다.
  • 코드 라인 7~20에서 부모 리소스의 현재 리소스 정보의 추가를 시도한다. 만일 부모 리소스가 없으면 리소스의 타입에 따라 최상위 리소스 트리인 iomem_resource 또는 ioport_resource를 사용한다.
  • 코드 라인 25~27에서 디바이스를 추가한다.

 

디바이스 트리를 사용한 플랫폼 디바이스 등록

 

아래 그림은 디바이스 트리에서 플랫폼 디바이스로 등록되는 노드와 그렇지 않은 노드를 보여준다.

  • 플랫폼 디바이스로 등록 가능한 노드
    • 부모 노드가 루트 노드인 경우
    • 부모 노드가 플랫폼 버스인 경우

 

다음 그림은 디바이스 트리 노드를 읽어 플랫폼 디바이스로 등록할 때 플랫폼 리소스 정보도 같이 수집하여 등록되는 과정을 보여준다.

  • 아래 예와 같이 메모리 정보와 irq 정보를 플랫폼 디바이스의 리소스로 등록한다.

 

다음 그림은 Legacy irq 대신 MSI를 사용하는 플랫폼 디바이스를 등록하는 과정을 보여준다.

  • 대부분의 MSI는 PCI 디바이스가 사용한다. 플랫폼 디바이스가 사용하는 매우 드물다.

 

of_platform_default_populate_init()

drivers/of/platform.c

static int __init of_platform_default_populate_init(void)                       
{                                                                               
    struct device_node *node;                                                   
                                                                                
    if (!of_have_populated_dt())                                                
        return -ENODEV;                                                         
                                                                                
    /*                                                                          
     * Handle ramoops explicitly, since it is inside /reserved-memory,          
     * which lacks a "compatible" property.                                     
     */                                                                         
    node = of_find_node_by_path("/reserved-memory");                            
    if (node) {                                                                 
        node = of_find_compatible_node(node, NULL, "ramoops");                  
        if (node)                                                               
            of_platform_device_create(node, NULL, NULL);                        
    }                                                                           
                                                                                
    /* Populate everything else. */                                             
    of_platform_default_populate(NULL, NULL, NULL);                             
                                                                                
    return 0;                                                                   
}                                                                               
arch_initcall_sync(of_platform_default_populate_init);

다비이스 트리를 파싱하여 플랫폼 디바이스들을 생성하여 등록한다.

  • 코드 라인 5~6에서 디바이스 트리가 없는 경우 -ENODEV 결과를 반환한다.
  • 코드 라인 12~17에서 “/reserved-memory” 노드가 발견된 경우 그 노드 안에 “ramoops” 디바이스명이 있으면 “ramoops”  디바이스를 생성하여 등록한다.
  • 코드 라인 20에서 디바이스 트리를 모두 파싱하여 플랫폼 디바이스를 생성하고 등록한다.

 

of_platform_default_populate()

drivers/of/platform.c

int of_platform_default_populate(struct device_node *root,                      
                 const struct of_dev_auxdata *lookup,                           
                 struct device *parent)                                         
{                                                                               
    return of_platform_populate(root, of_default_bus_match_table, lookup,       
                    parent);                                                    
}                                                                               
EXPORT_SYMBOL_GPL(of_platform_default_populate);

디바이스 트리를 루트부터 모두 파싱하여 아래 4개의 기본 버스 타입에 연결된 디바이스를 플랫폼 디바이스로 인식하여 생성하고 등록한다.

  • “simple-bus”
  • “simple-mfd”
  • “isa”
  • “arm,amba-bus”

of_default_bus_match_table[]

drivers/of/platform.c

const struct of_device_id of_default_bus_match_table[] = {                      
    { .compatible = "simple-bus", },                                            
    { .compatible = "simple-mfd", },                                            
    { .compatible = "isa", },                                                   
#ifdef CONFIG_ARM_AMBA                                                          
    { .compatible = "arm,amba-bus", },                                          
#endif /* CONFIG_ARM_AMBA */                                                    
    {} /* Empty terminated list */                                              
};

 

of_platform_populate()

drivers/of/platform.c

/**                                                                             
 * of_platform_populate() - Populate platform_devices from device tree data     
 * @root: parent of the first level to probe or NULL for the root of the tree   
 * @matches: match table, NULL to use the default                               
 * @lookup: auxdata table for matching id and platform_data with device nodes   
 * @parent: parent to hook devices from, NULL for toplevel                      
 *                                                                              
 * Similar to of_platform_bus_probe(), this function walks the device tree      
 * and creates devices from nodes.  It differs in that it follows the modern    
 * convention of requiring all device nodes to have a 'compatible' property,    
 * and it is suitable for creating devices which are children of the root       
 * node (of_platform_bus_probe will only create children of the root which      
 * are selected by the @matches argument).                                      
 *                                                                              
 * New board support should be using this function instead of                   
 * of_platform_bus_probe().                                                     
 *                                                                              
 * Returns 0 on success, < 0 on failure.                                        
 */
int of_platform_populate(struct device_node *root,                              
            const struct of_device_id *matches,                                 
            const struct of_dev_auxdata *lookup,                                
            struct device *parent)                                              
{                                                                               
    struct device_node *child;                                                  
    int rc = 0;                                                                 
                                                                                
    root = root ? of_node_get(root) : of_find_node_by_path("/");                
    if (!root)                                                                  
        return -EINVAL;                                                         
                                                                                
    pr_debug("%s()\n", __func__);                                               
    pr_debug(" starting at: %pOF\n", root);                                     
                                                                                
    for_each_child_of_node(root, child) {                                       
        rc = of_platform_bus_create(child, matches, lookup, parent, true);      
        if (rc) {                                                               
            of_node_put(child);                                                 
            break;                                                              
        }                                                                       
    }                                                                           
    of_node_set_flag(root, OF_POPULATED_BUS);                                   
                                                                                
    of_node_put(root);                                                          
    return rc;                                                                  
}                                                                               
EXPORT_SYMBOL_GPL(of_platform_populate);

디바이스 트리를 첫 번째 인자로 요청한 노드부터 시작하여 그 이하 노드를 파싱한다. 두 번째 인자로 요청한 버스 노드와 버스 매치 조건인 경우 자녀 노드들도 플랫폼 디바이스로 생성하고 등록한다.

  • 코드 라인 9~11에서 첫 번째 인자가 정상적으로 주어지지 않은 경우 -EINVAL 결과를 반환한다.
  • 코드 라인 16~22에서 두 번째 인자로 요청한 버스 노드와 버스 매치 조건인 경우 자녀 노드들도 플랫폼 디바이스로 생성하고 등록한다.
  • 코드 라인 23에서 첫 번째 인자로 요청한 노드에 OF_POPULATED_BUS 플래그를 설정하여 버스를 이미 커널에 등록하였음을 나타낸다.

 

of_platform_bus_create()

drivers/of/platform.c

/**                                                                             
 * of_platform_bus_create() - Create a device for a node and its children.      
 * @bus: device node of the bus to instantiate                                  
 * @matches: match table for bus nodes                                          
 * @lookup: auxdata table for matching id and platform_data with device nodes   
 * @parent: parent for new device, or NULL for top level.                       
 * @strict: require compatible property                                         
 *                                                                              
 * Creates a platform_device for the provided device_node, and optionally       
 * recursively create devices for all the child nodes.                          
 */                                                                             
static int of_platform_bus_create(struct device_node *bus,                      
                  const struct of_device_id *matches,                           
                  const struct of_dev_auxdata *lookup,                          
                  struct device *parent, bool strict)                           
{                                                                               
    const struct of_dev_auxdata *auxdata;                                       
    struct device_node *child;                                                  
    struct platform_device *dev;                                                
    const char *bus_id = NULL;                                                  
    void *platform_data = NULL;                                                 
    int rc = 0;                                                                 
                                                                                
    /* Make sure it has a compatible property */                                
    if (strict && (!of_get_property(bus, "compatible", NULL))) {                
        pr_debug("%s() - skipping %pOF, no compatible prop\n",                  
             __func__, bus);                                                    
        return 0;                                                               
    }                                                                           
                                                                                
    if (of_node_check_flag(bus, OF_POPULATED_BUS)) {                            
        pr_debug("%s() - skipping %pOF, already populated\n",                   
            __func__, bus);                                                     
        return 0;                                                               
    }                                                                           
                                                                                
    auxdata = of_dev_lookup(lookup, bus);                                       
    if (auxdata) {                                                              
        bus_id = auxdata->name;                                                 
        platform_data = auxdata->platform_data;                                 
    }                                                                           
                                                                                
    if (of_device_is_compatible(bus, "arm,primecell")) {                        
        /*                                                                      
         * Don't return an error here to keep compatibility with older          
         * device tree files.                                                   
         */                                                                     
        of_amba_device_create(bus, bus_id, platform_data, parent);              
        return 0;                                                               
    }                                                                           
                                                                                
    dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);  
    if (!dev || !of_match_node(matches, bus))                                   
        return 0;                                                               
                                                                                
    for_each_child_of_node(bus, child) {                                        
        pr_debug("   create child: %pOF\n", child);                             
        rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict); 
        if (rc) {                                                               
            of_node_put(child);                                                 
            break;                                                              
        }                                                                       
    }                                                                           
    of_node_set_flag(bus, OF_POPULATED_BUS);                                    
    return rc; 
}

첫 번째 인자로 요청한 버스 노드와 버스 매치 조건인 경우 자녀 노드들도 플랫폼 디바이스로 생성하고 등록한다.

  • 코드 라인 25~29에서 다섯 번째 인자가 true로 주어진 경우 “compatible” 속성이 발견되지 않으면 skip 하기 위해 성공(0)을 반환한다.
  • 코드 라인 31~35에서 이 버스가 이미 커널에 등록된 경우 skip 하기 위해 성공(0)을 반환한다.
  • 코드 라인 37~41에서 lookup 테이블에서 검색하여 플랫폼 데이터를 알아온다. lookup이 지정되지 않은 경우 아무런 일도 하지 않는다.
    • lookup table은 machine-specific 코드 셋업 루틴을 사용하는 디바이스 드라이버에서 사용되었지만 지금은 거의 사용하지 않는다.
  • 코드 라인 43~50에서 버스 노드의 compatible명이 “arm,primecell”인 경우 amba 버스 디바이스를 생성하기 위해 별도의 처리 함수를 사용한다. 그런 후 정상(0) 결과로 빠져나간다.
  • 코드 라인 52~54에서 플랫폼 디바이스를 생성하고 등록한다. 만일 생성이 실패하거나 버스가 매치되지 않은 경우 서브 디렉토리를 skip하기 위해 성공(0)을 반환한다.
    • 결국 부모 노드가 루트 노드인 경우 플랫폼 디바이스로 등록된다.
    • 부모 노드가 플랫폼 버스가 아닌 경우 그 이하의 서브 디렉토리들은 플랫폼 디바이스로 등록되지 않는다.
  • 코드 라인 56~63에서 버스 노드 아래 child 노드를 대상으로 이 함수를 재귀호출한다.
  • 코드 라인 64에서 요청한 노드에 OF_POPULATED_BUS 플래그를 설정하여 버스를 이미 커널에 등록하였음을 나타낸다.

 

auxdata lookup 테이블 예)
static struct of_dev_auxdata foo_auxdata_lookup[] __initdata = {            
    OF_DEV_AUXDATA("foo", 0xf1010600, "foo.0", NULL),       
    {},                                                                         
};
  • of_dev_auxdata 구조체에 사용되는 데이터는 compatible 디바이스명, 물리주소, 노드명, 플랫폼 데이터

 

of_platform_device_create_pdata()

drivers/of/platform.c

static struct platform_device *of_platform_device_create_pdata(                 
                    struct device_node *np,                                     
                    const char *bus_id,                                         
                    void *platform_data,                                        
                    struct device *parent)                                      
{                                                                               
    struct platform_device *dev;                                                
                                                                                
    if (!of_device_is_available(np) ||                                          
        of_node_test_and_set_flag(np, OF_POPULATED))                            
        return NULL;                                                            
                                                                                
    dev = of_device_alloc(np, bus_id, parent);                                  
    if (!dev)                                                                   
        goto err_clear_flag;                                                    
                                                                                
    dev->dev.bus = &platform_bus_type;                                          
    dev->dev.platform_data = platform_data;                                     
    of_msi_configure(&dev->dev, dev->dev.of_node);                              
                                                                                
    if (of_device_add(dev) != 0) {                                              
        platform_device_put(dev);                                               
        goto err_clear_flag;                                                    
    }                                                                           
                                                                                
    return dev;                                                                 
                                                                                
err_clear_flag:                                                                 
    of_node_clear_flag(np, OF_POPULATED);                                       
    return NULL;                                                                
}

플랫폼 디바이스를 생성하고 디바이스 트리로부터 msi 정보 및 플랫폼 리소스를 읽어 지정한 후 플랫폼 디바이스로 등록한다.

  • 코드 라인 8~11에서 디바이스가 준비되지 않은 경우 또는 노드가 이미 등록된 경우 null을 반환한다.
  • 코드 라인 13~15에서 디바이스를 생성한다.
  • 코드 라인 17~19에서 플랫폼 버스 타입과, 플랫폼 데이터를 지정한다. 그리고 디바이스 트리에서 msi 정보를 읽어 디바이스에 설정한다.
  • 코드 라인 21~24에서 플랫폼 디바이스를 생성하고 디바이스 트리로부터 플랫폼 리소스를 읽어 지정한 후 플랫폼 디바이스로 등록한다.

 

of_device_alloc()

drivers/of/platform.c

/**                                                                             
 * of_device_alloc - Allocate and initialize an of_device                       
 * @np: device node to assign to device                                         
 * @bus_id: Name to assign to the device.  May be null to use default name.     
 * @parent: Parent device.                                                      
 */                                                                             
struct platform_device *of_device_alloc(struct device_node *np,                 
                  const char *bus_id,                                           
                  struct device *parent)                                        
{                                                                               
    struct platform_device *dev;                                                
    int rc, i, num_reg = 0, num_irq;                                            
    struct resource *res, temp_res;                                             
                                                                                
    dev = platform_device_alloc("", PLATFORM_DEVID_NONE);                       
    if (!dev)                                                                   
        return NULL;                                                            
                                                                                
    /* count the io and irq resources */                                        
    while (of_address_to_resource(np, num_reg, &temp_res) == 0)                 
        num_reg++;                                                              
    num_irq = of_irq_count(np);                                                 
                                                                                
    /* Populate the resource table */                                           
    if (num_irq || num_reg) {                                                   
        res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);          
        if (!res) {                                                             
            platform_device_put(dev);                                           
            return NULL;                                                        
        }                                                                       
                                                                                
        dev->num_resources = num_reg + num_irq;                                 
        dev->resource = res;                                                    
        for (i = 0; i < num_reg; i++, res++) {                                  
            rc = of_address_to_resource(np, i, res);                            
            WARN_ON(rc);                                                        
        }                                                                       
        if (of_irq_to_resource_table(np, res, num_irq) != num_irq)              
            pr_debug("not all legacy IRQ resources mapped for %s\n",            
                 np->name);                                                     
    }                                                                           
                                                                                
    dev->dev.of_node = of_node_get(np);                                         
    dev->dev.fwnode = &np->fwnode;                                              
    dev->dev.parent = parent ? : &platform_bus;                                 
                                                                                
    if (bus_id)                                                                 
        dev_set_name(&dev->dev, "%s", bus_id);                                  
    else                                                                        
        of_device_make_bus_id(&dev->dev);                                       
                                                                                
    return dev;                                                                 
}                                                                               
EXPORT_SYMBOL(of_device_alloc);

플랫폼 디바이스를 생성하고 디바이스 트리로부터 플랫폼 리소스를 읽어 지정한 후 플랫폼 디바이스로 등록한다.

  • 코드 라인 15~17에서 플랫폼 디바이스를 할당하되 id는 주어지지 않게 한다.
  • 코드 라인 20~22에서 io 및 irq 리소스의 수를 파악하기 위해 임시로 읽어온다.
    • num_reg에는 노드에서 읽어온 io 주소 수가 담긴다.
    • num_irq에는 노드에서 읽어온 irq 수가 담긴다.
  • 코드 라인 25~41에서 io 및 irq 리소스가 존재하는 경우 그 수 만큼 리소스를 생성하고 노드에서 다시 리소스 정보를 정식으로 읽어 저장한다.
  • 코드 라인 43~45에서 디바이스에 노드 정보와 부모 디바이스 정보를 대입한다. 부모 디바이스가 없는 경우 플랫폼 버스를 지정한다.
  • 코드 라인 47~50에서 디바이스 이름을 bus_id로 설정한다. bus_id가 없는 경우 디바이스 트리 노드를 읽어 지정한다.
    • 디바이스 트리 노드를 읽어 사용한 예) 3f000000.pcie,   platform@c000000,  psci

 

디바이스명 생성 규칙 (for 디바이스 트리)

of_device_make_bus_id()

drivers/of/platform.c

/**                                                                             
 * of_device_make_bus_id - Use the device node data to assign a unique name     
 * @dev: pointer to device structure that is linked to a device tree node       
 *                                                                              
 * This routine will first try using the translated bus address to              
 * derive a unique name. If it cannot, then it will prepend names from          
 * parent nodes until a unique name can be derived.                             
 */                                                                             
static void of_device_make_bus_id(struct device *dev)                           
{                                                                               
    struct device_node *node = dev->of_node;                                    
    const __be32 *reg;                                                          
    u64 addr;                                                                   
                                                                                
    /* Construct the name, using parent nodes if necessary to ensure uniqueness */
    while (node->parent) {                                                      
        /*                                                                      
         * If the address can be translated, then that is as much               
         * uniqueness as we need. Make it the first component and return        
         */                                                                     
        reg = of_get_property(node, "reg", NULL);                               
        if (reg && (addr = of_translate_address(node, reg)) != OF_BAD_ADDR) {   
            dev_set_name(dev, dev_name(dev) ? "%llx.%s:%s" : "%llx.%s",         
                     (unsigned long long)addr, node->name,                      
                     dev_name(dev));                                            
            return;                                                             
        }                                                                       
                                                                                
        /* format arguments only used if dev_name() resolves to NULL */         
        dev_set_name(dev, dev_name(dev) ? "%s:%s" : "%s",                       
                 kbasename(node->full_name), dev_name(dev));                    
        node = node->parent;                                                    
    }                                                                           
}

디바이스 이름을 다음 규칙에 의해 이름을 만들어 지정한다.

1) 노드에 reg 속성이 있는 경우
  • 주어진 디바이스 명 존재 시: <주소>.<노드명>:<주어진 디바이스명>
  • 주어진 디바이스 명 없을 때: <주소>.<노드명>

 

다음 예와 같이 reg 속성이 있고, 주어진 디바이스명이 없는 경우 최종 디바이스명은 “9020000.fw-cfg”가 된다.

fw-cfg@9020000 {                                                            
        dma-coherent;                                                           
        reg = <0x0 0x9020000 0x0 0x18>;                                         
        compatible = "qemu,fw-cfg-mmio";                                        
};  

 

2) 노드에 reg 속성이 없는 경우
  • 주어진 디바이스 명 존재 시: <full_노드명>:<주어진 디바이스명>
  • 주어진 디바이스 명 없을 때: <full_노드명>

 

다음 예와 같이 reg 속성이 없고, 주어진 디바이스명이 없는 경우 최종 디바이스명은 “platform@c000000″가 된다.

platform@c000000 { 
        interrupt-parent = <0x8001>; 
        ranges = <0x0 0x0 0xc000000 0x2000000>; 
        #address-cells = <0x1>; 
        #size-cells = <0x1>; 
        compatible = "qemu,platform", "simple-bus"; 
}; 
 

Dynamic 디바이스 트리

Dynamic하게 디바이스 트리의 정보를 변경 시키고 플랫폼 디바이스의 등록 및 해제 시 호출되는 후크 함수이다. 보통 특정 아키텍처의 테스트 환경을 지원하기 위해 사용된다.

of_platform_notify()

drivers/of/platform.c

static int of_platform_notify(struct notifier_block *nb,                        
                unsigned long action, void *arg)                                
{                                                                               
    struct of_reconfig_data *rd = arg;                                          
    struct platform_device *pdev_parent, *pdev;                                 
    bool children_left;                                                         
                                                                                
    switch (of_reconfig_get_state_change(action, rd)) {                         
    case OF_RECONFIG_CHANGE_ADD:                                                
        /* verify that the parent is a bus */                                   
        if (!of_node_check_flag(rd->dn->parent, OF_POPULATED_BUS))              
            return NOTIFY_OK;   /* not for us */                                
                                                                                
        /* already populated? (driver using of_populate manually) */            
        if (of_node_check_flag(rd->dn, OF_POPULATED))                           
            return NOTIFY_OK;                                                   
                                                                                
        /* pdev_parent may be NULL when no bus platform device */               
        pdev_parent = of_find_device_by_node(rd->dn->parent);                   
        pdev = of_platform_device_create(rd->dn, NULL,                          
                pdev_parent ? &pdev_parent->dev : NULL);                        
        of_dev_put(pdev_parent);                                                
                                                                                
        if (pdev == NULL) {                                                     
            pr_err("%s: failed to create for '%pOF'\n",                         
                    __func__, rd->dn);                                          
            /* of_platform_device_create tosses the error code */               
            return notifier_from_errno(-EINVAL);                                
        }                                                                       
        break;                                                                  
                                                                                
    case OF_RECONFIG_CHANGE_REMOVE:                                             
                                                                                
        /* already depopulated? */                                              
        if (!of_node_check_flag(rd->dn, OF_POPULATED))                          
            return NOTIFY_OK;                                                   
                                                                                
        /* find our device by node */                                           
        pdev = of_find_device_by_node(rd->dn);                                  
        if (pdev == NULL)                                                       
            return NOTIFY_OK;   /* no? not meant for us */                      
                                                                                
        /* unregister takes one ref away */                                     
        of_platform_device_destroy(&pdev->dev, &children_left);                 
                                                                                
        /* and put the reference of the find */                                 
        of_dev_put(pdev);                                                       
        break;                                                                  
    }                                                                           
                                                                                
    return NOTIFY_OK;                                                           
}

디바이스 트리를 통해 플랫폼 디바이스가 등록되거나 호출될 때 사용되는 후크 함수이다.

  • 코드 라인 9~30에서 디바이스 트리에 플랫폼 디바이스가 발견되는 경우 등록한다.
  • 코드 라인 32~48에서 디바이스 트리로부터 등록된 플랫폼 디바이스를 등록 해제한다.

 

of_platform_device_create()

drivers/of/platform.c

/**                                                                             
 * of_platform_device_create - Alloc, initialize and register an of_device      
 * @np: pointer to node to create device for                                    
 * @bus_id: name to assign device                                               
 * @parent: Linux device model parent device.                                   
 *                                                                              
 * Returns pointer to created platform device, or NULL if a device was not      
 * registered.  Unavailable devices will not get registered.                    
 */                                                                             
struct platform_device *of_platform_device_create(struct device_node *np,       
                        const char *bus_id,                                     
                        struct device *parent)                                  
{                                                                               
    return of_platform_device_create_pdata(np, bus_id, NULL, parent);           
}                                                                               
EXPORT_SYMBOL(of_platform_device_create);

플랫폼 디바이스를 생성하고 디바이스 트리로부터 msi 정보 및 플랫폼 리소스를 읽어 저장한 후 플랫폼 디바이스로 등록한다.

  • platform_data만 null로 지정하여 호출한다.

 

플랫폼 드라이버 등록

 

드라이버 모듈 진입부

플랫폼 드라이버 모듈의 마지막에 다음과 같은 매크로 함수 중 하나를 사용하여 플랫폼 드라이버의 등록부 코드를 준비한다.

  • 모든 디바이스 및 드라이버 공통 진입부
    • device_initcall(foo_init)
      • 커널에 임베드하여 빌드한 경우 부트업 시 동작하며 모든 디바이스 및 드라이버의 진입부에 사용된다.
      • 커널에 임베드하지 않고 모듈로 빌드한 경우 module_init()과 동일하게 동작한다.
    • module_init(foo_init) & module_exit(foo_exit)
      • insmod를 사용한 모듈 방식으로 모든 디바이스 및 드라이버의 진입부에 사용된다.
  • 플랫폼 드라이버용 진입부
    • builtin_platform_driver(foo_driver)
      • 커널에 임베드하여 빌드한 경우 부트업 시 동작하며 플랫폼 드라이버 진입부에 사용된다.
      • 커널에 임베드하지 않고 모듈로 빌드한 경우 module_platform_driver()와 동일하게 동작한다.
    • module_platform_driver(foo_driver)
      • insmod를 사용한 모듈 방식으로 플랫폼 드라이버용 진입부에 사용된다.
  • 플랫폼 디바이스 생성 및 플랫폼 드라이버용 진입부
    • builtin_platform_driver_probe(foo_driver, foo_probe)
      • 커널에 임베드하여 빌드한 경우 부트업 시 동작하며 플랫폼 디바이스를 생성하면서 플랫폼 드라이버 진입에 사용된다.
      • 커널에 임베드하지 않고 모듈로 빌드한 경우 module_platform_driver_probe()와 동일하게 동작한다.
    • module_platform_driver_probe(foo_driver, foo_probe)
      • insmod를 사용한 모듈 방식으로 플랫폼 디바이스를 생성하면서 플랫폼 드라이버 진입에 사용된다.

 

플랫폼 드라이버를 등록하기 위해 다음을 준비한다.

  • probe() 함수
  • platform_device_id 구조체
  • platform_driver 구조체
#include <linux/module.h>
#include <linux/of.h>                                                           
#include <linux/of_device.h>
#include <linux/platform_device.h>

static int foo_probe(struct platform_device *pdev)                         
{
        ...
        return 0;
}

static const struct platform_device_id foo_id_table[] = {                      
    { "foo",    (unsigned long) &foo_id },                            
    { },                                                                        
};                                                                              

static const struct of_device_id foo_of_match_table[] = { 
    { 
        .compatible = "foo,foo", 
    }, 
    { /* sentinel */ }, 
}; 
MODULE_DEVICE_TABLE(of, foo_of_match_table);
                                                                                
static struct platform_driver foo_driver = {                               
    .probe      = foo_probe,                                               
    .driver     = {                                                             
        .name   = "foo",                                                   
        .of_match_table = foo_of_match_table,
    },                                                                          
    .id_table   = foo_id_table,                                                
};

 

1) device_initcall() 매크로 함수 사용 시

device_initcall() 함수의 동작은 다음과 같이 내부적으로 두 가지 상황으로 처리된다.

  • 이 드라이버가 커널에 임베드되어(커널의 menuconfig에서 ‘*’ 선택) 로드하는 경우
    • 커널의 부트업 시 initcall 호출 과정에서 foo_driver를 등록한다.
    • 그리고 이에 대한 디바이스도 이미 로드되어 매치 가능한 경우 bind하여 곧바로 foo_probe() 함수를 probe한다.
  • 커널에 임베드되지 않고 모듈 형태로 빌드한(커널의 menuconfig에서 ‘m’ 선택) 후 사용자 영역에서 insmod 명령에 의해 로드하는 경우
    • insmod 명령에 의해 드라이버 모듈을 로드하고 foo_init() 함수를 호출하여 foo_driver를 등록한다.
    • 역시 이에 대한 디바이스도 이미 로드되어 매치 가능한 경우 bind하여 곧바로 foo_probe() 함수를 probe한다.
static int __init foo_init(void)                                        
{                                                                               
        return platform_driver_register(&foo_driver);
}
device_initcall(foo_init);

 

2) module_init() & module_exit() 매크로 함수 사용 시

드라이버를 항상 모듈로 만들어 insmod에 의해서만 드라이버를 로드하려면 device_initcall() 대신 module_init()을 사용한다.

static int __init foo_init(void) { 
        return platform_driver_register(&foo_driver); 
} 
static void __exit foo_exit(void) { 
        platform_driver_unregister(&foo_driver);
} 
module_init(foo_init);
module_exit(foo_exit);

 

3) builtin_platform_driver() 매크로 함수 사용 시

커널 부트업 시 사용할 드라이버를 등록한다. device_initcall()보다 더 심플하게 코딩할 수 있다.

builtin_platform_driver(&foo_driver);

 

/* builtin_platform_driver() - Helper macro for builtin drivers that
 * don't do anything special in driver init.  This eliminates some
 * boilerplate.  Each driver may only use this macro once, and
 * calling it replaces device_initcall().  Note this is meant to be
 * a parallel of module_platform_driver() above, but w/o _exit stuff.
 */
#define builtin_platform_driver(__platform_driver) \
        builtin_driver(__platform_driver, platform_driver_register)

 

/*
 * builtin_driver() - Helper macro for drivers that don't do anything
 * special in init and have no exit. This eliminates some boilerplate.
 * Each driver may only use this macro once, and calling it replaces
 * device_initcall (or in some cases, the legacy __initcall).  This is
 * meant to be a direct parallel of module_driver() above but without
 * the __exit stuff that is not used for builtin cases.
 *
 * @__driver: driver name
 * @__register: register function for this driver type
 * @...: Additional arguments to be passed to __register
 *
 * Use this macro to construct bus specific macros for registering
 * drivers, and do not use it on its own.
 */
#define builtin_driver(__driver, __register, ...) \
static int __init __driver##_init(void) \
{ \
        return __register(&(__driver) , ##__VA_ARGS__); \
} \
device_initcall(__driver##_init);

 

4) module_platform_driver() 매크로 함수 사용 시

위의 module_init() 및 module_exit() 함수를 아래 매크로로 유사하게 만들어 사용하는 방법으로 매우 심플하게 코딩할 수 있다.

module_platform_driver(&foo_driver);

 

include/linux/platform_device.h

/* module_platform_driver() - Helper macro for drivers that don't do            
 * anything special in module init/exit.  This eliminates a lot of              
 * boilerplate.  Each module may only use this macro once, and                  
 * calling it replaces module_init() and module_exit()                          
 */                                                                             
#define module_platform_driver(__platform_driver) \                             
    module_driver(__platform_driver, platform_driver_register, \                
            platform_driver_unregister)

 

module_driver() 매크로 함수
/**                                                                             
 * module_driver() - Helper macro for drivers that don't do anything            
 * special in module init/exit. This eliminates a lot of boilerplate.           
 * Each module may only use this macro once, and calling it replaces            
 * module_init() and module_exit().                                             
 *                                                                              
 * @__driver: driver name                                                       
 * @__register: register function for this driver type                          
 * @__unregister: unregister function for this driver type                      
 * @...: Additional arguments to be passed to __register and __unregister.      
 *                                                                              
 * Use this macro to construct bus specific macros for registering              
 * drivers, and do not use it on its own.                                       
 */                                                                             
#define module_driver(__driver, __register, __unregister, ...) \                
static int __init __driver##_init(void) \                                       
{ \                                                                             
    return __register(&(__driver) , ##__VA_ARGS__); \                           
} \                                                                             
module_init(__driver##_init); \                                                 
static void __exit __driver##_exit(void) \                                      
{ \                                                                             
    __unregister(&(__driver) , ##__VA_ARGS__); \                                
} \                                                                             
module_exit(__driver##_exit);

 

5) builtin_platform_driver_probe() 매크로 함수 사용 시

builtin_platform_driver() 매크로 함수와 유사한 방식이다. device_driver 구조체에서 probe 함수 및 id_table을 지정하지 않은 것이 다른 점이다. 역시 아래와 같이 매우 심플하게 코딩할 수 있다.

static struct platform_driver foo_driver = {
        .remove = __exit_p(foo_remove),
        .driver = {
            .name = "foo",
        },
};
builtin_platform_driver_probe(foo_driver, foo_probe);

 

/* builtin_platform_driver_probe() - Helper macro for drivers that don't do
 * anything special in device init.  This eliminates some boilerplate.  Each
 * driver may only use this macro once, and using it replaces device_initcall.
 * This is meant to be a parallel of module_platform_driver_probe above, but
 * without the __exit parts.
 */
#define builtin_platform_driver_probe(__platform_driver, __platform_probe) \
static int __init __platform_driver##_init(void) \
{ \
        return platform_driver_probe(&(__platform_driver), \
                                     __platform_probe);    \
} \
device_initcall(__platform_driver##_init);

 

6) module_platform_driver_probe() 매크로 함수 사용 시

module_platform_driver() 매크로 함수와 유사한 방식이다. device_driver 구조체에서 probe 함수 및 id_table을 지정하지 않은 것이 다른 점이다. 역시 아래와 같이 매우 심플하게 코딩할 수 있다.

static struct platform_driver foo_driver = {
        .remove = __exit_p(foo_remove),
        .driver = {
            .name = "foo",
        },
};
module_platform_driver_probe(foo_driver, foo_probe);

 

/* module_platform_driver_probe() - Helper macro for drivers that don't do      
 * anything special in module init/exit.  This eliminates a lot of              
 * boilerplate.  Each module may only use this macro once, and                  
 * calling it replaces module_init() and module_exit()                          
 */                                                                             
#define module_platform_driver_probe(__platform_driver, __platform_probe) \     
static int __init __platform_driver##_init(void) \                              
{ \                                                                             
    return platform_driver_probe(&(__platform_driver), \                        
                     __platform_probe);    \                                    
} \                                                                             
module_init(__platform_driver##_init); \                                        
static void __exit __platform_driver##_exit(void) \                             
{ \                                                                             
    platform_driver_unregister(&(__platform_driver)); \                         
} \                                                                             
module_exit(__platform_driver##_exit);

 

플랫폼 드라이버 등록부

__platform_driver_register()

include/linux/platform_device.h

/*
 * use a macro to avoid include chaining to get THIS_MODULE
 */
#define platform_driver_register(drv) \
        __platform_driver_register(drv, THIS_MODULE)

플랫폼 드라이버를 등록한다.

 

__platform_driver_register()

drivers/base/platform.c

/**
 * __platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 * @owner: owning module/driver
 */
int __platform_driver_register(struct platform_driver *drv,
                                struct module *owner)
{               
        drv->driver.owner = owner;
        drv->driver.bus = &platform_bus_type;
        drv->driver.probe = platform_drv_probe;
        drv->driver.remove = platform_drv_remove;
        drv->driver.shutdown = platform_drv_shutdown;

        return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(__platform_driver_register);

플랫폼 드라이버를 등록한다.

  • 버스 타입 및 probe, remove, shutdown 함수는 플랫폼 드라이버 core가 제공하는 함수를 사용한다.

 

플랫폼 버스 타입

struct bus_type platform_bus_type = {
        .name           = "platform",
        .dev_groups     = platform_dev_groups,
        .match          = platform_match,
        .uevent         = platform_uevent,
        .pm             = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

 

플랫폼 디바이스 속성

플랫폼 디바이스에 부여되는 속성들은 다음과 같다.

  • drriver_override
  • modalias

 

플랫폼 디바이스 및 드라이버 매치 확인

platform_match()

drivers/base/platform.c

/**
 * platform_match - bind platform device to platform driver.
 * @dev: device.
 * @drv: driver.
 *
 * Platform device IDs are assumed to be encoded like this:
 * "<name><instance>", where <name> is a short description of the type of
 * device, like "pci" or "floppy", and <instance> is the enumerated
 * instance of the device, like '0' or '42'.  Driver IDs are simply
 * "<name>".  So, extract the <name> from the platform_device structure,
 * and compare it against the name of the driver. Return whether they match
 * or not.
 */
static int platform_match(struct device *dev, struct device_driver *drv)
{
        struct platform_device *pdev = to_platform_device(dev);
        struct platform_driver *pdrv = to_platform_driver(drv);

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

        /* Attempt an OF style match first */
        if (of_driver_match_device(dev, drv))
                return 1;

        /* Then try ACPI style match */
        if (acpi_driver_match_device(dev, drv))
                return 1;

        /* Then try to match against the id table */
        if (pdrv->id_table)
                return platform_match_id(pdrv->id_table, pdev) != NULL;

        /* fall-back to driver name match */
        return (strcmp(pdev->name, drv->name) == 0);
}

플랫폼 디바이스와 플랫폼 드라이버의 매치여부를 반환한다. (1=매치, 0=매치되지 않음)

  • 코드 라인 20~21에서 플랫폼 디바이스의 driver_override가 드라이버 명으로 설정된 경우 함수를 성공(1)을 반환한다.
  • 코드 라인 24~25에서 디바이스와 드라이버가 매치된 경우 성공(1)을 반환한다.
  • 코드 라인 28~29에서 ACPI 스타일로 디바이스와 드라이버가 매치되는 경우 성공(1)을 반환한다.
  • 코드 라인 32~33에서 플랫폼 디바이스에 있는 id_table과 플랫폼 드라이버에 있는 id_table이 매치되는 경우 성공(1)을 반환한다.
  • 코드 라인 36에서 마지막으로 플랫폼 디바이스명과 드라이버의 이름이 동일한 경우 성공(1)을 반환한다. 그렇지 않은 경우 0을 반환한다.

 

플랫폼 드라이버 등록과 동기 방법으로 probe

platform_driver_probe()

include/linux/platform_device.h

/* non-hotpluggable platform devices may use this so that probe() and           
 * its support may live in __init sections, conserving runtime memory.          
 */                                                                             
#define platform_driver_probe(drv, probe) \                                     
    __platform_driver_probe(drv, probe, THIS_MODULE)

플랫폼 디바이스 드라이버를 probe한다.

 

__platform_driver_probe()

drivers/base/platform.c

/**                                                                             
 * __platform_driver_probe - register driver for non-hotpluggable device        
 * @drv: platform driver structure                                              
 * @probe: the driver probe routine, probably from an __init section            
 * @module: module which will be the owner of the driver                        
 *                                                                              
 * Use this instead of platform_driver_register() when you know the device      
 * is not hotpluggable and has already been registered, and you want to         
 * remove its run-once probe() infrastructure from memory after the driver      
 * has bound to the device.                                                     
 *                                                                              
 * One typical use for this would be with drivers for controllers integrated    
 * into system-on-chip processors, where the controller devices have been       
 * configured as part of board setup.                                           
 *                                                                              
 * Note that this is incompatible with deferred probing.                        
 *                                                                              
 * Returns zero if the driver registered and bound to a device, else returns    
 * a negative error code and with the driver not registered.                    
 */
int __init_or_module __platform_driver_probe(struct platform_driver *drv,       
        int (*probe)(struct platform_device *), struct module *module)          
{                                                                               
    int retval, code;                                                           
                                                                                
    if (drv->driver.probe_type == PROBE_PREFER_ASYNCHRONOUS) {                  
        pr_err("%s: drivers registered with %s can not be probed asynchronously\n",
             drv->driver.name, __func__);                                       
        return -EINVAL;                                                         
    }                                                                           
                                                                                
    /*                                                                          
     * We have to run our probes synchronously because we check if              
     * we find any devices to bind to and exit with error if there              
     * are any.                                                                 
     */                                                                         
    drv->driver.probe_type = PROBE_FORCE_SYNCHRONOUS;                           
                                                                                
    /*                                                                          
     * Prevent driver from requesting probe deferral to avoid further           
     * futile probe attempts.                                                   
     */                                                                         
    drv->prevent_deferred_probe = true;                                         
                                                                                
    /* make sure driver won't have bind/unbind attributes */                    
    drv->driver.suppress_bind_attrs = true;                                     
                                                                                
    /* temporary section violation during probe() */                            
    drv->probe = probe;                                                         
    retval = code = __platform_driver_register(drv, module);                    
                                                                                
    /*                                                                          
     * Fixup that section violation, being paranoid about code scanning         
     * the list of drivers in order to probe new devices.  Check to see         
     * if the probe was successful, and make sure any forced probes of          
     * new devices fail.                                                        
     */                                                                         
    spin_lock(&drv->driver.bus->p->klist_drivers.k_lock);                       
    drv->probe = NULL;                                                          
    if (code == 0 && list_empty(&drv->driver.p->klist_devices.k_list))          
        retval = -ENODEV;                                                       
    drv->driver.probe = platform_drv_probe_fail;                                
    spin_unlock(&drv->driver.bus->p->klist_drivers.k_lock);                     
                                                                                
    if (code != retval)                                                         
        platform_driver_unregister(drv);                                        
    return retval;                                                              
}                                                                               
EXPORT_SYMBOL_GPL(__platform_driver_probe);

플랫폼 드라이버를 등록하고 동기 방법으로 probe한다.

  • 코드 라인 6~10에서 드라이버의 probe 타입이 PROBE_PREFER_ASYNCHRONOUS인 경우 에러를 출력하고 -EINVAL 에러를 반환한다. 이 함수는 비동기 probe를 지원하지 않는다.
    • PROBE_PREFER_ASYNCHRONOUS는 mtd 드라이버나 i2c 등에서 probing이 저속으로 동작하는 장치등에서 사용한다.
  • 코드 라인 17에서 드라이버의 probe 타입을 PROBE_FORCE_SYNCHRONOUS로 고정한다.
  • 코드 라인 23에서 deferred probe가 동작하지 못하게 금지시킨다.
  • 코드 라인 26에서 드라이버에 suppress_bind_attrs가 설정된 경우 sysfs에서 bind/unbind 속성 파일을 생성하지 못하게 한다.
  • 코드 라인 29~30에서 드라이버에 인자로 전달 받은 probe 함수를 지정하고 플랫폼 드라이버를 등록한다.

 

subsystem 등록

subsys_system_register()

/**                                                                             
 * subsys_system_register - register a subsystem at /sys/devices/system/        
 * @subsys: system subsystem                                                    
 * @groups: default attributes for the root device                              
 *                                                                              
 * All 'system' subsystems have a /sys/devices/system/<name> root device        
 * with the name of the subsystem. The root device can carry subsystem-         
 * wide attributes. All registered devices are below this single root           
 * device and are named after the subsystem with a simple enumeration           
 * number appended. The registered devices are not explicitly named;            
 * only 'id' in the device needs to be set.                                     
 *                                                                              
 * Do not use this interface for anything new, it exists for compatibility      
 * with bad ideas only. New subsystems should use plain subsystems; and         
 * add the subsystem-wide attributes should be added to the subsystem           
 * directory itself and not some create fake root-device placed in              
 * /sys/devices/system/<name>.                                                  
 */                                                                             
int subsys_system_register(struct bus_type *subsys,                             
               const struct attribute_group **groups)                           
{                                                                               
    return subsys_register(subsys, groups, &system_kset->kobj);                 
}                                                                               
EXPORT_SYMBOL_GPL(subsys_system_register);

서브 시스템을 “/sys/devices/system” 디렉토리 아래에 등록한다. (이하 코드는 생략)

 

기타 플랫폼 디바이스 & 드라이버 관련 API들

  • platform_get_resource()
    • 플랫폼 디바이스로부터 리소스를 가져온다.
  • platform_get_irq()
    • 플랫폼 디바이스로부터 irq 번호를 가져온다.
  • platform_irq_count()
    • 플랫폼 디바이스에있는 irq 수를 알아온다.
  • platform_get_resource_byname()
    • 플랫폼 디바이스에서 요청한 이름과 타입의 리소스를 알아온다.
  • platform_get_irq_byname()
    • 플랫폼 디바이스에서 요청한 irq 이름에 해당하는 irq 번호를 가져온다.
  • platform_device_put()
    • 플랫폼 디바이스를 destroy한다.
  • platform_device_add_resources()
    • 플랫폼 디바이스에 리소스들을 추가한다.
  • platform_device_add_data()
    • 플랫폼 디바이스에 플랫폼 데이터를 지정한다.
  • platform_device_add_properties()
    • 내장 속성을 플랫폼 디바이스에 추가한다.
  • platform_device_del()
    • 플랫폼 디바이스를 제거한다.
  • platform_device_unregister()
    • 플랫폼 디바이스를 등록 해제한다.
  • platform_driver_unregister()
    • 플랫폼 드라이버를 등록 해제한다.
  • __platform_create_bundle()
    • 플랫폼 드라이버를 등록하고 이에 대응하는 플랫폼 디바이스를 생성한다.
  • platform_unregister_drivers()
    • 복수의 플랫폼 드라이버들을 등록 해제한다.
  • platform_get_device_id()
  • to_platform_device()
    • 디바이스를 임베드한 플랫폼 디바이스를 반환한다.
  • to_platform_driver()
    • 드라이버를 임베드한 플랫폼 드라이버를 반환한다.

 

참고

 

 

Device & Driver -2- (Bus & Class)

<kernel v4.14>

버스, 디바이스, 드라이버 및 클래스의 관계

다음 그림의 위 박스에 물리적으로 버스에 두 개의 디바이스가 연결되어 있는 것을 알 수 있다. 아래 박스에는 이에 해당하는 구조체들이 구현 레벨로 표시되어 있다.

  • 버스 하나에 버스 컨트롤러 디바이스 및 Consumer 디바이스가 구성되어 있다.
  • 디바이스 하나에 device 구조체와 device_driver 구조체 두 개가 사용되어 등록되는 것을 알 수 있다.

 

디바이스는 버스와 연결될 디바이스와 클래스에 연결될 디바이스로 보통 구분하여 사용한다. 하나의 디바이스로 버스와 클래스를 동시에 지정하는 경우는 지원하지 않는다. 그리고 드라이버는 버스에 연결되는 디바이스와 매치되는 경우 바인딩되어 연동된다.

  • 버스 디바이스
    • 1개의 버스를 지정
  • 클래스 디바이스
    • 1개의 클래스를 지정
  • 드라이버
    • 버스 디바이스와 바인딩된다.

 

디바이스나 드라이버가 등록되기 이전에 버스와 클래스가 이미 등록되어 있어야 한다. 그런 후 버스에 디바이스와 드라이버가 포함될 수 있다. 클래스는 디바이스만을 포함시킬 수 있다.

  • 버스
    • 다수의 버스 디바이스들과 드라이버들을 관리한다.
  • 클래스
    • 다수의 클래스 디바이스들을 관리한다.

 

일반적으로 1개 이상의 클래스 디바이스를 만들 때, 먼저 버스 디바이스들을 만들고 그 밑으로 클래스 디바이스들을 생성하여 사용한다.

 

버스

버스를 등록하려면 다음과 같이 두 단계를 사용한다. (예: 플랫폼 버스)

  • 첫 번째, 버스 컨트롤러 디바이스(struct device) 등록
    • device_register(&platform_bus);
  • 두 번째, 버스(struct bus_type) 등록
    • bus_register(&platform_bus_type);

 

bus_type 구조체

include/linux/device.h

/**                                                                             
 * struct bus_type - The bus type of the device                                 
 *                                                                              
 * @name:   The name of the bus.                                                
 * @dev_name:   Used for subsystems to enumerate devices like ("foo%u", dev->id).
 * @dev_root:   Default device to use as the parent.                            
 * @bus_groups: Default attributes of the bus.                                  
 * @dev_groups: Default attributes of the devices on the bus.                   
 * @drv_groups: Default attributes of the device drivers on the bus.            
 * @match:  Called, perhaps multiple times, whenever a new device or driver     
 *      is added for this bus. It should return a positive value if the         
 *      given device can be handled by the given driver and zero                
 *      otherwise. It may also return error code if determining that            
 *      the driver supports the device is not possible. In case of              
 *      -EPROBE_DEFER it will queue the device for deferred probing.            
 * @uevent: Called when a device is added, removed, or a few other things       
 *      that generate uevents to add the environment variables.                 
 * @probe:  Called when a new device or driver add to this bus, and callback    
 *      the specific driver's probe to initial the matched device.              
 * @remove: Called when a device removed from this bus.                         
 * @shutdown:   Called at shut-down time to quiesce the device.                 
 *                                                                              
 * @online: Called to put the device back online (after offlining it).          
 * @offline:    Called to put the device offline for hot-removal. May fail.     
 *                                                                              
 * @suspend:    Called when a device on this bus wants to go to sleep mode.     
 * @resume: Called to bring a device on this bus out of sleep mode.             
 * @num_vf: Called to find out how many virtual functions a device on this      
 *      bus supports.                                                           
 * @pm:     Power management operations of this bus, callback the specific      
 *      device driver's pm-ops.                                                 
 * @iommu_ops:  IOMMU specific operations for this bus, used to attach IOMMU    
 *              driver implementations to a bus and allow the driver to do      
 *              bus-specific setup                                              
 * @p:      The private data of the driver core, only the driver core can       
 *      touch this.                                                             
 * @lock_key:   Lock class key for use by the lock validator                    
 *                                                                              
 * A bus is a channel between the processor and one or more devices. For the    
 * purposes of the device model, all devices are connected via a bus, even if   
 * it is an internal, virtual, "platform" bus. Buses can plug into each other.  
 * A USB controller is usually a PCI device, for example. The device model      
 * represents the actual connections between buses and the devices they control.
 * A bus is represented by the bus_type structure. It contains the name, the    
 * default attributes, the bus' methods, PM operations, and the driver core's   
 * private data.                                                                
 */
struct bus_type {                                                               
    const char      *name;                                                      
    const char      *dev_name;                                                  
    struct device       *dev_root;                                              
    const struct attribute_group **bus_groups;                                  
    const struct attribute_group **dev_groups;                                  
    const struct attribute_group **drv_groups;                                  
                                                                                
    int (*match)(struct device *dev, struct device_driver *drv);                
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);             
    int (*probe)(struct device *dev);                                           
    int (*remove)(struct device *dev);                                          
    void (*shutdown)(struct device *dev);                                       
                                                                                
    int (*online)(struct device *dev);                                          
    int (*offline)(struct device *dev);                                         
                                                                                
    int (*suspend)(struct device *dev, pm_message_t state);                     
    int (*resume)(struct device *dev);                                          
                                                                                
    int (*num_vf)(struct device *dev);                                          
                                                                                
    const struct dev_pm_ops *pm;                                                
                                                                                
    const struct iommu_ops *iommu_ops;                                          
                                                                                
    struct subsys_private *p;                                                   
    struct lock_class_key lock_key;                                             
};
  • *name
    • 버스명
  • *dev_name
    • 인스턴스가 포함된 버스 디바이스 명 (형태: “%s%d”, name, dev->id)
    • 예) foo1
  • *dev_root
    • 부모로 사용되는 디폴트 디바이스
  • **bus_groups
    • 버스 속성들을 지정하면 버스 디렉토리에 버스 속성들이 생성된다.
      • 예) /sys/bus/foo_bus 디렉토리에 버스 속성이 만들어진다.
  • **dev_groups
    • 버스에 디바이스가 추가되는 경우 생성되는 디바이스 디렉토리에 이 버스 디바이스 속성들이 생성된다.
      • 예) /sys/devices/foo 디렉토리에 속성들이 생성된다.
  • **drv_groups
    • 버스에 드라이버가 추가되는 경우 생성되는 드라이버 디렉토리에 이 버스 드라이버 속성들이 생성된다.
      • 예) /sys/bus/foo/drivers/foo_driver 디렉토리에 속성들이 생성된다.
  • (*match)
    • 디바이스나 드라이버가 버스에 연결될 때 이 후크 함수가 호출한다.
    • 이 후크 함수의 호출 결과가 양수면 다음 probe 과정이 진행된다.
  • (*uevent)
    • 디바이스가 추가/제거될 때 환경 변수를 추가하도록 이벤트가 발생을 위해 호출된다.
  • (*probe)
    • 버스에 id 매치된 새 디바이스 또는 드라이버가 추가될 때 이 후크 함수가 호출되어 디바이스나 드라이버가 등록되도록 한다.
  • (*remove)
    • 버스에서 디바이스 또는 드라이버가 제거될 때 호출되는 후크 함수이다.
  • (*shutdown)
    • 전원이 off되기 전에 호출되는 후크 함수이다.
  • (*online)
    • 디바이스가 연결(hot-plug)되어 online된 후 호출되는 후크 함수이다.
  • (*offline)
    • 디바이스가 분리(hot-removal)되어 offline될 때 호출되는 후크 함수이다.
  • (*suspend)
    • 절전 모드 진입 시 호출되는 후크 함수이다.
  • (*resume)
    • 절전 모드로 부터 벗어난 후 호출되는 후크 함수이다.
  • (*num_vf)
    • 버스에서 virtual 펑션들의 수를 알아내기 위해 호출되는 후크 함수이다.
  • *pm
    • 전원(절전 모드) 관리가 필요한 경우 사용되는 오퍼레이션
  • *iommu_ops
    • IOMMU 장치가 있는 경우 필요한 IOMMU 오퍼레이션
  • *p
    • 드라이버와 서브시스템간 하이라키 연결을 드라이버 core가 관리하는 내부 자료 구조이다.
  • lock_key
    • lock

 

버스 등록

인자로 받아온 bus_type 구조체를 통해 버스를 등록하는 과정을 알아본다.

 

bus_register()

drivers/base/bus.c

/**                                                                             
 * bus_register - register a driver-core subsystem                              
 * @bus: bus to register                                                        
 *                                                                              
 * Once we have that, we register the bus with the kobject                      
 * infrastructure, then register the children subsystems it has:                
 * the devices and drivers that belong to the subsystem.                        
 */                                                                             
int bus_register(struct bus_type *bus)                                          
{                                                                               
    int retval;                                                                 
    struct subsys_private *priv;                                                
    struct lock_class_key *key = &bus->lock_key;                                
                                                                                
    priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);                  
    if (!priv)                                                                  
        return -ENOMEM;                                                         
                                                                                
    priv->bus = bus;                                                            
    bus->p = priv;                                                              
                                                                                
    BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);                           
                                                                                
    retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);             
    if (retval)                                                                 
        goto out;                                                               
                                                                                
    priv->subsys.kobj.kset = bus_kset;                                          
    priv->subsys.kobj.ktype = &bus_ktype;                                       
    priv->drivers_autoprobe = 1;                                                
                                                                                
    retval = kset_register(&priv->subsys);                                      
    if (retval)                                                                 
        goto out;                                                               
                                                                                
    retval = bus_create_file(bus, &bus_attr_uevent);                            
    if (retval)                                                                 
        goto bus_uevent_fail;                                                   
                                                                                
    priv->devices_kset = kset_create_and_add("devices", NULL,                   
                         &priv->subsys.kobj);                                   
    if (!priv->devices_kset) {                                                  
        retval = -ENOMEM;                                                       
        goto bus_devices_fail;                                                  
    }                                                                           
                                                                                
    priv->drivers_kset = kset_create_and_add("drivers", NULL,                   
                         &priv->subsys.kobj);                                   
    if (!priv->drivers_kset) {                                                  
        retval = -ENOMEM;                                                       
        goto bus_drivers_fail;                                                  
    }

요청한 버스를 등록한다. 이 때 sysfs의 “/sys/bus”에 버스명으로 디렉토리를 만들고 관련 디렉토리들 및 버스 속성들을 추가한다.

  • 코드 라인 15~20에서 버스와 서브시스템간의 하이 라키 관계를 구성하도록 subsys_private 구조체를 할당받고 버스와 서로 연결한다.
  • 코드 라인 22에서 bus notifier 리스트를 초기화한다.
    • 디바이스가 이 버스에 등록/제거될 때 마다 이 리스트에 담긴 notifier 블럭의 함수를 호출하는 용도로 관리한다.
  • 코드 라인 24~26에서 버스 디렉토리 이름을 버스 이름으로 동일하게 사용한다.
    • 예) “foo” 버스명 -> /sys/bus/foo
  • 코드 라인 28~30에서 버스 위치를 /sys/bus로 지정하고, 속성 보기 및 변경에 대한 오퍼레이션을 디폴트 버스 오퍼레이션으로 지정한다. 그리고 이 버스에 등록되는 드라이버를 자동 probe할 수 있도록 설정한다.
    • (*release) 후크 함수
    • (*show) 및 (*store) 후크 함수
  • 코드 라인 32~34에서 버스명에 해당하는 디렉토리를 “/sys/bus”에 생성한다.
  • 코드 라인 36~38에서 uevent 속성을 추가한다.
  • 코드 라인 40~45에서 “/sys/bus” 디렉토리 뒤에 “devices”를 생성한다.
  • 코드 라인 47~52에서 “/sys/bus” 디렉토리 뒤에 “drivers”를 생성한다.

 

    INIT_LIST_HEAD(&priv->interfaces);                                          
    __mutex_init(&priv->mutex, "subsys mutex", key);                            
    klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);     
    klist_init(&priv->klist_drivers, NULL, NULL);                               
                                                                                
    retval = add_probe_files(bus);                                              
    if (retval)                                                                 
        goto bus_probe_files_fail;                                              
                                                                                
    retval = bus_add_groups(bus, bus->bus_groups);                              
    if (retval)                                                                 
        goto bus_groups_fail;                                                   
                                                                                
    pr_debug("bus: '%s': registered\n", bus->name);                             
    return 0;                                                                   
                                                                                
bus_groups_fail:                                                                
    remove_probe_files(bus);                                                    
bus_probe_files_fail:                                                           
    kset_unregister(bus->p->drivers_kset);                                      
bus_drivers_fail:                                                               
    kset_unregister(bus->p->devices_kset);                                      
bus_devices_fail:                                                               
    bus_remove_file(bus, &bus_attr_uevent);                                     
bus_uevent_fail:                                                                
    kset_unregister(&bus->p->subsys);                                           
out:                                                                            
    kfree(bus->p);                                                              
    bus->p = NULL;                                                              
    return retval;                                                              
}                                                                               
EXPORT_SYMBOL_GPL(bus_register);
  • 코드 라인 1~4에서 버스의 인터페이스, list_devices 및 klist_dirvers들을 초기화한다.
  • 코드 라인 6~8에서 drivers_probe 및 drivers_autoprobe 속성을 생성한다.
  • 코드 라인 10~12에서 버스 속성 그룹들을 추가한다.

 

다음 그림은 foo 버스를 추가할 때의 과정을 보여준다.

 

add_probe_files()

drivers/base/bus.c

static int add_probe_files(struct bus_type *bus)                                
{                                                                               
    int retval;                                                                 
                                                                                
    retval = bus_create_file(bus, &bus_attr_drivers_probe);                     
    if (retval)                                                                 
        goto out;                                                               
                                                                                
    retval = bus_create_file(bus, &bus_attr_drivers_autoprobe);                 
    if (retval)                                                                 
        bus_remove_file(bus, &bus_attr_drivers_probe);                          
out:                                                                            
    return retval;                                                              
}

drivers_probe 및 drivers_autoprobe 속성을 생성한다.

 

다음 그림은 플랫폼 버스에 연결된 i2c 컨트롤러와 1개의 온도 센서가 i2c 버스에 연결된 상태이다. 이 때 속성들을 살펴보자.

 

다음 그림은 위의 상황과 동일하며 다음 심볼 링크가 어느 곳에 연결되어 있는지 확인하여 보자.

  • /sys/devices
    • device 심볼 링크
    • subsystem 심볼 링크
  • /sys/bus
    • 디바이스명으로된 심볼 링크
  • /sys/class
    • 클래스 디바이스명으로된 심볼 링크

 

버스 및 클래스 하이라키 관계 관리

subsys_private 구조체

drivers/base/base.h

/**
 * struct subsys_private - structure to hold the private to the driver core portions of the bus_type/class structure.
 *
 * @subsys - the struct kset that defines this subsystem
 * @devices_kset - the subsystem's 'devices' directory
 * @interfaces - list of subsystem interfaces associated
 * @mutex - protect the devices, and interfaces lists.
 *
 * @drivers_kset - the list of drivers associated
 * @klist_devices - the klist to iterate over the @devices_kset
 * @klist_drivers - the klist to iterate over the @drivers_kset
 * @bus_notifier - the bus notifier list for anything that cares about things
 *                 on this bus.
 * @bus - pointer back to the struct bus_type that this structure is associated
 *        with.
 *
 * @glue_dirs - "glue" directory to put in-between the parent device to
 *              avoid namespace conflicts
 * @class - pointer back to the struct class that this structure is associated
 *          with.
 *
 * This structure is the one that is the actual kobject allowing struct
 * bus_type/class to be statically allocated safely.  Nothing outside of the
 * driver core should ever touch these fields.
 */
struct subsys_private {
        struct kset subsys;
        struct kset *devices_kset;
        struct list_head interfaces;
        struct mutex mutex;

        struct kset *drivers_kset;
        struct klist klist_devices;
        struct klist klist_drivers;
        struct blocking_notifier_head bus_notifier;
        unsigned int drivers_autoprobe:1;
        struct bus_type *bus;

        struct kset glue_dirs;
        struct class *class;
};

이 구조체는 다음과 bus에서도 사용되고 class에서도 사용된다.

bus에서 사용될 때
  • subsys
    • 버스 디렉토리에 해당하는 kset이다.
  • *devices_kset
    • 버스의 devices 디렉토리에 해당하는 kset를 가리킨다.
      • 예) /sys/bus/foo/devices
  • interfaces
    • 버스 인터페이스들
  • mutex
    • 리스트를 보호하기위한 mutex
  •  *drivers_kset
    • 버스의 drivers 디렉토리에 해당하는 kset를 가리킨다.
      • 예) /sys/bus/foo/drivers
  • klist_devices
    • 버스에 등록될 디바이스 리스트
  • klist_drivers
    • 버스에 등록될 드라이버 리스트
  • bus_notifier
    • 버스에 디바이스가 등록/해제 또는 드라이버가 bind/unbind될 때 마다 호출될 notifiler block 노드로 사용된다.
  • drivers_autoprobe
    • 버스가 autoprobe를 지원할지 여부
      • 디폴트: 1
  • *bus
    • 버스를 가리킨다.
class에서 사용될 때
  • subsys
    • 클래스 디렉토리에 해당하는 kset이다.
  • interfaces
    • 클래스 인터페이스들
  • mutex
    • 리스트를 보호하기위한 mutex
  • klist_devices
    • 클래스에 등록될 디바이스 리스트
  • glue_dirs
    • 이름 충돌을 피하기위해 부모 디바이스와 끼워넣을 디렉토리
  • *class
    • 클래스를 가리킨다.

 

버스에 디바이스 추가

bus_add_device()

drivers/base/bus.c

/**                                                                             
 * bus_add_device - add device to bus                                           
 * @dev: device being added                                                     
 *                                                                              
 * - Add device's bus attributes.                                               
 * - Create links to device's bus.                                              
 * - Add the device to its bus's list of devices.                               
 */                                                                             
int bus_add_device(struct device *dev)                                          
{                                                                               
    struct bus_type *bus = bus_get(dev->bus);                                   
    int error = 0;                                                              
                                                                                
    if (bus) {                                                                  
        pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));       
        error = device_add_groups(dev, bus->dev_groups);                        
        if (error)                                                              
            goto out_put;                                                       
        error = sysfs_create_link(&bus->p->devices_kset->kobj,                  
                        &dev->kobj, dev_name(dev));                             
        if (error)                                                              
            goto out_groups;                                                    
        error = sysfs_create_link(&dev->kobj,                                   
                &dev->bus->p->subsys.kobj, "subsystem");                        
        if (error)                                                              
            goto out_subsys;                                                    
        klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);             
    }                                                                           
    return 0;                                                                   
                                                                                
out_subsys:                                                                     
    sysfs_remove_link(&bus->p->devices_kset->kobj, dev_name(dev));              
out_groups:                                                                     
    device_remove_groups(dev, bus->dev_groups);                                 
out_put:                                                                        
    bus_put(dev->bus);                                                          
    return error;                                                               
}

버스에 디바이스를 추가한다.

  • 코드 라인 14~18에서 버스에 추가될 디바이스인 경우 해당 버스에 소속된 디바이스 속성들을 추가한다.
    • 예) gpio 디바이스가 플랫폼 버스에 추가되는 경우
      • drivers_autoprobe, drivers_probe 속성이 추가된다.
  • 코드 라인 19~22에서 해당 버스의 디바이스 디렉토리에 디바이스명으로 심볼 링크를 생성하여 해당 디바이스 디렉토리를 가리키게 한다.
    • 플랫폼 버스 예) /sys/bus/platform/devices/<devname> -> /sys/devices/platform/<devname>
  • 코드 라인 23~26에서 디바이스 디렉토리에  “subsystem” 심볼 링크를 생성하여 해당 버스 디렉토리를 가리키게한다.
    • 플랫폼 버스 예) /sys/devices/platform/<devname>/subsystem -> /sys/bus/platform
  • 코드 라인 27에서 버스가 관리하는 디바이스 리스트에 디바이스를 추가한다.

 

다음 그림과 같이 디바이스 디렉토리에 버스 타입 속성을 추가한다. 그리고 디바이스와 버스 타입과 심볼 링크로 서로 연결한다.

 

버스에 드라이버 추가

bus_add_driver()

drivers/base/bus.c

/**
 * bus_add_driver - Add a driver to the bus.
 * @drv: driver.
 */
int bus_add_driver(struct device_driver *drv)
{
        struct bus_type *bus; 
        struct driver_private *priv;
        int error = 0;

        bus = bus_get(drv->bus);
        if (!bus)
                return -EINVAL;

        pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);

        priv = kzalloc(sizeof(*priv), GFP_KERNEL);
        if (!priv) {
                error = -ENOMEM;
                goto out_put_bus;
        }
        klist_init(&priv->klist_devices, NULL, NULL);
        priv->driver = drv;
        drv->p = priv;
        priv->kobj.kset = bus->p->drivers_kset;
        error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
                                     "%s", drv->name);
        if (error)
                goto out_unregister;
        
        klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
        if (drv->bus->p->drivers_autoprobe) {
                if (driver_allows_async_probing(drv)) {
                        pr_debug("bus: '%s': probing driver %s asynchronously\n",
                                drv->bus->name, drv->name); 
                        async_schedule(driver_attach_async, drv);
                } else {
                        error = driver_attach(drv);
                        if (error)
                                goto out_unregister;
                }
        }
        module_add_driver(drv->owner, drv);

버스에 드라이버를 추가한다.

  • 코드 라인 11~13에서 드라이버에 해당되는 버스를 참조하기 위해 참조카운터를 1 증가시킨다.
  • 코드 라인 17~23에서 driver_private 구조체를 생성하고 드라이버와 서로 가리키게한다.
  • 코드 라인 24~28에서 버스의 드라이버 디렉토리에 드라이버명으로 디렉토리를 만든다.
    • 플랫폼 버스 예) sys/bus/platform/drivers/<foo>
  • 코드 라인 30에서 버스가 관리하는 드라이버 리스트에 현재 드라이버를 추가한다.
  • 코드 라인 31~41에서 버스에 drivers_autoprobe 속성이 1로 설정된 경우 드라이버에 디바이스들을 찾아 attach한다. 드라이버가 비동기 probe를 지원하는 경우에는 스레드를 사용하여 attach한다.
    • 예) /sys/bus/platform/drivers_autoprobe
  • 코드 라인 42에서 sysfs의 모듈 디렉토리에 드라이버의 모듈 디렉토리 및 속성을 추가한다.

 

        error = driver_create_file(drv, &driver_attr_uevent);
        if (error) {
                printk(KERN_ERR "%s: uevent attr (%s) failed\n",
                        __func__, drv->name);
        }
        error = driver_add_groups(drv, bus->drv_groups);
        if (error) {
                /* How the hell do we get out of this pickle? Give up */
                printk(KERN_ERR "%s: driver_create_groups(%s) failed\n",
                        __func__, drv->name);
        }

        if (!drv->suppress_bind_attrs) {
                error = add_bind_files(drv);
                if (error) {
                        /* Ditto */
                        printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
                                __func__, drv->name);
                }
        }

        return 0;

out_unregister:
        kobject_put(&priv->kobj);
        /* drv->p is freed in driver_release()  */
        drv->p = NULL;
out_put_bus:
        bus_put(bus);
        return error;
}
  • 코드 라인 1~5에서 드라이버에 uevent 속성을 추가한다.
  • 코드 라인 6~11에서 드라이버에 버스에 등록된 드라이버 속성들을 추가한다.
  • 코드 라인 13~20에서 드라이버에 suppress_bind_attrs 값이 설정되지 않은 상태인 경우 bind 및 unbind 속성을 추가한다.

 

Subsys 인터페이스

Subsys 인터페이스를 사용하는 드라이버가 일부 있으며 다음의 드라이버들에서 사용되고 있다.

  • drivers/net/rionet.c
  • drivers/cpufreq/cpufreq.c
  • drivers/cpufreq/s3c2412-cpufreq.c
  • drivers/rapidio/rio_cm.c
  • drivers/misc/mic/scif/scif_api.c

 

subsys_interface 구조체

include/linux/device.h

/**
 * struct subsys_interface - interfaces to device functions
 * @name:       name of the device function
 * @subsys:     subsytem of the devices to attach to
 * @node:       the list of functions registered at the subsystem
 * @add_dev:    device hookup to device function handler
 * @remove_dev: device hookup to device function handler
 *
 * Simple interfaces attached to a subsystem. Multiple interfaces can
 * attach to a subsystem and its devices. Unlike drivers, they do not
 * exclusively claim or control devices. Interfaces usually represent
 * a specific functionality of a subsystem/class of devices.
 */
struct subsys_interface {
        const char *name;
        struct bus_type *subsys;
        struct list_head node;
        int (*add_dev)(struct device *dev, struct subsys_interface *sif);
        void (*remove_dev)(struct device *dev, struct subsys_interface *sif);
};

이 구조체는 bus에서만 사용된다. (주의: class에서 사용하는 인터페이스는 class_interface 구조체이다)

  •  name
    • 버스 인터페이스명
  • *subsys
    • 버스 타입을 가리킨다.
  • node
    • bus_type->p->interfaces에 등록될 링크 노드로 사용된다.
  • (*add_dev)
    • 서브시스템 인터페이스가 등록될 때 버스 타입에 등록된 디바이스들을 대상으로 이 후크 함수가 호출된다.
  • (*remove_dev)
    • 서브시스템 인터페이스가 해제될 때 버스 타입에 등록된 디바이스들을 대상으로 이 후크 함수가 호출된다.

 

subsys_interface_register()

drivers/base/bus.c

int subsys_interface_register(struct subsys_interface *sif)
{
        struct bus_type *subsys;
        struct subsys_dev_iter iter;
        struct device *dev;

        if (!sif || !sif->subsys)
                return -ENODEV;

        subsys = bus_get(sif->subsys);
        if (!subsys)
                return -EINVAL;

        mutex_lock(&subsys->p->mutex);
        list_add_tail(&sif->node, &subsys->p->interfaces);
        if (sif->add_dev) {
                subsys_dev_iter_init(&iter, subsys, NULL, NULL);
                while ((dev = subsys_dev_iter_next(&iter)))
                        sif->add_dev(dev, sif);
                subsys_dev_iter_exit(&iter);
        }
        mutex_unlock(&subsys->p->mutex);

        return 0;
}
EXPORT_SYMBOL_GPL(subsys_interface_register);

sif(서브시스템 인터페이스)를 등록하고 버스 타입에 등록된 버스 디바이스들을 대상으로 (*add_dev) 후크 함수를 호출한다.

  • 코드 라인 7~8에서 인자로 전달받은 서브시스템 인터페이스가 null이거나 지정된 서브시스템이 없는 경우 -ENODEV 에러를 반환한다.
  • 코드 라인 10~12에서 버스 타입에 접근하기 위해 참조 카운터를 1 증가시킨다.
  • 코드 라인 14~15에서 버스 타입에 락을 걸고 버스 타입에 sif 인터페이스를 추가한다.
  • 코드 라인 16~21에서 sif에 (*add_dev) 후크 함수가 구현되어 있는 경우 버스 타입에 등록된 버스 디바이스들을 대상으로 (*add_dev) 후크 함수를 호출한다.

 

BUS 종류

다음과 같은 버스들이 커널에 정의되어 있다.

net/iucv/iucv.c                                 bus_register(&iucv_bus) 
sound/hda/hda_bus_type.c                        bus_register(&snd_hda_bus_type) 
sound/ac97_bus.c                                bus_register(&ac97_bus_type); 
sound/aoa/soundbus/core.c                       bus_register(&soundbus_bus_type) 
sound/core/seq_device.c                         bus_register(&snd_seq_bus_type) 
kernel/events/core.c                            bus_register(&pmu_bus)
drivers/macintosh/macio_asic.c			bus_register(&macio_bus_type)
drivers/platform/x86/wmi.c			bus_register(&wmi_bus_type)
drivers/target/loopback/tcm_loop.c		bus_register(&tcm_loop_lld_bus)
drivers/edac/edac_mc_sysfs.c			bus_register(mci->bus)
drivers/fmc/fmc-core.c				bus_register(&fmc_bus_type)
drivers/input/gameport/gameport.c		bus_register(&gameport_bus)
drivers/input/rmi4/rmi_bus.c			bus_register(&rmi_bus_type)
drivers/input/serio/serio.c			bus_register(&serio_bus)
drivers/gpu/drm/drm_mipi_dsi.c			bus_register(&mipi_dsi_bus_type)
drivers/gpu/host1x/dev.c			bus_register(&host1x_bus_type)
drivers/parport/share.c			        bus_register(&parport_bus_type)
drivers/acpi/bus.c				bus_register(&acpi_bus_type)
drivers/spmi/spmi.c				bus_register(&spmi_bus_type)
drivers/amba/bus.c				bus_register(&amba_bustype)
drivers/scsi/scsi_sysfs.c			bus_register(&scsi_bus_type)
drivers/scsi/fcoe/fcoe_sysfs.c			bus_register(&fcoe_bus_type)
drivers/scsi/scsi_debug.c			bus_register(&pseudo_lld_bus)
drivers/scsi/scsi_transport_iscsi.c		bus_register(&iscsi_flashnode_bus)
drivers/iio/industrialio-core.c		        bus_register(&iio_bus_type)
drivers/mcb/mcb-core.c				bus_register(&mcb_bus_type)
drivers/w1/w1.c				        bus_register(&w1_bus_type)
drivers/hwtracing/intel_th/core.c		bus_register(&intel_th_bus)
drivers/hwtracing/coresight/coresight.c	        bus_register(&coresight_bustype)
drivers/tty/serdev/core.c			bus_register(&serdev_bus_type)
drivers/pcmcia/ds.c				bus_register(&pcmcia_bus_type)
drivers/ssb/main.c				bus_register(&ssb_bustype)
drivers/vlynq/vlynq.c				bus_register(&vlynq_bus_type)
drivers/hid/hid-core.c				bus_register(&hid_bus_type)
drivers/hid/intel-ish-hid/ishtp/bus.c		bus_register(&ishtp_cl_bus_type)
drivers/pci/endpoint/pci-epf-core.c		bus_register(&pci_epf_bus_type)
drivers/pci/pci-driver.c			bus_register(&pci_bus_type)
drivers/pci/pcie/portdrv_bus.c			bus_register(&pcie_port_bus_type)
drivers/rapidio/rio-driver.c			bus_register(&rio_bus_type)
drivers/i2c/i2c-core-base.c			bus_register(&i2c_bus_type)
drivers/vme/vme.c				bus_register(&vme_bus_type)
drivers/eisa/eisa-bus.c			        bus_register(&eisa_bus_type)
drivers/fsi/fsi-core.c				bus_register(&fsi_bus_type)
drivers/hsi/hsi_core.c				bus_register(&hsi_bus_type)
drivers/sh/superhyway/superhyway.c		bus_register(&superhyway_bus_type)
drivers/sh/maple/maple.c			bus_register(&maple_bus_type)
drivers/zorro/zorro-driver.c			bus_register(&zorro_bus_type)
drivers/net/dummy.c				bus_register(&dummy_bus)
drivers/net/phy/mdio_bus.c			bus_register(&mdio_bus_type)
drivers/memstick/core/memstick.c		bus_register(&memstick_bus_type)
drivers/bcma/main.c				bus_register(&bcma_bus_type)
drivers/mfd/mcp-core.c				bus_register(&mcp_bus_type)
drivers/nvmem/core.c				bus_register(&nvmem_bus_type)
drivers/virtio/virtio.c			        bus_register(&virtio_bus)
drivers/spi/spi.c				bus_register(&spi_bus_type)
drivers/ipack/ipack.c				bus_register(&ipack_bus_type)
drivers/block/rbd.c				bus_register(&rbd_bus_type)
drivers/mmc/core/bus.c				bus_register(&mmc_bus_type)
drivers/mmc/core/sdio_bus.c			bus_register(&sdio_bus_type)
drivers/nvdimm/bus.c				bus_register(&nvdimm_bus_type)
drivers/rpmsg/rpmsg_core.c			bus_register(&rpmsg_bus)
drivers/gpio/gpiolib.c				bus_register(&gpio_bus_type)
drivers/firewire/core-transaction.c		bus_register(&fw_bus_type)
drivers/misc/mei/bus.c				bus_register(&mei_cl_bus_type)
drivers/misc/tifm_core.c			bus_register(&tifm_bus_type)
drivers/misc/mic/scif/scif_peer_bus.c		bus_register(&scif_peer_bus)
drivers/misc/mic/bus/mic_bus.c			bus_register(&mic_bus)
drivers/misc/mic/bus/scif_bus.c		        bus_register(&scif_bus)
drivers/misc/mic/bus/vop_bus.c			bus_register(&vop_bus)
drivers/misc/mic/bus/cosm_bus.c		        bus_register(&cosm_bus)
drivers/pnp/core.c				bus_register(&pnp_bus_type)
drivers/thunderbolt/domain.c			bus_register(&tb_bus_type)
drivers/bus/mips_cdmm.c			        bus_register(&mips_cdmm_bustype)
drivers/bus/sunxi-rsb.c			        bus_register(&sunxi_rsb_bus)
drivers/dio/dio-driver.c			bus_register(&dio_bus_type)
drivers/base/platform.c			        bus_register(&platform_bus_type)
drivers/base/soc.c				bus_register(&soc_bus_type)
drivers/base/isa.c				bus_register(&isa_bus_type)
drivers/xen/xenbus/xenbus_probe_backend.c	bus_register(&xenbus_backend.bus)
drivers/xen/xenbus/xenbus_probe_frontend.c	bus_register(&xenbus_frontend.bus)
drivers/hv/vmbus_drv.c				bus_register(&hv_bus)
drivers/usb/serial/usb-serial.c		        bus_register(&usb_serial_bus_type)
drivers/usb/core/usb.c				bus_register(&usb_bus_type)
drivers/usb/common/ulpi.c			bus_register(&ulpi_bus)
drivers/ntb/ntb.c				bus_register(&ntb_bus)
drivers/ntb/ntb_transport.c			bus_register(&ntb_transport_bus)
drivers/uwb/umc-bus.c				bus_register(&umc_bus_type)
drivers/uwb/driver.c				bus_register(&uwb_bus_type)
drivers/ide/ide.c				bus_register(&ide_bus_type)
drivers/vfio/mdev/mdev_driver.c		        bus_register(&mdev_bus_type)
drivers/tc/tc-driver.c				bus_register(&tc_bus_type)
drivers/media/pci/bt8xx/bttv-driver.c		bus_register(&bttv_sub_bus_type)
drivers/media/cec/cec-core.c			bus_register(&cec_bus_type)
drivers/media/media-devnode.c			bus_register(&media_bus_type)

 

Bus 관련 API들

  • bus_create_file()
    • 버스 디렉토리를 생성하고 그 디렉토리 내부에 지정한 버스 속성 파일도 생성한다.
      • 예) /sys/bus/foo-bus 디렉토리와 속성 파일이 생성된다.
  • bus_remove_file()
    • 버스 디렉토리 및 속성들을 삭제한다.
  • bus_for_each_dev()
    • 버스에 포함된 디바이스들을 순회(iteration)
  • bus_find_device()
    • 버스에 포함된 디바이스들을 대상으로 매치되는 디바이스를 찾는다.
  • bus_find_device_by_name()
    • 버스에 포함된 디바이스들을 대상으로 요청한 디바이스명으로 start 디바이스부터 검색한다.
  • subsys_find_device_by_id()
    • 서브시스템에 포함된 디바이스들을 대상으로 id로 검색한다.
    • 디바이스를 처음 검색할 디바이스 위치를 hint로 지정할 수 있다.
  • bus_for_each_drv()
    • 버스에 포함된 드라이버들을 순회(iteration)
  • bus_rescan_devices()
    • 버스에서 디바이스를 리스캔한다.
  • device_reprobe()
    • 요청 디바이스에 대한 드라이버를 제거한 후 다시 새 드라이버로 probe한다.
  • bus_unregister()
    • 버스를 등록 해제한다.
  • bus_register_notifier()
    • 버스에 디바이스의 등록/해제 그리고 드라이버의 바운드/언바운드에 대해 notifier할 nb를 등록한다.
  • bus_unregister_notifier()
    • 버스에 등록된 notify 블럭을 등록 해제한다.
  • bus_get_kset()
    • 버스의 서브시스템에 해당하는 kset을 알아한다.
  • bus_get_device_klist()
    • 버스에 등록된 디바이스 리스트를 알아한다.
  • bus_sort_breadthfirst()
  • subsys_dev_iter_init()
    • subsys 디바이스 순회자(iterator 또는 cursor)를 초기화한다.
  • subsys_dev_iter_next()
    • subsys 다음 디바이스를 지정하고 알아온다.
  • subsys_dev_iter_exit()
    • subsys 순회자를 종료한다.
  • subsys_interface_register()
    • subsys 인터페이스를 등록한다.
  • subsys_interface_unregister()
    • subsys 인터페이스를 등록 해제한다.
  • subsys_system_register()
    • subsys 시스템을 등록한다.
      • /sys/devices/system
  • subsys_virtual_register()
    • subsys virtual을 등록한다.
      • /sys/devices/virtual

 

Class

각 디바이스가 논리적인 용도로 구분하는 개개의 클래스로 등록할 수 있다.

  • 예) 여러 회사의 온도 및 습도 센서 디바이스를 사용할 때 이들을 hwmon이라는 클래스 하나를 등록하여 비슷한 유형들을 관리할 수 있다.

 

class 구조체

include/linux/device.h

/**                                                                             
 * struct class - device classes                                                
 * @name:   Name of the class.                                                  
 * @owner:  The module owner.                                                   
 * @class_groups: Default attributes of this class.                             
 * @dev_groups: Default attributes of the devices that belong to the class.     
 * @dev_kobj:   The kobject that represents this class and links it into the hierarchy.
 * @dev_uevent: Called when a device is added, removed from this class, or a    
 *      few other things that generate uevents to add the environment           
 *      variables.                                                              
 * @devnode:    Callback to provide the devtmpfs.                               
 * @class_release: Called to release this class.                                
 * @dev_release: Called to release the device.                                  
 * @suspend:    Used to put the device to sleep mode, usually to a low power    
 *      state.                                                                  
 * @resume: Used to bring the device from the sleep mode.                       
 * @shutdown_pre: Called at shut-down time before driver shutdown.              
 * @ns_type:    Callbacks so sysfs can detemine namespaces.                     
 * @namespace:  Namespace of the device belongs to this class.                  
 * @pm:     The default device power management operations of this class.       
 * @p:      The private data of the driver core, no one other than the          
 *      driver core can touch this.                                             
 *                                                                              
 * A class is a higher-level view of a device that abstracts out low-level      
 * implementation details. Drivers may see a SCSI disk or an ATA disk, but,     
 * at the class level, they are all simply disks. Classes allow user space      
 * to work with devices based on what they do, rather than how they are         
 * connected or how they work.                                                  
 */
struct class {                                                                  
    const char      *name;                                                      
    struct module       *owner;                                                 
                                                                                
    const struct attribute_group    **class_groups;                             
    const struct attribute_group    **dev_groups;                               
    struct kobject          *dev_kobj;                                          
                                                                                
    int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);         
    char *(*devnode)(struct device *dev, umode_t *mode);                        
                                                                                
    void (*class_release)(struct class *class);                                 
    void (*dev_release)(struct device *dev);                                    
                                                                                
    int (*suspend)(struct device *dev, pm_message_t state);                     
    int (*resume)(struct device *dev);                                          
    int (*shutdown_pre)(struct device *dev);                                    
                                                                                
    const struct kobj_ns_type_operations *ns_type;                              
    const void *(*namespace)(struct device *dev);                               
                                                                                
    const struct dev_pm_ops *pm;                                                
                                                                                
    struct subsys_private *p;                                                   
};
  • name
    • 클래스 명
  • *owner
    • 모듈 오너
  • **class_groups
    • 클래스 속성들을 지정하면 클래스 디렉토리에 클래스 속성들이 생성된다.
      • 예) /sys/devices/foo_class 디렉토리에 클래스 속성들이 생성된다.
  • **dev_groups
    • 클래스 디바이스 속성들을 지정하면 클래스 디바이스가 생성되는 디렉토리에 클래스 디바이스 속성들이 생성된다.
      • 예) /sys/devices/foo/foo_class/foo_class_device 디렉토리에 클래스 디바이스 속성들이 생성된다.
  • dev_kobj
    • 디바이스 드라이버 모델에 따라 하이라키에 소속되기 위한 kobject
  • (*dev_uevent)
    • 디바이스 이벤트 후크 함수
  • (*devnode)
  • (*class_release)
    • 클래스를 해제할 때 호출되는 후크 함수
  • (*dev_release)
    • 디바이스가 클래스로부터 해제할 때 호출되는 후크 함수
  • (*suspend)
    • 절전 진입 시 호출되는 후크 함수
  • (*resume)
    • 절전이 끝난 후 정상 진입 시 호출되는 후크 함수
  • (*showdown_pre)
    • 셧다운 전에 호출되는 후크 함수
  • *ns_type
    • namespace 타입에 대한 오퍼레이션
    • 현재 KOBJ_NS_TYPE_NONE(0)과 KOBJ_NS_TYPE_NET(1) 타입만 사용한다.
  • (*namespace)
    • 네임 스페이스 후크 함수
  • (*pm)
    • 전원(절전) 관리에 대한 오퍼레이션
  • *p
    • 클래스와 서브시스템간 하이라키 연결을 class core가 관리하는 내부 자료 구조이다.

 

 

Class 등록

class_register()

include/linux/device.h

/* This is a #define to keep the compiler from merging different                
 * instances of the __key variable */                                           
#define class_register(class)           \                                       
({                      \                                                       
    static struct lock_class_key __key; \                                       
    __class_register(class, &__key);    \                                       
})

요청한 클래스를 등록한다.

 

__class_register()

drivers/base/class.c

int __class_register(struct class *cls, struct lock_class_key *key)             
{                                                                               
    struct subsys_private *cp;                                                  
    int error;                                                                  
                                                                                
    pr_debug("device class '%s': registering\n", cls->name);                    
                                                                                
    cp = kzalloc(sizeof(*cp), GFP_KERNEL);                                      
    if (!cp)                                                                    
        return -ENOMEM;                                                         
    klist_init(&cp->klist_devices, klist_class_dev_get, klist_class_dev_put);   
    INIT_LIST_HEAD(&cp->interfaces);                                            
    kset_init(&cp->glue_dirs);                                                  
    __mutex_init(&cp->mutex, "subsys mutex", key);                              
    error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);                
    if (error) {                                                                
        kfree(cp);                                                              
        return error;                                                           
    }                                                                           
                                                                                
    /* set the default /sys/dev directory for devices of this class */          
    if (!cls->dev_kobj)                                                         
        cls->dev_kobj = sysfs_dev_char_kobj;                                    
                                                                                
#if defined(CONFIG_BLOCK)                                                       
    /* let the block class directory show up in the root of sysfs */            
    if (!sysfs_deprecated || cls != &block_class)                               
        cp->subsys.kobj.kset = class_kset;                                      
#else                                                                           
    cp->subsys.kobj.kset = class_kset;                                          
#endif                                                                          
    cp->subsys.kobj.ktype = &class_ktype;                                       
    cp->class = cls;                                                            
    cls->p = cp;                                                                
                                                                                
    error = kset_register(&cp->subsys);                                         
    if (error) {                                                                
        kfree(cp);                                                              
        return error;                                                           
    }                                                                           
    error = class_add_groups(class_get(cls), cls->class_groups);                
    class_put(cls);                                                             
    return error;                                                               
}                                                                               
EXPORT_SYMBOL_GPL(__class_register);

요청한 클래스를 등록한다. 이 때 sysfs의 “/sys/class”에 클래스명으로 디렉토리를 만들고 관련 디렉토리들 및 클래스 속성들을 추가한다.

  • 코드 라인 8~14에서 버스와 서브시스템간의 하이 라키 관계를 구성하도록 subsys_private 구조체를 할당받고 멤버들을 초기화한다.
  • 코드 라인 15~19에서 생성할 클래스 디렉토리에 사용할 이름을 클래스 이름으로 동일하게 사용한다.
    • 예) “foo” 클래스명 -> /sys/class/foo
  • 코드 라인 22~23에서 클래스의 디바이스로 디폴트인 “/sys/dev”를 가리키게한다.
  • 코드 라인 25~31에서 서브시스템이 “/sys/class”를 가리키게 한다. 단 sysfs _deprecated 설정이 되어 있고 추가할 클래스가  block 클래스 인경우는 지정하지 않는다.
  • 코드 라인 32~34에서 서브시스템이 디폴트 class 오퍼레이션을 사용하게 한다. 그리고 서브시스템과 클래스를 서로 연결한다.
  • 코드 라인 36~40에서 sysfs에 클래스를 등록한다.
  • 코드 라인 41에서 클래스에 클래스 속성들을 추가한다.
  • 코드 라인 42에서 클래스 사용이 루틴에서 완료되었으므로 참조 카운터를 1 감소시킨다.

 

다음 그림은 foo 클래스를 추가할 때의 과정을 보여준다.

 

클래스 디바이스 생성

클래스에 연동

디바이스에 클래스를 연결하기 위해 사용되는 일반적인 패턴이 있다. 디바이스 하나가 하나의 클래스를 가질 수 있는 제약이 있으므로, 1 개 이상의 클래스에 연동하기 위해서는 특정 디바이스의 아래에 클래스용 자식 디바이스를 만들어 사용한다. 클래스용 자식 디바이스의 디렉토리 구조는 클래스 명으로 빈 디렉토리를 하나 만들고 그 아래에 클래스 연동된 자식 디바이스가 위치한다. 클래스 디바이스들은 속성을 통해 추상화된 기능을 작성하여 사용하므로 드라이버와 연결하여 사용하는 경우가 없는 것이 특징이다.

 

다음 그림과 같이 클래스용 디바이스를 쉽게 생성하기 위해 device_create() 함수를 사용하였고, 그룹으로 이루어진 속성들도 추가할 수 있는 device_create_with_groups() 함수를 사용한 모습을 보여준다.

 

다음 그림은 2개의 클래스에 연결된 foo 디바이스를 보여준다. 클래스와 연결하기 위해 생성되는 서브 디바이스명 들은 foo_sub_device1과 foo_sub_device2로 하였다.

 

속성 없이 클래스 디바이스 생성과 등록

device_create()

drivers/base/core.c

/**                                                                             
 * device_create - creates a device and registers it with sysfs                 
 * @class: pointer to the struct class that this device should be registered to 
 * @parent: pointer to the parent struct device of this new device, if any      
 * @devt: the dev_t for the char device to be added                             
 * @drvdata: the data to be added to the device for callbacks                   
 * @fmt: string for the device's name                                           
 *                                                                              
 * This function can be used by char device classes.  A struct device           
 * will be created in sysfs, registered to the specified class.                 
 *                                                                              
 * A "dev" file will be created, showing the dev_t for the device, if           
 * the dev_t is not 0,0.                                                        
 * If a pointer to a parent struct device is passed in, the newly created       
 * struct device will be a child of that device in sysfs.                       
 * The pointer to the struct device will be returned from the call.             
 * Any further sysfs files that might be required can be created using this     
 * pointer.                                                                     
 *                                                                              
 * Returns &struct device pointer on success, or ERR_PTR() on error.            
 *                                                                              
 * Note: the struct class passed to this function must have previously          
 * been created with a call to class_create().                                  
 */
struct device *device_create(struct class *class, struct device *parent,        
                 dev_t devt, void *drvdata, const char *fmt, ...)               
{                                                                               
    va_list vargs;                                                              
    struct device *dev;                                                         
                                                                                
    va_start(vargs, fmt);                                                       
    dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);        
    va_end(vargs);                                                              
    return dev;                                                                 
}                                                                               
EXPORT_SYMBOL_GPL(device_create);

포맷과 가변인수를 사용한 디바이스명으로 클래스용 디바이스를 생성하고 등록한다. 부모 디바이스가 지정된 경우 부모 디바이스 아래에 생성한다.

 

device_create_vargs()

drivers/base/core.c

/**                                                                             
 * device_create_vargs - creates a device and registers it with sysfs           
 * @class: pointer to the struct class that this device should be registered to 
 * @parent: pointer to the parent struct device of this new device, if any      
 * @devt: the dev_t for the char device to be added                             
 * @drvdata: the data to be added to the device for callbacks                   
 * @fmt: string for the device's name                                           
 * @args: va_list for the device's name                                         
 *                                                                              
 * This function can be used by char device classes.  A struct device           
 * will be created in sysfs, registered to the specified class.                 
 *                                                                              
 * A "dev" file will be created, showing the dev_t for the device, if           
 * the dev_t is not 0,0.                                                        
 * If a pointer to a parent struct device is passed in, the newly created       
 * struct device will be a child of that device in sysfs.                       
 * The pointer to the struct device will be returned from the call.             
 * Any further sysfs files that might be required can be created using this     
 * pointer.                                                                     
 *                                                                              
 * Returns &struct device pointer on success, or ERR_PTR() on error.            
 *                                                                              
 * Note: the struct class passed to this function must have previously          
 * been created with a call to class_create().                                  
 */
struct device *device_create_vargs(struct class *class, struct device *parent,  
                   dev_t devt, void *drvdata, const char *fmt,                  
                   va_list args)                                                
{                                                                               
    return device_create_groups_vargs(class, parent, devt, drvdata, NULL,       
                      fmt, args);                                               
}                                                                               
EXPORT_SYMBOL_GPL(device_create_vargs);

포맷과 가변인수를 사용한 디바이스명으로 클래스용 디바이스를 생성하고 등록한다. 부모 디바이스가 지정된 경우 부모 디바이스 아래에 생성한다.

 

device_create_groups_vargs()

drivers/base/core.c

static struct device *                                                          
device_create_groups_vargs(struct class *class, struct device *parent,          
               dev_t devt, void *drvdata,                                       
               const struct attribute_group **groups,                           
               const char *fmt, va_list args)                                   
{                                                                               
    struct device *dev = NULL;                                                  
    int retval = -ENODEV;                                                       
                                                                                
    if (class == NULL || IS_ERR(class))                                         
        goto error;                                                             
                                                                                
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);                                    
    if (!dev) {                                                                 
        retval = -ENOMEM;                                                       
        goto error;                                                             
    }                                                                           
                                                                                
    device_initialize(dev);                                                     
    dev->devt = devt;                                                           
    dev->class = class;                                                         
    dev->parent = parent;                                                       
    dev->groups = groups;                                                       
    dev->release = device_create_release;                                       
    dev_set_drvdata(dev, drvdata);                                              
                                                                                
    retval = kobject_set_name_vargs(&dev->kobj, fmt, args);                     
    if (retval)                                                                 
        goto error;                                                             
                                                                                
    retval = device_add(dev);                                                   
    if (retval)                                                                 
        goto error;                                                             
                                                                                
    return dev;                                                                 
                                                                                
error:                                                                          
    put_device(dev);                                                            
    return ERR_PTR(retval);                                                     
}

포맷과 가변인수를 사용한 디바이스명으로 클래스용 디바이스를 생성하고 등록한다. 부모 디바이스가 지정된 경우 부모 디바이스 아래에 생성한다.

  • 코드 라인 10~11에서 클래스가 지정되지 않은 경우 에러로 함수를 빠져나간다.
  • 코드 라인 13~17에서 디바이스를 할당한다.
  • 코드 라인 19~25에서 디바이스를 초기화하고, 캐릭터 디바이스용 번호, 클래스, 부모 디바이스, 그룹 속성들을 지정한다. 그리고 디폴트 디바이스 해제 함수를 연결하고 드라이버 데이터를 연결한다.
  • 코드 라인 27~29에서 인자로 전달받은 포맷과 가변인수로 디바이스명을 설정한다.
  • 코드 라인 31~33에서 디바이스를 추가 등록한다.

 

속성을 포함하여 디바이스 생성과 등록

device_create_with_groups()

drivers/base/core.c

/**                                                                             
 * device_create_with_groups - creates a device and registers it with sysfs     
 * @class: pointer to the struct class that this device should be registered to 
 * @parent: pointer to the parent struct device of this new device, if any      
 * @devt: the dev_t for the char device to be added                             
 * @drvdata: the data to be added to the device for callbacks                   
 * @groups: NULL-terminated list of attribute groups to be created              
 * @fmt: string for the device's name                                           
 *                                                                              
 * This function can be used by char device classes.  A struct device           
 * will be created in sysfs, registered to the specified class.                 
 * Additional attributes specified in the groups parameter will also            
 * be created automatically.                                                    
 *                                                                              
 * A "dev" file will be created, showing the dev_t for the device, if           
 * the dev_t is not 0,0.                                                        
 * If a pointer to a parent struct device is passed in, the newly created       
 * struct device will be a child of that device in sysfs.                       
 * The pointer to the struct device will be returned from the call.             
 * Any further sysfs files that might be required can be created using this     
 * pointer.                                                                     
 *                                                                              
 * Returns &struct device pointer on success, or ERR_PTR() on error.            
 *                                                                              
 * Note: the struct class passed to this function must have previously          
 * been created with a call to class_create().                                  
 */
struct device *device_create_with_groups(struct class *class,                   
                     struct device *parent, dev_t devt,                         
                     void *drvdata,                                             
                     const struct attribute_group **groups,                     
                     const char *fmt, ...)                                      
{                                                                               
    va_list vargs;                                                              
    struct device *dev;                                                         
                                                                                
    va_start(vargs, fmt);                                                       
    dev = device_create_groups_vargs(class, parent, devt, drvdata, groups,      
                     fmt, vargs);                                               
    va_end(vargs);                                                              
    return dev;                                                                 
}                                                                               
EXPORT_SYMBOL_GPL(device_create_with_groups);

포맷과 가변인수를 사용한 디바이스명으로 클래스용 디바이스를 생성하고 등록한다. 부모 디바이스가 지정된 경우 부모 디바이스 아래에 생성한다. 생성된 클래스용 디바이스에 속성 파일들을 추가한다.

 

Class 인터페이스

Class 인터페이스를 사용하는 드라이버가 일부 있으며 다음의 드라이버들에서 사용되고 있다.

  • drivers/net/rionet.c
  • drivers/pcmcia/ds.c
  • drivers/pcmcia/rsrc_nonstatic.c
  • drivers/rapidio/devices/rio_mport_cdev.c
  • drivers/rapidio/rio_cm.c
  • drivers/scsi/ses.c
  • drivers/scsi/scsi_sysfs.c
  • drivers/scsi/sg.c

 

class_interface 구조체

include/linux/device.h

struct class_interface {
        struct list_head        node;
        struct class            *class;

        int (*add_dev)          (struct device *, struct class_interface *);
        void (*remove_dev)      (struct device *, struct class_interface *);
};
  • node
    • class->interfaces에 등록될 링크 노드로 사용된다.
  • *class
    • 부모 클래스
  • (*add_dev)
    • 클래스 인터페이스가 등록될 때 클래스에 등록된 디바이스들을 대상으로 이 후크 함수가 호출된다.
  • (*remove_dev)
    • 클래스 인터페이스가 해제될 때 클래스에 등록된 디바이스들을 대상으로 이 후크 함수가 호출된다.

 

Class 인터페이스 등록

class_interface_register()

drivers/base/class.c

int class_interface_register(struct class_interface *class_intf)
{
        struct class *parent;
        struct class_dev_iter iter;
        struct device *dev;

        if (!class_intf || !class_intf->class)
                return -ENODEV;

        parent = class_get(class_intf->class);
        if (!parent)
                return -EINVAL;

        mutex_lock(&parent->p->mutex);
        list_add_tail(&class_intf->node, &parent->p->interfaces);
        if (class_intf->add_dev) {
                class_dev_iter_init(&iter, parent, NULL, NULL);
                while ((dev = class_dev_iter_next(&iter)))
                        class_intf->add_dev(dev, class_intf);
                class_dev_iter_exit(&iter);
        }
        mutex_unlock(&parent->p->mutex);

        return 0;
}

클래스 인터페이스를 등록하고 클래스에 등록된 클래스 디바이스들을 대상으로 (*add_dev) 후크 함수를 호출한다.

  • 코드 라인 7~8에서 인자로 전달받은 클래스 인터페이스가 null이거나 지정된 클래스가 없는 경우 -ENODEV 에러를 반환한다.
  • 코드 라인 10~12에서 클래스에 접근하기 위해 참조 카운터를 1 증가시킨다.
  • 코드 라인 14~15에서 클래스에 락을 걸고 클래스 인터페이스를 추가한다.
  • 코드 라인 16~21에서 클래스 인터페이스에 (*add_dev) 후크 함수가 구현되어 있는 경우 클래스에 등록된 클래스 디바이스들을 대상으로 (*add_dev) 후크 함수를 호출한다.

 

Class 종류

다음과 같은 클래스들이 커널에 정의되어 있다.

./net/ieee802154/sysfs.c			class_register(&wpan_phy_class)
./net/atm/atm_sysfs.c				class_register(&atm_class)
./net/rfkill/core.c				class_register(&rfkill_class)
./net/nfc/core.c				class_register(&nfc_class)
./net/core/net-sysfs.c				class_register(&net_class)
./net/wireless/sysfs.c				class_register(&ieee80211_class)
./block/genhd.c					class_register(&block_class)
./drivers/platform/x86/wmi.c			class_register(&wmi_bus_class)
./drivers/platform/chrome/cros_ec_dev.c		class_register(&cros_class)
./drivers/iommu/iommu-sysfs.c			class_register(&iommu_class)
./drivers/input/input.c				class_register(&input_class)
./drivers/uio/uio.c				class_register(&uio_class)
./drivers/hwmon/hwmon.c				class_register(&hwmon_class)
./drivers/scsi/scsi_sysfs.c			class_register(&sdev_class)
./drivers/scsi/ibmvscsi_tgt/ibmvscsi_tgt.c	class_register(&ibmvscsis_class)
./drivers/scsi/scsi_transport_sas.c		transport_class_register(&sas_host_class)
./drivers/scsi/scsi_transport_sas.c		transport_class_register(&sas_phy_class)
./drivers/scsi/scsi_transport_sas.c		transport_class_register(&sas_port_class)
./drivers/scsi/scsi_transport_sas.c		transport_class_register(&sas_rphy_class)
./drivers/scsi/scsi_transport_sas.c		transport_class_register(&sas_end_dev_class)
./drivers/scsi/scsi_transport_sas.c		transport_class_register(&sas_expander_class)
./drivers/scsi/hosts.c				class_register(&shost_class)
./drivers/scsi/osd/osd_uld.c			class_register(&osd_uld_class)
./drivers/scsi/scsi_transport_srp.c		transport_class_register(&srp_host_class)
./drivers/scsi/scsi_transport_srp.c		transport_class_register(&srp_rport_class)
./drivers/scsi/sd.c				class_register(&sd_disk_class)
./drivers/scsi/scsi_transport_fc.c		transport_class_register(&fc_host_class)
./drivers/scsi/scsi_transport_fc.c		transport_class_register(&fc_vport_class)
./drivers/scsi/scsi_transport_fc.c		transport_class_register(&fc_rport_class)
./drivers/scsi/scsi_transport_fc.c		transport_class_register(&fc_transport_class)
./drivers/scsi/raid_class.c			transport_class_register(&raid_class)
./drivers/scsi/scsi_transport_iscsi.c		class_register(&iscsi_transport_class)
./drivers/scsi/scsi_transport_iscsi.c		class_register(&iscsi_endpoint_class)
./drivers/scsi/scsi_transport_iscsi.c		class_register(&iscsi_iface_class)
./drivers/scsi/scsi_transport_iscsi.c		transport_class_register(&iscsi_host_class)
./drivers/scsi/scsi_transport_iscsi.c		transport_class_register(&iscsi_connection_class)
./drivers/scsi/scsi_transport_iscsi.c		transport_class_register(&iscsi_session_class)
./drivers/scsi/scsi_transport_spi.c		transport_class_register(&spi_transport_class)
./drivers/scsi/scsi_transport_spi.c		transport_class_register(&spi_host_class)
./drivers/scsi/st.c				class_register(&st_sysfs_class)
./drivers/pwm/sysfs.c				class_register(&pwm_class)
./drivers/mux/core.c				class_register(&mux_class)
./drivers/regulator/core.c			class_register(&regulator_class)
./drivers/hwtracing/stm/core.c			class_register(&stm_class)
./drivers/hwtracing/stm/core.c			class_register(&stm_source_class)
./drivers/isdn/mISDN/core.c			class_register(&mISDN_class)
./drivers/pcmcia/cs.c				class_register(&pcmcia_socket_class)
./drivers/mtd/mtdcore.c				class_register(&mtd_class)
./drivers/mtd/ubi/build.c			class_register(&ubi_class)
./drivers/ata/libata-transport.c		transport_class_register(&ata_link_class)
./drivers/ata/libata-transport.c		transport_class_register(&ata_port_class)
./drivers/ata/libata-transport.c		transport_class_register(&ata_dev_class)
./drivers/pci/probe.c				class_register(&pcibus_class)
./drivers/rapidio/rio-driver.c			class_register(&rio_mport_class)
./drivers/staging/greybus/loopback.c		class_register(&loopback_class)
./drivers/staging/greybus/vibrator.c		class_register(&vibrator_class)
./drivers/net/ipvlan/ipvtap.c			class_register(&ipvtap_class)
./drivers/net/phy/mdio_bus.c			class_register(&mdio_bus_class)
./drivers/net/macvtap.c				class_register(&macvtap_class)
./drivers/memstick/core/memstick.c		class_register(&memstick_host_class)
./drivers/mfd/ucb1x00-core.c			class_register(&ucb1x00_class)
./drivers/powercap/powercap_sys.c		class_register(&powercap_class)
./drivers/spi/spi.c				class_register(&spi_master_class)
./drivers/spi/spi.c				class_register(&spi_slave_class)
./drivers/remoteproc/remoteproc_sysfs.c		class_register(&rproc_class)
./drivers/firmware/dmi-id.c			class_register(&dmi_class)
./drivers/watchdog/watchdog_dev.c		class_register(&watchdog_class)
./drivers/block/zram/zram_drv.c			class_register(&zram_control_class)
./drivers/block/pktcdvd.c			class_register(class_pktcdvd)
./drivers/mmc/core/host.c			class_register(&mmc_host_class)
./drivers/gpio/gpiolib-sysfs.c			class_register(&gpio_class)
./drivers/misc/tifm_core.c			class_register(&tifm_adapter_class)
./drivers/misc/enclosure.c			class_register(&enclosure_class)
./drivers/mailbox/omap-mailbox.c		class_register(&omap_mbox_class)
./drivers/base/devcoredump.c			class_register(&devcd_class)
./drivers/base/firmware_class.c			class_register(&firmware_class)
./drivers/uwb/driver.c				class_register(&uwb_rc_class)
./drivers/infiniband/ulp/srp/ib_srp.c		class_register(&srp_class)
./drivers/infiniband/core/device.c		class_register(&ib_class)
./drivers/infiniband/core/cm.c			class_register(&cm_class)
./drivers/thermal/thermal_core.c		class_register(&thermal_class)
./drivers/media/rc/rc-main.c			class_register(&rc_class)
./drivers/media/v4l2-core/v4l2-dev.c		class_register(&video_class)
./drivers/media/pci/ddbridge/ddbridge-core.c	class_register(&ddb_class)
./drivers/media/usb/pvrusb2/pvrusb2-sysfs.c	class_register(&clp->class) 
./drivers/dma/dmaengine.c			class_register(&dma_devclass)

 

Classs 관련 API들

  • class_dev_iter_init()
    • 클래스 디바이스 순회자를 초기화한다.
  • class_dev_iter_next()
    • 클래스 디바이스에서 다음 디바이스를 지정하고 얻어온다.
  • class_dev_iter_exit()
    • 클래스 디바이스 순회자를 종료한다.
  • class_for_each_device()
    • 클래스 디바이스를 순회한다.
  • class_find_device()
    • 클래스에서 start 디바이스부터 시작하여 매치되는 디바이스를 검색한다.
  • show_class_attr_string()
    • 클래스 속성 문자열을 출력한다.
  • class_compat_register()
    • compatibility 클래스를 등록한다.
  • class_compat_unregister()
    • compatibility 클래스를 등록 해제한다.
  • class_compat_create_link()
    • compatibility 클래스 링크 심볼을 생성한다.
  • class_compat_remove_link()
    • compatibility 클래스 링크 심볼을 삭제한다.

 

Device Binding

디바이스가 드라이버를 probing하는 과정은 다음과 같다.

  • 디바이스가 등록될 때 드라이버가 준비된 상태인 경우 자동으로 probe 한다.
  • 드라이버가 등록될 때 역시 디바이스가 준비된 상태인 경우 자동으로 probe 한다.
    • insmod에 의해 사용자 드라이버가 등록될 때 probe되는 과정과 동일하다.
  • 매치된 디바이스와 드라이버가 probe되지 못한 경우 스레드를 사용하여 probe를 시도한다.
  • pci 또는 usb와 같이 plug & plug를 지원하여 버스를 스캔하며 디바이스 드라이버를 등록할 수 있다.
    • 이 과정은 pci 드라이버를 다루는 과정에서 알아보자.

 

bus_probe_device()

drivers/base/bus.c

/**                                                                             
 * bus_probe_device - probe drivers for a new device                            
 * @dev: device to probe                                                        
 *                                                                              
 * - Automatically probe for a driver if the bus allows it.                     
 */                                                                             
void bus_probe_device(struct device *dev)                                       
{                                                                               
    struct bus_type *bus = dev->bus;                                            
    struct subsys_interface *sif;                                               
                                                                                
    if (!bus)                                                                   
        return;                                                                 
                                                                                
    if (bus->p->drivers_autoprobe)                                              
        device_initial_probe(dev);                                              
                                                                                
    mutex_lock(&bus->p->mutex);                                                 
    list_for_each_entry(sif, &bus->p->interfaces, node)                         
        if (sif->add_dev)                                                       
            sif->add_dev(dev, sif);                                             
    mutex_unlock(&bus->p->mutex);                                               
}

버스가 autoprobe  상태인 경우 디바이스를 probe한다.  (디폴트로 bus는 항상 autoprobe 상태이다)

 

device_initial_probe()

drivers/base/dd.c

void device_initial_probe(struct device *dev)                                   
{                                                                               
    __device_attach(dev, true);                                                 
}

디바이스가 드라이버와의 동기 attach를 시도한다.

 

__device_attach()

drivers/base/dd.c

static int __device_attach(struct device *dev, bool allow_async)                
{                                                                               
    int ret = 0;                                                                
                                                                                
    device_lock(dev);                                                           
    if (dev->driver) {                                                          
        if (device_is_bound(dev)) {                                             
            ret = 1;                                                            
            goto out_unlock;                                                    
        }                                                                       
        ret = device_bind_driver(dev);                                          
        if (ret == 0)                                                           
            ret = 1;                                                            
        else {                                                                  
            dev->driver = NULL;                                                 
            ret = 0;                                                            
        }                                                                       
    } else {                                                                    
        struct device_attach_data data = {                                      
            .dev = dev,                                                         
            .check_async = allow_async,                                         
            .want_async = false,                                                
        };                                                                      
                                                                                
        if (dev->parent)                                                        
            pm_runtime_get_sync(dev->parent);                                   
                                                                                
        ret = bus_for_each_drv(dev->bus, NULL, &data,                           
                    __device_attach_driver);                                    
        if (!ret && allow_async && data.have_async) {                           
            /*                                                                  
             * If we could not find appropriate driver                          
             * synchronously and we are allowed to do                           
             * async probes and there are drivers that                          
             * want to probe asynchronously, we'll                              
             * try them.                                                        
             */                                                                 
            dev_dbg(dev, "scheduling asynchronous probe\n");                    
            get_device(dev);                                                    
            async_schedule(__device_attach_async_helper, dev);                  
        } else {                                                                
            pm_request_idle(dev);                                               
        }                                                                       
                                                                                
        if (dev->parent)                                                        
            pm_runtime_put(dev->parent);                                        
    }                                                                           
out_unlock:                                                                     
    device_unlock(dev);                                                         
    return ret;                                                                 
}

디바이스가 드라이버의 attach를 시도한다. 인자로 비동기 및 동기 attach 요청을 지정할 수 있다. attach 시도 전에 부모 디바이스가 절전 모드인 경우 절전에서 빠져나온다.

  • 코드 라인 6~17에서 디바이스에 이미 드라이버가 지정된 경우 드라이버를 바인딩 시도한다. 성공(1) 또는 실패(0)를 반환한다. 만일 이미 디바이스와 드라이버가 바인딩 되어 있는 경우 성공(1)을 반환한다.
  • 코드 라인 18~29에서 버스에 등록되어있는 드라이버들을 대상으로 매치 체크하여 매치된 드라이버를 attach 시도한다.
  • 코드 라인 30~40에서 부모 디바이스가 절전 상태에서 빠져나오도록 대기(sync)한다. 그리고 비동기 요청으로 드라이버의 attach가 성공된 경우 __device_attach_async_helper 워크를 언바운드 워커스레드로 동작시킨다.
    • 이 때 async probe가 완료되도록 한다.
  • 코드 라인 41~47에서 디바이스를 idle 상태로 바꾼다. 그리고 부모 디바이스의 참조 카운터를 1 감소시킨다.

 

__driver_attach()

drivers/base/dd.c

static int __driver_attach(struct device *dev, void *data)                      
{                                                                               
    struct device_driver *drv = data;                                           
    int ret;                                                                    
                                                                                
    /*                                                                          
     * Lock device and try to bind to it. We drop the error                     
     * here and always return 0, because we need to keep trying                 
     * to bind to devices and some drivers will return an error                 
     * simply if it didn't support the device.                                  
     *                                                                          
     * driver_probe_device() will spit a warning if there                       
     * is an error.                                                             
     */                                                                         
                                                                                
    ret = driver_match_device(drv, dev);                                        
    if (ret == 0) {                                                             
        /* no match */                                                          
        return 0;                                                               
    } else if (ret == -EPROBE_DEFER) {                                          
        dev_dbg(dev, "Device match requests probe deferral\n");                 
        driver_deferred_probe_add(dev);                                         
    } else if (ret < 0) {                                                       
        dev_dbg(dev, "Bus failed to match device: %d", ret);                    
        return ret;                                                             
    } /* ret > 0 means positive match */                                        
                                                                                
    if (dev->parent)    /* Needed for USB */                                    
        device_lock(dev->parent);                                               
    device_lock(dev);                                                           
    if (!dev->driver)                                                           
        driver_probe_device(drv, dev);                                          
    device_unlock(dev);                                                         
    if (dev->parent)                                                            
        device_unlock(dev->parent);                                             
                                                                                
    return 0;                                                                   
}

인자로 전달받은 디바이스와 드라이버가 매치되는 경우 attach 시도한다.

  • 코드 라인 16~19에서 디바이스와 드라이버가 서로 매치되는 것이 없으면 성공(0)을 반환한다.
  • 코드 라인 20~26에서 probe 유예인 경우 deferred_probe_pending_list에 디바이스를 추가한다. 그 외 에러의 경우 함수를 빠져나간다.
  • 코드 라인 28~35에서 부모 디바이스의 락을 건채 디바이스와 드라이버를 probe 시도 한 후 성공(0)을 반환한다.

 

driver_probe_device()

drivers/base/dd.c

/**                                                                             
 * driver_probe_device - attempt to bind device & driver together               
 * @drv: driver to bind a device to                                             
 * @dev: device to try to bind to the driver                                    
 *                                                                              
 * This function returns -ENODEV if the device is not registered,               
 * 1 if the device is bound successfully and 0 otherwise.                       
 *                                                                              
 * This function must be called with @dev lock held.  When called for a         
 * USB interface, @dev->parent lock must be held as well.                       
 *                                                                              
 * If the device has a parent, runtime-resume the parent before driver probing. 
 */                                                                             
int driver_probe_device(struct device_driver *drv, struct device *dev)          
{                                                                               
    int ret = 0;                                                                
                                                                                
    if (!device_is_registered(dev))                                             
        return -ENODEV;                                                         
                                                                                
    pr_debug("bus: '%s': %s: matched device %s with driver %s\n",               
         drv->bus->name, __func__, dev_name(dev), drv->name);                   
                                                                                
    pm_runtime_get_suppliers(dev);                                              
    if (dev->parent)                                                            
        pm_runtime_get_sync(dev->parent);                                       
                                                                                
    pm_runtime_barrier(dev);                                                    
    ret = really_probe(dev, drv);                                               
    pm_request_idle(dev);                                                       
                                                                                
    if (dev->parent)                                                            
        pm_runtime_put(dev->parent);                                            
                                                                                
    pm_runtime_put_suppliers(dev);                                              
    return ret;                                                                 
}

디바이스와 드라이버를 서로 bind 시도한다. 반환되는 값이 -ENODEV인 경우 디바이스가 등록되지 않았음을 의미한다. 1인 경우 binding이 성공이고, 0인 경우 실패이다.

  • 코드 라인 18~19에서 디바이스가 등록되지 않은 경우 -ENODEV 결과를 반환한다.
  • 코드 라인 24~26에서 디바이스의 suppliers를 대상으로 DL_FLAG_PM_RUNTIME 플래그가 설정된 경우 pm 참조 카운터를 감소시키고 resunme 처리한다.
  • 코드 라인 28~30에서 pm 기능(suspend)이 정상 완료될 때까지 대기한 후 드라이버를 probe 요청한다. 그런 후 디바이스가 suspend되어야 하는 경우 pm idle 요청한다.
  • 코드 라인 32~33에서 부모 디바이스의 참조 카운터를 1 감소시킨다.
  • 코드 라인 35에서 suppolier 디바이스들의 참조 카운터를 1 감소시킨다.

 

really_probe()

drivers/base/dd.c

static int really_probe(struct device *dev, struct device_driver *drv)          
{                                                                               
    int ret = -EPROBE_DEFER;                                                    
    int local_trigger_count = atomic_read(&deferred_trigger_count);             
    bool test_remove = IS_ENABLED(CONFIG_DEBUG_TEST_DRIVER_REMOVE) &&           
               !drv->suppress_bind_attrs;                                       
                                                                                
    if (defer_all_probes) {                                                     
        /*                                                                      
         * Value of defer_all_probes can be set only by                         
         * device_defer_all_probes_enable() which, in turn, will call           
         * wait_for_device_probe() right after that to avoid any races.         
         */                                                                     
        dev_dbg(dev, "Driver %s force probe deferral\n", drv->name);            
        driver_deferred_probe_add(dev);                                         
        return ret;                                                             
    }                                                                           
                                                                                
    ret = device_links_check_suppliers(dev);                                    
    if (ret)                                                                    
        return ret;                                                             
                                                                                
    atomic_inc(&probe_count);                                                   
    pr_debug("bus: '%s': %s: probing driver %s with device %s\n",               
         drv->bus->name, __func__, drv->name, dev_name(dev));                   
    WARN_ON(!list_empty(&dev->devres_head));                                    
                                                                                
re_probe:                                                                       
    dev->driver = drv;                                                          
                                                                                
    /* If using pinctrl, bind pins now before probing */                        
    ret = pinctrl_bind_pins(dev);                                               
    if (ret)                                                                    
        goto pinctrl_bind_failed;                                               
                                                                                
    ret = dma_configure(dev);                                                   
    if (ret)                                                                    
        goto dma_failed;                                                        
                                                                                
    if (driver_sysfs_add(dev)) {                                                
        printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",                    
            __func__, dev_name(dev));                                           
        goto probe_failed;                                                      
    }                                                                           
                                                                                
    if (dev->pm_domain && dev->pm_domain->activate) {                           
        ret = dev->pm_domain->activate(dev);                                    
        if (ret)                                                                
            goto probe_failed;                                                  
    }

디바이스가 드라이버를 probe 한다.

  • 코드 라인 8~17에서 dpm 절전(suspend) 모드로 진입 시에는 디바이스를 전역 deferred_probe_pending_list에 추가하여  probe를 유예시킨다.
  • 코드 라인 19~21에서 supplier 디바이스들 중 DL_STATE_AVAILABLE 상태인 디바이스를 DL_STATE_CONSUMER_PROBE 상태로 변경한다. 그 후 요청한 디바이스의 상태는 DL_DEV_PROBING 상태로 변경한다.
  • 코드 라인 23~26에서 probe 카운터를 1 증가시키고 디버그 정보를 출력한다.
  • 코드 라인 32~34에서 디바이스를 사용하기 위해 pinctrl로 bind한다.
  • 코드 라인 36~38에서 디바이스가 dma를 사용하는 경우 디바이스 트리 또는 ACPI를 사용하여 dma 설정을 한다.
  • 코드 라인 40~44에서 sysfs에 드라이버와 디바이스간의 심볼 링크를 생성한다.
  • 코드 라인 46~50에서 디바이스의 pm 도메인에 있는 (*active) 후크 함수를 호출한다.

 

.   /*                                                                          
     * Ensure devices are listed in devices_kset in correct order               
     * It's important to move Dev to the end of devices_kset before             
     * calling .probe, because it could be recursive and parent Dev             
     * should always go first                                                   
     */                                                                         
    devices_kset_move_last(dev);                                                
                                                                                
    if (dev->bus->probe) {                                                      
        ret = dev->bus->probe(dev);                                             
        if (ret)                                                                
            goto probe_failed;                                                  
    } else if (drv->probe) {                                                    
        ret = drv->probe(dev);                                                  
        if (ret)                                                                
            goto probe_failed;                                                  
    }                                                                           
                                                                                
    if (test_remove) {                                                          
        test_remove = false;                                                    
                                                                                
        if (dev->bus->remove)                                                   
            dev->bus->remove(dev);                                              
        else if (drv->remove)                                                   
            drv->remove(dev);                                                   
                                                                                
        devres_release_all(dev);                                                
        driver_sysfs_remove(dev);                                               
        dev->driver = NULL;                                                     
        dev_set_drvdata(dev, NULL);                                             
        if (dev->pm_domain && dev->pm_domain->dismiss)                          
            dev->pm_domain->dismiss(dev);                                       
        pm_runtime_reinit(dev);                                                 
                                                                                
        goto re_probe;                                                          
    }                                                                           
                                                                                
    pinctrl_init_done(dev);                                                     
                                                                                
    if (dev->pm_domain && dev->pm_domain->sync)                                 
        dev->pm_domain->sync(dev);                                              
                                                                                
    driver_bound(dev);                                                          
    ret = 1;                                                                    
    pr_debug("bus: '%s': %s: bound device %s to driver %s\n",                   
         drv->bus->name, __func__, dev_name(dev), drv->name);                   
    goto done;
  • 코드 라인 7에서 디바이스를 devices_kset 리스트의 마지막으로 옮긴다.
  • 코드 라인 9~17에서 디바이스의 버스에 있는 (*probe) 후크 함수를 호출한다. 만일 없으면 드라이버의 (*probe) 후크 함수를 호출한다.
  • 코드 라인 19~36에서probe 시 테스트 드라이버 기능을 사용하는 경우 드라이버를 제거한 후 다시 probe하는 과정을 거친다.
  • 코드 라인 38에서 probe가 완료되었음을 pinctrl에 전한다.
  • 코드 라인 40~41에서 디바이스의 pm_domain에 있는 (*sync) 후크를 호출한다.
  • 코드 라인 43~47에서 디바이스가 드라이버를 바운드한 후 done 레이블로 이동하여 probe_waitqueue에 있는 스레드를 깨우고 정상적으로 함수를 빠져나간다.

 

probe_failed:                                                                   
    dma_deconfigure(dev);                                                       
dma_failed:                                                                     
    if (dev->bus)                                                               
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,                
                         BUS_NOTIFY_DRIVER_NOT_BOUND, dev);                     
pinctrl_bind_failed:                                                            
    device_links_no_driver(dev);                                                
    devres_release_all(dev);                                                    
    driver_sysfs_remove(dev);                                                   
    dev->driver = NULL;                                                         
    dev_set_drvdata(dev, NULL);                                                 
    if (dev->pm_domain && dev->pm_domain->dismiss)                              
        dev->pm_domain->dismiss(dev);                                           
    pm_runtime_reinit(dev);                                                     
                                                                                
    switch (ret) {                                                              
    case -EPROBE_DEFER:                                                         
        /* Driver requested deferred probing */                                 
        dev_dbg(dev, "Driver %s requests probe deferral\n", drv->name);         
        driver_deferred_probe_add(dev);                                         
        /* Did a trigger occur while probing? Need to re-trigger if yes */      
        if (local_trigger_count != atomic_read(&deferred_trigger_count))        
            driver_deferred_probe_trigger();                                    
        break;                                                                  
    case -ENODEV:                                                               
    case -ENXIO:                                                                
        pr_debug("%s: probe of %s rejects match %d\n",                          
             drv->name, dev_name(dev), ret);                                    
        break;                                                                  
    default:                                                                    
        /* driver matched but the probe failed */                               
        printk(KERN_WARNING                                                     
               "%s: probe of %s failed with error %d\n",                        
               drv->name, dev_name(dev), ret);                                  
    }                                                                           
    /*                                                                          
     * Ignore errors returned by ->probe so that the next driver can try        
     * its luck.                                                                
     */                                                                         
    ret = 0;                                                                    
done:                                                                           
    atomic_dec(&probe_count);                                                   
    wake_up(&probe_waitqueue);                                                  
    return ret;                                                                 
}

sysfs 속성을 사용하여 바인딩하는 방법은 다음을 참고한다.참고:

 

디바이스 드라이버 바인딩 관련 API들

  • device_bind_driver()
    • 디바이스에 대응하는 드라이버를 바인딩한다.
  • wait_for_device_probe()
    • 디바이스 probe의 완료를 기다린다. (deferred probe 포함)
  • device_attach()
    • 디바이스에 대응하는 드라이버를 동기 attach 한다.
  • driver_attach()
    • 드라이버에 대응하는 디바이스 찾아 attach 한다.
  • device_release_driver()
    • 디바이스에 attach된 드라이버를 분리시킨다.

 

디바이스, 드라이버, 버스, 클래스 및 모듈 간의 관계 관리

다음 그림에서 sysfs에서 관계 관리를 위해 kobject, kset 및 klist 이외에도 다음의 구조체들이 사용되는 것을 알 수 있다.

  • device
    • device_private
  • device_driver
    • driver_private
  • bus & class
    • subsys_private
  • module
    • module_kobject

 

다음 그림은 각 구조체에서 정의한 속성들에 대해 생성되는 디렉토리 위치를 보여준다.

 

참고

 

 

Device & Driver -1- (Basic)

<kernel v4.14>

디바이스와 드라이버 -1-

리눅스 커널에서 디바이스 드라이버를 표현하자면 디바이스를 구동하는 프로그램이 디바이스 드라이버이다.  리눅스 커널의 디바이스 드라이버 모델에 따라 이들을 구분하여 표현할 때 디바이스 정보를 먼저 등록하고, 디바이스 core에서 hotplug 기반으로 id 매치되는 디바이스 드라이버를 probe하는 것으로 나누어 디바이스 드라이버를 가동하게 된다. 디바이스 드라이버를 가동(probe)할 때, 미리 등록된 디바이스 정보를 사용하여 디바이스 드라이버의 HW 조작을 위한 설정등을 할 수 있다. 다음과 같이 드라이버 모델에 따른 분류를 더 자세히 살펴보자.

  • 디바이스
    • 한국말로 그대로 장치라고한다. 장치에 대한 정보를 등록하면 나중에 디바이스 드라이버가 호출(probe)될 때 등록된 디바이스 정보를 사용할 수 있게된다.
    • 디바이스를 등록 시 리눅스 커널은 버스 및 클래스(option)로 분류한다.
      • bus
        • 버스 컨트롤러 또는 호스트 컨트롤러 기능이 있는 디바이스
        • 예) pci, i2c, usb, scsi, spi, acpi, mdio_bus, platform
      • class
        • scsi 디스크나 ata 디스크는 클래스 단계로 볼 때 동일한 디스크이다. 이렇게 디바이스를 추상화 시켜 상위 단계에서 보았을 때 분류하는 방법이다.
        • 예) input, block, dma, gpio, leds, net, phy, rtc, tty, watchdog, mem, bdi, …
  • 디바이스 드라이버
    • 디바이스 HW의 식별, 구동/해제 및 절전 기능 등에 대한 구현이 담긴 프로그램이다.
    • 디바이스 드라이버는 특정 디바이스 HW가 detect되어 hotplug 할 수 있도록 디바이스 부분과 디바이스 드라이버 부분이 분리되어있다.
    • 디바이스 드라이버가 HW 정보를 설정할 때 등록된 디바이스 정보를 알아와서 사용할 수 있다.
      • 레지스터, irq, dma 정보 등…

 

유저 스페이스를 위한 또 다른 디바이스 유형 분류

유저 스페이스에서 각 유형별 디바이스에 접근하기 위해 사용되는 분류는 다음과 같이 3가지가 있다. 이러한 디바이스 유형들은 별도의 글에서 자세히 알아보기로 한다.

  • char
    • 캐릭터 디바이스
  • block
    • 블럭 디바이스
  • net
    • 네트웍 디바이스

 

임베드된 device와 device_driver

디바이스를 정의하기 위해 device 구조체가 그대로 사용되는 경우는 많지 않으며 다음과 같이 보통 사용자 디바이스 구조체안에 임베드되어 사용된다.

struct foo_device {
        struct device;
        ...
}

 

다음 그림은 임베드된 device 및 device_driver 구조체를 보여준다.

 

다음 그림은 디바이스 드라이버가 호출되는 과정을 간략히 보여준다. 최근의 디바이스 드라이버 구현 부분은 hotplug 기반으로 probe되어 호출되는 구조로되어있다.

  • hotplug 인지를 할 수 없는 플랫폼 디바이스 드라이버일지라도 디바이스 드라이버 모델에 맞추어 probe 후크가 구현되어야 한다.

 

버스

대부분의 디바이스는 반드시 자신이 속한 버스(부모 버스)가 있다. 아래 그림을 보고 디바이스 수, 버스 컨트롤러 수를 확인해보자.

  • 참고로 버스 없이 사용되는 custom 디바이스 및 클래스 디바이스로만 등록되어 사용되는 디바이스 드라이버도 있다.
  • 주의: 버스 컨트롤러 자신도 디바이스이다.
    • 디바이스 수: 6개
    • 버스 컨트롤러 수: 2개
  • bus Controller B의 소속 버스는 bus A이다.
  • bus Controller A의 소속 버스는 아래 그림에 나와 있지 않지만 보통 platform, system, virtual 등 중 하나이다.

 

아래의 그림을 보면 다음과 같은 특징을 가지고 있음을 알 수 있다.

  • pci 고속(high speed) 버스에 i2c 저속(low speed) 버스가 연결되었다.
  • gpio 및 cpld 디바이스가 pci 버스에도 연결되어 있는 것이 있고, i2c 버스에 연결되어 있는 것도 있다.

 

device 구조체

include/linux/device.h

/**                                                                             
 * struct device - The basic device structure                                   
 * @parent: The device's "parent" device, the device to which it is attached.   
 *      In most cases, a parent device is some sort of bus or host              
 *      controller. If parent is NULL, the device, is a top-level device,       
 *      which is not usually what you want.                                     
 * @p:      Holds the private data of the driver core portions of the device.   
 *      See the comment of the struct device_private for detail.                
 * @kobj:   A top-level, abstract class from which other classes are derived.   
 * @init_name:  Initial name of the device.                                     
 * @type:   The type of device.                                                 
 *      This identifies the device type and carries type-specific               
 *      information.                                                            
 * @mutex:  Mutex to synchronize calls to its driver.                           
 * @bus:    Type of bus device is on.                                           
 * @driver: Which driver has allocated this                                     
 * @platform_data: Platform data specific to the device.                        
 *      Example: For devices on custom boards, as typical of embedded           
 *      and SOC based hardware, Linux often uses platform_data to point         
 *      to board-specific structures describing devices and how they            
 *      are wired.  That can include what ports are available, chip             
 *      variants, which GPIO pins act in what additional roles, and so          
 *      on.  This shrinks the "Board Support Packages" (BSPs) and               
 *      minimizes board-specific #ifdefs in drivers.                            
 * @driver_data: Private pointer for driver specific info.                      
 * @links:  Links to suppliers and consumers of this device.                    
 * @power:  For device power management.                                        
 *      See Documentation/driver-api/pm/devices.rst for details.                
 * @pm_domain:  Provide callbacks that are executed during system suspend,      
 *      hibernation, system resume and during runtime PM transitions            
 *      along with subsystem-level and driver-level callbacks.                  
 * @pins:   For device pin management.                                          
 *      See Documentation/driver-api/pinctl.rst for details.                    
 * @msi_list:   Hosts MSI descriptors                                           
 * @msi_domain: The generic MSI domain this device is using.                    
 * @numa_node:  NUMA node this device is close to.                              
 * @dma_ops:    DMA mapping operations for this device.                         
 * @dma_mask:   Dma mask (if dma'ble device).                                   
 * @coherent_dma_mask: Like dma_mask, but for alloc_coherent mapping as not all 
 *      hardware supports 64-bit addresses for consistent allocations           
 *      such descriptors.                                                       
 * @dma_pfn_offset: offset of DMA memory range relatively of RAM                
 * @dma_parms:  A low level driver may set these to teach IOMMU code about      
 *      segment limitations.                                                    
 * @dma_pools:  Dma pools (if dma'ble device).                                  
 * @dma_mem:    Internal for coherent mem override.                             
 * @cma_area:   Contiguous memory area for dma allocations                      
 * @archdata:   For arch-specific additions.                                    
 * @of_node:    Associated device tree node.                                    
 * @fwnode: Associated device node supplied by platform firmware.               
 * @devt:   For creating the sysfs "dev".                                       
 * @id:     device instance                                                     
 * @devres_lock: Spinlock to protect the resource of the device.                
 * @devres_head: The resources list of the device.                              
 * @knode_class: The node used to add the device to the class list.             
 * @class:  The class of the device.                                            
 * @groups: Optional attribute groups.                                     
 * @release:    Callback to free the device after all references have           
 *      gone away. This should be set by the allocator of the                   
 *      device (i.e. the bus driver that discovered the device).                
 * @iommu_group: IOMMU group the device belongs to.                             
 * @iommu_fwspec: IOMMU-specific properties supplied by firmware.               
 *                                                                              
 * @offline_disabled: If set, the device is permanently online.                 
 * @offline:    Set after successful invocation of bus type's .offline().       
 * @of_node_reused: Set if the device-tree node is shared with an ancestor      
 *              device.                                                         
 *                                                                              
 * At the lowest level, every device in a Linux system is represented by an     
 * instance of struct device. The device structure contains the information     
 * that the device model core needs to model the system. Most subsystems,       
 * however, track additional information about the devices they host. As a      
 * result, it is rare for devices to be represented by bare device structures;  
 * instead, that structure, like kobject structures, is usually embedded within 
 * a higher-level representation of the device.                                 
 */
struct device {                                                                 
    struct device       *parent;                                                
                                                                                
    struct device_private   *p;                                                 
                                                                                
    struct kobject kobj;                                                        
    const char      *init_name; /* initial name of the device */                
    const struct device_type *type;                                             
                                                                                
    struct mutex        mutex;  /* mutex to synchronize calls to                
                     * its driver.                                              
                     */                                                         
                                                                                
    struct bus_type *bus;       /* type of bus device is on */                  
    struct device_driver *driver;   /* which driver has allocated this          
                       device */                                                
    void        *platform_data; /* Platform specific data, device               
                       core doesn't touch it */                                 
    void        *driver_data;   /* Driver data, set and get with                
                       dev_set/get_drvdata */                                   
    struct dev_links_info   links;                                              
    struct dev_pm_info  power;                                                  
    struct dev_pm_domain    *pm_domain;                                         
                                                                                
#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN                                            
    struct irq_domain   *msi_domain;                                            
#endif                                                                          
#ifdef CONFIG_PINCTRL                                                           
    struct dev_pin_info *pins;                                                  
#endif                                                                          
#ifdef CONFIG_GENERIC_MSI_IRQ                                                   
    struct list_head    msi_list;                                               
#endif                                                                          
                                                                                
#ifdef CONFIG_NUMA                                                              
    int     numa_node;  /* NUMA node this device is close to */                 
#endif                                                                          
    const struct dma_map_ops *dma_ops;                                          
    u64     *dma_mask;  /* dma mask (if dma'able device) */                     
    u64     coherent_dma_mask;/* Like dma_mask, but for                         
                         alloc_coherent mappings as                             
                         not all hardware supports                              
                         64 bit addresses for consistent                        
                         allocations such descriptors. */                       
    unsigned long   dma_pfn_offset;                                             
                                                                                
    struct device_dma_parameters *dma_parms;             

    struct list_head    dma_pools;  /* dma pools (if dma'ble) */                
                                                                                
    struct dma_coherent_mem *dma_mem; /* internal for coherent mem              
                         override */                                            
#ifdef CONFIG_DMA_CMA                                                           
    struct cma *cma_area;       /* contiguous memory area for dma               
                       allocations */                                           
#endif                                                                          
    /* arch specific additions */                                               
    struct dev_archdata archdata;                                               
                                                                                
    struct device_node  *of_node; /* associated device tree node */             
    struct fwnode_handle    *fwnode; /* firmware device node */                 
                                                                                
    dev_t           devt;   /* dev_t, creates the sysfs "dev" */                
    u32         id; /* device instance */                                       
                                                                                
    spinlock_t      devres_lock;                                                
    struct list_head    devres_head;                                            
                                                                                
    struct klist_node   knode_class;                                            
    struct class        *class;                                                 
    const struct attribute_group **groups;  /* optional groups */               
                                                                                
    void    (*release)(struct device *dev);                                     
    struct iommu_group  *iommu_group;                                           
    struct iommu_fwspec *iommu_fwspec;                                          
                                                                                
    bool            offline_disabled:1;                                         
    bool            offline:1;                                                  
    bool            of_node_reused:1;                                           
};
  • *parent
    • 부모 디바이스를 가리킨다.
  • *p
    • 디바이스 연관 관계를 위해 디바이스 코어가 관리한다.
  • kobj
    • 상속 관계에 대한 추상클래스 역할을 하는 kobject이다.
  • *init_name
    • 초기 이름
  • *type
    • 디바이스 타입(device_type)이 지정된다.
  • *mutex
    • 드라이버의 호출 동기화를 위해 사용한다.
  • *bus
    • 버스 디바이스인 경우 버스 타입(bus_type)이 지정된다.
  • *driver
    • 디바이스 드라이버(device_driver)가 지정된다.
  • *platform_data
    • 개별 플랫폼 디바이스 전용 데이터로 HW 연결 정보등을 드라이버에게 제공하기 위해 사용한다.
  • *driver_data
    • 개별 디바이스 드라이버 전용 데이터로 dev_set() 및 get_drvdat() 함수를 사용하여 설정 및 알아올 수 있다.
  • links
    • 디바이스의 supplier들과 consumer들에 대한 연결 정보(dev_links_info)가 지정된다.
  • power
    • 디바이스의 절전 관리
  • *pm_domain
    • 서브 시스템 레벨과 드라이버 레벨의 콜백을 통해 디바이스의 절전 관리를 수행하는 핸들러이다.
  • msi_list
    • 호스트의 msi 디스크립터 정보가 지정된다.
  • *msi_domain
    • MSI 방식을 사용하는 irq 도메인이 지정된다.
  • *pins
    • pin control 정보(dev_pin_info)가 지정된다.
  • numa_node
    • NUMA 시스템인 경우 디바이스가 붙어있는 노드 번호이다.
  • *dma_ops
    • dma를 사용하는 장치인 경우 dma 오페레이션이 지정된다.
  • *dma_mask
    • dma를 사용하는 장치인 경우 dma 마스크가 지정된다.
  • coherent_dma_mask
    • coherent dma를 지원하는 시스템에서 사용하는 coherent dma 마스크가 지정된다.
  • dma_pfn_offset
    • dma 페이지의 pfn offset이 지정된다.
    • 물리 메모리로부터 DMA 페이지가 위치한 offset이다.
  • *dma_parms
    • dma 파라메터가 지정된다.
  • dma_pools
    • dma 장치인 경우 사용하는 dma pool을 가리킨다.
  • *dma_mem
    • dma 장치가 사용하는 coherent 메모리를 가리킨다.
  • *cma_area
    • dma 장치가 사용할 커널의 연속된 메모리인 cma 영역을 가리킨다.
  • archdata
    • 아키텍처 추가 데이터
  • *of_node
    • 디바이스 트리에서 해당 디바이스 노드를 가리킨다.
  • *fwnode
    • 펌웨어 디바이스 노드 핸들러를 가리킨다.
  • devt
    • sysfs의 /dev를 생성 시 사용한다.
  • id
    • 디바이스 인스턴트가 담긴다.
    • -1은 인스턴스가 하나일 때, 복수개인 경우 0부터 시작
  • devres_lock
    • 디바이스 리소스의 조작 시 protect를 위해 사용할 spinlock이다.
  • devres_head
    • 디바이스 리소스들이 담길 리스트이다.
  • knode_class
    • 디바이스 클래스 분류 집합
  • *class
    • 디바이스 클래스를 지정한다.
  • **groups
    • 디바이스 속성 그룹(attribute_group)이 옵션으로 지정된다.
  • (*release)
    • 디바이스 참조가 0이 될 때 디바이스 해제 시 사용할 후크 함수가 지정된다.
  • *iommu_group
    • 디바이스가 소속될 iommu 그룹(iommu_group을 지정한다.
  • *iommu_fwspec
    • iommu용 펌웨어 핸들러(iommu_fwspec)가 지정된다.
  • offline_disabled:1
    • 디바이스가 영구적으로 online인 경우 설정된다.
  • offline:1
    • 버스 타입의 (*offline) 후크 함수의 호출 결과가 성공인 경우 1로 설정된다.
  • of_node_reused:1
    • 디바이스 트리노드가 상위 디바이스 트리노드와 공유되는 경우 1로 설정된다.

 

device_driver 구조체

include/linux/device.h

/**                                                                             
 * struct device_driver - The basic device driver structure                     
 * @name:   Name of the device driver.                                          
 * @bus:    The bus which the device of this driver belongs to.                 
 * @owner:  The module owner.                                                   
 * @mod_name:   Used for built-in modules.                                      
 * @suppress_bind_attrs: Disables bind/unbind via sysfs.                        
 * @probe_type: Type of the probe (synchronous or asynchronous) to use.         
 * @of_match_table: The open firmware table.                                    
 * @acpi_match_table: The ACPI match table.                                     
 * @probe:  Called to query the existence of a specific device,                 
 *      whether this driver can work with it, and bind the driver               
 *      to a specific device.                                                   
 * @remove: Called when the device is removed from the system to                
 *      unbind a device from this driver.                                       
 * @shutdown:   Called at shut-down time to quiesce the device.                 
 * @suspend:    Called to put the device to sleep mode. Usually to a            
 *      low power state.                                                        
 * @resume: Called to bring a device from sleep mode.                           
 * @groups: Default attributes that get created by the driver core              
 *      automatically.                                                          
 * @pm:     Power management operations of the device which matched             
 *      this driver.                                                            
 * @p:      Driver core's private data, no one other than the driver            
 *      core can touch this.                                                    
 *                                                                              
 * The device driver-model tracks all of the drivers known to the system.       
 * The main reason for this tracking is to enable the driver core to match      
 * up drivers with new devices. Once drivers are known objects within the       
 * system, however, a number of other things become possible. Device drivers    
 * can export information and configuration variables that are independent      
 * of any specific device.                                                      
 */
struct device_driver {                                                          
    const char      *name;                                                      
    struct bus_type     *bus;                                                   
                                                                                
    struct module       *owner;                                                 
    const char      *mod_name;  /* used for built-in modules */                 
                                                                                
    bool suppress_bind_attrs;   /* disables bind/unbind via sysfs */            
    enum probe_type probe_type;                                                 
                                                                                
    const struct of_device_id   *of_match_table;                                
    const struct acpi_device_id *acpi_match_table;                              
                                                                                
    int (*probe) (struct device *dev);                                          
    int (*remove) (struct device *dev);                                         
    void (*shutdown) (struct device *dev);                                      
    int (*suspend) (struct device *dev, pm_message_t state);                    
    int (*resume) (struct device *dev);                                         
    const struct attribute_group **groups;                                      
                                                                                
    const struct dev_pm_ops *pm;                                                
                                                                                
    struct driver_private *p;                                                   
};
  • *name
    • 디바이스 드라이버명
  • *bus
    • 버스 타입(bus_type)이 지정된다.
  • *owner
    • 모듈 오너
  • *mod_name
    • 내장 모듈에서 사용하는 모듈명
  • suppress_bind_attrs
    • sysfs를 통한 bind/unbind를 사용하지 못하게할 때 1로 설정된다.
  • probe_type
    • probe 타입이 지정된다.
  • *of_match_table
    • 디바이스 트리 식별를 위한 매치 테이블이 지정된다.
  • *acpi_match_table
    • ACPI 식별을 위한 매치 테이블이 지정된다.
  • (*probe)
    • HW 디바이스 존재 유무를 판단하기 위해 호출되고 해당 디바이스를 사용하기 위해 디바이스 드라이버와 바인드한다.
  • (*remove)
    • 디바이스의 사용을 완료시킨 경우 호출된다.
  • (*shutdown)
    • 디바이스의 전원을 끄려고할 때 호출된다.
  • (*suspend)
    • 디바이스가 절전 모드로 진입할 때 호출된다.
  • (*resume)
    • 디바이스가 절전 모드로부터 정상 모드로 돌아올 때 호출된다.
  • **groups
    • 디바이스 속성 그룹(attribute_group)이 옵션으로 지정된다.
  • *pm
    • 드라이버 레벨의 콜백을 통해 디바이스의 절전 관리를 수행하는 핸들러이다.
  • *p
    • 드라이버의 하이라키 관계 정보가 관리된다.
    • 이 정보는 드라이버 코어가 업데이트하고 관리한다.

 

디바이스 등록

다음 API를 사용하여 디바이스를 요청한 버스에 등록할 때 버스 타입에 따라 리스트로 관리된다. 등록된 디바이스 정보들은 추후 디바이스 드라이버와의 매치 여부를 판단할 때 사용한다.

  • int device_register(struct device * dev);

 

디바이스가 등록될 때 다음 매크로 함수를 사용하여 디바이스 속성을 추가할 수 있다. 속성에 대해서는 조금 후에 자세히 알아본다.

  • #define DEVICE_ATTR(name,mode,show,store)

 

device_register()

drivers/base/core.c

/**                                                                             
 * device_register - register a device with the system.                         
 * @dev: pointer to the device structure                                        
 *                                                                              
 * This happens in two clean steps - initialize the device                      
 * and add it to the system. The two steps can be called                        
 * separately, but this is the easiest and most common.                         
 * I.e. you should only call the two helpers separately if                      
 * have a clearly defined need to use and refcount the device                   
 * before it is added to the hierarchy.                                         
 *                                                                              
 * For more information, see the kerneldoc for device_initialize()              
 * and device_add().                                                            
 *                                                                              
 * NOTE: _Never_ directly free @dev after calling this function, even           
 * if it returned an error! Always use put_device() to give up the              
 * reference initialized in this function instead.                              
 */
int device_register(struct device *dev)                                         
{                                                                               
    device_initialize(dev);                                                     
    return device_add(dev);                                                     
}                                                                               
EXPORT_SYMBOL_GPL(device_register);

요청한 디바이스를 추가한다.

  • 코드 라인 3에서 인자로 받은 디바이스 구조체를 초기화한다.
  • 코드 라인 4에서 인자로 받은 디바이스를 초기화한다.

 

device_initialize()

drivers/base/core.c

/**                                                                             
 * device_initialize - init device structure.                                   
 * @dev: device.                                                                
 *                                                                              
 * This prepares the device for use by other layers by initializing             
 * its fields.                                                                  
 * It is the first half of device_register(), if called by                      
 * that function, though it can also be called separately, so one               
 * may use @dev's fields. In particular, get_device()/put_device()              
 * may be used for reference counting of @dev after calling this                
 * function.                                                                    
 *                                                                              
 * All fields in @dev must be initialized by the caller to 0, except            
 * for those explicitly set to some other value.  The simplest                  
 * approach is to use kzalloc() to allocate the structure containing            
 * @dev.                                                                        
 *                                                                              
 * NOTE: Use put_device() to give up your reference instead of freeing          
 * @dev directly once you have called this function.                            
 */
void device_initialize(struct device *dev)                                      
{                                                                               
    dev->kobj.kset = devices_kset;                                              
    kobject_init(&dev->kobj, &device_ktype);                                    
    INIT_LIST_HEAD(&dev->dma_pools);                                            
    mutex_init(&dev->mutex);                                                    
    lockdep_set_novalidate_class(&dev->mutex);                                  
    spin_lock_init(&dev->devres_lock);                                          
    INIT_LIST_HEAD(&dev->devres_head);                                          
    device_pm_init(dev);                                                        
    set_dev_node(dev, -1);                                                      
#ifdef CONFIG_GENERIC_MSI_IRQ                                                   
    INIT_LIST_HEAD(&dev->msi_list);                                             
#endif                                                                          
    INIT_LIST_HEAD(&dev->links.consumers);                                      
    INIT_LIST_HEAD(&dev->links.suppliers);                                      
    dev->links.status = DL_DEV_NO_DRIVER;                                       
}                                                                               
EXPORT_SYMBOL_GPL(device_initialize);

인자로 받은 디바이스 구조체를 초기화한다. (kobject, dma pools, mutex, resource, pm, node, msi_list, links)

 

device_add()

drivers/base/core.c -1/3-

/**                                                                             
 * device_add - add device to device hierarchy.                                 
 * @dev: device.                                                                
 *                                                                              
 * This is part 2 of device_register(), though may be called                    
 * separately _iff_ device_initialize() has been called separately.             
 *                                                                              
 * This adds @dev to the kobject hierarchy via kobject_add(), adds it           
 * to the global and sibling lists for the device, then                         
 * adds it to the other relevant subsystems of the driver model.                
 *                                                                              
 * Do not call this routine or device_register() more than once for             
 * any device structure.  The driver model core is not designed to work         
 * with devices that get unregistered and then spring back to life.             
 * (Among other things, it's very hard to guarantee that all references         
 * to the previous incarnation of @dev have been dropped.)  Allocate            
 * and register a fresh new struct device instead.                              
 *                                                                              
 * NOTE: _Never_ directly free @dev after calling this function, even           
 * if it returned an error! Always use put_device() to give up your             
 * reference instead.                                                           
 */
int device_add(struct device *dev)                                              
{                                                                               
    struct device *parent;                                                      
    struct kobject *kobj;                                                       
    struct class_interface *class_intf;                                         
    int error = -EINVAL;                                                        
    struct kobject *glue_dir = NULL;                                            
                                                                                
    dev = get_device(dev);                                                      
    if (!dev)                                                                   
        goto done;                                                              
                                                                                
    if (!dev->p) {                                                              
        error = device_private_init(dev);                                       
        if (error)                                                              
            goto done;                                                          
    }                                                                           
                                                                                
    /*                                                                          
     * for statically allocated devices, which should all be converted          
     * some day, we need to initialize the name. We prevent reading back        
     * the name, and force the use of dev_name()                                
     */                                                                         
    if (dev->init_name) {                                                       
        dev_set_name(dev, "%s", dev->init_name);                                
        dev->init_name = NULL;                                                  
    }                                                                           
                                                                                
    /* subsystems can specify simple device enumeration */                      
    if (!dev_name(dev) && dev->bus && dev->bus->dev_name)                       
        dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);                 
                                                                                
    if (!dev_name(dev)) {                                                       
        error = -EINVAL;                                                        
        goto name_error;                                                        
    }                                                                           
                                                                                
    pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

디바이스 하이라키에 디바이스를 추가한다. (sysfs에 연동된다)

  • 코드 라인 9~11에서 디바이스 참조 카운터를 1 증가시킨다.
  • 코드 라인 13~17에서 디바이스에 device_private 구조체가 할당되어 있지 않은 경우 할당한다.
  • 코드 라인 24~27에서 디바이스의 초기 이름(init_name)이 주어진 경우 이를 사용하여 디바이스 명(kobject의 이름)을 지정한다. 그런 후 init_name은 null로 변경한다.
  • 코드 라인 30~31에서 디바이스 명이 주어지지 않지만 버스 이름이 있는 경우 디바이스명으로 버스 이름을 사용한다.
  • 코드 라인 33~36에서 여전히 디바이스명이 없는 경우 에러로 함수를 빠져나간다.

 

drivers/base/core.c -2/3-

.   parent = get_device(dev->parent);                                           
    kobj = get_device_parent(dev, parent);                                      
    if (kobj)                                                                   
        dev->kobj.parent = kobj;                                                
                                                                                
    /* use parent numa_node */                                                  
    if (parent && (dev_to_node(dev) == NUMA_NO_NODE))                           
        set_dev_node(dev, dev_to_node(parent));                                 
                                                                                
    /* first, register with generic layer. */                                   
    /* we require the name to be set before, and pass NULL */                   
    error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);                    
    if (error) {                                                                
        glue_dir = get_glue_dir(dev);                                           
        goto Error;                                                             
    }                                                                           
                                                                                
    /* notify platform of device entry */                                       
    if (platform_notify)                                                        
        platform_notify(dev);                                                   
                                                                                
    error = device_create_file(dev, &dev_attr_uevent);                          
    if (error)                                                                  
        goto attrError;                                                         
                                                                                
    error = device_add_class_symlinks(dev);                                     
    if (error)                                                                  
        goto SymlinkError;                                                      
    error = device_add_attrs(dev);                                              
    if (error)                                                                  
        goto AttrsError;                                                        
    error = bus_add_device(dev);                                                
    if (error)                                                                  
        goto BusError;                                                          
    error = dpm_sysfs_add(dev);                                                 
    if (error)                                                                  
        goto DPMError;                                                          
    device_pm_add(dev);                                                         
                                                                                
    if (MAJOR(dev->devt)) {                                                     
        error = device_create_file(dev, &dev_attr_dev);                         
        if (error)                                                              
            goto DevAttrError;                                                  
                                                                                
        error = device_create_sys_dev_entry(dev);                               
        if (error)                                                              
            goto SysEntryError;                                                 
                                                                                
        devtmpfs_create_node(dev);                                              
    }
  • 코드 라인 1~4에서 부모 디바이스의 참조 카운터를 1 증가시켜 알아오고 현재 디바이스의 부모를 지정한다.
  • 코드 라인 7~8에서 부모 디바이스가 존재하고 현재 디바이스가 모든 노드의 사용이 가능하면 현재 디바이스가 부모 디바이스의 노드번호를 사용하도록 한다.
  • 코드 라인 12~16에서 현재 디바이스를 부모 디바이스에 추가한다.
  • 코드 라인 19~20에서 플랫폼 notify 후크 함수가 지정된 경우 호출한다.
    • of_platform_notify() 또는 acpi_platform_notify() 함수가 사용된다.
  • 코드 라인 22~24에서 uevent 속성에 대한 파일을 생성하고 속성 핸들러를 연동한다.
    • 속성과 관련하여 연동되는 함수는 다음과 같다.
      • uevent_show()
      • uevent_store()
    • kset과 관련하여 연동되는 함수는 다음과 같다. 이들 함수들은 devices_init() 함수를 통해 “/sys/devices” 디렉토리에 연동된다.
      • dev_uevent_filter()
      • dev_uevent_name()
      • dev_uevent()
  • 코드 라인 26~28에서 요청한 디바이스에 대해 of_node, subsystem 및 device 심볼 링크를 생성한다. 부모 디바이스에서는 <요청한 디바이스명>으로 심볼 링크를 생성한다.
  • 코드 라인 29~31에서 디바이스, 디바이스 타입, 클래스에 등록된 속성 그룹들을 추가하여 sysfs에 반영한다. 디바이스의 hot-removal이 지원되는 경우 디바이스에 online 속성도 추가하여 sysfs에 반영한다.
  • 코드 라인 32~34에서 버스에 추가될 디바이스인 경우 디바이스에 버스 타입 속성들을 추가한다. 이들은 그대로 sysfs에 반영된다. 그리고 디바이스와 버스 타입을 서로 심볼 링크로 연결한다
  • 코드 라인 35~37에서 디바이스 디렉토리에 pm 디렉토리와 pm 관련 속성 그룹들을 머지(merge)한 후 추가한다.
  • 코드 라인 38에서 dpm_list에 현재 디바이스를 추가한다.
    • 부모 디바이스가 pm core에 의해 관리되면 부모 디바이스가 슬립하면 안된다는 경고 메시지를 출력한다.
  • 코드 라인 40~50에서 “/sys/dev”에 등록되는 디바이스의 경우 디바이스 디렉토리에 dev 속성을 생성한다. 그리고 “/sys/dev/<dev type>” 디렉토리에 <major>:<minor> 번호로 심볼 링크를 만들어 디바이스와 연결한다.
    • 예) /sys/devices/platform/soc/66090000.watchdog/misc/watchdog/dev
    • 예) /sys/dev/char/10:130 -> /sys/devices/platform/soc/66090000.watchdog/misc/watchdog

 

drivers/base/core.c -3/3-

    /* Notify clients of device addition.  This call must come                  
     * after dpm_sysfs_add() and before kobject_uevent().                       
     */                                                                         
    if (dev->bus)                                                               
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,                
                         BUS_NOTIFY_ADD_DEVICE, dev);                           
                                                                                
    kobject_uevent(&dev->kobj, KOBJ_ADD);                                       
    bus_probe_device(dev);                                                      
    if (parent)                                                                 
        klist_add_tail(&dev->p->knode_parent,                                   
                   &parent->p->klist_children);                                 
                                                                                
    if (dev->class) {                                                           
        mutex_lock(&dev->class->p->mutex);                                      
        /* tie the class to the device */                                       
        klist_add_tail(&dev->knode_class,                                       
                   &dev->class->p->klist_devices);                              
                                                                                
        /* notify any interfaces that the device is here */                     
        list_for_each_entry(class_intf,                                         
                    &dev->class->p->interfaces, node)                           
            if (class_intf->add_dev)                                            
                class_intf->add_dev(dev, class_intf);                           
        mutex_unlock(&dev->class->p->mutex);                                    
    }                                                                           
done:                                                                           
    put_device(dev);                                                            
    return error;                                                               
 SysEntryError:                                                                 
    if (MAJOR(dev->devt))                                                       
        device_remove_file(dev, &dev_attr_dev);                                 
 DevAttrError:                                                                  
    device_pm_remove(dev);                                                      
    dpm_sysfs_remove(dev);                                                      
 DPMError:                                                                      
    bus_remove_device(dev);                                                     
 BusError:                                                                      
    device_remove_attrs(dev);                                                   
 AttrsError:                                                                    
    device_remove_class_symlinks(dev);                                          
 SymlinkError:                                                                  
    device_remove_file(dev, &dev_attr_uevent);                                  
 attrError:                                                                     
    kobject_uevent(&dev->kobj, KOBJ_REMOVE);                                    
    glue_dir = get_glue_dir(dev);                                               
    kobject_del(&dev->kobj);                                                    
 Error:                                                                         
    cleanup_glue_dir(dev, glue_dir);                                            
    put_device(parent);                                                         
name_error:                                                                     
    kfree(dev->p);                                                              
    dev->p = NULL;                                                              
    goto done;                                                                  
}                                                                               
EXPORT_SYMBOL_GPL(device_add);
  • 코드 라인 4~6에서 버스에 디바이스가 추가된 경우 버스가 알도록 bus notifier 호출 체인에 등록된 함수를 호출한다.
  • 코드 라인 8에서 userspace에서 알 수 있도록 uevent를 발생시킨다.
  • 코드 라인 9에서 버스에 연결될 autoprobe가 가능한 디바이스의 경우 곧바로 디바이스 드라이버의 probe 함수를 호출한다.
  • 코드 라인 10~12에서 부모 디바이스가 있는 경우 현재 디바이스를 children으로 등록한다.
  • 코드 라인 14~18에서 클래스가 있는 디바이스의 경우 클래스의 소속되도록 리스트에 디바이스를 추가하고, 클래스 인터페이스의 후크함수 (*add_dev)를 호출한다.

 

디바이스 하이라키 관리 정보

device_private 구조체

drivers/base/base.h

/**
 * struct device_private - structure to hold the private to the driver core portions of the device structure.
 *
 * @klist_children - klist containing all children of this device
 * @knode_parent - node in sibling list
 * @knode_driver - node in driver list
 * @knode_bus - node in bus list
 * @deferred_probe - entry in deferred_probe_list which is used to retry the
 *      binding of drivers which were unable to get all the resources needed by
 *      the device; typically because it depends on another driver getting
 *      probed first.
 * @device - pointer back to the struct device that this structure is
 * associated with.
 *
 * Nothing outside of the driver core should ever touch these fields.
 */
struct device_private {
        struct klist klist_children;
        struct klist_node knode_parent;
        struct klist_node knode_driver;
        struct klist_node knode_bus;
        struct list_head deferred_probe;
        struct device *device;
};
  • klist_children
    • child 디바이스가 추가될 리스트
  • knode_parent
    • child 디바이스가 위의 리스트에 추가시킬 때 사용하는 노드
  • knode_driver
    • 이 디바이스가 드라이버의 디바이스 리스트에 포함될 때 사용되는 노드
      • driver->p->klist_devices
  • knode_bus
    • 이 디바이스가 버스의 디바이스 리스트에 포함될 때 사용되는 노드
      • bus_type->p->klist_devices
  • deferred_probe
    • 전역 deferred probe 리스트에 추가시킬 때 사용되는 노드
      • 전역 deferred_probe_pending_list
  • *device
    • 자신 디바이스를 가리키는 포인터

 

속성(Attribute) & 속성 그룹(Attribute Group)

 

다음 그림과 같이 속성과 속성 그룹간의 관계를 4 단계로 표현해보았다.

 

attribute 구조체

include/linux/sysfs.h

struct attribute {                                                              
    const char      *name;                                                      
    umode_t         mode;                                                       
#ifdef CONFIG_DEBUG_LOCK_ALLOC                                                  
    bool            ignore_lockdep:1;                                           
    struct lock_class_key   *key;                                               
    struct lock_class_key   skey;                                               
#endif                                                                          
};

속성은 attribute 구조체를 사용하여 표현된다. 이 구조체는 여러 가지 타입의 속성 구조체에 임베드되는 기본 골격이다.

  • *name
    • 속성명
  • mode
    • 파일 권한(permission)
      • 예) 0664 = -rw-rw-r–

 

다양한 속성 타입과 속성 정의 매크로

위의 attribute 구조체를 임베드하고 (*show) 및 (*store) 두 가지 후크를 추가하여 다음과 같이 4개의 속성 구조체가 사용된다.

  • device_attribute 구조체
  • bus_attribute 구조체
  • class_attribute 구조체
  • driver_attribute 구조체

 

4가지 구조체 소스를 확인해보면 (*show) 및 (*store) 후크의 인자만 조금씩 다른 것을 확인할 수 있다.

device_attribute()
struct device_attribute {
        struct attribute        attr;
        ssize_t (*show)(struct device *dev, struct device_attribute *attr,
                        char *buf);
        ssize_t (*store)(struct device *dev, struct device_attribute *attr,
                         const char *buf, size_t count);
};

디바이스 속성을 생성하는 경우 /sys/devices/foo에 속성들이 생성된다. 속성 값을 보거나 변경하기 위해 device_attribute 구조체가 지원하는 후크 함수의 기능은 다음과 같다.

  • (*show)
    • 디바이스 속성을 출력
  • (*store)
    • 디바이스 속성을 변경

 

bus_attribute()
struct bus_attribute {
        struct attribute        attr;
        ssize_t (*show)(struct bus_type *bus, char *buf);
        ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};

버스 속성을 생성하는 경우 /sys/bus/foo에 속성들이 생성된다.

 

class_attribute()
struct class_attribute {
        struct attribute attr;
        ssize_t (*show)(struct class *class, struct class_attribute *attr,
                        char *buf);
        ssize_t (*store)(struct class *class, struct class_attribute *attr,
                        const char *buf, size_t count);
};

클래스 속성을 생성하는 경우 /sys/class/foo에 속성들이 생성된다.

 

driver_attribute()
struct driver_attribute {
        struct attribute attr;
        ssize_t (*show)(struct device_driver *driver, char *buf);
        ssize_t (*store)(struct device_driver *driver, const char *buf,
                         size_t count);
};

드라이버 속성을 생성하는 경우 /sys/bus/drivers/foo에 속성들이 생성된다.

 

각 속성 타입별로 속성 구조체를 심플하게 작성해주는 매크로들이 준비되어 있다.

device_attribute 구조체 정의 매크로
#define DEVICE_ATTR(_name, _mode, _show, _store) \
        struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define DEVICE_ATTR_RW(_name) \
        struct device_attribute dev_attr_##_name = __ATTR_RW(_name)
#define DEVICE_ATTR_RO(_name) \
        struct device_attribute dev_attr_##_name = __ATTR_RO(_name)
#define DEVICE_ATTR_WO(_name) \
        struct device_attribute dev_attr_##_name = __ATTR_WO(_name)

 

예) 다음과 같이 디바이스 속성을 하나 정의할 때 생성되는 구조체와 연결된 후크 함수들을 알아본다.

static DEVICE_ATTR_RW(a1);

 

생성되는 구조체 함수는 다음과 같다. a1_show() 함수와 a1_store() 함수는 사용자가 직접 추가 구현해야 한다.

struct device_attribute dev_attr_a1 = {
	.attr = {
		.name = "a1",
		.mode = 644
	}
	.show = a1_show,
	.store = a1_store,
}

 

bus_attribute 구조체 정의 매크로
#define BUS_ATTR(_name, _mode, _show, _store)   \
        struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define BUS_ATTR_RW(_name) \
        struct bus_attribute bus_attr_##_name = __ATTR_RW(_name)
#define BUS_ATTR_RO(_name) \
        struct bus_attribute bus_attr_##_name = __ATTR_RO(_name)

 

class_attribute 구조체 정의 매크로
#define CLASS_ATTR_RW(_name) \
        struct class_attribute class_attr_##_name = __ATTR_RW(_name)
#define CLASS_ATTR_RO(_name) \
        struct class_attribute class_attr_##_name = __ATTR_RO(_name)
#define CLASS_ATTR_WO(_name) \
        struct class_attribute class_attr_##_name = __ATTR_WO(_name)

 

driver_attribute 구조체 정의 매크로
#define DRIVER_ATTR_RW(_name) \
        struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)
#define DRIVER_ATTR_RO(_name) \
        struct driver_attribute driver_attr_##_name = __ATTR_RO(_name)
#define DRIVER_ATTR_WO(_name) \
        struct driver_attribute driver_attr_##_name = __ATTR_WO(_name)

 

각 속성 구조체의 멤버 설정

위의 4가지 매크로에서 공통으로 사용되며, 구조체의 멤버들에 값을 부여하는 매크로이다.

__ATTR()

include/linux/sysfs.h

#define __ATTR(_name, _mode, _show, _store) {               \                   
    .attr = {.name = __stringify(_name),                \                       
         .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },     \                       
    .show   = _show,                        \                                   
    .store  = _store,                       \                                   
}

__ATTR() 매크로에서 사용하는 VERIFY_OCTAL_PERMISSIONS() 매크로는 컴파일 타임에 파일 권한 값이 다음의 조건을 만족하는지 체크한다.

  • 0000 ~ 0777 범위 이내
  • USER_READABLE >= GROUP_READABLE >= OTHER_READABLE
    • 0444 (ok)
    • 0440 (ok)
    • 0400 (ok)
    • 0000 (ok)
    • 0404 (X)
    • 0004 (X)
    • 0040 (X)
    • 가장 많이 실수하는 값은 예전에는 허용했던 0666 값이 최근에는 OTHER_WRITABLE 때문에 문제가 발생한다. 이를 0644 또는 664등으로 수정해야 한다.
  • USER_WRITABLE >= GROUP_WRITABLE
    • 0220 (ok)
    • 0200 (ok)
    • 0000 (ok)
    • 0020 (X)
    • OTHER_WRITABLE 안됨!
      • 0XX0 (ok)
      • 0XX2 (X)

 

__ATTR_WO()
#define __ATTR_RW(_name) __ATTR(_name, (S_IWUSR | S_IRUGO),             \
                         _name##_show, _name##_store)

파일 권한 값으로 0644를 사용한다.

  • user, group, other 모두 읽기 전용

 

__ATTR_RO()
#define __ATTR_RO(_name) { \
.attr = { .name = __stringify(_name), .mode = S_IRUGO }, \
.show = _name##_show, \
}

파일 권한 값으로 0444를 사용한다.

  • user, group, other 모두 읽기 전용

 

__ATTR_WO()
#define __ATTR_WO(_name) { \
.attr = { .name = __stringify(_name), .mode = S_IWUSR }, \
.store = _name##_store, \
}

파일 권한 값으로 0200을 사용한다.

  • user만 쓰기 전용
  • group, other는 금지

 

속성 그룹 및 속성 그룹들

속성 그룹들을 정의하기 위해 다음 단계와 같이 준비한다.

  • 첫 번째, 다수의 속성을 갖는 속성들을 정의한다.
  • 두 번째 ATTRIBUTE_GROUPS()를 사용하여 다음과 같이 2개의 구조체를 생성한다.
    • 하나의 속성 그룹을 만들고 지정한 속성들을 포함시킨다.
    • 속성 그룹들을 만들고 속성 그룹을 포함시킨다.

 

첫 번째, 다수의 속성이 포함된 속성들 정의

다음과 같이 foo 디바이스를 위해 속성들을 정의한다.

static struct attribute *foo_attrs[] = {
        &dev_attr_a1.attr,
        &dev_attr_a2.attr,
        NULL,
};

 

두 번째, 속성 그룹들 정의

이어서 foo 디바이스를 위해 위에서 정의한 속성들을 포함하는 속성 그룹과 속성 그룹들을 정의한다.

ATTRIBUTE_GROUPS(foo);

 

ATTRIBUTE_GROUPS() 매크로 함수
#define ATTRIBUTE_GROUPS(_name)                                 \
static const struct attribute_group _name##_group = {           \
        .attrs = _name##_attrs,                                 \
};                                                              \
__ATTRIBUTE_GROUPS(_name)

 

__ATTRIBUTE_GROUPS() 매크로 함수
#define __ATTRIBUTE_GROUPS(_name)                               \
static const struct attribute_group *_name##_groups[] = {       \
        &_name##_group,                                         \
        NULL,                                                   \
}

 

위의 매크로를 사용하여 생성되는 두 개의 구조체는 다음과 같다.

  • a1과 a2 속성이 foo 속성 그룹이 생성된다.
  • foo 속성 그룹이 담긴 foo 속성 그룹들이 생성된다.
static const struct attribute_group foo_group = {
	.attrs = foo_attrs,
}

static const struct attribute_group *foo_groups[] = {
        &foo_group,
        NULL,
}

 

다음 그림은 플랫폼 디바이스로 등록한 디바이스 속성들을 보여준다.

 

다음 그림에서 foo 디바이스에 대한 디바이스 속성 및 속성 그룹들을 알아보자.

 

속성 그룹들 동적 추가

device_add_attrs()

drivers/base/core.c

static int device_add_attrs(struct device *dev)
{
        struct class *class = dev->class;
        const struct device_type *type = dev->type;
        int error;

        if (class) {
                error = device_add_groups(dev, class->dev_groups);
                if (error)
                        return error;
        }

        if (type) {
                error = device_add_groups(dev, type->groups);
                if (error)
                        goto err_remove_class_groups;
        }

        error = device_add_groups(dev, dev->groups);
        if (error)
                goto err_remove_type_groups;

        if (device_supports_offline(dev) && !dev->offline_disabled) {
                error = device_create_file(dev, &dev_attr_online);
                if (error)
                        goto err_remove_dev_groups;
        }

        return 0;

 err_remove_dev_groups:
        device_remove_groups(dev, dev->groups);
 err_remove_type_groups:
        if (type)
                device_remove_groups(dev, type->groups);
 err_remove_class_groups:
        if (class)
                device_remove_groups(dev, class->dev_groups);

        return error;
}

디바이스, 디바이스 타입, 클래스에 등록된 속성 그룹들을 추가하여 sysfs에 반영한다. 디바이스의 hot-removal이 지원되는 경우 디바이스에 online 속성도 추가하여 sysfs에 반영한다.

  • 코드 라인 7~11에서 디바이스의 클래스에 소속된 그룹들 속성을 추가하여 sysfs에 반영한다.
  • 코드 라인 13~17에서 디바이스의 타입에 소속된 그룹들 속성을 추가하여 sysfs에 반영한다.
  • 코드 라인 19~21에서 디바이스에 소속된 그룹들 속성을 추가하여 sysfs에 반영한다.
  • 코드 라인 23~27에서 디바이스의 hot-removal을 지원하고 현재 offline 기능을 동작하게 할 수 있는 경우 디바이스에 online 속성도 추가하여 sysfs에 반영한다.

 

device_add_groups()

drivers/base/core.c

int device_add_groups(struct device *dev, const struct attribute_group **groups)
{
        return sysfs_create_groups(&dev->kobj, groups);
}
EXPORT_SYMBOL_GPL(device_add_groups);

속성이 담겨있는는 그룹들을 디바이스에 추가하여 sysfs에 반영한다.

 

디바이스 등록 시 생성되는 속성들

아래와 같이 디바이스가 등록되면 디바이스 공통 또는 디바이스 타입, 클래스 타입 및 버스 타입 등에 따라 속성들이 각각 추가된다.

  • 예) gpio 컨트롤러 디바이스에 gpio 0번이 등록된 경우

 

아래 그림을 보면 gpio 디바이스, gpio 컨트롤러 디바이스, gpio 포트 디바이스들이 생성된 경우 아래와 같은 속성들이 등록되는 것을 보여준다.

 

sysfs 반영

device_create_file()

drivers/base/core.c

/**
 * device_create_file - create sysfs attribute file for device.
 * @dev: device.
 * @attr: device attribute descriptor.
 */
int device_create_file(struct device *dev,
                       const struct device_attribute *attr)
{
        int error = 0;

        if (dev) {
                WARN(((attr->attr.mode & S_IWUGO) && !attr->store),
                        "Attribute %s: write permission without 'store'\n",
                        attr->attr.name);
                WARN(((attr->attr.mode & S_IRUGO) && !attr->show),
                        "Attribute %s: read permission without 'show'\n",
                        attr->attr.name);
                error = sysfs_create_file(&dev->kobj, &attr->attr);
        }

        return error;
}
EXPORT_SYMBOL_GPL(device_create_file);

sysfs에 디바이스명에 해당하는 디렉토리와 속성들을 생성한다. 만일 속성에 해당하는 (*store) 및 (*show) 후크 핸들러가 없는 경우 경고 메시지가 출력된다.

 

다음 그림은 foo 디바이스에 준비된 a1 속성이 추가되는 모습을 보여준다.

  • 참고로 대부분의 디바이스 드라이버를 등록할 때 개별 디바이스에 대한 custom 속성들은 대부분 보이지 않을 것이다. 보통 디바이스 타입,  버스 타입 및 클래스 타입에 등록되어 있는 속성들을 사용하므로 개별 드라이버의 custom 속성을 사용할 일이 거의 없다.

 

device_add_class_symlinks()

drivers/base/core.c

static int device_add_class_symlinks(struct device *dev)
{
        struct device_node *of_node = dev_of_node(dev);
        int error;

        if (of_node) {
                error = sysfs_create_link(&dev->kobj, &of_node->kobj,"of_node");
                if (error)
                        dev_warn(dev, "Error %d creating of_node link\n",error);
                /* An error here doesn't warrant bringing down the device */
        }

        if (!dev->class)
                return 0;

        error = sysfs_create_link(&dev->kobj,
                                  &dev->class->p->subsys.kobj,
                                  "subsystem");
        if (error)
                goto out_devnode;

        if (dev->parent && device_is_not_partition(dev)) {
                error = sysfs_create_link(&dev->kobj, &dev->parent->kobj,
                                          "device");
                if (error)
                        goto out_subsys;
        }

#ifdef CONFIG_BLOCK
        /* /sys/block has directories and does not need symlinks */
        if (sysfs_deprecated && dev->class == &block_class)
                return 0;
#endif

        /* link in the class directory pointing to the device */
        error = sysfs_create_link(&dev->class->p->subsys.kobj,
                                  &dev->kobj, dev_name(dev));
        if (error)
                goto out_device;

        return 0;

out_device:
        sysfs_remove_link(&dev->kobj, "device");

out_subsys:
        sysfs_remove_link(&dev->kobj, "subsystem");
out_devnode:
        sysfs_remove_link(&dev->kobj, "of_node");
        return error;
}

요청한 디바이스에 대해 of_node, subsystem 및 device 심볼 링크를 생성한다. 부모 디바이스에서는 <요청한 디바이스명>으로 심볼 링크를 생성한다.

  • 코드 라인 6~14에서 디바이스가 있는 디렉토리에서 디바이스 트리 노드로 “of_node” 파일명으로 심볼 링크를 연결한다.
    • 예) /sys/devices/platform/soc/foo/foo0/of_node -> /sys/firmware/devicetree/base/soc/foo/foo0@660a0000
  • 코드 라인 16~20에서 디바이스가 있는 디렉토리에서 클래스의 타입이 있는 디렉토리를 향하도록 “subsystem” 파일명으로 심볼 링크를 연결한다.
    • 예) /sys/devices/platform/soc/foo/foo0/subsystem -> /sys/class/foo타입
  • 코드 라인 22~27에서 디바이스가 파티션 타입이 아닌 경우 부모 디바이스가 있는 디렉토리로 연동되도록 “device” 파일명으로 심볼 링크를 연결한다.
    • 예) /sys/devices/platform/soc/foo/foo0/device -> /sys/devices/platform/soc/foo/foo0
  • 코드 라인 29~33에서 deprecated된 block 클래스 디바이스인 경우 성공(0) 결과로 그냥 함수를 빠져나간다.
    • CONFIG_SYSFS_DEPRECATED 또는 CONFIG_SYSFS_DEPRECATED_V2 커널 옵션을 사용하면 “/dev/block”을 사용하지 않게 한다.
  • 코드 라인 36~39에서 부모 디바이스에 해당하는 디렉토리에 현재 디바이스 디렉토리를 향하도록 <디바이스명> 파일명으로 심볼 링크를 연결한다.
    • 예) /sys/class/foo타입/foo0 -> /sys/devices/platform/soc/foo/foo0

 

다음 그림은 foo0디바이스와 클래스 및 디바이스에 해당하는 디바이스 트리 노드를 심볼 링크로 연결한 모습을 보여준다.

 

uevent 속성

커널에서 유저 application(udev)에게 디바이스의 hotplug 등의 변경 정보를 전달하기 위해 uevent 속성을 사용한다.

 

드라이버

동작 유형이 비슷한 디바이스들은 벤더에서 드라이버 코드를 작성할 때 하나의 드라이버에서 동작시킨다. 따라서 드라이버는 1개 이상의 디바이스들을 대상으로 동작한다.

 

driver_register()

drivers/base/driver.c

/**
 * driver_register - register driver with bus
 * @drv: driver to register
 *
 * We pass off most of the work to the bus_add_driver() call,
 * since most of the things we have to do deal with the bus
 * structures.
 */
int driver_register(struct device_driver *drv)
{
        int ret;
        struct device_driver *other;

        BUG_ON(!drv->bus->p);

        if ((drv->bus->probe && drv->probe) ||
            (drv->bus->remove && drv->remove) ||
            (drv->bus->shutdown && drv->shutdown))
                printk(KERN_WARNING "Driver '%s' needs updating - please use "
                        "bus_type methods\n", drv->name);

        other = driver_find(drv->name, drv->bus);
        if (other) {
                printk(KERN_ERR "Error: Driver '%s' is already registered, "
                        "aborting...\n", drv->name);
                return -EBUSY;
        }

        ret = bus_add_driver(drv);
        if (ret)
                return ret;
        ret = driver_add_groups(drv, drv->groups);
        if (ret) {
                bus_remove_driver(drv);
                return ret;
        }
        kobject_uevent(&drv->p->kobj, KOBJ_ADD);

        return ret;
}
EXPORT_SYMBOL_GPL(driver_register);

드라이버를 등록한다.

  • 코드 라인 16~20에서 드라이버 또는 버스에 probe, remove 또는 shutdown 후크 함수가 이미 구현된 경우 경고 메시지를 출력한다.
  • 코드 라인 22~27에서 버스에서 인자로 요청한 드라이버에 해당하는 이름으로 검색하여 발견되는 경우 드라이버가 이미 등록되었다는 에러 메시지를 출력하고 -EBUSY 에러를 반환한다.
  • 코드 라인 29~31에서 버스에 드라이버를 등록한다.
    • sysfs의 드라이버 디렉토리에 관련 속성들을 생성한다.
    • sysfs의 모듈 디렉토리에 관련 파라메터들을 생성한다.
  • 코드 라인 32~36에서 sysfs의 드라이버 디렉토리와 드라이버에 해당하는 속성들을 생성한다.
    • /sys/bus/<bus_name>/drivers/foo 디렉토리
  • 코드 라인 37에서 sysfs 드라이버 디렉토리에 uevent 속성도 추가한다.

 

아래 그림은 드라이버를 등록하는 과정에서  sysfs에 생성되는 디렉토리와 속성 파일들을 보여준다.

 

드라이버 하이라키 관리 정보

driver_private 구조체

drivers/base/base.h

struct driver_private {
        struct kobject kobj;
        struct klist klist_devices;
        struct klist_node knode_bus;
        struct module_kobject *mkobj;
        struct device_driver *driver;
};
  • kobj
    • 드라이버를 관리하는 kobject
  • klist_devices
    • 드라이버에 속한 디바이스들
  • knode_bus
    • 소속 버스 klist에 포함될 때 사용되는 klist_node
  • *mkobj
    • module_kobject를 가리킨다.
  • *driver
    • 자신 드라이버를 가리키는 포인터

 

드라이버 관련 API들

  • driver_for_each_device()
    • 드라이버에 소속된 디바이스드을 순회한다.
  • driver_find_device()
    • 드라이버에서 start 디바이스부터 시작하여 매치된 디바이스를 검색한다.
  • driver_create_file()
    • 드라이버 디렉토리를 생성하고 그 디렉토리 내부에 드라이버 속성 파일을 생성한다.
  • driver_remove_file()
    • 드라이버 디렉토리를 삭제한다.
  • driver_unregister()
    • 드라이버를 할당 해제한다.
  • driver_find()
    • 버스에서 요청 드라이버명으로 드라이버를 검색한다.

 

참고

 

do_initcalls()

<kernel v4.14>

do_initcalls()

init/main.c

static void __init do_initcalls(void)
{
        int level;

        for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
                do_initcall_level(level);
}

0 레벨부터  7 레벨까지 단계별로 *_initcall() 매크로 함수로 등록된 함수들을 모두 호출한다.

 

do_initcall_level()

init/main.c

static void __init do_initcall_level(int level)
{
        initcall_t *fn;

        strcpy(initcall_command_line, saved_command_line);
        parse_args(initcall_level_names[level],
                   initcall_command_line, __start___param,
                   __stop___param - __start___param,
                   level, level,
                   NULL, &repair_env_string);

        for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
                do_one_initcall(*fn);
}

커멘드 라인 파라메터들을 파싱하여 커널에 등록된 파라메터들 중 요청 레벨 및 이름이 모두 매치되는 파라메터를 해당함수를 호출하여 설정한다. 그 후 지정된 레벨에 등록된 initcall 함수들을 모두 호출한다.

 

do_one_initcall()

init/main.c

int __init_or_module do_one_initcall(initcall_t fn)
{
        int count = preempt_count();
        int ret;
        char msgbuf[64];

        if (initcall_blacklisted(fn))
                return -EPERM;

        if (initcall_debug)
                ret = do_one_initcall_debug(fn);
        else
                ret = fn();

        msgbuf[0] = 0;

        if (preempt_count() != count) {
                sprintf(msgbuf, "preemption imbalance ");
                preempt_count_set(count);
        }
        if (irqs_disabled()) {
                strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf));
                local_irq_enable();
        }
        WARN(msgbuf[0], "initcall %pF returned with %s\n", fn, msgbuf);

        add_latent_entropy();
        return ret;
}

initcall 함수 하나를 호출한다. 커널 파라메터를 사용하여 블랙리스트에 등록된 함수들은 실행하지 않는다.

 

initcall 레벨별 주요 파라메터

각 단계별 주요 파라메터명 및 호출 함수를 확인해보자. 참고로 현재 커널 소스에는 0레벨과 1레벨만 등록하여 사용하고 있다.

  •  레벨 0: 이 레벨은 셋업 함수와 연동된다.
    • early_param(“earlycon”, param_setup_earlycon);
    • early_param(“cma”, early_cma);
    • early_param(“sysfs.deprecated”, sysfs_deprecated_setup);
    • early_param(“kgdbdbgp”, kgdbdbgp_parse_config);
  • 레벨 1: 이 레벨은 변수와 연동된다.
    • core_param(panic, panic_timeout, int, 0644);
    • core_param(irqtime, irqtime, int, 0400);
    • core_param(nomodule, modules_disabled, bint, 0);
    • core_param(module_blacklist, module_blacklist, charp, 0400);
    • core_param(consoleblank, blankinterval, int, 0444);
    • core_param(initcall_debug, initcall_debug, bool, 0644);
  • 참고

 

각 단계별 initcall에 해당하는 이름은 다음에서 확인할 수 있다.

init/main.c

/* Keep these in sync with initcalls in include/linux/init.h */
static char *initcall_level_names[] __initdata = {
        "early",
        "core",
        "postcore",
        "arch",
        "subsys",
        "fs",
        "device",
        "late",
};

 

레벨별 initcall 함수가 모여있는 섹션의 시작 위치를 담은 배열은 다음에서 확인할 수 있다.

init/main.c

static initcall_t *initcall_levels[] __initdata = {
        __initcall0_start,
        __initcall1_start,
        __initcall2_start,
        __initcall3_start,
        __initcall4_start,
        __initcall5_start,
        __initcall6_start,
        __initcall7_start,
        __initcall_end,
};

 

initcall 섹션

initcall 함수들이 위치하는 곳은 컴파일 타임에 .init.data 섹션의 INIT_CALLS 매크로 자리에 위치하게된다.

arch/arm64/kernel/vmlinux.lds.S

...
        .init.data : {
                INIT_DATA
                INIT_SETUP(16)
                INIT_CALLS
                CON_INITCALL
                SECURITY_INITCALL
                INIT_RAM_FS
                *(.init.rodata.* .init.bss)     /* from the EFI stub */
        }
...

 

INIT_CALLS 매크로에는 early, 0~5, rootfs, 6, 7 레벨에 해당하는 함수들의 주소가 적재된다.

include/asm-generic/vmlinux.lds.h

#define INIT_CALLS                                                      \
                VMLINUX_SYMBOL(__initcall_start) = .;                   \
                KEEP(*(.initcallearly.init))                            \
                INIT_CALLS_LEVEL(0)                                     \
                INIT_CALLS_LEVEL(1)                                     \
                INIT_CALLS_LEVEL(2)                                     \
                INIT_CALLS_LEVEL(3)                                     \
                INIT_CALLS_LEVEL(4)                                     \
                INIT_CALLS_LEVEL(5)                                     \
                INIT_CALLS_LEVEL(rootfs)                                \
                INIT_CALLS_LEVEL(6)                                     \
                INIT_CALLS_LEVEL(7)                                     \
                VMLINUX_SYMBOL(__initcall_end) = .;

 

다음 매크로 함수에서 해당 레벨의 initcall 시작 심볼에 주소가 담기고, initcall 함수들 주소가 적재되는 섹션 위치를 알 수 있다.

include/asm-generic/vmlinux.lds.h

#define INIT_CALLS_LEVEL(level)                                         \
                VMLINUX_SYMBOL(__initcall##level##_start) = .;          \
                KEEP(*(.initcall##level##.init))                        \
                KEEP(*(.initcall##level##s.init))                       \

 

initcall 함수 등록

다음은 레벨 0보다 먼저 시작되어야 할 early 함수들이 컴파일 타임에 정적으로 등록하는 매크로 함수이다.

include/linux/init.h

/*
 * Early initcalls run before initializing SMP.
 *
 * Only for built-in code, not modules.
 */
#define early_initcall(fn)              __define_initcall(fn, early)

 

다음 매크로 함수는 컴파일된 호출 함수의 주소를 해당 레벨의 섹션 위치에 저장한다. (함수 포인터 하나를 저장한다)

include/linux/init.h

/*
 * initcalls are now grouped by functionality into separate
 * subsections. Ordering inside the subsections is determined
 * by link order. 
 * For backwards compatibility, initcall() puts the call in 
 * the device init subsection.
 *
 * The `id' arg to __define_initcall() is needed so that multiple initcalls
 * can point at the same handler without causing duplicate-symbol build errors.
 *
 * Initcalls are run by placing pointers in initcall sections that the
 * kernel iterates at runtime. The linker can do dead code / data elimination
 * and remove that completely, so the initcall sections have to be marked
 * as KEEP() in the linker script.
 */

#define __define_initcall(fn, id) \
        static initcall_t __initcall_##fn##id __used \
        __attribute__((__section__(".initcall" #id ".init"))) = fn;

 

다음 매크로 함수들은 initcall 함수들의 주소를 해당 레벨의 위치에 저장한다. (함수 포인터 하나를 저장한다)

include/linux/init.h

/*
 * A "pure" initcall has no dependencies on anything else, and purely
 * initializes variables that couldn't be statically initialized.
 *
 * This only exists for built-in code, not for modules.
 * Keep main.c:initcall_level_names[] in sync.
 */
#define pure_initcall(fn)               __define_initcall(fn, 0)

#define core_initcall(fn)               __define_initcall(fn, 1)
#define core_initcall_sync(fn)          __define_initcall(fn, 1s)
#define postcore_initcall(fn)           __define_initcall(fn, 2)
#define postcore_initcall_sync(fn)      __define_initcall(fn, 2s)
#define arch_initcall(fn)               __define_initcall(fn, 3)
#define arch_initcall_sync(fn)          __define_initcall(fn, 3s)
#define subsys_initcall(fn)             __define_initcall(fn, 4)
#define subsys_initcall_sync(fn)        __define_initcall(fn, 4s)
#define fs_initcall(fn)                 __define_initcall(fn, 5)
#define fs_initcall_sync(fn)            __define_initcall(fn, 5s)
#define rootfs_initcall(fn)             __define_initcall(fn, rootfs)
#define device_initcall(fn)             __define_initcall(fn, 6)
#define device_initcall_sync(fn)        __define_initcall(fn, 6s)
#define late_initcall(fn)               __define_initcall(fn, 7)
#define late_initcall_sync(fn)          __define_initcall(fn, 7s)

 

console에 대한 initcall이 별도로 정의되고 저장되는 위치도 별도로 두었다.

include/linux/init.h

#define console_initcall(fn)                                    \
        static initcall_t __initcall_##fn                       \
        __used __section(.con_initcall.init) = fn

 

initcall 레벨별 주요 호출 함수

다음은 QEMU에서 arm64 커널을 동작시킬 때 호출되는 initcall들이다.

 

early 레벨: early_initcall()

[    0.049844] initcall cpu_suspend_init+0x0/0x4c returned 0 after 0 usecs
[    0.050052] initcall asids_init+0x0/0xd0 returned 0 after 0 usecs
[    0.050120] initcall xen_guest_init+0x0/0x2e4 returned 0 after 0 usecs
[    0.060595] initcall spawn_ksoftirqd+0x0/0x50 returned 0 after 7812 usecs
[    0.060665] initcall migration_init+0x0/0x4c returned 0 after 0 usecs
[    0.060711] initcall check_cpu_stall_init+0x0/0x2c returned 0 after 0 usecs
[    0.060787] initcall srcu_bootup_announce+0x0/0x40 returned 0 after 0 usecs
[    0.061902] initcall rcu_spawn_gp_kthread+0x0/0x12c returned 0 after 0 usecs
[    0.072857] initcall cpu_stop_init+0x0/0xd4 returned 0 after 7812 usecs
[    0.072966] initcall jump_label_init_module+0x0/0x1c returned 0 after 0 usecs
[    0.073171] initcall its_pci_msi_init+0x0/0xc4 returned 0 after 0 usecs
[    0.073286] initcall its_pmsi_init+0x0/0xa8 returned 0 after 0 usecs
[    0.073394] initcall renesas_soc_init+0x0/0x214 returned -19 after 0 usecs
[    0.073566] initcall rcar_sysc_pd_init+0x0/0x2f0 returned -19 after 0 usecs
[    0.073800] initcall tegra_init_fuse+0x0/0x18c returned 0 after 0 usecs
[    0.073928] initcall tegra_flowctrl_init+0x0/0xc8 returned 0 after 0 usecs
[    0.074271] initcall tegra_pmc_early_init+0x0/0x58c returned 0 after 0 usecs
[    0.075707] initcall rand_initialize+0x0/0x128 returned 0 after 0 usecs
[    0.075794] initcall arm_enable_runtime_services+0x0/0x1fc returned 0 after 0 usecs
[    0.075943] initcall dummy_timer_register+0x0/0x34 returned 0 after 0 usecs

 

0 레벨: pure_initcall()

<pre “>[ 0.190141] initcall ipc_ns_init+0x0/0x68 returned 0 after 0 usecs [ 0.190196] initcall init_mmap_min_addr+0x0/0x18 returned 0 after 0 usecs [ 0.190316] initcall init_cpufreq_transition_notifier_list+0x0/0x34 returned 0 after 0 usecs [ 0.190369] initcall jit_init+0x0/0x8 returned 0 after 0 usecs [ 0.192294] initcall net_ns_init+0x0/0x154 returned 0 after 3906 usecs

 

1 레벨: core_initcall()

[    0.192760] initcall fpsimd_init+0x0/0x80 returned 0 after 0 usecs
[    0.192834] initcall enable_mrs_emulation+0x0/0x24 returned 0 after 0 usecs
[    0.193145] initcall map_entry_trampoline+0x0/0x128 returned 0 after 0 usecs
[    0.193216] initcall cpu_hotplug_pm_sync_init+0x0/0x24 returned 0 after 0 usecs
[    0.193252] initcall alloc_frozen_cpus+0x0/0x8 returned 0 after 0 usecs
[    0.193811] initcall wq_sysfs_init+0x0/0x40 returned 0 after 0 usecs
[    0.194052] initcall ksysfs_init+0x0/0xc0 returned 0 after 0 usecs
[    0.194327] initcall pm_init+0x0/0x80 returned 0 after 0 usecs
[    0.194407] initcall pm_disk_init+0x0/0x24 returned 0 after 0 usecs
[    0.195198] initcall swsusp_header_init+0x0/0x40 returned 0 after 0 usecs
[    0.195239] initcall rcu_set_runtime_mode+0x0/0x14 returned 0 after 0 usecs
[    0.195364] initcall init_jiffies_clocksource+0x0/0x24 returned 0 after 0 usecs
[    0.195683] initcall futex_init+0x0/0xfc returned 0 after 0 usecs
[    0.195819] initcall cgroup_wq_init+0x0/0x40 returned 0 after 0 usecs
[    0.195967] initcall cgroup1_wq_init+0x0/0x40 returned 0 after 0 usecs
[    0.196044] initcall cpu_pm_init+0x0/0x20 returned 0 after 0 usecs
[    0.196439] initcall init_per_zone_wmark_min+0x0/0x8c returned 0 after 0 usecs
[    0.196484] initcall init_zero_pfn+0x0/0x28 returned 0 after 0 usecs
[    0.197700] initcall cma_init_reserved_areas+0x0/0x1c4 returned 0 after 0 usecs
[    0.197821] initcall fsnotify_init+0x0/0x54 returned 0 after 0 usecs
[    0.197966] initcall filelock_init+0x0/0xbc returned 0 after 0 usecs
[    0.198259] initcall init_script_binfmt+0x0/0x24 returned 0 after 0 usecs
[    0.198304] initcall init_elf_binfmt+0x0/0x24 returned 0 after 0 usecs
[    0.198344] initcall init_compat_elf_binfmt+0x0/0x24 returned 0 after 0 usecs
[    0.198470] initcall configfs_init+0x0/0xb8 returned 0 after 0 usecs
[    0.198551] initcall debugfs_init+0x0/0x78 returned 0 after 0 usecs
[    0.198684] initcall prandom_init+0x0/0x154 returned 0 after 0 usecs
[    0.200146] initcall pinctrl_init+0x0/0xc0 returned 0 after 3906 usecs
[    0.200434] initcall gpiolib_dev_init+0x0/0xf4 returned 0 after 0 usecs
[    0.201001] initcall hi3516cv300_crg_init+0x0/0x20 returned 0 after 0 usecs
[    0.201145] initcall hi3519_clk_init+0x0/0x20 returned 0 after 0 usecs
[    0.201288] initcall hi3660_clk_init+0x0/0x20 returned 0 after 0 usecs
[    0.201408] initcall hi3798cv200_crg_init+0x0/0x20 returned 0 after 0 usecs
[    0.201534] initcall gcc_ipq8074_init+0x0/0x20 returned 0 after 0 usecs
[    0.201667] initcall gcc_msm8916_init+0x0/0x20 returned 0 after 0 usecs
[    0.201895] initcall gcc_msm8994_init+0x0/0x20 returned 0 after 0 usecs
[    0.202030] initcall gcc_msm8996_init+0x0/0x20 returned 0 after 0 usecs
[    0.202150] initcall rpm_smd_clk_init+0x0/0x20 returned 0 after 0 usecs
[    0.202295] initcall vexpress_osc_init+0x0/0x20 returned 0 after 0 usecs
[    0.202416] initcall zx_clk_init+0x0/0x20 returned 0 after 0 usecs
[    0.202539] initcall fsl_guts_init+0x0/0x20 returned 0 after 0 usecs
[    0.202866] initcall exynos4_pm_init_power_domain+0x0/0x2fc returned 0 after 0 usecs
[    0.203028] initcall virtio_init+0x0/0x30 returned 0 after 0 usecs
[    0.208022] initcall regulator_init+0x0/0xb0 returned 0 after 3906 usecs
[    0.208149] initcall iommu_init+0x0/0x3c returned 0 after 0 usecs
[    0.208221] initcall opp_debug_init+0x0/0x50 returned 0 after 0 usecs
[    0.208275] initcall dma_init_reserved_memory+0x0/0x60 returned -12 after 0 usecs
[    0.208417] initcall soc_bus_register+0x0/0x50 returned 0 after 0 usecs
[    0.208460] initcall register_cpufreq_notifier+0x0/0x54 returned -22 after 0 usecs
[    0.208668] initcall vexpress_syscfg_init+0x0/0x20 returned 0 after 0 usecs
[    0.208871] initcall vexpress_sysreg_init+0x0/0x70 returned 0 after 0 usecs
[    0.208958] initcall cpufreq_core_init+0x0/0x68 returned 0 after 0 usecs
[    0.209296] initcall cpuidle_init+0x0/0x54 returned 0 after 3906 usecs
[    0.209354] initcall capsule_reboot_register+0x0/0x20 returned 0 after 0 usecs
[    0.209450] initcall arm_dmi_init+0x0/0x28 returned 0 after 0 usecs
[    0.209628] initcall tegra_bpmp_init+0x0/0x20 returned 0 after 0 usecs
[    0.209779] initcall hi6220_mbox_init+0x0/0x20 returned 0 after 0 usecs
[    0.209914] initcall tegra_hsp_init+0x0/0x20 returned 0 after 0 usecs
[    0.213400] initcall sock_init+0x0/0xc0 returned 0 after 0 usecs
[    0.213537] initcall net_inuse_init+0x0/0x30 returned 0 after 0 usecs
[    0.213591] initcall net_defaults_init+0x0/0x30 returned 0 after 0 usecs
[    0.213715] initcall init_default_flow_dissectors+0x0/0x54 returned 0 after 0 usecs
[    0.229426] initcall netlink_proto_init+0x0/0x174 returned 0 after 15625 usecs

 

1 레벨: early_initcall_sync()

core_s
[    0.229483] initcall __gnttab_init+0x0/0x40 returned -19 after 0 usecs

 

2 레벨: postcore_initcall()

postcore
[    0.230197] initcall debug_monitors_init+0x0/0x38 returned 0 after 0 usecs
[    0.230610] initcall irq_sysfs_init+0x0/0xb4 returned 0 after 0 usecs
[    0.230880] initcall bdi_class_init+0x0/0x74 returned 0 after 0 usecs
[    0.230961] initcall mm_sysfs_init+0x0/0x3c returned 0 after 0 usecs
[    0.231166] initcall irqc_init+0x0/0x20 returned 0 after 0 usecs
[    0.231314] initcall vexpress_config_init+0x0/0xec returned 0 after 0 usecs
[    0.231499] initcall rockchip_pinctrl_drv_register+0x0/0x20 returned 0 after 0 usecs
[    0.231655] initcall samsung_pinctrl_drv_register+0x0/0x20 returned 0 after 0 usecs
[    0.231796] initcall sh_pfc_init+0x0/0x20 returned 0 after 0 usecs
[    0.231983] initcall gpiolib_sysfs_init+0x0/0xbc returned 0 after 0 usecs
[    0.232120] initcall tegra_gpio_init+0x0/0x20 returned 0 after 0 usecs
[    0.232222] initcall pcibus_class_init+0x0/0x24 returned 0 after 0 usecs
[    0.232382] initcall pci_driver_init+0x0/0x1c returned 0 after 0 usecs
[    0.232868] initcall backlight_class_init+0x0/0xc0 returned 0 after 0 usecs
[    0.233041] initcall amba_init+0x0/0x1c returned 0 after 0 usecs
[    0.233340] initcall rockchip_grf_init+0x0/0xf8 returned -19 after 0 usecs
[    0.233859] initcall rockchip_pm_domain_drv_register+0x0/0x20 returned 0 after 3906 usecs
[    0.234031] initcall exynos_pmu_init+0x0/0x20 returned 0 after 0 usecs
[    0.234103] initcall xenbus_init+0x0/0x2e8 returned -19 after 0 usecs
[    0.234250] initcall hi6220_reset_init+0x0/0x20 returned 0 after 0 usecs
[    0.234383] initcall tty_class_init+0x0/0x54 returned 0 after 0 usecs
[    0.234848] initcall vtconsole_class_init+0x0/0xfc returned 0 after 0 usecs
[    0.235077] initcall serdev_init+0x0/0x2c returned 0 after 0 usecs
[    0.235173] initcall iommu_dev_init+0x0/0x24 returned 0 after 0 usecs
[    0.235304] initcall mipi_dsi_bus_init+0x0/0x1c returned 0 after 0 usecs
[    0.235389] initcall wakeup_sources_debugfs_init+0x0/0x34 returned 0 after 0 usecs
[    0.235634] initcall register_node_type+0x0/0x20 returned 0 after 0 usecs
[    0.235745] initcall regmap_initcall+0x0/0x18 returned 0 after 0 usecs
[    0.235935] initcall sram_init+0x0/0x20 returned 0 after 0 usecs
[    0.236071] initcall syscon_init+0x0/0x24 returned 0 after 0 usecs
[    0.236334] initcall spi_init+0x0/0xac returned 0 after 0 usecs
[    0.236476] initcall spmi_init+0x0/0x30 returned 0 after 0 usecs
[    0.237354] initcall i2c_init+0x0/0xf0 returned 0 after 0 usecs
[    0.239930] initcall init_menu+0x0/0x1c returned 0 after 3906 usecs
[    0.239996] initcall pcc_init+0x0/0x3dc returned -19 after 0 usecs
[    0.240204] initcall qcom_hwspinlock_init+0x0/0x20 returned 0 after 0 usecs
[    0.240377] initcall rpmsg_init+0x0/0x4c returned 0 after 0 usecs
[    0.240680] initcall kobject_uevent_init+0x0/0x20 returned 0 after 0 usecs

 

2 레벨: postcore_initcall_sync()

[    0.241128] initcall of_iommu_init+0x0/0x88 returned 0 after 0 usecs

 

3 레벨: arch_initcall()

[    0.241563] initcall debug_traps_init+0x0/0x54 returned 0 after 0 usecs
[    0.241706] initcall vdso_init+0x0/0x150 returned 0 after 0 usecs
[    0.245891] initcall alloc_vectors_page+0x0/0xb4 returned 0 after 3906 usecs
[    0.246968] initcall arch_hw_breakpoint_init+0x0/0xf8 returned 0 after 0 usecs
[    0.247140] initcall __iommu_dma_init+0x0/0x14 returned 0 after 0 usecs
[    0.259147] initcall arm64_dma_init+0x0/0x48 returned 0 after 11718 usecs
[    0.259212] initcall p2m_init+0x0/0x18 returned 0 after 0 usecs
[    0.259253] initcall xen_mm_init+0x0/0x94 returned 0 after 0 usecs
[    0.260103] initcall ns2_pinmux_init+0x0/0x20 returned 0 after 0 usecs
[    0.260459] initcall ipq8074_pinctrl_init+0x0/0x20 returned 0 after 0 usecs
[    0.261524] initcall msm8916_pinctrl_init+0x0/0x20 returned 0 after 3906 usecs
[    0.262042] initcall msm8994_pinctrl_init+0x0/0x20 returned 0 after 0 usecs
[    0.262422] initcall msm8996_pinctrl_init+0x0/0x20 returned 0 after 0 usecs
[    0.262996] initcall qdf2xxx_pinctrl_init+0x0/0x20 returned 0 after 0 usecs
[    0.263438] initcall mtk_pinctrl_init+0x0/0x20 returned 0 after 0 usecs
[    0.263553] initcall acpi_pci_init+0x0/0x80 returned 0 after 0 usecs
[    0.264249] initcall clk_mt6797_init+0x0/0x20 returned 0 after 0 usecs
[    0.264306] initcall tegra_clocks_apply_init_table+0x0/0x2c returned 0 after 0 usecs
[    0.264978] initcall dma_bus_init+0x0/0xf4 returned 0 after 0 usecs
[    0.265462] initcall dma_channel_table_init+0x0/0x108 returned 0 after 0 usecs
[    0.266509] initcall qcom_smd_rpm_init+0x0/0x24 returned 0 after 0 usecs
[    0.268196] initcall qcom_smem_init+0x0/0x20 returned 0 after 3906 usecs
[    0.268251] initcall setup_vcpu_hotplug_event+0x0/0x34 returned -19 after 0 usecs
[    0.268300] initcall register_xen_amba_notifier+0x0/0x5c returned 0 after 0 usecs
[    0.268343] initcall register_xen_platform_notifier+0x0/0x58 returned 0 after 0 usecs
[    0.268381] initcall register_xen_pci_notifier+0x0/0x4c returned 0 after 0 usecs
[    0.268908] initcall hi3660_reset_init+0x0/0x20 returned 0 after 0 usecs
[    0.269749] initcall pl011_init+0x0/0x54 returned 0 after 0 usecs
[    0.271510] initcall mvebu_uart_init+0x0/0x60 returned 0 after 3906 usecs
[    0.271566] initcall dmi_id_init+0x0/0x334 returned -19 after 0 usecs

 

3 레벨: arch_initcall_sync()

[    0.272148] initcall iproc_gpio_init+0x0/0x20 returned 0 after 0 usecs
[    0.374345] initcall of_platform_default_populate_init+0x0/0x78 returned 0 after 85937 usecs

 

4 레벨: subsys_initcall()

[    0.379095] initcall topology_init+0x0/0xf4 returned 0 after 3906 usecs
[    0.379245] initcall uid_cache_init+0x0/0xa8 returned 0 after 0 usecs
[    0.547249] initcall param_sysfs_init+0x0/0x1b4 returned 0 after 140625 usecs
[    0.547505] initcall user_namespace_sysctl_init+0x0/0x4c returned 0 after 0 usecs
[    0.554072] initcall pm_sysrq_init+0x0/0x24 returned 0 after 3906 usecs
[    0.554137] initcall create_proc_profile+0x0/0xe8 returned 0 after 0 usecs
[    0.554859] initcall crash_save_vmcoreinfo_init+0x0/0x52c returned 0 after 0 usecs
[    0.554941] initcall crash_notes_memory_init+0x0/0x3c returned 0 after 0 usecs
[    0.554978] initcall cgroup_namespaces_init+0x0/0x8 returned 0 after 0 usecs
[    0.555111] initcall user_namespaces_init+0x0/0x38 returned 0 after 0 usecs
[    0.568422] initcall oom_init+0x0/0x6c returned 0 after 11718 usecs
[    0.570858] initcall default_bdi_init+0x0/0xa4 returned 0 after 0 usecs
[    0.570911] initcall percpu_enable_async+0x0/0x14 returned 0 after 0 usecs
[    0.571306] initcall kcompactd_init+0x0/0xbc returned 0 after 0 usecs
[    0.571350] initcall init_reserve_notifier+0x0/0x8 returned 0 after 0 usecs
[    0.571413] initcall init_admin_reserve+0x0/0x30 returned 0 after 0 usecs
[    0.571459] initcall init_user_reserve+0x0/0x30 returned 0 after 0 usecs
[    0.571562] initcall swap_init_sysfs+0x0/0x78 returned 0 after 0 usecs
[    0.572016] initcall swapfile_init+0x0/0xd8 returned 0 after 3906 usecs
[    0.573415] initcall hugetlb_init+0x0/0x47c returned 0 after 0 usecs
[    0.574489] initcall ksm_init+0x0/0x194 returned 0 after 0 usecs
[    0.575332] initcall hugepage_init+0x0/0x158 returned 0 after 0 usecs
[    0.575419] initcall mem_cgroup_swap_init+0x0/0x7c returned 0 after 0 usecs
[    0.575725] initcall mem_cgroup_init+0x0/0x190 returned 0 after 0 usecs
[    0.576955] initcall crypto_wq_init+0x0/0x44 returned 0 after 3906 usecs
[    0.577026] initcall cryptomgr_init+0x0/0x1c returned 0 after 0 usecs
[    0.577257] initcall cryptd_init+0x0/0xe4 returned 0 after 0 usecs
[    0.578228] initcall init_bio+0x0/0xec returned 0 after 0 usecs
[    0.578280] initcall blk_settings_init+0x0/0x30 returned 0 after 0 usecs
[    0.578363] initcall blk_ioc_init+0x0/0x38 returned 0 after 0 usecs
[    0.578458] initcall blk_softirq_init+0x0/0xa4 returned 0 after 0 usecs
[    0.578503] initcall blk_mq_init+0x0/0x38 returned 0 after 0 usecs
[    0.582086] initcall genhd_device_init+0x0/0x94 returned 0 after 3906 usecs
[    0.582163] initcall gpiolib_debugfs_init+0x0/0x38 returned 0 after 0 usecs
[    0.582368] initcall pca953x_init+0x0/0x20 returned 0 after 0 usecs
[    0.582445] initcall pwm_debugfs_init+0x0/0x38 returned 0 after 0 usecs
[    0.582551] initcall pwm_sysfs_init+0x0/0x28 returned 0 after 0 usecs
[    0.582644] initcall pci_slot_init+0x0/0x58 returned 0 after 0 usecs
[    0.583008] initcall xgene_pcie_msi_init+0x0/0x20 returned 0 after 0 usecs
[    0.583917] initcall fbmem_init+0x0/0x100 returned 0 after 0 usecs
[    0.584243] initcall acpi_init+0x0/0x334 returned -19 after 0 usecs
[    0.584436] initcall pnp_init+0x0/0x1c returned 0 after 0 usecs
[    0.584679] initcall s2mps11_clk_init+0x0/0x20 returned 0 after 0 usecs
[    0.584993] initcall hi6220_stub_clk_init+0x0/0x20 returned 0 after 0 usecs
[    0.586440] initcall cpg_mssr_init+0x0/0x28 returned -19 after 3906 usecs
[    0.586493] initcall balloon_init+0x0/0xd8 returned -19 after 0 usecs
[    0.586547] initcall xen_setup_shutdown_event+0x0/0x48 returned -19 after 0 usecs
[    0.586756] initcall xenbus_probe_backend_init+0x0/0x3c returned 0 after 0 usecs
[    0.586908] initcall xenbus_probe_frontend_init+0x0/0x3c returned 0 after 0 usecs
[    0.587323] initcall regulator_fixed_voltage_init+0x0/0x20 returned 0 after 0 usecs
[    0.587637] initcall gpio_regulator_init+0x0/0x20 returned 0 after 0 usecs
[    0.588079] initcall rpm_reg_init+0x0/0x20 returned 0 after 0 usecs
[    0.593126] initcall misc_init+0x0/0xe4 returned 0 after 3906 usecs
[    0.593282] initcall rk_iommu_init+0x0/0x9c returned 0 after 0 usecs
[    0.594535] initcall vga_arb_device_init+0x0/0xfc returned 0 after 0 usecs
[    0.594650] initcall register_cpu_capacity_sysctl+0x0/0x98 returned 0 after 0 usecs
[    0.595211] initcall sec_pmic_init+0x0/0x20 returned 0 after 0 usecs
[    0.595352] initcall dma_buf_init+0x0/0x98 returned 0 after 0 usecs
[    0.602516] initcall init_scsi+0x0/0x8c returned 0 after 7812 usecs
[    0.609111] initcall ata_init+0x0/0x338 returned 0 after 3906 usecs
[    0.609302] initcall pl022_init+0x0/0x1c returned 0 after 0 usecs
[    0.609785] initcall phy_init+0x0/0x78 returned 0 after 0 usecs
[    0.609921] initcall hnae_init+0x0/0x40 returned 0 after 0 usecs
[    0.613279] initcall usb_init+0x0/0x180 returned 0 after 3906 usecs
[    0.613813] initcall usb_phy_generic_init+0x0/0x20 returned 0 after 0 usecs
[    0.614019] initcall usb_udc_init+0x0/0x64 returned 0 after 0 usecs
[    0.614263] initcall serio_init+0x0/0x44 returned 0 after 0 usecs
[    0.615323] initcall input_init+0x0/0x128 returned 0 after 0 usecs
[    0.615478] initcall rtc_init+0x0/0x68 returned 0 after 0 usecs
[    0.616360] initcall dw_i2c_init_driver+0x0/0x20 returned 0 after 3906 usecs
[    0.617258] initcall i2c_adap_imx_init+0x0/0x20 returned 0 after 0 usecs
[    0.617753] initcall i2c_adap_pxa_init+0x0/0x20 returned 0 after 0 usecs
[    0.618487] initcall sh_mobile_i2c_adap_init+0x0/0x20 returned 0 after 0 usecs
[    0.619106] initcall tegra_i2c_init_driver+0x0/0x20 returned 0 after 0 usecs
[    0.635798] initcall pps_init+0x0/0xd0 returned 0 after 15625 usecs
[    0.636361] initcall ptp_init+0x0/0xb0 returned 0 after 0 usecs
[    0.636891] initcall brcmstb_reboot_init+0x0/0x28 returned -19 after 0 usecs
[    0.637068] initcall power_supply_class_init+0x0/0x60 returned 0 after 0 usecs
[    0.637178] initcall hwmon_init+0x0/0x48 returned 0 after 0 usecs
[    0.640240] initcall edac_init+0x0/0x90 returned 0 after 3906 usecs
[    0.640770] initcall mmc_init+0x0/0x4c returned 0 after 0 usecs
[    0.640921] initcall leds_init+0x0/0x5c returned 0 after 0 usecs
[    0.641354] initcall dmi_init+0x0/0x11c returned -61 after 0 usecs
[    0.641474] initcall qcom_scm_init+0x0/0x7c returned -19 after 0 usecs
[    0.641544] initcall efisubsys_init+0x0/0x26c returned 0 after 0 usecs
[    0.641590] initcall register_gop_device+0x0/0x64 returned 0 after 0 usecs
[    0.642107] initcall qcom_smd_init+0x0/0x24 returned 0 after 0 usecs
[    0.642548] initcall iio_init+0x0/0xac returned 0 after 0 usecs
[    0.642650] initcall arm_pmu_hp_init+0x0/0x60 returned 0 after 0 usecs
[    0.642717] initcall arm_pmu_acpi_init+0x0/0x1e8 returned 0 after 0 usecs
[    0.642957] initcall ras_init+0x0/0x18 returned 0 after 0 usecs
[    0.643260] initcall nvmem_init+0x0/0x20 returned 0 after 0 usecs
[    0.645159] initcall tee_init+0x0/0xac returned 0 after 0 usecs
[    0.646417] initcall init_soundcore+0x0/0x58 returned 0 after 0 usecs
[    0.648290] initcall alsa_sound_init+0x0/0xb8 returned 0 after 0 usecs
[    0.653378] initcall proto_init+0x0/0x1c returned 0 after 3906 usecs
[    0.659593] initcall net_dev_init+0x0/0x1f4 returned 0 after 7812 usecs
[    0.659668] initcall neigh_init+0x0/0xa0 returned 0 after 0 usecs
[    0.659724] initcall fib_notifier_init+0x0/0x1c returned 0 after 0 usecs
[    0.660292] initcall genl_init+0x0/0x4c returned 0 after 0 usecs
[    0.660368] initcall ipv4_netfilter_init+0x0/0x1c returned 0 after 0 usecs

 

4 레벨: subsys_initcall_sync()

[    0.663834] initcall watchdog_init+0x0/0x90 returned 0 after 3906 usecs

 

5 레벨: fs_initcall()

[    0.664462] initcall create_debug_debugfs_entry+0x0/0x30 returned 0 after 0 usecs
[    0.664502] initcall dma_debug_do_init+0x0/0x8 returned 0 after 0 usecs
[    0.666317] initcall clocksource_done_booting+0x0/0x58 returned 0 after 1403 usecs
[    0.668279] initcall init_pipe_fs+0x0/0x60 returned 0 after 1855 usecs
[    0.668480] initcall cgroup_writeback_init+0x0/0x44 returned 0 after 142 usecs
[    0.668640] initcall inotify_user_setup+0x0/0x5c returned 0 after 106 usecs
[    0.668859] initcall eventpoll_init+0x0/0xd4 returned 0 after 164 usecs
[    0.670573] initcall anon_inode_init+0x0/0x6c returned 0 after 1616 usecs
[    0.670691] initcall proc_locks_init+0x0/0x34 returned 0 after 67 usecs
[    0.671856] initcall dquot_init+0x0/0x168 returned 0 after 1091 usecs
[    0.671952] initcall proc_cmdline_init+0x0/0x30 returned 0 after 54 usecs
[    0.672014] initcall proc_consoles_init+0x0/0x30 returned 0 after 23 usecs
[    0.672079] initcall proc_cpuinfo_init+0x0/0x30 returned 0 after 25 usecs
[    0.672162] initcall proc_devices_init+0x0/0x30 returned 0 after 25 usecs
[    0.672222] initcall proc_interrupts_init+0x0/0x30 returned 0 after 22 usecs
[    0.672284] initcall proc_loadavg_init+0x0/0x30 returned 0 after 23 usecs
[    0.672371] initcall proc_meminfo_init+0x0/0x30 returned 0 after 47 usecs
[    0.672436] initcall proc_stat_init+0x0/0x30 returned 0 after 26 usecs
[    0.672499] initcall proc_uptime_init+0x0/0x30 returned 0 after 22 usecs
[    0.672563] initcall proc_version_init+0x0/0x30 returned 0 after 24 usecs
[    0.672623] initcall proc_softirqs_init+0x0/0x30 returned 0 after 21 usecs
[    0.672724] initcall vmcore_init+0x0/0x580 returned 0 after 60 usecs
[    0.672790] initcall proc_kmsg_init+0x0/0x30 returned 0 after 26 usecs
[    0.672876] initcall proc_page_init+0x0/0x6c returned 0 after 45 usecs
[    0.672922] initcall init_ramfs_fs+0x0/0x3c returned 0 after 9 usecs
[    0.674538] initcall init_hugetlbfs_fs+0x0/0x158 returned 0 after 1522 usecs
[    0.674614] initcall blk_scsi_ioctl_init+0x0/0xa4 returned 0 after 25 usecs
[    0.674675] initcall acpi_event_init+0x0/0x44 returned 0 after 17 usecs
[    0.674947] initcall pnp_system_init+0x0/0x1c returned 0 after 222 usecs
[    0.675284] initcall pnpacpi_init+0x0/0x8c returned 0 after 288 usecs
[    0.742028] initcall chr_dev_init+0x0/0xcc returned 0 after 65106 usecs
[    0.742135] initcall firmware_class_init+0x0/0xb8 returned 0 after 51 usecs
[    0.744481] initcall thermal_init+0x0/0xec returned 0 after 2218 usecs
[    0.744617] initcall cpufreq_gov_performance_init+0x0/0x1c returned 0 after 72 usecs
[    0.744849] initcall sysctl_core_init+0x0/0x40 returned 0 after 184 usecs
[    0.744974] initcall eth_offload_init+0x0/0x20 returned 0 after 68 usecs
[    0.776668] initcall inet_init+0x0/0x260 returned 0 after 30887 usecs
[    0.776794] initcall ipv4_offload_init+0x0/0x80 returned 0 after 72 usecs
[    0.778001] initcall af_unix_init+0x0/0x64 returned 0 after 1119 usecs
[    0.778153] initcall ipv6_offload_init+0x0/0x84 returned 0 after 104 usecs
[    0.787329] initcall init_sunrpc+0x0/0x84 returned 0 after 8894 usecs

 

5 레벨: fs_initcall_sync()

[    0.787473] initcall pci_apply_final_quirks+0x0/0x140 returned 0 after 94 usecs
[    0.787601] initcall acpi_reserve_resources+0x0/0xfc returned 0 after 85 usecs

 

rootfs 레벨: rootfs_initcall_sync()

[    0.790338] initcall populate_rootfs+0x0/0x134 returned 0 after 2594 usecs

 

__ 레벨: early_initcall_sync()

[    0.790986] initcall register_kernel_offset_dumper+0x0/0x28 returned 0 after 107 usecs

 

6 레벨: device_initcall_sync() 또는 module_

[    0.791836] initcall cpuinfo_regs_init+0x0/0xc4 returned 0 after 780 usecs
[    0.791913] initcall register_cpu_hwcaps_dumper+0x0/0x28 returned 0 after 21 usecs
[    0.796165] initcall armv8_pmu_driver_init+0x0/0x3c returned 0 after 4047 usecs
[    0.796243] initcall register_mem_limit_dumper+0x0/0x2c returned 0 after 16 usecs
[    0.796701] initcall arm_init+0x0/0x28 returned -19 after 387 usecs
[    0.799885] initcall cpu_feature_match_SHA1_init+0x0/0x30 returned 0 after 3021 usecs
[    0.801951] initcall cpu_feature_match_SHA2_init+0x0/0x34 returned 0 after 1894 usecs
[    0.803528] initcall ghash_ce_mod_init+0x0/0x98 returned 0 after 1448 usecs
[    0.804428] initcall cpu_feature_match_AES_init+0x0/0x30 returned 0 after 797 usecs
[    0.804948] initcall aes_mod_init+0x0/0x30 returned 0 after 442 usecs
[    0.816877] initcall cpu_feature_match_AES_init+0x0/0xcc returned 0 after 10510 usecs
[    0.819621] initcall sha256_mod_init+0x0/0x70 returned 0 after 2593 usecs
[    0.820286] initcall aes_init+0x0/0x1c returned 0 after 586 usecs
[    0.820537] initcall proc_execdomains_init+0x0/0x30 returned 0 after 196 usecs
[    0.820779] initcall cpuhp_sysfs_init+0x0/0x9c returned 0 after 191 usecs
[    0.820864] initcall ioresources_init+0x0/0x54 returned 0 after 41 usecs
[    0.821957] initcall snapshot_device_init+0x0/0x1c returned 0 after 1018 usecs
[    0.822012] initcall irq_gc_init_ops+0x0/0x24 returned 0 after 13 usecs
[    0.822104] initcall irq_debugfs_init+0x0/0x40 returned 0 after 53 usecs
[    0.822148] initcall irq_pm_init_ops+0x0/0x20 returned 0 after 10 usecs
[    0.823233] initcall irq_debugfs_init+0x0/0xa4 returned 0 after 1006 usecs
[    0.823294] initcall timekeeping_init_ops+0x0/0x20 returned 0 after 16 usecs
[    0.824201] initcall init_clocksource_sysfs+0x0/0x78 returned 0 after 803 usecs
[    0.824837] initcall init_timer_list_procfs+0x0/0x38 returned 0 after 545 usecs
[    0.830739] initcall alarmtimer_init+0x0/0x104 returned 0 after 5651 usecs
[    0.830889] initcall init_posix_timers+0x0/0x38 returned 0 after 91 usecs
[    0.838299] initcall clockevents_init_sysfs+0x0/0x108 returned 0 after 7163 usecs
[    0.838368] initcall sched_clock_syscore_init+0x0/0x24 returned 0 after 15 usecs
[    0.838855] initcall proc_modules_init+0x0/0x30 returned 0 after 435 usecs
[    0.839191] initcall kallsyms_init+0x0/0x30 returned 0 after 287 usecs
[    0.839360] initcall pid_namespaces_init+0x0/0x38 returned 0 after 124 usecs
[    0.839717] initcall ikconfig_init+0x0/0x44 returned 0 after 308 usecs
[    0.842610] initcall audit_init+0x0/0x158 returned 0 after 2762 usecs
[    0.842769] initcall audit_watch_init+0x0/0x40 returned 0 after 99 usecs
[    0.842826] initcall audit_fsnotify_init+0x0/0x40 returned 0 after 19 usecs
[    0.842919] initcall audit_tree_init+0x0/0x68 returned 0 after 53 usecs
[    0.843048] initcall seccomp_sysctl_init+0x0/0x38 returned 0 after 88 usecs
[    0.843123] initcall utsname_sysctl_init+0x0/0x20 returned 0 after 36 usecs
[    0.847383] initcall perf_event_sysfs_init+0x0/0xb8 returned 0 after 4039 usecs
[    0.848077] initcall kswapd_init+0x0/0xac returned 0 after 587 usecs
[    0.848410] initcall extfrag_debug_init+0x0/0x90 returned 0 after 253 usecs
[    0.848515] initcall mm_compute_batch_init+0x0/0x60 returned 0 after 44 usecs
[    0.849579] initcall slab_proc_init+0x0/0x30 returned 0 after 657 usecs
[    0.851874] initcall workingset_init+0x0/0xa8 returned 0 after 673 usecs
[    0.852271] initcall proc_vmalloc_init+0x0/0x30 returned 0 after 342 usecs
[    0.852531] initcall memblock_init_debugfs+0x0/0x8c returned 0 after 199 usecs
[    0.852602] initcall procswaps_init+0x0/0x34 returned 0 after 27 usecs
[    0.905566] initcall slab_sysfs_init+0x0/0x124 returned 0 after 51089 usecs
[    0.907179] initcall fcntl_init+0x0/0x38 returned 0 after 106 usecs
[    0.907569] initcall proc_filesystems_init+0x0/0x30 returned 0 after 338 usecs
[    0.907661] initcall start_dirtytime_writeback+0x0/0x3c returned 0 after 52 usecs
[    0.907835] initcall blkdev_init+0x0/0x38 returned 0 after 130 usecs
[    0.908349] initcall dio_init+0x0/0x38 returned 0 after 455 usecs
[    0.908758] initcall dnotify_init+0x0/0x88 returned 0 after 351 usecs
[    0.908889] initcall fanotify_user_setup+0x0/0x84 returned 0 after 87 usecs
[    0.909175] initcall aio_setup+0x0/0x94 returned 0 after 235 usecs
[    0.915272] initcall init_sys32_ioctl+0x0/0x34 returned 0 after 5885 usecs
[    0.915870] initcall mbcache_init+0x0/0x44 returned 0 after 521 usecs
[    0.915961] initcall init_grace+0x0/0x1c returned 0 after 47 usecs
[    0.916322] initcall init_devpts_fs+0x0/0x3c returned 0 after 313 usecs
[    0.919777] initcall ext4_init_fs+0x0/0x160 returned 0 after 3307 usecs
[    0.920333] initcall init_ext2_fs+0x0/0x78 returned 0 after 481 usecs
[    0.928362] initcall journal_init+0x0/0x110 returned 0 after 7765 usecs
[    0.930268] initcall init_squashfs_fs+0x0/0x88 returned 0 after 1768 usecs
[    0.932008] initcall init_fat_fs+0x0/0x5c returned 0 after 1628 usecs
[    0.932398] initcall init_vfat_fs+0x0/0x1c returned 0 after 327 usecs
[    0.938506] initcall init_nfs_fs+0x0/0x150 returned 0 after 5898 usecs
[    0.938626] initcall init_nfs_v2+0x0/0x20 returned 0 after 44 usecs
[    0.938668] initcall init_nfs_v3+0x0/0x20 returned 0 after 9 usecs
[    0.940279] initcall init_nfs_v4+0x0/0x48 returned 0 after 1532 usecs
[    0.940758] initcall nfs4filelayout_init+0x0/0x34 returned 0 after 428 usecs
[    0.943165] initcall init_nlm+0x0/0x88 returned 0 after 2254 usecs
[    0.943361] initcall init_nls_cp437+0x0/0x20 returned 0 after 121 usecs
[    0.943542] initcall init_nls_iso8859_1+0x0/0x20 returned 0 after 13 usecs
[    0.952865] initcall init_cifs+0x0/0x350 returned 0 after 9029 usecs
[    0.954912] initcall init_autofs4_fs+0x0/0x38 returned 0 after 1327 usecs
[    0.955893] initcall init_v9fs+0x0/0xfc returned 0 after 902 usecs
[    0.956240] initcall init_pstore_fs+0x0/0x64 returned 0 after 292 usecs
[    0.956286] initcall efivarfs_init+0x0/0x44 returned -19 after 8 usecs
[    0.959594] initcall ipc_init+0x0/0x64 returned 0 after 3154 usecs
[    0.959729] initcall ipc_sysctl_init+0x0/0x20 returned 0 after 75 usecs
[    0.961814] initcall init_mqueue_fs+0x0/0x104 returned 0 after 1930 usecs
[    0.964280] initcall key_proc_init+0x0/0x74 returned 0 after 2290 usecs
[    0.964574] initcall crypto_algapi_init+0x0/0x18 returned 0 after 215 usecs
[    0.964657] initcall seqiv_module_init+0x0/0x1c returned 0 after 43 usecs
[    0.964709] initcall echainiv_module_init+0x0/0x1c returned 0 after 15 usecs
[    0.964755] initcall crypto_cmac_module_init+0x0/0x1c returned 0 after 10 usecs
[    0.964799] initcall hmac_module_init+0x0/0x1c returned 0 after 10 usecs
[    0.967749] initcall crypto_null_mod_init+0x0/0x58 returned 0 after 2805 usecs
[    0.968429] initcall md4_mod_init+0x0/0x1c returned 0 after 576 usecs
[    0.970118] initcall md5_mod_init+0x0/0x1c returned 0 after 1568 usecs
[    0.971357] initcall sha1_generic_mod_init+0x0/0x1c returned 0 after 1123 usecs
[    0.973454] initcall sha256_generic_mod_init+0x0/0x20 returned 0 after 1965 usecs
[    0.973525] initcall crypto_ecb_module_init+0x0/0x1c returned 0 after 18 usecs
[    0.973593] initcall crypto_ctr_module_init+0x0/0x58 returned 0 after 30 usecs
[    0.973698] initcall crypto_ccm_module_init+0x0/0x90 returned 0 after 66 usecs
[    0.974890] initcall des_generic_mod_init+0x0/0x20 returned 0 after 1114 usecs
[    0.975638] initcall aes_init+0x0/0x1c returned 0 after 632 usecs
[    0.976787] initcall arc4_init+0x0/0x20 returned 0 after 1026 usecs
[    0.979462] initcall crc32c_mod_init+0x0/0x1c returned 0 after 2527 usecs
[    0.980201] initcall crct10dif_mod_init+0x0/0x1c returned 0 after 657 usecs
[    0.980644] initcall prng_mod_init+0x0/0x20 returned 0 after 379 usecs
[    0.988584] initcall drbg_init+0x0/0x1dc returned 0 after 7681 usecs
[    0.991197] initcall jent_mod_init+0x0/0x3c returned 0 after 2484 usecs
[    0.991501] initcall proc_genhd_init+0x0/0x54 returned 0 after 204 usecs
[    0.992870] initcall bsg_init+0x0/0x150 returned 0 after 1269 usecs
[    0.994240] initcall noop_init+0x0/0x1c returned 0 after 1282 usecs
[    0.995532] initcall cfq_init+0x0/0x74 returned 0 after 1209 usecs
[    0.995950] initcall deadline_init+0x0/0x1c returned 0 after 364 usecs
[    0.996254] initcall kyber_init+0x0/0x1c returned 0 after 256 usecs
[    0.996391] initcall crc_t10dif_mod_init+0x0/0x50 returned 0 after 94 usecs
[    0.998384] initcall percpu_counter_startup+0x0/0x6c returned 0 after 1855 usecs
[    0.998750] initcall audit_classes_init+0x0/0xb0 returned 0 after 233 usecs
[    0.999272] initcall sg_pool_init+0x0/0x108 returned 0 after 432 usecs
[    0.999858] initcall mbigen_platform_driver_init+0x0/0x20 returned 0 after 506 usecs
[    1.000271] initcall mvebu_gicp_driver_init+0x0/0x20 returned 0 after 354 usecs
[    1.000592] initcall mvebu_icu_driver_init+0x0/0x20 returned 0 after 268 usecs
[    1.001282] initcall mvebu_pic_driver_init+0x0/0x20 returned 0 after 291 usecs
[    1.003670] initcall ls_scfg_msi_driver_init+0x0/0x24 returned 0 after 1257 usecs
[    1.004119] initcall qcom_irq_combiner_probe_init+0x0/0x20 returned 0 after 358 usecs
[    1.005098] initcall uniphier_aidet_driver_init+0x0/0x20 returned 0 after 884 usecs
[    1.007038] initcall brcm_gisb_driver_init+0x0/0x28 returned -19 after 1802 usecs
[    1.007642] initcall qcom_ebi2_driver_init+0x0/0x20 returned 0 after 520 usecs
[    1.008102] initcall sunxi_rsb_init+0x0/0x54 returned 0 after 402 usecs
[    1.008419] initcall uniphier_system_bus_driver_init+0x0/0x20 returned 0 after 263 usecs
[    1.008555] initcall phy_core_init+0x0/0x68 returned 0 after 90 usecs
[    1.008955] initcall xgene_phy_driver_init+0x0/0x20 returned 0 after 329 usecs
[    1.015040] initcall sun4i_usb_phy_driver_init+0x0/0x20 returned 0 after 5828 usecs
[    1.015665] initcall phy_meson8b_usb2_driver_init+0x0/0x20 returned 0 after 546 usecs
[    1.016212] initcall phy_meson_gxl_usb2_driver_init+0x0/0x20 returned 0 after 479 usecs
[    1.016751] initcall rcar_gen3_phy_usb2_driver_init+0x0/0x20 returned 0 after 412 usecs
[    1.017076] initcall rockchip_emmc_driver_init+0x0/0x20 returned 0 after 271 usecs
[    1.017512] initcall rockchip_usb2phy_driver_init+0x0/0x20 returned 0 after 376 usecs
[    1.028836] initcall tegra_xusb_padctl_driver_init+0x0/0x24 returned 0 after 10990 usecs
[    1.029373] initcall mdio_module_init+0x0/0x1c returned 0 after 439 usecs
[    1.029720] initcall ns2_drd_phy_driver_init+0x0/0x20 returned 0 after 290 usecs
[    1.030199] initcall brcm_sata_phy_driver_init+0x0/0x20 returned 0 after 420 usecs
[    1.030585] initcall hi6220_phy_driver_init+0x0/0x20 returned 0 after 329 usecs
[    1.030982] initcall exynos_dp_video_phy_driver_init+0x0/0x20 returned 0 after 339 usecs
[    1.031486] initcall exynos_mipi_video_phy_driver_init+0x0/0x20 returned 0 after 428 usecs
[    1.031820] initcall samsung_usb2_phy_driver_init+0x0/0x20 returned 0 after 263 usecs
[    1.032258] initcall exynos5_usb3drd_phy_init+0x0/0x20 returned 0 after 378 usecs
[    1.032497] initcall max77620_pinctrl_driver_init+0x0/0x20 returned 0 after 185 usecs
[    1.039774] initcall meson_pinctrl_driver_init+0x0/0x20 returned 0 after 7032 usecs
[    1.040447] initcall pcs_driver_init+0x0/0x20 returned 0 after 593 usecs
[    1.040800] initcall tegra124_pinctrl_driver_init+0x0/0x20 returned 0 after 294 usecs
[    1.045937] initcall tegra210_pinctrl_driver_init+0x0/0x20 returned 0 after 4945 usecs
[    1.046327] initcall bcm2835_pinctrl_driver_init+0x0/0x24 returned 0 after 319 usecs
[    1.046681] initcall armada_ap806_pinctrl_driver_init+0x0/0x20 returned 0 after 297 usecs
[    1.047099] initcall armada_cp110_pinctrl_driver_init+0x0/0x20 returned 0 after 360 usecs
[    1.047581] initcall armada_37xx_pinctrl_driver_init+0x0/0x28 returned -19 after 419 usecs
[    1.048072] initcall pmic_gpio_driver_init+0x0/0x20 returned 0 after 430 usecs
[    1.048606] initcall pmic_mpp_driver_init+0x0/0x20 returned 0 after 473 usecs
[    1.049113] initcall a64_pinctrl_driver_init+0x0/0x20 returned 0 after 433 usecs
[    1.056448] initcall sun50i_a64_r_pinctrl_driver_init+0x0/0x20 returned 0 after 642 usecs
[    1.056865] initcall sun8i_h3_r_pinctrl_driver_init+0x0/0x20 returned 0 after 347 usecs
[    1.058279] initcall sun50i_h5_pinctrl_driver_init+0x0/0x20 returned 0 after 1319 usecs
[    1.058663] initcall uniphier_ld11_pinctrl_driver_init+0x0/0x20 returned 0 after 323 usecs
[    1.058991] initcall uniphier_ld20_pinctrl_driver_init+0x0/0x20 returned 0 after 275 usecs
[    1.059321] initcall uniphier_pxs3_pinctrl_driver_init+0x0/0x20 returned 0 after 277 usecs
[    1.059823] initcall bgpio_driver_init+0x0/0x20 returned 0 after 443 usecs
[    1.060156] initcall brcmstb_gpio_driver_init+0x0/0x20 returned 0 after 277 usecs
[    1.060573] initcall dwapb_gpio_driver_init+0x0/0x20 returned 0 after 345 usecs
[    1.060855] initcall max77620_gpio_driver_init+0x0/0x20 returned 0 after 217 usecs
[    1.063868] initcall mvebu_gpio_driver_init+0x0/0x20 returned 0 after 2780 usecs
[    1.064304] initcall pl061_gpio_init+0x0/0x1c returned 0 after 319 usecs
[    1.067876] initcall gpio_rcar_device_driver_init+0x0/0x20 returned 0 after 3383 usecs
[    1.068416] initcall xgene_gpio_driver_init+0x0/0x20 returned 0 after 458 usecs
[    1.068747] initcall xgene_gpio_sb_driver_init+0x0/0x20 returned 0 after 275 usecs
[    1.069692] initcall iproc_pwmc_driver_init+0x0/0x20 returned 0 after 874 usecs
[    1.070125] initcall rockchip_pwm_driver_init+0x0/0x20 returned 0 after 375 usecs
[    1.070636] initcall pwm_samsung_driver_init+0x0/0x20 returned 0 after 453 usecs
[    1.071312] initcall pci_proc_init+0x0/0x88 returned 0 after 608 usecs
[    1.071759] initcall pcie_portdrv_init+0x0/0xa4 returned 0 after 390 usecs
[    1.072022] initcall aer_service_init+0x0/0x4c returned 0 after 213 usecs
[    1.072142] initcall pcie_pme_service_init+0x0/0x1c returned 0 after 76 usecs
[    1.072178] initcall pci_hotplug_init+0x0/0x8 returned 0 after 3 usecs
[    1.072337] initcall pcied_init+0x0/0x7c returned 0 after 116 usecs
[    1.072679] initcall advk_pcie_driver_init+0x0/0x20 returned 0 after 289 usecs
[    1.077727] initcall rcar_pcie_driver_init+0x0/0x20 returned 0 after 4858 usecs
[    1.115609] initcall gen_pci_driver_init+0x0/0x20 returned 0 after 36874 usecs
[    1.116331] initcall iproc_pcie_pltfm_driver_init+0x0/0x20 returned 0 after 623 usecs
[    1.116655] initcall xgene_pcie_driver_init+0x0/0x20 returned 0 after 265 usecs
[    1.117921] initcall ls_pcie_driver_init+0x0/0x28 returned -19 after 1183 usecs
[    1.118455] initcall qcom_pcie_driver_init+0x0/0x20 returned 0 after 469 usecs
[    1.118854] initcall armada8k_pcie_driver_init+0x0/0x20 returned 0 after 342 usecs
[    1.119235] initcall kirin_pcie_driver_init+0x0/0x20 returned 0 after 315 usecs
[    1.119634] initcall hisi_pcie_almost_ecam_driver_init+0x0/0x24 returned 0 after 340 usecs
[    1.119989] initcall hisi_pcie_driver_init+0x0/0x20 returned 0 after 299 usecs
[    1.120235] initcall amba_clcdfb_init+0x0/0x3c returned 0 after 196 usecs
[    1.120284] initcall xenfb_init+0x0/0x54 returned -19 after 11 usecs
[    1.120550] initcall ged_driver_init+0x0/0x20 returned 0 after 214 usecs
[    1.120622] initcall acpi_button_driver_init+0x0/0x20 returned -19 after 31 usecs
[    1.120868] initcall acpi_fan_driver_init+0x0/0x20 returned 0 after 196 usecs
[    1.121381] initcall acpi_processor_driver_init+0x0/0xb0 returned 0 after 451 usecs
[    1.128339] initcall acpi_thermal_init+0x0/0x94 returned -19 after 6683 usecs
[    1.128410] initcall acpi_hed_driver_init+0x0/0x20 returned -19 after 11 usecs
[    1.128503] initcall erst_init+0x0/0x2d4 returned 0 after 55 usecs
[    1.128566] initcall ghes_init+0x0/0x210 returned -19 after 27 usecs
[    1.128631] initcall gtdt_sbsa_gwdt_init+0x0/0xe4 returned 0 after 25 usecs
[    1.130444] initcall tegra_ahb_driver_init+0x0/0x20 returned 0 after 1707 usecs
[    1.131101] initcall of_fixed_factor_clk_driver_init+0x0/0x20 returned 0 after 550 usecs
[    1.131651] initcall of_fixed_clk_driver_init+0x0/0x20 returned 0 after 472 usecs
[    1.132171] initcall gpio_clk_driver_init+0x0/0x20 returned 0 after 452 usecs
[    1.132329] initcall cs2000_driver_init+0x0/0x20 returned 0 after 111 usecs
[    1.132686] initcall clk_pwm_driver_init+0x0/0x20 returned 0 after 305 usecs
[    1.132963] initcall rk808_clkout_driver_init+0x0/0x20 returned 0 after 223 usecs
[    1.133309] initcall scpi_clocks_driver_init+0x0/0x20 returned 0 after 291 usecs
[    1.133665] initcall bcm2835_clk_driver_init+0x0/0x20 returned 0 after 300 usecs
[    1.137105] initcall bcm2835_aux_clk_driver_init+0x0/0x20 returned 0 after 3254 usecs
[    1.140914] initcall sr_clk_driver_init+0x0/0x20 returned 0 after 3625 usecs
[    1.142305] initcall gxbb_driver_init+0x0/0x20 returned 0 after 1294 usecs
[    1.142706] initcall gxbb_aoclkc_driver_init+0x0/0x20 returned 0 after 338 usecs
[    1.143107] initcall armada_3700_xtal_clock_driver_init+0x0/0x20 returned 0 after 343 usecs
[    1.143574] initcall armada_3700_tbg_clock_driver_init+0x0/0x20 returned 0 after 381 usecs
[    1.144487] initcall armada_3700_periph_clock_driver_init+0x0/0x20 returned 0 after 748 usecs
[    1.155650] initcall ap806_clock_driver_init+0x0/0x24 returned 0 after 10783 usecs
[    1.156219] initcall ap806_syscon_legacy_driver_init+0x0/0x20 returned 0 after 490 usecs
[    1.156567] initcall cp110_clock_driver_init+0x0/0x24 returned 0 after 290 usecs
[    1.157354] initcall cp110_syscon_legacy_driver_init+0x0/0x20 returned 0 after 712 usecs
[    1.157768] initcall mmcc_msm8996_driver_init+0x0/0x20 returned 0 after 352 usecs
[    1.158271] initcall exynos_audss_clk_driver_init+0x0/0x24 returned 0 after 437 usecs
[    1.158642] initcall sun4i_a10_mod0_clk_driver_init+0x0/0x20 returned 0 after 308 usecs
[    1.159039] initcall sun9i_a80_mmc_config_clk_driver_init+0x0/0x20 returned 0 after 290 usecs
[    1.159432] initcall sun50i_a64_ccu_driver_init+0x0/0x20 returned 0 after 319 usecs
[    1.160553] initcall uniphier_clk_driver_init+0x0/0x20 returned 0 after 1042 usecs
[    1.160917] initcall k3_pdma_driver_init+0x0/0x20 returned 0 after 305 usecs
[    1.161296] initcall mv_xor_v2_driver_init+0x0/0x20 returned 0 after 319 usecs
[    1.161474] initcall pl330_driver_init+0x0/0x1c returned 0 after 128 usecs
[    1.161556] initcall shdma_enter+0x0/0x48 returned 0 after 41 usecs
[    1.162729] initcall shdma_of_init+0x0/0x20 returned 0 after 366 usecs
[    1.163121] initcall rcar_dmac_driver_init+0x0/0x20 returned 0 after 327 usecs
[    1.163821] initcall tegra_dmac_driver_init+0x0/0x20 returned 0 after 601 usecs
[    1.164530] initcall bam_dma_driver_init+0x0/0x20 returned 0 after 619 usecs
[    1.166110] initcall hidma_mgmt_init+0x0/0x6c returned 0 after 1478 usecs
[    1.166561] initcall hidma_driver_init+0x0/0x20 returned 0 after 384 usecs
[    1.166951] initcall rpi_power_driver_init+0x0/0x20 returned 0 after 329 usecs
[    1.167489] initcall scpsys_drv_init+0x0/0x20 returned 0 after 473 usecs
[    1.167616] initcall meson_gx_socinfo_init+0x0/0x268 returned -19 after 80 usecs
[    1.168059] initcall qcom_smp2p_driver_init+0x0/0x20 returned 0 after 384 usecs
[    1.168429] initcall qcom_smsm_driver_init+0x0/0x20 returned 0 after 311 usecs
[    1.168875] initcall sunxi_sram_driver_init+0x0/0x20 returned 0 after 355 usecs
[    1.169047] initcall tegra_init_soc+0x0/0x5c returned 0 after 124 usecs
[    1.170853] initcall tegra_fuse_driver_init+0x0/0x20 returned 0 after 1670 usecs
[    1.171390] initcall tegra_flowctrl_driver_init+0x0/0x20 returned 0 after 446 usecs
[    1.172004] initcall tegra_pmc_driver_init+0x0/0x20 returned 0 after 543 usecs
[    1.172427] initcall tegra186_pmc_driver_init+0x0/0x20 returned 0 after 346 usecs
[    1.200380] initcall virtio_mmio_init+0x0/0x20 returned 0 after 27225 usecs
[    1.207300] initcall virtio_pci_driver_init+0x0/0x28 returned 0 after 6655 usecs
[    1.207725] initcall virtio_balloon_driver_init+0x0/0x1c returned 0 after 343 usecs
[    1.207782] initcall xenbus_probe_initcall+0x0/0x70 returned -19 after 15 usecs
[    1.207822] initcall xenbus_init+0x0/0x54 returned -19 after 6 usecs
[    1.207865] initcall xenbus_backend_init+0x0/0x68 returned -19 after 7 usecs
[    1.207951] initcall evtchn_init+0x0/0x64 returned -19 after 16 usecs
[    1.207993] initcall gntdev_init+0x0/0x6c returned -19 after 7 usecs
[    1.208055] initcall gntalloc_init+0x0/0x54 returned -19 after 23 usecs
[    1.208095] initcall xenfs_init+0x0/0x30 returned 0 after 7 usecs
[    1.208138] initcall hypervisor_subsys_init+0x0/0x2c returned -19 after 8 usecs
[    1.208191] initcall hyper_sysfs_init+0x0/0x138 returned -19 after 20 usecs
[    1.208232] initcall privcmd_init+0x0/0x54 returned -19 after 6 usecs
[    1.208685] initcall axp20x_regulator_driver_init+0x0/0x20 returned 0 after 393 usecs
[    1.208915] initcall fan53555_regulator_driver_init+0x0/0x20 returned 0 after 106 usecs
[    1.212681] initcall hi6421v530_regulator_driver_init+0x0/0x20 returned 0 after 1352 usecs
[    1.213269] initcall hi655x_regulator_driver_init+0x0/0x20 returned 0 after 510 usecs
[    1.214381] initcall max77620_regulator_driver_init+0x0/0x20 returned 0 after 1026 usecs
[    1.215728] initcall qcom_spmi_regulator_driver_init+0x0/0x20 returned 0 after 1191 usecs
[    1.219085] initcall pwm_regulator_driver_init+0x0/0x20 returned 0 after 3185 usecs
[    1.219857] initcall rk808_regulator_driver_init+0x0/0x20 returned 0 after 685 usecs
[    1.220691] initcall s2mps11_pmic_driver_init+0x0/0x20 returned 0 after 758 usecs
[    1.222473] initcall berlin_reset_driver_init+0x0/0x20 returned 0 after 1678 usecs
[    1.225723] initcall meson_reset_driver_init+0x0/0x20 returned 0 after 2448 usecs
[    1.227767] initcall sunxi_reset_driver_init+0x0/0x20 returned 0 after 863 usecs
[    1.229999] initcall uniphier_reset_driver_init+0x0/0x20 returned 0 after 2111 usecs
[    1.230068] initcall n_null_init+0x0/0x28 returned 0 after 20 usecs
[    1.276899] initcall pty_init+0x0/0x39c returned 0 after 45660 usecs
[    1.277958] initcall sysrq_init+0x0/0x90 returned 0 after 425 usecs
[    1.278008] initcall xen_hvc_init+0x0/0x288 returned -19 after 9 usecs
[    1.291375] initcall serial8250_init+0x0/0x17c returned 0 after 12983 usecs
[    1.291891] initcall serial_pci_driver_init+0x0/0x28 returned 0 after 438 usecs
[    1.292565] initcall exar_pci_driver_init+0x0/0x28 returned 0 after 597 usecs
[    1.295918] initcall bcm2835aux_serial_driver_init+0x0/0x20 returned 0 after 3180 usecs
[    1.297914] initcall dw8250_platform_driver_init+0x0/0x20 returned 0 after 1874 usecs
[    1.298605] initcall mtk8250_platform_driver_init+0x0/0x20 returned 0 after 609 usecs
[    1.299982] initcall uniphier_uart_platform_driver_init+0x0/0x20 returned 0 after 1251 usecs
[    1.301123] initcall of_platform_serial_driver_init+0x0/0x20 returned 0 after 1052 usecs
[    1.302984] initcall samsung_serial_driver_init+0x0/0x24 returned 0 after 1738 usecs
[    1.308461] initcall sci_init+0x0/0x34 returned 0 after 5176 usecs
[    1.311928] initcall meson_uart_init+0x0/0x60 returned 0 after 3280 usecs
[    1.315302] initcall msm_serial_init+0x0/0x5c returned 0 after 3215 usecs
[    1.316842] initcall cdns_uart_init+0x0/0x60 returned 0 after 1431 usecs
[    1.318819] initcall tegra_uart_init+0x0/0x88 returned 0 after 1844 usecs
[    1.318990] initcall init_kgdboc+0x0/0x2c returned 0 after 114 usecs
[    1.319537] initcall init+0x0/0x124 returned 0 after 471 usecs
[    1.320263] initcall arm_smmu_driver_init+0x0/0x24 returned 0 after 625 usecs
[    1.322183] initcall arm_smmu_driver_init+0x0/0x24 returned 0 after 1743 usecs
[    1.322510] initcall topology_sysfs_init+0x0/0x38 returned 0 after 255 usecs
[    1.327690] initcall cacheinfo_sysfs_init+0x0/0x38 returned -2 after 4950 usecs
[    1.379986] initcall loop_init+0x0/0x158 returned 0 after 50990 usecs
[    1.434777] initcall init+0x0/0xa8 returned 0 after 53435 usecs
[    1.434887] initcall xlblk_init+0x0/0x10c returned -19 after 23 usecs
[    1.435446] initcall cros_ec_driver_init+0x0/0x20 returned 0 after 501 usecs
[    1.435881] initcall cros_ec_driver_spi_init+0x0/0x20 returned 0 after 376 usecs
[    1.436446] initcall axp20x_rsb_driver_init+0x0/0x1c returned 0 after 503 usecs
[    1.436814] initcall max77620_driver_init+0x0/0x20 returned 0 after 311 usecs
[    1.437737] initcall pmic_spmi_driver_init+0x0/0x20 returned 0 after 851 usecs
[    1.438359] initcall rk808_i2c_driver_init+0x0/0x20 returned 0 after 555 usecs
[    1.439070] initcall hi6421_pmic_driver_init+0x0/0x20 returned 0 after 639 usecs
[    1.439893] initcall hi655x_pmic_driver_init+0x0/0x20 returned 0 after 750 usecs
[    1.443050] initcall sas_transport_init+0x0/0xd0 returned 0 after 3013 usecs
[    1.443241] initcall sas_class_init+0x0/0x44 returned 0 after 78 usecs
[    1.446843] initcall init_sd+0x0/0x184 returned 0 after 3451 usecs
[    1.447391] initcall hisi_sas_init+0x0/0x38 returned 0 after 478 usecs
[    1.449505] initcall hisi_sas_v1_driver_init+0x0/0x20 returned 0 after 2000 usecs
[    1.450612] initcall hisi_sas_v2_driver_init+0x0/0x20 returned 0 after 1017 usecs
[    1.451309] initcall sas_v3_pci_driver_init+0x0/0x28 returned 0 after 626 usecs
[    1.451834] initcall ahci_pci_driver_init+0x0/0x28 returned 0 after 458 usecs
[    1.453128] initcall ahci_driver_init+0x0/0x20 returned 0 after 1174 usecs
[    1.454652] initcall sil24_pci_driver_init+0x0/0x28 returned 0 after 1418 usecs
[    1.455526] initcall ceva_ahci_driver_init+0x0/0x20 returned 0 after 790 usecs
[    1.456835] initcall ahci_mvebu_driver_init+0x0/0x20 returned 0 after 1181 usecs
[    1.460396] initcall xgene_ahci_driver_init+0x0/0x20 returned 0 after 3397 usecs
[    1.461305] initcall ahci_qoriq_driver_init+0x0/0x20 returned 0 after 824 usecs
[    1.463532] initcall sata_rcar_driver_init+0x0/0x20 returned 0 after 2076 usecs
[    1.465937] initcall pata_platform_driver_init+0x0/0x24 returned 0 after 2131 usecs
[    1.468369] initcall pata_of_platform_driver_init+0x0/0x20 returned 0 after 2284 usecs
[    1.470490] initcall init_mtd+0x0/0x138 returned 0 after 2011 usecs
[    1.470597] initcall ofpart_parser_init+0x0/0x3c returned 0 after 61 usecs
[    1.470771] initcall init_mtdblock+0x0/0x1c returned 0 after 133 usecs
[    1.471460] initcall m25p80_driver_init+0x0/0x20 returned 0 after 627 usecs
[    1.472144] initcall denali_dt_driver_init+0x0/0x20 returned 0 after 616 usecs
[    1.473070] initcall bcm_iproc_driver_init+0x0/0x20 returned 0 after 851 usecs
[    1.473737] initcall brcmstb_qspi_driver_init+0x0/0x20 returned 0 after 598 usecs
[    1.475155] initcall orion_spi_driver_init+0x0/0x20 returned 0 after 1330 usecs
[    1.476101] initcall spi_qup_driver_init+0x0/0x20 returned 0 after 869 usecs
[    1.477410] initcall rockchip_spi_driver_init+0x0/0x20 returned 0 after 1225 usecs
[    1.478876] initcall s3c64xx_spi_driver_init+0x0/0x20 returned 0 after 1365 usecs
[    1.479590] initcall spmi_pmic_arb_driver_init+0x0/0x20 returned 0 after 638 usecs
[    1.479772] initcall net_olddevs_init+0x0/0x90 returned 0 after 135 usecs
[    1.481010] initcall mdiomux_iproc_driver_init+0x0/0x20 returned 0 after 1159 usecs
[    1.481822] initcall mdio_mux_mmioreg_driver_init+0x0/0x20 returned 0 after 559 usecs
[    1.483010] initcall xgene_mdio_driver_init+0x0/0x20 returned 0 after 886 usecs
[    1.485688] initcall fixed_mdio_bus_init+0x0/0xe0 returned 0 after 2564 usecs
[    1.493407] initcall phy_module_init+0x0/0x24 returned 0 after 7482 usecs
[    1.493757] initcall phy_module_init+0x0/0x24 returned 0 after 289 usecs
[    1.496330] initcall tun_init+0x0/0xd0 returned 0 after 2454 usecs
[    1.506304] initcall virtio_net_driver_init+0x0/0xb8 returned 0 after 9666 usecs
[    1.507837] initcall xgbe_mod_init+0x0/0x2c returned 0 after 1430 usecs
[    1.508372] initcall xgene_enet_driver_init+0x0/0x20 returned 0 after 470 usecs
[    1.509588] initcall macb_driver_init+0x0/0x20 returned 0 after 1137 usecs
[    1.510060] initcall bgmac_enet_driver_init+0x0/0x20 returned 0 after 411 usecs
[    1.510527] initcall hns_mdio_driver_init+0x0/0x20 returned 0 after 407 usecs
[    1.511005] initcall g_dsaf_driver_init+0x0/0x20 returned 0 after 359 usecs
[    1.511574] initcall hns_nic_dev_driver_init+0x0/0x20 returned 0 after 485 usecs
[    1.512543] initcall e1000_init_module+0x0/0x48 returned 0 after 893 usecs
[    1.520800] initcall igb_init_module+0x0/0x60 returned 0 after 7982 usecs
[    1.522971] initcall igbvf_init_module+0x0/0x5c returned 0 after 2054 usecs
[    1.523578] initcall orion_mdio_driver_init+0x0/0x20 returned 0 after 539 usecs
[    1.524163] initcall mvneta_driver_init+0x0/0xc0 returned 0 after 515 usecs
[    1.524649] initcall mvpp2_driver_init+0x0/0x20 returned 0 after 412 usecs
[    1.530106] initcall sky2_init_module+0x0/0x34 returned 0 after 5234 usecs
[    1.530877] initcall ravb_driver_init+0x0/0x20 returned 0 after 673 usecs
[    1.531788] initcall smc_driver_init+0x0/0x20 returned 0 after 753 usecs
[    1.532662] initcall smsc911x_init_module+0x0/0x20 returned 0 after 740 usecs
[    1.532736] initcall netif_init+0x0/0x78 returned -19 after 13 usecs
[    1.534867] initcall vfio_init+0x0/0x184 returned 0 after 2031 usecs
[    1.540978] initcall vfio_virqfd_init+0x0/0x50 returned 0 after 5896 usecs
[    1.541117] initcall vfio_iommu_type1_init+0x0/0x1c returned 0 after 81 usecs
[    1.542418] initcall vfio_pci_init+0x0/0x140 returned 0 after 1221 usecs
[    1.543172] initcall msm_otg_driver_init+0x0/0x20 returned 0 after 601 usecs
[    1.544077] initcall phy_8x16_driver_init+0x0/0x20 returned 0 after 782 usecs
[    1.544774] initcall dwc3_driver_init+0x0/0x20 returned 0 after 599 usecs
[    1.547131] initcall dwc3_exynos_driver_init+0x0/0x20 returned 0 after 2168 usecs
[    1.547468] initcall dwc3_pci_driver_init+0x0/0x28 returned 0 after 263 usecs
[    1.548097] initcall dwc3_of_simple_driver_init+0x0/0x20 returned 0 after 528 usecs
[    1.548906] initcall dwc2_platform_driver_init+0x0/0x20 returned 0 after 738 usecs
[    1.551572] initcall isp1760_init+0x0/0x68 returned 0 after 2535 usecs
[    1.552055] initcall ehci_hcd_init+0x0/0x70 returned 0 after 416 usecs
[    1.552631] initcall ehci_pci_init+0x0/0x88 returned 0 after 516 usecs
[    1.555084] initcall ehci_platform_init+0x0/0x60 returned 0 after 2330 usecs
[    1.555834] initcall ehci_orion_init+0x0/0x5c returned 0 after 677 usecs
[    1.556731] initcall ehci_exynos_init+0x0/0x5c returned 0 after 822 usecs
[    1.558700] initcall ehci_msm_init+0x0/0x5c returned 0 after 1854 usecs
[    1.559441] initcall ohci_hcd_mod_init+0x0/0x84 returned 0 after 596 usecs
[    1.560067] initcall ohci_pci_init+0x0/0x88 returned 0 after 560 usecs
[    1.560932] initcall ohci_platform_init+0x0/0x60 returned 0 after 795 usecs
[    1.566367] initcall ohci_exynos_init+0x0/0x5c returned 0 after 5234 usecs
[    1.566438] initcall xhci_hcd_init+0x0/0x20 returned 0 after 21 usecs
[    1.566834] initcall xhci_pci_init+0x0/0x5c returned 0 after 343 usecs
[    1.567610] initcall xhci_plat_init+0x0/0x34 returned 0 after 706 usecs
[    1.568097] initcall tegra_xusb_init+0x0/0x34 returned 0 after 421 usecs
[    1.568888] initcall usb_storage_driver_init+0x0/0x48 returned 0 after 721 usecs
[    1.579523] initcall usb3503_init+0x0/0x68 returned 0 after 1214 usecs
[    1.579901] initcall ci_hdrc_platform_register+0x0/0x28 returned 0 after 309 usecs
[    1.580325] initcall ci_hdrc_usb2_driver_init+0x0/0x20 returned 0 after 364 usecs
[    1.580751] initcall ci_hdrc_msm_driver_init+0x0/0x20 returned 0 after 366 usecs
[    1.581173] initcall ci_hdrc_zevio_driver_init+0x0/0x20 returned 0 after 362 usecs
[    1.582138] initcall ci_hdrc_pci_driver_init+0x0/0x28 returned 0 after 887 usecs
[    1.582876] initcall usbmisc_imx_driver_init+0x0/0x20 returned 0 after 669 usecs
[    1.583561] initcall ci_hdrc_imx_driver_init+0x0/0x20 returned 0 after 609 usecs
[    1.584318] initcall tegra_udc_driver_init+0x0/0x20 returned 0 after 614 usecs
[    1.585080] initcall udc_plat_driver_init+0x0/0x20 returned 0 after 660 usecs
[    1.592688] initcall bdc_driver_init+0x0/0x20 returned 0 after 7347 usecs
[    1.593717] initcall bdc_pci_driver_init+0x0/0x28 returned 0 after 935 usecs
[    1.593886] initcall ambakmi_driver_init+0x0/0x1c returned 0 after 114 usecs
[    1.593944] initcall input_leds_init+0x0/0x1c returned 0 after 19 usecs
[    1.593990] initcall evdev_init+0x0/0x1c returned 0 after 10 usecs
[    1.594579] initcall atkbd_init+0x0/0x34 returned 0 after 517 usecs
[    1.595613] initcall cros_ec_keyb_driver_init+0x0/0x20 returned 0 after 942 usecs
[    1.596091] initcall psmouse_init+0x0/0xa8 returned 0 after 413 usecs
[    1.596683] initcall hi65xx_powerkey_driver_init+0x0/0x20 returned 0 after 524 usecs
[    1.597103] initcall pm8941_pwrkey_driver_init+0x0/0x20 returned 0 after 357 usecs
[    1.597157] initcall xenkbd_init+0x0/0x50 returned -19 after 12 usecs
[    1.598769] initcall rtc_init+0x0/0x48 returned 0 after 1463 usecs
[    1.599877] initcall brcmstb_waketmr_driver_init+0x0/0x20 returned 0 after 965 usecs
[    1.600253] initcall ds323x_init+0x0/0x78 returned 0 after 291 usecs
[    1.600790] initcall efi_rtc_driver_init+0x0/0x28 returned -19 after 467 usecs
[    1.601123] initcall max77686_rtc_driver_init+0x0/0x20 returned 0 after 273 usecs
[    1.617394] initcall pl031_driver_init+0x0/0x1c returned 0 after 15816 usecs
[    1.618749] initcall s3c_rtc_driver_init+0x0/0x20 returned 0 after 1208 usecs
[    1.619188] initcall s5m_rtc_driver_init+0x0/0x20 returned 0 after 353 usecs
[    1.619647] initcall sun6i_rtc_driver_init+0x0/0x20 returned 0 after 388 usecs
[    1.620226] initcall tegra_rtc_driver_init+0x0/0x28 returned -19 after 503 usecs
[    1.620638] initcall xgene_rtc_driver_init+0x0/0x20 returned 0 after 351 usecs
[    1.622204] initcall i2c_dev_init+0x0/0xdc returned 0 after 1437 usecs
[    1.622724] initcall bcm_iproc_i2c_driver_init+0x0/0x20 returned 0 after 444 usecs
[    1.623288] initcall exynos5_i2c_driver_init+0x0/0x20 returned 0 after 489 usecs
[    1.623871] initcall meson_i2c_driver_init+0x0/0x20 returned 0 after 499 usecs
[    1.624817] initcall mv64xxx_i2c_driver_init+0x0/0x20 returned 0 after 826 usecs
[    1.627232] initcall qup_i2c_driver_init+0x0/0x20 returned 0 after 2245 usecs
[    1.627967] initcall rk3x_i2c_driver_init+0x0/0x20 returned 0 after 648 usecs
[    1.628374] initcall tegra_bpmp_i2c_driver_init+0x0/0x20 returned 0 after 344 usecs
[    1.628810] initcall uniphier_fi2c_drv_init+0x0/0x20 returned 0 after 372 usecs
[    1.630138] initcall rcar_i2c_driver_init+0x0/0x20 returned 0 after 1238 usecs
[    1.630594] initcall zx2967_i2c_driver_init+0x0/0x20 returned 0 after 392 usecs
[    1.631083] initcall brcmstb_i2c_driver_init+0x0/0x20 returned 0 after 425 usecs
[    1.631561] initcall ec_i2c_tunnel_driver_init+0x0/0x20 returned 0 after 414 usecs
[    1.631735] initcall pca954x_driver_init+0x0/0x20 returned 0 after 124 usecs
[    1.632174] initcall ptp_dte_driver_init+0x0/0x20 returned 0 after 379 usecs
[    1.632614] initcall msm_restart_init+0x0/0x20 returned 0 after 377 usecs
[    1.634403] initcall vexpress_reset_init+0x0/0x20 returned 0 after 1679 usecs
[    1.634837] initcall xgene_reboot_init+0x0/0x20 returned 0 after 366 usecs
[    1.635295] initcall syscon_reboot_driver_init+0x0/0x20 returned 0 after 355 usecs
[    1.635802] initcall syscon_reboot_mode_driver_init+0x0/0x20 returned 0 after 405 usecs
[    1.635993] initcall bq27xxx_battery_i2c_driver_init+0x0/0x20 returned 0 after 132 usecs
[    1.636697] initcall scpi_hwmon_platdrv_init+0x0/0x20 returned 0 after 631 usecs
[    1.638775] initcall exynos_tmu_driver_init+0x0/0x20 returned 0 after 1948 usecs
[    1.639429] initcall hisi_thermal_driver_init+0x0/0x20 returned 0 after 566 usecs
[    1.639933] initcall mtk_thermal_driver_init+0x0/0x20 returned 0 after 436 usecs
[    1.640496] initcall s3c2410wdt_driver_init+0x0/0x20 returned 0 after 497 usecs
[    1.645954] initcall bcm2835_wdt_driver_init+0x0/0x20 returned 0 after 5256 usecs
[    1.646588] initcall rwdt_driver_init+0x0/0x20 returned 0 after 529 usecs
[    1.647008] initcall uniphier_wdt_driver_init+0x0/0x20 returned 0 after 355 usecs
[    1.647387] initcall dt_cpufreq_platdrv_init+0x0/0x20 returned 0 after 275 usecs
[    1.647532] initcall cpufreq_dt_platdev_init+0x0/0xdc returned -19 after 98 usecs
[    1.648018] initcall brcm_avs_cpufreq_platdrv_init+0x0/0x20 returned 0 after 364 usecs
[    1.648358] initcall scpi_cpufreq_platdrv_init+0x0/0x20 returned 0 after 277 usecs
[    1.648489] initcall tegra_cpufreq_init+0x0/0xb0 returned -2 after 84 usecs
[    1.648572] initcall tegra_cpufreq_init+0x0/0x94 returned -19 after 42 usecs
[    1.648769] initcall arm_idle_init+0x0/0x1ec returned -19 after 150 usecs
[    1.660251] initcall mmc_pwrseq_simple_driver_init+0x0/0x20 returned 0 after 11123 usecs
[    1.668572] initcall mmc_pwrseq_emmc_driver_init+0x0/0x20 returned 0 after 7957 usecs
[    1.670498] initcall mmc_blk_init+0x0/0xa0 returned 0 after 1737 usecs
[    1.670709] initcall mmci_driver_init+0x0/0x1c returned 0 after 140 usecs
[    1.671503] initcall sdhci_drv_init+0x0/0x2c returned 0 after 732 usecs
[    1.671901] initcall sdhci_acpi_driver_init+0x0/0x20 returned 0 after 340 usecs
[    1.672040] initcall mmc_spi_driver_init+0x0/0x20 returned 0 after 90 usecs
[    1.672528] initcall renesas_internal_dmac_sdhi_driver_init+0x0/0x20 returned 0 after 427 usecs
[    1.672933] initcall dw_mci_init+0x0/0x20 returned 0 after 347 usecs
[    1.675560] initcall dw_mci_pltfm_driver_init+0x0/0x20 returned 0 after 858 usecs
[    1.676362] initcall dw_mci_exynos_pltfm_driver_init+0x0/0x20 returned 0 after 653 usecs
[    1.677170] initcall dw_mci_k3_pltfm_driver_init+0x0/0x20 returned 0 after 705 usecs
[    1.678732] initcall dw_mci_rockchip_pltfm_driver_init+0x0/0x20 returned 0 after 1455 usecs
[    1.679471] initcall meson_mmc_driver_init+0x0/0x20 returned 0 after 644 usecs
[    1.680429] initcall sunxi_mmc_driver_init+0x0/0x20 returned 0 after 852 usecs
[    1.680892] initcall bcm2835_driver_init+0x0/0x20 returned 0 after 398 usecs
[    1.682911] initcall sdhci_pltfm_drv_init+0x0/0x20 returned 0 after 1911 usecs
[    1.683659] initcall sdhci_cdns_driver_init+0x0/0x20 returned 0 after 668 usecs
[    1.684486] initcall sdhci_tegra_driver_init+0x0/0x20 returned 0 after 748 usecs
[    1.685846] initcall sdhci_arasan_driver_init+0x0/0x20 returned 0 after 1275 usecs
[    1.686419] initcall sdhci_esdhc_driver_init+0x0/0x20 returned 0 after 508 usecs
[    1.686971] initcall sdhci_iproc_driver_init+0x0/0x20 returned 0 after 490 usecs
[    1.687407] initcall sdhci_msm_driver_init+0x0/0x20 returned 0 after 378 usecs
[    1.687868] initcall sdhci_brcmstb_driver_init+0x0/0x20 returned 0 after 403 usecs
[    1.688581] initcall sdhci_xenon_driver_init+0x0/0x20 returned 0 after 628 usecs
[    1.693735] initcall gpio_led_driver_init+0x0/0x20 returned 0 after 4956 usecs
[    1.694320] initcall led_pwm_driver_init+0x0/0x20 returned 0 after 512 usecs
[    1.694776] initcall syscon_led_driver_init+0x0/0x20 returned 0 after 395 usecs
[    1.694889] initcall heartbeat_trig_init+0x0/0x4c returned 0 after 68 usecs
[    1.700264] initcall ledtrig_cpu_init+0x0/0x10c returned 0 after 5171 usecs
[    1.700345] initcall defon_trig_init+0x0/0x1c returned 0 after 24 usecs
[    1.703122] initcall scpi_driver_init+0x0/0x20 returned 0 after 2604 usecs
[    1.704221] initcall scpi_power_domain_driver_init+0x0/0x20 returned 0 after 942 usecs
[    1.704979] initcall rpi_firmware_driver_init+0x0/0x20 returned 0 after 661 usecs
[    1.710427] initcall meson_sm_init+0x0/0xc8 returned -19 after 116 usecs
[    1.710569] initcall esrt_sysfs_init+0x0/0x2a0 returned -38 after 56 usecs
[    1.710615] initcall efi_capsule_loader_init+0x0/0x58 returned -19 after 10 usecs
[    1.711094] initcall hid_init+0x0/0x64 returned 0 after 422 usecs
[    1.711363] initcall hid_generic_init+0x0/0x28 returned 0 after 215 usecs
[    1.711673] initcall a4_driver_init+0x0/0x28 returned 0 after 235 usecs
[    1.712047] initcall apple_driver_init+0x0/0x28 returned 0 after 294 usecs
[    1.712317] initcall belkin_driver_init+0x0/0x28 returned 0 after 193 usecs
[    1.712631] initcall ch_driver_init+0x0/0x28 returned 0 after 237 usecs
[    1.712824] initcall ch_driver_init+0x0/0x28 returned 0 after 129 usecs
[    1.713172] initcall cp_driver_init+0x0/0x28 returned 0 after 292 usecs
[    1.714330] initcall ez_driver_init+0x0/0x28 returned 0 after 1051 usecs
[    1.714769] initcall ite_driver_init+0x0/0x28 returned 0 after 322 usecs
[    1.715046] initcall ks_driver_init+0x0/0x28 returned 0 after 214 usecs
[    1.715267] initcall lg_driver_init+0x0/0x28 returned 0 after 163 usecs
[    1.715508] initcall ms_driver_init+0x0/0x28 returned 0 after 186 usecs
[    1.715708] initcall mr_driver_init+0x0/0x28 returned 0 after 145 usecs
[    1.716800] initcall hid_init+0x0/0x6c returned 0 after 1010 usecs
[    1.717872] initcall arm_mhu_driver_init+0x0/0x1c returned 0 after 994 usecs
[    1.718543] initcall platform_mhu_driver_init+0x0/0x20 returned 0 after 584 usecs
[    1.719424] initcall bcm2835_mbox_driver_init+0x0/0x20 returned 0 after 751 usecs
[    1.720295] initcall flexrm_mbox_driver_init+0x0/0x20 returned 0 after 705 usecs
[    1.720536] initcall extcon_class_init+0x0/0x28 returned 0 after 174 usecs
[    1.721918] initcall usb_extcon_driver_init+0x0/0x20 returned 0 after 1296 usecs
[    1.722652] initcall exynos_adc_driver_init+0x0/0x20 returned 0 after 662 usecs
[    1.723453] initcall meson_sar_adc_driver_init+0x0/0x20 returned 0 after 695 usecs
[    1.723973] initcall register_l2_cache_pmu_driver+0x0/0x50 returned 0 after 429 usecs
[    1.724359] initcall register_qcom_l3_cache_pmu_driver+0x0/0x50 returned 0 after 322 usecs
[    1.724894] initcall bcm_otpc_driver_init+0x0/0x20 returned 0 after 469 usecs
[    1.724983] initcall optee_driver_init+0x0/0x498 returned -19 after 43 usecs
[    1.727296] initcall alsa_timer_init+0x0/0x1c4 returned 0 after 2193 usecs
[    1.727742] initcall alsa_pcm_init+0x0/0x7c returned 0 after 379 usecs
[    1.734073] initcall snd_soc_init+0x0/0x100 returned 0 after 6110 usecs
[    1.734534] initcall asoc_simple_card_init+0x0/0x20 returned 0 after 376 usecs
[    1.735102] initcall sock_diag_init+0x0/0x4c returned 0 after 480 usecs
[    1.735167] initcall gre_offload_init+0x0/0x5c returned 0 after 24 usecs
[    1.735534] initcall sysctl_ipv4_init+0x0/0x5c returned 0 after 318 usecs
[    1.735656] initcall xfrm4_beet_init+0x0/0x20 returned 0 after 81 usecs
[    1.735707] initcall xfrm4_transport_init+0x0/0x20 returned 0 after 17 usecs
[    1.735751] initcall xfrm4_mode_tunnel_init+0x0/0x20 returned 0 after 9 usecs
[    1.735930] initcall inet_diag_init+0x0/0x98 returned 0 after 139 usecs
[    1.736015] initcall tcp_diag_init+0x0/0x1c returned 0 after 49 usecs
[    1.736091] initcall cubictcp_register+0x0/0x60 returned 0 after 41 usecs
[    1.753228] initcall packet_init+0x0/0x58 returned 0 after 16634 usecs
[    1.756392] initcall init_rpcsec_gss+0x0/0x88 returned 0 after 2924 usecs
[    1.758522] initcall init_p9+0x0/0x28 returned 0 after 1949 usecs
[    1.759400] initcall p9_virtio_init+0x0/0x40 returned 0 after 716 usecs
[    1.760137] initcall init_dns_resolver+0x0/0x108 returned 0 after 641 usecs

 

6 레벨: device_initcall_sync()

[    1.760203] initcall arm_smmu_legacy_bus_init+0x0/0x30 returned 0 after 23 usecs

 

7 레벨: late_initcall()

[    1.760773] initcall sys_reg_genericv8_init+0x0/0x6c returned 0 after 37 usecs
[    1.760822] initcall xen_pm_init+0x0/0xd4 returned -19 after 15 usecs
[    1.760880] initcall init_oops_id+0x0/0x40 returned 0 after 20 usecs
[    1.764204] initcall pm_qos_power_init+0x0/0xcc returned 0 after 3108 usecs
[    1.764341] initcall pm_debugfs_init+0x0/0x34 returned 0 after 64 usecs
[    1.764448] initcall printk_late_init+0x0/0x130 returned 0 after 66 usecs
[    1.764524] initcall tk_debug_sleep_time_init+0x0/0x4c returned 0 after 36 usecs
[    1.766003] initcall taskstats_init+0x0/0x4c returned 0 after 1395 usecs
[    1.766130] initcall fault_around_debugfs+0x0/0x44 returned 0 after 73 usecs
[    1.766174] initcall max_swapfiles_check+0x0/0x8 returned 0 after 4 usecs
[    1.766305] initcall split_huge_pages_debugfs+0x0/0x48 returned 0 after 90 usecs
[    1.766378] initcall check_early_ioremap_leak+0x0/0x5c returned 0 after 35 usecs
[    1.768147] initcall init_root_keyring+0x0/0x14 returned 0 after 1672 usecs
[    1.768324] initcall prandom_reseed+0x0/0x40 returned 0 after 124 usecs
[    1.768424] initcall pci_resource_alignment_sysfs_init+0x0/0x28 returned 0 after 54 usecs
[    1.771184] initcall pci_sysfs_init+0x0/0x60 returned 0 after 2629 usecs
[    1.771262] initcall bert_init+0x0/0x21c returned 0 after 27 usecs
[    1.771765] initcall clk_debug_init+0x0/0x14c returned 0 after 447 usecs
[    1.771855] initcall boot_wait_for_devices+0x0/0x28 returned 0 after 46 usecs
[    1.772122] initcall deferred_probe_initcall+0x0/0x48 returned 0 after 212 usecs
[    1.772248] initcall pm_genpd_debug_init+0x0/0x17c returned 0 after 77 usecs
[    1.772311] initcall genpd_power_off_unused+0x0/0x94 returned 0 after 28 usecs
[    1.775732] initcall gpio_keys_init+0x0/0x20 returned 0 after 3271 usecs
[    1.778098] initcall rtc_hctosys+0x0/0xe4 returned 0 after 2222 usecs
[    1.778189] initcall register_update_efi_random_seed+0x0/0x38 returned 0 after 36 usecs
[    1.778236] initcall efi_shutdown_init+0x0/0x54 returned -19 after 9 usecs
[    1.778530] initcall of_fdt_raw_init+0x0/0x7c returned 0 after 248 usecs
[    1.778648] initcall tcp_congestion_default+0x0/0x1c returned 0 after 69 usecs
[    1.778849] initcall ip_auto_config+0x0/0xebc returned 0 after 115 usecs

 

7 레벨: late_initcall_sync()

[    1.778931] initcall software_resume+0x0/0x298 returned -2 after 38 usecs
[    1.778976] initcall fb_logo_late_init+0x0/0x14 returned 0 after 6 usecs
[    1.779135] initcall clk_disable_unused+0x0/0x138 returned 0 after 115 usecs
[    1.779236] initcall regulator_init_complete+0x0/0x70 returned 0 after 56 usecs
[    1.779790] initcall alsa_sound_last_init+0x0/0x80 returned 0 after 497 usecs
[    2.441747] initcall inet6_init+0x0/0x350 [ipv6] returned 0 after 11985 usecs
[    2.452314] initcall xt_init+0x0/0x1000 [x_tables] returned 0 after 167 usecs
[    2.462849] initcall ip_tables_init+0x0/0x1000 [ip_tables] returned 0 after 3393 usecs
[   40.729837] initcall cpu_feature_match_PMULL_init+0x0/0x1000 [crct10dif_ce] returned 0 after 100664 usecs
[   40.868079] initcall crc32_pmull_mod_init+0x0/0x1000 [crc32_ce] returned 0 after 67230 usecs

 

참고