| 1 | |
| 2 | |
| 3 | * Copyright (C) 2021 - Google LLC
|
| 4 | * Author: David Brazdil <[email protected]>
|
| 5 | * Author: Song Guo <[email protected]>
|
| 6 | *
|
| 7 | * Driver for Open Profile for DICE.
|
| 8 | *
|
| 9 | * This driver takes ownership of a reserved memory region containing data
|
| 10 | * generated by the Open Profile for DICE measured boot protocol. The memory
|
| 11 | * contents are not interpreted by the kernel but can be mapped into a userspace
|
| 12 | * process via a misc device. Userspace can also request a wipe of the memory.
|
| 13 | *
|
| 14 | * Userspace can access the data with (w/o error handling):
|
| 15 | *
|
| 16 | * fd = open("/dev/open-dice0", O_RDWR);
|
| 17 | * read(fd, &size, sizeof(unsigned long));
|
| 18 | * data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
| 19 | * write(fd, NULL, 0); // wipe
|
| 20 | * close(fd);
|
| 21 |
|
| 22 |
|
| 23 | #include <linux/acpi.h>
|
| 24 | #include <linux/io.h>
|
| 25 | #include <linux/miscdevice.h>
|
| 26 | #include <linux/mm.h>
|
| 27 | #include <linux/module.h>
|
| 28 | #include <linux/of_reserved_mem.h>
|
| 29 | #include <linux/platform_device.h>
|
| 30 |
|
| 31 | #define DRIVER_NAME "open-dice"
|
| 32 |
|
| 33 | struct open_dice_drvdata {
|
| 34 | struct mutex lock;
|
| 35 | char name[16];
|
| 36 | phys_addr_t mem_base;
|
| 37 | resource_size_t mem_size;
|
| 38 | struct miscdevice misc;
|
| 39 | };
|
| 40 |
|
| 41 | static inline struct open_dice_drvdata *to_open_dice_drvdata(struct file *filp)
|
| 42 | {
|
| 43 | return container_of(filp->private_data, struct open_dice_drvdata, misc);
|
| 44 | }
|
| 45 |
|
| 46 | static int open_dice_wipe(struct open_dice_drvdata *drvdata)
|
| 47 | {
|
| 48 | void *kaddr;
|
| 49 |
|
| 50 | mutex_lock(&drvdata->lock);
|
| 51 | kaddr = devm_memremap(drvdata->misc.this_device, drvdata->mem_base,
|
| 52 | drvdata->mem_size, MEMREMAP_WC);
|
| 53 | if (IS_ERR(kaddr)) {
|
| 54 | mutex_unlock(&drvdata->lock);
|
| 55 | return PTR_ERR(kaddr);
|
| 56 | }
|
| 57 |
|
| 58 | memset(kaddr, 0, drvdata->mem_size);
|
| 59 | devm_memunmap(drvdata->misc.this_device, kaddr);
|
| 60 | mutex_unlock(&drvdata->lock);
|
| 61 | return 0;
|
| 62 | }
|
| 63 |
|
| 64 | |
| 65 | * Copies the size of the reserved memory region to the user-provided buffer.
|
| 66 |
|
| 67 | static ssize_t open_dice_read(struct file *filp, char __user *ptr, size_t len,
|
| 68 | loff_t *off)
|
| 69 | {
|
| 70 | unsigned long val = to_open_dice_drvdata(filp)->mem_size;
|
| 71 |
|
| 72 | return simple_read_from_buffer(ptr, len, off, &val, sizeof(val));
|
| 73 | }
|
| 74 |
|
| 75 | |
| 76 | * Triggers a wipe of the reserved memory region. The user-provided pointer
|
| 77 | * is never dereferenced.
|
| 78 |
|
| 79 | static ssize_t open_dice_write(struct file *filp, const char __user *ptr,
|
| 80 | size_t len, loff_t *off)
|
| 81 | {
|
| 82 | if (open_dice_wipe(to_open_dice_drvdata(filp)))
|
| 83 | return -EIO;
|
| 84 |
|
| 85 |
|
| 86 | return len;
|
| 87 | }
|
| 88 |
|
| 89 | |
| 90 | * Creates a mapping of the reserved memory region in user address space.
|
| 91 |
|
| 92 | static int open_dice_mmap_prepare(struct vm_area_desc *desc)
|
| 93 | {
|
| 94 | struct file *filp = desc->file;
|
| 95 | struct open_dice_drvdata *drvdata = to_open_dice_drvdata(filp);
|
| 96 |
|
| 97 | if (vma_desc_test(desc, VMA_MAYSHARE_BIT)) {
|
| 98 |
|
| 99 | if (vma_desc_test(desc, VMA_WRITE_BIT))
|
| 100 | return -EPERM;
|
| 101 |
|
| 102 | vma_desc_clear_flags(desc, VMA_MAYWRITE_BIT);
|
| 103 | }
|
| 104 |
|
| 105 |
|
| 106 | desc->page_prot = pgprot_writecombine(desc->page_prot);
|
| 107 | vma_desc_set_flags(desc, VMA_DONTCOPY_BIT, VMA_DONTDUMP_BIT);
|
| 108 | mmap_action_simple_ioremap(desc, drvdata->mem_base,
|
| 109 | drvdata->mem_size);
|
| 110 | return 0;
|
| 111 | }
|
| 112 |
|
| 113 | static const struct file_operations open_dice_fops = {
|
| 114 | .owner = THIS_MODULE,
|
| 115 | .read = open_dice_read,
|
| 116 | .write = open_dice_write,
|
| 117 | .mmap_prepare = open_dice_mmap_prepare,
|
| 118 | };
|
| 119 |
|
| 120 | static int __init open_dice_probe(struct platform_device *pdev)
|
| 121 | {
|
| 122 | static unsigned int dev_idx;
|
| 123 | struct device *dev = &pdev->dev;
|
| 124 | struct open_dice_drvdata *drvdata;
|
| 125 | phys_addr_t mem_base;
|
| 126 | resource_size_t mem_size;
|
| 127 | int ret;
|
| 128 |
|
| 129 | if (dev->of_node) {
|
| 130 | struct reserved_mem *rmem = of_reserved_mem_lookup(dev->of_node);
|
| 131 |
|
| 132 | if (!rmem) {
|
| 133 | dev_err(dev, "failed to lookup reserved memory\n");
|
| 134 | return -EINVAL;
|
| 135 | }
|
| 136 | mem_base = rmem->base;
|
| 137 | mem_size = rmem->size;
|
| 138 | } else if (is_acpi_node(dev->fwnode)) {
|
| 139 | struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
| 140 |
|
| 141 | if (!res) {
|
| 142 | dev_err(dev, "failed to get MMIO resource\n");
|
| 143 | return -EINVAL;
|
| 144 | }
|
| 145 | mem_base = res->start;
|
| 146 | mem_size = resource_size(res);
|
| 147 | } else {
|
| 148 | dev_err(dev, "device not supported (no DT or ACPI node)\n");
|
| 149 | return -EINVAL;
|
| 150 | }
|
| 151 |
|
| 152 | if (!mem_size || (mem_size > ULONG_MAX)) {
|
| 153 | dev_err(dev, "invalid memory region size\n");
|
| 154 | return -EINVAL;
|
| 155 | }
|
| 156 |
|
| 157 | if (!PAGE_ALIGNED(mem_base) || !PAGE_ALIGNED(mem_size)) {
|
| 158 | dev_err(dev, "memory region must be page-aligned\n");
|
| 159 | return -EINVAL;
|
| 160 | }
|
| 161 |
|
| 162 | drvdata = devm_kmalloc(dev, sizeof(*drvdata), GFP_KERNEL);
|
| 163 | if (!drvdata)
|
| 164 | return -ENOMEM;
|
| 165 |
|
| 166 | *drvdata = (struct open_dice_drvdata){
|
| 167 | .mem_base = mem_base,
|
| 168 | .mem_size = mem_size,
|
| 169 | .misc = (struct miscdevice){
|
| 170 | .parent = dev,
|
| 171 | .name = drvdata->name,
|
| 172 | .minor = MISC_DYNAMIC_MINOR,
|
| 173 | .fops = &open_dice_fops,
|
| 174 | .mode = 0600,
|
| 175 | },
|
| 176 | };
|
| 177 | mutex_init(&drvdata->lock);
|
| 178 |
|
| 179 |
|
| 180 | snprintf(drvdata->name, sizeof(drvdata->name), DRIVER_NAME"%u", dev_idx++);
|
| 181 |
|
| 182 | ret = misc_register(&drvdata->misc);
|
| 183 | if (ret) {
|
| 184 | dev_err(dev, "failed to register misc device '%s': %d\n",
|
| 185 | drvdata->name, ret);
|
| 186 | return ret;
|
| 187 | }
|
| 188 |
|
| 189 | platform_set_drvdata(pdev, drvdata);
|
| 190 | return 0;
|
| 191 | }
|
| 192 |
|
| 193 | static void open_dice_remove(struct platform_device *pdev)
|
| 194 | {
|
| 195 | struct open_dice_drvdata *drvdata = platform_get_drvdata(pdev);
|
| 196 |
|
| 197 | misc_deregister(&drvdata->misc);
|
| 198 | }
|
| 199 |
|
| 200 | static const struct of_device_id open_dice_of_match[] = {
|
| 201 | { .compatible = "google,open-dice" },
|
| 202 | { }
|
| 203 | };
|
| 204 |
|
| 205 | static struct platform_driver open_dice_driver = {
|
| 206 | .remove = open_dice_remove,
|
| 207 | .driver = {
|
| 208 | .name = DRIVER_NAME,
|
| 209 | .of_match_table = open_dice_of_match,
|
| 210 | },
|
| 211 | };
|
| 212 |
|
| 213 | static int __init open_dice_init(void)
|
| 214 | {
|
| 215 | int ret = platform_driver_probe(&open_dice_driver, open_dice_probe);
|
| 216 |
|
| 217 |
|
| 218 | return (ret == -ENODEV) ? 0 : ret;
|
| 219 | }
|
| 220 |
|
| 221 | static void __exit open_dice_exit(void)
|
| 222 | {
|
| 223 | platform_driver_unregister(&open_dice_driver);
|
| 224 | }
|
| 225 |
|
| 226 | module_init(open_dice_init);
|
| 227 | module_exit(open_dice_exit);
|
| 228 |
|
| 229 | MODULE_DESCRIPTION("Driver for Open Profile for DICE.");
|
| 230 | MODULE_LICENSE("GPL v2");
|
| 231 | MODULE_AUTHOR("David Brazdil <[email protected]>");
|
| 232 | MODULE_AUTHOR("Song Guo <[email protected]>");
|