-
[컴퓨터 구조] MMIO공학/컴퓨터구조 2023. 8. 11. 04:09
글의 참고
- https://www.kernel.org/doc/html/v4.18/driver-api/device-io.html
- https://ko.wikipedia.org/wiki/%EB%A9%94%EB%AA%A8%EB%A6%AC_%EB%A7%B5_%EC%9E%85%EC%B6%9C%EB%A0%A5
- http://jidum.com/jidums/view.do?jidumId=467
- https://velog.io/@coral2cola/Cortex-M-Processors-Memory-Systems
- http://www.jkelec.co.kr/img/lecture/cortex_arch/cortex_arch_2.html#1.1
- https://velog.io/@coral2cola/Cortex-M-Processors-Memory-Systems
- https://dhpark1212.tistory.com/entry/ARM-Product-%EA%B4%80%EB%A0%A8-%EB%AC%B8%EC%84%9C
글의 전제
- 내가 글을 쓰다가 궁금한 점은 파란색 볼드체로 표현했다. 나도 모르기 때문에 나중에 알아봐야 할 내용이라는 뜻이다.
- 밑줄로 작성된 글은 좀 더 긴 설명이 필요해서 친 것이다. 그러므로, 밑 줄 처친 글이 이해가 안간다면 링크를 따라서 관련 내용을 공부하자.
글의 내용
- Overview
" 메모리 맵 입출력(Memory-mapped I/O, MMIO)는 CPU`s address space 의 일부를 memory 를 access 하는 용도로 사용하는게 아니라, device 에 access 하는 용도로 사용하는 것을 의미한다(The most widely supported form of IO is memory mapped IO. That is, a part of the CPU’s address space is interpreted not as accesses to memory, but as accesses to a device). 일부 CPU 제조사에서는 특정 device 를 fixed a single physical address 에 mapping 시키는 경우도 있지만, 대개는 MMIO 영역이 사이즈가 꽤 크기 때문에, IOMMU 를 통해서 동적으로 CPU`s physical address space 에 mapping 된다.
" 따라서 전체 메모리의 입출력 장치의 메모리나 레지스터를 메모리로 취급하여 전체 메모리의 일부분으로 특정 영역에 할당하여 배치는 방식이다. 이 구조의 장점은 입출력 장치의 메모리 주소가 메인 메모리에 맵핑된 거기 때문에, 입출력 장치에 액세스할 떄, 결국 메인 메모리에 액세스 하는 것과 동일한 기계어 코드로 수행이 가능하다. 즉, LOAD, STORE 명령어로 입출력 장치들을 컨트롤 할 수 있다는 뜻이다. 각 입출력 장치들은 CPU의 주소 버스를 감시하고 있다가, CPU가 입출력 장치를 위해 할당한 메모리 공간에 접근하면 이에 반응한다. 그리고 데이터 버스를 해당 장치의 하드웨어 레지스터에 연결해준다.
Memory Mapped I/O I/O Mapped I/O 출처 - http://jidum.com/jidums/view.do?jidumId=467
- Memory map
" 8비트 마이크로프로세스(CPU)로 구현된 간단한 시스템이 있다고 하자. CPU는 16비트 주소 라인을 가지고 있어서, 64KByte의 메모리에 접근할 수 있다. 이 말은 메모리의 주소가 0x0000 ~ 0xFFFF 까지 할당이 가능하다는 소리이다. 그치만 그 이상은 CPU가 처리를 못한다. 이러한 시스템에서, 주소 공간의 첫 32Kbyte는 램에 할당되어 있고, 그 다음 16Kbyte는 롬에 할당시킨다고 치자. 그리고 나머지는 타이머나 카운터, 디스플레이 비디오 칩, 소리 구현 장치와 같은 주소들을 위해서 할당되어 있다고 하자. 연결된 장치들은 자신들을 뜻하는 주소에 대해서만 반응하고 다른 주소들은 무시한다. 이런 것은 주소 디코딩 회로가 하게 되고, 이런 식으로 시스템의 메모리 맵이 만들어 진다. 아래는 그 예시다.
" 그리고, memory map 과 memory mapped I/O 는 엄연히 다르다. memory mapped I/O 는 CPU 와 I/O devices 들이 데이터 통신을 위해 사용하는 mechanism 을 의미하고, memory map 은 말 그대로 RAM 을 어떻게 layout 했는지를 의미한다.
장치 주소 범위(16진수) 크기 램 0x0000 ~ 0x7FFF 32 Kbyte 일반 목적 입출력 0x8000 ~ 0x80FF 256 Byte 사운드 컨트롤로 0x9000 ~ 0x90FF 256 Byte 비디오 컨트롤러/텍스트 매핑 디스플레이 램 0xA000 ~ 0xA7FF 2 Kbyte 롬 0xC000 ~ 0xFFFF 16 Kbyte " 메모리 맵 공간에 빈 공간들이 있다는 점에 주목하자. 이제 비디오 컨트롤러의 네 번째 레지스터가 화면의 바탕색을 지정하는데 쓰인다고 하자. CPU는 일반적으로 메모리에 기록하는 명령을 이용해서, 0xA003 위치에 값을 기록하여 배경을 지정할 것이다. 이와 같은 MMIO 방법을 이용해서, 비디오 컨트롤러의 특별한 램 공간에 문자를 써서, 화면에 글자를 표현할 수 있다.
- STM32로 MMIO 예제 살펴보기
- MMIO방식으로 메모리 영역을 나눌 때, 가장 먼저 고려할 점은 몇 비트 CPU 인가이다.
- 8-bit CPU : 0x00 ~ 0xFF ( Byte )
- 16-bit CPU : 0x0000 ~ 0xFFFF ( Kbyte )
- 24-bit CPU : 0x00_0000 ~ 0xFF_FFFF ( Mbyte )
- 32-bit CPU : 0x0000_0000 ~ 0xFFFF_FFFF ( GByte )
- 예를 들어, ARMv7 버전에서 Cortex-M3는 32-bit CPU이다. 그리고 ARM에서 제시하는 Cortex-M3는 MMIO는 다음과 같다. 아래 그림에서 Cortex-M3는 32bit 이기 때문에, 메모리 영역을 4GB까지 사용할 수 있다는 의미다.
- Cortex-M3 프로세서는 4 GB의 addres space를 가지고 있다. 또한 Cortex-M3 프로세서는 fixed memory map을 가지고 있다. fixed memory map이기 때문에 Cortex-M3 product는 NVIC와 MPU가 모두 동일한 memory location을 갖고 있어서 그들 사이의 software porting이 쉽다고 함. 거의 모든 하드웨어가 address를 부여받고 그 address를 통해 하드웨어에 접근할 수 있음.
- Cortex-M3 에서는 CODE 영역(ROM) 과 RAM(SRAM)의 주소가 Architecture 차원에서 정의가 되어 있기 때문에(fixed memory map) Cortex-M3 Core를 기반으로한 CPU(ST, Luminsary, TI, Samsung 사의 Cortex-M3 CPU)들 사이에는 포팅 작업이 많이 쉬워졌다. 이전의 전통적인 ARM에서는 CPU의 메모리 뱅크에 따라서 RAM의 시작주소가 같지 않을수 있고, Peripheral 들의 시작 주소 또한 CPU 마다 다를수 있다. 위의 Memory Map을 보면 32bit Core 이기 때문에 4GB 메모리까지 접근이 가능하고 SRAM, Peripheral 영역의 주소에 특이하게도 Bit band alias 영역이라는 것이 존재한다. 이 부분은 Bit Banding 장에서 자세히 설명 하도록 하겠습니다.
- 아래 그림은 Cortex-M3를 기반으로 한 STM32F103의 MMIO다.
- 위의 그림에서 0번에 해당되는 영역이 Code 영역이다. 즉, 다시말해서 ROM(Flash memory) 영역인 것이다.
- 위의 그림에서 알아두어야 할 것은 0x08000000에서 시작되는 Flash memory 영역과 0x1FFFF000에서 시작되는 System memory 영역이다. Flash 영역은 프로그램을 빌드한 실행 파일이 저장되는 영역이라 할 수 있고, System memory 영역은 bootloader 프로그램이 저장되어 있는 영역이다. 위의 그림에도 표현되어 있는것처럼 0x00000000 번지의 영역은 BOOT 핀의 설정에 따라 Flash memory나 system memory가 보여지도록 되어 있다. BOOT0 핀이 0이면 flash memory 영역이 0번지에 나타나게 되고, BOOT0 핀이 1로 설정이 되면 System memory 영역이 0번지에 보여지게 되는것이다.
- 그런데 중요한 부분이 하니 있다. 아래 그림은 Cortex-M3의 vector table이다.
- reset vector가 0x0000_0004에 있다. 즉, Cortex-M3의 최초 실행은 0x0000_0000 번지에 있는 값을 MSP(Main Stack Pointer)에 넣는것이다. 그리고 나서 0x0000_0004 번지에 있는 주소로 점프한다. 이 주소가 바로 리셋 핸들러가 있는 주소이다.
- 위에서 설명한 내용들을 직접 작성해보자.
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) MEMORY { rom (rx) : ORIGIN = 0x08000000, LENGTH = 64K ram (rw!x) : ORIGIN = 0x20000000, LENGTH = 20K } SECTIONS { .text : { KEEP(*(.vectors.table)) KEEP(*(.vectors.code)) *(.text .text.*) *(.rodata .rodata.*) } > rom . = ALIGN(4); _etext = .; .data : AT (_etext) { _sdata = .; *(.data .data.*) . = ALIGN(4); _edata = .; } > ram .bss (NOLOAD) : { . = ALIGN(4); _sbss = .; *(.bss .bss.*) *(COMMON) . = ALIGN(4); _ebss = .; } > ram . = ALIGN(4); end = .; _ram_end = ORIGIN(ram) + LENGTH(ram) - 0x1; }
- 6번 줄 - ROM은 Flash Memory 영역의 시작 주소와 크기를 표현.
- 7번 줄 - SRAM 영역에 해당되는 주소와 크기를 표현하였다.
- 14, 15번 줄 - reset vector를 ROM 영역의 제일 앞에 놓여지기 위해서 사용하였다.
- 47번 줄 - end는 나중에 heap 영역 설정을 위하여 사용될 예정이다.
- 48번 줄 - _ram_end는 stack의 시작 주소를 알려주기 위하여 사용된다.
- Makefile은 생략한다. 이제 reset handler를 작성해보자.
void Reset_handler(void) __attribute__((section(".vectors.code"))); extern unsigned int _ram_end; const void *exception_table[] __attribute__((section(".vectors.table"))) = { (void*)&_ram_end, (void*)Reset_handler, }; void Reset_handler(void) { extern int main(int, char **); main(0, 0); }
- 4 ~ 8번 줄 - vector table을 만들기 위해서 배열을 사용. 이 배열은 ROM 영역의 시작 부분에 놓여야 하므로, .vector.table 이라는 섹션을 지정해 주고, linker가 linker script 파일을 참조하여 가장 앞 부분에 놓여지도록 해준다.
- 위에 vector table에서 0x0000_0000 번지에 `initial SP value`라고 되어있는데, 그게 바로 Stack pointer 레지스터에 들어갈 메모리 주소를 제공해야 한다. 위의 프로그램에서 _ram_end 심볼이 가리키는 주소를 사용하도록 하였다.
- 위의 파일을 빌드하고 어셈블리 파일을 확인해보면 아래와 같은 내용을 볼 수 있다.
08000000 <exception_table>: 8000000: 20004fff strdcs r4, [r0], -pc ; <UNPREDICTABLE> 8000004: 08000009 stmdaeq r0, {r0, r3} 08000008 <Reset_handler>: 8000008: 2100 movs r1, #0 800000a: 4608 mov r0, r1 800000c: f000 b800 b.w 8000010 <main> 08000010 <main>: 8000010: e7fe b.n 8000010 <main> 8000012: bf00 nop
- 2번줄에 보이는 0x08000000에 SRAM 영역의 마지막 주소인 0x20004fff가 쓰여진것을 볼 수 있다. 이 값이 SP 레지스터에 들어가게 된다. SP 레지스터는 항상 4바이트 align되도록 되어 있기 때문에 실제로는 0x20004ffc가 쓰여지게 될 것이다.
- 3번줄은 Reset_handler 함수의 주소값이 들어가게 된다. 그런데 실제 Reset_handler의 주소는 0x08000008이지만 vector table에는 0x08000009라고 되어있다. 그이유는 함수주소의 마지막 비트값이 1이 되어 있으면 thumb 모드로 전환되도록 하는 의미를 가지고 있다. Cortex-M은 thumb2 명령어 체계를 가지고 있으므로 이러한 현상이 발생하게 되는것이다.
- 6,7번 줄은 Reset_handler에서 main()함수를 부를때 argument로 0,0을 넣어주는 동작이다. 큰 의미는 없다.
- 8번줄은 main() 함수로 점프하는 명령어이다.
- 11번줄은 main() 함수 내에서 무한루프를 수행하는 코드이다.
- 장점 및 단점
- 장점
- MMIO는 포트 입출력을 구현할 때 부수적인 복잡성이 없어지기 때문에, CPU는 내부 로직이 덜 필요하고, 그러므로 더 저렴해지고 더 빠르고 더 쉽게 CPU를 만들 수 있다. 바로 이 점이 RISC가 추구하는 바와 그 노선이 비슷하고, 이런 방식은 임베디드 시스템을 구현할 때 장점으로 작용한다. 메모리 사이즈가 적었던 16비트 CPU가 없어지고, 64비트 구조로 대체되어 감에 따라, 입출력 장치를 메모리의 일부로 사용하는것에 대해 더 이상 문제가 되지 않는다.
- 단점
- MMIO는 주소와 데이터 버스를 무척 많이 사용하게 된다. 그래서 보통 메인 메모리에 접근하는 것보다 매핑된 장치에 접근하는 것이 더 느리다.
: 항상 궁금했다. `MMIO는 어떤 기준으로 할당하는 걸까?`, `이건 최초 부팅 시에 운영 체제에 의해서 메모리 맵이 구축되는 건가?` 등 말이다. 위의 질문으로 해결됬다.
: 통합 개발 칩(SoC 및 Motherboard 등)이 만들어 질 때, 특정 디바이스들은 하드웨어 디자인 단계에서 이미 회로적으로 특정 메모리의 영역으로 연결된다고 한다. 통합된 칩, 즉, 개발단계에서 이미 납땜이 되어 나오는 디바이스들에게는 동적으로 MMIO를 할 필요가 없다. 임베디드에서 PCI 나 USB가 거의 쓰이지 않는 이유를 이제야 알 것 같다.
: 임베디드에서는 PCI나 USB같은 동적으로 디바이스가 붙는 버스들은 거의 사용하지 않는다. 물론 모듈로 나오는 칩들은 PCI로 붙는 경우를 그래도 꽤 봤지만, MCU 레벨에서는 거의 위의 2개의 버스는 사용하는 것을 본적이 없다.
: 임베디드 디바이스에서는 주변 장치들을 대부분 MMIO로 할당한다. 아래 그림은 TM4C123GH6PM ARM Cortex M4 microcontroller의 메모리 맵이라고 한다. 참 빽빽이도 설정해놓은 것 같다. 근데 External Device는 왜 있나 봤더니 TM4C123Gxxx 모델에 USB Controller가 내장되어 있다.
: 아래 보드가 TM4C123Gxxx의 개발 보드 외관 모습이다. USB Connector가 2개 있긴 한데, 하나는 파워이고 하나는 외부 디바이스와 연결할 수 있는 부분인 것 같다. 추측이지만, USB를 통해 외부 디바이스에 MMIO를 할당해야 하면, External Device(1GB) 영역쪽에 USB 디바이스에 대한 MMIO를 할당하지 않을까 추측해본다.
'공학 > 컴퓨터구조' 카테고리의 다른 글
Boot ROM (0) 2023.08.13 [컴퓨터 구조] Cache (0) 2023.08.12 Port-Mapped IO (1) 2023.08.11 [컴퓨터 구조] AHCI (0) 2023.08.10 [컴퓨터 구조] ISA 버스 (0) 2023.08.09 - MMIO방식으로 메모리 영역을 나눌 때, 가장 먼저 고려할 점은 몇 비트 CPU 인가이다.