| /* $Id: bus-el.c,v 1.18 2009/08/29 17:59:17 fredette Exp $ */ |
| |
| /* generic/bus-el.c - a real generic bus element: */ |
| |
| /* |
| * Copyright (c) 2003 Matt Fredette |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. All advertising materials mentioning features or use of this software |
| * must display the following acknowledgement: |
| * This product includes software developed by Matt Fredette. |
| * 4. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
| * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, |
| * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
| * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <tme/common.h> |
| _TME_RCSID("$Id: bus-el.c,v 1.18 2009/08/29 17:59:17 fredette Exp $"); |
| |
| /* includes: */ |
| #define TME_BUS_VERSION TME_X_VERSION(0, 0) |
| #include <tme/generic/bus.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| /* macros: */ |
| |
| /* globals: */ |
| |
| /* the generic bus signals: */ |
| static const struct tme_bus_signals _tme_bus_signals_default[] = { |
| TME_BUS_SIGNALS_GENERIC |
| }; |
| |
| /* this adds a bus signal set to the bus: */ |
| static int |
| _tme_bus_signals_add(struct tme_bus_connection *conn_bus_caller, |
| struct tme_bus_signals *bus_signals) |
| { |
| struct tme_bus *bus; |
| unsigned int signal_i; |
| tme_uint32_t signals_count_new; |
| tme_uint32_t signals_count_old; |
| struct tme_bus_connection_int *conn_bus_int; |
| int rc; |
| |
| /* recover our bus: */ |
| bus = conn_bus_caller->tme_bus_connection.tme_connection_element->tme_element_private; |
| |
| /* lock the bus for writing: */ |
| rc = tme_rwlock_timedwrlock(&bus->tme_bus_rwlock, TME_THREAD_TIMEDLOCK); |
| if (TME_THREADS_ERRNO(rc) != TME_OK) { |
| return (TME_THREADS_ERRNO(rc)); |
| } |
| |
| /* search for an existing bus signals set that matches the caller's: */ |
| for (signal_i = 0; |
| signal_i < bus->tme_bus_signals_count; |
| signal_i++) { |
| |
| /* stop if this existing bus signals set has the right ID and the |
| versions overlap: */ |
| if ((bus->tme_bus_signals[signal_i].tme_bus_signals_id |
| == bus_signals->tme_bus_signals_id) |
| && TME_X_VERSION_OK(bus->tme_bus_signals[signal_i].tme_bus_signals_version, |
| bus_signals->tme_bus_signals_version)) { |
| break; |
| } |
| } |
| |
| /* assume that this call succeeds: */ |
| rc = TME_OK; |
| |
| /* if an existing bus signals set was not found: */ |
| if (signal_i == bus->tme_bus_signals_count) { |
| |
| /* get the old count of bus signals from the current last bus |
| signals set in the bus signals sets array: */ |
| signals_count_old = |
| (TME_BUS_SIGNAL_INDEX(bus->tme_bus_signals[bus->tme_bus_signals_count - 1].tme_bus_signals_first) |
| + bus->tme_bus_signals[bus->tme_bus_signals_count - 1].tme_bus_signals_count); |
| |
| /* resize the bus signals sets array: */ |
| bus->tme_bus_signals |
| = tme_renew(struct tme_bus_signals, |
| bus->tme_bus_signals, |
| bus->tme_bus_signals_count |
| + 1); |
| |
| /* add the new bus signals set: */ |
| signals_count_new = signals_count_old + bus_signals->tme_bus_signals_count; |
| assert (signals_count_new > signals_count_old); |
| bus_signals->tme_bus_signals_first = TME_BUS_SIGNAL_X(signals_count_old); |
| bus->tme_bus_signals[bus->tme_bus_signals_count] = *bus_signals; |
| bus->tme_bus_signals_count++; |
| |
| /* reallocate the bus' asserted-signals count array: */ |
| bus->tme_bus_signal_asserts |
| = tme_renew(unsigned int, |
| bus->tme_bus_signal_asserts, |
| signals_count_new); |
| memset ((char *) &bus->tme_bus_signal_asserts[signals_count_old], |
| 0, |
| (sizeof(bus->tme_bus_signal_asserts[0]) |
| * (signals_count_new |
| - signals_count_old))); |
| |
| /* if needed, reallocate each connection's asserted-signals |
| bitmap: */ |
| if (TME_BUS_SIGNAL_BIT_BYTES(signals_count_new) |
| > TME_BUS_SIGNAL_BIT_BYTES(signals_count_old)) { |
| for (conn_bus_int = bus->tme_bus_connections; |
| conn_bus_int != NULL; |
| conn_bus_int = |
| (struct tme_bus_connection_int *) |
| conn_bus_int->tme_bus_connection_int |
| .tme_bus_connection |
| .tme_connection_next) { |
| conn_bus_int->tme_bus_connection_int_signals |
| = tme_renew(tme_uint8_t, |
| conn_bus_int->tme_bus_connection_int_signals, |
| TME_BUS_SIGNAL_BIT_BYTES(signals_count_new)); |
| memset ((char *) &conn_bus_int->tme_bus_connection_int_signals[TME_BUS_SIGNAL_BIT_BYTES(signals_count_old)], |
| 0, |
| (sizeof (conn_bus_int->tme_bus_connection_int_signals[0]) |
| * (TME_BUS_SIGNAL_BIT_BYTES(signals_count_new) |
| - TME_BUS_SIGNAL_BIT_BYTES(signals_count_old)))); |
| } |
| } |
| } |
| |
| /* otherwise, we found an existing bus signals set. however, even |
| though the versions overlap, if they don't support the same least |
| version, something is wrong: */ |
| else if ((TME_X_VERSION_CURRENT(bus->tme_bus_signals[signal_i].tme_bus_signals_version) |
| - TME_X_VERSION_AGE(bus->tme_bus_signals[signal_i].tme_bus_signals_version)) |
| != (TME_X_VERSION_CURRENT(bus_signals->tme_bus_signals_version) |
| - TME_X_VERSION_AGE(bus_signals->tme_bus_signals_version))) { |
| rc = EINVAL; |
| } |
| |
| /* otherwise, we found an existing bus signals set that fully matches |
| and is compatible with the caller's: */ |
| else { |
| |
| /* update the versioning on this bus signals set: */ |
| if (TME_X_VERSION_CURRENT(bus_signals->tme_bus_signals_version) |
| > TME_X_VERSION_CURRENT(bus->tme_bus_signals[signal_i].tme_bus_signals_version)) { |
| bus->tme_bus_signals[signal_i].tme_bus_signals_version = bus_signals->tme_bus_signals_version; |
| } |
| |
| /* return the existing bus signals set: */ |
| *bus_signals = bus->tme_bus_signals[signal_i]; |
| } |
| |
| /* unlock the bus and return: */ |
| tme_rwlock_unlock(&bus->tme_bus_rwlock); |
| return (rc); |
| } |
| |
| /* this handles a bus connection signal edge: */ |
| static int |
| _tme_bus_signal(struct tme_bus_connection *conn_bus_edger, unsigned int signal) |
| { |
| struct tme_bus *bus; |
| struct tme_bus_connection_int *conn_bus_int_edger; |
| struct tme_bus_connection_int *conn_bus_int; |
| unsigned int level_edge; |
| struct tme_bus_connection *conn_bus; |
| struct tme_bus_connection *conn_bus_other; |
| int signal_asserted, need_propagate; |
| unsigned int signal_index; |
| tme_uint8_t signal_mask; |
| int rc; |
| int deadlocked; |
| |
| /* recover our bus: */ |
| bus = conn_bus_edger->tme_bus_connection.tme_connection_element->tme_element_private; |
| conn_bus_int_edger = (struct tme_bus_connection_int *) conn_bus_edger; |
| |
| /* take out the level and edge: */ |
| level_edge = signal; |
| signal = TME_BUS_SIGNAL_WHICH(signal); |
| level_edge ^= signal; |
| |
| /* lock the bus for writing: */ |
| rc = tme_rwlock_timedwrlock(&bus->tme_bus_rwlock, TME_THREAD_TIMEDLOCK); |
| if (TME_THREADS_ERRNO(rc) != TME_OK) { |
| return (TME_THREADS_ERRNO(rc)); |
| } |
| |
| /* if this device doesn't know its interrupt signal, fix it: */ |
| if (signal == TME_BUS_SIGNAL_INT_UNSPEC) { |
| signal = conn_bus_int_edger->tme_bus_connection_int_signal_int; |
| if (signal == TME_BUS_SIGNAL_INT_UNSPEC) { |
| /* this bus connection is misconfigured: */ |
| if (!conn_bus_int_edger->tme_bus_connection_int_logged_int) { |
| conn_bus_int_edger->tme_bus_connection_int_logged_int = TRUE; |
| /* XXX diagnostic */ |
| abort(); |
| } |
| tme_rwlock_unlock(&bus->tme_bus_rwlock); |
| return (TME_OK); |
| } |
| } |
| |
| /* assume we don't need to propagate this signal across the bus: */ |
| need_propagate = FALSE; |
| |
| /* see whether the device is asserting or negating this signal. iff |
| one or more devices on a bus are asserting a signal, the signal |
| appears asserted on the bus. this gives an ORed effect: */ |
| signal_asserted = TRUE; |
| switch (level_edge & TME_BUS_SIGNAL_LEVEL_MASK) { |
| case TME_BUS_SIGNAL_LEVEL_NEGATED: |
| signal_asserted = FALSE; |
| case TME_BUS_SIGNAL_LEVEL_ASSERTED: |
| break; |
| default: |
| abort(); |
| } |
| |
| /* get the index and mask of this signal in signal byte arrays: */ |
| signal_index = TME_BUS_SIGNAL_BIT_INDEX(signal); |
| signal_mask = TME_BUS_SIGNAL_BIT_MASK(signal); |
| |
| /* if this signal is being asserted: */ |
| if (signal_asserted) { |
| |
| /* if this device wasn't already asserting this signal: */ |
| if (!(conn_bus_int_edger->tme_bus_connection_int_signals[signal_index] |
| & signal_mask)) { |
| |
| /* it is now asserting this signal: */ |
| conn_bus_int_edger->tme_bus_connection_int_signals[signal_index] |
| |= signal_mask; |
| bus->tme_bus_signal_asserts[TME_BUS_SIGNAL_INDEX(signal)]++; |
| |
| /* if this is the only device asserting this signal, |
| propagate the change across the bus: */ |
| if (bus->tme_bus_signal_asserts[TME_BUS_SIGNAL_INDEX(signal)] == 1) { |
| need_propagate = TRUE; |
| } |
| } |
| |
| /* otherwise, this device was already asserting this signal: */ |
| else { |
| assert(bus->tme_bus_signal_asserts[TME_BUS_SIGNAL_INDEX(signal)] > 0); |
| } |
| } |
| |
| /* otherwise, this signal is being negated: */ |
| else { |
| |
| /* if this device was asserting this signal: */ |
| if (conn_bus_int_edger->tme_bus_connection_int_signals[signal_index] |
| & signal_mask) { |
| |
| /* it is no longer asserting this signal: */ |
| conn_bus_int_edger->tme_bus_connection_int_signals[signal_index] |
| &= ~signal_mask; |
| assert(bus->tme_bus_signal_asserts[TME_BUS_SIGNAL_INDEX(signal)] > 0); |
| bus->tme_bus_signal_asserts[TME_BUS_SIGNAL_INDEX(signal)]--; |
| |
| /* if this was the last device asserting this signal, propagate |
| the change across the bus: */ |
| if (bus->tme_bus_signal_asserts[TME_BUS_SIGNAL_INDEX(signal)] == 0) { |
| need_propagate = TRUE; |
| } |
| } |
| |
| /* otherwise, this device was not asserting this signal, but it |
| negated it anyways. often, lazy code will only send negating |
| edges for signals (for example, see the do_reset code in |
| machine/sun2/sun2-mainbus.c, which should really assert RESET, |
| sleep, then negate it), so if we get a negated edge for a |
| signal that no one else (including the edger) was asserting, we |
| propagate the edge anyways. |
| |
| so, TME_BUS_SIGNAL_EDGE should only be used for this purpose: */ |
| else if ((level_edge & TME_BUS_SIGNAL_EDGE) |
| && bus->tme_bus_signal_asserts[TME_BUS_SIGNAL_INDEX(signal)] == 0) { |
| need_propagate = TRUE; |
| } |
| } |
| |
| /* if we're propagating this signal across the bus: */ |
| rc = TME_OK; |
| if (need_propagate) { |
| |
| /* put the level and edge back in: */ |
| signal |= level_edge; |
| |
| /* assume that we won't deadlock: */ |
| deadlocked = FALSE; |
| |
| /* propagate the signal to each connection to the bus: */ |
| for (conn_bus_int = bus->tme_bus_connections; |
| conn_bus_int != NULL; |
| conn_bus_int = |
| (struct tme_bus_connection_int *) |
| conn_bus_int->tme_bus_connection_int |
| .tme_bus_connection |
| .tme_connection_next) { |
| conn_bus = &conn_bus_int->tme_bus_connection_int; |
| conn_bus_other = |
| (struct tme_bus_connection *) |
| conn_bus->tme_bus_connection.tme_connection_other; |
| |
| /* skip this device if it edged the line to begin with: */ |
| if (conn_bus == conn_bus_edger) { |
| continue; |
| } |
| |
| /* skip this device if it doesn't care about bus signals: */ |
| if (conn_bus_other->tme_bus_signal == NULL) { |
| continue; |
| } |
| |
| /* give the edge to this connection: */ |
| rc = (*conn_bus_other->tme_bus_signal)(conn_bus_other, signal); |
| |
| /* if we deadlocked, remember to tell the caller: */ |
| if (rc == TME_EDEADLK) { |
| deadlocked = TRUE; |
| } |
| } |
| rc = (deadlocked ? TME_EDEADLK : TME_OK); |
| } |
| |
| /* unlock the bus: */ |
| tme_rwlock_unlock(&bus->tme_bus_rwlock); |
| |
| /* done: */ |
| return (rc); |
| } |
| |
| /* this handles a bus interrupt acknowledge: */ |
| static int |
| _tme_bus_intack(struct tme_bus_connection *conn_bus_acker, unsigned int signal, int *vector) |
| { |
| struct tme_bus *bus; |
| struct tme_bus_connection_int *conn_bus_int; |
| struct tme_bus_connection *conn_bus; |
| struct tme_bus_connection *conn_bus_other; |
| unsigned int signal_index; |
| tme_uint8_t signal_mask; |
| int rc; |
| |
| /* recover our bus: */ |
| bus = conn_bus_acker->tme_bus_connection.tme_connection_element->tme_element_private; |
| |
| /* get rid of any level and edge: */ |
| signal = TME_BUS_SIGNAL_WHICH(signal); |
| |
| /* this must be an interrupt signal: */ |
| assert(TME_BUS_SIGNAL_IS_INT(signal)); |
| |
| /* lock the bus for writing: */ |
| rc = tme_rwlock_timedwrlock(&bus->tme_bus_rwlock, TME_THREAD_TIMEDLOCK); |
| if (TME_THREADS_ERRNO(rc) != TME_OK) { |
| return (TME_THREADS_ERRNO(rc)); |
| } |
| |
| /* get the index and mask of this signal in signal byte arrays: */ |
| signal_index = TME_BUS_SIGNAL_BIT_INDEX(signal); |
| signal_mask = TME_BUS_SIGNAL_BIT_MASK(signal); |
| |
| /* find the first connection to the bus that is asserting this |
| interrupt signal. if no connection is asserting the signal, |
| return ENOENT: */ |
| rc = ENOENT; |
| for (conn_bus_int = bus->tme_bus_connections; |
| conn_bus_int != NULL; |
| conn_bus_int = |
| (struct tme_bus_connection_int *) |
| conn_bus_int->tme_bus_connection_int |
| .tme_bus_connection |
| .tme_connection_next) { |
| conn_bus = &conn_bus_int->tme_bus_connection_int; |
| conn_bus_other = |
| (struct tme_bus_connection *) |
| conn_bus->tme_bus_connection.tme_connection_other; |
| |
| /* if this device is asserting this interrupt signal: */ |
| if (conn_bus_int->tme_bus_connection_int_signals[signal_index] |
| & signal_mask) { |
| |
| /* unlock the bus: */ |
| tme_rwlock_unlock(&bus->tme_bus_rwlock); |
| |
| /* if this device doesn't acknowledge interrupts, return any |
| user-specified vector or TME_BUS_INTERRUPT_VECTOR_UNDEF: */ |
| if (conn_bus_other->tme_bus_intack == NULL) { |
| *vector = conn_bus_int->tme_bus_connection_int_vector_int; |
| rc = TME_OK; |
| } |
| |
| /* otherwise, run the interrupt acknowledge with this connection: */ |
| else { |
| rc = (*conn_bus_other->tme_bus_intack)(conn_bus_other, signal, vector); |
| } |
| |
| /* done: */ |
| return (rc); |
| } |
| } |
| |
| /* unlock the bus: */ |
| tme_rwlock_unlock(&bus->tme_bus_rwlock); |
| |
| /* done: */ |
| return (rc); |
| } |
| |
| static int |
| _tme_bus_fault(void *junk0, struct tme_bus_cycle *junk1) |
| { |
| return (ENOENT); |
| } |
| |
| /* this fills a TLB entry: */ |
| static int |
| _tme_bus_tlb_fill(struct tme_bus_connection *conn_bus_asker, |
| struct tme_bus_tlb *tlb, |
| tme_bus_addr_t address, |
| unsigned int cycles) |
| { |
| struct tme_bus *bus; |
| struct tme_bus_connection_int *conn_int; |
| int rc; |
| |
| /* recover our bus and our connection to the asker: */ |
| bus = conn_bus_asker->tme_bus_connection.tme_connection_element->tme_element_private; |
| conn_int = (struct tme_bus_connection_int *) conn_bus_asker; |
| |
| /* put our fault handler in the TLB entry: */ |
| tlb->tme_bus_tlb_cycle_private = NULL; |
| tlb->tme_bus_tlb_cycle = _tme_bus_fault; |
| |
| /* lock the bus for reading: */ |
| rc = tme_rwlock_timedrdlock(&bus->tme_bus_rwlock, TME_THREAD_TIMEDLOCK); |
| if (TME_THREADS_ERRNO(rc) != TME_OK) { |
| return (TME_THREADS_ERRNO(rc)); |
| } |
| |
| /* call the generic bus support function: */ |
| rc = tme_bus_tlb_fill(bus, |
| conn_int, |
| tlb, address, cycles); |
| |
| /* unlock the bus: */ |
| tme_rwlock_unlock(&bus->tme_bus_rwlock); |
| |
| /* done: */ |
| return (rc); |
| } |
| |
| /* this adds a new TLB set: */ |
| static int |
| _tme_bus_tlb_set_add(struct tme_bus_connection *conn_bus_asker, |
| struct tme_bus_tlb_set_info *tlb_set_info) |
| { |
| struct tme_bus *bus; |
| struct tme_bus_connection_int *conn_int; |
| int rc; |
| |
| /* recover our bus and our connection to the asker: */ |
| bus = conn_bus_asker->tme_bus_connection.tme_connection_element->tme_element_private; |
| conn_int = (struct tme_bus_connection_int *) conn_bus_asker; |
| |
| /* lock the bus for reading: */ |
| rc = tme_rwlock_timedrdlock(&bus->tme_bus_rwlock, TME_THREAD_TIMEDLOCK); |
| if (TME_THREADS_ERRNO(rc) != TME_OK) { |
| return (TME_THREADS_ERRNO(rc)); |
| } |
| |
| /* call the generic bus support function: */ |
| rc = tme_bus_tlb_set_add(bus, |
| conn_int, |
| tlb_set_info); |
| |
| /* unlock the bus: */ |
| tme_rwlock_unlock(&bus->tme_bus_rwlock); |
| |
| /* done: */ |
| return (rc); |
| } |
| |
| /* this scores a new connection: */ |
| static int |
| _tme_bus_connection_score(struct tme_connection *conn, unsigned int *_score) |
| { |
| struct tme_bus *bus; |
| struct tme_bus_connection_int *conn_int; |
| int rc, ok; |
| |
| /* both sides must be generic bus connections: */ |
| assert(conn->tme_connection_type == TME_CONNECTION_BUS_GENERIC); |
| assert(conn->tme_connection_other->tme_connection_type == TME_CONNECTION_BUS_GENERIC); |
| |
| /* recover our bus and our internal connection side: */ |
| bus = conn->tme_connection_element->tme_element_private; |
| conn_int = (struct tme_bus_connection_int *) conn; |
| |
| /* lock the bus for reading: */ |
| rc = tme_rwlock_timedrdlock(&bus->tme_bus_rwlock, TME_THREAD_TIMEDLOCK); |
| if (TME_THREADS_ERRNO(rc) != TME_OK) { |
| return (TME_THREADS_ERRNO(rc)); |
| } |
| |
| /* call the generic bus support function: */ |
| ok = tme_bus_connection_ok(bus, |
| conn_int); |
| |
| /* unlock the bus: */ |
| tme_rwlock_unlock(&bus->tme_bus_rwlock); |
| |
| /* return the score: */ |
| *_score = (ok ? 1 : 0); |
| return (TME_OK); |
| } |
| |
| /* this makes a new connection: */ |
| static int |
| _tme_bus_connection_make(struct tme_connection *conn, unsigned int state) |
| { |
| struct tme_bus *bus; |
| struct tme_bus_connection_int *conn_int; |
| const struct tme_bus_signals *bus_signals; |
| int rc; |
| |
| /* both sides must be generic bus connections: */ |
| assert(conn->tme_connection_type == TME_CONNECTION_BUS_GENERIC); |
| assert(conn->tme_connection_other->tme_connection_type == TME_CONNECTION_BUS_GENERIC); |
| |
| /* recover our bus and our internal connection side: */ |
| bus = conn->tme_connection_element->tme_element_private; |
| conn_int = (struct tme_bus_connection_int *) conn; |
| |
| /* lock the bus for writing: */ |
| rc = tme_rwlock_timedwrlock(&bus->tme_bus_rwlock, TME_THREAD_TIMEDLOCK); |
| if (TME_THREADS_ERRNO(rc) != TME_OK) { |
| return (TME_THREADS_ERRNO(rc)); |
| } |
| |
| /* call the generic bus support function: */ |
| rc = tme_bus_connection_make(bus, |
| conn_int, |
| state); |
| |
| /* XXX this is a perfect example of the poor division between |
| bus-el.c and bus.c. should the signal handling code be in bus.c? */ |
| if (rc == TME_OK) { |
| bus_signals = &bus->tme_bus_signals[bus->tme_bus_signals_count - 1]; |
| conn_int->tme_bus_connection_int_signals |
| = tme_new0(tme_uint8_t, |
| TME_BUS_SIGNAL_BIT_BYTES(TME_BUS_SIGNAL_INDEX(bus_signals->tme_bus_signals_first) |
| + bus_signals->tme_bus_signals_count)); |
| } |
| |
| /* unlock the bus: */ |
| tme_rwlock_unlock(&bus->tme_bus_rwlock); |
| |
| return (rc); |
| } |
| |
| /* this breaks a connection: */ |
| static int |
| _tme_bus_connection_break(struct tme_connection *conn, unsigned int state) |
| { |
| abort(); |
| } |
| |
| /* this returns the new connections possible: */ |
| static int |
| _tme_bus_connections_new(struct tme_element *element, |
| const char * const *args, |
| struct tme_connection **_conns, |
| char **_output) |
| { |
| const struct tme_bus *bus; |
| struct tme_bus_connection_int *conn_int; |
| struct tme_bus_connection *conn_bus; |
| struct tme_connection *conn; |
| int ipl; |
| int vector; |
| const struct tme_bus_slot *bus_slot; |
| int arg_i; |
| int usage; |
| |
| /* recover our bus. we only read the address mask, so we don't lock |
| the rwlock: */ |
| bus = element->tme_element_private; |
| |
| /* allocate the new connection side: */ |
| conn_int = tme_new0(struct tme_bus_connection_int, 1); |
| conn_bus = &conn_int->tme_bus_connection_int; |
| conn = &conn_bus->tme_bus_connection; |
| |
| /* loop reading our arguments: */ |
| usage = FALSE; |
| arg_i = 1; |
| conn_int->tme_bus_connection_int_vector_int = TME_BUS_INTERRUPT_VECTOR_UNDEF; |
| bus_slot = NULL; |
| for (;;) { |
| |
| /* the address of this connection: */ |
| if (TME_ARG_IS(args[arg_i + 0], "addr")) { |
| conn_int->tme_bus_connection_int_flags |= TME_BUS_CONNECTION_INT_FLAG_ADDRESSABLE; |
| conn_int->tme_bus_connection_int_address = tme_bus_addr_parse_any(args[arg_i + 1], &usage); |
| if (usage |
| || (conn_int->tme_bus_connection_int_address |
| > bus->tme_bus_address_mask)) { |
| usage = TRUE; |
| break; |
| } |
| arg_i += 2; |
| } |
| |
| /* the interrupt signal for this connection: */ |
| else if (TME_ARG_IS(args[arg_i + 0], "ipl") |
| && args[arg_i + 1] != NULL |
| && (ipl = atoi(args[arg_i + 1])) > 0) { |
| conn_int->tme_bus_connection_int_signal_int = TME_BUS_SIGNAL_INT(ipl); |
| arg_i += 2; |
| } |
| |
| /* the interrupt vector for this connection: */ |
| else if (TME_ARG_IS(args[arg_i + 0], "vector") |
| && args[arg_i + 1] != NULL |
| && (vector = atoi(args[arg_i + 1])) > 0) { |
| conn_int->tme_bus_connection_int_vector_int = vector; |
| arg_i += 2; |
| } |
| |
| /* the slot for this connection: */ |
| else if (TME_ARG_IS(args[arg_i + 0], "slot") |
| && args[arg_i + 1] != NULL) { |
| |
| /* you can't give more than one slot for a connection: */ |
| if (bus_slot != NULL) { |
| tme_output_append_error(_output, |
| "slot %s %s, ", |
| args[arg_i + 1], |
| _("redefined")); |
| usage = TRUE; |
| break; |
| } |
| |
| /* make sure this slot has been defined: */ |
| for (bus_slot = bus->tme_bus_slots; |
| bus_slot != NULL; |
| bus_slot = bus_slot->tme_bus_slot_next) { |
| if (strcmp(bus_slot->tme_bus_slot_name, |
| args[arg_i + 1]) == 0) { |
| break; |
| } |
| } |
| if (bus_slot == NULL) { |
| tme_output_append_error(_output, |
| "slot %s %s, ", |
| args[arg_i + 1], |
| _("unknown")); |
| usage = TRUE; |
| break; |
| } |
| arg_i += 2; |
| } |
| |
| /* the slot offset for this connection: */ |
| else if (TME_ARG_IS(args[arg_i + 0], "offset")) { |
| if (bus_slot == NULL) { |
| tme_output_append_error(_output, |
| "slot %s, ", |
| _("unknown")); |
| usage = TRUE; |
| break; |
| } |
| conn_int->tme_bus_connection_int_flags |= TME_BUS_CONNECTION_INT_FLAG_ADDRESSABLE; |
| conn_int->tme_bus_connection_int_address |
| = (bus_slot->tme_bus_slot_address |
| + tme_bus_addr_parse_any(args[arg_i + 1], &usage)); |
| if (usage |
| || (conn_int->tme_bus_connection_int_address |
| > bus->tme_bus_address_mask)) { |
| usage = TRUE; |
| break; |
| } |
| arg_i += 2; |
| } |
| |
| /* if this connection is for a slot controller: */ |
| else if (TME_ARG_IS(args[arg_i + 0], "controller")) { |
| if (bus->tme_bus_controller != NULL) { |
| tme_free(conn_int); |
| return (EEXIST); |
| } |
| conn_int->tme_bus_connection_int_flags |= TME_BUS_CONNECTION_INT_FLAG_CONTROLLER; |
| arg_i++; |
| } |
| |
| /* if this connection has an automatic DMA offset: */ |
| else if (TME_ARG_IS(args[arg_i + 0], "dma-offset")) { |
| conn_int->tme_bus_connection_int_sourced = tme_bus_addr_parse_any(args[arg_i + 1], &usage); |
| if (usage |
| || (conn_int->tme_bus_connection_int_sourced |
| > bus->tme_bus_address_mask)) { |
| usage = TRUE; |
| break; |
| } |
| arg_i += 2; |
| } |
| |
| /* if we've run out of arguments: */ |
| else if (args[arg_i + 0] == NULL) { |
| break; |
| } |
| |
| /* this is a bad argument: */ |
| else { |
| tme_output_append_error(_output, |
| "%s %s, ", |
| args[arg_i], |
| _("unexpected")); |
| usage = TRUE; |
| break; |
| } |
| } |
| |
| if (usage) { |
| tme_output_append_error(_output, |
| "%s %s [ controller ] [ addr %s ] [ slot %s offset %s ] [ dma-offset %s ] [ ipl %s ] [ vector %s ]", |
| _("usage:"), |
| args[0], |
| _("BUS-ADDRESS"), |
| _("SLOT"), |
| _("OFFSET"), |
| _("OFFSET"), |
| _("INTERRUPT-LEVEL"), |
| _("INTERRUPT-VECTOR")); |
| tme_free(conn_int); |
| return (EINVAL); |
| } |
| |
| /* fill in the bus connection: */ |
| conn_bus->tme_bus_subregions.tme_bus_subregion_address_first |
| = 0; |
| conn_bus->tme_bus_subregions.tme_bus_subregion_address_last |
| = bus->tme_bus_address_mask; |
| conn_bus->tme_bus_subregions.tme_bus_subregion_next |
| = NULL; |
| conn_bus->tme_bus_signals_add = _tme_bus_signals_add; |
| conn_bus->tme_bus_signal = _tme_bus_signal; |
| conn_bus->tme_bus_intack = _tme_bus_intack; |
| conn_bus->tme_bus_tlb_set_add = _tme_bus_tlb_set_add; |
| conn_bus->tme_bus_tlb_fill = _tme_bus_tlb_fill; |
| |
| /* fill in the generic connection: */ |
| conn->tme_connection_next = *_conns; |
| conn->tme_connection_type = TME_CONNECTION_BUS_GENERIC; |
| conn->tme_connection_score = _tme_bus_connection_score; |
| conn->tme_connection_make = _tme_bus_connection_make; |
| conn->tme_connection_break = _tme_bus_connection_break; |
| |
| /* return the new connection side: */ |
| *_conns = conn; |
| return (TME_OK); |
| } |
| |
| /* this creates a new bus element: */ |
| TME_ELEMENT_SUB_NEW_DECL(tme_generic,bus) { |
| struct tme_bus *bus; |
| tme_bus_addr_t bus_size_mask; |
| tme_bus_addr_t bus_slot_size; |
| tme_bus_addr_t bus_slot_addr; |
| int bus_slot_addr_defined; |
| struct tme_bus_slot *bus_slot; |
| struct tme_bus_slot *bus_slots; |
| int arg_i; |
| int failed; |
| |
| /* our arguments must include the bus size, and the |
| bus size must be a power of two: */ |
| failed = FALSE; |
| arg_i = 1; |
| bus_size_mask = 0; |
| bus_slot_size = 0; |
| bus_slot_addr_defined = FALSE; |
| bus_slot_addr = 0; |
| bus_slots = NULL; |
| for (; !failed; ) { |
| |
| /* the bus size: */ |
| if (TME_ARG_IS(args[arg_i + 0], "size")) { |
| /* XXX FIXME - this is a hack: */ |
| if (sizeof(bus_size_mask) == sizeof(tme_uint32_t) && |
| TME_ARG_IS(args[arg_i + 1], "4GB")) { |
| bus_size_mask = ((tme_bus_addr_t) 0) - 1; |
| } |
| else { |
| bus_size_mask = tme_bus_addr_parse_any(args[arg_i + 1], &failed); |
| if (!failed |
| && bus_size_mask < 2) { |
| failed = TRUE; |
| } |
| else { |
| bus_size_mask -= 1; |
| } |
| } |
| if (bus_size_mask & (bus_size_mask + 1)) { |
| failed = TRUE; |
| } |
| arg_i += 2; |
| } |
| |
| /* the address for the next slots: */ |
| else if (TME_ARG_IS(args[arg_i + 0], "slot-addr")) { |
| bus_slot_addr = tme_bus_addr_parse_any(args[arg_i + 1], &failed); |
| bus_slot_addr_defined = TRUE; |
| arg_i += 2; |
| } |
| |
| /* the size for the next slots: */ |
| else if (TME_ARG_IS(args[arg_i + 0], "slot-size")) { |
| bus_slot_size = tme_bus_addr_parse_any(args[arg_i + 1], &failed); |
| if (bus_slot_size < 1) { |
| failed = TRUE; |
| } |
| arg_i += 2; |
| } |
| |
| /* a slot definition: */ |
| else if (TME_ARG_IS(args[arg_i + 0], "slot")) { |
| if (args[arg_i + 1] == NULL) { |
| failed = TRUE; |
| break; |
| } |
| if (!bus_slot_addr_defined) { |
| failed = TRUE; |
| break; |
| } |
| if (bus_slot_size == 0) { |
| failed = TRUE; |
| break; |
| } |
| |
| /* make sure this slot hasn't already been defined: */ |
| for (bus_slot = bus_slots; |
| bus_slot != NULL; |
| bus_slot = bus_slot->tme_bus_slot_next) { |
| if (strcmp(bus_slot->tme_bus_slot_name, |
| args[arg_i + 1]) == 0) { |
| tme_output_append_error(_output, |
| "slot %s %s", |
| args[arg_i + 1], |
| _("redefined")); |
| failed = TRUE; |
| break; |
| } |
| } |
| if (failed) { |
| break; |
| } |
| |
| /* add this slot: */ |
| bus_slot = tme_new0(struct tme_bus_slot, 1); |
| bus_slot->tme_bus_slot_next = bus_slots; |
| bus_slots = bus_slot; |
| bus_slot->tme_bus_slot_name = tme_strdup(args[arg_i + 1]); |
| bus_slot->tme_bus_slot_address = bus_slot_addr; |
| bus_slot->tme_bus_slot_size = bus_slot_size; |
| |
| /* advance for the next slot: */ |
| bus_slot_addr += bus_slot_size; |
| arg_i += 2; |
| } |
| |
| /* if we've run out of arguments: */ |
| else if (args[arg_i + 0] == NULL) { |
| break; |
| } |
| |
| /* an unknown argument: */ |
| else { |
| tme_output_append_error(_output, |
| "%s %s, ", |
| args[arg_i], |
| _("unexpected")); |
| failed = TRUE; |
| } |
| } |
| if (failed) { |
| tme_output_append_error(_output, |
| "%s %s size %s [ slot-addr %s slot-size %s slot %s0 .. slot %sN ]", |
| _("usage:"), |
| args[0], |
| _("SIZE"), |
| _("ADDRESS"), |
| _("SIZE"), |
| _("SLOT-NAME"), |
| _("SLOT-NAME")); |
| for (; (bus_slot = bus_slots) != NULL; ) { |
| bus_slots = bus_slots->tme_bus_slot_next; |
| tme_free(bus_slot->tme_bus_slot_name); |
| tme_free(bus_slot); |
| } |
| return (EINVAL); |
| } |
| |
| /* allocate and initialize the new bus: */ |
| bus = tme_new0(struct tme_bus, 1); |
| tme_rwlock_init(&bus->tme_bus_rwlock); |
| bus->tme_bus_address_mask = bus_size_mask; |
| bus->tme_bus_addressables_count = 0; |
| bus->tme_bus_addressables_size = 1; |
| bus->tme_bus_addressables = tme_new(struct tme_bus_addressable, |
| bus->tme_bus_addressables_size); |
| bus->tme_bus_signals_count = TME_ARRAY_ELS(_tme_bus_signals_default); |
| bus->tme_bus_signals = tme_dup(struct tme_bus_signals, |
| _tme_bus_signals_default, |
| TME_ARRAY_ELS(_tme_bus_signals_default)); |
| bus->tme_bus_signal_asserts = tme_new0(unsigned int, |
| _tme_bus_signals_default[0].tme_bus_signals_count); |
| bus->tme_bus_slots = bus_slots; |
| |
| /* fill the element: */ |
| element->tme_element_private = bus; |
| element->tme_element_connections_new = _tme_bus_connections_new; |
| |
| return (TME_OK); |
| } |