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는 필요가 없어서 해보지 않았고, 위의 함수에서도 이 경우에는 그냥 에러로 리턴하게 하였다.
소스를 보면 간단하지만, 이것도 아는 사람에게는 간단한 것이지만 모르는 이에겐 난감한 상황에서 도움을 받을 수 있는 것이기에 이렇게 간단히 공유해 본다.

카테고리:

업데이트: