/*- * Copyright (c) 2005,2006 Option Wireless Sweden AB * Copyright (c) 2006 Sphere Systems Ltd * Copyright (c) 2006 Option Wireless n/v * Copyright (c) 2006 Nikolay Denev * All rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software *Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* NOZOMI_BSD : $Id: nozomi.c,v 1.8 2006/08/23 11:14:03 nike_d Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CFG_SIG 0xEFEFFEFE #define TOGGLE_VALID 0x0000 /* Our fake UART values */ #define MCR_DTR 0x01 #define MCR_RTS 0x02 #define MCR_LOOP 0x04 #define MSR_CTS 0x08 #define MSR_CD 0x10 #define MSR_RI 0x20 #define MSR_DSR 0x40 #define R_IIR 0x0000 /* Interrupt Identity Register */ #define R_FCR 0x0000 /* Flow Control Register */ #define R_IER 0x0004 /* Interrupt Enable Register */ /* Definition of interrupt tokens */ #define MDM_DL1 0x0001 #define MDM_UL1 0x0002 #define MDM_DL2 0x0004 #define MDM_UL2 0x0008 #define DIAG_DL1 0x0010 #define DIAG_DL2 0x0020 #define DIAG_UL 0x0040 #define APP1_DL 0x0080 #define APP1_UL 0x0100 #define APP2_DL 0x0200 #define APP2_UL 0x0400 #define CTRL_DL 0x0800 #define CTRL_UL 0x1000 #define RESET 0x8000 #define MDM_DL (MDM_DL1 | MDM_DL2) #define MDM_UL (MDM_UL1 | MDM_UL2) #define DIAG_DL (DIAG_DL1 | DIAG_DL2) #define ENABLE 1 #define DISABLE 0 #define MAX_PORT 4 #define NOZOMI_MAX_PORTS 5 /* Size of tmp send buffer to card */ #define SEND_BUF_MAX 1024 #define RECEIVE_BUF_MAX 4 #define SET_FCR(value__) bus_write_2(sc->res, (sc->card_type/2)+R_FCR, (value__)); #define SET_IER(value__, mask__) \ sc->ier_last_written = (sc->ier_last_written & ~mask__) | (value__ & mask__ );\ bus_write_2(sc->res, (sc->card_type/2)+R_IER, sc->ier_last_written); #define GET_IER(read_val__) (read_val__) = bus_read_2(sc->res, (sc->card_type/2)+R_IER); #define GET_IIR(read_val__) (read_val__) = bus_read_2(sc->res, (sc->card_type/2)+R_IIR); /* * There are two types of nozomi cards, one with 2048 memory and with 8192 memory * One has 512 bytes downlink and uplink * 2 -> 2048, the other has 3072 bytes downlink and 1024 bytes uplink * 2 -> 8192 */ typedef enum { F32_2 = 2048, F32_8 = 8192, } card_type_t; /* Two different toggle channels exist */ typedef enum { CH_A=0, CH_B=1, } channel_t; /* Port definition for the card regarding flow control */ typedef enum { CTRL_CMD = 0x00, CTRL_MDM = 0x01, CTRL_DIAG = 0x02, CTRL_APP1 = 0x03, CTRL_APP2 = 0x04, CTRL_ERROR = -1, } ctrl_port_t; /* Ports that the nozomi has */ typedef enum { PORT_MDM = 0, PORT_DIAG= 1, PORT_APP1= 2, PORT_APP2= 3, PORT_CTRL= 4, PORT_ERROR=-1, } port_type_t; /* This represents the toggle information */ typedef struct { unsigned mdm_ul : 1; unsigned mdm_dl : 1; unsigned diag_dl : 1; unsigned enabled : 5; /* Toggle fields are valid if enabled is 0, else A-channels must always be used. */ } __attribute__ ((packed)) toggles_t; /* Configuration table to read at startup of card */ typedef struct { u_int32_t sig; u_int16_t version; u_int16_t product_information; toggles_t toggle; u_int8_t pad1[7]; u_int16_t dl_start; u_int16_t dl_mdm1_len; /* If this is 64, it can hold 60 bytes + 4 that is length field */ u_int16_t dl_mdm2_len; u_int16_t dl_diag1_len; u_int16_t dl_diag2_len; u_int16_t dl_app1_len; u_int16_t dl_app2_len; u_int16_t dl_ctrl_len; u_int8_t pad2[16]; u_int16_t ul_start; u_int16_t ul_mdm2_len; u_int16_t ul_mdm1_len; u_int16_t ul_diag_len; u_int16_t ul_app1_len; u_int16_t ul_app2_len; u_int16_t ul_ctrl_len; } __attribute__ ((packed)) config_table_t; /* This stores all control downlink flags */ typedef struct { unsigned DSR : 1; unsigned DCD : 1; unsigned RI : 1; unsigned CTS : 1; unsigned reserverd : 4; u_int8_t port; } __attribute__ ((packed)) ctrl_dl_t; /* This stores all control uplink flags */ typedef struct { unsigned DTR : 1; unsigned RTS : 1; unsigned reserved : 6; u_int8_t port; } __attribute__ ((packed)) ctrl_ul_t; /* This is a data packet that is read or written to/from card */ typedef struct { u_int32_t size; /* size is the length of the data buffer */ u_int8_t *data; } __attribute__ ((packed)) buf_t; STAILQ_HEAD(, fifo_buf) fifo_head = STAILQ_HEAD_INITIALIZER(fifo_head); #define FIFO_BUF_MAX 1024 struct fifo_buf { u_char data[FIFO_BUF_MAX]; u_int size; STAILQ_ENTRY(fifo_buf) fifo_bufs; }; /* This holds all information that is needed regarding a port */ typedef struct { u_int8_t update_flow_control; ctrl_ul_t ctrl_ul; ctrl_dl_t ctrl_dl; struct fifo_buf *fifo_ul; u_int32_t dl_offset[2]; u_int32_t dl_size[2]; u_int8_t toggle_dl; u_int32_t ul_offset[2]; u_int32_t ul_size[2]; u_int8_t toggle_ul; u_int16_t token_dl; struct tty *tty; u_char sc_lsr; u_char sc_msr; u_char sc_mcr; u_char sc_state; int tty_open_count; //struct semaphore tty_sem; //wait_queue_head_t tty_wait; //struct async_icount tty_icount; int tty_devunit; u_int32_t rx_data, tx_data; u_int8_t tty_dont_flip; } port_t; // ************************* LUNIX /* The softc holds our per-instance data. */ struct nozomi_softc { device_t dev; card_type_t card_type; /* Memory Resources */ int rid; struct resource *res; /* Interrupt Resource */ int intr_rid; struct resource *intr_res; u_int16_t ier_last_written; void *intr_cookie; struct mtx intr_lock; struct task flip_tty_task; u_int8_t dying; config_table_t cfg_table; port_t port[NOZOMI_MAX_PORTS]; u_int8_t send_buf[SEND_BUF_MAX]; }; /* nozomi device methods */ static int nozomi_read_config_table(struct nozomi_softc *); static void nozomi_setup_private_data(struct nozomi_softc *); static void nozomi_interrupt_handler(void *); static int receive_flow_control(struct nozomi_softc *); static int nozomi_cards; static void intr_ul(struct nozomi_softc *, port_type_t, int); //static void intr_dl(struct nozomi_softc *, port_type_t, int); static int receive_data(port_type_t , struct nozomi_softc *); static int send_data(port_type_t, struct nozomi_softc *); int portbytty(struct tty *); static int nzmodem(struct tty *, int, int); static void nozomi_rts(struct nozomi_softc *sc, int index, int rts) { sc->port[index].ctrl_ul.RTS = rts; sc->port[index].update_flow_control = 1; intr_ul(sc, PORT_CTRL, ENABLE); } static void nozomi_dtr(struct nozomi_softc *sc, int index, int dtr) { sc->port[index].ctrl_ul.DTR = dtr; sc->port[index].update_flow_control = 1; intr_ul(sc, PORT_CTRL, ENABLE); } int portbytty(struct tty *tty) { struct nozomi_softc *sc; sc = tty->t_sc; int i; for(i=PORT_MDM; i<=MAX_PORT; i++) { if(sc->port[i].tty_devunit == tty->t_devunit) return i; } return -1; } static int nzopen(struct tty *tty, struct cdev *dev) { struct nozomi_softc *sc; sc = tty->t_sc; int pidx; port_t *port; pidx = portbytty(tty); port = &sc->port[pidx]; port->tty_open_count++; if (port->tty_open_count == 1) { port->rx_data = port->tx_data = 0; //mtx_lock_spin(&sc->intr_lock); SET_IER(port->token_dl, port->token_dl); //mtx_unlock_spin(&sc->intr_lock); } return 0; }; static void nzclose(struct tty *tty) { struct nozomi_softc *sc; int pidx; sc = tty->t_sc; port_t *port; pidx = portbytty(tty); port = &sc->port[pidx]; port->tty_open_count--; if (port->tty_open_count == 0) { //mtx_lock_spin(&sc->intr_lock); SET_IER(0, port->token_dl); //mtx_unlock_spin(&sc->intr_lock); } return; }; static void nzstart(struct tty *tty) { struct nozomi_softc *sc; sc = tty->t_sc; //int s; u_char *data; int cnt; int pidx = portbytty(tty); struct cblock *cbp; struct fifo_buf *buf; if(sc->dying) return; if (tty->t_state & TS_TBLOCK) { if ((sc->port[pidx].sc_mcr & SER_RTS) && (sc->port[pidx].sc_state & CRTS_IFLOW)) { printf("nz_start: clear RTS\n"); nzmodem(tty, 0, SER_RTS); } } else { if (!(sc->port[pidx].sc_mcr & SER_RTS) && tty->t_rawq.c_cc <= tty->t_ilowat && (sc->port[pidx].sc_state & CRTS_IFLOW)) { printf("nz_start: set RTS\n"); nzmodem(tty, SER_RTS, 0); } } #if 0 if (tty->t_state & ( TS_BUSY | TS_TIMEOUT | TS_TTSTOP )) { ttwwakeup(tty); printf("ucomstart: stopped\n"); goto out; } #endif #if 0 if (tty->t_outq.c_cc <= tty->t_olowat) { if (tty->t_state & TS_SO_OLOWAT) { tty->t_state &= ~(unsigned)TS_SO_OLOWAT; wakeup(TSA_OLOWAT(tty)); } selwakeuppri(&tty->t_wsel, TTIPRI); if (tty->t_outq.c_cc == 0) { if ((tty->t_state & (TS_BUSY | TS_SO_OCOMPLETE)) == TS_SO_OCOMPLETE && tty->t_outq.c_cc == 0) { tty->t_state &= ~(unsigned)TS_SO_OCOMPLETE; wakeup(TSA_OCOMPLETE(tty)); } goto out; } } #endif data = tty->t_outq.c_cf; cbp = (struct cblock *) ((intptr_t) tty->t_outq.c_cf & ~CROUND); cnt = min((char *) (cbp+1) - tty->t_outq.c_cf, tty->t_outq.c_cc); if(cnt == 0) goto out; //tty->t_state |= TS_BUSY; buf = malloc(sizeof(struct fifo_buf), M_DEVBUF, M_NOWAIT); memcpy(buf->data, data, cnt < sizeof(buf->data) ? cnt : sizeof(buf->data)); buf->size = cnt; STAILQ_INSERT_TAIL(&fifo_head, buf, fifo_bufs); ndflush(&tty->t_outq, cnt); intr_ul(sc, pidx, ENABLE); out: return; }; static void nzstop(struct tty *tty, int rw) { struct nozomi_softc *sc; sc = tty->t_sc; if(rw & FWRITE) { /* 0x0002 */ } if(rw & FREAD) { /* 0x0001 */ } nzstart(tty); return; } static int nzparam(struct tty *tty, struct termios *tios) { struct nozomi_softc *sc; int pidx; int cflag; sc = tty->t_sc; pidx = portbytty(tty); if(sc->dying) return(EIO); if (tios->c_ospeed == 0) (void)nzmodem(tty, 0, SER_DTR);/* hang up line */ else (void)nzmodem(tty, SER_DTR, 0); cflag = tios->c_cflag; #if 0 switch (cflag & CSIZE) { case CS5: cfcr = CFCR_5BITS; break; case CS6: cfcr = CFCR_6BITS; break; case CS7: cfcr = CFCR_7BITS; break; default: cfcr = CFCR_8BITS; break; } if (cflag & PARENB) { cfcr |= CFCR_PENAB; if (!(cflag & PARODD)) cfcr |= CFCR_PEVEN; } if (cflag & CSTOPB) cfcr |= CFCR_STOPB; #endif ttsetwater(tty); //ttyldoptim(tty); return 0; } static void nzbreak(struct tty *tty, int flags) { int pidx; pidx = portbytty(tty); printf("%s called for port %d!\n", __func__, pidx); return; } static int nzmodem(struct tty *tty, int sigon, int sigoff) { struct nozomi_softc *sc; int mcr; int msr; int onoff; int pidx; sc = tty->t_sc; pidx = portbytty(tty); if (sigon == 0 && sigoff == 0) { mcr = sc->port[pidx].sc_mcr; if (mcr & SER_DTR) sigon |= SER_DTR; if (mcr & SER_RTS) sigon |= SER_RTS; msr = sc->port[pidx].sc_msr; if (msr & SER_CTS) sigon |= SER_CTS; if (msr & SER_DCD) sigon |= SER_DCD; if (msr & SER_DSR) sigon |= SER_DSR; if (msr & SER_RI) sigon |= SER_RI; return (sigon); } mcr = sc->port[pidx].sc_mcr; if (sigon & SER_DTR) mcr |= SER_DTR; if (sigoff & SER_DTR) mcr &= ~SER_DTR; if (sigon & SER_RTS) mcr |= SER_RTS; if (sigoff & SER_RTS) mcr &= ~SER_RTS; sc->port[pidx].sc_mcr = mcr; onoff = (sc->port[pidx].sc_mcr & SER_DTR) ? 1 : 0; nozomi_dtr(sc, pidx, onoff); onoff = (sc->port[pidx].sc_mcr & SER_RTS) ? 1 : 0; nozomi_rts(sc, pidx, onoff); return 0; } static int nzioctl(struct tty *tty, u_long cmd, void *data, int flag, struct thread *td) { printf("nozomi ioctl cmd = %ld\n", cmd); return 0; } #if 0 static void tty_flip_queue_function(void *arg) { struct nozomi_softc *sc = (struct nozomi_softc *)arg; int i; printf("taskqueue function run\n"); /* Enable interrupt for that port */ for(i=0;iport[i].tty_dont_flip) { sc->port[i].tty_dont_flip = 0; //mtx_lock_spin(&sc->intr_lock); intr_dl(sc, i, ENABLE); //mtx_unlock_spin(&sc->intr_lock); } } } #endif void intr_ul(struct nozomi_softc *sc, port_type_t port, int set) { switch(port) { case PORT_MDM: SET_IER( set ? MDM_UL : 0 , MDM_UL ); break; case PORT_DIAG: SET_IER( set ? DIAG_UL : 0 , DIAG_UL ); break; case PORT_APP1: SET_IER( set ? APP1_UL : 0 , APP1_UL ); break; case PORT_APP2: SET_IER( set ? APP2_UL : 0 , APP2_UL ); break; case PORT_CTRL: SET_IER( set ? CTRL_UL : 0 , CTRL_UL ); break; default: printf("%s wrong port?\n", __func__); break; }; } #if 0 void intr_dl(struct nozomi_softc *sc, port_type_t port, int set) { switch(port) { case PORT_MDM: SET_IER( set ? MDM_DL : 0 , MDM_DL ); break; case PORT_DIAG: SET_IER( set ? DIAG_DL : 0 , DIAG_DL ); break; case PORT_APP1: SET_IER( set ? APP1_DL : 0 , APP1_DL ); break; case PORT_APP2: SET_IER( set ? APP2_DL : 0 , APP2_DL ); break; case PORT_CTRL: SET_IER( set ? CTRL_DL : 0 , CTRL_DL ); break; default: printf("%s wrong port?\n", __func__); break; }; } #endif static u_int8_t port2ctrl(port_type_t port) { switch(port) { case PORT_MDM: return CTRL_MDM; case PORT_DIAG: return CTRL_DIAG; case PORT_APP1: return CTRL_APP1; case PORT_APP2: return CTRL_APP2; default: printf("%s wrong port?\n", __func__); }; return -1; } /* Return 0 - If we have updated all flow control */ /* Return 1 - If we need to update more flow control, ack current enable more */ static int send_flow_control(struct nozomi_softc *sc) { u_int32_t i; u_int32_t more_flow_control_to_be_updated = 0; u_int16_t *ctrl; for(i=PORT_MDM; iport[i].update_flow_control && more_flow_control_to_be_updated) return 1; sc->port[i].ctrl_ul.port = port2ctrl(i); ctrl = (u_int16_t *)&sc->port[i].ctrl_ul; /* D1( "sending flow control 0x%04X for port %d, %d", (u16) *ctrl, i, dc->port[i].ctrl_ul.port ); */ /* XXX */ //bus_write_4(sc->res, sc->port[PORT_CTRL].ul_offset[0], *(u_int32_t *)&ctrl); bus_write_2(sc->res, sc->port[PORT_CTRL].ul_offset[0], *ctrl); sc->port[i].update_flow_control = 0; more_flow_control_to_be_updated = 1; } return 0; } static int receive_flow_control(struct nozomi_softc *sc) { port_type_t port; ctrl_dl_t ctrl_dl; ctrl_dl_t old_ctrl; /* default port */ port = PORT_MDM; /* read ctrl_dl */ *(u_int32_t *)&ctrl_dl = bus_read_4(sc->res, sc->port[PORT_CTRL].dl_offset[CH_A]); switch(ctrl_dl.port) { /* XXX WTF is this? :) */ case CTRL_CMD: printf("The Base Band sends this value as a response to a request for IMSI" " detach sent over the control channel uplink (see sec. 7.6.1)."); break; case CTRL_MDM: port = PORT_MDM; break; case CTRL_DIAG: port = PORT_DIAG; break; case CTRL_APP1: port = PORT_APP1; break; case CTRL_APP2: port = PORT_APP2; break; default: printf("ERROR: flow control received for non-existing port"); return 0; }; old_ctrl = sc->port[port].ctrl_dl; sc->port[port].ctrl_dl = ctrl_dl; if ( old_ctrl.CTS == 1 && ctrl_dl.CTS == 0 ) { intr_ul(sc, port, DISABLE); } else if( old_ctrl.CTS == 0 && ctrl_dl.CTS == 1 ) { if (!STAILQ_EMPTY(&fifo_head)) intr_ul(sc, port, ENABLE); } /* return if no change in mctrl */ if(*(u_int16_t *)&old_ctrl == *(u_int16_t *)&ctrl_dl) return 1; /* Update statistics */ if(old_ctrl.CTS != ctrl_dl.CTS) { //printf("CTS change!\n"); //sc->port[port].tty_icount.cts++; } if(old_ctrl.DSR != ctrl_dl.DSR) { //printf("DSR change!\n"); //sc->port[port].tty_icount.dsr++; } if(old_ctrl.RI != ctrl_dl.RI) { //printf("RI change!\n"); //sc->port[port].tty_icount.rng++; } if(old_ctrl.DCD != ctrl_dl.DCD) { //printf("DCD change!\n"); //sc->port[port].tty_icount.dcd++; } //device_printf(sc->dev, "port: %d DCD(%d), CTS(%d), RI(%d), DSR(%d)", //port, sc->port[port].tty_icount.dcd, sc->port[port].tty_icount.cts, //sc->port[port].tty_icount.rng, sc->port[port].tty_icount.dsr); return 1; } /* Handle donlink data, ports that are handled are modem and diagnostics */ /* Return 1 - ok */ /* Return 0 - toggle fields are out of sync */ static int handle_data_dl(struct nozomi_softc *sc, u_int16_t irq, port_type_t port, u_int8_t *toggle, u_int16_t mask1, u_int16_t mask2) { if(*toggle == 0 && irq & mask1) { if(receive_data(port, sc)) { SET_FCR(mask1); *toggle = !(*toggle); } if(irq & mask2) { if(receive_data(port, sc)) { SET_FCR(mask2); *toggle = !(*toggle); } } } else if(*toggle == 1 && irq & mask2) { if (receive_data(port, sc)) { SET_FCR(mask2); *toggle = !(*toggle); } if (irq & mask1) { if (receive_data(port, sc)) { SET_FCR(mask1); *toggle = !(*toggle); } } } else { printf("port out of sync!, toggle:%d\n", *toggle); return 0; } return 1; } /* Handle uplink data, this is currently for the modem port */ /* Return 1 - ok */ /* Return 0 - toggle field are out of sync */ static int handle_data_ul(struct nozomi_softc *sc, u_int16_t irq, port_type_t port) { u_int8_t *toggle = &(sc->port[port].toggle_ul); if(*toggle==0 && irq & MDM_UL1) { SET_IER(0, MDM_UL); if(send_data(port, sc)) { SET_FCR(MDM_UL1); SET_IER(MDM_UL, MDM_UL); *toggle = !(*toggle); } if(irq & MDM_UL2) { SET_IER(0, MDM_UL); if(send_data(port, sc)) { SET_FCR(MDM_UL2); SET_IER(MDM_UL, MDM_UL); *toggle = !(*toggle); } } } else if(*toggle==1 && irq & MDM_UL2) { SET_IER(0, MDM_UL); if (send_data(port, sc)) { SET_FCR(MDM_UL2); SET_IER(MDM_UL, MDM_UL); *toggle = !(*toggle); } if (irq & MDM_UL1 ) { SET_IER(0, MDM_UL); if (send_data(port, sc)) { SET_FCR(MDM_UL1); SET_IER(MDM_UL, MDM_UL); *toggle = !(*toggle); } } } else { SET_FCR(irq & MDM_UL); printf("port out of sync!\n"); return 0; } return 1; } /* Return 1 - send buffer to card and ack. */ /* Return 0 - don't ack, don't send buffer to card. */ int send_data(port_type_t index, struct nozomi_softc *sc) { u_int32_t size = 0; port_t *port = &sc->port[index]; u_int8_t toggle = port->toggle_ul; u_int32_t ul_offs = port->ul_offset[toggle]; u_int32_t ul_size = port->ul_size[toggle]; struct fifo_buf *buf; //int s; //struct tty *tty = port->tty; if (STAILQ_EMPTY(&fifo_head)) { //printf("no data?\n"); return 0; } buf = STAILQ_FIRST(&fifo_head); size = buf->size; if ( size <= 0 ) { printf("incorrect buf size?"); } memcpy(sc->send_buf, buf->data, ul_size < SEND_BUF_MAX ? ul_size : SEND_BUF_MAX ); STAILQ_REMOVE_HEAD(&fifo_head, fifo_bufs); free(buf, M_DEVBUF); port->tx_data += size; /* Write length + data */ bus_write_4(sc->res, ul_offs, size); bus_write_region_4(sc->res, ul_offs + 4, (u_int32_t *)sc->send_buf, size); //if (port->tty) { // ttwwakeup(port->tty); //} return 1; } /* If all data has been read, return 1, else 0 */ static int receive_data(port_type_t index, struct nozomi_softc *sc) { u_int8_t buf[RECEIVE_BUF_MAX] = {0}; int i, size; port_t *port = &sc->port[index]; u_int8_t toggle = port->toggle_dl; u_int32_t dl_offs = port->dl_offset[toggle]; u_int32_t offset = 4; struct tty *tty = port->tty; if (!tty) { printf("tty not open for port: %d?", index); return 1; } #if 0 if(bit_test(&tty->t_flags, TTY_DONT_FLIP)) { printf("TTY_DONT_FLIP set!! %d\n", index); /* Here we disable interrupt for that port and schedule */ /* task. Task wakes up a little bit later and enables interrupt.. */ port->tty_dont_flip = 1; intr_dl(sc, index, DISABLE); if(!taskqueue_enqueue(taskqueue_swi_giant, &sc->flip_tty_task)) { printf("error adding work to taskqueue\n"); return 0; } } #endif size = bus_read_4(sc->res, dl_offs); #if 0 if(bit_test(&tty->flags, TTY_THROTTLED)) { printf("No room in tty, don't read data, don't ack interrupt, disable interrupt\n"); /* disable interrupt in downlink... */ intr_dl(sc, index, DISABLE); return 0; } #endif if (size == 0) { printf("size == 0?\n"); return 1; } while(size > 0) { *(u_int32_t *)&buf = bus_read_4(sc->res, dl_offs + offset); i = 0; while (i < 4 && size > 0) { if (ttyld_rint(tty, buf[i]) == -1) { printf("lost char! %c", buf[i]); } port->rx_data++; i++; size--; } offset += 4; } //ttwwakeup(tty); return 1; } void nozomi_interrupt_handler(void *arg) { struct nozomi_softc *sc = (struct nozomi_softc *)arg; u_int16_t irq; /* get irq identity */ GET_IIR(irq); /* handle only interrupts set in IER */ irq &= sc->ier_last_written; if(irq == 0) return; //mtx_lock_spin(&sc->intr_lock); /* RESET interrupt */ if (irq & RESET) { if (!nozomi_read_config_table(sc)) { SET_IER(0, 0xFFFF); device_printf(sc->dev, "couldn't read status from card, disabling interrupts\n"); } else { SET_FCR(RESET); } goto out; } /* CTRL_UL interrupt */ if(irq & CTRL_UL) { SET_IER(0, CTRL_UL); if(send_flow_control(sc)) { SET_FCR(CTRL_UL); SET_IER(CTRL_UL, CTRL_UL); } } /* CTRL_DL interrupt */ if(irq & CTRL_DL) { receive_flow_control(sc); SET_FCR(CTRL_DL); } /* MDM_DL interrupt */ if(irq & MDM_DL) { if(!(handle_data_dl(sc, irq, PORT_MDM, &(sc->port[PORT_MDM].toggle_dl), MDM_DL1, MDM_DL2))) { printf("MDM_DL out of sync!\n"); goto out; } } /* MDM_UL interrupt */ if(irq & MDM_UL) { if(!handle_data_ul(sc, irq, PORT_MDM)) { printf("MDM_UL out of sync!\n"); goto out; } } /* DIAG_DL interrupt */ if(irq & DIAG_DL) { if ( !(handle_data_dl(sc, irq, PORT_DIAG, &(sc->port[PORT_DIAG].toggle_dl), DIAG_DL1, DIAG_DL2)) ) { printf("DIAG_DL out of sync!\n"); goto out; } } /* DIAG_UL interrupt */ if(irq & DIAG_UL) { SET_IER(0, DIAG_UL); if(send_data(PORT_DIAG, sc)) { SET_FCR(DIAG_UL); SET_IER(DIAG_UL, DIAG_UL); } } /* APP1_DL interrupt */ if(irq & APP1_DL) { if (receive_data(PORT_APP1, sc)) { SET_FCR(APP1_DL); } } /* APP1_UL interrupt */ if(irq & APP1_UL) { SET_IER(0, APP1_UL); if(send_data(PORT_APP1, sc)) { SET_FCR(APP1_UL); SET_IER(APP1_UL, APP1_UL); } } /* APP2_DL interrupt */ if(irq & APP2_DL) { if (receive_data(PORT_APP2, sc)) { SET_FCR(APP2_DL); } } /* APP2_UL interrupt */ if(irq & APP2_UL) { SET_IER(0, APP2_UL); if(send_data(PORT_APP2, sc)) { SET_FCR(APP2_UL); SET_IER(APP2_UL, APP2_UL); } } out: if(0) { mtx_unlock_spin(&sc->intr_lock); } } /* Setup pointers to different channels and also setup buffer sizes. */ static void setup_memory(struct nozomi_softc *sc) { u_int16_t offset = sc->cfg_table.dl_start; /* The length reported is including the length field of 4 bytes, hence subtract with 4. */ u_int16_t buff_offset = 4; /* Modem port dl configuration */ sc->port[PORT_MDM].dl_offset[CH_A] = offset; sc->port[PORT_MDM].dl_offset[CH_B] = (offset += sc->cfg_table.dl_mdm1_len); sc->port[PORT_MDM].dl_size[CH_A] = sc->cfg_table.dl_mdm1_len - buff_offset; sc->port[PORT_MDM].dl_size[CH_B] = sc->cfg_table.dl_mdm2_len - buff_offset; /* Diag port dl configuration */ sc->port[PORT_DIAG].dl_offset[CH_A] = (offset += sc->cfg_table.dl_mdm2_len); sc->port[PORT_DIAG].dl_size[CH_A] = sc->cfg_table.dl_diag1_len - buff_offset; sc->port[PORT_DIAG].dl_offset[CH_B] = (offset += sc->cfg_table.dl_diag1_len); sc->port[PORT_DIAG].dl_size[CH_B] = sc->cfg_table.dl_diag2_len - buff_offset; /* App1 port dl configuration */ sc->port[PORT_APP1].dl_offset[CH_A] = (offset += sc->cfg_table.dl_diag2_len); sc->port[PORT_APP1].dl_size[CH_A] = sc->cfg_table.dl_app1_len - buff_offset; /* App2 port dl configuration */ sc->port[PORT_APP2].dl_offset[CH_A] = (offset += sc->cfg_table.dl_app1_len); sc->port[PORT_APP2].dl_size[CH_A] = sc->cfg_table.dl_app2_len - buff_offset; /* Ctrl dl configuration */ sc->port[PORT_CTRL].dl_offset[CH_A] = (offset += sc->cfg_table.dl_app2_len); sc->port[PORT_CTRL].dl_size[CH_A] = sc->cfg_table.dl_ctrl_len - buff_offset; /* Modem Port ul configuration */ sc->port[PORT_MDM].ul_offset[CH_A] = (offset = sc->cfg_table.ul_start); sc->port[PORT_MDM].ul_size[CH_A] = sc->cfg_table.ul_mdm1_len - buff_offset; sc->port[PORT_MDM].ul_offset[CH_B] = (offset += sc->cfg_table.ul_mdm1_len); sc->port[PORT_MDM].ul_size[CH_B] = sc->cfg_table.ul_mdm2_len - buff_offset; /* Diag port ul configuration */ sc->port[PORT_DIAG].ul_offset[CH_A] = (offset += sc->cfg_table.ul_mdm2_len); sc->port[PORT_DIAG].ul_size[CH_A] = sc->cfg_table.ul_diag_len - buff_offset; /* App1 port ul configuration */ sc->port[PORT_APP1].ul_offset[CH_A] = (offset += sc->cfg_table.ul_diag_len); sc->port[PORT_APP1].ul_size[CH_A] = sc->cfg_table.ul_app1_len - buff_offset; /* App2 port ul configuration */ sc->port[PORT_APP2].ul_offset[CH_A] = (offset += sc->cfg_table.ul_app1_len); sc->port[PORT_APP2].ul_size[CH_A] = sc->cfg_table.ul_app2_len - buff_offset; /* Ctrl ul configuration */ sc->port[PORT_CTRL].ul_offset[CH_A] = (offset += sc->cfg_table.ul_app2_len); sc->port[PORT_CTRL].ul_size[CH_A] = sc->cfg_table.ul_ctrl_len - buff_offset; offset = sc->cfg_table.ul_start; } static void nozomi_tty_init(struct nozomi_softc *sc) { int i; struct tty *tmptty; for(i=PORT_MDM; i<=MAX_PORT; i++) { tmptty = sc->port[i].tty = ttyalloc(); tmptty->t_open = nzopen; tmptty->t_close = nzclose; tmptty->t_oproc = nzstart; tmptty->t_stop = nzstop; tmptty->t_param = nzparam; tmptty->t_break = nzbreak; tmptty->t_modem = nzmodem; tmptty->t_ioctl = nzioctl; tmptty->t_sc = sc; ttycreate(tmptty, NULL, 0, 0, "N%r", i); sc->port[i].tty_devunit = tmptty->t_devunit; sc->port[i].tty_open_count = 0; } } static int nozomi_read_config_table(struct nozomi_softc *sc) { int i; //struct tty *tmptty; /* read config table in memory */ bus_read_region_1(sc->res, 0, (u_int8_t *)&sc->cfg_table, sizeof(config_table_t)); if(sc->cfg_table.sig != CFG_SIG) { device_printf(sc->dev, "config table bad : 0x%08X != 0x%08X\n", sc->cfg_table.sig, CFG_SIG); return 0; } if((sc->cfg_table.version == 0) || (sc->cfg_table.toggle.enabled == TOGGLE_VALID) ) { setup_memory(sc); sc->port[PORT_MDM].toggle_ul = sc->cfg_table.toggle.mdm_ul; sc->port[PORT_MDM].toggle_dl = sc->cfg_table.toggle.mdm_dl; sc->port[PORT_DIAG].toggle_dl = sc->cfg_table.toggle.diag_dl; STAILQ_INIT(&fifo_head); for (i=PORT_MDM; i< MAX_PORT;i++) { memset(&sc->port[i].ctrl_dl, 0, sizeof(ctrl_dl_t)); memset(&sc->port[i].ctrl_ul, 0, sizeof(ctrl_ul_t)); } /* Enable control channel */ SET_IER( CTRL_DL, CTRL_DL ); device_printf(sc->dev, "initialization complete\n"); return 1; } if((sc->cfg_table.version > 0) && (sc->cfg_table.toggle.enabled != TOGGLE_VALID)) { u_int32_t zero = 0; device_printf(sc->dev, "ver. %d with %u bytes of memory\n", sc->cfg_table.version, sc->card_type); /* Here we should disable all I/O over F32. */ setup_memory(sc); /* We should send ALL channel pair tokens back along with reset token */ /* push upload modem buffers */ bus_write_4(sc->res, sc->port[PORT_MDM].ul_offset[CH_A], zero); bus_write_4(sc->res, sc->port[PORT_MDM].ul_offset[CH_B], zero); SET_FCR( MDM_UL | DIAG_DL | MDM_DL ); } return 1; } static void nozomi_setup_private_data(struct nozomi_softc *sc) { int i; sc->ier_last_written = 0; sc->dying = 0; sc->port[PORT_MDM ].token_dl = MDM_DL; sc->port[PORT_DIAG].token_dl = DIAG_DL; sc->port[PORT_APP1].token_dl = APP1_DL; sc->port[PORT_APP2].token_dl = APP2_DL; for(i=PORT_MDM;iport[i].rx_data = sc->port[i].tx_data = 0; sc->port[i].tty_dont_flip = 0; } } static int nozomi_probe(device_t dev) { if (pci_get_vendor(dev) == 0x1931 && pci_get_device(dev) == 0x000c) { device_set_desc(dev, "Option N.V. GlobeTrotter 3G+"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int nozomi_attach(device_t dev) { struct nozomi_softc *sc; nozomi_cards++; if(nozomi_cards > 1) { printf("nozomi driver supports only one card per system!\n"); return(ENXIO); } sc = device_get_softc(dev); sc->dev = dev; sc->rid = PCIR_BAR(0); sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->rid, RF_ACTIVE); if(sc->res == NULL) { device_printf(sc->dev, "unable to allocate memory resource!\n"); return(ENXIO); } sc->intr_rid = 0; sc->intr_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->intr_rid, RF_SHAREABLE | RF_ACTIVE); if(sc->intr_res == NULL) { device_printf(dev, "unable to allocate interrupt resource!\n"); return(ENXIO); } sc->card_type = rman_get_size(sc->res) == 2048 ? F32_2 : F32_8; nozomi_setup_private_data(sc); /* disable interrupts */ SET_IER(0, 0xFFFF); /* setup ttys */ nozomi_tty_init(sc); /* setup interrupt and interrupt handler */ if(bus_setup_intr(dev, sc->intr_res, INTR_TYPE_TTY , nozomi_interrupt_handler, sc, &sc->intr_cookie)) { printf("unable to register interrupt handler\n"); goto fail; } //TASK_INIT(&sc->flip_tty_task, 0, (task_fn_t *)tty_flip_queue_function, sc); /* initialize interrupt handler spin mutex */ //mtx_init(&sc->intr_lock, device_get_nameunit(dev), NULL, MTX_SPIN); /* enable RESET interrupt */ SET_IER(RESET, 0xFFFF); return 0; fail: nozomi_cards--; bus_release_resource(dev, SYS_RES_MEMORY, sc->rid, sc->res); bus_release_resource(dev, SYS_RES_IRQ, sc->intr_rid, sc->intr_res); return(ENXIO); } static int nozomi_detach(device_t dev) { struct nozomi_softc *sc; int i; ctrl_ul_t ctrl; sc = device_get_softc(dev); SET_IER(0, 0xFFFF); ctrl.port = 0x00; ctrl.reserved = 0; ctrl.RTS=0; ctrl.DTR=1; nozomi_setup_private_data(sc); bus_write_4(sc->res, sc->port[PORT_CTRL].ul_offset[0], *(u_int32_t *)&ctrl); SET_FCR(CTRL_UL); for(i=PORT_MDM; i<=MAX_PORT; i++) { if(sc->port[i].tty) ttyfree(sc->port[i].tty); } bus_release_resource(dev, SYS_RES_MEMORY, sc->rid, sc->res); bus_teardown_intr(dev, sc->intr_res, sc->intr_cookie); bus_release_resource(dev, SYS_RES_IRQ, sc->intr_rid, sc->intr_res); nozomi_cards--; return (0); } /* Called during system shutdown after sync. */ static int nozomi_shutdown(device_t dev) { return (0); } /* * Device suspend routine. */ static int nozomi_suspend(device_t dev) { return (0); } /* * Device resume routine. */ static int nozomi_resume(device_t dev) { return (0); } static device_method_t nozomi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, nozomi_probe), DEVMETHOD(device_attach, nozomi_attach), DEVMETHOD(device_detach, nozomi_detach), DEVMETHOD(device_shutdown, nozomi_shutdown), DEVMETHOD(device_suspend, nozomi_suspend), DEVMETHOD(device_resume, nozomi_resume), { 0, 0 } }; static devclass_t nozomi_devclass; DEFINE_CLASS_0(nozomi, nozomi_driver, nozomi_methods, sizeof(struct nozomi_softc)); DRIVER_MODULE(nozomi, pci, nozomi_driver, nozomi_devclass, 0, 0); DRIVER_MODULE(nozomi, cardbus, nozomi_driver, nozomi_devclass, 0, 0);