| /* |
| * Copyright (C) 2012 The Chromium OS Authors |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #define pr_fmt(fmt) "chromeos_vbc_blk: " fmt |
| |
| #include <linux/ide.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/string.h> |
| |
| #include "chromeos.h" |
| |
| static struct { |
| bool initialized; |
| phandle phandle; |
| u64 lba; |
| u16 offset, size; |
| } config; |
| |
| static int match_of_node(struct device *dev, void *data) |
| { |
| return dev->of_node == data; |
| } |
| |
| static struct block_device *vbc_blk_get_device(phandle phandle, fmode_t devmode) |
| { |
| struct device_node *dn; |
| struct device *dev; |
| |
| dn = of_find_node_by_phandle(phandle); |
| if (!dn) |
| return ERR_PTR(-ENODEV); |
| |
| dev = bus_find_device(&platform_bus_type, NULL, dn, match_of_node); |
| if (!dev) |
| return ERR_PTR(-ENODEV); |
| |
| /* |
| * TODO(chrome-os-partner:16441): Search block_device from the dev |
| * struct we just found instead of hard-coding major and minor here. |
| */ |
| return blkdev_get_by_dev(MKDEV(MMC_BLOCK_MAJOR, 0), devmode, NULL); |
| } |
| |
| static void vbc_blk_endio(struct bio *bio, int err) |
| { |
| struct completion *c = bio->bi_private; |
| bio->bi_private = (void *)err; |
| complete(c); |
| } |
| |
| static void vbc_blk_submit_bio(struct bio *bio, int rq) |
| { |
| DECLARE_COMPLETION_ONSTACK(wait); |
| |
| bio->bi_end_io = vbc_blk_endio; |
| bio->bi_private = &wait; |
| submit_bio(rq, bio); |
| wait_for_completion(&wait); |
| } |
| |
| static int vbc_blk_access(struct page *page, sector_t sector, bool is_read) |
| { |
| struct block_device *bdev; |
| struct bio *bio; |
| int err, rq; |
| fmode_t devmode = is_read ? FMODE_READ : FMODE_WRITE; |
| |
| bdev = vbc_blk_get_device(config.phandle, devmode); |
| if (IS_ERR(bdev)) { |
| pr_err("could not open block dev\n"); |
| return PTR_ERR(bdev); |
| } |
| |
| /* map the sector to page */ |
| bio = bio_alloc(GFP_NOIO, 1); |
| if (!bio) { |
| err = -ENOMEM; |
| goto unwind_bdev; |
| } |
| bio->bi_bdev = bdev; |
| bio->bi_sector = sector; |
| bio->bi_vcnt = 1; |
| bio->bi_idx = 0; |
| bio->bi_size = SECTOR_SIZE; |
| bio->bi_io_vec[0].bv_page = page; |
| bio->bi_io_vec[0].bv_len = SECTOR_SIZE; |
| bio->bi_io_vec[0].bv_offset = 0; |
| |
| /* submit bio */ |
| rq = REQ_SYNC | REQ_SOFTBARRIER | REQ_NOIDLE; |
| if (!is_read) |
| rq |= REQ_WRITE; |
| |
| vbc_blk_submit_bio(bio, rq); |
| |
| /* vbc_blk_endio passes up any error in bi_private */ |
| err = (int)bio->bi_private; |
| bio_put(bio); |
| |
| unwind_bdev: |
| if (!is_read) { |
| fsync_bdev(bdev); |
| invalidate_bdev(bdev); |
| } |
| blkdev_put(bdev, devmode); |
| |
| return err; |
| } |
| |
| /* |
| * This function accepts a buffer with exactly config.size bytes. It reads the |
| * appropriate mmc one sector block, extacts the nonvolatile data from there |
| * and copies it to the provided buffer. |
| */ |
| static int vbc_blk_read(u8 *data) |
| { |
| struct page *page; |
| char *virtual_addr; |
| int ret; |
| |
| page = alloc_page(GFP_NOIO); |
| if (!page) { |
| pr_err("page allocation failed\n"); |
| return -ENOMEM; |
| } |
| |
| virtual_addr = page_address(page); |
| if (!virtual_addr) { |
| pr_err("page not mapped!\n"); |
| __free_page(page); |
| return -EFAULT; |
| } |
| |
| ret = vbc_blk_access(page, config.lba, 1); |
| if (ret) |
| goto out; |
| |
| memcpy(data, virtual_addr + config.offset, config.size); |
| ret = config.size; |
| |
| out: |
| __free_page(page); |
| return ret; |
| } |
| |
| /* |
| * This function accepts a buffer with exactly config.size bytes. It reads the |
| * appropriate mmc one sector block, blends in the new vboot context contents |
| * and then writes the sector back. |
| */ |
| static int vbc_blk_write(const u8 *data) |
| { |
| struct page *page; |
| char *virtual_addr; |
| int ret; |
| |
| page = alloc_page(GFP_NOIO); |
| if (!page) { |
| pr_err("page allocation failed\n"); |
| return -ENOMEM; |
| } |
| |
| virtual_addr = page_address(page); |
| if (!virtual_addr) { |
| pr_err("page not mapped!\n"); |
| __free_page(page); |
| return -EFAULT; |
| } |
| |
| ret = vbc_blk_access(page, config.lba, 1); |
| if (ret) |
| goto out; |
| |
| /* |
| * Sector has been read, lets blend in vboot context data and write |
| * the sector back. |
| */ |
| memcpy(virtual_addr + config.offset, data, config.size); |
| |
| ret = vbc_blk_access(page, config.lba, 0); |
| if (!ret) |
| ret = config.size; |
| |
| out: |
| __free_page(page); |
| return ret; |
| } |
| |
| static int __init vbc_blk_init(struct device_node *fw_dn) |
| { |
| int err; |
| u32 prop[4]; |
| |
| err = of_property_read_u32_array(fw_dn, "chromeos-vbc-blk", prop, |
| sizeof(prop) / sizeof(prop[0])); |
| if (err) { |
| pr_err("missing chromeos-vbc-blk property\n"); |
| goto err; |
| } |
| |
| config.phandle = prop[0]; |
| config.lba = prop[1]; |
| config.offset = prop[2]; |
| config.size = prop[3]; |
| |
| if ((config.offset + config.size > SECTOR_SIZE) || |
| (config.size > MAX_VBOOT_CONTEXT_BUFFER_SIZE)) { |
| /* vboot context block won't fit into a sector */ |
| pr_err("bad vboot context location: %d:%d!\n", |
| config.offset, config.size); |
| err = -EINVAL; |
| goto err; |
| } |
| |
| config.initialized = true; |
| err = 0; |
| err: |
| return err; |
| } |
| |
| static ssize_t chromeos_vbc_blk_read(void *buf, size_t count) |
| { |
| if (!config.initialized) { |
| pr_err("not initialized\n"); |
| return -ENODEV; |
| } |
| |
| if (count < config.size) { |
| pr_err("not enough room to read vboot context (%zd < %d)\n", |
| count, config.size); |
| return -ENOSPC; |
| } |
| |
| return vbc_blk_read(buf); |
| } |
| |
| static ssize_t chromeos_vbc_blk_write(const void *buf, size_t count) |
| { |
| if (!config.initialized) { |
| pr_err("not initialized\n"); |
| return -ENODEV; |
| } |
| |
| if (count != config.size) { |
| pr_err("wrong write buffer size (%zd != %d)\n", |
| count, config.size); |
| return -ENOSPC; |
| } |
| |
| return vbc_blk_write(buf); |
| } |
| |
| static struct chromeos_vbc chromeos_vbc_blk = { |
| .name = "chromeos_vbc_blk", |
| .read = chromeos_vbc_blk_read, |
| .write = chromeos_vbc_blk_write, |
| }; |
| |
| static int __init chromeos_vbc_blk_init(void) |
| { |
| struct device_node *of_node; |
| const char *vbc_type; |
| int err; |
| |
| of_node = of_find_compatible_node(NULL, NULL, "chromeos-firmware"); |
| if (!of_node) |
| return -ENODEV; |
| |
| err = of_property_read_string(of_node, "nonvolatile-context-storage", |
| &vbc_type); |
| if (err) |
| goto exit; |
| |
| if (strcmp(vbc_type, "disk")) { |
| err = 0; /* not configured to use vbc_blk, exit normally. */ |
| goto exit; |
| } |
| |
| err = vbc_blk_init(of_node); |
| if (err < 0) |
| goto exit; |
| |
| err = chromeos_vbc_register(&chromeos_vbc_blk); |
| if (err < 0) |
| goto exit; |
| |
| err = 0; |
| exit: |
| of_node_put(of_node); |
| return err; |
| } |
| module_init(chromeos_vbc_blk_init); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("ChromeOS vboot context on block device accessor"); |