eMMC read/write C API 구현
eMMC read/write를 위한 C 언어용 API를 구현하기
회사에서 eMMC를 사용하는 보드에서 eMMC에 일반적으로 사용하는 EXT4 파일 시스템을 사용하는 것 이외에도, 파일 시스템이 없는 파티션에 read/write를 raw access 해야 할 일이 생겼다.
특히 eMMC는 NAND booting을 지원하는데, 이 부트 파티션에 raw write를 하는 경우에는 디폴트로 read-only 프로텍션이 걸려있어서 바로 되지는 않았고, 추가로 read-only 프로텍션을 해제해야 하는 과정이 필요하였다.
참고로 아래의 모든 테스트는 eMMC가 장착된 타겟 보드에서 수행하였다.
커널 eMMC 정보 출력
Linux 커널에서 제공하는 eMMC 정보는 아래 예와 같이 얻을 수 있다.
$ sudo cat /sys/kernel/debug/mmc0/ios
결과로 내 타겟 보드에서는 아래와 같이 출력되었다.
clock: 200000000 Hz
actual clock: 200000000 Hz
vdd: 21 (3.3 ~ 3.4 V)
bus mode: 2 (push-pull)
chip select: 0 (don't care)
power mode: 2 (on)
bus width: 3 (8 bits)
timing spec: 9 (mmc HS200)
signal voltage: 1 (1.80 V)
driver type: 0 (driver type B)
eMMC 파티션
gdisk
나, 아래 예와 같이 sgdisk
툴을 사용하여 파티션 정보를 얻을 수 있다.
- 전체 파티션 정보 출력 예
$ sgdisk --print /dev/mmcblk0
- 파티션 정보 출력 예 (아래에서는 10번 파티션)
$ sudo sgdisk --info 10 /dev/mmcblk0
- 파티션 삭제 예 (아래에서는 10번 파티션)
$ sudo sgdisk --delete 10 /dev/mmcblk0
eMMC raw access
- eMMC는
boot partition
,RPMB
(Replay Protection Memory Block),user data
영역을 포함한다. - 일반적인 파티션에 raw 엑세스를 하려면 /dev/mmcblkX 디바이스 노드로 할 수 있다.
- Boot 부트 파티션의 디바이스 노드 경로는 /dev/mmcblkXbootY 형태이다.
- 그런데 부트 파티션은 데이터 파티션과는 다르게 디폴트로 read-only 상태이다. 즉, 아래와 같이 read-only 상태임을 알 수 있다. (재부팅시마다 자동으로 이 상태가 됨)
$ sudo cat /sys/block/mmcblk1boot0/force_ro 1
- 따라서 부트 파티션에 write 하려면 먼저 아래와 같이 0을 쓰서 read-only를 disable 시킨 후에 write를 해야 한다. (그렇지 않으면 write permission 에러가 발생함)
$ sudo echo 0 > /sys/block/mmcblk1boot0/force_ro
- Read-only 상태를 다시 enable 하려면 다시 1을 쓰거나, 재부팅하면 다시 read-only 상태가 된다.
C로 구현하기
이제 방법을 알았으므로, 아래와 같이 간단히 C 함수로 구현할 수 있었다. (참고로 아래 코드에서는 시스템에 eMMC는 1개만 있는 것으로 간주하였고, 의도적으로 모든 주석은 제거하였고, 파티션 크기 초과 여부는 검사하지 않았음)
- 파티션 타입 정의 및 read-only 세팅 함수
typedef enum { eMMC_PART_TYPE_BOOT, eMMC_PART_TYPE_DATA, eMMC_PART_TYPE_RPMB } eMMC_PART_TYPE; static int emmc_set_read_only(int part_num, bool set) { int fd; char file_name[40]; char enable[] = "1", disable[] = "0", *data; snprintf(file_name, sizeof(file_name), "/sys/block/mmcblk1boot%d/force_ro", part_num); if ((fd = open(file_name, O_WRONLY)) == -1) { return -1; } data = set ? enable : disable; if (write(fd, data, 1) == -1) { close(fd); return -1; } close(fd); return 0; }
- Read 함수
int emmc_read(eMMC_PART_TYPE part_type, int part_num, void *data, int offset, size_t len) { int fd; char file_name[20]; unsigned char *read_buf = (unsigned char *)data; if (part_type == eMMC_PART_TYPE_BOOT) { snprintf(file_name, sizeof(file_name), "/dev/mmcblk1boot%d", part_num); } else if (part_type == eMMC_PART_TYPE_DATA) { snprintf(file_name, sizeof(file_name), "/dev/mmcblk1p%d", part_num); } else { return -1; } if ((fd = open(file_name, O_RDONLY)) == -1) { return -1; } if (lseek(fd, offset, SEEK_SET) == -1) { close(fd); return -1; } if (read(fd, read_buf, len) == -1) { close(fd); return -1; } close(fd); return 0; }
- Write 함수
int emmc_write(eMMC_PART_TYPE part_type, int part_num, const void *data, int offset, size_t len) { int fd; char file_name[20]; if (part_type == eMMC_PART_TYPE_BOOT) { snprintf(file_name, sizeof(file_name), "/dev/mmcblk1boot%d", part_num); } else if (part_type == eMMC_PART_TYPE_DATA) { snprintf(file_name, sizeof(file_name), "/dev/mmcblk1p%d", part_num); } else { return -1; } if ((fd = open(file_name, O_RDWR)) == -1) { return -1; } if (lseek(fd, offset, SEEK_SET) == -1) { close(fd); return -1; } if (part_type == eMMC_PART_TYPE_BOOT) { emmc_set_read_only(part_num, false); } if (write(fd, data, len) == -1) { close(fd); return -1; } close(fd); if (part_type == eMMC_PART_TYPE_BOOT) { emmc_set_read_only(part_num, true); } return 0; }
결론
eMMC는 controller에서 자체적으로 bad block 처리를 해 주어서, NAND FLASH를 raw access 하기가 너무나 간단하였고, 위와 같이 간단히 read/write API를 만들어 두니깐 아주 쉽게 access 할 수 있었다.
참고로 RPMB 파티션에 대한 read/write는 필요가 없어서 해보지 않았고, 위의 함수에서도 이 경우에는 그냥 에러로 리턴하게 하였다.
소스를 보면 간단하지만, 이것도 아는 사람에게는 간단한 것이지만 모르는 이에겐 난감한 상황에서 도움을 받을 수 있는 것이기에 이렇게 간단히 공유해 본다.