关键词:eMMC boot、PARTITION_CONFIG、force_ro等。
1. eMMC的分区
大部分eMMC都有类似如下的分区,其中BOOT、RPMB和UDA一般是默认存在的,gpp分区需要手动创建。

BOOT主要是为了支持从eMMC启动系统而设计的;RPMB即Replay Protected Memory Block简称,通常用来保存安全线管的数据;GPP主要用于存储系统或者用户数据。
UDA通常会进行再分区,然后根据不同目的存放相关数据,或者格式化成不同文件系统。
2. Linux下读写boot分区
因为boot分区中一般存放的是bootloader或者相关配置参数,这些参数一般是不允许修改的,所以默认情况下是能读boot分区,不能写。
2.1 使能读写
如果需要些则需要,修改/sys/block/mmcblk0boot1/force_ro。
使能写:
echo 0 > /sys/block/mmcblk0boot1/force_ro
关闭写:
echo 1 > /sys/block/mmcblk0boot1/force_ro
在重启之后,force_ro会恢复为1。
2.2 内核force_ro实现
下面来看看force_ro是如何起作用的?
eMMC在被初始化的时候,调用mmc_blk_probe(),这里面会在每个设备下创建force_ro sysfs节点。
1static int mmc_blk_probe(struct mmc_card *card) 2{ 3... 4 if (mmc_add_disk(md)) 5 goto out; 6... 7} 8 9static int mmc_add_disk(struct mmc_blk_data *md) 10{ 11 int ret; 12 struct mmc_card *card = md->queue.card; 13 14 device_add_disk(md->parent, md->disk); 15 md->force_ro.show = force_ro_show; 16 md->force_ro.store = force_ro_store;----------------------------------------------设置分区是否只读,0可读写;1只读。 17 sysfs_attr_init(&md->force_ro.attr); 18 md->force_ro.attr.name = "force_ro"; 19 md->force_ro.attr.mode = S_IRUGO | S_IWUSR; 20 ret = device_create_file(disk_to_dev(md->disk), &md->force_ro); 21 if (ret) 22 goto force_ro_fail; 23 24 if ((md->area_type & MMC_BLK_DATA_AREA_BOOT) && 25 card->ext_csd.boot_ro_lockable) { 26 umode_t mode; 27 28 if (card->ext_csd.boot_ro_lock & EXT_CSD_BOOT_WP_B_PWR_WP_DIS) 29 mode = S_IRUGO; 30 else 31 mode = S_IRUGO | S_IWUSR; 32 33 md->power_ro_lock.show = power_ro_lock_show; 34 md->power_ro_lock.store = power_ro_lock_store; 35 sysfs_attr_init(&md->power_ro_lock.attr); 36 md->power_ro_lock.attr.mode = mode; 37 md->power_ro_lock.attr.name = 38 "ro_lock_until_next_power_on"; 39 ret = device_create_file(disk_to_dev(md->disk), 40 &md->power_ro_lock); 41 if (ret) 42 goto power_ro_lock_fail; 43 } 44 return ret; 45 46power_ro_lock_fail: 47 device_remove_file(disk_to_dev(md->disk), &md->force_ro); 48force_ro_fail: 49 del_gendisk(md->disk); 50 51 return ret; 52} 53static ssize_t force_ro_show(struct device *dev, struct device_attribute *attr, 54 char *buf) 55{ 56 int ret; 57 struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); 58 59 ret = snprintf(buf, PAGE_SIZE, "%d\n", 60 get_disk_ro(dev_to_disk(dev)) ^ 61 md->read_only); 62 mmc_blk_put(md); 63 return ret; 64} 65 66static ssize_t force_ro_store(struct device *dev, struct device_attribute *attr, 67 const char *buf, size_t count) 68{ 69 int ret; 70 char *end; 71 struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); 72 unsigned long set = simple_strtoul(buf, &end, 0); 73 if (end == buf) { 74 ret = -EINVAL; 75 goto out; 76 } 77 78 set_disk_ro(dev_to_disk(dev), set || md->read_only); 79 ret = count; 80out: 81 mmc_blk_put(md); 82 return ret; 83}
2.3 读写boot分区操作
在force_ro为1的情况下,写boot分区返回Operation not permitted。
1echo updt | dd of=/dev/mmcblk0boot1 bs=4 count=1 seek=0 && sync 2dd: writing '/dev/mmcblk0boot1': Operation not permitted 31+0 records in 40+0 records out
然后打开force_ro=0:
echo 0 > /sys/block/mmcblk0boot1/force_ro && echo updt | dd of=/dev/mmcblk0boot1 bs=4 count=1 seek=0 && sync
通过hexdump验证一下:
1hexdump -v -n 4 -s 0 /dev/mmcblk0boot1 20000000 7075 7464 30000004
3. uboot下读写boot分区
uboot下操作boot分区需要打开CONFIG_SUPPORT_EMMC_BOOT。
在Linux下/dev/mmcblk0boot1就表示切换到boot分区了,在uboot下需要先切换到boot分区。
3.1 PARTITION_CONFIG寄存器
由于默认分区是UDA,而eMMC每个分区都是独立编址的。所以要使用boot分区需要切换分区。
PARTITION_CONFIG寄存器,通过EXT_CSD_PART_CONF命令来设置。
根据下面的寄存解释,BOOT_ACK设置为0x0,;BOOT_PARTITION_ENABLE设置为0x2;PARTITION_ACCESS设置为0x2。


