Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | /* |
2 | * ========== Copyright Header Begin ========================================== | |
3 | * | |
4 | * OpenSPARC T2 Processor File: memsparse.c | |
5 | * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. | |
6 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES. | |
7 | * | |
8 | * The above named program is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU General Public | |
10 | * License version 2 as published by the Free Software Foundation. | |
11 | * | |
12 | * The above named program is distributed in the hope that it will be | |
13 | * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | * General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public | |
18 | * License along with this work; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. | |
20 | * | |
21 | * ========== Copyright Header End ============================================ | |
22 | */ | |
23 | /* | |
24 | * Copyright 2007 Sun Microsystems, Inc. All rights reserved. | |
25 | * Use is subject to license terms. | |
26 | */ | |
27 | #pragma ident "@(#)memsparse.c 1.2 07/03/26 SMI" | |
28 | ||
29 | /* | |
30 | * Special purpose memory device to allow for holey memory based on | |
31 | * address patterns. Only accesses to the memory device are checked, | |
32 | * the device implements data for the entire range of the device and | |
33 | * a backing file, if used, also contains data for the entire range. | |
34 | * | |
35 | * For example: | |
36 | * device "memsparse" 0x100000 +1M { | |
37 | * mask 0xc0 0x40; | |
38 | * load "test.bin"; | |
39 | * } | |
40 | * This specifies that bits [7:6] of the address (the '0xc0') must | |
41 | * be 01(binary) (the '0x40'). | |
42 | */ | |
43 | ||
44 | #include <stdio.h> | |
45 | #include <stdlib.h> | |
46 | #include <unistd.h> | |
47 | #include <fcntl.h> | |
48 | #include <sys/types.h> | |
49 | #include <sys/stat.h> | |
50 | #include <sys/mman.h> | |
51 | #include <errno.h> | |
52 | #include <fcntl.h> | |
53 | #include <strings.h> | |
54 | ||
55 | #include "basics.h" | |
56 | #include "allocate.h" | |
57 | #include "lexer.h" | |
58 | #include "simcore.h" | |
59 | #include "config.h" | |
60 | #include "dumpinfo.h" | |
61 | #include "strutil.h" | |
62 | #include "fatal.h" | |
63 | #include "options.h" | |
64 | ||
65 | ||
66 | static void memsprse_parse(config_dev_t *); | |
67 | static void memsprse_init(config_dev_t *); | |
68 | static void memsprse_dump(config_dev_t *); | |
69 | static tpaddr_t memsprse_cacheable(config_addr_t *, dev_access_t type, | |
70 | tpaddr_t off, uint8_t **cbp); | |
71 | static bool_t memsprse_cpu_access(simcpu_t *, config_addr_t *, tpaddr_t off, | |
72 | maccess_t op, uint64_t *regp); | |
73 | ||
74 | dev_type_t dev_type_memsparse = { | |
75 | "memsparse", | |
76 | memsprse_parse, | |
77 | memsprse_init, | |
78 | memsprse_dump, | |
79 | memsprse_cacheable, | |
80 | memsprse_cpu_access, /* shouldnt be called if we're a RAM */ | |
81 | DEV_MAGIC | |
82 | }; | |
83 | ||
84 | ||
85 | typedef struct { | |
86 | uint8_t *datap; | |
87 | uint64_t size; /* size of memory segment */ | |
88 | char *fnamep; | |
89 | uint64_t mask; | |
90 | uint64_t match; | |
91 | uint64_t stride; /* 2^mask_SLB */ | |
92 | uint64_t stridemask; /* stride - 1 */ | |
93 | uint64_t fileextent; /* how much of file to load in */ | |
94 | uint64_t fileextenta; /* page alligned fileextent */ | |
95 | uint64_t fileoffset; /* offset in file to start loading */ | |
96 | uint64_t memoffset; /* offset in memory of where to load */ | |
97 | bool_t is_rom; /* mmap file with MAP_PRIVATE flag */ | |
98 | bool_t is_shared; /* mmap file with MAP_SHARED flag */ | |
99 | } memsprse_dev_t; | |
100 | ||
101 | /* This should be in the processor structure - but is common to all now. */ | |
102 | #define SS_CACHE_LINE 64 | |
103 | ||
104 | ||
105 | /* | |
106 | * | |
107 | */ | |
108 | ||
109 | void | |
110 | memsprse_parse(config_dev_t *config_devp) | |
111 | { | |
112 | lexer_tok_t tok; | |
113 | memsprse_dev_t *mdp; | |
114 | long pgsize; | |
115 | uint64_t x; | |
116 | ||
117 | pgsize = getpagesize(); | |
118 | /* | |
119 | * Allocate the memory device and all that stuff | |
120 | */ | |
121 | mdp = Xcalloc(1, memsprse_dev_t); | |
122 | ||
123 | mdp->is_rom = false; | |
124 | mdp->is_shared = false; | |
125 | mdp->mask = 0; | |
126 | mdp->match = 0; | |
127 | mdp->fnamep = NULL; | |
128 | mdp->datap = (void*)0; | |
129 | mdp->size = config_devp->addrp->range; | |
130 | ||
131 | /* CSTYLED */ | |
132 | DBG( PRINTF(("memsprse_parse: parsing device %d\n", config_devp->device_id)); ); | |
133 | ||
134 | tok = lex_get_token(); | |
135 | switch (tok) { | |
136 | case T_S_Colon: | |
137 | goto finished; /* nothing more to parse */ | |
138 | case T_L_Brace: | |
139 | break; | |
140 | default: | |
141 | lex_fatal("expecting either ; or { when parsing memory device"); | |
142 | } | |
143 | ||
144 | ||
145 | /* | |
146 | * Start the parsing loop | |
147 | */ | |
148 | do { | |
149 | char *fnamep; | |
150 | uint64_t startoffset = 0LL; | |
151 | uint64_t foffset = 0LL; | |
152 | uint64_t flen = 0LL; | |
153 | struct stat sb; | |
154 | int idx; | |
155 | bool_t is_shared, is_rom; | |
156 | ||
157 | tok = lex_get_token(); | |
158 | ||
159 | if (tok == T_R_Brace) break; | |
160 | ||
161 | if (tok != T_Token) { | |
162 | fail:; | |
163 | lex_fatal("expected load, rom, shared or mask " | |
164 | "directive parsing memory device"); | |
165 | } | |
166 | ||
167 | if (streq(lex.strp, "mask")) { | |
168 | if (mdp->mask) lex_fatal("mask already specified"); | |
169 | tok = lex_get_token(); | |
170 | if (tok != T_Number) | |
171 | lex_fatal("mask expects a number"); | |
172 | if (lex.val == 0) | |
173 | lex_fatal("mask must be non-zero"); | |
174 | mdp->mask = lex.val; | |
175 | tok = lex_get_token(); | |
176 | if (tok != T_Number) | |
177 | lex_fatal("mask expects a second number"); | |
178 | if ((lex.val & ~mdp->mask) != 0) | |
179 | lex_fatal("(match & ~mask) must be zero"); | |
180 | mdp->match = lex.val; | |
181 | lex_get(T_S_Colon); | |
182 | continue; | |
183 | } | |
184 | ||
185 | if (streq(lex.strp, "rom")) { | |
186 | if (mdp->is_rom) lex_fatal("rom already specified"); | |
187 | mdp->is_rom = true; | |
188 | lex_get(T_S_Colon); | |
189 | continue; | |
190 | } | |
191 | ||
192 | if (streq(lex.strp, "shared")) { | |
193 | if (mdp->is_shared) | |
194 | lex_fatal("shared already specified"); | |
195 | mdp->is_shared = true; | |
196 | lex_get(T_S_Colon); | |
197 | continue; | |
198 | } | |
199 | ||
200 | if (!streq(lex.strp, "load")) goto fail; | |
201 | ||
202 | if (mdp->fnamep != NULL) | |
203 | lex_fatal("load already specified"); | |
204 | ||
205 | tok = lex_get_token(); | |
206 | ||
207 | /* | |
208 | * Get the load offset, ie. the offset from the base | |
209 | * address to load this file. For disk devices, we | |
210 | * don't need this as we use the size of the previous | |
211 | * disk to work out where to load the next disk. | |
212 | */ | |
213 | switch (tok) { | |
214 | case T_Plus: | |
215 | lex_get(T_Number); | |
216 | startoffset = lex.val; | |
217 | break; | |
218 | case T_Number: | |
219 | if (lex.val < config_devp->addrp->baseaddr || | |
220 | lex.val >= config_devp->addrp->topaddr) | |
221 | lex_fatal("specified load address is outside " | |
222 | "the range of the memory device"); | |
223 | ||
224 | startoffset = lex.val - config_devp->addrp->baseaddr; | |
225 | break; | |
226 | case T_String: | |
227 | startoffset = 0x0LL; | |
228 | goto got_string; | |
229 | default: | |
230 | lex_fatal("Expected either a start address / offset " | |
231 | "or filename for memory device load directive"); | |
232 | } | |
233 | ||
234 | lex_get(T_String); | |
235 | ||
236 | got_string:; | |
237 | fnamep = Xstrdup(lex.strp); | |
238 | ||
239 | tok = lex_get_token(); | |
240 | if (tok == T_S_Colon) | |
241 | goto load_file; | |
242 | if (tok != T_Number) | |
243 | lex_fatal("Expected ; or file offset for memory device " | |
244 | "load directive"); | |
245 | if (lex.val < 0LL) | |
246 | lex_fatal("load file offset must be >0 for load directive"); | |
247 | ||
248 | foffset = lex.val; | |
249 | ||
250 | tok = lex_get_token(); | |
251 | if (tok == T_S_Colon) | |
252 | goto load_file; | |
253 | if (tok != T_Number) | |
254 | lex_fatal("Expected ; or load length for memory device " | |
255 | "load directive"); | |
256 | if (lex.val <= 0LL) | |
257 | lex_fatal("load length must be >=0 for load directive"); | |
258 | ||
259 | flen = lex.val; | |
260 | ||
261 | lex_get(T_S_Colon); | |
262 | ||
263 | load_file: | |
264 | if (stat(fnamep, &sb) < 0) | |
265 | lex_fatal("error opening load file %s", fnamep); | |
266 | ||
267 | if (flen == 0LL) | |
268 | flen = sb.st_size - foffset; | |
269 | ||
270 | if (sb.st_size < (foffset + flen)) | |
271 | lex_fatal("load file %s is smaller than the specified " | |
272 | "load range", fnamep); | |
273 | ||
274 | if ((startoffset + flen) > config_devp->addrp->range) | |
275 | lex_fatal("load file %s is larger than the memory device", | |
276 | fnamep); | |
277 | ||
278 | /* OK have parsed file info - add it to the load block */ | |
279 | ||
280 | mdp->fnamep = fnamep; | |
281 | mdp->fileextent = flen; /* how much of file to load in */ | |
282 | mdp->fileextenta = sim_roundup(flen, pgsize); | |
283 | mdp->fileoffset = foffset; /* offset in file to start load */ | |
284 | mdp->memoffset = startoffset; /* offset in mem to start load */ | |
285 | mdp->is_shared = is_shared; /* MMAP with flag MAP_SHARED */ | |
286 | mdp->is_rom = is_rom; /* MMAP with flag MAP_PRIVATE */ | |
287 | ||
288 | } while (1); | |
289 | ||
290 | finished:; | |
291 | config_devp->devp = mdp; | |
292 | if (mdp->mask == 0) | |
293 | lex_fatal("memsparse must have a mask directive"); | |
294 | mdp->stride = 1; | |
295 | x = mdp->mask; | |
296 | while ((x & 1) == 0) { | |
297 | x >>= 1; | |
298 | mdp->stride <<= 1; | |
299 | } | |
300 | if (mdp->stride < SS_CACHE_LINE) | |
301 | warning("memsparse mask stride 0x%llx < size of cache " | |
302 | "line 0x%llx", mdp->stride, SS_CACHE_LINE); | |
303 | mdp->stridemask = mdp->stride - 1; | |
304 | if ((config_devp->addrp->baseaddr & mdp->stridemask) != 0) | |
305 | lex_fatal("memsparse: base must be aligned to stride of " | |
306 | "0x%llx", mdp->stride); | |
307 | if ((config_devp->addrp->range & mdp->stridemask) != 0) | |
308 | lex_fatal("memsparse: size must be a multiple of stride of " | |
309 | "0x%llx", mdp->stride); | |
310 | } | |
311 | ||
312 | ||
313 | ||
314 | /* | |
315 | * Initialise the mem after parsing is complete | |
316 | * If no files have been requested, then we force a | |
317 | * /dev/zero mapping ... | |
318 | */ | |
319 | ||
320 | void | |
321 | memsprse_init(config_dev_t *config_devp) | |
322 | { | |
323 | long pgsize; | |
324 | memsprse_dev_t *mdp; | |
325 | uint8_t *datap; | |
326 | int idx; | |
327 | ||
328 | mdp = (memsprse_dev_t *)config_devp->devp; | |
329 | pgsize = getpagesize(); | |
330 | ||
331 | /* | |
332 | * Perform a check to see that the requested loaded files are | |
333 | * a) correctly aligned, and | |
334 | * b) whether we need a /dev/zero mapping to back sections | |
335 | * not covered by other mmapped files (beginning, middle, | |
336 | * end), or because we need to "load" files in that are | |
337 | * not correctly aligned. | |
338 | */ | |
339 | ||
340 | ||
341 | /* | |
342 | * For the moment we create a MAP_ANON block of the entire | |
343 | * ram/rom then map over than any additional data files we | |
344 | * need. | |
345 | * Force a segv if we ever try and write to a "ROM". | |
346 | */ | |
347 | ||
348 | /* CSTYLED */ | |
349 | DBG( PRINTF(("memory mapping : 0x%llx\n", sim_roundup(mdp->size, pgsize))); ); | |
350 | ||
351 | datap = mdp->datap = (void*)mmap(NULL, sim_roundup(mdp->size, pgsize), | |
352 | PROT_READ | (mdp->is_rom ? 0 : PROT_WRITE), | |
353 | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); | |
354 | if (MAP_FAILED == datap) fatal("Initial mmap of anon memory failed"); | |
355 | ||
356 | /* | |
357 | * Now we either map or load in all the remaining files that the | |
358 | * config file may have specified. | |
359 | */ | |
360 | ||
361 | if (mdp->fnamep != NULL) { | |
362 | int fd; | |
363 | uint8_t *mapp; | |
364 | bool_t is_shared = mdp->is_shared; | |
365 | ||
366 | ||
367 | do { | |
368 | fd = open(mdp->fnamep, is_shared ? O_RDWR : O_RDONLY); | |
369 | } while (fd < 0 && EAGAIN == errno); | |
370 | ||
371 | if (fd < 0) fatal("Failed opening file %s", mdp->fnamep); | |
372 | ||
373 | /* | |
374 | * Check that the fileoffset (offset in file to start | |
375 | * loading) is page alligned | |
376 | */ | |
377 | if ((mdp->fileoffset % pgsize) != 0) | |
378 | fatal("Offset 0x%llx in file %s is not aligned " | |
379 | "with 0x%llx pagesize", | |
380 | mdp->fileoffset, mdp->fnamep, pgsize); | |
381 | ||
382 | #if 0 /* { */ | |
383 | /* Allow non-rounded out contents */ | |
384 | if ((mdp->fileextent % pgsize) != 0) | |
385 | fatal("Loaded extent 0x%llx for file %s is not " | |
386 | "aligned with 0x%llx pagesize", | |
387 | mdp->fileextent, mdp->fnamep, pgsize); | |
388 | #endif /* } */ | |
389 | ||
390 | /* | |
391 | * Check that the memoffset (offset in memory to start | |
392 | * loading) is page alligned | |
393 | */ | |
394 | if ((mdp->memoffset % pgsize) != 0) | |
395 | fatal("Offset 0x%llx into RAM for file %s " | |
396 | "is not aligned with 0x%llx pagesize", | |
397 | mdp->memoffset, mdp->fnamep, pgsize); | |
398 | ||
399 | ASSERT(NULL != datap || (NULL == datap && | |
400 | 0LL == mdp->memoffset)); | |
401 | ||
402 | /* CSTYLED */ | |
403 | DBG( PRINTF(("mapping %s (%s): 0x%llx -> 0x%llx (0x%llx)\n", | |
404 | mdp->fnamep, | |
405 | (is_shared ? "SHARED" : "PRIVATE"), | |
406 | config_devp->addrp->baseaddr + mdp->memoffset, | |
407 | config_devp->addrp->baseaddr + mdp->memoffset + | |
408 | /* CSTYLED */ | |
409 | mdp->fileextent, mdp->fileextent));); | |
410 | ||
411 | /* | |
412 | * mmap the file into memory with the appropriate flags. | |
413 | */ | |
414 | mapp = (void*)mmap((void*)(datap + mdp->memoffset), | |
415 | mdp->fileextent, | |
416 | PROT_READ | PROT_WRITE, | |
417 | (is_shared ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED | | |
418 | MAP_NORESERVE, fd, mdp->fileoffset); | |
419 | ||
420 | if (((uint8_t *)MAP_FAILED) == mapp) | |
421 | fatal("Failed mapping file %s", | |
422 | mdp->fnamep); | |
423 | ||
424 | ASSERT((datap + mdp->memoffset) == mapp); | |
425 | } | |
426 | } | |
427 | ||
428 | ||
429 | ||
430 | ||
431 | ||
432 | ||
433 | /* | |
434 | * Memory configuration dump | |
435 | */ | |
436 | ||
437 | void | |
438 | memsprse_dump(config_dev_t *config_devp) | |
439 | { | |
440 | } | |
441 | ||
442 | ||
443 | /* | |
444 | * Returns the extent of the linear cacheable block | |
445 | * starting at offset, and a pointer to the state block | |
446 | * | |
447 | * Note: if the device is a ROM, attempts to write | |
448 | * i.e. DA_Store as type must be failed - | |
449 | * the function is supposed to return 0. | |
450 | */ | |
451 | ||
452 | tpaddr_t | |
453 | memsprse_cacheable(config_addr_t *config_addrp, dev_access_t type, | |
454 | tpaddr_t offset, uint8_t **blockp) | |
455 | { | |
456 | memsprse_dev_t *mdp; | |
457 | ||
458 | mdp = config_addrp->config_devp->devp; | |
459 | ||
460 | /* fail store attempts to a ROM */ | |
461 | if (mdp->is_rom && (type & DA_Store)) | |
462 | return ((tpaddr_t)0); | |
463 | ||
464 | ||
465 | if ((offset < 0) || (offset >= config_addrp->range)) { | |
466 | /* CSTYLED */ | |
467 | SANITY(*blockp = NULL;); | |
468 | return (NULL); | |
469 | } | |
470 | ||
471 | if (((config_addrp->baseaddr + offset) & mdp->mask) != | |
472 | mdp->match) { | |
473 | return ((tpaddr_t)0); | |
474 | } | |
475 | ||
476 | *blockp = mdp->datap + offset; | |
477 | ||
478 | /* | |
479 | * This expression for the extent relies on parse enforcing | |
480 | * alignment of base address and size to mdp->stride. | |
481 | */ | |
482 | return (mdp->stride - (offset & (mdp->stride - 1))); | |
483 | } | |
484 | ||
485 | ||
486 | ||
487 | /* | |
488 | * Should only get invoked if this is a ROM. | |
489 | * Indicate the store failed - return false | |
490 | * ROMs are read only. | |
491 | */ | |
492 | ||
493 | bool_t | |
494 | memsprse_cpu_access(simcpu_t *sp, config_addr_t *cap, tpaddr_t offset, | |
495 | maccess_t op, uint64_t *regp) | |
496 | { | |
497 | memsprse_dev_t *mdp; | |
498 | ||
499 | mdp = cap->config_devp->devp; | |
500 | ||
501 | if (mdp->is_rom) { | |
502 | EXEC_WARNING(("memsprse_cpu_access: attempted store to ROM " | |
503 | "@ pc 0x%llx of %d bytes at offset 0x%llx", | |
504 | sp->pc, 1<<(op & MA_Size_Mask), offset)); | |
505 | } | |
506 | return (false); | |
507 | } |