1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /*====================================================================== |
3 | |
4 | This driver provides a method to access memory not used by the kernel |
5 | itself (i.e. if the kernel commandline mem=xxx is used). To actually |
6 | use slram at least mtdblock or mtdchar is required (for block or |
7 | character device access). |
8 | |
9 | Usage: |
10 | |
11 | if compiled as loadable module: |
12 | modprobe slram map=<name>,<start>,<end/offset> |
13 | if statically linked into the kernel use the following kernel cmd.line |
14 | slram=<name>,<start>,<end/offset> |
15 | |
16 | <name>: name of the device that will be listed in /proc/mtd |
17 | <start>: start of the memory region, decimal or hex (0xabcdef) |
18 | <end/offset>: end of the memory region. It's possible to use +0x1234 |
19 | to specify the offset instead of the absolute address |
20 | |
21 | NOTE: |
22 | With slram it's only possible to map a contiguous memory region. Therefore |
23 | if there's a device mapped somewhere in the region specified slram will |
24 | fail to load (see kernel log if modprobe fails). |
25 | |
26 | - |
27 | |
28 | Jochen Schaeuble <psionic@psionic.de> |
29 | |
30 | ======================================================================*/ |
31 | |
32 | |
33 | #include <linux/module.h> |
34 | #include <linux/uaccess.h> |
35 | #include <linux/types.h> |
36 | #include <linux/kernel.h> |
37 | #include <linux/ptrace.h> |
38 | #include <linux/slab.h> |
39 | #include <linux/string.h> |
40 | #include <linux/timer.h> |
41 | #include <linux/major.h> |
42 | #include <linux/fs.h> |
43 | #include <linux/ioctl.h> |
44 | #include <linux/init.h> |
45 | #include <linux/io.h> |
46 | |
47 | #include <linux/mtd/mtd.h> |
48 | |
49 | #define SLRAM_MAX_DEVICES_PARAMS 6 /* 3 parameters / device */ |
50 | #define SLRAM_BLK_SZ 0x4000 |
51 | |
52 | #define T(fmt, args...) printk(KERN_DEBUG fmt, ## args) |
53 | #define E(fmt, args...) printk(KERN_NOTICE fmt, ## args) |
54 | |
55 | typedef struct slram_priv { |
56 | u_char *start; |
57 | u_char *end; |
58 | } slram_priv_t; |
59 | |
60 | typedef struct slram_mtd_list { |
61 | struct mtd_info *mtdinfo; |
62 | struct slram_mtd_list *next; |
63 | } slram_mtd_list_t; |
64 | |
65 | #ifdef MODULE |
66 | static char *map[SLRAM_MAX_DEVICES_PARAMS]; |
67 | |
68 | module_param_array(map, charp, NULL, 0); |
69 | MODULE_PARM_DESC(map, "List of memory regions to map. \"map=<name>, <start>, <length / end>\"" ); |
70 | #else |
71 | static char *map; |
72 | #endif |
73 | |
74 | static slram_mtd_list_t *slram_mtdlist = NULL; |
75 | |
76 | static int slram_erase(struct mtd_info *, struct erase_info *); |
77 | static int slram_point(struct mtd_info *, loff_t, size_t, size_t *, void **, |
78 | resource_size_t *); |
79 | static int slram_unpoint(struct mtd_info *, loff_t, size_t); |
80 | static int slram_read(struct mtd_info *, loff_t, size_t, size_t *, u_char *); |
81 | static int slram_write(struct mtd_info *, loff_t, size_t, size_t *, const u_char *); |
82 | |
83 | static int slram_erase(struct mtd_info *mtd, struct erase_info *instr) |
84 | { |
85 | slram_priv_t *priv = mtd->priv; |
86 | |
87 | memset(priv->start + instr->addr, 0xff, instr->len); |
88 | |
89 | return(0); |
90 | } |
91 | |
92 | static int slram_point(struct mtd_info *mtd, loff_t from, size_t len, |
93 | size_t *retlen, void **virt, resource_size_t *phys) |
94 | { |
95 | slram_priv_t *priv = mtd->priv; |
96 | |
97 | *virt = priv->start + from; |
98 | *retlen = len; |
99 | return(0); |
100 | } |
101 | |
102 | static int slram_unpoint(struct mtd_info *mtd, loff_t from, size_t len) |
103 | { |
104 | return 0; |
105 | } |
106 | |
107 | static int slram_read(struct mtd_info *mtd, loff_t from, size_t len, |
108 | size_t *retlen, u_char *buf) |
109 | { |
110 | slram_priv_t *priv = mtd->priv; |
111 | |
112 | memcpy(buf, priv->start + from, len); |
113 | *retlen = len; |
114 | return(0); |
115 | } |
116 | |
117 | static int slram_write(struct mtd_info *mtd, loff_t to, size_t len, |
118 | size_t *retlen, const u_char *buf) |
119 | { |
120 | slram_priv_t *priv = mtd->priv; |
121 | |
122 | memcpy(priv->start + to, buf, len); |
123 | *retlen = len; |
124 | return(0); |
125 | } |
126 | |
127 | /*====================================================================*/ |
128 | |
129 | static int register_device(char *name, unsigned long start, unsigned long length) |
130 | { |
131 | slram_mtd_list_t **curmtd; |
132 | |
133 | curmtd = &slram_mtdlist; |
134 | while (*curmtd) { |
135 | curmtd = &(*curmtd)->next; |
136 | } |
137 | |
138 | *curmtd = kmalloc(size: sizeof(slram_mtd_list_t), GFP_KERNEL); |
139 | if (!(*curmtd)) { |
140 | E("slram: Cannot allocate new MTD device.\n" ); |
141 | return(-ENOMEM); |
142 | } |
143 | (*curmtd)->mtdinfo = kzalloc(size: sizeof(struct mtd_info), GFP_KERNEL); |
144 | (*curmtd)->next = NULL; |
145 | |
146 | if ((*curmtd)->mtdinfo) { |
147 | (*curmtd)->mtdinfo->priv = |
148 | kzalloc(size: sizeof(slram_priv_t), GFP_KERNEL); |
149 | |
150 | if (!(*curmtd)->mtdinfo->priv) { |
151 | kfree(objp: (*curmtd)->mtdinfo); |
152 | (*curmtd)->mtdinfo = NULL; |
153 | } |
154 | } |
155 | |
156 | if (!(*curmtd)->mtdinfo) { |
157 | E("slram: Cannot allocate new MTD device.\n" ); |
158 | return(-ENOMEM); |
159 | } |
160 | |
161 | if (!(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start = |
162 | memremap(offset: start, size: length, |
163 | flags: MEMREMAP_WB | MEMREMAP_WT | MEMREMAP_WC))) { |
164 | E("slram: memremap failed\n" ); |
165 | return -EIO; |
166 | } |
167 | ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end = |
168 | ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start + length; |
169 | |
170 | |
171 | (*curmtd)->mtdinfo->name = name; |
172 | (*curmtd)->mtdinfo->size = length; |
173 | (*curmtd)->mtdinfo->flags = MTD_CAP_RAM; |
174 | (*curmtd)->mtdinfo->_erase = slram_erase; |
175 | (*curmtd)->mtdinfo->_point = slram_point; |
176 | (*curmtd)->mtdinfo->_unpoint = slram_unpoint; |
177 | (*curmtd)->mtdinfo->_read = slram_read; |
178 | (*curmtd)->mtdinfo->_write = slram_write; |
179 | (*curmtd)->mtdinfo->owner = THIS_MODULE; |
180 | (*curmtd)->mtdinfo->type = MTD_RAM; |
181 | (*curmtd)->mtdinfo->erasesize = SLRAM_BLK_SZ; |
182 | (*curmtd)->mtdinfo->writesize = 1; |
183 | |
184 | if (mtd_device_register((*curmtd)->mtdinfo, NULL, 0)) { |
185 | E("slram: Failed to register new device\n" ); |
186 | memunmap(addr: ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start); |
187 | kfree(objp: (*curmtd)->mtdinfo->priv); |
188 | kfree(objp: (*curmtd)->mtdinfo); |
189 | return(-EAGAIN); |
190 | } |
191 | T("slram: Registered device %s from %luKiB to %luKiB\n" , name, |
192 | (start / 1024), ((start + length) / 1024)); |
193 | T("slram: Mapped from 0x%p to 0x%p\n" , |
194 | ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start, |
195 | ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end); |
196 | return(0); |
197 | } |
198 | |
199 | static void unregister_devices(void) |
200 | { |
201 | slram_mtd_list_t *nextitem; |
202 | |
203 | while (slram_mtdlist) { |
204 | nextitem = slram_mtdlist->next; |
205 | mtd_device_unregister(master: slram_mtdlist->mtdinfo); |
206 | memunmap(addr: ((slram_priv_t *)slram_mtdlist->mtdinfo->priv)->start); |
207 | kfree(objp: slram_mtdlist->mtdinfo->priv); |
208 | kfree(objp: slram_mtdlist->mtdinfo); |
209 | kfree(objp: slram_mtdlist); |
210 | slram_mtdlist = nextitem; |
211 | } |
212 | } |
213 | |
214 | static unsigned long handle_unit(unsigned long value, char *unit) |
215 | { |
216 | if ((*unit == 'M') || (*unit == 'm')) { |
217 | return(value * 1024 * 1024); |
218 | } else if ((*unit == 'K') || (*unit == 'k')) { |
219 | return(value * 1024); |
220 | } |
221 | return(value); |
222 | } |
223 | |
224 | static int parse_cmdline(char *devname, char *szstart, char *szlength) |
225 | { |
226 | char *buffer; |
227 | unsigned long devstart; |
228 | unsigned long devlength; |
229 | |
230 | if ((!devname) || (!szstart) || (!szlength)) { |
231 | unregister_devices(); |
232 | return(-EINVAL); |
233 | } |
234 | |
235 | devstart = simple_strtoul(szstart, &buffer, 0); |
236 | devstart = handle_unit(value: devstart, unit: buffer); |
237 | |
238 | if (*(szlength) != '+') { |
239 | devlength = simple_strtoul(szlength, &buffer, 0); |
240 | devlength = handle_unit(value: devlength, unit: buffer); |
241 | if (devlength < devstart) |
242 | goto err_out; |
243 | |
244 | devlength -= devstart; |
245 | } else { |
246 | devlength = simple_strtoul(szlength + 1, &buffer, 0); |
247 | devlength = handle_unit(value: devlength, unit: buffer); |
248 | } |
249 | T("slram: devname=%s, devstart=0x%lx, devlength=0x%lx\n" , |
250 | devname, devstart, devlength); |
251 | if (devlength % SLRAM_BLK_SZ != 0) |
252 | goto err_out; |
253 | |
254 | if ((devstart = register_device(name: devname, start: devstart, length: devlength))){ |
255 | unregister_devices(); |
256 | return((int)devstart); |
257 | } |
258 | return(0); |
259 | |
260 | err_out: |
261 | E("slram: Illegal length parameter.\n" ); |
262 | return(-EINVAL); |
263 | } |
264 | |
265 | #ifndef MODULE |
266 | |
267 | static int __init mtd_slram_setup(char *str) |
268 | { |
269 | map = str; |
270 | return(1); |
271 | } |
272 | |
273 | __setup("slram=" , mtd_slram_setup); |
274 | |
275 | #endif |
276 | |
277 | static int __init init_slram(void) |
278 | { |
279 | char *devname; |
280 | |
281 | #ifndef MODULE |
282 | char *devstart; |
283 | char *devlength; |
284 | |
285 | if (!map) { |
286 | E("slram: not enough parameters.\n" ); |
287 | return(-EINVAL); |
288 | } |
289 | while (map) { |
290 | devname = devstart = devlength = NULL; |
291 | |
292 | if (!(devname = strsep(&map, "," ))) { |
293 | E("slram: No devicename specified.\n" ); |
294 | break; |
295 | } |
296 | T("slram: devname = %s\n" , devname); |
297 | if ((!map) || (!(devstart = strsep(&map, "," )))) { |
298 | E("slram: No devicestart specified.\n" ); |
299 | } |
300 | T("slram: devstart = %s\n" , devstart); |
301 | if ((!map) || (!(devlength = strsep(&map, "," )))) { |
302 | E("slram: No devicelength / -end specified.\n" ); |
303 | } |
304 | T("slram: devlength = %s\n" , devlength); |
305 | if (parse_cmdline(devname, szstart: devstart, szlength: devlength) != 0) { |
306 | return(-EINVAL); |
307 | } |
308 | } |
309 | #else |
310 | int count; |
311 | int i; |
312 | |
313 | for (count = 0; count < SLRAM_MAX_DEVICES_PARAMS && map[count]; |
314 | count++) { |
315 | } |
316 | |
317 | if ((count % 3 != 0) || (count == 0)) { |
318 | E("slram: not enough parameters.\n" ); |
319 | return(-EINVAL); |
320 | } |
321 | for (i = 0; i < (count / 3); i++) { |
322 | devname = map[i * 3]; |
323 | |
324 | if (parse_cmdline(devname, map[i * 3 + 1], map[i * 3 + 2])!=0) { |
325 | return(-EINVAL); |
326 | } |
327 | |
328 | } |
329 | #endif /* !MODULE */ |
330 | |
331 | return(0); |
332 | } |
333 | |
334 | static void __exit cleanup_slram(void) |
335 | { |
336 | unregister_devices(); |
337 | } |
338 | |
339 | module_init(init_slram); |
340 | module_exit(cleanup_slram); |
341 | |
342 | MODULE_LICENSE("GPL" ); |
343 | MODULE_AUTHOR("Jochen Schaeuble <psionic@psionic.de>" ); |
344 | MODULE_DESCRIPTION("MTD driver for uncached system RAM" ); |
345 | |