3.2 读取boot分区
uboot中读取boot分区,首先需要将分区切换到boot分区,然后读写分区,最后将分区切换回原来分区。
1static int do_mmc_bootmode(cmd_tbl_t *cmdtp, int flag, 2 int argc, char * const argv[]) 3{ 4 struct mmc *mmc; 5 int ret = BOOTMODE_NORMAL; 6 u32 blk, cnt, n; 7 void *addr; 8 char original_part; 9 10 addr = (void *)malloc(512); 11 blk = BOOTMODE_BLK_NUM; 12 cnt = BOOTMODE_BLK_COUNT; 13 14 mmc = init_mmc_device(curr_device, false); 15 if (!mmc) 16 { 17 free(addr); 18 return CMD_RET_FAILURE; 19 } 20 21 /* Switch to the Boot 2 partition */ 22 original_part = mmc->block_dev.hwpart; 23 blk_select_hwpart_devnum(IF_TYPE_MMC, curr_device, MMC_PART_BOOT2); 24 mmc_set_part_conf(mmc, 0, MMC_PART_BOOT2, 2);------------------------------------------切换到eMMC boot1分区。 25 n = blk_dread(mmc_get_blk_desc(mmc), blk, cnt, addr);----------------------------------读取一个block。 26 if(n != cnt) 27 { 28 free(addr); 29 return CMD_RET_FAILURE; 30 } 31 /* flush cache after read */ 32 flush_cache((ulong)addr, cnt * 512); /* FIXME */ 33 34 if(*(unsigned int *)addr == BOOTMODE_UPDATE_MAGIC) 35 { 36 ret = BOOTMODE_UPDATE; 37 } 38 else 39 { 40 ret = BOOTMODE_NORMAL; 41 } 42 43#if 0 44 for(int i = 0; i < 512/16; i++) 45 printf("%08x %08x %08x %08x\n", *((int *)addr+i*4), *((int *)addr+i*4+1), *((int *)addr+i*4+2), *((int *)addr+i*4+3)); 46#endif 47 /* Switch to original partition. */ 48 blk_select_hwpart_devnum(IF_TYPE_MMC, curr_device, original_part);----------------------切换到默认分区。 49 50 free(addr); 51 52 return ret; 53}
至此可以在Linux和Uboot下对boot分区进行操作,进行bootloader烧写或者进行重要数据更新。