summaryrefslogtreecommitdiff
path: root/sys/dev/ic/rlnsubr.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/ic/rlnsubr.c')
-rw-r--r--sys/dev/ic/rlnsubr.c934
1 files changed, 934 insertions, 0 deletions
diff --git a/sys/dev/ic/rlnsubr.c b/sys/dev/ic/rlnsubr.c
new file mode 100644
index 00000000000..db6dcae5b91
--- /dev/null
+++ b/sys/dev/ic/rlnsubr.c
@@ -0,0 +1,934 @@
+/* $OpenBSD: rlnsubr.c,v 1.1 1999/07/30 13:43:36 d Exp $ */
+/*
+ * David Leonard <d@openbsd.org>, 1999. Public Domain.
+ *
+ * Low level card protocol access to the Proxim RangeLAN2 wireless
+ * network adaptor.
+ *
+ * Information and ideas gleaned from
+ * - disassembly of Dave Koberstein's <davek@komacke.com> Linux driver
+ * (which is built with Proxim source),
+ * - Yoichi Shinoda's <shinoda@cs.washington.edu> BSDI driver, and
+ * - Geoff Voelker's <voelker@cs.washington.edu> Linux port of the same.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/mbuf.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/syslog.h>
+#include <sys/device.h>
+#include <sys/queue.h>
+#include <sys/proc.h>
+#include <sys/kernel.h>
+
+#include <net/if.h>
+
+#ifdef INET
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#endif
+
+#include <machine/bus.h>
+#include <machine/intr.h>
+
+#include <dev/ic/rln.h>
+#include <dev/ic/rlnvar.h>
+#include <dev/ic/rlnreg.h>
+#include <dev/ic/rlncmd.h>
+
+static int rln_tx_request __P((struct rln_softc *, u_int16_t));
+static int rln_tx_end __P((struct rln_softc *));
+
+/*
+ * Disables or enables interrupts from the card. Returns the old
+ * interrupt-enable state.
+ */
+int
+rln_enable(sc, enable)
+ struct rln_softc * sc;
+ int enable;
+{
+ int s;
+ int was_enabled;
+
+ s = splhigh();
+ was_enabled = (sc->sc_intsel & RLN_INTSEL_ENABLE) ? 1 : 0;
+ if (enable != was_enabled) {
+ if (enable)
+ sc->sc_intsel |= RLN_INTSEL_ENABLE;
+ else
+ sc->sc_intsel &=~RLN_INTSEL_ENABLE;
+ _rln_register_write_1(sc, RLN_REG_INTSEL, sc->sc_intsel);
+ }
+ splx(s);
+ return (was_enabled);
+}
+
+/*
+ * Perform a hard reset of the card. Determines bus width (8 or
+ * 16 bit), if sc->sc_width is unset. Returns 0 on success.
+ * Note: takes about 200ms at splhigh, meaning this is an expensive call,
+ * but normal (error-free) operation of the card will not need more than
+ * two resets - one at probe time, and the other when the interface is
+ * brought up.
+ */
+int
+rln_reset(sc)
+ struct rln_softc * sc;
+{
+ int s;
+ int i;
+ int status;
+ u_int8_t op = 0x00;
+
+ s = splhigh();
+ dprintf(" R[");
+ if (sc->sc_cardtype & (RLN_CTYPE_UISA | RLN_CTYPE_ONE_PIECE))
+ op = 0x04;
+ if (rln_status_read(sc) & RLN_STATUS_WAKEUP) {
+ rln_control_write(sc, op);
+ rln_control_write(sc, op | RLN_CONTROL_RESET);
+ dprintf(" 7ms");
+ DELAY(7000);
+ rln_control_write(sc, op);
+ dprintf(" 7ms");
+ DELAY(7000);
+ }
+ rln_control_write(sc, op);
+ rln_control_write(sc, op);
+ rln_control_write(sc, op | RLN_CONTROL_BIT3);
+ dprintf(" 67ms");
+ DELAY(67000);
+ rln_status_write(sc, 0x00);
+ if (sc->sc_cardtype & (RLN_CTYPE_UISA | RLN_CTYPE_ONE_PIECE))
+ rln_control_write(sc, 0x38);
+ /* RLN_CONTROL_BIT3 | RLN_CONTROL_RESET | RLN_CONTROL_16BIT */
+ else
+ rln_control_write(sc, 0x2c);
+ /* RLN_CONTROL_BIT3 | RLN_CONTROL_BIT2 | RLN_CONTROL_16BIT */
+ dprintf(" 67ms");
+ DELAY(67000);
+ rln_data_write_2(sc, 0xaa55);
+ rln_status_write(sc, 0x5a);
+ splx(s);
+ for (i = 0; i < 200 *5; i++) { /* Proxim says 200. */
+ if ((status = rln_status_read(sc)) == 0x5a)
+ break;
+ DELAY(1000);
+ }
+ dprintf(" (%dms)", i);
+ s = splhigh();
+ if (status != 0x5a) {
+ splx(s);
+ /* Only winge if bus width not yet probed */
+ if (sc->sc_width != 0)
+ printf("%s: reset timeout\n", sc->sc_dev.dv_xname);
+ dprintf("]=-1");
+ return (-1);
+ }
+ if (sc->sc_width == 8) {
+ if (sc->sc_cardtype & (RLN_CTYPE_UISA | RLN_CTYPE_ONE_PIECE))
+ rln_control_write(sc, RLN_CONTROL_BIT3);
+ else
+ rln_control_write(sc, RLN_CONTROL_BIT3 |
+ RLN_CONTROL_BIT2);
+ rln_data_write_1(sc, 0x20);
+ } else if (sc->sc_width == 16) {
+ rln_data_write_2(sc, 0x0000);
+ } else {
+ if (rln_data_read_2(sc) == 0x55aa) {
+ rln_data_write_2(sc, 0x0000);
+ sc->sc_width = 16;
+ } else {
+ if (sc->sc_cardtype & (RLN_CTYPE_UISA |
+ RLN_CTYPE_ONE_PIECE))
+ rln_control_write(sc, RLN_CONTROL_BIT3);
+ else
+ rln_control_write(sc, RLN_CONTROL_BIT3 |
+ RLN_CONTROL_BIT2);
+ rln_data_write_1(sc, 0x20);
+ sc->sc_width = 8;
+ }
+ /* printf("%s: %d bit bus\n", sc->sc_dev.dv_xname,
+ sc->sc_width); */
+ }
+ rln_status_write(sc, 0x00);
+ sc->sc_intsel = 0;
+ rln_intsel_write(sc, sc->sc_irq);
+ splx(s);
+ dprintf("]");
+ return (0);
+}
+
+/*
+ * Sets the new 'wakeup' state. Returns the old wakeup state.
+ * The special state value RLN_WAKEUP_SET should be used to wake the
+ * card up. The card can be partially put to sleep (presumably to save
+ * power) by sending it the 'Standby' command.
+ */
+u_int8_t
+rln_wakeup(sc, wnew)
+ struct rln_softc * sc;
+ u_int8_t wnew;
+{
+ u_int8_t wold, s;
+ int i;
+
+ /* Save what the last-written values were. */
+ wold = (sc->sc_status & RLN_STATUS_WAKEUP) |
+ (sc->sc_control & RLN_CONTROL_RESET);
+
+ if (wnew == RLN_WAKEUP_SET) {
+ /* SetWakeupBit() */
+ dprintf(" Ws[");
+ rln_status_set(sc, RLN_STATUS_WAKEUP);
+ if (0/*LLDInactivityTimeOut &&
+ (sc->sc_cardtype & RLN_CTYPE_OEM)*/) {
+ dprintf (" 167ms");
+ DELAY(167000);
+ } else {
+ dprintf (" .1ms");
+ DELAY(100);
+ }
+ s = rln_status_read(sc);
+ rln_control_set(sc, RLN_CONTROL_RESET);
+ if ((s & RLN_STATUS_WAKEUP) != 0)
+ for (i = 0; i < 9; i++) {
+ dprintf(" 2ms");
+ DELAY(2000);
+ rln_status_set(sc, RLN_STATUS_WAKEUP);
+ }
+ dprintf("]");
+ } else {
+ /* ClearWakeupBit() */
+ dprintf(" Wc[");
+ if ((wnew & RLN_STATUS_WAKEUP) == 0)
+ rln_status_clear(sc, RLN_STATUS_WAKEUP);
+ if ((wnew & RLN_CONTROL_RESET) == 0)
+ rln_control_clear(sc, RLN_CONTROL_RESET);
+ dprintf("]");
+ }
+ return (wold);
+}
+
+/*
+ * Performs the first (request) stage of transmitting a command message
+ * to the card. 'len' is the expected length of the message is needed.
+ * Returns: 0 on success
+ * 1 on timeout
+ * 2 on NAK (card busy, and will need a rln_clear_nak() after 100ms)
+ */
+static int
+rln_tx_request(sc, len)
+ struct rln_softc * sc;
+ u_int16_t len;
+{
+ /* TxRequest() */
+ int s;
+ int i;
+ u_int8_t status;
+
+ /* u_int8_t w; */
+ /* w = rln_wakeup(sc, RLN_WAKEUP_SET); */
+
+ dprintf(" Tr[");
+ if (sc->sc_width == 16) {
+ rln_status_tx_write(sc, RLN_STATUS_TX_HILEN_AVAIL);
+ rln_data_write_2(sc, len);
+ rln_status_tx_int(sc);
+
+ s = spl0();
+ for (i = 0; i < 600; i++) {
+ status = rln_status_tx_read(sc);
+ if (status == RLN_STATUS_TX_HILEN_ACCEPT ||
+ status == RLN_STATUS_TX_ERROR)
+ break;
+ DELAY(1000);
+ }
+ splx(s);
+ dprintf(" %dms", i);
+ if (status == RLN_STATUS_TX_HILEN_ACCEPT)
+ goto success;
+ if (status == RLN_STATUS_TX_ERROR)
+ goto error;
+ } else if (sc->sc_width == 8) {
+ rln_status_tx_write(sc, RLN_STATUS_TX_LOLEN_AVAIL);
+ rln_data_write_1(sc, len & 0xff);
+ rln_status_tx_int(sc);
+ s = spl0();
+ for (i = 0; i < 6800; i++) {
+ status = rln_status_tx_read(sc);
+ if (status == RLN_STATUS_TX_LOLEN_ACCEPT)
+ break;
+ DELAY(1000);
+ }
+ splx(s);
+ dprintf(" %dms", i);
+ if (status == RLN_STATUS_TX_LOLEN_ACCEPT) {
+ rln_data_write_1(sc, (len >> 8) & 0xff);
+ rln_status_tx_write(sc, RLN_STATUS_TX_HILEN_AVAIL);
+ s = spl0();
+ for (i = 0; i < 600; i++) {
+ status = rln_status_tx_read(sc);
+ if (status == RLN_STATUS_TX_HILEN_ACCEPT ||
+ status == RLN_STATUS_TX_ERROR)
+ break;
+ DELAY(1000);
+ }
+ splx(s);
+ dprintf(" %dms", i);
+ if (status == RLN_STATUS_TX_HILEN_ACCEPT)
+ goto success;
+ if (status == RLN_STATUS_TX_ERROR)
+ goto error;
+ }
+ }
+#ifdef DIAGNOSTIC
+ else
+ panic("rln: bus width");
+#endif
+
+ printf("%s: tx_request timed out, status 0x%02x",
+ sc->sc_dev.dv_xname, status);
+ dprintf("]=(1)");
+ return (1);
+
+error:
+ /* Will need to clear nak within 100 ms. */
+ dprintf("]=2");
+#ifdef DIAGNOSTIC
+ printf("%s: tx protocol fault (nak)\n", sc->sc_dev.dv_xname);
+#endif
+ return (2);
+
+success:
+ /* rln_wakeup(sc, w); */
+ dprintf("]=0");
+ return (0);
+}
+
+/*
+ * Performs the third (and final) stage of transmitting a command
+ * message to the card.
+ * Returns: 0 on command success.
+ * non-zero on failure (card will need reset)
+ */
+static int
+rln_tx_end(sc)
+ struct rln_softc * sc;
+{
+ /* EndOfTx() */
+ int i;
+ int s;
+ u_int8_t status;
+
+ dprintf(" Te[");
+ s = spl0();
+ for (i = 0; i < 600; i++) {
+ status = rln_status_tx_read(sc);
+ if (status == RLN_STATUS_TX_XFR_COMPLETE)
+ break;
+ DELAY(1000);
+ }
+ splx(s);
+ if (status == RLN_STATUS_TX_XFR_COMPLETE) {
+ rln_status_tx_write(sc, RLN_STATUS_TX_IDLE);
+ dprintf("]=0");
+ return (0);
+ } else {
+ printf("%s: tx cmd failed (%02x)\n", sc->sc_dev.dv_xname,
+ status);
+ rln_need_reset(sc);
+ dprintf("]=-1");
+ return (-1);
+ }
+}
+
+/*
+ * Performs first (request) stage of receiving a message from the card.
+ * Returns: 0 on failure,
+ * n>0 on success, where 'n' is the length of the message
+ */
+
+int
+rln_rx_request(sc, timeo)
+ struct rln_softc * sc;
+ int timeo; /* milliseconds */
+{
+ /* RxRequest */
+ int s;
+ int len = 0;
+ int i;
+ u_int8_t status;
+ u_int8_t hi, lo;
+
+ dprintf(" Rr[");
+ status = rln_status_rx_read(sc);
+
+ /* Short wait for states 1|5|6. */
+ s = spl0();
+ for (i = 0; i < timeo; i++) {
+ if (status == RLN_STATUS_RX_LOLEN_AVAIL ||
+ status == RLN_STATUS_RX_HILEN_AVAIL ||
+ status == RLN_STATUS_RX_ERROR)
+ break;
+ DELAY(1000);
+ status = rln_status_rx_read(sc);
+ }
+ splx(s);
+ dprintf(" (%dms)",i);
+
+ if (sc->sc_width == 16) {
+ if (status != RLN_STATUS_RX_HILEN_AVAIL)
+ goto badstatus_quiet;
+ /* Read 2 octets. */
+ len = rln_data_read_2(sc);
+ } else if (sc->sc_width == 8) {
+ if (status != RLN_STATUS_RX_LOLEN_AVAIL)
+ goto badstatus_quiet;
+ /* Read low octet. */
+ lo = rln_data_read_1(sc);
+ rln_status_rx_write(sc, RLN_STATUS_RX_LOLEN_ACCEPT);
+ rln_status_rx_int(sc);
+ s = spl0();
+ for (i = 0; i < 600; i++) {
+ status = rln_status_rx_read(sc);
+ if (status == RLN_STATUS_RX_HILEN_AVAIL)
+ break;
+ DELAY(1000);
+ }
+ splx(s);
+ if (status != RLN_STATUS_RX_HILEN_AVAIL)
+ goto badstatus;
+ /* Read high octet. */
+ hi = rln_data_read_1(sc);
+ len = lo | (hi << 8);
+ }
+#ifdef DIAGNOSTIC
+ else
+ panic("rln: bus width %d", sc->sc_width);
+#endif
+
+ dprintf(" len=%d]", len);
+ return (len);
+
+badstatus:
+ printf("%s: rx_request timed out, status %02x\n",
+ sc->sc_dev.dv_xname, status);
+badstatus_quiet:
+ if (status == RLN_STATUS_RX_ERROR)
+ printf("%s: rx protocol error (nak)\n", sc->sc_dev.dv_xname);
+ dprintf("]");
+ return (-1);
+}
+
+/* Performs part of the second (transfer) stage of receiving a data message. */
+void
+rln_rx_pdata(sc, buf, len, pd)
+ struct rln_softc * sc;
+ void * buf;
+ int len;
+ struct rln_pdata * pd;
+{
+ char * data = (char *)buf;
+
+ if (pd->p_nremain) {
+ *data++ = pd->p_data;
+ if (--len == 0)
+ return;
+ }
+
+ pd->p_nremain = 0;
+
+ if (sc->sc_width == 16) {
+ /* Round down to the closest even multiple. */
+ rln_data_read_multi_2(sc, data, len / 2);
+#ifdef RLNDEBUG_REG
+ dprintf(" D>");
+ dprinthex(data, len);
+#endif
+ if (len & 1) {
+ /* Read the last octet plus a bit extra. */
+ union {
+ u_int16_t w;
+ u_int8_t b[2];
+ } u;
+
+ u.w = rln_data_read_2(sc);
+ data[len - 1] = u.b[0];
+ pd->p_data = u.b[1];
+ pd->p_nremain = 1;
+#ifdef RLNDEBUG_REG
+ dprintf(" D>{%02x%02x}", u.b[0], u.b[1]);
+#endif
+ }
+ } else if (sc->sc_width == 8) {
+ rln_data_read_multi_1(sc, data, len);
+#ifdef RLNDEBUG_REG
+ dprintf(" D>");
+ dprinthex(data, len);
+#endif
+ if (len & 1) {
+ /* Must read multiples of two. */
+ pd->p_data = rln_data_read_1(sc);
+ pd->p_nremain = 1;
+#ifdef RLNDEBUG_REG
+ dprintf(" D>{%02x}", pd->p_data);
+#endif
+ }
+ }
+
+}
+
+int
+rln_rx_data(sc, buf, len)
+ struct rln_softc * sc;
+ void * buf;
+ int len;
+{
+ /* RxData() */
+ struct rln_pdata pd = { 0, 0 };
+ int s;
+ int i;
+ u_int8_t status;
+
+ dprintf(" Rd[");
+ rln_status_rx_write(sc, RLN_STATUS_RX_HILEN_ACCEPT);
+ rln_status_rx_int(sc);
+ s = spl0();
+ for (i = 0; i < 600; i++) {
+ status = rln_status_rx_read(sc);
+ if (status == RLN_STATUS_RX_XFR)
+ break;
+ DELAY(1000);
+ }
+ splx(s);
+ if (status != RLN_STATUS_RX_XFR) {
+ dprintf("]=-1");
+ return (-1);
+ }
+
+ rln_rx_pdata(sc, buf, len, &pd);
+#ifdef DIAGNOSTIC
+ /* We should have nothing left over. */
+ if (pd.p_nremain || len & 1)
+ panic("rln_rx_data: leftover");
+#endif
+
+ dprintf("]=0");
+ return (0);
+}
+
+void
+rln_rx_end(sc)
+ struct rln_softc * sc;
+{
+ /* EndOfRx() */
+
+ dprintf(" Re[");
+ rln_status_rx_write(sc, RLN_STATUS_RX_XFR_COMPLETE);
+ rln_status_rx_int(sc);
+ /* rln_wakeup(sc, 0); */
+ dprintf("]");
+}
+
+/* Clear a transmission NAK from the card. */
+void
+rln_clear_nak(sc)
+ struct rln_softc * sc;
+{
+ /* ClearNAK() */
+
+ rln_status_tx_write(sc, RLN_STATUS_CLRNAK);
+ rln_status_tx_int(sc);
+}
+
+/*
+ * Send a command message to the card. Returns;
+ * 2: NAK
+ * -1: failure
+ * 0: success
+ */
+int
+rln_msg_tx_start(sc, buf, pktlen, state)
+ struct rln_softc * sc;
+ void * buf;
+ int pktlen;
+ struct rln_msg_tx_state * state;
+{
+ struct rln_mm_cmd * cmd = (struct rln_mm_cmd *)buf;
+ int ret;
+
+ state->ien = rln_enable(sc, 0);
+ state->pd.p_nremain = 0;
+
+ if (!(cmd->cmd_letter == 'A' && cmd->cmd_fn == 6)) /* Standby. */
+ state->w = rln_wakeup(sc, RLN_WAKEUP_SET);
+ else
+ state->w = RLN_WAKEUP_NOCHANGE;
+
+ ret = rln_tx_request(sc, pktlen);
+ if (ret == 2) {
+ rln_clear_nak(sc);
+ if (sc->sc_cardtype & RLN_CTYPE_OEM)
+ rln_need_reset(sc);
+ ret = 2;
+ }
+ else if (ret == 1) {
+ /* Timeout. */
+ rln_status_tx_write(sc, RLN_STATUS_TX_XFR);
+ ret = -1;
+ }
+ return (ret);
+}
+
+void
+rln_msg_tx_data(sc, buf, len, state)
+ struct rln_softc * sc;
+ void * buf;
+ u_int16_t len;
+ struct rln_msg_tx_state * state;
+{
+ char * data = (char *)buf;
+
+ if (sc->sc_width == 16 && state->pd.p_nremain) {
+ /* XXX htons() needed? */
+ union {
+ u_int8_t b[2];
+ u_int16_t w;
+ } u;
+
+ u.b[0] = state->pd.p_data;
+ if (len) {
+ u.b[1] = *data++;
+ len--;
+ } else
+ u.b[1] = '\0';
+#ifdef RLNDEBUG_REG
+ dprintf(" D<%02x%02x", u.b[0], u.b[1]);
+#endif
+ rln_data_write_2(sc, u.w);
+ state->pd.p_nremain = 0;
+ }
+
+ if (len) {
+ if (sc->sc_width == 16) {
+ if (len >= 2)
+ rln_data_write_multi_2(sc, buf, len / 2);
+ if (len & 1) {
+ state->pd.p_nremain = 1;
+ state->pd.p_data = data[len - 1];
+ }
+ } else if (sc->sc_width == 8)
+ rln_data_write_multi_1(sc, buf, len);
+#ifdef DIAGNOSTIC
+ else
+ panic("rln_msg_tx_data width %d", sc->sc_width);
+#endif
+#ifdef RLNDEBUG_REG
+ dprintf(" D<");
+ dprinthex(data, len);
+#endif
+ }
+}
+
+
+int
+rln_msg_tx_end(sc, state)
+ struct rln_softc * sc;
+ struct rln_msg_tx_state * state;
+{
+ int ret;
+
+ /* Flush the tx buffer. */
+ if (state->pd.p_nremain)
+ rln_msg_tx_data(sc, NULL, 0, state);
+
+#ifdef DIAGNOSTIC
+ if (state->pd.p_nremain)
+ panic("rln_msg_tx_end remain %d", state->pd.p_nremain);
+#endif
+ ret = rln_tx_end(sc);
+ if (sc->sc_arpcom.ac_if.if_flags & IFF_OACTIVE)
+ state->w = RLN_WAKEUP_NOCHANGE;
+ rln_wakeup(sc, state->w);
+ rln_enable(sc, state->ien);
+ return (ret);
+}
+
+/* Return the next unique sequence number to use for a transmitted command */
+u_int8_t
+rln_newseq(sc)
+ struct rln_softc * sc;
+{
+ int s;
+ u_int8_t seq;
+
+ s = splhigh();
+ seq = sc->sc_pktseq++;
+ if (sc->sc_pktseq > RLN_MAXSEQ)
+ sc->sc_pktseq = 0;
+ splx(s);
+ return (seq);
+}
+
+/*
+ * Transmit a command message to, and (optionally) receive a response
+ * message from the card. Each transmitted message has a sequence
+ * number, and corresponding reply messages have the same sequence
+ * number. We use the sequence numbers to index the mailboxes so
+ * that rlnsoftintr() can signal this routine when it has serviced
+ * and correctly received a response.
+ */
+
+int
+rln_msg_txrx(sc, tx, txlen, rx, rxlen)
+ struct rln_softc * sc;
+ void * tx;
+ int txlen;
+ void * rx;
+ int rxlen;
+{
+ struct rln_mm_cmd * txc = (struct rln_mm_cmd *)tx;
+ struct rln_mm_cmd * rxc = (struct rln_mm_cmd *)rx;
+ struct rln_msg_tx_state state;
+ int ien;
+ int ret;
+
+#ifdef DIAGNOSTIC
+ if (rx != NULL && rxlen < sizeof *rxc)
+ panic("rln_msg_txrx");
+#endif
+
+ txc->cmd_seq = rln_newseq(sc);
+
+#ifdef RLNDUMP
+ printf("%s: send %c%d seq %d data ", sc->sc_dev.dv_xname,
+ txc->cmd_letter, txc->cmd_fn, txc->cmd_seq);
+ RLNDUMPHEX(txc, sizeof *txc);
+ printf(":");
+ RLNDUMPHEX((char *)tx + sizeof *txc, txlen - sizeof *txc);
+ printf("\n");
+#endif
+
+ if (rx != NULL)
+ if (rln_mbox_create(sc, txc->cmd_seq, rx, rxlen) < 0)
+ /* Mailbox collision. */
+ return (-1);
+
+ /* Start the transfer. */
+ if ((ret = rln_msg_tx_start(sc, tx, txlen, &state))) {
+ if (rx != NULL)
+ rln_mbox_wait(sc, txc->cmd_seq, -1);
+ return (ret);
+ }
+
+ /* Always send an even number of octets. */
+ rln_msg_tx_data(sc, tx, (txlen + 1) & ~1, &state);
+
+ /* End the transmission. */
+ if ((ret = rln_msg_tx_end(sc, &state))) {
+ /* Destroy mailbox. */
+ if (rx != NULL)
+ rln_mbox_wait(sc, txc->cmd_seq, -1);
+ return (ret);
+ }
+
+ /* Don't wait for reply if there is nowhere to put it. */
+ if (rx == NULL)
+ return (0);
+
+ /* Enable interrupts if not already. */
+ ien = rln_enable(sc, 1);
+
+ /* Wait for the reply message. */
+ if (rln_mbox_wait(sc, txc->cmd_seq, 2000) <= 0) {
+ printf("%s: lost message %c%d seq %d\n", sc->sc_dev.dv_xname,
+ txc->cmd_letter, txc->cmd_fn, txc->cmd_seq);
+ rln_enable(sc, ien);
+ return (-1);
+ }
+ rln_enable(sc, ien);
+
+#ifdef RLNDUMP
+ printf("%s: recv %c%d seq %d data ", sc->sc_dev.dv_xname,
+ rxc->cmd_letter, rxc->cmd_fn, rxc->cmd_seq);
+ RLNDUMPHEX(rxc, sizeof *rxc);
+ printf(":");
+ RLNDUMPHEX(((char *)rx) + sizeof *rxc, rxlen - sizeof *rxc);
+ printf("\n");
+#endif
+
+ /* Check for errors in the received message. */
+ if (rxc->cmd_error & 0x80) {
+ printf("%s: command error 0x%02x command %c%d\n",
+ sc->sc_dev.dv_xname,
+ rxc->cmd_error & ~0x80,
+ rxc->cmd_letter, rxc->cmd_fn);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Mailboxes provide a simple way to tell the interrupt
+ * service routine that someone is expecting a reply message.
+ * Mailboxes are identified by the message sequence number
+ * and also hold a pointer to storage supplied by the waiter.
+ * The interrupt service routine signals the mailbox when it
+ * gets the reply message.
+ */
+
+/* Create a mailbox for filling. */
+int
+rln_mbox_create(sc, seq, buf, len)
+ struct rln_softc * sc;
+ u_int8_t seq;
+ void * buf;
+ size_t len;
+{
+ int s;
+ struct rln_mbox * mb = &sc->sc_mbox[seq];
+
+ dprintf(" <create %d", seq);
+
+#ifdef DIAGNOSTIC
+ if (seq > RLN_NMBOX)
+ panic("mbox create");
+#endif
+
+ s = splhigh();
+ if (mb->mb_state != RLNMBOX_VOID) {
+#ifdef DIAGNOSTIC
+ printf("mbox collision");
+#endif
+ splx(s);
+ return (-1);
+ }
+ mb->mb_buf = buf;
+ mb->mb_len = len;
+ mb->mb_actlen = 0;
+ mb->mb_state = RLNMBOX_EMPTY;
+ dprintf(" empty>");
+ splx(s);
+ return (0);
+}
+
+
+/* Wait for a mailbox to be filled. */
+int
+rln_mbox_wait(sc, seq, timeo)
+ struct rln_softc * sc;
+ u_int8_t seq;
+ int timeo;
+{
+ int i;
+ int s;
+ int ret;
+ volatile struct rln_mbox * mb = &sc->sc_mbox[seq];
+ extern int cold;
+
+ dprintf(" <wait %d", seq);
+
+#ifdef DIAGNOSTIC
+ if (seq > RLN_NMBOX)
+ panic("mbox wait");
+#endif
+ if (cold) {
+ /* Autoconfiguration - spin at spl0. */
+ s = spl0();
+ i = 0;
+ while (mb->mb_state == RLNMBOX_EMPTY && i < timeo) {
+ DELAY(1000);
+ i++;
+ }
+ if (i)
+ dprintf(" %dms", i);
+ while (mb->mb_state == RLNMBOX_FILLING)
+ ;
+ splx(s);
+ } else {
+ tsleep((void *)mb, PRIBIO, "rlnmbox", hz * timeo / 1000);
+ if (mb->mb_state == RLNMBOX_FILLING) {
+ /* Must wait until filled. */
+ s = spl0();
+ while (mb->mb_state == RLNMBOX_FILLING)
+ ;
+ splx(s);
+ }
+ }
+
+ s = splhigh();
+#ifdef DIAGNOSTIC
+ if (mb->mb_state != RLNMBOX_EMPTY && mb->mb_state != RLNMBOX_FILLED)
+ panic("mbox wait %d", mb->mb_state);
+#endif
+ ret = mb->mb_actlen;
+ mb->mb_state = RLNMBOX_VOID;
+ dprintf(" void>=%d", ret);
+ splx(s);
+ return (ret);
+}
+
+/* Lock a mailbox for filling. */
+int
+rln_mbox_lock(sc, seq, bufp, lenp)
+ struct rln_softc * sc;
+ u_int8_t seq;
+ void ** bufp;
+ size_t * lenp;
+{
+ int s;
+ struct rln_mbox * mb = &sc->sc_mbox[seq];
+
+ dprintf(" <lock %d", seq);
+
+ s = splhigh();
+#ifdef DIAGNOSTIC
+ if (seq > RLN_NMBOX)
+ panic("mbox lock");
+#endif
+ if (mb->mb_state != RLNMBOX_EMPTY) {
+ splx(s);
+ dprintf(" ?>");
+ return (-1);
+ }
+
+ mb->mb_state = RLNMBOX_FILLING;
+ dprintf(" filling>");
+ *bufp = mb->mb_buf;
+ *lenp = mb->mb_len;
+
+ splx(s);
+ return (0);
+}
+
+/* Unlock a mailbox and inform the waiter of the actual number of octets. */
+void
+rln_mbox_unlock(sc, seq, actlen)
+ struct rln_softc * sc;
+ u_int8_t seq;
+ size_t actlen;
+{
+ int s;
+ struct rln_mbox * mb = &sc->sc_mbox[seq];
+
+ dprintf(" <unlock %d", seq);
+
+ s = splhigh();
+#ifdef DIAGNOSTIC
+ if (seq > RLN_NMBOX)
+ panic("mbox unlock seq");
+ if (mb->mb_state != RLNMBOX_FILLING)
+ panic("mbox unlock");
+#endif
+ mb->mb_state = RLNMBOX_FILLED;
+ dprintf(" filled>");
+ mb->mb_actlen = actlen;
+ wakeup(mb);
+ splx(s);
+}
+