blob: 2a8d4aad51f677bd084bfc2b9d0c8586ddf8135a [file] [log] [blame]
/*
* Phonet device TTY line discipline
*
* Copyright (c) 1999-2002 RMC
*
*
*
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*/
#include <linux/uaccess.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/serio.h>
#include <linux/tty.h>
#include <asm/unaligned.h>
#include <net/sock.h>
#include <linux/errno.h>
#include <linux/if_arp.h>
#include <linux/if_phonet.h>
#include <linux/phonet.h>
#include <net/phonet/phonet.h>
#include <net/phonet/pn_dev.h>
MODULE_AUTHOR("david RMC");
MODULE_DESCRIPTION("Phonet TTY line discipline");
MODULE_LICENSE("GPL");
MODULE_ALIAS_LDISC(N_PHONET);
#define SEND_QUEUE_LOW 10
#define SEND_QUEUE_HIGH 100
#define PHONET_SENDING 1 /* Bit 1 = 0x02*/
#define PHONET_FLOW_OFF_SENT 4 /* Bit 4 = 0x10 */
#define MAX_WRITE_CHUNK 8192
#define ISI_MSG_HEADER_SIZE 6
#define MAX_BUFF_SIZE 20000
#define LD_PHONET_NEW_ISI_MSG 0
#define LD_PHONET_ISI_MSG_LEN 1
#define LD_PHONET_ISI_MSG_NO_LEN 2
#define PN_MEDIA_USB 0x1B
struct ld_phonet {
struct tty_struct *tty;
wait_queue_head_t wait;
spinlock_t lock;
unsigned long flags;
struct sk_buff *skb;
unsigned long len;
unsigned long lentorcv;
unsigned long datarcv ;
unsigned long state;
struct net_device *dev;
struct list_head node;
struct sk_buff_head head;
char *tty_name;
int ld_phonet_state;
int n_Data_Processed;
int n_Data_Sent;
int n_Remaining_Data;
bool link_up;
int nb_try_to_tx;
};
static int ld_pn_net_open(struct net_device *dev)
{
netif_wake_queue(dev);
return 0;
}
static int ld_pn_net_close(struct net_device *dev)
{
netif_stop_queue(dev);
return 0;
}
static int ld_pn_handle_tx(struct ld_phonet *ld_pn)
{
struct tty_struct *tty = ld_pn->tty;
struct sk_buff *skb;
int tty_wr, len, room, i;
PN_PRINTK("Write Data in tty\n");
if (tty == NULL)
return 0;
/* Enter critical section */
if (test_and_set_bit(PHONET_SENDING, &ld_pn->state))
return 0;
/* skb_peek is safe because handle_tx is called after skb_queue_tail */
while ((skb = skb_peek(&ld_pn->head)) != NULL) {
/* Make sure you don't write too much */
len = skb->len;
room = tty_write_room(tty);
if (!room) {
if (ld_pn->nb_try_to_tx++ > 40) {
ld_pn->link_up = false;
/* Flush TX queue */
while ((skb = \
skb_dequeue(&ld_pn->head)) != NULL) {
skb->dev->stats.tx_dropped++;
if (in_interrupt())
dev_kfree_skb_irq(skb);
else
kfree_skb(skb);
}
}
break;
}
/* Get room => reset nb_try_to_tx counter */
ld_pn->nb_try_to_tx = 0;
if (room > MAX_WRITE_CHUNK)
room = MAX_WRITE_CHUNK;
if (len > room)
len = room;
tty_wr = tty->ops->write(tty, skb->data, len);
ld_pn->dev->stats.tx_packets++;
ld_pn->dev->stats.tx_bytes += tty_wr;
PN_DATA_PRINTK("PHONET: write data in tty\n");
for (i = 1; i <= len; i++) {
PN_DATA_PRINTK(" %02x", skb->data[i-1]);
if ((i%8) == 0)
PN_DATA_PRINTK("\n");
}
PN_DATA_PRINTK("\n");
/* Error on TTY ?! */
if (tty_wr < 0)
goto error;
/* Reduce buffer written, and discard if empty */
skb_pull(skb, tty_wr);
if (skb->len == 0) {
struct sk_buff *tmp = skb_dequeue(&ld_pn->head);
BUG_ON(tmp != skb);
if (in_interrupt())
dev_kfree_skb_irq(skb);
else
kfree_skb(skb);
}
}
/* Send flow off if queue is empty */
clear_bit(PHONET_SENDING, &ld_pn->state);
return 0;
error:
clear_bit(PHONET_SENDING, &ld_pn->state);
return tty_wr;
}
static int ld_pn_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct ld_phonet *ld_pn;
u8 *ptr;
BUG_ON(dev == NULL);
ld_pn = netdev_priv(dev);
/* Add special Pattern before each ISI message */
ptr = skb_push(skb, 6);
ptr[0] = 0xdd;
ptr[1] = 0x7f;
ptr[2] = 0x21;
ptr[3] = 0x9a;
ptr[4] = skb->data[10];
ptr[5] = skb->data[11];
if (ld_pn->link_up == true) {
skb_queue_tail(&ld_pn->head, skb);
return ld_pn_handle_tx(ld_pn);
} else {
if (tty_write_room(ld_pn->tty)) {
/* link is up again */
ld_pn->link_up = true;
ld_pn->nb_try_to_tx = 0;
skb_queue_tail(&ld_pn->head, skb);
return ld_pn_handle_tx(ld_pn);
} else {
if (in_interrupt())
dev_kfree_skb_irq(skb);
else
kfree_skb(skb);
dev->stats.tx_dropped++;
return NETDEV_TX_OK;
}
}
}
static int
ld_pn_net_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
int ret = 0;
switch (cmd) {
case SIOCPNGAUTOCONF:
ret = phonet_address_add(dev, PN_MEDIA_USB);
if (ret)
return ret;
phonet_address_notify(RTM_NEWADDR, dev, PN_MEDIA_USB);
phonet_route_add(dev, PN_DEV_PC);
dev_open(dev);
netif_carrier_on(dev);
/* Return NOIOCTLCMD so Phonet won't do it again */
return -ENOIOCTLCMD;
}
return -ENOIOCTLCMD;
}
static int ld_pn_net_mtu(struct net_device *dev, int new_mtu)
{
if ((new_mtu < PHONET_MIN_MTU) || (new_mtu > PHONET_MAX_MTU))
return -EINVAL;
dev->mtu = new_mtu;
return 0;
}
static const struct net_device_ops ld_pn_netdev_ops = {
.ndo_open = ld_pn_net_open,
.ndo_stop = ld_pn_net_close,
.ndo_start_xmit = ld_pn_net_xmit,
.ndo_do_ioctl = ld_pn_net_ioctl,
.ndo_change_mtu = ld_pn_net_mtu,
};
static void ld_pn_net_setup(struct net_device *dev)
{
dev->features = 0;
dev->type = ARPHRD_PHONET;
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
dev->mtu = PHONET_DEV_MTU;
dev->hard_header_len = 1;
dev->dev_addr[0] = PN_MEDIA_USB;
dev->addr_len = 1;
dev->tx_queue_len = 5;
dev->netdev_ops = &ld_pn_netdev_ops;
dev->destructor = free_netdev;
dev->header_ops = &phonet_header_ops;
};
/*****************************************
*** TTY
******************************************/
static int ld_phonet_ldisc_open(struct tty_struct *tty)
{
struct ld_phonet *ld_pn;
struct net_device *dev;
int err = 0;
/* Create net device */
dev = alloc_netdev(sizeof(*ld_pn), "upnlink%d", ld_pn_net_setup);
if (!dev)
return -ENOMEM;
ld_pn = netdev_priv(dev);
spin_lock_init(&ld_pn->lock);
netif_carrier_off(dev);
skb_queue_head_init(&ld_pn->head);
ld_pn->tty = tty;
tty->disc_data = ld_pn;
tty->receive_room = 65536;
ld_pn->dev = dev;
ld_pn->skb = NULL;
ld_pn->len = 0;
ld_pn->lentorcv = 0;
ld_pn->datarcv = 0 ;
ld_pn->ld_phonet_state = LD_PHONET_NEW_ISI_MSG;
ld_pn->n_Data_Processed = 0;
ld_pn->n_Data_Sent = 0;
ld_pn->n_Remaining_Data = 0;
ld_pn->link_up = true;
ld_pn->nb_try_to_tx = 0;
err = register_netdev(dev);
if (err)
free_netdev(dev);
return err;
}
static void ld_phonet_ldisc_close(struct tty_struct *tty)
{
struct ld_phonet *ld_pn = tty->disc_data;
tty->disc_data = NULL;
ld_pn->tty = NULL;
unregister_netdev(ld_pn->dev);
/*free_netdev(ld_pn->dev); David a checker*/
}
static void ld_phonet_ldisc_initiate_transfer \
(struct ld_phonet *ld_pn, const unsigned char *cp, int count)
{
struct sk_buff *skb = NULL;
unsigned int msglen = 0;
struct phonethdr *ph = NULL;
/* Check if there is still data in cp */
while (ld_pn->n_Data_Processed < count) {
/* Check if extract length is possible */
if (count > ISI_MSG_HEADER_SIZE) {
/* Extract length */
/* Move one byte since media parameter
is not there in phonethdr structure */
ph = (struct phonethdr *) \
(cp + ld_pn->n_Data_Processed + sizeof(char));
msglen = get_unaligned_be16(&ph->pn_length);
ld_pn->len = msglen + ISI_MSG_HEADER_SIZE;
/* Alloc SKBuff */
skb = netdev_alloc_skb(ld_pn->dev, ld_pn->len);
if (NULL == skb) {
/* TBD handle error */
return;
}
skb->dev = ld_pn->dev;
skb->protocol = htons(ETH_P_PHONET);
skb_reset_mac_header(skb);
ld_pn->skb = skb;
/* check if we receive complete
data in this usb frame */
if (ld_pn->len <= count) {
/* We receive complete data
in this usb frame */
/* copy the ISI buffer */
memcpy(skb_put(skb, ld_pn->len), \
cp + ld_pn->n_Data_Processed, ld_pn->len);
ld_pn->n_Data_Processed += ld_pn->len;
/* Send to Phonet */
ld_pn->dev->stats.rx_packets++;
ld_pn->dev->stats.rx_bytes += skb->len;
__skb_pull(skb, 1);
/* we remove media id
(Why ? because we always do it ;-)) */
netif_rx(skb);
ld_pn->n_Data_Sent += ld_pn->len;
/* TBD : Reset pointers */
} else {
/* We receive only partial ISI message */
/* Copy the partial ISI message */
memcpy(skb_put(skb, count - \
ld_pn->n_Data_Sent), cp + \
ld_pn->n_Data_Processed, count - \
ld_pn->n_Data_Sent);
ld_pn->ld_phonet_state = LD_PHONET_ISI_MSG_LEN;
ld_pn->n_Remaining_Data = ld_pn->len - \
(count - ld_pn->n_Data_Sent);
ld_pn->n_Data_Processed += count - \
ld_pn->n_Data_Sent;
return;
}
} else {
/* Not able to extract length since received
usb frame length is
less than ISI message header size */
/* Alloc SKBuff with max size */
skb = netdev_alloc_skb(ld_pn->dev, MAX_BUFF_SIZE);
if (NULL == skb) {
/* TBD handle error */
return;
}
skb->dev = ld_pn->dev;
skb->protocol = htons(ETH_P_PHONET);
skb_reset_mac_header(skb);
ld_pn->skb = skb;
/* Copy available data */
memcpy(skb_put(skb, count - ld_pn->n_Data_Sent), \
cp + ld_pn->n_Data_Processed, count - \
ld_pn->n_Data_Sent);
ld_pn->ld_phonet_state = LD_PHONET_ISI_MSG_NO_LEN;
ld_pn->n_Data_Processed += count - ld_pn->n_Data_Sent;
ld_pn->len += count - ld_pn->n_Data_Sent;
return;
}
}
/* No more data in cp */
ld_pn->ld_phonet_state = LD_PHONET_NEW_ISI_MSG;
ld_pn->len = 0;
ld_pn->n_Data_Processed = 0;
ld_pn->n_Data_Sent = 0;
ld_pn->n_Remaining_Data = 0;
return;
}
static void ld_phonet_ldisc_receive
(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)
{
struct ld_phonet *ld_pn = tty->disc_data;
struct sk_buff *skb = ld_pn->skb;
unsigned long flags = 0;
unsigned int msglen = 0;
unsigned int i = 0;
struct phonethdr *ph = NULL;
PN_DATA_PRINTK("PHONET : Receive Data From tty = %d\nPHONET :", count);
for (i = 1; i <= count; i++) {
PN_DATA_PRINTK(" %02x", cp[i-1]);
if ((i%8) == 0)
PN_DATA_PRINTK("\n");
}
if (ld_pn->link_up == false) {
/* data received from PC => can TX */
ld_pn->link_up = true;
ld_pn->nb_try_to_tx = 0;
}
spin_lock_irqsave(&ld_pn->lock, flags);
/*Whenever you receive a new USB frame Data Processed should be reset*/
ld_pn->n_Data_Processed = 0;
switch (ld_pn->ld_phonet_state) {
case LD_PHONET_NEW_ISI_MSG:
PN_PRINTK("ld_phonet : new_isi_msg\n");
ld_phonet_ldisc_initiate_transfer(ld_pn, cp, count);
break;
case LD_PHONET_ISI_MSG_LEN:
/* check if Remaining Data is complete */
PN_PRINTK("ld_phonet : isi_msg_len\n");
if (ld_pn->n_Remaining_Data > count) {
/* We dont receive complete data */
/* Copy the available data */
memcpy(skb_put(skb, count), cp + \
ld_pn->n_Data_Processed, count);
ld_pn->n_Data_Processed += count;
ld_pn->ld_phonet_state = LD_PHONET_ISI_MSG_LEN;
ld_pn->n_Remaining_Data -= count;
} else {
/* We have complete data available */
/* Copy remaining data */
memcpy(skb_put(skb, ld_pn->n_Remaining_Data), \
cp + ld_pn->n_Data_Processed, ld_pn->n_Remaining_Data);
/* Send to Phonet */
ld_pn->dev->stats.rx_packets++;
ld_pn->dev->stats.rx_bytes += skb->len;
__skb_pull(skb, sizeof(char));
netif_rx(skb);
ld_pn->n_Data_Sent += ld_pn->len;
/* TBD : Update pointers */
ld_pn->n_Data_Sent += ld_pn->n_Remaining_Data;
ld_pn->n_Data_Processed += ld_pn->n_Remaining_Data;
/* Initiate a new ISI transfer */
ld_phonet_ldisc_initiate_transfer(ld_pn, cp, count);
}
break;
case LD_PHONET_ISI_MSG_NO_LEN:
/*Check if we can extact length */
PN_PRINTK("ld_phonet : isi_msg_no_len\n");
if ((ld_pn->len + count) >= ISI_MSG_HEADER_SIZE) {
/* Copy remaining header to SKBuff to extract length */
memcpy(skb_put(skb, ISI_MSG_HEADER_SIZE - ld_pn->len),\
cp + ld_pn->n_Data_Processed, ISI_MSG_HEADER_SIZE - \
ld_pn->len);
ph = (struct phonethdr *) (skb->data + sizeof(char));
msglen = get_unaligned_be16(&ph->pn_length);
ld_pn->n_Data_Processed += \
ISI_MSG_HEADER_SIZE - ld_pn->len;
/* Check if we receive complete data */
if ((count + ld_pn->len) < \
(msglen + ISI_MSG_HEADER_SIZE)) {
/* We have not received complete data */
/* Copy available data */
memcpy(skb_put(skb, count - \
(ISI_MSG_HEADER_SIZE - ld_pn->len)), \
cp + ld_pn->n_Data_Processed, count - \
(ISI_MSG_HEADER_SIZE - ld_pn->len));
ld_pn->ld_phonet_state = LD_PHONET_ISI_MSG_LEN;
ld_pn->n_Remaining_Data = (msglen + \
ISI_MSG_HEADER_SIZE) - (count + ld_pn->len);
ld_pn->n_Data_Processed += count - \
(ISI_MSG_HEADER_SIZE - ld_pn->len);
/* Reset pointers */
ld_pn->len = msglen + ISI_MSG_HEADER_SIZE;
/*return;*/
break;
} else {
/* We receive complete data */
/* Copy remaining data */
/*memcpy(skb_put(skb, msglen), cp + \
ld_pn->n_Data_Processed \
+ (ISI_MSG_HEADER_SIZE - ld_pn->len), \
(msglen + ISI_MSG_HEADER_SIZE) - ld_pn->len);*/
memcpy( \
skb_put(skb, (msglen + ISI_MSG_HEADER_SIZE) - \
(ld_pn->len + ld_pn->n_Data_Processed)), \
cp + ld_pn->n_Data_Processed, \
(msglen + ISI_MSG_HEADER_SIZE) - \
(ld_pn->len + ld_pn->n_Data_Processed));
/* Send to Phonet */
ld_pn->dev->stats.rx_packets++;
ld_pn->dev->stats.rx_bytes += skb->len;
__skb_pull(skb, sizeof(char));
netif_rx(skb);
/* Update pointers */
/*ld_pn->n_Data_Sent += \
(msglen + ISI_MSG_HEADER_SIZE) - ld_pn->len;
ld_pn->n_Data_Processed += \
(msglen + ISI_MSG_HEADER_SIZE) - ld_pn->len;*/
ld_pn->n_Data_Sent += \
(msglen + ISI_MSG_HEADER_SIZE) - \
(ld_pn->len + ld_pn->n_Data_Processed);
ld_pn->n_Data_Processed += \
(msglen + ISI_MSG_HEADER_SIZE) - \
(ld_pn->len + ld_pn->n_Data_Processed);
/* Check if we still have data in cp */
/*if (count > ld_pn->n_Data_Sent) {*/
if (count > ld_pn->n_Data_Processed) {
/* We still have data in cp */
/* Initiate a new ISI transfer */
ld_phonet_ldisc_initiate_transfer\
(ld_pn, cp, count);
} else {
/* No more data in cp */
ld_pn->ld_phonet_state = \
LD_PHONET_NEW_ISI_MSG;
/* Reset pointers */
ld_pn->len = 0;
ld_pn->n_Data_Processed = 0;
ld_pn->n_Data_Sent = 0;
ld_pn->n_Remaining_Data = 0;
}
}
} else {
/* Cannot extract length */
/* Copy available data */
memcpy(skb_put(skb, count), cp + \
ld_pn->n_Data_Processed, count);
ld_pn->len += count;
ld_pn->ld_phonet_state = \
LD_PHONET_ISI_MSG_NO_LEN;
ld_pn->n_Data_Processed += count;
}
break;
default:
break;
}
spin_unlock_irqrestore(&ld_pn->lock, flags);
}
static void ld_phonet_ldisc_write_wakeup(struct tty_struct *tty)
{
struct ld_phonet *ld_pn;
ld_pn = tty->disc_data;
BUG_ON(ld_pn == NULL);
BUG_ON(ld_pn->tty != tty);
ld_pn_handle_tx(ld_pn);
}
static struct tty_ldisc_ops ld_phonet_ldisc = {
.owner = THIS_MODULE,
.name = "phonet",
.open = ld_phonet_ldisc_open,
.close = ld_phonet_ldisc_close,
.receive_buf = ld_phonet_ldisc_receive,
.write_wakeup = ld_phonet_ldisc_write_wakeup
};
/*
* The functions for insering/removing us as a module.
*/
static int __init ld_phonet_init(void)
{
int retval;
retval = tty_register_ldisc(N_PHONET, &ld_phonet_ldisc);
return retval;
}
static void __exit ld_phonet_exit(void)
{
tty_unregister_ldisc(N_PHONET);
}
module_init(ld_phonet_init);
module_exit(ld_phonet_exit);