1 |
/* |
2 |
* Cisco router simulation platform. |
3 |
* Copyright (c) 2006 Christophe Fillot. All rights reserved. |
4 |
* |
5 |
* Intel Flash SIMM emulation. |
6 |
* |
7 |
* Intelligent ID Codes: |
8 |
* 28F008SA: 0x89A2 (1 Mb) |
9 |
* 28F016SA: 0x89A0 (2 Mb) |
10 |
* |
11 |
* Manuals: |
12 |
* http://www.ortodoxism.ro/datasheets/Intel/mXvsysv.pdf |
13 |
* |
14 |
* TODO: A lot of commands are lacking. Doesn't work with NPE-G2. |
15 |
*/ |
16 |
|
17 |
#include <stdio.h> |
18 |
#include <stdlib.h> |
19 |
#include <string.h> |
20 |
#include <time.h> |
21 |
#include <errno.h> |
22 |
|
23 |
#include "cpu.h" |
24 |
#include "vm.h" |
25 |
#include "dynamips.h" |
26 |
#include "memory.h" |
27 |
#include "device.h" |
28 |
|
29 |
#define DEBUG_ACCESS 0 |
30 |
#define DEBUG_WRITE 0 |
31 |
|
32 |
/* Flash command states */ |
33 |
enum { |
34 |
FLASH_CMD_READ_ARRAY = 0, |
35 |
FLASH_CMD_READ_ID, |
36 |
FLASH_CMD_READ_QUERY, |
37 |
FLASH_CMD_READ_STATUS, |
38 |
FLASH_CMD_WRITE_BUF_CNT, |
39 |
FLASH_CMD_WRITE_BUF_DATA, |
40 |
FLASH_CMD_WRITE_BUF_CONFIRM, |
41 |
FLASH_CMD_WB_PROG, |
42 |
FLASH_CMD_WB_PROG_DONE, |
43 |
FLASH_CMD_BLK_ERASE, |
44 |
FLASH_CMD_BLK_ERASE_DONE, |
45 |
FLASH_CMD_CONFIG, |
46 |
}; |
47 |
|
48 |
/* Flash access mode (byte or word) */ |
49 |
enum { |
50 |
FLASH_MODE_BYTE, |
51 |
FLASH_MODE_WORD, |
52 |
}; |
53 |
|
54 |
#define MAX_FLASH 4 |
55 |
#define FLASH_BUF_SIZE 32 |
56 |
|
57 |
/* Forward declarations */ |
58 |
struct flash_data; |
59 |
struct flashset_data; |
60 |
|
61 |
/* Flash model */ |
62 |
struct flash_model { |
63 |
char *name; |
64 |
u_int total_size; |
65 |
u_int mode; |
66 |
u_int nr_flash_bits; |
67 |
u_int blk_size; |
68 |
u_int id_manufacturer; |
69 |
u_int id_device; |
70 |
}; |
71 |
|
72 |
/* Flash internal data */ |
73 |
struct flash_data { |
74 |
u_int mode,offset_shift,state,blk_size; |
75 |
m_uint8_t id_manufacturer,id_device; |
76 |
m_uint8_t status_reg; |
77 |
|
78 |
struct flashset_data *flash_set; |
79 |
u_int flash_pos; |
80 |
|
81 |
/* Write buffer */ |
82 |
u_int wb_offset,wb_count,wb_remain; |
83 |
u_int wbuf[FLASH_BUF_SIZE]; |
84 |
}; |
85 |
|
86 |
/* Flashset private data */ |
87 |
struct flashset_data { |
88 |
vm_instance_t *vm; |
89 |
vm_obj_t vm_obj; |
90 |
struct vdevice dev; |
91 |
char *filename; |
92 |
|
93 |
u_int nr_flash_bits; |
94 |
u_int nr_flash_count; |
95 |
struct flash_data flash[MAX_FLASH]; |
96 |
}; |
97 |
|
98 |
/* Log a Flash message */ |
99 |
#define FLASH_LOG(d,msg...) vm_log((d)->flash_set->vm, \ |
100 |
(d)->flash_set->dev.name, \ |
101 |
msg) |
102 |
|
103 |
#define BPTR(d,offset) (((u_char *)(d)->dev.host_addr) + offset) |
104 |
|
105 |
/* Some Flash models */ |
106 |
static struct flash_model flash_models[] = { |
107 |
/* C1700 4 Mb bootflash: 1x28F320 in word mode */ |
108 |
{ "c1700-bootflash-4mb",4 * 1048576,FLASH_MODE_WORD,0,0x10000,0x89,0x14 }, |
109 |
|
110 |
/* C1700 8 Mb bootflash: 1x28F640 in word mode */ |
111 |
{ "c1700-bootflash-8mb",8 * 1048576,FLASH_MODE_WORD,0,0x10000,0x89,0x15 }, |
112 |
|
113 |
/* C3600 8 Mb bootflash: 4x28F016SA in byte mode */ |
114 |
{ "c3600-bootflash-8mb",8 * 1048576,FLASH_MODE_BYTE,2,0x10000,0x89,0xA0 }, |
115 |
|
116 |
/* C7200 4 Mb bootflash: 4x28F008SA in byte mode */ |
117 |
{ "c7200-bootflash-4mb",4 * 1048576,FLASH_MODE_BYTE,2,0x10000,0x89,0xA2 }, |
118 |
|
119 |
/* C7200 8 Mb bootflash: 4x28F016SA in byte mode */ |
120 |
{ "c7200-bootflash-8mb",8 * 1048576,FLASH_MODE_BYTE,2,0x10000,0x89,0xA0 }, |
121 |
|
122 |
/* |
123 |
* C7200 64 Mb bootflash: 4x128 Mb Intel flash in byte mode |
124 |
* (for NPE-G2 but doesn't work now). |
125 |
*/ |
126 |
{ "c7200-bootflash-64mb",64 * 1048576,FLASH_MODE_BYTE,2,0x10000,0x89,0x18 }, |
127 |
|
128 |
/* C2600 8 Mb bootflash: 4x28F016SA in byte mode */ |
129 |
{ "c2600-bootflash-8mb",8 * 1048576,FLASH_MODE_BYTE,2,0x10000,0x89,0xA0 }, |
130 |
|
131 |
{ NULL, 0, 0, 0, 0, 0 }, |
132 |
}; |
133 |
|
134 |
/* Flash model lookup */ |
135 |
static struct flash_model *flash_model_find(char *name) |
136 |
{ |
137 |
struct flash_model *fm; |
138 |
|
139 |
for(fm=&flash_models[0];fm->name!=NULL;fm++) |
140 |
if (!strcmp(fm->name,name)) |
141 |
return fm; |
142 |
|
143 |
return NULL; |
144 |
} |
145 |
|
146 |
/* Initialize a flashset */ |
147 |
static int flashset_init(struct flashset_data *d, |
148 |
u_int mode,u_int nr_flash_bits,u_int blk_size, |
149 |
m_uint8_t id_manufacturer,m_uint8_t id_device) |
150 |
{ |
151 |
struct flash_data *flash; |
152 |
u_int i,offset_shift; |
153 |
|
154 |
d->nr_flash_bits = nr_flash_bits; |
155 |
d->nr_flash_count = 1 << d->nr_flash_bits; |
156 |
|
157 |
switch(mode) { |
158 |
case FLASH_MODE_BYTE: |
159 |
offset_shift = 0; |
160 |
break; |
161 |
case FLASH_MODE_WORD: |
162 |
offset_shift = 1; |
163 |
break; |
164 |
default: |
165 |
return(-1); |
166 |
} |
167 |
|
168 |
for(i=0;i<d->nr_flash_count;i++) { |
169 |
flash = &d->flash[i]; |
170 |
|
171 |
flash->mode = mode; |
172 |
flash->offset_shift = offset_shift; |
173 |
flash->state = FLASH_CMD_READ_ARRAY; |
174 |
|
175 |
flash->id_manufacturer = id_manufacturer; |
176 |
flash->id_device = id_device; |
177 |
|
178 |
flash->flash_set = d; |
179 |
flash->flash_pos = i; |
180 |
|
181 |
flash->blk_size = blk_size; |
182 |
} |
183 |
|
184 |
return(0); |
185 |
} |
186 |
|
187 |
/* Read a byte from a Flash */ |
188 |
static int flash_read(struct flash_data *d,u_int offset,u_int *data) |
189 |
{ |
190 |
u_int real_offset; |
191 |
|
192 |
real_offset = (offset << (d->flash_set->nr_flash_bits)) + d->flash_pos; |
193 |
|
194 |
if (d->mode == FLASH_MODE_BYTE) { |
195 |
*data = *BPTR(d->flash_set,real_offset); |
196 |
} else { |
197 |
*data = *BPTR(d->flash_set,(real_offset << 1)) << 8; |
198 |
*data |= *BPTR(d->flash_set,(real_offset << 1)+1); |
199 |
} |
200 |
return(0); |
201 |
} |
202 |
|
203 |
/* Write a byte to a Flash */ |
204 |
static int flash_write(struct flash_data *d,u_int offset,u_int data) |
205 |
{ |
206 |
u_int real_offset; |
207 |
|
208 |
real_offset = (offset << (d->flash_set->nr_flash_bits)) + d->flash_pos; |
209 |
|
210 |
if (d->mode == FLASH_MODE_BYTE) { |
211 |
*BPTR(d->flash_set,real_offset) = data; |
212 |
} else { |
213 |
*BPTR(d->flash_set,(real_offset << 1)) = data >> 8; |
214 |
*BPTR(d->flash_set,(real_offset << 1)+1) = data & 0xFF; |
215 |
} |
216 |
return(0); |
217 |
} |
218 |
|
219 |
/* Set machine state given a command */ |
220 |
static void flash_cmd(struct flash_data *d,u_int offset,u_int cmd) |
221 |
{ |
222 |
cmd = cmd & 0xFF; |
223 |
|
224 |
switch(cmd) { |
225 |
case 0x40: |
226 |
case 0x10: |
227 |
d->state = FLASH_CMD_WB_PROG; |
228 |
break; |
229 |
case 0xe8: |
230 |
d->state = FLASH_CMD_WRITE_BUF_CNT; |
231 |
d->wb_offset = offset; |
232 |
d->wb_count = d->wb_remain = 0; |
233 |
break; |
234 |
case 0x70: |
235 |
d->state = FLASH_CMD_READ_STATUS; |
236 |
break; |
237 |
case 0x50: |
238 |
d->status_reg = 0; |
239 |
d->state = FLASH_CMD_READ_ARRAY; |
240 |
break; |
241 |
case 0x90: |
242 |
d->state = FLASH_CMD_READ_ID; |
243 |
break; |
244 |
case 0x20: |
245 |
d->state = FLASH_CMD_BLK_ERASE; |
246 |
break; |
247 |
case 0xff: |
248 |
d->state = FLASH_CMD_READ_ARRAY; |
249 |
break; |
250 |
default: |
251 |
FLASH_LOG(d,"flash_cmd(%u): command 0x%2.2x not implemented\n", |
252 |
d->flash_pos,(u_int)cmd); |
253 |
} |
254 |
} |
255 |
|
256 |
/* Generic Flash access */ |
257 |
static void flash_access(struct flash_data *d,m_uint32_t offset,u_int op_type, |
258 |
u_int *data) |
259 |
{ |
260 |
u_int i; |
261 |
|
262 |
if (op_type == MTS_READ) |
263 |
*data = 0x00; |
264 |
|
265 |
#if DEBUG_ACCESS |
266 |
if (op_type == MTS_READ) { |
267 |
FLASH_LOG(d,"flash_access(%u): read access to offset 0x%8.8x " |
268 |
"(state=%u)\n",d->flash_pos,offset,d->state); |
269 |
} else { |
270 |
FLASH_LOG(d,"flash_access(%u): write access to offset 0x%8.8x, " |
271 |
"data=0x%4.4x (state=%u)\n", |
272 |
d->flash_pos,offset,*data,d->state); |
273 |
} |
274 |
#endif |
275 |
|
276 |
offset >>= d->offset_shift; |
277 |
|
278 |
/* State machine for Flash commands */ |
279 |
switch(d->state) { |
280 |
case FLASH_CMD_READ_ARRAY: |
281 |
if (op_type == MTS_READ) { |
282 |
flash_read(d,offset,data); |
283 |
return; |
284 |
} |
285 |
|
286 |
/* Command Write */ |
287 |
flash_cmd(d,offset,*data); |
288 |
break; |
289 |
|
290 |
/* Write byte/word */ |
291 |
case FLASH_CMD_WB_PROG: |
292 |
if (op_type == MTS_WRITE) { |
293 |
flash_write(d,offset,*data); |
294 |
d->state = FLASH_CMD_WB_PROG_DONE; |
295 |
} |
296 |
break; |
297 |
|
298 |
/* Write byte/word (done) */ |
299 |
case FLASH_CMD_WB_PROG_DONE: |
300 |
if (op_type == MTS_WRITE) { |
301 |
flash_cmd(d,offset,*data); |
302 |
} else { |
303 |
*data = 0x80; |
304 |
} |
305 |
break; |
306 |
|
307 |
/* Write buffer (count) */ |
308 |
case FLASH_CMD_WRITE_BUF_CNT: |
309 |
if (op_type == MTS_WRITE) { |
310 |
d->wb_count = d->wb_remain = (*data & 0x1F) + 1; |
311 |
d->state = FLASH_CMD_WRITE_BUF_DATA; |
312 |
} else { |
313 |
*data = 0x80; |
314 |
} |
315 |
break; |
316 |
|
317 |
/* Write buffer (data) */ |
318 |
case FLASH_CMD_WRITE_BUF_DATA: |
319 |
if (op_type == MTS_WRITE) { |
320 |
if ((offset >= d->wb_offset) && |
321 |
(offset < (d->wb_offset + d->wb_count))) |
322 |
{ |
323 |
d->wbuf[offset - d->wb_offset] = *data; |
324 |
d->wb_remain--; |
325 |
|
326 |
if (!d->wb_remain) |
327 |
d->state = FLASH_CMD_WRITE_BUF_CONFIRM; |
328 |
} |
329 |
} else { |
330 |
*data = 0x80; |
331 |
} |
332 |
break; |
333 |
|
334 |
/* Write buffer (confirm) */ |
335 |
case FLASH_CMD_WRITE_BUF_CONFIRM: |
336 |
if (op_type == MTS_WRITE) { |
337 |
if ((*data & 0xFF) == 0xD0) { |
338 |
for(i=0;i<d->wb_count;i++) |
339 |
flash_write(d,d->wb_offset+i,d->wbuf[i]); |
340 |
} else { |
341 |
/* XXX Error */ |
342 |
} |
343 |
|
344 |
d->state = FLASH_CMD_READ_ARRAY; |
345 |
} else { |
346 |
*data = 0x80; |
347 |
} |
348 |
break; |
349 |
|
350 |
/* Read status register */ |
351 |
case FLASH_CMD_READ_STATUS: |
352 |
if (op_type == MTS_READ) |
353 |
*data = 0x80; //d->status_reg; |
354 |
|
355 |
d->state = FLASH_CMD_READ_ARRAY; |
356 |
break; |
357 |
|
358 |
/* Read identifier codes */ |
359 |
case FLASH_CMD_READ_ID: |
360 |
if (op_type == MTS_READ) { |
361 |
switch(offset) { |
362 |
case 0x00: |
363 |
*data = d->id_manufacturer; |
364 |
break; |
365 |
case 0x01: |
366 |
*data = d->id_device; |
367 |
break; |
368 |
default: |
369 |
*data = 0x00; |
370 |
break; |
371 |
} |
372 |
} else { |
373 |
flash_cmd(d,offset,*data); |
374 |
} |
375 |
break; |
376 |
|
377 |
/* Block Erase */ |
378 |
case FLASH_CMD_BLK_ERASE: |
379 |
if (op_type == MTS_WRITE) { |
380 |
#if DEBUG_WRITE |
381 |
FLASH_LOG(d,"flash_access(%u): erasing block at offset 0x%8.8x\n" |
382 |
offset); |
383 |
#endif |
384 |
if ((*data & 0xFF) == 0xD0) { |
385 |
for(i=0;i<d->blk_size;i++) |
386 |
flash_write(d,offset+i,0xFFFF); |
387 |
|
388 |
d->state = FLASH_CMD_BLK_ERASE_DONE; |
389 |
} |
390 |
} else { |
391 |
*data = 0x80; |
392 |
} |
393 |
break; |
394 |
|
395 |
/* Block Erase Done */ |
396 |
case FLASH_CMD_BLK_ERASE_DONE: |
397 |
if (op_type == MTS_WRITE) { |
398 |
flash_cmd(d,offset,*data); |
399 |
} else { |
400 |
*data = 0x80; |
401 |
} |
402 |
break; |
403 |
} |
404 |
} |
405 |
|
406 |
/* |
407 |
* dev_bootflash_access() |
408 |
*/ |
409 |
void *dev_bootflash_access(cpu_gen_t *cpu,struct vdevice *dev, |
410 |
m_uint32_t offset,u_int op_size,u_int op_type, |
411 |
m_uint64_t *data) |
412 |
{ |
413 |
struct flashset_data *d = dev->priv_data; |
414 |
u_int flash_data[MAX_FLASH]; |
415 |
u_int i; |
416 |
|
417 |
#if DEBUG_ACCESS |
418 |
if (op_type == MTS_READ) |
419 |
cpu_log(cpu,dev->name,"read access to offset = 0x%x, pc = 0x%llx\n", |
420 |
offset,cpu_get_pc(cpu)); |
421 |
else |
422 |
cpu_log(cpu,dev->name,"write access to vaddr = 0x%x, pc = 0x%llx, " |
423 |
"val = 0x%llx\n",offset,cpu_get_pc(cpu),*data); |
424 |
#endif |
425 |
|
426 |
if (op_type == MTS_READ) { |
427 |
*data = 0; |
428 |
|
429 |
for(i=0;i<d->nr_flash_count;i++) { |
430 |
flash_access(&d->flash[i],(offset >> d->nr_flash_bits),op_type, |
431 |
&flash_data[i]); |
432 |
*data |= flash_data[i] << (8 * (d->nr_flash_count - i - 1)); |
433 |
} |
434 |
} else { |
435 |
for(i=0;i<d->nr_flash_count;i++) { |
436 |
flash_data[i] = *data >> (8 * (d->nr_flash_count - i - 1)); |
437 |
flash_access(&d->flash[i],(offset >> d->nr_flash_bits),op_type, |
438 |
&flash_data[i]); |
439 |
} |
440 |
} |
441 |
|
442 |
return NULL; |
443 |
} |
444 |
|
445 |
/* Shutdown a bootflash device */ |
446 |
void dev_bootflash_shutdown(vm_instance_t *vm,struct flashset_data *d) |
447 |
{ |
448 |
if (d != NULL) { |
449 |
/* Remove the device */ |
450 |
dev_remove(vm,&d->dev); |
451 |
|
452 |
/* We don't remove the file, since it used as permanent storage */ |
453 |
if (d->filename) |
454 |
free(d->filename); |
455 |
|
456 |
/* Free the structure itself */ |
457 |
free(d); |
458 |
} |
459 |
} |
460 |
|
461 |
/* Create a 8 Mb bootflash */ |
462 |
int dev_bootflash_init(vm_instance_t *vm,char *name,char *model, |
463 |
m_uint64_t paddr) |
464 |
{ |
465 |
struct flash_model *fm; |
466 |
struct flashset_data *d; |
467 |
u_char *ptr; |
468 |
|
469 |
/* Find the flash model */ |
470 |
if (!(fm = flash_model_find(model))) { |
471 |
vm_error(vm,"bootflash: unable to find model '%s'\n",model); |
472 |
return(-1); |
473 |
} |
474 |
|
475 |
/* Allocate the private data structure */ |
476 |
if (!(d = malloc(sizeof(*d)))) { |
477 |
vm_error(vm,"bootflash: unable to create device.\n"); |
478 |
return(-1); |
479 |
} |
480 |
|
481 |
memset(d,0,sizeof(*d)); |
482 |
d->vm = vm; |
483 |
|
484 |
/* Initialize flash based on model properties */ |
485 |
flashset_init(d,fm->mode,fm->nr_flash_bits,fm->blk_size, |
486 |
fm->id_manufacturer,fm->id_device); |
487 |
|
488 |
vm_object_init(&d->vm_obj); |
489 |
d->vm_obj.name = name; |
490 |
d->vm_obj.data = d; |
491 |
d->vm_obj.shutdown = (vm_shutdown_t)dev_bootflash_shutdown; |
492 |
|
493 |
if (!(d->filename = vm_build_filename(vm,name))) { |
494 |
vm_error(vm,"bootflash: unable to create filename.\n"); |
495 |
goto err_filename; |
496 |
} |
497 |
|
498 |
dev_init(&d->dev); |
499 |
d->dev.name = name; |
500 |
d->dev.priv_data = d; |
501 |
d->dev.phys_addr = paddr; |
502 |
d->dev.phys_len = fm->total_size; |
503 |
d->dev.handler = dev_bootflash_access; |
504 |
d->dev.fd = memzone_create_file(d->filename,d->dev.phys_len,&ptr); |
505 |
d->dev.host_addr = (m_iptr_t)ptr; |
506 |
d->dev.flags = VDEVICE_FLAG_NO_MTS_MMAP; |
507 |
|
508 |
if (d->dev.fd == -1) { |
509 |
vm_error(vm,"bootflash: unable to map file '%s'\n",d->filename); |
510 |
goto err_fd_create; |
511 |
} |
512 |
|
513 |
/* Map this device to the VM */ |
514 |
vm_bind_device(vm,&d->dev); |
515 |
vm_object_add(vm,&d->vm_obj); |
516 |
return(0); |
517 |
|
518 |
err_fd_create: |
519 |
free(d->filename); |
520 |
err_filename: |
521 |
free(d); |
522 |
return(-1); |
523 |
} |