From: Light Hsieh <light.hsieh@xxxxxxxxxxxx> Since MMC layer does not implement BLKROSET ioctl, BLKROSET ioctl that tries to clear ro attribute of an mmcblk device will get -EINVAL from __blkdev_driver_ioctl(). blkdev_roset() regard -EINVAL as "unrecogized ioctl" and then clear the ro attribute un-conditionally. However, when eMMC write-protection (power-on, temporarily write, or permanent) is enabled in some area, this un-conditional clear of ro will lead to issue. From user's view, eMMC device is writable since ro is not set. But write operation sent to eMMC will get write-protection error. Since most write are asynchronus buffered write, such write-protection error won't be delivered to user who send the write operation. This patch implement BLKROSET in MMC layer. 1. For SD device, 0 is retured. 2. For setting ro to eMMC area, 0 is returned without any other check. 2. For clearing ro to eMMC area, write-proetction status is checked: 2a. For boot0 or boot1 partition, boot_wp_status get from EXTCSD is checked. -EACCES is returned when the target boot partition is write-protected; 0 is returned otherwise. 2b. For user area partition, one or more MMC_SEND_WRITE_PROT_TYPE commands are sent to get/check write-protection status of target address range. -EACCES is returned when target address range is fully/partially write-protected; 0 is returned otherwise. With the above implementation and correct ioctl parameters, return value of __blkdev_driver_ioctl() will be 0 or -EACCES. blkdev_roset() can continue to clear ro attribute when return value is 0, which means whole target eMMC address range is not write-protected. Signed-off-by: Light Hsieh <light.hsieh@xxxxxxxxxxxx> --- block/ioctl.c | 2 +- drivers/mmc/core/block.c | 216 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/mmc.h | 1 + 3 files changed, 218 insertions(+), 1 deletion(-) diff --git a/block/ioctl.c b/block/ioctl.c index 127194b..af047a0 100644 --- a/block/ioctl.c +++ b/block/ioctl.c @@ -485,7 +485,7 @@ static int blkdev_roset(struct block_device *bdev, fmode_t mode, return -EACCES; ret = __blkdev_driver_ioctl(bdev, mode, cmd, arg); - if (!is_unrecognized_ioctl(ret)) + if (ret && !is_unrecognized_ioctl(ret)) return ret; if (get_user(n, (int __user *)arg)) return -EFAULT; diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index 663d879..ee85abf 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -778,6 +778,220 @@ static int mmc_blk_check_blkdev(struct block_device *bdev) return 0; } +#define MMC_BLK_NO_WP 0 +#define MMC_BLK_PARTIALLY_WP 1 +#define MMC_BLK_FULLY_WP 2 + +static int mmc_blk_check_disk_range_wp(struct gendisk *disk, + sector_t part_start, sector_t part_nr_sects) +{ + struct mmc_command cmd = {0}; + struct mmc_request mrq = {NULL}; + struct mmc_data data = {0}; + struct mmc_blk_data *md; + struct mmc_card *card; + struct scatterlist sg; + unsigned char *buf = NULL, status; + sector_t start, end, quot; + sector_t wp_grp_rem, wp_grp_total, wp_grp_found, status_query_cnt; + unsigned int remain; + int err = 0, i, j, k; + u8 boot_wp_status = 0; + + md = mmc_blk_get(disk); + if (!md) + return -EINVAL; + + if (!md->queue.card) { + err = -EINVAL; + goto out2; + } + + card = md->queue.card; + if (!mmc_card_mmc(card) || + md->part_type == EXT_CSD_PART_CONFIG_ACC_RPMB) { + err = MMC_BLK_NO_WP; + goto out2; + } + + if (md->part_type == 0) + goto check_user_area_wp_status; + + /* BOOT_WP_STATUS in EXT_CSD: + * |-----bit[7:4]-----|-------bit[3:2]--------|-------bit[1:0]--------| + * |-----reserved-----|----boot1 wp status----|----boot0 wp status----| + * boot0 area wp type:depending on bit[1:0] + * 0->not wp; 1->power on wp; 2->permanent wp; 3:reserved value + * boot1 area wp type:depending on bit[3:2] + * 0->not wp; 1->power on wp; 2->permanent wp; 3:reserved value + */ + if (md->part_type == EXT_CSD_PART_CONFIG_ACC_BOOT0) + boot_wp_status = card->ext_csd.boot_wp_status & 0x3; + else if (md->part_type == (EXT_CSD_PART_CONFIG_ACC_BOOT0 + 1)) + boot_wp_status = (card->ext_csd.boot_wp_status >> 2) & 0x3; + + if (boot_wp_status == 0x1 || boot_wp_status == 0x2) { + pr_notice("%s is fully write protected\n", disk->disk_name); + err = MMC_BLK_FULLY_WP; + } else + err = MMC_BLK_NO_WP; + goto out2; + +check_user_area_wp_status: + if (!card->wp_grp_size) { + pr_notice("Write protect group size cannot be 0!\n"); + err = -EINVAL; + goto out2; + } + + start = part_start; + quot = start; + remain = do_div(quot, card->wp_grp_size); + if (remain) { + pr_notice("Start 0x%llx of disk %s not write group aligned\n", + (unsigned long long)part_start, disk->disk_name); + start -= remain; + } + + end = part_start + part_nr_sects; + quot = end; + remain = do_div(quot, card->wp_grp_size); + if (remain) { + pr_notice("End 0x%llx of disk %s not write group aligned\n", + (unsigned long long)part_start, disk->disk_name); + end += card->wp_grp_size - remain; + } + wp_grp_total = end - start; + do_div(wp_grp_total, card->wp_grp_size); + wp_grp_rem = wp_grp_total; + wp_grp_found = 0; + + cmd.opcode = MMC_SEND_WRITE_PROT_TYPE; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; + + buf = kmalloc(8, GFP_KERNEL); + if (!buf) { + err = -ENOMEM; + goto out2; + } + sg_init_one(&sg, buf, 8); + + data.blksz = 8; + data.blocks = 1; + data.flags = MMC_DATA_READ; + data.sg = &sg; + data.sg_len = 1; + mmc_set_data_timeout(&data, card); + + mrq.cmd = &cmd; + mrq.data = &data; + + mmc_get_card(card, NULL); + + err = mmc_blk_part_switch(card, md->part_type); + if (err) { + err = -EIO; + goto out; + } + + status_query_cnt = (wp_grp_total + 31) / 32; + for (i = 0; i < status_query_cnt; i++) { + cmd.arg = start + i * card->wp_grp_size * 32; + mmc_wait_for_req(card->host, &mrq); + if (cmd.error) { + pr_notice("%s: cmd error %d\n", __func__, cmd.error); + err = -EIO; + goto out; + } + + /* wp status is returned in 8 bytes. + * The 8 bytes are regarded as 64-bits bit-stream: + * +--------+--------+-------------------------+--------+ + * | byte 7 | byte 6 | ... | byte 0 | + * | bits | bits | | bits | + * |76543210|76543210| |76543210| + * +--------+--------+-------------------------+--------+ + * The 2 LSBits represent write-protect group status of + * the lowest address group being queried. + * The 2 MSBits represent write-protect group status of + * the highest address group being queried. + */ + /* Check write-protect group status from lowest address + * group to highest address group + */ + for (j = 0; j < 8; j++) { + status = buf[7 - j]; + for (k = 0; k < 8; k += 2) { + if (status & (3 << k)) + wp_grp_found++; + wp_grp_rem--; + if (!wp_grp_rem) + goto out; + } + } + + memset(buf, 0, 8); + } + +out: + mmc_put_card(card, NULL); + if (!wp_grp_rem) { + if (!wp_grp_found) + err = MMC_BLK_NO_WP; + else if (wp_grp_found == wp_grp_total) { + pr_notice("0x%llx ~ 0x%llx of %s is fully write protected\n", + (unsigned long long)part_start, + (unsigned long long)part_start + part_nr_sects, + disk->disk_name); + err = MMC_BLK_FULLY_WP; + } else { + pr_notice("0x%llx ~ 0x%llx of %s is %u%% write protected\n", + wp_grp_found * 100 / wp_grp_total, + (unsigned long long)part_start, + (unsigned long long)part_start + part_nr_sects, + disk->disk_name); + err = MMC_BLK_PARTIALLY_WP; + } + } + + kfree(buf); + +out2: + mmc_blk_put(md); + return err; +} + +static int mmc_blk_check_wp(struct block_device *bdev) +{ + if (!bdev->bd_disk || !bdev->bd_part) + return -EINVAL; + + return mmc_blk_check_disk_range_wp(bdev->bd_disk, + bdev->bd_part->start_sect, + bdev->bd_part->nr_sects); +} + +static int mmc_blk_ioctl_roset(struct block_device *bdev, + unsigned long arg) +{ + int val; + + /* Always return -EACCES to block layer on any error + * and then block layer will abort the remaining operation + */ + if (get_user(val, (int __user *)arg)) + return -EACCES; + + /* No need to check write-protect status when setting as readonly */ + if (val) + return 0; + + if (mmc_blk_check_wp(bdev) != MMC_BLK_NO_WP) + return -EACCES; + + return 0; +} + static int mmc_blk_ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg) { @@ -809,6 +1023,8 @@ static int mmc_blk_ioctl(struct block_device *bdev, fmode_t mode, NULL); mmc_blk_put(md); return ret; + case BLKROSET: + return mmc_blk_ioctl_roset(bdev, arg); default: return -EINVAL; } diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 2c9d988..f7c1237 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -69,6 +69,7 @@ #define MMC_SET_WRITE_PROT 28 /* ac [31:0] data addr R1b */ #define MMC_CLR_WRITE_PROT 29 /* ac [31:0] data addr R1b */ #define MMC_SEND_WRITE_PROT 30 /* adtc [31:0] wpdata addr R1 */ +#define MMC_SEND_WRITE_PROT_TYPE 31 /* adtc [31:0] wpdata addr R1 */ /* class 5 */ #define MMC_ERASE_GROUP_START 35 /* ac [31:0] data addr R1 */ -- 1.8.1.1.dirty