| 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_GLUE_C_ |
| 22 | #define _HW_GLUE_C_ |
| 23 | |
| 24 | #include "device_table.h" |
| 25 | |
| 26 | |
| 27 | /* DEVICE |
| 28 | |
| 29 | |
| 30 | glue - glue to interconnect and test interrupts |
| 31 | |
| 32 | |
| 33 | DESCRIPTION |
| 34 | |
| 35 | |
| 36 | The glue device provides two functions. Firstly, it provides a |
| 37 | mechanism for inspecting and driving the interrupt net. Secondly, |
| 38 | it provides a set of boolean primitives that can be used add |
| 39 | combinatorial operations to the interrupt network. |
| 40 | |
| 41 | Glue devices have a variable number of big endian <<output>> |
| 42 | registers. Each host-word size. The registers can be both read |
| 43 | and written. |
| 44 | |
| 45 | Writing a value to an output register causes an interrupt (of the |
| 46 | specified level) to be driven on the devices corresponding output |
| 47 | interrupt port. |
| 48 | |
| 49 | Reading an <<output>> register returns either the last value |
| 50 | written or the most recently computed value (for that register) as |
| 51 | a result of an interrupt ariving (which ever was computed last). |
| 52 | |
| 53 | At present the following sub device types are available: |
| 54 | |
| 55 | <<glue>>: In addition to driving its output interrupt port with any |
| 56 | value written to an interrupt input port is stored in the |
| 57 | corresponding <<output>> register. Such input interrupts, however, |
| 58 | are not propogated to an output interrupt port. |
| 59 | |
| 60 | <<glue-and>>: The bit-wise AND of the interrupt inputs is computed |
| 61 | and then both stored in <<output>> register zero and propogated to |
| 62 | output interrupt output port zero. |
| 63 | |
| 64 | |
| 65 | PROPERTIES |
| 66 | |
| 67 | |
| 68 | reg = <address> <size> (required) |
| 69 | |
| 70 | Specify the address (within the parent bus) that this device is to |
| 71 | live. The address must be 2048 * sizeof(word) (8k in a 32bit |
| 72 | simulation) aligned. |
| 73 | |
| 74 | |
| 75 | interrupt-ranges = <int-number> <range> (optional) |
| 76 | |
| 77 | If present, this specifies the number of valid interrupt inputs (up |
| 78 | to the maximum of 2048). By default, <<int-number>> is zero and |
| 79 | range is determined by the <<reg>> size. |
| 80 | |
| 81 | |
| 82 | EXAMPLES |
| 83 | |
| 84 | |
| 85 | Enable tracing of the device: |
| 86 | |
| 87 | | -t glue-device \ |
| 88 | |
| 89 | |
| 90 | Create source, bitwize-and, and sink glue devices. Since the |
| 91 | device at address <<0x10000>> is of size <<8>> it will have two |
| 92 | output interrupt ports. |
| 93 | |
| 94 | | -o '/iobus@0xf0000000/glue@0x10000/reg 0x10000 8' \ |
| 95 | | -o '/iobus@0xf0000000/glue-and@0x20000/reg 0x20000 4' \ |
| 96 | | -o '/iobus@0xf0000000/glue-and/interrupt-ranges 0 2' \ |
| 97 | | -o '/iobus@0xf0000000/glue@0x30000/reg 0x30000 4' \ |
| 98 | |
| 99 | |
| 100 | Wire the two source interrupts to the AND device: |
| 101 | |
| 102 | | -o '/iobus@0xf0000000/glue@0x10000 > 0 0 /iobus/glue-and' \ |
| 103 | | -o '/iobus@0xf0000000/glue@0x10000 > 1 1 /iobus/glue-and' \ |
| 104 | |
| 105 | |
| 106 | Wire the AND device up to the sink so that the and's output is not |
| 107 | left open. |
| 108 | |
| 109 | | -o '/iobus@0xf0000000/glue-and > 0 0 /iobus/glue@0x30000' \ |
| 110 | |
| 111 | |
| 112 | With the above configuration. The client program is able to |
| 113 | compute a two bit AND. For instance the <<C>> stub below prints 1 |
| 114 | AND 0. |
| 115 | |
| 116 | | unsigned *input = (void*)0xf0010000; |
| 117 | | unsigned *output = (void*)0xf0030000; |
| 118 | | unsigned ans; |
| 119 | | input[0] = htonl(1); |
| 120 | | input[1] = htonl(0); |
| 121 | | ans = ntohl(*output); |
| 122 | | write_string("AND is "); |
| 123 | | write_int(ans); |
| 124 | | write_line(); |
| 125 | |
| 126 | |
| 127 | BUGS |
| 128 | |
| 129 | |
| 130 | A future implementation of this device may support multiple |
| 131 | interrupt ranges. |
| 132 | |
| 133 | Some of the devices listed may not yet be fully implemented. |
| 134 | |
| 135 | Additional devices such as a dff, an inverter or a latch may be |
| 136 | useful. |
| 137 | |
| 138 | */ |
| 139 | |
| 140 | |
| 141 | enum { |
| 142 | max_nr_interrupts = 2048, |
| 143 | }; |
| 144 | |
| 145 | typedef enum _hw_glue_type { |
| 146 | glue_undefined = 0, |
| 147 | glue_io, |
| 148 | glue_and, |
| 149 | glue_nand, |
| 150 | glue_or, |
| 151 | glue_xor, |
| 152 | glue_nor, |
| 153 | glue_not, |
| 154 | } hw_glue_type; |
| 155 | |
| 156 | typedef struct _hw_glue_device { |
| 157 | hw_glue_type type; |
| 158 | int int_number; |
| 159 | int *input; |
| 160 | int nr_inputs; |
| 161 | unsigned sizeof_input; |
| 162 | /* our output registers */ |
| 163 | int space; |
| 164 | unsigned_word address; |
| 165 | unsigned sizeof_output; |
| 166 | int *output; |
| 167 | int nr_outputs; |
| 168 | } hw_glue_device; |
| 169 | |
| 170 | |
| 171 | static void |
| 172 | hw_glue_init_address(device *me) |
| 173 | { |
| 174 | hw_glue_device *glue = (hw_glue_device*)device_data(me); |
| 175 | |
| 176 | /* attach to my parent */ |
| 177 | generic_device_init_address(me); |
| 178 | |
| 179 | /* establish the output registers */ |
| 180 | if (glue->output != NULL) { |
| 181 | memset(glue->output, 0, glue->sizeof_output); |
| 182 | } |
| 183 | else { |
| 184 | reg_property_spec unit; |
| 185 | int reg_nr; |
| 186 | /* find a relevant reg entry */ |
| 187 | reg_nr = 0; |
| 188 | while (device_find_reg_array_property(me, "reg", reg_nr, &unit) |
| 189 | && !device_size_to_attach_size(device_parent(me), &unit.size, |
| 190 | &glue->sizeof_output, me)) |
| 191 | reg_nr++; |
| 192 | /* check out the size */ |
| 193 | if (glue->sizeof_output == 0) |
| 194 | device_error(me, "at least one reg property size must be nonzero"); |
| 195 | if (glue->sizeof_output % sizeof(unsigned_word) != 0) |
| 196 | device_error(me, "reg property size must be %d aligned", sizeof(unsigned_word)); |
| 197 | /* and the address */ |
| 198 | device_address_to_attach_address(device_parent(me), |
| 199 | &unit.address, &glue->space, &glue->address, |
| 200 | me); |
| 201 | if (glue->address % (sizeof(unsigned_word) * max_nr_interrupts) != 0) |
| 202 | device_error(me, "reg property address must be %d aligned", |
| 203 | sizeof(unsigned_word) * max_nr_interrupts); |
| 204 | glue->nr_outputs = glue->sizeof_output / sizeof(unsigned_word); |
| 205 | glue->output = zalloc(glue->sizeof_output); |
| 206 | } |
| 207 | |
| 208 | /* establish the input interrupt ports */ |
| 209 | if (glue->input != NULL) { |
| 210 | memset(glue->input, 0, glue->sizeof_input); |
| 211 | } |
| 212 | else { |
| 213 | const device_property *ranges = device_find_property(me, "interrupt-ranges"); |
| 214 | if (ranges == NULL) { |
| 215 | glue->int_number = 0; |
| 216 | glue->nr_inputs = glue->nr_outputs; |
| 217 | } |
| 218 | else if (ranges->sizeof_array != sizeof(unsigned_cell) * 2) { |
| 219 | device_error(me, "invalid interrupt-ranges property (incorrect size)"); |
| 220 | } |
| 221 | else { |
| 222 | const unsigned_cell *int_range = ranges->array; |
| 223 | glue->int_number = BE2H_cell(int_range[0]); |
| 224 | glue->nr_inputs = BE2H_cell(int_range[1]); |
| 225 | } |
| 226 | glue->sizeof_input = glue->nr_inputs * sizeof(unsigned); |
| 227 | glue->input = zalloc(glue->sizeof_input); |
| 228 | } |
| 229 | |
| 230 | /* determine our type */ |
| 231 | if (glue->type == glue_undefined) { |
| 232 | const char *name = device_name(me); |
| 233 | if (strcmp(name, "glue") == 0) |
| 234 | glue->type = glue_io; |
| 235 | else if (strcmp(name, "glue-and") == 0) |
| 236 | glue->type = glue_and; |
| 237 | else |
| 238 | device_error(me, "unimplemented glue type"); |
| 239 | } |
| 240 | |
| 241 | DTRACE(glue, ("int-number %d, nr_inputs %d, nr_outputs %d\n", |
| 242 | glue->int_number, glue->nr_inputs, glue->nr_outputs)); |
| 243 | } |
| 244 | |
| 245 | static unsigned |
| 246 | hw_glue_io_read_buffer_callback(device *me, |
| 247 | void *dest, |
| 248 | int space, |
| 249 | unsigned_word addr, |
| 250 | unsigned nr_bytes, |
| 251 | cpu *processor, |
| 252 | unsigned_word cia) |
| 253 | { |
| 254 | hw_glue_device *glue = (hw_glue_device*)device_data(me); |
| 255 | int reg = ((addr - glue->address) / sizeof(unsigned_word)) % glue->nr_outputs; |
| 256 | if (nr_bytes != sizeof(unsigned_word) |
| 257 | || (addr % sizeof(unsigned_word)) != 0) |
| 258 | device_error(me, "missaligned read access (%d:0x%lx:%d) not supported", |
| 259 | space, (unsigned long)addr, nr_bytes); |
| 260 | *(unsigned_word*)dest = H2BE_4(glue->output[reg]); |
| 261 | DTRACE(glue, ("read - interrupt %d (0x%lx), level %d\n", |
| 262 | reg, (unsigned long) addr, glue->output[reg])); |
| 263 | return nr_bytes; |
| 264 | } |
| 265 | |
| 266 | |
| 267 | static unsigned |
| 268 | hw_glue_io_write_buffer_callback(device *me, |
| 269 | const void *source, |
| 270 | int space, |
| 271 | unsigned_word addr, |
| 272 | unsigned nr_bytes, |
| 273 | cpu *processor, |
| 274 | unsigned_word cia) |
| 275 | { |
| 276 | hw_glue_device *glue = (hw_glue_device*)device_data(me); |
| 277 | int reg = ((addr - glue->address) / sizeof(unsigned_word)) % max_nr_interrupts; |
| 278 | if (nr_bytes != sizeof(unsigned_word) |
| 279 | || (addr % sizeof(unsigned_word)) != 0) |
| 280 | device_error(me, "missaligned write access (%d:0x%lx:%d) not supported", |
| 281 | space, (unsigned long)addr, nr_bytes); |
| 282 | glue->output[reg] = H2BE_4(*(unsigned_word*)source); |
| 283 | DTRACE(glue, ("write - interrupt %d (0x%lx), level %d\n", |
| 284 | reg, (unsigned long) addr, glue->output[reg])); |
| 285 | device_interrupt_event(me, reg, glue->output[reg], processor, cia); |
| 286 | return nr_bytes; |
| 287 | } |
| 288 | |
| 289 | static void |
| 290 | hw_glue_interrupt_event(device *me, |
| 291 | int my_port, |
| 292 | device *source, |
| 293 | int source_port, |
| 294 | int level, |
| 295 | cpu *processor, |
| 296 | unsigned_word cia) |
| 297 | { |
| 298 | hw_glue_device *glue = (hw_glue_device*)device_data(me); |
| 299 | int i; |
| 300 | if (my_port < glue->int_number |
| 301 | || my_port >= glue->int_number + glue->nr_inputs) |
| 302 | device_error(me, "interrupt %d outside of valid range", my_port); |
| 303 | glue->input[my_port - glue->int_number] = level; |
| 304 | switch (glue->type) { |
| 305 | case glue_io: |
| 306 | { |
| 307 | int port = my_port % glue->nr_outputs; |
| 308 | glue->output[port] = level; |
| 309 | DTRACE(glue, ("input - interrupt %d (0x%lx), level %d\n", |
| 310 | my_port, |
| 311 | (unsigned long)glue->address + port * sizeof(unsigned_word), |
| 312 | level)); |
| 313 | break; |
| 314 | } |
| 315 | case glue_and: |
| 316 | glue->output[0] = glue->input[0]; |
| 317 | for (i = 1; i < glue->nr_inputs; i++) |
| 318 | glue->output[0] &= glue->input[i]; |
| 319 | DTRACE(glue, ("and - interrupt %d, level %d arrived - output %d\n", |
| 320 | my_port, level, glue->output[0])); |
| 321 | device_interrupt_event(me, 0, glue->output[0], processor, cia); |
| 322 | break; |
| 323 | default: |
| 324 | device_error(me, "operator not implemented"); |
| 325 | break; |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | |
| 330 | static const device_interrupt_port_descriptor hw_glue_interrupt_ports[] = { |
| 331 | { "int", 0, max_nr_interrupts }, |
| 332 | { NULL } |
| 333 | }; |
| 334 | |
| 335 | |
| 336 | static device_callbacks const hw_glue_callbacks = { |
| 337 | { hw_glue_init_address, NULL }, |
| 338 | { NULL, }, /* address */ |
| 339 | { hw_glue_io_read_buffer_callback, |
| 340 | hw_glue_io_write_buffer_callback, }, |
| 341 | { NULL, }, /* DMA */ |
| 342 | { hw_glue_interrupt_event, NULL, hw_glue_interrupt_ports }, /* interrupt */ |
| 343 | { NULL, }, /* unit */ |
| 344 | NULL, /* instance */ |
| 345 | }; |
| 346 | |
| 347 | |
| 348 | static void * |
| 349 | hw_glue_create(const char *name, |
| 350 | const device_unit *unit_address, |
| 351 | const char *args) |
| 352 | { |
| 353 | /* create the descriptor */ |
| 354 | hw_glue_device *glue = ZALLOC(hw_glue_device); |
| 355 | return glue; |
| 356 | } |
| 357 | |
| 358 | |
| 359 | const device_descriptor hw_glue_device_descriptor[] = { |
| 360 | { "glue", hw_glue_create, &hw_glue_callbacks }, |
| 361 | { "glue-and", hw_glue_create, &hw_glue_callbacks }, |
| 362 | { "glue-nand", hw_glue_create, &hw_glue_callbacks }, |
| 363 | { "glue-or", hw_glue_create, &hw_glue_callbacks }, |
| 364 | { "glue-xor", hw_glue_create, &hw_glue_callbacks }, |
| 365 | { "glue-nor", hw_glue_create, &hw_glue_callbacks }, |
| 366 | { "glue-not", hw_glue_create, &hw_glue_callbacks }, |
| 367 | { NULL }, |
| 368 | }; |
| 369 | |
| 370 | #endif /* _HW_GLUE_C_ */ |