phram.c (9148B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Copyright (c) ???? Jochen Schäuble <psionic@psionic.de> 4 * Copyright (c) 2003-2004 Joern Engel <joern@wh.fh-wedel.de> 5 * 6 * Usage: 7 * 8 * one commend line parameter per device, each in the form: 9 * phram=<name>,<start>,<len>[,<erasesize>] 10 * <name> may be up to 63 characters. 11 * <start>, <len>, and <erasesize> can be octal, decimal or hexadecimal. If followed 12 * by "ki", "Mi" or "Gi", the numbers will be interpreted as kilo, mega or 13 * gigabytes. <erasesize> is optional and defaults to PAGE_SIZE. 14 * 15 * Example: 16 * phram=swap,64Mi,128Mi phram=test,900Mi,1Mi,64Ki 17 */ 18 19#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 20 21#include <linux/io.h> 22#include <linux/init.h> 23#include <linux/kernel.h> 24#include <linux/list.h> 25#include <linux/module.h> 26#include <linux/moduleparam.h> 27#include <linux/slab.h> 28#include <linux/mtd/mtd.h> 29#include <asm/div64.h> 30#include <linux/platform_device.h> 31#include <linux/of_address.h> 32#include <linux/of.h> 33 34struct phram_mtd_list { 35 struct mtd_info mtd; 36 struct list_head list; 37 bool cached; 38}; 39 40static LIST_HEAD(phram_list); 41 42static int phram_erase(struct mtd_info *mtd, struct erase_info *instr) 43{ 44 u_char *start = mtd->priv; 45 46 memset(start + instr->addr, 0xff, instr->len); 47 48 return 0; 49} 50 51static int phram_point(struct mtd_info *mtd, loff_t from, size_t len, 52 size_t *retlen, void **virt, resource_size_t *phys) 53{ 54 *virt = mtd->priv + from; 55 *retlen = len; 56 return 0; 57} 58 59static int phram_unpoint(struct mtd_info *mtd, loff_t from, size_t len) 60{ 61 return 0; 62} 63 64static int phram_read(struct mtd_info *mtd, loff_t from, size_t len, 65 size_t *retlen, u_char *buf) 66{ 67 u_char *start = mtd->priv; 68 69 memcpy(buf, start + from, len); 70 *retlen = len; 71 return 0; 72} 73 74static int phram_write(struct mtd_info *mtd, loff_t to, size_t len, 75 size_t *retlen, const u_char *buf) 76{ 77 u_char *start = mtd->priv; 78 79 memcpy(start + to, buf, len); 80 *retlen = len; 81 return 0; 82} 83 84static int phram_map(struct phram_mtd_list *phram, phys_addr_t start, size_t len) 85{ 86 void *addr = NULL; 87 88 if (phram->cached) 89 addr = memremap(start, len, MEMREMAP_WB); 90 else 91 addr = (void __force *)ioremap(start, len); 92 if (!addr) 93 return -EIO; 94 95 phram->mtd.priv = addr; 96 97 return 0; 98} 99 100static void phram_unmap(struct phram_mtd_list *phram) 101{ 102 void *addr = phram->mtd.priv; 103 104 if (phram->cached) { 105 memunmap(addr); 106 return; 107 } 108 109 iounmap((void __iomem *)addr); 110} 111 112static void unregister_devices(void) 113{ 114 struct phram_mtd_list *this, *safe; 115 116 list_for_each_entry_safe(this, safe, &phram_list, list) { 117 mtd_device_unregister(&this->mtd); 118 phram_unmap(this); 119 kfree(this->mtd.name); 120 kfree(this); 121 } 122} 123 124static int register_device(struct platform_device *pdev, const char *name, 125 phys_addr_t start, size_t len, uint32_t erasesize) 126{ 127 struct device_node *np = pdev ? pdev->dev.of_node : NULL; 128 bool cached = np ? !of_property_read_bool(np, "no-map") : false; 129 struct phram_mtd_list *new; 130 int ret = -ENOMEM; 131 132 new = kzalloc(sizeof(*new), GFP_KERNEL); 133 if (!new) 134 goto out0; 135 136 new->cached = cached; 137 138 ret = phram_map(new, start, len); 139 if (ret) { 140 pr_err("ioremap failed\n"); 141 goto out1; 142 } 143 144 145 new->mtd.name = name; 146 new->mtd.size = len; 147 new->mtd.flags = MTD_CAP_RAM; 148 new->mtd._erase = phram_erase; 149 new->mtd._point = phram_point; 150 new->mtd._unpoint = phram_unpoint; 151 new->mtd._read = phram_read; 152 new->mtd._write = phram_write; 153 new->mtd.owner = THIS_MODULE; 154 new->mtd.type = MTD_RAM; 155 new->mtd.erasesize = erasesize; 156 new->mtd.writesize = 1; 157 158 mtd_set_of_node(&new->mtd, np); 159 160 ret = -EAGAIN; 161 if (mtd_device_register(&new->mtd, NULL, 0)) { 162 pr_err("Failed to register new device\n"); 163 goto out2; 164 } 165 166 if (pdev) 167 platform_set_drvdata(pdev, new); 168 else 169 list_add_tail(&new->list, &phram_list); 170 171 return 0; 172 173out2: 174 phram_unmap(new); 175out1: 176 kfree(new); 177out0: 178 return ret; 179} 180 181static int parse_num64(uint64_t *num64, char *token) 182{ 183 size_t len; 184 int shift = 0; 185 int ret; 186 187 len = strlen(token); 188 /* By dwmw2 editorial decree, "ki", "Mi" or "Gi" are to be used. */ 189 if (len > 2) { 190 if (token[len - 1] == 'i') { 191 switch (token[len - 2]) { 192 case 'G': 193 shift += 10; 194 fallthrough; 195 case 'M': 196 shift += 10; 197 fallthrough; 198 case 'k': 199 shift += 10; 200 token[len - 2] = 0; 201 break; 202 default: 203 return -EINVAL; 204 } 205 } 206 } 207 208 ret = kstrtou64(token, 0, num64); 209 *num64 <<= shift; 210 211 return ret; 212} 213 214static int parse_name(char **pname, const char *token) 215{ 216 size_t len; 217 char *name; 218 219 len = strlen(token) + 1; 220 if (len > 64) 221 return -ENOSPC; 222 223 name = kstrdup(token, GFP_KERNEL); 224 if (!name) 225 return -ENOMEM; 226 227 *pname = name; 228 return 0; 229} 230 231 232static inline void kill_final_newline(char *str) 233{ 234 char *newline = strrchr(str, '\n'); 235 236 if (newline && !newline[1]) 237 *newline = 0; 238} 239 240 241#define parse_err(fmt, args...) do { \ 242 pr_err(fmt , ## args); \ 243 return 1; \ 244} while (0) 245 246#ifndef MODULE 247static int phram_init_called; 248/* 249 * This shall contain the module parameter if any. It is of the form: 250 * - phram=<device>,<address>,<size>[,<erasesize>] for module case 251 * - phram.phram=<device>,<address>,<size>[,<erasesize>] for built-in case 252 * We leave 64 bytes for the device name, 20 for the address , 20 for the 253 * size and 20 for the erasesize. 254 * Example: phram.phram=rootfs,0xa0000000,512Mi,65536 255 */ 256static char phram_paramline[64 + 20 + 20 + 20]; 257#endif 258 259static int phram_setup(const char *val) 260{ 261 char buf[64 + 20 + 20 + 20], *str = buf; 262 char *token[4]; 263 char *name; 264 uint64_t start; 265 uint64_t len; 266 uint64_t erasesize = PAGE_SIZE; 267 uint32_t rem; 268 int i, ret; 269 270 if (strnlen(val, sizeof(buf)) >= sizeof(buf)) 271 parse_err("parameter too long\n"); 272 273 strcpy(str, val); 274 kill_final_newline(str); 275 276 for (i = 0; i < 4; i++) 277 token[i] = strsep(&str, ","); 278 279 if (str) 280 parse_err("too many arguments\n"); 281 282 if (!token[2]) 283 parse_err("not enough arguments\n"); 284 285 ret = parse_name(&name, token[0]); 286 if (ret) 287 return ret; 288 289 ret = parse_num64(&start, token[1]); 290 if (ret) { 291 parse_err("illegal start address\n"); 292 goto error; 293 } 294 295 ret = parse_num64(&len, token[2]); 296 if (ret) { 297 parse_err("illegal device length\n"); 298 goto error; 299 } 300 301 if (token[3]) { 302 ret = parse_num64(&erasesize, token[3]); 303 if (ret) { 304 parse_err("illegal erasesize\n"); 305 goto error; 306 } 307 } 308 309 if (len == 0 || erasesize == 0 || erasesize > len 310 || erasesize > UINT_MAX) { 311 parse_err("illegal erasesize or len\n"); 312 ret = -EINVAL; 313 goto error; 314 } 315 316 div_u64_rem(len, (uint32_t)erasesize, &rem); 317 if (rem) { 318 parse_err("len is not multiple of erasesize\n"); 319 ret = -EINVAL; 320 goto error; 321 } 322 323 ret = register_device(NULL, name, start, len, (uint32_t)erasesize); 324 if (ret) 325 goto error; 326 327 pr_info("%s device: %#llx at %#llx for erasesize %#llx\n", name, len, start, erasesize); 328 return 0; 329 330error: 331 kfree(name); 332 return ret; 333} 334 335static int phram_param_call(const char *val, const struct kernel_param *kp) 336{ 337#ifdef MODULE 338 return phram_setup(val); 339#else 340 /* 341 * If more parameters are later passed in via 342 * /sys/module/phram/parameters/phram 343 * and init_phram() has already been called, 344 * we can parse the argument now. 345 */ 346 347 if (phram_init_called) 348 return phram_setup(val); 349 350 /* 351 * During early boot stage, we only save the parameters 352 * here. We must parse them later: if the param passed 353 * from kernel boot command line, phram_param_call() is 354 * called so early that it is not possible to resolve 355 * the device (even kmalloc() fails). Defer that work to 356 * phram_setup(). 357 */ 358 359 if (strlen(val) >= sizeof(phram_paramline)) 360 return -ENOSPC; 361 strcpy(phram_paramline, val); 362 363 return 0; 364#endif 365} 366 367module_param_call(phram, phram_param_call, NULL, NULL, 0200); 368MODULE_PARM_DESC(phram, "Memory region to map. \"phram=<name>,<start>,<length>[,<erasesize>]\""); 369 370#ifdef CONFIG_OF 371static const struct of_device_id phram_of_match[] = { 372 { .compatible = "phram" }, 373 {} 374}; 375MODULE_DEVICE_TABLE(of, phram_of_match); 376#endif 377 378static int phram_probe(struct platform_device *pdev) 379{ 380 struct resource *res; 381 382 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 383 if (!res) 384 return -ENOMEM; 385 386 /* mtd_set_of_node() reads name from "label" */ 387 return register_device(pdev, NULL, res->start, resource_size(res), 388 PAGE_SIZE); 389} 390 391static int phram_remove(struct platform_device *pdev) 392{ 393 struct phram_mtd_list *phram = platform_get_drvdata(pdev); 394 395 mtd_device_unregister(&phram->mtd); 396 phram_unmap(phram); 397 kfree(phram); 398 399 return 0; 400} 401 402static struct platform_driver phram_driver = { 403 .probe = phram_probe, 404 .remove = phram_remove, 405 .driver = { 406 .name = "phram", 407 .of_match_table = of_match_ptr(phram_of_match), 408 }, 409}; 410 411static int __init init_phram(void) 412{ 413 int ret; 414 415 ret = platform_driver_register(&phram_driver); 416 if (ret) 417 return ret; 418 419#ifndef MODULE 420 if (phram_paramline[0]) 421 ret = phram_setup(phram_paramline); 422 phram_init_called = 1; 423#endif 424 425 if (ret) 426 platform_driver_unregister(&phram_driver); 427 428 return ret; 429} 430 431static void __exit cleanup_phram(void) 432{ 433 unregister_devices(); 434 platform_driver_unregister(&phram_driver); 435} 436 437module_init(init_phram); 438module_exit(cleanup_phram); 439 440MODULE_LICENSE("GPL"); 441MODULE_AUTHOR("Joern Engel <joern@wh.fh-wedel.de>"); 442MODULE_DESCRIPTION("MTD driver for physical RAM");