/* * Copyright (C) 2017 Google. * * This file is released under the GPLv2. * * Based on drivers/md/dm-verity-chromeos.c */ #include #include #include #include #include #define DM_MSG_PREFIX "verity-avb" #define APBDEV_PMC_SCRATCH37_0 0x130 #define SCRATCH_AVB_EIO_FLAG BIT(26) /* Set via module parameters. */ static char avb_vbmeta_device[64]; static char avb_invalidate_on_error[4]; static char avb_veritymode_managed[4]; static void invalidate_vbmeta_endio(struct bio *bio) { if (bio->bi_error) DMERR("invalidate_vbmeta_endio: error %d", bio->bi_error); complete(bio->bi_private); } static int invalidate_vbmeta_submit(struct bio *bio, struct block_device *bdev, int op, int access_last_sector, struct page *page) { DECLARE_COMPLETION_ONSTACK(wait); bio->bi_private = &wait; bio->bi_end_io = invalidate_vbmeta_endio; bio->bi_bdev = bdev; bio_set_op_attrs(bio, op, REQ_SYNC | REQ_NOIDLE); bio->bi_iter.bi_sector = 0; if (access_last_sector) { sector_t last_sector; last_sector = (i_size_read(bdev->bd_inode)>>SECTOR_SHIFT) - 1; bio->bi_iter.bi_sector = last_sector; } if (!bio_add_page(bio, page, PAGE_SIZE, 0)) { DMERR("invalidate_vbmeta_submit: bio_add_page error"); return -EIO; } submit_bio(bio); /* Wait up to 2 seconds for completion or fail. */ if (!wait_for_completion_timeout(&wait, msecs_to_jiffies(2000))) return -EIO; return 0; } static int invalidate_vbmeta(dev_t vbmeta_devt) { int ret = 0; struct block_device *bdev; struct bio *bio; struct page *page; fmode_t dev_mode; /* Ensure we do synchronous unblocked I/O. We may also need * sync_bdev() on completion, but it really shouldn't. */ int access_last_sector = 0; DMINFO("invalidate_vbmeta: acting on device %d:%d", MAJOR(vbmeta_devt), MINOR(vbmeta_devt)); /* First we open the device for reading. */ dev_mode = FMODE_READ | FMODE_EXCL; bdev = blkdev_get_by_dev(vbmeta_devt, dev_mode, invalidate_vbmeta); if (IS_ERR(bdev)) { DMERR("invalidate_kernel: could not open device for reading"); dev_mode = 0; ret = -ENOENT; goto failed_to_read; } bio = bio_alloc(GFP_NOIO, 1); if (!bio) { ret = -ENOMEM; goto failed_bio_alloc; } page = alloc_page(GFP_NOIO); if (!page) { ret = -ENOMEM; goto failed_to_alloc_page; } access_last_sector = 0; ret = invalidate_vbmeta_submit(bio, bdev, REQ_OP_READ, access_last_sector, page); if (ret) { DMERR("invalidate_vbmeta: error reading"); goto failed_to_submit_read; } /* We have a page. Let's make sure it looks right. */ if (memcmp("AVB0", page_address(page), 4) == 0) { /* Stamp it. */ memcpy(page_address(page), "AVE0", 4); DMINFO("invalidate_vbmeta: found vbmeta partition"); } else { /* Could be this is on a AVB footer, check. Also, since the * AVB footer is in the last 64 bytes, adjust for the fact that * we're dealing with 512-byte sectors. */ size_t offset = (1<bi_remaining. */ bio_reset(bio); ret = invalidate_vbmeta_submit(bio, bdev, REQ_OP_WRITE, access_last_sector, page); if (ret) { DMERR("invalidate_vbmeta: error writing"); goto failed_to_submit_write; } DMERR("invalidate_vbmeta: completed."); ret = 0; failed_to_submit_write: failed_to_write: invalid_header: __free_page(page); failed_to_submit_read: /* Technically, we'll leak a page with the pending bio, but * we're about to reboot anyway. */ failed_to_alloc_page: bio_put(bio); failed_bio_alloc: if (dev_mode) blkdev_put(bdev, dev_mode); failed_to_read: return ret; } void dm_verity_avb_error_handler(void) { dev_t dev; u32 pmc_reg_val = 0; DMINFO("AVB error handler called for %s", avb_vbmeta_device); if (tegra_chip_get_revision() == TEGRA210B01_REVISION_A01) { if (strcmp(avb_veritymode_managed, "yes") != 0) { DMINFO("EIO mode is set"); pmc_reg_val = tegra_pmc_readl(APBDEV_PMC_SCRATCH37_0); tegra_pmc_writel((pmc_reg_val | SCRATCH_AVB_EIO_FLAG), APBDEV_PMC_SCRATCH37_0); } } if (strcmp(avb_invalidate_on_error, "yes") != 0) { DMINFO("Not configured to invalidate"); return; } if (avb_vbmeta_device[0] == '\0') { DMERR("avb_vbmeta_device parameter not set"); goto fail_no_dev; } dev = name_to_dev_t(avb_vbmeta_device); if (!dev) { DMERR("No matching partition for device: %s", avb_vbmeta_device); goto fail_no_dev; } invalidate_vbmeta(dev); fail_no_dev: ; } static int __init dm_verity_avb_init(void) { DMINFO("AVB error handler initialized with vbmeta device: %s", avb_vbmeta_device); return 0; } static void __exit dm_verity_avb_exit(void) { } module_init(dm_verity_avb_init); module_exit(dm_verity_avb_exit); MODULE_AUTHOR("David Zeuthen "); MODULE_DESCRIPTION("AVB-specific error handler for dm-verity"); MODULE_LICENSE("GPL"); /* Declare parameter with no module prefix */ #undef MODULE_PARAM_PREFIX #define MODULE_PARAM_PREFIX "androidboot.vbmeta." module_param_string(device, avb_vbmeta_device, sizeof(avb_vbmeta_device), 0); module_param_string(invalidate_on_error, avb_invalidate_on_error, sizeof(avb_invalidate_on_error), 0); /* query androidboot.veritymode.managed for EIO mode */ #undef MODULE_PARAM_PREFIX #define MODULE_PARAM_PREFIX "androidboot.veritymode." module_param_string(managed, avb_veritymode_managed, sizeof(avb_veritymode_managed), 0);