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