]>
Commit | Line | Data |
---|---|---|
c906108c SS |
1 | /* This file is part of the program psim. |
2 | ||
3 | Copyright (C) 1994-1996, Andrew Cagney <[email protected]> | |
4 | ||
5 | This program is free software; you can redistribute it and/or modify | |
6 | it under the terms of the GNU General Public License as published by | |
3fd725ef | 7 | the Free Software Foundation; either version 3 of the License, or |
c906108c SS |
8 | (at your option) any later version. |
9 | ||
10 | This program is distributed in the hope that it will be useful, | |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | GNU General Public License for more details. | |
14 | ||
15 | You should have received a copy of the GNU General Public License | |
51b318de | 16 | along with this program; if not, see <http://www.gnu.org/licenses/>. |
c906108c SS |
17 | |
18 | */ | |
19 | ||
20 | ||
21 | #ifndef _HW_COM_C_ | |
22 | #define _HW_COM_C_ | |
23 | ||
24 | #ifndef STATIC_INLINE_HW_COM | |
25 | #define STATIC_INLINE_HW_COM STATIC_INLINE | |
26 | #endif | |
27 | ||
28 | #include "device_table.h" | |
29 | ||
c906108c | 30 | #include <string.h> |
c906108c SS |
31 | #ifdef HAVE_UNISTD_H |
32 | #include <unistd.h> | |
33 | #endif | |
c906108c | 34 | #include <stdlib.h> |
c906108c SS |
35 | |
36 | /* DEVICE | |
37 | ||
38 | ||
39 | com - '550 compatible serial device | |
40 | ||
41 | ||
42 | DESCRIPTION | |
43 | ||
44 | ||
45 | Models the basics of the 8 register '550 serial device. The model | |
46 | includes an interrupt line, input and output fifos, and status | |
47 | information. | |
48 | ||
49 | Independent configuration of the devices input and output streams is | |
50 | allowed: use either the console or a file (buffered or unbuffered) as | |
51 | the data source/sink; specify the real-time delay between each character | |
52 | transfer. | |
53 | ||
54 | When the devices input stream is being taken from a file, the end of | |
55 | file is signaled by a loss of carrier (the loss of carrier may be | |
56 | incorrectly proceeded by a single null character). | |
57 | ||
58 | ||
59 | PROPERTIES | |
60 | ||
61 | ||
62 | reg = <address> <size> ... (optional - note 1) | |
63 | ||
64 | List of <address> <size> pairs. Each pair specifies an address for | |
65 | the devices 8 registers. The address should be 8 byte aligned. | |
66 | ||
67 | ||
68 | alternate-reg = <address> <size> ... (optional - note 1) | |
69 | ||
70 | Alternative addreses for the registers. | |
71 | ||
72 | ||
73 | assigned-addresses = <address> <size> ... (optional - note 1) | |
74 | ||
75 | On a PCI bus, this property specifies the addresses assigned to the | |
76 | device. The values reflect the devices configuration base registers. | |
77 | ||
78 | Note 1: At least one of "assigned-addresses", "reg" or "alternative-reg" | |
79 | must be specified. If "assigned-addresses" is specified the other | |
80 | address specifications are ignored. | |
81 | ||
82 | ||
83 | input-file = <file-name> (optional) | |
84 | ||
85 | File to take all serial port input from (instead of the simulation | |
86 | console). | |
87 | ||
88 | ||
89 | output-file = <file-name> (optional) | |
90 | ||
91 | File to send all output to (instead of the simulation console). | |
92 | ||
93 | ||
94 | input-buffering = "unbuffered" (optional) | |
95 | ||
96 | Specifying "unbuffered" buffering disables buffering on the serial | |
576054f1 | 97 | devices input stream (all data is immediately read). In the future, |
c906108c SS |
98 | this option may be used to provide input buffering alternatives. |
99 | ||
100 | ||
101 | output-buffering = "unbuffered" (optional) | |
102 | ||
103 | Specifying "unbuffered" buffering disables buffering on the serial | |
576054f1 | 104 | devices output stream (all data is immediately written). In the future, |
c906108c SS |
105 | this option may be extended to include other buffering alternatives. |
106 | ||
107 | ||
108 | input-delay = <integer-delay> (optional) | |
109 | ||
110 | Specify the number of ticks after the current character has been | |
111 | read from the serial port that the next character becomes | |
112 | available. | |
113 | ||
114 | ||
115 | output-delay = <integer-delay> (optional) | |
116 | ||
117 | Specify the number of ticks after a character has been written to | |
118 | the empty output fifo that the fifo finishes draining. Any | |
119 | characters written to the output fifo before it has drained will | |
120 | not be lost and will still be displayed. | |
121 | ||
122 | ||
123 | EXAMPLES | |
124 | ||
125 | ||
126 | | /iobus@0xf0000000/com@0x3000/reg 0x3000 8 | |
127 | ||
128 | Create a simple console device at address <<0x3000>> within | |
129 | <<iobus>>. Since iobus starts at address <<0xf0000000>> the | |
130 | absolute address of the serial port will be <<0xf0003000>>. | |
131 | ||
132 | The device will always be ready for I/O (no delay properties specified) | |
133 | and both the input and output streams will use the simulation console | |
134 | (no file properties). | |
135 | ||
136 | ||
137 | | $ psim \ | |
138 | | -o '/cpus/cpu@0' \ | |
139 | | -o '/iobus@0xf0000000/com@0x4000/reg 0x4000 8' \ | |
140 | | -o '/iobus@0xf0000000/com@0x4000/input-file /etc/passwd' \ | |
141 | | -o '/iobus@0xf0000000/com@0x4000/input-delay 1000' \ | |
142 | | -o '/iobus@0xf0000000/com@0x4000 > 0 int /cpus/cpu@0x0' \ | |
143 | | psim-test/hw-com/cat.be 0xf0004000 | |
144 | ||
145 | The serial port (at address <<0xf0004000>> is configured so that it | |
146 | takes its input from the file <</etc/passwd>> while its output is | |
147 | allowed to appear on the simulation console. | |
148 | ||
149 | The node <</cpus/cpu@0>> was explicitly specified to ensure that it had | |
150 | been created before any interrupts were attached to it. | |
151 | ||
152 | The program <<psim-test/hw-com/cat>> copies any characters on the serial | |
153 | port's input (<</etc/passwd>>) to its output (the console). | |
154 | Consequently, the aove program will display the contents of the file | |
155 | <</etc/passwd>> on the screen. | |
156 | ||
157 | ||
158 | BUGS | |
159 | ||
160 | ||
161 | IEEE 1275 requires that a device on a PCI bus have, as its first reg | |
162 | entry, the address of its configuration space registers. Currently, | |
163 | this device does not even implement configuration registers. | |
164 | ||
165 | This model does not attempt to model the '550's input and output fifos. | |
166 | Instead, the input fifo is limited to a single character at a time, | |
167 | while the output fifo is effectivly infinite. Consequently, unlike the | |
168 | '550, this device will not discard output characters once a stream of 16 | |
169 | have been written to the data output register. | |
170 | ||
171 | The input and output can only be taken from a file (or the current | |
172 | terminal device). In the future, the <<com>> device should allow the | |
173 | specification of other data streams (such as an xterm or TK window). | |
174 | ||
175 | The input blocks if no data is available. | |
176 | ||
177 | Interrupts have not been tested. | |
178 | ||
179 | */ | |
180 | ||
181 | enum { | |
182 | max_hw_com_registers = 8, | |
183 | }; | |
184 | ||
185 | typedef struct _com_port { | |
186 | int ready; | |
187 | int delay; | |
188 | int interrupting; | |
189 | FILE *file; | |
190 | } com_port; | |
191 | ||
192 | typedef struct _com_modem { | |
193 | int carrier; | |
194 | int carrier_changed; | |
195 | int interrupting; | |
196 | } com_modem; | |
197 | ||
198 | typedef struct _hw_com_device { | |
199 | com_port input; | |
200 | com_port output; | |
201 | com_modem modem; | |
202 | char dlab[2]; | |
203 | char reg[max_hw_com_registers]; | |
204 | int interrupting; | |
205 | } hw_com_device; | |
206 | ||
207 | ||
208 | static void | |
209 | hw_com_device_init_data(device *me) | |
210 | { | |
211 | hw_com_device *com = (hw_com_device*)device_data(me); | |
212 | /* clean up */ | |
213 | if (com->output.file != NULL) | |
214 | fclose(com->output.file); | |
215 | if (com->input.file != NULL) | |
216 | fclose(com->input.file); | |
217 | memset(com, 0, sizeof(hw_com_device)); | |
218 | ||
219 | /* the fifo speed */ | |
220 | com->output.delay = (device_find_property(me, "output-delay") != NULL | |
221 | ? device_find_integer_property(me, "output-delay") | |
222 | : 0); | |
223 | com->input.delay = (device_find_property(me, "input-delay") != NULL | |
224 | ? device_find_integer_property(me, "input-delay") | |
225 | : 0); | |
226 | ||
227 | /* the data source/sink */ | |
228 | if (device_find_property(me, "input-file") != NULL) { | |
229 | const char *input_file = device_find_string_property(me, "input-file"); | |
230 | com->input.file = fopen(input_file, "r"); | |
231 | if (com->input.file == NULL) | |
232 | device_error(me, "Problem opening input file %s\n", input_file); | |
233 | if (device_find_property(me, "input-buffering") != NULL) { | |
234 | const char *buffering = device_find_string_property(me, "input-buffering"); | |
235 | if (strcmp(buffering, "unbuffered") == 0) | |
236 | setbuf(com->input.file, NULL); | |
237 | } | |
238 | } | |
239 | if (device_find_property(me, "output-file") != NULL) { | |
240 | const char *output_file = device_find_string_property(me, "output-file"); | |
241 | com->output.file = fopen(output_file, "w"); | |
61ca1de7 | 242 | if (com->output.file == NULL) |
c906108c SS |
243 | device_error(me, "Problem opening output file %s\n", output_file); |
244 | if (device_find_property(me, "output-buffering") != NULL) { | |
245 | const char *buffering = device_find_string_property(me, "output-buffering"); | |
246 | if (strcmp(buffering, "unbuffered") == 0) | |
247 | setbuf(com->output.file, NULL); | |
248 | } | |
249 | } | |
250 | ||
251 | /* ready from the start */ | |
252 | com->input.ready = 1; | |
253 | com->modem.carrier = 1; | |
254 | com->output.ready = 1; | |
255 | } | |
256 | ||
257 | ||
258 | static void | |
259 | update_com_interrupts(device *me, | |
260 | hw_com_device *com) | |
261 | { | |
262 | int interrupting; | |
263 | com->modem.interrupting = (com->modem.carrier_changed && (com->reg[1] & 0x80)); | |
264 | com->input.interrupting = (com->input.ready && (com->reg[1] & 0x1)); | |
265 | com->output.interrupting = (com->output.ready && (com->reg[1] & 0x2)); | |
266 | interrupting = (com->input.interrupting | |
267 | || com->output.interrupting | |
268 | || com->modem.interrupting); | |
269 | ||
270 | if (interrupting) { | |
271 | if (!com->interrupting) { | |
272 | device_interrupt_event(me, 0 /*port*/, 1 /*value*/, NULL, 0); | |
273 | } | |
274 | } | |
275 | else /*!interrupting*/ { | |
276 | if (com->interrupting) | |
277 | device_interrupt_event(me, 0 /*port*/, 0 /*value*/, NULL, 0); | |
278 | } | |
279 | com->interrupting = interrupting; | |
280 | } | |
281 | ||
282 | ||
283 | static void | |
284 | make_read_ready(void *data) | |
285 | { | |
286 | device *me = (device*)data; | |
287 | hw_com_device *com = (hw_com_device*)device_data(me); | |
288 | com->input.ready = 1; | |
289 | update_com_interrupts(me, com); | |
290 | } | |
291 | ||
292 | static void | |
293 | read_com(device *me, | |
294 | hw_com_device *com, | |
295 | unsigned_word a, | |
296 | char val[1]) | |
297 | { | |
298 | unsigned_word addr = a % 8; | |
299 | ||
300 | /* the divisor latch is special */ | |
301 | if (com->reg[3] & 0x8 && addr < 2) { | |
302 | *val = com->dlab[addr]; | |
303 | return; | |
304 | } | |
305 | ||
306 | switch (addr) { | |
307 | ||
308 | case 0: | |
309 | /* fifo */ | |
310 | if (!com->modem.carrier) | |
311 | *val = '\0'; | |
312 | if (com->input.ready) { | |
313 | /* read the char in */ | |
314 | if (com->input.file == NULL) { | |
315 | if (sim_io_read_stdin(val, 1) < 0) | |
316 | com->modem.carrier_changed = 1; | |
317 | } | |
318 | else { | |
319 | if (fread(val, 1, 1, com->input.file) == 0) | |
320 | com->modem.carrier_changed = 1; | |
321 | } | |
322 | /* setup for next read */ | |
323 | if (com->modem.carrier_changed) { | |
324 | /* once lost carrier, never ready */ | |
325 | com->modem.carrier = 0; | |
326 | com->input.ready = 0; | |
327 | *val = '\0'; | |
328 | } | |
329 | else if (com->input.delay > 0) { | |
330 | com->input.ready = 0; | |
331 | device_event_queue_schedule(me, com->input.delay, make_read_ready, me); | |
332 | } | |
333 | } | |
334 | else { | |
335 | /* discard it? */ | |
336 | /* overflow input fifo? */ | |
337 | *val = '\0'; | |
338 | } | |
339 | break; | |
340 | ||
341 | case 2: | |
342 | /* interrupt ident */ | |
343 | if (com->interrupting) { | |
344 | if (com->input.interrupting) | |
345 | *val = 0x4; | |
346 | else if (com->output.interrupting) | |
347 | *val = 0x2; | |
348 | else if (com->modem.interrupting == 0) | |
349 | *val = 0; | |
350 | else | |
351 | device_error(me, "bad elif for interrupts\n"); | |
352 | } | |
353 | else | |
354 | *val = 0x1; | |
355 | break; | |
356 | ||
357 | case 5: | |
358 | /* line status */ | |
359 | *val = ((com->input.ready ? 0x1 : 0) | |
360 | | (com->output.ready ? 0x60 : 0) | |
361 | ); | |
362 | break; | |
363 | ||
364 | case 6: | |
365 | /* modem status */ | |
366 | *val = ((com->modem.carrier_changed ? 0x08 : 0) | |
367 | | (com->modem.carrier ? 0x80 : 0) | |
368 | ); | |
369 | com->modem.carrier_changed = 0; | |
370 | break; | |
371 | ||
372 | default: | |
373 | *val = com->reg[addr]; | |
374 | break; | |
375 | ||
376 | } | |
377 | update_com_interrupts(me, com); | |
378 | } | |
379 | ||
380 | static unsigned | |
381 | hw_com_io_read_buffer_callback(device *me, | |
382 | void *dest, | |
383 | int space, | |
384 | unsigned_word addr, | |
385 | unsigned nr_bytes, | |
386 | cpu *processor, | |
387 | unsigned_word cia) | |
388 | { | |
389 | hw_com_device *com = device_data(me); | |
390 | int i; | |
391 | for (i = 0; i < nr_bytes; i++) { | |
392 | read_com(me, com, addr + i, &((char*)dest)[i]); | |
393 | } | |
394 | return nr_bytes; | |
395 | } | |
396 | ||
397 | ||
398 | static void | |
399 | make_write_ready(void *data) | |
400 | { | |
401 | device *me = (device*)data; | |
402 | hw_com_device *com = (hw_com_device*)device_data(me); | |
403 | com->output.ready = 1; | |
404 | update_com_interrupts(me, com); | |
405 | } | |
406 | ||
407 | static void | |
408 | write_com(device *me, | |
409 | hw_com_device *com, | |
410 | unsigned_word a, | |
411 | char val) | |
412 | { | |
413 | unsigned_word addr = a % 8; | |
414 | ||
415 | /* the divisor latch is special */ | |
416 | if (com->reg[3] & 0x8 && addr < 2) { | |
417 | com->dlab[addr] = val; | |
418 | return; | |
419 | } | |
420 | ||
421 | switch (addr) { | |
422 | ||
423 | case 0: | |
424 | /* fifo */ | |
425 | if (com->output.file == NULL) { | |
426 | sim_io_write_stdout(&val, 1); | |
427 | } | |
428 | else { | |
429 | fwrite(&val, 1, 1, com->output.file); | |
430 | } | |
431 | /* setup for next write */ | |
432 | if (com->output.ready && com->output.delay > 0) { | |
433 | com->output.ready = 0; | |
434 | device_event_queue_schedule(me, com->output.delay, make_write_ready, me); | |
435 | } | |
436 | break; | |
437 | ||
438 | default: | |
439 | com->reg[addr] = val; | |
440 | break; | |
441 | ||
442 | } | |
443 | update_com_interrupts(me, com); | |
444 | } | |
445 | ||
446 | static unsigned | |
447 | hw_com_io_write_buffer_callback(device *me, | |
448 | const void *source, | |
449 | int space, | |
450 | unsigned_word addr, | |
451 | unsigned nr_bytes, | |
452 | cpu *processor, | |
453 | unsigned_word cia) | |
454 | { | |
455 | hw_com_device *com = device_data(me); | |
456 | int i; | |
457 | for (i = 0; i < nr_bytes; i++) { | |
458 | write_com(me, com, addr + i, ((char*)source)[i]); | |
459 | } | |
460 | return nr_bytes; | |
461 | } | |
462 | ||
463 | ||
464 | /* instances of the hw_com device */ | |
465 | ||
466 | static void | |
467 | hw_com_instance_delete(device_instance *instance) | |
468 | { | |
469 | /* nothing to delete, the hw_com is attached to the device */ | |
470 | return; | |
471 | } | |
472 | ||
473 | static int | |
474 | hw_com_instance_read(device_instance *instance, | |
475 | void *buf, | |
476 | unsigned_word len) | |
477 | { | |
478 | device *me = device_instance_device(instance); | |
479 | hw_com_device *com = device_data(me); | |
480 | if (com->input.file == NULL) | |
481 | return sim_io_read_stdin(buf, len); | |
482 | else { | |
483 | return fread(buf, 1, len, com->input.file); | |
484 | } | |
485 | } | |
486 | ||
487 | static int | |
488 | hw_com_instance_write(device_instance *instance, | |
489 | const void *buf, | |
490 | unsigned_word len) | |
491 | { | |
492 | device *me = device_instance_device(instance); | |
493 | hw_com_device *com = device_data(me); | |
494 | if (com->output.file == NULL) | |
495 | return sim_io_write_stdout(buf, len); | |
496 | else { | |
497 | return fwrite(buf, 1, len, com->output.file); | |
498 | } | |
499 | } | |
500 | ||
501 | static const device_instance_callbacks hw_com_instance_callbacks = { | |
502 | hw_com_instance_delete, | |
503 | hw_com_instance_read, | |
504 | hw_com_instance_write, | |
505 | }; | |
506 | ||
507 | static device_instance * | |
508 | hw_com_create_instance(device *me, | |
509 | const char *path, | |
510 | const char *args) | |
511 | { | |
512 | /* point an instance directly at the device */ | |
513 | return device_create_instance_from(me, NULL, | |
514 | device_data(me), | |
515 | path, args, | |
516 | &hw_com_instance_callbacks); | |
517 | } | |
518 | ||
519 | ||
520 | static device_callbacks const hw_com_callbacks = { | |
521 | { generic_device_init_address, | |
522 | hw_com_device_init_data }, | |
523 | { NULL, }, /* address */ | |
524 | { hw_com_io_read_buffer_callback, | |
525 | hw_com_io_write_buffer_callback, }, | |
526 | { NULL, }, /* DMA */ | |
527 | { NULL, }, /* interrupt */ | |
528 | { NULL, }, /* unit */ | |
529 | hw_com_create_instance, | |
530 | }; | |
531 | ||
532 | ||
533 | static void * | |
534 | hw_com_create(const char *name, | |
535 | const device_unit *unit_address, | |
536 | const char *args) | |
537 | { | |
538 | /* create the descriptor */ | |
539 | hw_com_device *hw_com = ZALLOC(hw_com_device); | |
540 | return hw_com; | |
541 | } | |
542 | ||
543 | ||
544 | const device_descriptor hw_com_device_descriptor[] = { | |
545 | { "com", hw_com_create, &hw_com_callbacks }, | |
546 | { NULL }, | |
547 | }; | |
548 | ||
549 | #endif /* _HW_COM_C_ */ |