1 |
/* |
2 |
* Cisco 7200 (Predator) simulation platform. |
3 |
* Copyright (c) 2005,2006 Christophe Fillot (cf@utc.fr) |
4 |
* |
5 |
* PCI devices. |
6 |
* |
7 |
* Very interesting docs: |
8 |
* http://www.science.unitn.it/~fiorella/guidelinux/tlk/node72.html |
9 |
* http://www.science.unitn.it/~fiorella/guidelinux/tlk/node76.html |
10 |
*/ |
11 |
|
12 |
#include <stdio.h> |
13 |
#include <stdlib.h> |
14 |
#include <string.h> |
15 |
#include <unistd.h> |
16 |
#include <sys/types.h> |
17 |
|
18 |
#include "mips64.h" |
19 |
#include "dynamips.h" |
20 |
#include "memory.h" |
21 |
#include "device.h" |
22 |
#include "vm.h" |
23 |
|
24 |
#define DEBUG_PCI 1 |
25 |
|
26 |
#define GET_PCI_ADDR(offset,mask) ((pci_bus->pci_addr >> offset) & mask) |
27 |
|
28 |
/* Trigger a PCI device IRQ */ |
29 |
void pci_dev_trigger_irq(vm_instance_t *vm,struct pci_device *dev) |
30 |
{ |
31 |
if (dev->irq != -1) |
32 |
vm_set_irq(vm,dev->irq); |
33 |
} |
34 |
|
35 |
/* Clear a PCI device IRQ */ |
36 |
void pci_dev_clear_irq(vm_instance_t *vm,struct pci_device *dev) |
37 |
{ |
38 |
if (dev->irq != -1) |
39 |
vm_clear_irq(vm,dev->irq); |
40 |
} |
41 |
|
42 |
/* Swapping function */ |
43 |
static inline m_uint32_t pci_swap(m_uint32_t val,int swap) |
44 |
{ |
45 |
return((swap) ? swap32(val) : val); |
46 |
} |
47 |
|
48 |
/* PCI bus lookup */ |
49 |
struct pci_bus *pci_bus_lookup(struct pci_bus *pci_bus_root,int bus) |
50 |
{ |
51 |
struct pci_bus *next_bus,*cur_bus = pci_bus_root; |
52 |
struct pci_bridge *bridge; |
53 |
|
54 |
while(cur_bus != NULL) { |
55 |
if (cur_bus->bus == bus) |
56 |
return cur_bus; |
57 |
|
58 |
/* Try busses behind PCI bridges */ |
59 |
next_bus = NULL; |
60 |
|
61 |
for(bridge=cur_bus->bridge_list;bridge;bridge=bridge->next) { |
62 |
/* |
63 |
* Specific case: final bridge with no checking of secondary |
64 |
* bus number. Dynamically programming. |
65 |
*/ |
66 |
if (bridge->skip_bus_check) { |
67 |
pci_bridge_set_bus_info(bridge,cur_bus->bus,bus,bus); |
68 |
bridge->skip_bus_check = FALSE; |
69 |
return bridge->pci_bus; |
70 |
} |
71 |
|
72 |
if ((bus >= bridge->sec_bus) && (bus <= bridge->sub_bus)) { |
73 |
next_bus = bridge->pci_bus; |
74 |
break; |
75 |
} |
76 |
} |
77 |
|
78 |
cur_bus = next_bus; |
79 |
} |
80 |
|
81 |
return NULL; |
82 |
} |
83 |
|
84 |
/* PCI device local lookup */ |
85 |
struct pci_device *pci_dev_lookup_local(struct pci_bus *pci_bus, |
86 |
int device,int function) |
87 |
{ |
88 |
struct pci_device *dev; |
89 |
|
90 |
for(dev=pci_bus->dev_list;dev;dev=dev->next) |
91 |
if ((dev->device == device) && (dev->function == function)) |
92 |
return dev; |
93 |
|
94 |
return NULL; |
95 |
} |
96 |
|
97 |
/* PCI Device lookup */ |
98 |
struct pci_device *pci_dev_lookup(struct pci_bus *pci_bus_root, |
99 |
int bus,int device,int function) |
100 |
{ |
101 |
struct pci_bus *req_bus; |
102 |
|
103 |
/* Find, try to find the request bus */ |
104 |
if (!(req_bus = pci_bus_lookup(pci_bus_root,bus))) |
105 |
return NULL; |
106 |
|
107 |
/* Walk through devices present on this bus */ |
108 |
return pci_dev_lookup_local(req_bus,device,function); |
109 |
} |
110 |
|
111 |
/* Handle the address register access */ |
112 |
void pci_dev_addr_handler(cpu_mips_t *cpu,struct pci_bus *pci_bus, |
113 |
u_int op_type,int swap,m_uint64_t *data) |
114 |
{ |
115 |
if (op_type == MTS_WRITE) |
116 |
pci_bus->pci_addr = pci_swap(*data,swap); |
117 |
else |
118 |
*data = pci_swap(pci_bus->pci_addr,swap); |
119 |
} |
120 |
|
121 |
/* |
122 |
* Handle the data register access. |
123 |
* |
124 |
* The address of requested register is first written at address 0xcf8 |
125 |
* (with pci_dev_addr_handler). |
126 |
* |
127 |
* The data is read/written at address 0xcfc. |
128 |
*/ |
129 |
void pci_dev_data_handler(cpu_mips_t *cpu,struct pci_bus *pci_bus, |
130 |
u_int op_type,int swap,m_uint64_t *data) |
131 |
{ |
132 |
struct pci_device *dev; |
133 |
int bus,device,function,reg; |
134 |
|
135 |
if (op_type == MTS_READ) |
136 |
*data = 0; |
137 |
|
138 |
/* |
139 |
* http://www.mega-tokyo.com/osfaq2/index.php/PciSectionOfPentiumVme |
140 |
* |
141 |
* 31 : Enable Bit |
142 |
* 30 - 24 : Reserved |
143 |
* 23 - 16 : Bus Number |
144 |
* 15 - 11 : Device Number |
145 |
* 10 - 8 : Function Number |
146 |
* 7 - 2 : Register Number |
147 |
* 1 - 0 : always 00 |
148 |
*/ |
149 |
bus = GET_PCI_ADDR(16,0xff); |
150 |
device = GET_PCI_ADDR(11,0x1f); |
151 |
function = GET_PCI_ADDR(8,0x7); |
152 |
reg = GET_PCI_ADDR(0,0xff); |
153 |
|
154 |
/* Find the corresponding PCI device */ |
155 |
dev = pci_dev_lookup(pci_bus,bus,device,function); |
156 |
|
157 |
#if DEBUG_PCI |
158 |
if (op_type == MTS_READ) { |
159 |
cpu_log(cpu,"PCI","read request at pc=0x%llx: " |
160 |
"bus=%d,device=%d,function=%d,reg=0x%2.2x\n", |
161 |
cpu->pc, bus, device, function, reg); |
162 |
} else { |
163 |
cpu_log(cpu,"PCI","write request (data=0x%8.8x) at pc=0x%llx: " |
164 |
"bus=%d,device=%d,function=%d,reg=0x%2.2x\n", |
165 |
pci_swap(*data,swap), cpu->pc, bus, device, function, reg); |
166 |
} |
167 |
#endif |
168 |
|
169 |
if (!dev) { |
170 |
if (op_type == MTS_READ) { |
171 |
cpu_log(cpu,"PCI","read request for unknown device at pc=0x%llx " |
172 |
"(bus=%d,device=%d,function=%d,reg=0x%2.2x).\n", |
173 |
cpu->pc, bus, device, function, reg); |
174 |
} else { |
175 |
cpu_log(cpu,"PCI","write request (data=0x%8.8x) for unknown device " |
176 |
"at pc=0x%llx (bus=%d,device=%d,function=%d,reg=0x%2.2x).\n", |
177 |
pci_swap(*data,swap), cpu->pc, bus, device, function, reg); |
178 |
} |
179 |
|
180 |
/* Returns an invalid device ID */ |
181 |
if ((op_type == MTS_READ) && (reg == PCI_REG_ID)) |
182 |
*data = 0xffffffff; |
183 |
} else { |
184 |
if (op_type == MTS_WRITE) { |
185 |
if (dev->write_register != NULL) |
186 |
dev->write_register(cpu,dev,reg,pci_swap(*data,swap)); |
187 |
} else { |
188 |
if (reg == PCI_REG_ID) |
189 |
*data = pci_swap((dev->product_id << 16) | dev->vendor_id,swap); |
190 |
else { |
191 |
if (dev->read_register != NULL) |
192 |
*data = pci_swap(dev->read_register(cpu,dev,reg),swap); |
193 |
} |
194 |
} |
195 |
} |
196 |
} |
197 |
|
198 |
/* Add a PCI bridge */ |
199 |
struct pci_bridge *pci_bridge_add(struct pci_bus *pci_bus) |
200 |
{ |
201 |
struct pci_bridge *bridge; |
202 |
|
203 |
if (!pci_bus) |
204 |
return NULL; |
205 |
|
206 |
if (!(bridge = malloc(sizeof(*bridge)))) { |
207 |
fprintf(stderr,"pci_bridge_add: unable to create new PCI bridge.\n"); |
208 |
return NULL; |
209 |
} |
210 |
|
211 |
memset(bridge,0,sizeof(*bridge)); |
212 |
bridge->pri_bus = pci_bus->bus; |
213 |
bridge->sec_bus = -1; |
214 |
bridge->sub_bus = -1; |
215 |
bridge->pci_bus = NULL; |
216 |
|
217 |
/* Insert the bridge in the double-linked list */ |
218 |
bridge->next = pci_bus->bridge_list; |
219 |
bridge->pprev = &pci_bus->bridge_list; |
220 |
|
221 |
if (pci_bus->bridge_list != NULL) |
222 |
pci_bus->bridge_list->pprev = &bridge->next; |
223 |
|
224 |
pci_bus->bridge_list = bridge; |
225 |
return bridge; |
226 |
} |
227 |
|
228 |
/* Remove a PCI bridge from the double-linked list */ |
229 |
static inline void pci_bridge_remove_from_list(struct pci_bridge *bridge) |
230 |
{ |
231 |
if (bridge->next) |
232 |
bridge->next->pprev = bridge->pprev; |
233 |
|
234 |
if (bridge->pprev) |
235 |
*(bridge->pprev) = bridge->next; |
236 |
} |
237 |
|
238 |
/* Remove a PCI bridge */ |
239 |
void pci_bridge_remove(struct pci_bridge *bridge) |
240 |
{ |
241 |
if (bridge != NULL) { |
242 |
pci_bridge_remove_from_list(bridge); |
243 |
free(bridge); |
244 |
} |
245 |
} |
246 |
|
247 |
/* Map secondary bus to a PCI bridge */ |
248 |
void pci_bridge_map_bus(struct pci_bridge *bridge,struct pci_bus *pci_bus) |
249 |
{ |
250 |
if (bridge != NULL) { |
251 |
bridge->pci_bus = pci_bus; |
252 |
|
253 |
if (bridge->pci_bus != NULL) |
254 |
bridge->pci_bus->bus = bridge->sec_bus; |
255 |
} |
256 |
} |
257 |
|
258 |
/* Set PCI bridge bus info */ |
259 |
void pci_bridge_set_bus_info(struct pci_bridge *bridge, |
260 |
int pri_bus,int sec_bus,int sub_bus) |
261 |
{ |
262 |
if (bridge != NULL) { |
263 |
bridge->pri_bus = pri_bus; |
264 |
bridge->sec_bus = sec_bus; |
265 |
bridge->sub_bus = sub_bus; |
266 |
|
267 |
if (bridge->pci_bus != NULL) |
268 |
bridge->pci_bus->bus = bridge->sec_bus; |
269 |
} |
270 |
} |
271 |
|
272 |
/* Add a PCI device */ |
273 |
struct pci_device * |
274 |
pci_dev_add(struct pci_bus *pci_bus,char *name, |
275 |
u_int vendor_id,u_int product_id, |
276 |
int device,int function,int irq, |
277 |
void *priv_data,pci_init_t init, |
278 |
pci_reg_read_t read_register, |
279 |
pci_reg_write_t write_register) |
280 |
{ |
281 |
struct pci_device *dev; |
282 |
|
283 |
if (!pci_bus) |
284 |
return NULL; |
285 |
|
286 |
if ((dev = pci_dev_lookup_local(pci_bus,device,function)) != NULL) { |
287 |
fprintf(stderr,"pci_dev_add: bus %s, device %d, function %d already " |
288 |
"registered (device '%s').\n", |
289 |
pci_bus->name,device,function,dev->name); |
290 |
return NULL; |
291 |
} |
292 |
|
293 |
/* we can create safely the new device */ |
294 |
if (!(dev = malloc(sizeof(*dev)))) { |
295 |
fprintf(stderr,"pci_dev_add: unable to create new PCI device.\n"); |
296 |
return NULL; |
297 |
} |
298 |
|
299 |
memset(dev,0,sizeof(*dev)); |
300 |
dev->name = name; |
301 |
dev->vendor_id = vendor_id; |
302 |
dev->product_id = product_id; |
303 |
dev->pci_bus = pci_bus; |
304 |
dev->device = device; |
305 |
dev->function = function; |
306 |
dev->irq = irq; |
307 |
dev->priv_data = priv_data; |
308 |
dev->init = init; |
309 |
dev->read_register = read_register; |
310 |
dev->write_register = write_register; |
311 |
|
312 |
/* Insert the device in the double-linked list */ |
313 |
dev->next = pci_bus->dev_list; |
314 |
dev->pprev = &pci_bus->dev_list; |
315 |
|
316 |
if (pci_bus->dev_list != NULL) |
317 |
pci_bus->dev_list->pprev = &dev->next; |
318 |
|
319 |
pci_bus->dev_list = dev; |
320 |
|
321 |
if (init) init(dev); |
322 |
return dev; |
323 |
} |
324 |
|
325 |
/* Add a basic PCI device that just returns a Vendor/Product ID */ |
326 |
struct pci_device * |
327 |
pci_dev_add_basic(struct pci_bus *pci_bus, |
328 |
char *name,u_int vendor_id,u_int product_id, |
329 |
int device,int function) |
330 |
{ |
331 |
return(pci_dev_add(pci_bus,name,vendor_id,product_id, |
332 |
device,function,-1,NULL, |
333 |
NULL,NULL,NULL)); |
334 |
} |
335 |
|
336 |
/* Remove a device from the double-linked list */ |
337 |
static inline void pci_dev_remove_from_list(struct pci_device *dev) |
338 |
{ |
339 |
if (dev->next) |
340 |
dev->next->pprev = dev->pprev; |
341 |
|
342 |
if (dev->pprev) |
343 |
*(dev->pprev) = dev->next; |
344 |
} |
345 |
|
346 |
/* Remove a PCI device */ |
347 |
void pci_dev_remove(struct pci_device *dev) |
348 |
{ |
349 |
if (dev != NULL) { |
350 |
pci_dev_remove_from_list(dev); |
351 |
free(dev); |
352 |
} |
353 |
} |
354 |
|
355 |
/* Remove a PCI device given its ID (bus,device,function) */ |
356 |
int pci_dev_remove_by_id(struct pci_bus *pci_bus, |
357 |
int bus,int device,int function) |
358 |
{ |
359 |
struct pci_device *dev; |
360 |
|
361 |
if (!(dev = pci_dev_lookup(pci_bus,bus,device,function))) |
362 |
return(-1); |
363 |
|
364 |
pci_dev_remove(dev); |
365 |
return(0); |
366 |
} |
367 |
|
368 |
/* Remove a PCI device given its name */ |
369 |
int pci_dev_remove_by_name(struct pci_bus *pci_bus,char *name) |
370 |
{ |
371 |
struct pci_device *dev,*next; |
372 |
int count = 0; |
373 |
|
374 |
for(dev=pci_bus->dev_list;dev;dev=next) { |
375 |
next = dev->next; |
376 |
|
377 |
if (!strcmp(dev->name,name)) { |
378 |
pci_dev_remove(dev); |
379 |
count++; |
380 |
} |
381 |
} |
382 |
|
383 |
return(count); |
384 |
} |
385 |
|
386 |
/* Create a PCI bus */ |
387 |
struct pci_bus *pci_bus_create(char *name,int bus) |
388 |
{ |
389 |
struct pci_bus *d; |
390 |
|
391 |
if (!(d = malloc(sizeof(*d)))) { |
392 |
fprintf(stderr,"pci_bus_create: unable to create PCI info.\n"); |
393 |
return NULL; |
394 |
} |
395 |
|
396 |
memset(d,0,sizeof(*d)); |
397 |
d->name = strdup(name); |
398 |
d->bus = bus; |
399 |
return d; |
400 |
} |
401 |
|
402 |
/* Delete a PCI bus */ |
403 |
void pci_bus_remove(struct pci_bus *pci_bus) |
404 |
{ |
405 |
struct pci_device *dev,*next; |
406 |
struct pci_bridge *bridge,*next_bridge; |
407 |
|
408 |
if (pci_bus) { |
409 |
/* Remove all devices */ |
410 |
for(dev=pci_bus->dev_list;dev;dev=next) { |
411 |
next = dev->next; |
412 |
free(dev); |
413 |
} |
414 |
|
415 |
/* Remove all bridges */ |
416 |
for(bridge=pci_bus->bridge_list;bridge;bridge=next_bridge) { |
417 |
next_bridge = bridge->next; |
418 |
free(bridge); |
419 |
} |
420 |
|
421 |
/* Free the structure itself */ |
422 |
free(pci_bus->name); |
423 |
free(pci_bus); |
424 |
} |
425 |
} |
426 |
|
427 |
/* Read a configuration register of a PCI bridge */ |
428 |
static m_uint32_t pci_bridge_read_reg(cpu_mips_t *cpu,struct pci_device *dev, |
429 |
int reg) |
430 |
{ |
431 |
struct pci_bridge *bridge = dev->priv_data; |
432 |
m_uint32_t val = 0; |
433 |
|
434 |
switch(reg) { |
435 |
case 0x18: |
436 |
return(bridge->cfg_reg_bus); |
437 |
default: |
438 |
if (bridge->fallback_read != NULL) |
439 |
val = bridge->fallback_read(cpu,dev,reg); |
440 |
|
441 |
/* Returns appropriate PCI bridge class code if nothing defined */ |
442 |
if ((reg == 0x08) && !val) |
443 |
val = 0x06040000; |
444 |
|
445 |
return(val); |
446 |
} |
447 |
} |
448 |
|
449 |
/* Write a configuration register of a PCI bridge */ |
450 |
static void pci_bridge_write_reg(cpu_mips_t *cpu,struct pci_device *dev, |
451 |
int reg,m_uint32_t value) |
452 |
{ |
453 |
struct pci_bridge *bridge = dev->priv_data; |
454 |
u_int pri_bus,sec_bus,sub_bus; |
455 |
|
456 |
switch(reg) { |
457 |
case 0x18: |
458 |
bridge->cfg_reg_bus = value; |
459 |
sub_bus = (value >> 16) & 0xFF; |
460 |
sec_bus = (value >> 8) & 0xFF; |
461 |
pri_bus = value & 0xFF; |
462 |
|
463 |
/* Modify the PCI bridge settings */ |
464 |
vm_log(cpu->vm,"PCI", |
465 |
"PCI bridge %d,%d,%d -> pri: %2.2u, sec: %2.2u, sub: %2.2u\n", |
466 |
dev->pci_bus->bus,dev->device,dev->function, |
467 |
pri_bus,sec_bus,sub_bus); |
468 |
|
469 |
pci_bridge_set_bus_info(bridge,pri_bus,sec_bus,sub_bus); |
470 |
break; |
471 |
|
472 |
default: |
473 |
if (bridge->fallback_write != NULL) |
474 |
bridge->fallback_write(cpu,dev,reg,value); |
475 |
} |
476 |
} |
477 |
|
478 |
/* Create a PCI bridge device */ |
479 |
struct pci_device *pci_bridge_create_dev(struct pci_bus *pci_bus,char *name, |
480 |
u_int vendor_id,u_int product_id, |
481 |
int device,int function, |
482 |
struct pci_bus *sec_bus, |
483 |
pci_reg_read_t fallback_read, |
484 |
pci_reg_write_t fallback_write) |
485 |
{ |
486 |
struct pci_bridge *bridge; |
487 |
struct pci_device *dev; |
488 |
|
489 |
/* Create the PCI bridge structure */ |
490 |
if (!(bridge = pci_bridge_add(pci_bus))) |
491 |
return NULL; |
492 |
|
493 |
/* Create the PCI device corresponding to the bridge */ |
494 |
dev = pci_dev_add(pci_bus,name,vendor_id,product_id,device,function,-1, |
495 |
bridge,NULL,pci_bridge_read_reg,pci_bridge_write_reg); |
496 |
|
497 |
if (!dev) |
498 |
goto err_pci_dev; |
499 |
|
500 |
/* Keep the associated PCI device for this bridge */ |
501 |
bridge->pci_dev = dev; |
502 |
|
503 |
/* Set the fallback functions */ |
504 |
bridge->fallback_read = fallback_read; |
505 |
bridge->fallback_write = fallback_write; |
506 |
|
507 |
/* Map the secondary bus (disabled at startup) */ |
508 |
pci_bridge_map_bus(bridge,sec_bus); |
509 |
return dev; |
510 |
|
511 |
err_pci_dev: |
512 |
pci_bridge_remove(bridge); |
513 |
return NULL; |
514 |
} |
515 |
|
516 |
/* Show PCI device list of the specified bus */ |
517 |
static void pci_bus_show_dev_list(struct pci_bus *pci_bus) |
518 |
{ |
519 |
struct pci_device *dev; |
520 |
struct pci_bridge *bridge; |
521 |
char bus_id[32]; |
522 |
|
523 |
if (!pci_bus) |
524 |
return; |
525 |
|
526 |
if (pci_bus->bus != -1) { |
527 |
snprintf(bus_id,sizeof(bus_id),"%2d",pci_bus->bus); |
528 |
} else { |
529 |
strcpy(bus_id,"XX"); |
530 |
} |
531 |
|
532 |
for(dev=pci_bus->dev_list;dev;dev=dev->next) { |
533 |
printf(" %-18s: ID %4.4x:%4.4x, Bus %s, Dev. %2d, Func. %2d", |
534 |
dev->name,dev->vendor_id,dev->product_id, |
535 |
bus_id,dev->device,dev->function); |
536 |
|
537 |
if (dev->irq != -1) |
538 |
printf(", IRQ: %d\n",dev->irq); |
539 |
else |
540 |
printf("\n"); |
541 |
} |
542 |
|
543 |
for(bridge=pci_bus->bridge_list;bridge;bridge=bridge->next) |
544 |
pci_bus_show_dev_list(bridge->pci_bus); |
545 |
} |
546 |
|
547 |
/* Show PCI device list */ |
548 |
void pci_dev_show_list(struct pci_bus *pci_bus) |
549 |
{ |
550 |
if (!pci_bus) |
551 |
return; |
552 |
|
553 |
printf("PCI Bus \"%s\" Device list:\n",pci_bus->name); |
554 |
pci_bus_show_dev_list(pci_bus); |
555 |
printf("\n"); |
556 |
} |