OS <-> Device “Controller” communication

  • MMIO 는 device 의 address space 를 host 의 physical memory space 와 kernel (혹은 device driver) 의 virtual memory address 에 매핑하는 것이다.
  • 이렇게 매핑한 이후에는 virtual address 에 접근하는 방식으로 device controller 의 address 에 접근하게 된다.
    • 즉, 이렇게 함으로써 kernel 과 device controller 간의 high-level 통신 채널이 생기는 것.

원리

이후의 내용 + 용어는 PCIe 기반입니당.

Initiate

  • 컴퓨터의 전원이 들어오면, 부팅 과정에서 BIOS (혹은 OS) 가 부착되어 있는 PCI 장비들을 검색한다.
  • 이때 PCI 장비는 BAR 레지스터에 host 의 원하는 physical memory size 를 명시하여 BIOS (혹은 OS) 에게 요청한다.
    • 보통은 그냥 전부 1 로 채워 얼만큼 매핑할지도 BIOS (혹은 OS) 가 결정하게 한다고 한다.
  • 그럼 BIOS (혹은 OS) 는 매핑할 physical memory space 를 선정한 후, 이곳의 시작 주소를 BAR 에 채워넣는다.
    • 그럼 이제 BAR 에는 mapping 될 physical memory space 의 (1) 시작주소와 (2) 크기가 저장되는 셈이다.
  • 이후에 OS 는 이 BAR 에 저장된 physical memory address 를 이용해 kernel 혹은 device driver 의 virtual memory space 에 매핑한다.

눈으로 확인해보기

  • 이렇게 해서 매핑된 MMIO 영역은 lspci 명령어로 확인할 수 있다.
sudo lspci -v

  • 저기 Memory at ... 부분이 그거임

Access

               CPU                  CPU                  Bus
             Virtual              Physical             Address
             Address              Address               Space
              Space                Space

            +-------+             +------+             +------+
            |       |             |MMIO  |   Offset    |      |
            |       |  Virtual    |Space |   applied   |      |
          C +-------+ --------> B +------+ ----------> +------+ A
            |       |  mapping    |      |   by host   |      |
  +-----+   |       |   (MMU)     |      |   bridge    |      |   +--------+
  |     |   |       |             +------+             |      |   |        |
  | CPU |   |       |             | RAM  |             |      |   | Device |
  |     |   |       |             |      |             |      |   |        |
  +-----+   +-------+             +------+             +------+   +--------+

출처: kernel.org

  • 어떤 device driver 가 virtual memory address 로 MMIO 에 접근하려 한다고 해보자.
  • 그럼 일단 이 주소는 MMU 에 의해 physical memory address 로 바뀔 것이다.
  • 그리고 이 주소는 PCI controller 에 의해 bus address 로 바뀐다.
    • bus virtual addressIO address 혹은 device virtual address 라고도 불리고, device 내부적인 주소 체계라고 이해하면 된다.
    • 바꾸는 과정을 좀 더 구체적으로 설명하면,
      • PCI controller 는 BAR 를 읽어 base physical memory address 를 알아낸다.
      • 그리고 requested physical address 에 저 값을 빼면 offset 이 나올 테고, 그것이 bus address 인 것.

C API

ioremap()

  • 일단 기본적으로 ioremap() 함수는 MMIO 된 physical space 의 시작 주소에 대해 page table entry 를 만들어 virtual address 를 반환해 준다.

memremap()

void *memremap(resource_size_t offset, size_t size, unsigned long flags);
  • 근데 ioremap() 은 좀 문제가 있다고 한다.
    • 가령 device memory 에 접근할 때 caching 을 하는 것이 좋을 때도 있고 안좋을 때도 있는데 (x86 기준) caching 을 무조건 비활성화 하는 등.
  • 따라서 좀 더 좋은 wrapper 함수가 공개되었는데, 그것이 memremap() 이다.
    • ioremap() 과 기능은 동일하다; physical address 를 받아서 그에 맞는 virtual address 를 생성하여 반환한다.

__iomem annotation

  • ioremap() 이나 memremap() 이 반환해준 주소는 device 를 참조하기 떄문에 바로 dereference 하는 것은 별로 좋지 않다고 한다.
  • 따라서 이것을 dereference 할 때는 ioread32() 와 같은 함수를 통하는 것이 안전한데,
  • 이것을 강제하기 위한 annotation 이 바로 저 __iomem 이다.
    • 이건 Sparse 라는 sementic checker 를 위한 것으로, 저 annotation 을 넣지 않은 상태로 주소를 담을 변수를 선언한 뒤 sparse 를 돌리면 경고가 뜬다.
  • 즉, 아래의 두 경우는 잘못된 사용법이다.
void *io = ioremap(42, 4);
u32 __iomem* io = ioremap(42, 4);
pr_info("%x\n", *io);
  • 이렇게 해야 맞다는 것.
void __iomem* io = ioremap(42, 4);
pr_info("%x\n", ioread32(io));