1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (C) 2020 Nicolas Saenz Julienne <nsaenzjulienne@suse.de> 4 */ 5 6 #include <linux/crc32.h> 7 #include <linux/io.h> 8 #include <linux/module.h> 9 #include <linux/nvmem-provider.h> 10 #include <linux/of_reserved_mem.h> 11 #include <linux/platform_device.h> 12 #include <linux/slab.h> 13 14 struct rmem { 15 struct device *dev; 16 struct nvmem_device *nvmem; 17 struct reserved_mem *mem; 18 }; 19 20 struct rmem_match_data { 21 int (*checksum)(struct rmem *priv); 22 }; 23 24 struct __packed rmem_eyeq5_header { 25 u32 magic; 26 u32 version; 27 u32 size; 28 }; 29 30 #define RMEM_EYEQ5_MAGIC ((u32)0xDABBAD00) 31 32 static int rmem_read(void *context, unsigned int offset, 33 void *val, size_t bytes) 34 { 35 struct rmem *priv = context; 36 void *addr; 37 38 if ((phys_addr_t)offset + bytes > priv->mem->size) 39 return -EIO; 40 41 /* 42 * Only map the reserved memory at this point to avoid potential rogue 43 * kernel threads inadvertently modifying it. Based on the current 44 * uses-cases for this driver, the performance hit isn't a concern. 45 * Nor is likely to be, given the nature of the subsystem. Most nvmem 46 * devices operate over slow buses to begin with. 47 * 48 * An alternative would be setting the memory as RO, set_memory_ro(), 49 * but as of Dec 2020 this isn't possible on arm64. 50 */ 51 addr = memremap(priv->mem->base, priv->mem->size, MEMREMAP_WB); 52 if (!addr) { 53 dev_err(priv->dev, "Failed to remap memory region\n"); 54 return -ENOMEM; 55 } 56 57 memcpy(val, addr + offset, bytes); 58 59 memunmap(addr); 60 61 return 0; 62 } 63 64 static int rmem_eyeq5_checksum(struct rmem *priv) 65 { 66 void *buf __free(kfree) = NULL; 67 struct rmem_eyeq5_header header; 68 u32 computed_crc, *target_crc; 69 size_t data_size; 70 int ret; 71 72 ret = rmem_read(priv, 0, &header, sizeof(header)); 73 if (ret) 74 return ret; 75 76 if (header.magic != RMEM_EYEQ5_MAGIC) 77 return -EINVAL; 78 79 /* 80 * Avoid massive kmalloc() if header read is invalid; 81 * the check would be done by the next rmem_read() anyway. 82 */ 83 if (header.size > priv->mem->size) 84 return -EINVAL; 85 86 /* 87 * 0 +-------------------+ 88 * | Header (12 bytes) | \ 89 * +-------------------+ | 90 * | | | data to be CRCed 91 * | ... | | 92 * | | / 93 * data_size +-------------------+ 94 * | CRC (4 bytes) | 95 * header.size +-------------------+ 96 */ 97 98 buf = kmalloc(header.size, GFP_KERNEL); 99 if (!buf) 100 return -ENOMEM; 101 102 ret = rmem_read(priv, 0, buf, header.size); 103 if (ret) 104 return ret; 105 106 data_size = header.size - sizeof(*target_crc); 107 target_crc = buf + data_size; 108 computed_crc = crc32(U32_MAX, buf, data_size) ^ U32_MAX; 109 110 if (computed_crc == *target_crc) 111 return 0; 112 113 dev_err(priv->dev, 114 "checksum failed: computed %#x, expected %#x, header (%#x, %#x, %#x)\n", 115 computed_crc, *target_crc, header.magic, header.version, header.size); 116 return -EINVAL; 117 } 118 119 static int rmem_probe(struct platform_device *pdev) 120 { 121 struct nvmem_config config = { }; 122 struct device *dev = &pdev->dev; 123 const struct rmem_match_data *match_data = device_get_match_data(dev); 124 struct reserved_mem *mem; 125 struct rmem *priv; 126 127 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 128 if (!priv) 129 return -ENOMEM; 130 priv->dev = dev; 131 132 mem = of_reserved_mem_lookup(dev->of_node); 133 if (!mem) { 134 dev_err(dev, "Failed to lookup reserved memory\n"); 135 return -EINVAL; 136 } 137 priv->mem = mem; 138 139 config.dev = dev; 140 config.priv = priv; 141 config.name = "rmem"; 142 config.id = NVMEM_DEVID_AUTO; 143 config.size = mem->size; 144 config.reg_read = rmem_read; 145 146 if (match_data && match_data->checksum) { 147 int ret = match_data->checksum(priv); 148 149 if (ret) 150 return ret; 151 } 152 153 return PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &config)); 154 } 155 156 static const struct rmem_match_data rmem_eyeq5_match_data = { 157 .checksum = rmem_eyeq5_checksum, 158 }; 159 160 static const struct of_device_id rmem_match[] = { 161 { .compatible = "mobileye,eyeq5-bootloader-config", .data = &rmem_eyeq5_match_data }, 162 { .compatible = "nvmem-rmem", }, 163 { /* sentinel */ }, 164 }; 165 MODULE_DEVICE_TABLE(of, rmem_match); 166 167 static struct platform_driver rmem_driver = { 168 .probe = rmem_probe, 169 .driver = { 170 .name = "rmem", 171 .of_match_table = rmem_match, 172 }, 173 }; 174 module_platform_driver(rmem_driver); 175 176 MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>"); 177 MODULE_DESCRIPTION("Reserved Memory Based nvmem Driver"); 178 MODULE_LICENSE("GPL"); 179