diff options
Diffstat (limited to 'drivers/tty/n_gsm.c')
| -rw-r--r-- | drivers/tty/n_gsm.c | 144 | 
1 files changed, 87 insertions, 57 deletions
diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c index c43b683b6eb..1e8e8ce5595 100644 --- a/drivers/tty/n_gsm.c +++ b/drivers/tty/n_gsm.c @@ -108,7 +108,7 @@ struct gsm_mux_net {   */  struct gsm_msg { -	struct gsm_msg *next; +	struct list_head list;  	u8 addr;		/* DLCI address + flags */  	u8 ctrl;		/* Control byte + flags */  	unsigned int len;	/* Length of data block (can be zero) */ @@ -245,8 +245,7 @@ struct gsm_mux {  	unsigned int tx_bytes;		/* TX data outstanding */  #define TX_THRESH_HI		8192  #define TX_THRESH_LO		2048 -	struct gsm_msg *tx_head;	/* Pending data packets */ -	struct gsm_msg *tx_tail; +	struct list_head tx_list;	/* Pending data packets */  	/* Control messages */  	struct timer_list t2_timer;	/* Retransmit timer for commands */ @@ -489,7 +488,7 @@ static void gsm_print_packet(const char *hdr, int addr, int cr,  	default:  		if (!(control & 0x01)) {  			pr_cont("I N(S)%d N(R)%d", -				(control & 0x0E) >> 1, (control & 0xE) >> 5); +				(control & 0x0E) >> 1, (control & 0xE0) >> 5);  		} else switch (control & 0x0F) {  			case RR:  				pr_cont("RR(%d)", (control & 0xE0) >> 5); @@ -663,7 +662,7 @@ static struct gsm_msg *gsm_data_alloc(struct gsm_mux *gsm, u8 addr, int len,  	m->len = len;  	m->addr = addr;  	m->ctrl = ctrl; -	m->next = NULL; +	INIT_LIST_HEAD(&m->list);  	return m;  } @@ -673,22 +672,21 @@ static struct gsm_msg *gsm_data_alloc(struct gsm_mux *gsm, u8 addr, int len,   *   *	The tty device has called us to indicate that room has appeared in   *	the transmit queue. Ram more data into the pipe if we have any + *	If we have been flow-stopped by a CMD_FCOFF, then we can only + *	send messages on DLCI0 until CMD_FCON   *   *	FIXME: lock against link layer control transmissions   */  static void gsm_data_kick(struct gsm_mux *gsm)  { -	struct gsm_msg *msg = gsm->tx_head; +	struct gsm_msg *msg, *nmsg;  	int len;  	int skip_sof = 0; -	/* FIXME: We need to apply this solely to data messages */ -	if (gsm->constipated) -		return; - -	while (gsm->tx_head != NULL) { -		msg = gsm->tx_head; +	list_for_each_entry_safe(msg, nmsg, &gsm->tx_list, list) { +		if (gsm->constipated && msg->addr) +			continue;  		if (gsm->encoding != 0) {  			gsm->txframe[0] = GSM1_SOF;  			len = gsm_stuff_frame(msg->data, @@ -711,14 +709,13 @@ static void gsm_data_kick(struct gsm_mux *gsm)  						len - skip_sof) < 0)  			break;  		/* FIXME: Can eliminate one SOF in many more cases */ -		gsm->tx_head = msg->next; -		if (gsm->tx_head == NULL) -			gsm->tx_tail = NULL;  		gsm->tx_bytes -= msg->len; -		kfree(msg);  		/* For a burst of frames skip the extra SOF within the  		   burst */  		skip_sof = 1; + +		list_del(&msg->list); +		kfree(msg);  	}  } @@ -768,11 +765,7 @@ static void __gsm_data_queue(struct gsm_dlci *dlci, struct gsm_msg *msg)  	msg->data = dp;  	/* Add to the actual output queue */ -	if (gsm->tx_tail) -		gsm->tx_tail->next = msg; -	else -		gsm->tx_head = msg; -	gsm->tx_tail = msg; +	list_add_tail(&msg->list, &gsm->tx_list);  	gsm->tx_bytes += msg->len;  	gsm_data_kick(gsm);  } @@ -875,7 +868,7 @@ static int gsm_dlci_data_output_framed(struct gsm_mux *gsm,  	/* dlci->skb is locked by tx_lock */  	if (dlci->skb == NULL) { -		dlci->skb = skb_dequeue(&dlci->skb_list); +		dlci->skb = skb_dequeue_tail(&dlci->skb_list);  		if (dlci->skb == NULL)  			return 0;  		first = 1; @@ -886,7 +879,7 @@ static int gsm_dlci_data_output_framed(struct gsm_mux *gsm,  	if (len > gsm->mtu) {  		if (dlci->adaption == 3) {  			/* Over long frame, bin it */ -			kfree_skb(dlci->skb); +			dev_kfree_skb_any(dlci->skb);  			dlci->skb = NULL;  			return 0;  		} @@ -899,8 +892,11 @@ static int gsm_dlci_data_output_framed(struct gsm_mux *gsm,  	/* FIXME: need a timer or something to kick this so it can't  	   get stuck with no work outstanding and no buffer free */ -	if (msg == NULL) +	if (msg == NULL) { +		skb_queue_tail(&dlci->skb_list, dlci->skb); +		dlci->skb = NULL;  		return -ENOMEM; +	}  	dp = msg->data;  	if (dlci->adaption == 4) { /* Interruptible framed (Packetised Data) */ @@ -912,7 +908,7 @@ static int gsm_dlci_data_output_framed(struct gsm_mux *gsm,  	skb_pull(dlci->skb, len);  	__gsm_data_queue(dlci, msg);  	if (last) { -		kfree_skb(dlci->skb); +		dev_kfree_skb_any(dlci->skb);  		dlci->skb = NULL;  	}  	return size; @@ -971,16 +967,22 @@ static void gsm_dlci_data_sweep(struct gsm_mux *gsm)  static void gsm_dlci_data_kick(struct gsm_dlci *dlci)  {  	unsigned long flags; +	int sweep; + +	if (dlci->constipated)  +		return;  	spin_lock_irqsave(&dlci->gsm->tx_lock, flags);  	/* If we have nothing running then we need to fire up */ +	sweep = (dlci->gsm->tx_bytes < TX_THRESH_LO);  	if (dlci->gsm->tx_bytes == 0) {  		if (dlci->net)  			gsm_dlci_data_output_framed(dlci->gsm, dlci);  		else  			gsm_dlci_data_output(dlci->gsm, dlci); -	} else if (dlci->gsm->tx_bytes < TX_THRESH_LO) -		gsm_dlci_data_sweep(dlci->gsm); +	} +	if (sweep) + 		gsm_dlci_data_sweep(dlci->gsm);  	spin_unlock_irqrestore(&dlci->gsm->tx_lock, flags);  } @@ -1027,6 +1029,7 @@ static void gsm_process_modem(struct tty_struct *tty, struct gsm_dlci *dlci,  {  	int  mlines = 0;  	u8 brk = 0; +	int fc;  	/* The modem status command can either contain one octet (v.24 signals)  	   or two octets (v.24 signals + break signals). The length field will @@ -1038,19 +1041,21 @@ static void gsm_process_modem(struct tty_struct *tty, struct gsm_dlci *dlci,  	else {  		brk = modem & 0x7f;  		modem = (modem >> 7) & 0x7f; -	}; +	}  	/* Flow control/ready to communicate */ -	if (modem & MDM_FC) { +	fc = (modem & MDM_FC) || !(modem & MDM_RTR); +	if (fc && !dlci->constipated) {  		/* Need to throttle our output on this device */  		dlci->constipated = 1; -	} -	if (modem & MDM_RTC) { -		mlines |= TIOCM_DSR | TIOCM_DTR; +	} else if (!fc && dlci->constipated) {  		dlci->constipated = 0;  		gsm_dlci_data_kick(dlci);  	} +  	/* Map modem bits */ +	if (modem & MDM_RTC) +		mlines |= TIOCM_DSR | TIOCM_DTR;  	if (modem & MDM_RTR)  		mlines |= TIOCM_RTS | TIOCM_CTS;  	if (modem & MDM_IC) @@ -1061,7 +1066,7 @@ static void gsm_process_modem(struct tty_struct *tty, struct gsm_dlci *dlci,  	/* Carrier drop -> hangup */  	if (tty) {  		if ((mlines & TIOCM_CD) == 0 && (dlci->modem_rx & TIOCM_CD)) -			if (!(tty->termios->c_cflag & CLOCAL)) +			if (!(tty->termios.c_cflag & CLOCAL))  				tty_hangup(tty);  		if (brk & 0x01)  			tty_insert_flip_char(tty, 0, TTY_BREAK); @@ -1190,6 +1195,8 @@ static void gsm_control_message(struct gsm_mux *gsm, unsigned int command,  							u8 *data, int clen)  {  	u8 buf[1]; +	unsigned long flags; +  	switch (command) {  	case CMD_CLD: {  		struct gsm_dlci *dlci = gsm->dlci[0]; @@ -1206,16 +1213,18 @@ static void gsm_control_message(struct gsm_mux *gsm, unsigned int command,  		gsm_control_reply(gsm, CMD_TEST, data, clen);  		break;  	case CMD_FCON: -		/* Modem wants us to STFU */ -		gsm->constipated = 1; -		gsm_control_reply(gsm, CMD_FCON, NULL, 0); -		break; -	case CMD_FCOFF:  		/* Modem can accept data again */  		gsm->constipated = 0; -		gsm_control_reply(gsm, CMD_FCOFF, NULL, 0); +		gsm_control_reply(gsm, CMD_FCON, NULL, 0);  		/* Kick the link in case it is idling */ +		spin_lock_irqsave(&gsm->tx_lock, flags);  		gsm_data_kick(gsm); +		spin_unlock_irqrestore(&gsm->tx_lock, flags); +		break; +	case CMD_FCOFF: +		/* Modem wants us to STFU */ +		gsm->constipated = 1; +		gsm_control_reply(gsm, CMD_FCOFF, NULL, 0);  		break;  	case CMD_MSC:  		/* Out of band modem line change indicator for a DLCI */ @@ -1668,7 +1677,7 @@ static void gsm_dlci_free(struct kref *ref)  	dlci->gsm->dlci[dlci->addr] = NULL;  	kfifo_free(dlci->fifo);  	while ((dlci->skb = skb_dequeue(&dlci->skb_list))) -		kfree_skb(dlci->skb); +		dev_kfree_skb(dlci->skb);  	kfree(dlci);  } @@ -2007,7 +2016,7 @@ void gsm_cleanup_mux(struct gsm_mux *gsm)  {  	int i;  	struct gsm_dlci *dlci = gsm->dlci[0]; -	struct gsm_msg *txq; +	struct gsm_msg *txq, *ntxq;  	struct gsm_control *gc;  	gsm->dead = 1; @@ -2042,11 +2051,9 @@ void gsm_cleanup_mux(struct gsm_mux *gsm)  		if (gsm->dlci[i])  			gsm_dlci_release(gsm->dlci[i]);  	/* Now wipe the queues */ -	for (txq = gsm->tx_head; txq != NULL; txq = gsm->tx_head) { -		gsm->tx_head = txq->next; +	list_for_each_entry_safe(txq, ntxq, &gsm->tx_list, list)  		kfree(txq); -	} -	gsm->tx_tail = NULL; +	INIT_LIST_HEAD(&gsm->tx_list);  }  EXPORT_SYMBOL_GPL(gsm_cleanup_mux); @@ -2157,6 +2164,7 @@ struct gsm_mux *gsm_alloc_mux(void)  	}  	spin_lock_init(&gsm->lock);  	kref_init(&gsm->ref); +	INIT_LIST_HEAD(&gsm->tx_list);  	gsm->t1 = T1;  	gsm->t2 = T2; @@ -2273,7 +2281,7 @@ static void gsmld_receive_buf(struct tty_struct *tty, const unsigned char *cp,  			gsm->error(gsm, *dp, flags);  			break;  		default: -			WARN_ONCE("%s: unknown flag %d\n", +			WARN_ONCE(1, "%s: unknown flag %d\n",  			       tty_name(tty, buf), flags);  			break;  		} @@ -2377,12 +2385,12 @@ static void gsmld_write_wakeup(struct tty_struct *tty)  	/* Queue poll */  	clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); +	spin_lock_irqsave(&gsm->tx_lock, flags);  	gsm_data_kick(gsm);  	if (gsm->tx_bytes < TX_THRESH_LO) { -		spin_lock_irqsave(&gsm->tx_lock, flags);  		gsm_dlci_data_sweep(gsm); -		spin_unlock_irqrestore(&gsm->tx_lock, flags);  	} +	spin_unlock_irqrestore(&gsm->tx_lock, flags);  }  /** @@ -2868,14 +2876,14 @@ static const struct tty_port_operations gsm_port_ops = {  	.dtr_rts = gsm_dtr_rts,  }; - -static int gsmtty_open(struct tty_struct *tty, struct file *filp) +static int gsmtty_install(struct tty_driver *driver, struct tty_struct *tty)  {  	struct gsm_mux *gsm;  	struct gsm_dlci *dlci; -	struct tty_port *port;  	unsigned int line = tty->index;  	unsigned int mux = line >> 6; +	bool alloc = false; +	int ret;  	line = line & 0x3F; @@ -2889,14 +2897,35 @@ static int gsmtty_open(struct tty_struct *tty, struct file *filp)  	gsm = gsm_mux[mux];  	if (gsm->dead)  		return -EL2HLT; +	/* If DLCI 0 is not yet fully open return an error. This is ok from a locking +	   perspective as we don't have to worry about this if DLCI0 is lost */ +	if (gsm->dlci[0] && gsm->dlci[0]->state != DLCI_OPEN)  +		return -EL2NSYNC;  	dlci = gsm->dlci[line]; -	if (dlci == NULL) +	if (dlci == NULL) { +		alloc = true;  		dlci = gsm_dlci_alloc(gsm, line); +	}  	if (dlci == NULL)  		return -ENOMEM; -	port = &dlci->port; -	port->count++; +	ret = tty_port_install(&dlci->port, driver, tty); +	if (ret) { +		if (alloc) +			dlci_put(dlci); +		return ret; +	} +  	tty->driver_data = dlci; + +	return 0; +} + +static int gsmtty_open(struct tty_struct *tty, struct file *filp) +{ +	struct gsm_dlci *dlci = tty->driver_data; +	struct tty_port *port = &dlci->port; + +	port->count++;  	dlci_get(dlci);  	dlci_get(dlci->gsm->dlci[0]);  	mux_get(dlci->gsm); @@ -3043,13 +3072,13 @@ static void gsmtty_set_termios(struct tty_struct *tty, struct ktermios *old)  	   the RPN control message. This however rapidly gets nasty as we  	   then have to remap modem signals each way according to whether  	   our virtual cable is null modem etc .. */ -	tty_termios_copy_hw(tty->termios, old); +	tty_termios_copy_hw(&tty->termios, old);  }  static void gsmtty_throttle(struct tty_struct *tty)  {  	struct gsm_dlci *dlci = tty->driver_data; -	if (tty->termios->c_cflag & CRTSCTS) +	if (tty->termios.c_cflag & CRTSCTS)  		dlci->modem_tx &= ~TIOCM_DTR;  	dlci->throttled = 1;  	/* Send an MSC with DTR cleared */ @@ -3059,7 +3088,7 @@ static void gsmtty_throttle(struct tty_struct *tty)  static void gsmtty_unthrottle(struct tty_struct *tty)  {  	struct gsm_dlci *dlci = tty->driver_data; -	if (tty->termios->c_cflag & CRTSCTS) +	if (tty->termios.c_cflag & CRTSCTS)  		dlci->modem_tx |= TIOCM_DTR;  	dlci->throttled = 0;  	/* Send an MSC with DTR set */ @@ -3085,6 +3114,7 @@ static int gsmtty_break_ctl(struct tty_struct *tty, int state)  /* Virtual ttys for the demux */  static const struct tty_operations gsmtty_ops = { +	.install		= gsmtty_install,  	.open			= gsmtty_open,  	.close			= gsmtty_close,  	.write			= gsmtty_write,  |