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