313 lines
6.8 KiB
C
313 lines
6.8 KiB
C
/*
|
|
* 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");
|