| 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 3 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, see <http://www.gnu.org/licenses/>. |
| 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 |
| 106 | devices input stream (all data is immediately read). In the future, |
| 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 |
| 113 | devices output stream (all data is immediately written). In the future, |
| 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"); |
| 251 | if (com->output.file == NULL) |
| 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_ */ |