diff options
Diffstat (limited to 'net/irda')
50 files changed, 32233 insertions, 0 deletions
diff --git a/net/irda/Kconfig b/net/irda/Kconfig new file mode 100644 index 000000000000..9efb17ba48ac --- /dev/null +++ b/net/irda/Kconfig @@ -0,0 +1,96 @@ +# +# IrDA protocol configuration +# + +menuconfig IRDA + depends on NET + tristate "IrDA (infrared) subsystem support" + select CRC_CCITT + ---help--- + Say Y here if you want to build support for the IrDA (TM) protocols. + The Infrared Data Associations (tm) specifies standards for wireless + infrared communication and is supported by most laptops and PDA's. + + To use Linux support for the IrDA (tm) protocols, you will also need + some user-space utilities like irattach. For more information, see + the file <file:Documentation/networking/irda.txt>. You also want to + read the IR-HOWTO, available at + <http://www.tldp.org/docs.html#howto>. + + If you want to exchange bits of data (vCal, vCard) with a PDA, you + will need to install some OBEX application, such as OpenObex : + <http://sourceforge.net/projects/openobex/> + + To compile this support as a module, choose M here: the module will + be called irda. + +comment "IrDA protocols" + depends on IRDA + +source "net/irda/irlan/Kconfig" + +source "net/irda/irnet/Kconfig" + +source "net/irda/ircomm/Kconfig" + +config IRDA_ULTRA + bool "Ultra (connectionless) protocol" + depends on IRDA + help + Say Y here to support the connectionless Ultra IRDA protocol. + Ultra allows to exchange data over IrDA with really simple devices + (watch, beacon) without the overhead of the IrDA protocol (no handshaking, + no management frames, simple fixed header). + Ultra is available as a special socket : socket(AF_IRDA, SOCK_DGRAM, 1); + +comment "IrDA options" + depends on IRDA + +config IRDA_CACHE_LAST_LSAP + bool "Cache last LSAP" + depends on IRDA + help + Say Y here if you want IrLMP to cache the last LSAP used. This + makes sense since most frames will be sent/received on the same + connection. Enabling this option will save a hash-lookup per frame. + + If unsure, say Y. + +config IRDA_FAST_RR + bool "Fast RRs (low latency)" + depends on IRDA + ---help--- + Say Y here is you want IrLAP to send fast RR (Receive Ready) frames + when acting as a primary station. + Disabling this option will make latency over IrDA very bad. Enabling + this option will make the IrDA stack send more packet than strictly + necessary, thus reduce your battery life (but not that much). + + Fast RR will make IrLAP send out a RR frame immediately when + receiving a frame if its own transmit queue is currently empty. This + will give a lot of speed improvement when receiving much data since + the secondary station will not have to wait the max. turn around + time (usually 500ms) before it is allowed to transmit the next time. + If the transmit queue of the secondary is also empty, the primary will + start backing-off before sending another RR frame, waiting longer + each time until the back-off reaches the max. turn around time. + This back-off increase in controlled via + /proc/sys/net/irda/fast_poll_increase + + If unsure, say Y. + +config IRDA_DEBUG + bool "Debug information" + depends on IRDA + help + Say Y here if you want the IrDA subsystem to write debug information + to your syslog. You can change the debug level in + /proc/sys/net/irda/debug . + When this option is enabled, the IrDA also perform many extra internal + verifications which will usually prevent the kernel to crash in case of + bugs. + + If unsure, say Y (since it makes it easier to find the bugs). + +source "drivers/net/irda/Kconfig" + diff --git a/net/irda/Makefile b/net/irda/Makefile new file mode 100644 index 000000000000..d1366c2a39cb --- /dev/null +++ b/net/irda/Makefile @@ -0,0 +1,15 @@ +# +# Makefile for the Linux IrDA protocol layer. +# + +obj-$(CONFIG_IRDA) += irda.o +obj-$(CONFIG_IRLAN) += irlan/ +obj-$(CONFIG_IRNET) += irnet/ +obj-$(CONFIG_IRCOMM) += ircomm/ + +irda-y := iriap.o iriap_event.o irlmp.o irlmp_event.o irlmp_frame.o \ + irlap.o irlap_event.o irlap_frame.o timer.o qos.o irqueue.o \ + irttp.o irda_device.o irias_object.o wrapper.o af_irda.o \ + discovery.o parameters.o irmod.o +irda-$(CONFIG_PROC_FS) += irproc.o +irda-$(CONFIG_SYSCTL) += irsysctl.o diff --git a/net/irda/af_irda.c b/net/irda/af_irda.c new file mode 100644 index 000000000000..92c6e8d4e731 --- /dev/null +++ b/net/irda/af_irda.c @@ -0,0 +1,2586 @@ +/********************************************************************* + * + * Filename: af_irda.c + * Version: 0.9 + * Description: IrDA sockets implementation + * Status: Stable + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sun May 31 10:12:43 1998 + * Modified at: Sat Dec 25 21:10:23 1999 + * Modified by: Dag Brattli <dag@brattli.net> + * Sources: af_netroom.c, af_ax25.c, af_rose.c, af_x25.c etc. + * + * Copyright (c) 1999 Dag Brattli <dagb@cs.uit.no> + * Copyright (c) 1999-2003 Jean Tourrilhes <jt@hpl.hp.com> + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * Linux-IrDA now supports four different types of IrDA sockets: + * + * o SOCK_STREAM: TinyTP connections with SAR disabled. The + * max SDU size is 0 for conn. of this type + * o SOCK_SEQPACKET: TinyTP connections with SAR enabled. TTP may + * fragment the messages, but will preserve + * the message boundaries + * o SOCK_DGRAM: IRDAPROTO_UNITDATA: TinyTP connections with Unitdata + * (unreliable) transfers + * IRDAPROTO_ULTRA: Connectionless and unreliable data + * + ********************************************************************/ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/sockios.h> +#include <linux/init.h> +#include <linux/net.h> +#include <linux/irda.h> +#include <linux/poll.h> + +#include <asm/ioctls.h> /* TIOCOUTQ, TIOCINQ */ +#include <asm/uaccess.h> + +#include <net/sock.h> +#include <net/tcp.h> + +#include <net/irda/af_irda.h> + +static int irda_create(struct socket *sock, int protocol); + +static struct proto_ops irda_stream_ops; +static struct proto_ops irda_seqpacket_ops; +static struct proto_ops irda_dgram_ops; + +#ifdef CONFIG_IRDA_ULTRA +static struct proto_ops irda_ultra_ops; +#define ULTRA_MAX_DATA 382 +#endif /* CONFIG_IRDA_ULTRA */ + +#define IRDA_MAX_HEADER (TTP_MAX_HEADER) + +/* + * Function irda_data_indication (instance, sap, skb) + * + * Received some data from TinyTP. Just queue it on the receive queue + * + */ +static int irda_data_indication(void *instance, void *sap, struct sk_buff *skb) +{ + struct irda_sock *self; + struct sock *sk; + int err; + + IRDA_DEBUG(3, "%s()\n", __FUNCTION__); + + self = instance; + sk = instance; + IRDA_ASSERT(sk != NULL, return -1;); + + err = sock_queue_rcv_skb(sk, skb); + if (err) { + IRDA_DEBUG(1, "%s(), error: no more mem!\n", __FUNCTION__); + self->rx_flow = FLOW_STOP; + + /* When we return error, TTP will need to requeue the skb */ + return err; + } + + return 0; +} + +/* + * Function irda_disconnect_indication (instance, sap, reason, skb) + * + * Connection has been closed. Check reason to find out why + * + */ +static void irda_disconnect_indication(void *instance, void *sap, + LM_REASON reason, struct sk_buff *skb) +{ + struct irda_sock *self; + struct sock *sk; + + self = instance; + + IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self); + + /* Don't care about it, but let's not leak it */ + if(skb) + dev_kfree_skb(skb); + + sk = instance; + if (sk == NULL) { + IRDA_DEBUG(0, "%s(%p) : BUG : sk is NULL\n", + __FUNCTION__, self); + return; + } + + /* Prevent race conditions with irda_release() and irda_shutdown() */ + if (!sock_flag(sk, SOCK_DEAD) && sk->sk_state != TCP_CLOSE) { + sk->sk_state = TCP_CLOSE; + sk->sk_err = ECONNRESET; + sk->sk_shutdown |= SEND_SHUTDOWN; + + sk->sk_state_change(sk); + /* Uh-oh... Should use sock_orphan ? */ + sock_set_flag(sk, SOCK_DEAD); + + /* Close our TSAP. + * If we leave it open, IrLMP put it back into the list of + * unconnected LSAPs. The problem is that any incoming request + * can then be matched to this socket (and it will be, because + * it is at the head of the list). This would prevent any + * listening socket waiting on the same TSAP to get those + * requests. Some apps forget to close sockets, or hang to it + * a bit too long, so we may stay in this dead state long + * enough to be noticed... + * Note : all socket function do check sk->sk_state, so we are + * safe... + * Jean II + */ + if (self->tsap) { + irttp_close_tsap(self->tsap); + self->tsap = NULL; + } + } + + /* Note : once we are there, there is not much you want to do + * with the socket anymore, apart from closing it. + * For example, bind() and connect() won't reset sk->sk_err, + * sk->sk_shutdown and sk->sk_flags to valid values... + * Jean II + */ +} + +/* + * Function irda_connect_confirm (instance, sap, qos, max_sdu_size, skb) + * + * Connections has been confirmed by the remote device + * + */ +static void irda_connect_confirm(void *instance, void *sap, + struct qos_info *qos, + __u32 max_sdu_size, __u8 max_header_size, + struct sk_buff *skb) +{ + struct irda_sock *self; + struct sock *sk; + + self = instance; + + IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self); + + sk = instance; + if (sk == NULL) { + dev_kfree_skb(skb); + return; + } + + dev_kfree_skb(skb); + // Should be ??? skb_queue_tail(&sk->sk_receive_queue, skb); + + /* How much header space do we need to reserve */ + self->max_header_size = max_header_size; + + /* IrTTP max SDU size in transmit direction */ + self->max_sdu_size_tx = max_sdu_size; + + /* Find out what the largest chunk of data that we can transmit is */ + switch (sk->sk_type) { + case SOCK_STREAM: + if (max_sdu_size != 0) { + IRDA_ERROR("%s: max_sdu_size must be 0\n", + __FUNCTION__); + return; + } + self->max_data_size = irttp_get_max_seg_size(self->tsap); + break; + case SOCK_SEQPACKET: + if (max_sdu_size == 0) { + IRDA_ERROR("%s: max_sdu_size cannot be 0\n", + __FUNCTION__); + return; + } + self->max_data_size = max_sdu_size; + break; + default: + self->max_data_size = irttp_get_max_seg_size(self->tsap); + }; + + IRDA_DEBUG(2, "%s(), max_data_size=%d\n", __FUNCTION__, + self->max_data_size); + + memcpy(&self->qos_tx, qos, sizeof(struct qos_info)); + + /* We are now connected! */ + sk->sk_state = TCP_ESTABLISHED; + sk->sk_state_change(sk); +} + +/* + * Function irda_connect_indication(instance, sap, qos, max_sdu_size, userdata) + * + * Incoming connection + * + */ +static void irda_connect_indication(void *instance, void *sap, + struct qos_info *qos, __u32 max_sdu_size, + __u8 max_header_size, struct sk_buff *skb) +{ + struct irda_sock *self; + struct sock *sk; + + self = instance; + + IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self); + + sk = instance; + if (sk == NULL) { + dev_kfree_skb(skb); + return; + } + + /* How much header space do we need to reserve */ + self->max_header_size = max_header_size; + + /* IrTTP max SDU size in transmit direction */ + self->max_sdu_size_tx = max_sdu_size; + + /* Find out what the largest chunk of data that we can transmit is */ + switch (sk->sk_type) { + case SOCK_STREAM: + if (max_sdu_size != 0) { + IRDA_ERROR("%s: max_sdu_size must be 0\n", + __FUNCTION__); + kfree_skb(skb); + return; + } + self->max_data_size = irttp_get_max_seg_size(self->tsap); + break; + case SOCK_SEQPACKET: + if (max_sdu_size == 0) { + IRDA_ERROR("%s: max_sdu_size cannot be 0\n", + __FUNCTION__); + kfree_skb(skb); + return; + } + self->max_data_size = max_sdu_size; + break; + default: + self->max_data_size = irttp_get_max_seg_size(self->tsap); + }; + + IRDA_DEBUG(2, "%s(), max_data_size=%d\n", __FUNCTION__, + self->max_data_size); + + memcpy(&self->qos_tx, qos, sizeof(struct qos_info)); + + skb_queue_tail(&sk->sk_receive_queue, skb); + sk->sk_state_change(sk); +} + +/* + * Function irda_connect_response (handle) + * + * Accept incoming connection + * + */ +static void irda_connect_response(struct irda_sock *self) +{ + struct sk_buff *skb; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + + skb = dev_alloc_skb(64); + if (skb == NULL) { + IRDA_DEBUG(0, "%s() Unable to allocate sk_buff!\n", + __FUNCTION__); + return; + } + + /* Reserve space for MUX_CONTROL and LAP header */ + skb_reserve(skb, IRDA_MAX_HEADER); + + irttp_connect_response(self->tsap, self->max_sdu_size_rx, skb); +} + +/* + * Function irda_flow_indication (instance, sap, flow) + * + * Used by TinyTP to tell us if it can accept more data or not + * + */ +static void irda_flow_indication(void *instance, void *sap, LOCAL_FLOW flow) +{ + struct irda_sock *self; + struct sock *sk; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + self = instance; + sk = instance; + IRDA_ASSERT(sk != NULL, return;); + + switch (flow) { + case FLOW_STOP: + IRDA_DEBUG(1, "%s(), IrTTP wants us to slow down\n", + __FUNCTION__); + self->tx_flow = flow; + break; + case FLOW_START: + self->tx_flow = flow; + IRDA_DEBUG(1, "%s(), IrTTP wants us to start again\n", + __FUNCTION__); + wake_up_interruptible(sk->sk_sleep); + break; + default: + IRDA_DEBUG(0, "%s(), Unknown flow command!\n", __FUNCTION__); + /* Unknown flow command, better stop */ + self->tx_flow = flow; + break; + } +} + +/* + * Function irda_getvalue_confirm (obj_id, value, priv) + * + * Got answer from remote LM-IAS, just pass object to requester... + * + * Note : duplicate from above, but we need our own version that + * doesn't touch the dtsap_sel and save the full value structure... + */ +static void irda_getvalue_confirm(int result, __u16 obj_id, + struct ias_value *value, void *priv) +{ + struct irda_sock *self; + + self = (struct irda_sock *) priv; + if (!self) { + IRDA_WARNING("%s: lost myself!\n", __FUNCTION__); + return; + } + + IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self); + + /* We probably don't need to make any more queries */ + iriap_close(self->iriap); + self->iriap = NULL; + + /* Check if request succeeded */ + if (result != IAS_SUCCESS) { + IRDA_DEBUG(1, "%s(), IAS query failed! (%d)\n", __FUNCTION__, + result); + + self->errno = result; /* We really need it later */ + + /* Wake up any processes waiting for result */ + wake_up_interruptible(&self->query_wait); + + return; + } + + /* Pass the object to the caller (so the caller must delete it) */ + self->ias_result = value; + self->errno = 0; + + /* Wake up any processes waiting for result */ + wake_up_interruptible(&self->query_wait); +} + +/* + * Function irda_selective_discovery_indication (discovery) + * + * Got a selective discovery indication from IrLMP. + * + * IrLMP is telling us that this node is new and matching our hint bit + * filter. Wake up any process waiting for answer... + */ +static void irda_selective_discovery_indication(discinfo_t *discovery, + DISCOVERY_MODE mode, + void *priv) +{ + struct irda_sock *self; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + self = (struct irda_sock *) priv; + if (!self) { + IRDA_WARNING("%s: lost myself!\n", __FUNCTION__); + return; + } + + /* Pass parameter to the caller */ + self->cachedaddr = discovery->daddr; + + /* Wake up process if its waiting for device to be discovered */ + wake_up_interruptible(&self->query_wait); +} + +/* + * Function irda_discovery_timeout (priv) + * + * Timeout in the selective discovery process + * + * We were waiting for a node to be discovered, but nothing has come up + * so far. Wake up the user and tell him that we failed... + */ +static void irda_discovery_timeout(u_long priv) +{ + struct irda_sock *self; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + self = (struct irda_sock *) priv; + IRDA_ASSERT(self != NULL, return;); + + /* Nothing for the caller */ + self->cachelog = NULL; + self->cachedaddr = 0; + self->errno = -ETIME; + + /* Wake up process if its still waiting... */ + wake_up_interruptible(&self->query_wait); +} + +/* + * Function irda_open_tsap (self) + * + * Open local Transport Service Access Point (TSAP) + * + */ +static int irda_open_tsap(struct irda_sock *self, __u8 tsap_sel, char *name) +{ + notify_t notify; + + if (self->tsap) { + IRDA_WARNING("%s: busy!\n", __FUNCTION__); + return -EBUSY; + } + + /* Initialize callbacks to be used by the IrDA stack */ + irda_notify_init(¬ify); + notify.connect_confirm = irda_connect_confirm; + notify.connect_indication = irda_connect_indication; + notify.disconnect_indication = irda_disconnect_indication; + notify.data_indication = irda_data_indication; + notify.udata_indication = irda_data_indication; + notify.flow_indication = irda_flow_indication; + notify.instance = self; + strncpy(notify.name, name, NOTIFY_MAX_NAME); + + self->tsap = irttp_open_tsap(tsap_sel, DEFAULT_INITIAL_CREDIT, + ¬ify); + if (self->tsap == NULL) { + IRDA_DEBUG(0, "%s(), Unable to allocate TSAP!\n", + __FUNCTION__); + return -ENOMEM; + } + /* Remember which TSAP selector we actually got */ + self->stsap_sel = self->tsap->stsap_sel; + + return 0; +} + +/* + * Function irda_open_lsap (self) + * + * Open local Link Service Access Point (LSAP). Used for opening Ultra + * sockets + */ +#ifdef CONFIG_IRDA_ULTRA +static int irda_open_lsap(struct irda_sock *self, int pid) +{ + notify_t notify; + + if (self->lsap) { + IRDA_WARNING("%s(), busy!\n", __FUNCTION__); + return -EBUSY; + } + + /* Initialize callbacks to be used by the IrDA stack */ + irda_notify_init(¬ify); + notify.udata_indication = irda_data_indication; + notify.instance = self; + strncpy(notify.name, "Ultra", NOTIFY_MAX_NAME); + + self->lsap = irlmp_open_lsap(LSAP_CONNLESS, ¬ify, pid); + if (self->lsap == NULL) { + IRDA_DEBUG( 0, "%s(), Unable to allocate LSAP!\n", __FUNCTION__); + return -ENOMEM; + } + + return 0; +} +#endif /* CONFIG_IRDA_ULTRA */ + +/* + * Function irda_find_lsap_sel (self, name) + * + * Try to lookup LSAP selector in remote LM-IAS + * + * Basically, we start a IAP query, and then go to sleep. When the query + * return, irda_getvalue_confirm will wake us up, and we can examine the + * result of the query... + * Note that in some case, the query fail even before we go to sleep, + * creating some races... + */ +static int irda_find_lsap_sel(struct irda_sock *self, char *name) +{ + IRDA_DEBUG(2, "%s(%p, %s)\n", __FUNCTION__, self, name); + + IRDA_ASSERT(self != NULL, return -1;); + + if (self->iriap) { + IRDA_WARNING("%s(): busy with a previous query\n", + __FUNCTION__); + return -EBUSY; + } + + self->iriap = iriap_open(LSAP_ANY, IAS_CLIENT, self, + irda_getvalue_confirm); + if(self->iriap == NULL) + return -ENOMEM; + + /* Treat unexpected wakeup as disconnect */ + self->errno = -EHOSTUNREACH; + + /* Query remote LM-IAS */ + iriap_getvaluebyclass_request(self->iriap, self->saddr, self->daddr, + name, "IrDA:TinyTP:LsapSel"); + + /* Wait for answer, if not yet finished (or failed) */ + if (wait_event_interruptible(self->query_wait, (self->iriap==NULL))) + /* Treat signals as disconnect */ + return -EHOSTUNREACH; + + /* Check what happened */ + if (self->errno) + { + /* Requested object/attribute doesn't exist */ + if((self->errno == IAS_CLASS_UNKNOWN) || + (self->errno == IAS_ATTRIB_UNKNOWN)) + return (-EADDRNOTAVAIL); + else + return (-EHOSTUNREACH); + } + + /* Get the remote TSAP selector */ + switch (self->ias_result->type) { + case IAS_INTEGER: + IRDA_DEBUG(4, "%s() int=%d\n", + __FUNCTION__, self->ias_result->t.integer); + + if (self->ias_result->t.integer != -1) + self->dtsap_sel = self->ias_result->t.integer; + else + self->dtsap_sel = 0; + break; + default: + self->dtsap_sel = 0; + IRDA_DEBUG(0, "%s(), bad type!\n", __FUNCTION__); + break; + } + if (self->ias_result) + irias_delete_value(self->ias_result); + + if (self->dtsap_sel) + return 0; + + return -EADDRNOTAVAIL; +} + +/* + * Function irda_discover_daddr_and_lsap_sel (self, name) + * + * This try to find a device with the requested service. + * + * It basically look into the discovery log. For each address in the list, + * it queries the LM-IAS of the device to find if this device offer + * the requested service. + * If there is more than one node supporting the service, we complain + * to the user (it should move devices around). + * The, we set both the destination address and the lsap selector to point + * on the service on the unique device we have found. + * + * Note : this function fails if there is more than one device in range, + * because IrLMP doesn't disconnect the LAP when the last LSAP is closed. + * Moreover, we would need to wait the LAP disconnection... + */ +static int irda_discover_daddr_and_lsap_sel(struct irda_sock *self, char *name) +{ + discinfo_t *discoveries; /* Copy of the discovery log */ + int number; /* Number of nodes in the log */ + int i; + int err = -ENETUNREACH; + __u32 daddr = DEV_ADDR_ANY; /* Address we found the service on */ + __u8 dtsap_sel = 0x0; /* TSAP associated with it */ + + IRDA_DEBUG(2, "%s(), name=%s\n", __FUNCTION__, name); + + IRDA_ASSERT(self != NULL, return -1;); + + /* Ask lmp for the current discovery log + * Note : we have to use irlmp_get_discoveries(), as opposed + * to play with the cachelog directly, because while we are + * making our ias query, le log might change... */ + discoveries = irlmp_get_discoveries(&number, self->mask.word, + self->nslots); + /* Check if the we got some results */ + if (discoveries == NULL) + return -ENETUNREACH; /* No nodes discovered */ + + /* + * Now, check all discovered devices (if any), and connect + * client only about the services that the client is + * interested in... + */ + for(i = 0; i < number; i++) { + /* Try the address in the log */ + self->daddr = discoveries[i].daddr; + self->saddr = 0x0; + IRDA_DEBUG(1, "%s(), trying daddr = %08x\n", + __FUNCTION__, self->daddr); + + /* Query remote LM-IAS for this service */ + err = irda_find_lsap_sel(self, name); + switch (err) { + case 0: + /* We found the requested service */ + if(daddr != DEV_ADDR_ANY) { + IRDA_DEBUG(1, "%s(), discovered service ''%s'' in two different devices !!!\n", + __FUNCTION__, name); + self->daddr = DEV_ADDR_ANY; + kfree(discoveries); + return(-ENOTUNIQ); + } + /* First time we found that one, save it ! */ + daddr = self->daddr; + dtsap_sel = self->dtsap_sel; + break; + case -EADDRNOTAVAIL: + /* Requested service simply doesn't exist on this node */ + break; + default: + /* Something bad did happen :-( */ + IRDA_DEBUG(0, "%s(), unexpected IAS query failure\n", __FUNCTION__); + self->daddr = DEV_ADDR_ANY; + kfree(discoveries); + return(-EHOSTUNREACH); + break; + } + } + /* Cleanup our copy of the discovery log */ + kfree(discoveries); + + /* Check out what we found */ + if(daddr == DEV_ADDR_ANY) { + IRDA_DEBUG(1, "%s(), cannot discover service ''%s'' in any device !!!\n", + __FUNCTION__, name); + self->daddr = DEV_ADDR_ANY; + return(-EADDRNOTAVAIL); + } + + /* Revert back to discovered device & service */ + self->daddr = daddr; + self->saddr = 0x0; + self->dtsap_sel = dtsap_sel; + + IRDA_DEBUG(1, "%s(), discovered requested service ''%s'' at address %08x\n", + __FUNCTION__, name, self->daddr); + + return 0; +} + +/* + * Function irda_getname (sock, uaddr, uaddr_len, peer) + * + * Return the our own, or peers socket address (sockaddr_irda) + * + */ +static int irda_getname(struct socket *sock, struct sockaddr *uaddr, + int *uaddr_len, int peer) +{ + struct sockaddr_irda saddr; + struct sock *sk = sock->sk; + struct irda_sock *self = irda_sk(sk); + + if (peer) { + if (sk->sk_state != TCP_ESTABLISHED) + return -ENOTCONN; + + saddr.sir_family = AF_IRDA; + saddr.sir_lsap_sel = self->dtsap_sel; + saddr.sir_addr = self->daddr; + } else { + saddr.sir_family = AF_IRDA; + saddr.sir_lsap_sel = self->stsap_sel; + saddr.sir_addr = self->saddr; + } + + IRDA_DEBUG(1, "%s(), tsap_sel = %#x\n", __FUNCTION__, saddr.sir_lsap_sel); + IRDA_DEBUG(1, "%s(), addr = %08x\n", __FUNCTION__, saddr.sir_addr); + + /* uaddr_len come to us uninitialised */ + *uaddr_len = sizeof (struct sockaddr_irda); + memcpy(uaddr, &saddr, *uaddr_len); + + return 0; +} + +/* + * Function irda_listen (sock, backlog) + * + * Just move to the listen state + * + */ +static int irda_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + if ((sk->sk_type != SOCK_STREAM) && (sk->sk_type != SOCK_SEQPACKET) && + (sk->sk_type != SOCK_DGRAM)) + return -EOPNOTSUPP; + + if (sk->sk_state != TCP_LISTEN) { + sk->sk_max_ack_backlog = backlog; + sk->sk_state = TCP_LISTEN; + + return 0; + } + + return -EOPNOTSUPP; +} + +/* + * Function irda_bind (sock, uaddr, addr_len) + * + * Used by servers to register their well known TSAP + * + */ +static int irda_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) +{ + struct sock *sk = sock->sk; + struct sockaddr_irda *addr = (struct sockaddr_irda *) uaddr; + struct irda_sock *self = irda_sk(sk); + int err; + + IRDA_ASSERT(self != NULL, return -1;); + + IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self); + + if (addr_len != sizeof(struct sockaddr_irda)) + return -EINVAL; + +#ifdef CONFIG_IRDA_ULTRA + /* Special care for Ultra sockets */ + if ((sk->sk_type == SOCK_DGRAM) && + (sk->sk_protocol == IRDAPROTO_ULTRA)) { + self->pid = addr->sir_lsap_sel; + if (self->pid & 0x80) { + IRDA_DEBUG(0, "%s(), extension in PID not supp!\n", __FUNCTION__); + return -EOPNOTSUPP; + } + err = irda_open_lsap(self, self->pid); + if (err < 0) + return err; + + /* Pretend we are connected */ + sock->state = SS_CONNECTED; + sk->sk_state = TCP_ESTABLISHED; + + return 0; + } +#endif /* CONFIG_IRDA_ULTRA */ + + err = irda_open_tsap(self, addr->sir_lsap_sel, addr->sir_name); + if (err < 0) + return err; + + /* Register with LM-IAS */ + self->ias_obj = irias_new_object(addr->sir_name, jiffies); + irias_add_integer_attrib(self->ias_obj, "IrDA:TinyTP:LsapSel", + self->stsap_sel, IAS_KERNEL_ATTR); + irias_insert_object(self->ias_obj); + + return 0; +} + +/* + * Function irda_accept (sock, newsock, flags) + * + * Wait for incoming connection + * + */ +static int irda_accept(struct socket *sock, struct socket *newsock, int flags) +{ + struct sock *sk = sock->sk; + struct irda_sock *new, *self = irda_sk(sk); + struct sock *newsk; + struct sk_buff *skb; + int err; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return -1;); + + err = irda_create(newsock, sk->sk_protocol); + if (err) + return err; + + if (sock->state != SS_UNCONNECTED) + return -EINVAL; + + if ((sk = sock->sk) == NULL) + return -EINVAL; + + if ((sk->sk_type != SOCK_STREAM) && (sk->sk_type != SOCK_SEQPACKET) && + (sk->sk_type != SOCK_DGRAM)) + return -EOPNOTSUPP; + + if (sk->sk_state != TCP_LISTEN) + return -EINVAL; + + /* + * The read queue this time is holding sockets ready to use + * hooked into the SABM we saved + */ + + /* + * We can perform the accept only if there is incoming data + * on the listening socket. + * So, we will block the caller until we receive any data. + * If the caller was waiting on select() or poll() before + * calling us, the data is waiting for us ;-) + * Jean II + */ + skb = skb_dequeue(&sk->sk_receive_queue); + if (skb == NULL) { + int ret = 0; + DECLARE_WAITQUEUE(waitq, current); + + /* Non blocking operation */ + if (flags & O_NONBLOCK) + return -EWOULDBLOCK; + + /* The following code is a cut'n'paste of the + * wait_event_interruptible() macro. + * We don't us the macro because the condition has + * side effects : we want to make sure that only one + * skb get dequeued - Jean II */ + add_wait_queue(sk->sk_sleep, &waitq); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + skb = skb_dequeue(&sk->sk_receive_queue); + if (skb != NULL) + break; + if (!signal_pending(current)) { + schedule(); + continue; + } + ret = -ERESTARTSYS; + break; + } + current->state = TASK_RUNNING; + remove_wait_queue(sk->sk_sleep, &waitq); + if(ret) + return -ERESTARTSYS; + } + + newsk = newsock->sk; + newsk->sk_state = TCP_ESTABLISHED; + + new = irda_sk(newsk); + IRDA_ASSERT(new != NULL, return -1;); + + /* Now attach up the new socket */ + new->tsap = irttp_dup(self->tsap, new); + if (!new->tsap) { + IRDA_DEBUG(0, "%s(), dup failed!\n", __FUNCTION__); + kfree_skb(skb); + return -1; + } + + new->stsap_sel = new->tsap->stsap_sel; + new->dtsap_sel = new->tsap->dtsap_sel; + new->saddr = irttp_get_saddr(new->tsap); + new->daddr = irttp_get_daddr(new->tsap); + + new->max_sdu_size_tx = self->max_sdu_size_tx; + new->max_sdu_size_rx = self->max_sdu_size_rx; + new->max_data_size = self->max_data_size; + new->max_header_size = self->max_header_size; + + memcpy(&new->qos_tx, &self->qos_tx, sizeof(struct qos_info)); + + /* Clean up the original one to keep it in listen state */ + irttp_listen(self->tsap); + + /* Wow ! What is that ? Jean II */ + skb->sk = NULL; + skb->destructor = NULL; + kfree_skb(skb); + sk->sk_ack_backlog--; + + newsock->state = SS_CONNECTED; + + irda_connect_response(new); + + return 0; +} + +/* + * Function irda_connect (sock, uaddr, addr_len, flags) + * + * Connect to a IrDA device + * + * The main difference with a "standard" connect is that with IrDA we need + * to resolve the service name into a TSAP selector (in TCP, port number + * doesn't have to be resolved). + * Because of this service name resoltion, we can offer "auto-connect", + * where we connect to a service without specifying a destination address. + * + * Note : by consulting "errno", the user space caller may learn the cause + * of the failure. Most of them are visible in the function, others may come + * from subroutines called and are listed here : + * o EBUSY : already processing a connect + * o EHOSTUNREACH : bad addr->sir_addr argument + * o EADDRNOTAVAIL : bad addr->sir_name argument + * o ENOTUNIQ : more than one node has addr->sir_name (auto-connect) + * o ENETUNREACH : no node found on the network (auto-connect) + */ +static int irda_connect(struct socket *sock, struct sockaddr *uaddr, + int addr_len, int flags) +{ + struct sock *sk = sock->sk; + struct sockaddr_irda *addr = (struct sockaddr_irda *) uaddr; + struct irda_sock *self = irda_sk(sk); + int err; + + IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self); + + /* Don't allow connect for Ultra sockets */ + if ((sk->sk_type == SOCK_DGRAM) && (sk->sk_protocol == IRDAPROTO_ULTRA)) + return -ESOCKTNOSUPPORT; + + if (sk->sk_state == TCP_ESTABLISHED && sock->state == SS_CONNECTING) { + sock->state = SS_CONNECTED; + return 0; /* Connect completed during a ERESTARTSYS event */ + } + + if (sk->sk_state == TCP_CLOSE && sock->state == SS_CONNECTING) { + sock->state = SS_UNCONNECTED; + return -ECONNREFUSED; + } + + if (sk->sk_state == TCP_ESTABLISHED) + return -EISCONN; /* No reconnect on a seqpacket socket */ + + sk->sk_state = TCP_CLOSE; + sock->state = SS_UNCONNECTED; + + if (addr_len != sizeof(struct sockaddr_irda)) + return -EINVAL; + + /* Check if user supplied any destination device address */ + if ((!addr->sir_addr) || (addr->sir_addr == DEV_ADDR_ANY)) { + /* Try to find one suitable */ + err = irda_discover_daddr_and_lsap_sel(self, addr->sir_name); + if (err) { + IRDA_DEBUG(0, "%s(), auto-connect failed!\n", __FUNCTION__); + return err; + } + } else { + /* Use the one provided by the user */ + self->daddr = addr->sir_addr; + IRDA_DEBUG(1, "%s(), daddr = %08x\n", __FUNCTION__, self->daddr); + + /* If we don't have a valid service name, we assume the + * user want to connect on a specific LSAP. Prevent + * the use of invalid LSAPs (IrLMP 1.1 p10). Jean II */ + if((addr->sir_name[0] != '\0') || + (addr->sir_lsap_sel >= 0x70)) { + /* Query remote LM-IAS using service name */ + err = irda_find_lsap_sel(self, addr->sir_name); + if (err) { + IRDA_DEBUG(0, "%s(), connect failed!\n", __FUNCTION__); + return err; + } + } else { + /* Directly connect to the remote LSAP + * specified by the sir_lsap field. + * Please use with caution, in IrDA LSAPs are + * dynamic and there is no "well-known" LSAP. */ + self->dtsap_sel = addr->sir_lsap_sel; + } + } + + /* Check if we have opened a local TSAP */ + if (!self->tsap) + irda_open_tsap(self, LSAP_ANY, addr->sir_name); + + /* Move to connecting socket, start sending Connect Requests */ + sock->state = SS_CONNECTING; + sk->sk_state = TCP_SYN_SENT; + + /* Connect to remote device */ + err = irttp_connect_request(self->tsap, self->dtsap_sel, + self->saddr, self->daddr, NULL, + self->max_sdu_size_rx, NULL); + if (err) { + IRDA_DEBUG(0, "%s(), connect failed!\n", __FUNCTION__); + return err; + } + + /* Now the loop */ + if (sk->sk_state != TCP_ESTABLISHED && (flags & O_NONBLOCK)) + return -EINPROGRESS; + + if (wait_event_interruptible(*(sk->sk_sleep), + (sk->sk_state != TCP_SYN_SENT))) + return -ERESTARTSYS; + + if (sk->sk_state != TCP_ESTABLISHED) { + sock->state = SS_UNCONNECTED; + return sock_error(sk); /* Always set at this point */ + } + + sock->state = SS_CONNECTED; + + /* At this point, IrLMP has assigned our source address */ + self->saddr = irttp_get_saddr(self->tsap); + + return 0; +} + +static struct proto irda_proto = { + .name = "IRDA", + .owner = THIS_MODULE, + .obj_size = sizeof(struct irda_sock), +}; + +/* + * Function irda_create (sock, protocol) + * + * Create IrDA socket + * + */ +static int irda_create(struct socket *sock, int protocol) +{ + struct sock *sk; + struct irda_sock *self; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + /* Check for valid socket type */ + switch (sock->type) { + case SOCK_STREAM: /* For TTP connections with SAR disabled */ + case SOCK_SEQPACKET: /* For TTP connections with SAR enabled */ + case SOCK_DGRAM: /* For TTP Unitdata or LMP Ultra transfers */ + break; + default: + return -ESOCKTNOSUPPORT; + } + + /* Allocate networking socket */ + sk = sk_alloc(PF_IRDA, GFP_ATOMIC, &irda_proto, 1); + if (sk == NULL) + return -ENOMEM; + + self = irda_sk(sk); + IRDA_DEBUG(2, "%s() : self is %p\n", __FUNCTION__, self); + + init_waitqueue_head(&self->query_wait); + + /* Initialise networking socket struct */ + sock_init_data(sock, sk); /* Note : set sk->sk_refcnt to 1 */ + sk->sk_family = PF_IRDA; + sk->sk_protocol = protocol; + + switch (sock->type) { + case SOCK_STREAM: + sock->ops = &irda_stream_ops; + self->max_sdu_size_rx = TTP_SAR_DISABLE; + break; + case SOCK_SEQPACKET: + sock->ops = &irda_seqpacket_ops; + self->max_sdu_size_rx = TTP_SAR_UNBOUND; + break; + case SOCK_DGRAM: + switch (protocol) { +#ifdef CONFIG_IRDA_ULTRA + case IRDAPROTO_ULTRA: + sock->ops = &irda_ultra_ops; + /* Initialise now, because we may send on unbound + * sockets. Jean II */ + self->max_data_size = ULTRA_MAX_DATA - LMP_PID_HEADER; + self->max_header_size = IRDA_MAX_HEADER + LMP_PID_HEADER; + break; +#endif /* CONFIG_IRDA_ULTRA */ + case IRDAPROTO_UNITDATA: + sock->ops = &irda_dgram_ops; + /* We let Unitdata conn. be like seqpack conn. */ + self->max_sdu_size_rx = TTP_SAR_UNBOUND; + break; + default: + IRDA_ERROR("%s: protocol not supported!\n", + __FUNCTION__); + return -ESOCKTNOSUPPORT; + } + break; + default: + return -ESOCKTNOSUPPORT; + } + + /* Register as a client with IrLMP */ + self->ckey = irlmp_register_client(0, NULL, NULL, NULL); + self->mask.word = 0xffff; + self->rx_flow = self->tx_flow = FLOW_START; + self->nslots = DISCOVERY_DEFAULT_SLOTS; + self->daddr = DEV_ADDR_ANY; /* Until we get connected */ + self->saddr = 0x0; /* so IrLMP assign us any link */ + return 0; +} + +/* + * Function irda_destroy_socket (self) + * + * Destroy socket + * + */ +static void irda_destroy_socket(struct irda_sock *self) +{ + IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self); + + IRDA_ASSERT(self != NULL, return;); + + /* Unregister with IrLMP */ + irlmp_unregister_client(self->ckey); + irlmp_unregister_service(self->skey); + + /* Unregister with LM-IAS */ + if (self->ias_obj) { + irias_delete_object(self->ias_obj); + self->ias_obj = NULL; + } + + if (self->iriap) { + iriap_close(self->iriap); + self->iriap = NULL; + } + + if (self->tsap) { + irttp_disconnect_request(self->tsap, NULL, P_NORMAL); + irttp_close_tsap(self->tsap); + self->tsap = NULL; + } +#ifdef CONFIG_IRDA_ULTRA + if (self->lsap) { + irlmp_close_lsap(self->lsap); + self->lsap = NULL; + } +#endif /* CONFIG_IRDA_ULTRA */ +} + +/* + * Function irda_release (sock) + */ +static int irda_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + if (sk == NULL) + return 0; + + sk->sk_state = TCP_CLOSE; + sk->sk_shutdown |= SEND_SHUTDOWN; + sk->sk_state_change(sk); + + /* Destroy IrDA socket */ + irda_destroy_socket(irda_sk(sk)); + + sock_orphan(sk); + sock->sk = NULL; + + /* Purge queues (see sock_init_data()) */ + skb_queue_purge(&sk->sk_receive_queue); + + /* Destroy networking socket if we are the last reference on it, + * i.e. if(sk->sk_refcnt == 0) -> sk_free(sk) */ + sock_put(sk); + + /* Notes on socket locking and deallocation... - Jean II + * In theory we should put pairs of sock_hold() / sock_put() to + * prevent the socket to be destroyed whenever there is an + * outstanding request or outstanding incoming packet or event. + * + * 1) This may include IAS request, both in connect and getsockopt. + * Unfortunately, the situation is a bit more messy than it looks, + * because we close iriap and kfree(self) above. + * + * 2) This may include selective discovery in getsockopt. + * Same stuff as above, irlmp registration and self are gone. + * + * Probably 1 and 2 may not matter, because it's all triggered + * by a process and the socket layer already prevent the + * socket to go away while a process is holding it, through + * sockfd_put() and fput()... + * + * 3) This may include deferred TSAP closure. In particular, + * we may receive a late irda_disconnect_indication() + * Fortunately, (tsap_cb *)->close_pend should protect us + * from that. + * + * I did some testing on SMP, and it looks solid. And the socket + * memory leak is now gone... - Jean II + */ + + return 0; +} + +/* + * Function irda_sendmsg (iocb, sock, msg, len) + * + * Send message down to TinyTP. This function is used for both STREAM and + * SEQPACK services. This is possible since it forces the client to + * fragment the message if necessary + */ +static int irda_sendmsg(struct kiocb *iocb, struct socket *sock, + struct msghdr *msg, size_t len) +{ + struct sock *sk = sock->sk; + struct irda_sock *self; + struct sk_buff *skb; + unsigned char *asmptr; + int err; + + IRDA_DEBUG(4, "%s(), len=%zd\n", __FUNCTION__, len); + + /* Note : socket.c set MSG_EOR on SEQPACKET sockets */ + if (msg->msg_flags & ~(MSG_DONTWAIT|MSG_EOR|MSG_CMSG_COMPAT)) + return -EINVAL; + + if (sk->sk_shutdown & SEND_SHUTDOWN) { + send_sig(SIGPIPE, current, 0); + return -EPIPE; + } + + if (sk->sk_state != TCP_ESTABLISHED) + return -ENOTCONN; + + self = irda_sk(sk); + IRDA_ASSERT(self != NULL, return -1;); + + /* Check if IrTTP is wants us to slow down */ + + if (wait_event_interruptible(*(sk->sk_sleep), + (self->tx_flow != FLOW_STOP || sk->sk_state != TCP_ESTABLISHED))) + return -ERESTARTSYS; + + /* Check if we are still connected */ + if (sk->sk_state != TCP_ESTABLISHED) + return -ENOTCONN; + + /* Check that we don't send out to big frames */ + if (len > self->max_data_size) { + IRDA_DEBUG(2, "%s(), Chopping frame from %zd to %d bytes!\n", + __FUNCTION__, len, self->max_data_size); + len = self->max_data_size; + } + + skb = sock_alloc_send_skb(sk, len + self->max_header_size + 16, + msg->msg_flags & MSG_DONTWAIT, &err); + if (!skb) + return -ENOBUFS; + + skb_reserve(skb, self->max_header_size + 16); + + asmptr = skb->h.raw = skb_put(skb, len); + err = memcpy_fromiovec(asmptr, msg->msg_iov, len); + if (err) { + kfree_skb(skb); + return err; + } + + /* + * Just send the message to TinyTP, and let it deal with possible + * errors. No need to duplicate all that here + */ + err = irttp_data_request(self->tsap, skb); + if (err) { + IRDA_DEBUG(0, "%s(), err=%d\n", __FUNCTION__, err); + return err; + } + /* Tell client how much data we actually sent */ + return len; +} + +/* + * Function irda_recvmsg_dgram (iocb, sock, msg, size, flags) + * + * Try to receive message and copy it to user. The frame is discarded + * after being read, regardless of how much the user actually read + */ +static int irda_recvmsg_dgram(struct kiocb *iocb, struct socket *sock, + struct msghdr *msg, size_t size, int flags) +{ + struct sock *sk = sock->sk; + struct irda_sock *self = irda_sk(sk); + struct sk_buff *skb; + size_t copied; + int err; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return -1;); + + skb = skb_recv_datagram(sk, flags & ~MSG_DONTWAIT, + flags & MSG_DONTWAIT, &err); + if (!skb) + return err; + + skb->h.raw = skb->data; + copied = skb->len; + + if (copied > size) { + IRDA_DEBUG(2, "%s(), Received truncated frame (%zd < %zd)!\n", + __FUNCTION__, copied, size); + copied = size; + msg->msg_flags |= MSG_TRUNC; + } + skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied); + + skb_free_datagram(sk, skb); + + /* + * Check if we have previously stopped IrTTP and we know + * have more free space in our rx_queue. If so tell IrTTP + * to start delivering frames again before our rx_queue gets + * empty + */ + if (self->rx_flow == FLOW_STOP) { + if ((atomic_read(&sk->sk_rmem_alloc) << 2) <= sk->sk_rcvbuf) { + IRDA_DEBUG(2, "%s(), Starting IrTTP\n", __FUNCTION__); + self->rx_flow = FLOW_START; + irttp_flow_request(self->tsap, FLOW_START); + } + } + + return copied; +} + +/* + * Function irda_recvmsg_stream (iocb, sock, msg, size, flags) + */ +static int irda_recvmsg_stream(struct kiocb *iocb, struct socket *sock, + struct msghdr *msg, size_t size, int flags) +{ + struct sock *sk = sock->sk; + struct irda_sock *self = irda_sk(sk); + int noblock = flags & MSG_DONTWAIT; + size_t copied = 0; + int target = 1; + DECLARE_WAITQUEUE(waitq, current); + + IRDA_DEBUG(3, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return -1;); + + if (sock->flags & __SO_ACCEPTCON) + return(-EINVAL); + + if (flags & MSG_OOB) + return -EOPNOTSUPP; + + if (flags & MSG_WAITALL) + target = size; + + msg->msg_namelen = 0; + + do { + int chunk; + struct sk_buff *skb = skb_dequeue(&sk->sk_receive_queue); + + if (skb==NULL) { + int ret = 0; + + if (copied >= target) + break; + + /* The following code is a cut'n'paste of the + * wait_event_interruptible() macro. + * We don't us the macro because the test condition + * is messy. - Jean II */ + set_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags); + add_wait_queue(sk->sk_sleep, &waitq); + set_current_state(TASK_INTERRUPTIBLE); + + /* + * POSIX 1003.1g mandates this order. + */ + if (sk->sk_err) + ret = sock_error(sk); + else if (sk->sk_shutdown & RCV_SHUTDOWN) + ; + else if (noblock) + ret = -EAGAIN; + else if (signal_pending(current)) + ret = -ERESTARTSYS; + else if (skb_peek(&sk->sk_receive_queue) == NULL) + /* Wait process until data arrives */ + schedule(); + + current->state = TASK_RUNNING; + remove_wait_queue(sk->sk_sleep, &waitq); + clear_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags); + + if(ret) + return(ret); + if (sk->sk_shutdown & RCV_SHUTDOWN) + break; + + continue; + } + + chunk = min_t(unsigned int, skb->len, size); + if (memcpy_toiovec(msg->msg_iov, skb->data, chunk)) { + skb_queue_head(&sk->sk_receive_queue, skb); + if (copied == 0) + copied = -EFAULT; + break; + } + copied += chunk; + size -= chunk; + + /* Mark read part of skb as used */ + if (!(flags & MSG_PEEK)) { + skb_pull(skb, chunk); + + /* put the skb back if we didn't use it up.. */ + if (skb->len) { + IRDA_DEBUG(1, "%s(), back on q!\n", + __FUNCTION__); + skb_queue_head(&sk->sk_receive_queue, skb); + break; + } + + kfree_skb(skb); + } else { + IRDA_DEBUG(0, "%s() questionable!?\n", __FUNCTION__); + + /* put message back and return */ + skb_queue_head(&sk->sk_receive_queue, skb); + break; + } + } while (size); + + /* + * Check if we have previously stopped IrTTP and we know + * have more free space in our rx_queue. If so tell IrTTP + * to start delivering frames again before our rx_queue gets + * empty + */ + if (self->rx_flow == FLOW_STOP) { + if ((atomic_read(&sk->sk_rmem_alloc) << 2) <= sk->sk_rcvbuf) { + IRDA_DEBUG(2, "%s(), Starting IrTTP\n", __FUNCTION__); + self->rx_flow = FLOW_START; + irttp_flow_request(self->tsap, FLOW_START); + } + } + + return copied; +} + +/* + * Function irda_sendmsg_dgram (iocb, sock, msg, len) + * + * Send message down to TinyTP for the unreliable sequenced + * packet service... + * + */ +static int irda_sendmsg_dgram(struct kiocb *iocb, struct socket *sock, + struct msghdr *msg, size_t len) +{ + struct sock *sk = sock->sk; + struct irda_sock *self; + struct sk_buff *skb; + unsigned char *asmptr; + int err; + + IRDA_DEBUG(4, "%s(), len=%zd\n", __FUNCTION__, len); + + if (msg->msg_flags & ~(MSG_DONTWAIT|MSG_CMSG_COMPAT)) + return -EINVAL; + + if (sk->sk_shutdown & SEND_SHUTDOWN) { + send_sig(SIGPIPE, current, 0); + return -EPIPE; + } + + if (sk->sk_state != TCP_ESTABLISHED) + return -ENOTCONN; + + self = irda_sk(sk); + IRDA_ASSERT(self != NULL, return -1;); + + /* + * Check that we don't send out to big frames. This is an unreliable + * service, so we have no fragmentation and no coalescence + */ + if (len > self->max_data_size) { + IRDA_DEBUG(0, "%s(), Warning to much data! " + "Chopping frame from %zd to %d bytes!\n", + __FUNCTION__, len, self->max_data_size); + len = self->max_data_size; + } + + skb = sock_alloc_send_skb(sk, len + self->max_header_size, + msg->msg_flags & MSG_DONTWAIT, &err); + if (!skb) + return -ENOBUFS; + + skb_reserve(skb, self->max_header_size); + + IRDA_DEBUG(4, "%s(), appending user data\n", __FUNCTION__); + asmptr = skb->h.raw = skb_put(skb, len); + err = memcpy_fromiovec(asmptr, msg->msg_iov, len); + if (err) { + kfree_skb(skb); + return err; + } + + /* + * Just send the message to TinyTP, and let it deal with possible + * errors. No need to duplicate all that here + */ + err = irttp_udata_request(self->tsap, skb); + if (err) { + IRDA_DEBUG(0, "%s(), err=%d\n", __FUNCTION__, err); + return err; + } + return len; +} + +/* + * Function irda_sendmsg_ultra (iocb, sock, msg, len) + * + * Send message down to IrLMP for the unreliable Ultra + * packet service... + */ +#ifdef CONFIG_IRDA_ULTRA +static int irda_sendmsg_ultra(struct kiocb *iocb, struct socket *sock, + struct msghdr *msg, size_t len) +{ + struct sock *sk = sock->sk; + struct irda_sock *self; + __u8 pid = 0; + int bound = 0; + struct sk_buff *skb; + unsigned char *asmptr; + int err; + + IRDA_DEBUG(4, "%s(), len=%zd\n", __FUNCTION__, len); + + if (msg->msg_flags & ~(MSG_DONTWAIT|MSG_CMSG_COMPAT)) + return -EINVAL; + + if (sk->sk_shutdown & SEND_SHUTDOWN) { + send_sig(SIGPIPE, current, 0); + return -EPIPE; + } + + self = irda_sk(sk); + IRDA_ASSERT(self != NULL, return -1;); + + /* Check if an address was specified with sendto. Jean II */ + if (msg->msg_name) { + struct sockaddr_irda *addr = (struct sockaddr_irda *) msg->msg_name; + /* Check address, extract pid. Jean II */ + if (msg->msg_namelen < sizeof(*addr)) + return -EINVAL; + if (addr->sir_family != AF_IRDA) + return -EINVAL; + + pid = addr->sir_lsap_sel; + if (pid & 0x80) { + IRDA_DEBUG(0, "%s(), extension in PID not supp!\n", __FUNCTION__); + return -EOPNOTSUPP; + } + } else { + /* Check that the socket is properly bound to an Ultra + * port. Jean II */ + if ((self->lsap == NULL) || + (sk->sk_state != TCP_ESTABLISHED)) { + IRDA_DEBUG(0, "%s(), socket not bound to Ultra PID.\n", + __FUNCTION__); + return -ENOTCONN; + } + /* Use PID from socket */ + bound = 1; + } + + /* + * Check that we don't send out to big frames. This is an unreliable + * service, so we have no fragmentation and no coalescence + */ + if (len > self->max_data_size) { + IRDA_DEBUG(0, "%s(), Warning to much data! " + "Chopping frame from %zd to %d bytes!\n", + __FUNCTION__, len, self->max_data_size); + len = self->max_data_size; + } + + skb = sock_alloc_send_skb(sk, len + self->max_header_size, + msg->msg_flags & MSG_DONTWAIT, &err); + if (!skb) + return -ENOBUFS; + + skb_reserve(skb, self->max_header_size); + + IRDA_DEBUG(4, "%s(), appending user data\n", __FUNCTION__); + asmptr = skb->h.raw = skb_put(skb, len); + err = memcpy_fromiovec(asmptr, msg->msg_iov, len); + if (err) { + kfree_skb(skb); + return err; + } + + err = irlmp_connless_data_request((bound ? self->lsap : NULL), + skb, pid); + if (err) { + IRDA_DEBUG(0, "%s(), err=%d\n", __FUNCTION__, err); + return err; + } + return len; +} +#endif /* CONFIG_IRDA_ULTRA */ + +/* + * Function irda_shutdown (sk, how) + */ +static int irda_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + struct irda_sock *self = irda_sk(sk); + + IRDA_ASSERT(self != NULL, return -1;); + + IRDA_DEBUG(1, "%s(%p)\n", __FUNCTION__, self); + + sk->sk_state = TCP_CLOSE; + sk->sk_shutdown |= SEND_SHUTDOWN; + sk->sk_state_change(sk); + + if (self->iriap) { + iriap_close(self->iriap); + self->iriap = NULL; + } + + if (self->tsap) { + irttp_disconnect_request(self->tsap, NULL, P_NORMAL); + irttp_close_tsap(self->tsap); + self->tsap = NULL; + } + + /* A few cleanup so the socket look as good as new... */ + self->rx_flow = self->tx_flow = FLOW_START; /* needed ??? */ + self->daddr = DEV_ADDR_ANY; /* Until we get re-connected */ + self->saddr = 0x0; /* so IrLMP assign us any link */ + + return 0; +} + +/* + * Function irda_poll (file, sock, wait) + */ +static unsigned int irda_poll(struct file * file, struct socket *sock, + poll_table *wait) +{ + struct sock *sk = sock->sk; + struct irda_sock *self = irda_sk(sk); + unsigned int mask; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + poll_wait(file, sk->sk_sleep, wait); + mask = 0; + + /* Exceptional events? */ + if (sk->sk_err) + mask |= POLLERR; + if (sk->sk_shutdown & RCV_SHUTDOWN) { + IRDA_DEBUG(0, "%s(), POLLHUP\n", __FUNCTION__); + mask |= POLLHUP; + } + + /* Readable? */ + if (!skb_queue_empty(&sk->sk_receive_queue)) { + IRDA_DEBUG(4, "Socket is readable\n"); + mask |= POLLIN | POLLRDNORM; + } + + /* Connection-based need to check for termination and startup */ + switch (sk->sk_type) { + case SOCK_STREAM: + if (sk->sk_state == TCP_CLOSE) { + IRDA_DEBUG(0, "%s(), POLLHUP\n", __FUNCTION__); + mask |= POLLHUP; + } + + if (sk->sk_state == TCP_ESTABLISHED) { + if ((self->tx_flow == FLOW_START) && + sock_writeable(sk)) + { + mask |= POLLOUT | POLLWRNORM | POLLWRBAND; + } + } + break; + case SOCK_SEQPACKET: + if ((self->tx_flow == FLOW_START) && + sock_writeable(sk)) + { + mask |= POLLOUT | POLLWRNORM | POLLWRBAND; + } + break; + case SOCK_DGRAM: + if (sock_writeable(sk)) + mask |= POLLOUT | POLLWRNORM | POLLWRBAND; + break; + default: + break; + } + return mask; +} + +/* + * Function irda_ioctl (sock, cmd, arg) + */ +static int irda_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) +{ + struct sock *sk = sock->sk; + + IRDA_DEBUG(4, "%s(), cmd=%#x\n", __FUNCTION__, cmd); + + switch (cmd) { + case TIOCOUTQ: { + long amount; + amount = sk->sk_sndbuf - atomic_read(&sk->sk_wmem_alloc); + if (amount < 0) + amount = 0; + if (put_user(amount, (unsigned int __user *)arg)) + return -EFAULT; + return 0; + } + + case TIOCINQ: { + struct sk_buff *skb; + long amount = 0L; + /* These two are safe on a single CPU system as only user tasks fiddle here */ + if ((skb = skb_peek(&sk->sk_receive_queue)) != NULL) + amount = skb->len; + if (put_user(amount, (unsigned int __user *)arg)) + return -EFAULT; + return 0; + } + + case SIOCGSTAMP: + if (sk != NULL) + return sock_get_timestamp(sk, (struct timeval __user *)arg); + return -EINVAL; + + case SIOCGIFADDR: + case SIOCSIFADDR: + case SIOCGIFDSTADDR: + case SIOCSIFDSTADDR: + case SIOCGIFBRDADDR: + case SIOCSIFBRDADDR: + case SIOCGIFNETMASK: + case SIOCSIFNETMASK: + case SIOCGIFMETRIC: + case SIOCSIFMETRIC: + return -EINVAL; + default: + IRDA_DEBUG(1, "%s(), doing device ioctl!\n", __FUNCTION__); + return dev_ioctl(cmd, (void __user *) arg); + } + + /*NOTREACHED*/ + return 0; +} + +/* + * Function irda_setsockopt (sock, level, optname, optval, optlen) + * + * Set some options for the socket + * + */ +static int irda_setsockopt(struct socket *sock, int level, int optname, + char __user *optval, int optlen) +{ + struct sock *sk = sock->sk; + struct irda_sock *self = irda_sk(sk); + struct irda_ias_set *ias_opt; + struct ias_object *ias_obj; + struct ias_attrib * ias_attr; /* Attribute in IAS object */ + int opt; + + IRDA_ASSERT(self != NULL, return -1;); + + IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self); + + if (level != SOL_IRLMP) + return -ENOPROTOOPT; + + switch (optname) { + case IRLMP_IAS_SET: + /* The user want to add an attribute to an existing IAS object + * (in the IAS database) or to create a new object with this + * attribute. + * We first query IAS to know if the object exist, and then + * create the right attribute... + */ + + if (optlen != sizeof(struct irda_ias_set)) + return -EINVAL; + + ias_opt = kmalloc(sizeof(struct irda_ias_set), GFP_ATOMIC); + if (ias_opt == NULL) + return -ENOMEM; + + /* Copy query to the driver. */ + if (copy_from_user(ias_opt, optval, optlen)) { + kfree(ias_opt); + return -EFAULT; + } + + /* Find the object we target. + * If the user gives us an empty string, we use the object + * associated with this socket. This will workaround + * duplicated class name - Jean II */ + if(ias_opt->irda_class_name[0] == '\0') { + if(self->ias_obj == NULL) { + kfree(ias_opt); + return -EINVAL; + } + ias_obj = self->ias_obj; + } else + ias_obj = irias_find_object(ias_opt->irda_class_name); + + /* Only ROOT can mess with the global IAS database. + * Users can only add attributes to the object associated + * with the socket they own - Jean II */ + if((!capable(CAP_NET_ADMIN)) && + ((ias_obj == NULL) || (ias_obj != self->ias_obj))) { + kfree(ias_opt); + return -EPERM; + } + + /* If the object doesn't exist, create it */ + if(ias_obj == (struct ias_object *) NULL) { + /* Create a new object */ + ias_obj = irias_new_object(ias_opt->irda_class_name, + jiffies); + } + + /* Do we have the attribute already ? */ + if(irias_find_attrib(ias_obj, ias_opt->irda_attrib_name)) { + kfree(ias_opt); + return -EINVAL; + } + + /* Look at the type */ + switch(ias_opt->irda_attrib_type) { + case IAS_INTEGER: + /* Add an integer attribute */ + irias_add_integer_attrib( + ias_obj, + ias_opt->irda_attrib_name, + ias_opt->attribute.irda_attrib_int, + IAS_USER_ATTR); + break; + case IAS_OCT_SEQ: + /* Check length */ + if(ias_opt->attribute.irda_attrib_octet_seq.len > + IAS_MAX_OCTET_STRING) { + kfree(ias_opt); + return -EINVAL; + } + /* Add an octet sequence attribute */ + irias_add_octseq_attrib( + ias_obj, + ias_opt->irda_attrib_name, + ias_opt->attribute.irda_attrib_octet_seq.octet_seq, + ias_opt->attribute.irda_attrib_octet_seq.len, + IAS_USER_ATTR); + break; + case IAS_STRING: + /* Should check charset & co */ + /* Check length */ + /* The length is encoded in a __u8, and + * IAS_MAX_STRING == 256, so there is no way + * userspace can pass us a string too large. + * Jean II */ + /* NULL terminate the string (avoid troubles) */ + ias_opt->attribute.irda_attrib_string.string[ias_opt->attribute.irda_attrib_string.len] = '\0'; + /* Add a string attribute */ + irias_add_string_attrib( + ias_obj, + ias_opt->irda_attrib_name, + ias_opt->attribute.irda_attrib_string.string, + IAS_USER_ATTR); + break; + default : + kfree(ias_opt); + return -EINVAL; + } + irias_insert_object(ias_obj); + kfree(ias_opt); + break; + case IRLMP_IAS_DEL: + /* The user want to delete an object from our local IAS + * database. We just need to query the IAS, check is the + * object is not owned by the kernel and delete it. + */ + + if (optlen != sizeof(struct irda_ias_set)) + return -EINVAL; + + ias_opt = kmalloc(sizeof(struct irda_ias_set), GFP_ATOMIC); + if (ias_opt == NULL) + return -ENOMEM; + + /* Copy query to the driver. */ + if (copy_from_user(ias_opt, optval, optlen)) { + kfree(ias_opt); + return -EFAULT; + } + + /* Find the object we target. + * If the user gives us an empty string, we use the object + * associated with this socket. This will workaround + * duplicated class name - Jean II */ + if(ias_opt->irda_class_name[0] == '\0') + ias_obj = self->ias_obj; + else + ias_obj = irias_find_object(ias_opt->irda_class_name); + if(ias_obj == (struct ias_object *) NULL) { + kfree(ias_opt); + return -EINVAL; + } + + /* Only ROOT can mess with the global IAS database. + * Users can only del attributes from the object associated + * with the socket they own - Jean II */ + if((!capable(CAP_NET_ADMIN)) && + ((ias_obj == NULL) || (ias_obj != self->ias_obj))) { + kfree(ias_opt); + return -EPERM; + } + + /* Find the attribute (in the object) we target */ + ias_attr = irias_find_attrib(ias_obj, + ias_opt->irda_attrib_name); + if(ias_attr == (struct ias_attrib *) NULL) { + kfree(ias_opt); + return -EINVAL; + } + + /* Check is the user space own the object */ + if(ias_attr->value->owner != IAS_USER_ATTR) { + IRDA_DEBUG(1, "%s(), attempting to delete a kernel attribute\n", __FUNCTION__); + kfree(ias_opt); + return -EPERM; + } + + /* Remove the attribute (and maybe the object) */ + irias_delete_attrib(ias_obj, ias_attr, 1); + kfree(ias_opt); + break; + case IRLMP_MAX_SDU_SIZE: + if (optlen < sizeof(int)) + return -EINVAL; + + if (get_user(opt, (int __user *)optval)) + return -EFAULT; + + /* Only possible for a seqpacket service (TTP with SAR) */ + if (sk->sk_type != SOCK_SEQPACKET) { + IRDA_DEBUG(2, "%s(), setting max_sdu_size = %d\n", + __FUNCTION__, opt); + self->max_sdu_size_rx = opt; + } else { + IRDA_WARNING("%s: not allowed to set MAXSDUSIZE for this socket type!\n", + __FUNCTION__); + return -ENOPROTOOPT; + } + break; + case IRLMP_HINTS_SET: + if (optlen < sizeof(int)) + return -EINVAL; + + /* The input is really a (__u8 hints[2]), easier as an int */ + if (get_user(opt, (int __user *)optval)) + return -EFAULT; + + /* Unregister any old registration */ + if (self->skey) + irlmp_unregister_service(self->skey); + + self->skey = irlmp_register_service((__u16) opt); + break; + case IRLMP_HINT_MASK_SET: + /* As opposed to the previous case which set the hint bits + * that we advertise, this one set the filter we use when + * making a discovery (nodes which don't match any hint + * bit in the mask are not reported). + */ + if (optlen < sizeof(int)) + return -EINVAL; + + /* The input is really a (__u8 hints[2]), easier as an int */ + if (get_user(opt, (int __user *)optval)) + return -EFAULT; + + /* Set the new hint mask */ + self->mask.word = (__u16) opt; + /* Mask out extension bits */ + self->mask.word &= 0x7f7f; + /* Check if no bits */ + if(!self->mask.word) + self->mask.word = 0xFFFF; + + break; + default: + return -ENOPROTOOPT; + } + return 0; +} + +/* + * Function irda_extract_ias_value(ias_opt, ias_value) + * + * Translate internal IAS value structure to the user space representation + * + * The external representation of IAS values, as we exchange them with + * user space program is quite different from the internal representation, + * as stored in the IAS database (because we need a flat structure for + * crossing kernel boundary). + * This function transform the former in the latter. We also check + * that the value type is valid. + */ +static int irda_extract_ias_value(struct irda_ias_set *ias_opt, + struct ias_value *ias_value) +{ + /* Look at the type */ + switch (ias_value->type) { + case IAS_INTEGER: + /* Copy the integer */ + ias_opt->attribute.irda_attrib_int = ias_value->t.integer; + break; + case IAS_OCT_SEQ: + /* Set length */ + ias_opt->attribute.irda_attrib_octet_seq.len = ias_value->len; + /* Copy over */ + memcpy(ias_opt->attribute.irda_attrib_octet_seq.octet_seq, + ias_value->t.oct_seq, ias_value->len); + break; + case IAS_STRING: + /* Set length */ + ias_opt->attribute.irda_attrib_string.len = ias_value->len; + ias_opt->attribute.irda_attrib_string.charset = ias_value->charset; + /* Copy over */ + memcpy(ias_opt->attribute.irda_attrib_string.string, + ias_value->t.string, ias_value->len); + /* NULL terminate the string (avoid troubles) */ + ias_opt->attribute.irda_attrib_string.string[ias_value->len] = '\0'; + break; + case IAS_MISSING: + default : + return -EINVAL; + } + + /* Copy type over */ + ias_opt->irda_attrib_type = ias_value->type; + + return 0; +} + +/* + * Function irda_getsockopt (sock, level, optname, optval, optlen) + */ +static int irda_getsockopt(struct socket *sock, int level, int optname, + char __user *optval, int __user *optlen) +{ + struct sock *sk = sock->sk; + struct irda_sock *self = irda_sk(sk); + struct irda_device_list list; + struct irda_device_info *discoveries; + struct irda_ias_set * ias_opt; /* IAS get/query params */ + struct ias_object * ias_obj; /* Object in IAS */ + struct ias_attrib * ias_attr; /* Attribute in IAS object */ + int daddr = DEV_ADDR_ANY; /* Dest address for IAS queries */ + int val = 0; + int len = 0; + int err; + int offset, total; + + IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self); + + if (level != SOL_IRLMP) + return -ENOPROTOOPT; + + if (get_user(len, optlen)) + return -EFAULT; + + if(len < 0) + return -EINVAL; + + switch (optname) { + case IRLMP_ENUMDEVICES: + /* Ask lmp for the current discovery log */ + discoveries = irlmp_get_discoveries(&list.len, self->mask.word, + self->nslots); + /* Check if the we got some results */ + if (discoveries == NULL) + return -EAGAIN; /* Didn't find any devices */ + err = 0; + + /* Write total list length back to client */ + if (copy_to_user(optval, &list, + sizeof(struct irda_device_list) - + sizeof(struct irda_device_info))) + err = -EFAULT; + + /* Offset to first device entry */ + offset = sizeof(struct irda_device_list) - + sizeof(struct irda_device_info); + + /* Copy the list itself - watch for overflow */ + if(list.len > 2048) + { + err = -EINVAL; + goto bed; + } + total = offset + (list.len * sizeof(struct irda_device_info)); + if (total > len) + total = len; + if (copy_to_user(optval+offset, discoveries, total - offset)) + err = -EFAULT; + + /* Write total number of bytes used back to client */ + if (put_user(total, optlen)) + err = -EFAULT; +bed: + /* Free up our buffer */ + kfree(discoveries); + if (err) + return err; + break; + case IRLMP_MAX_SDU_SIZE: + val = self->max_data_size; + len = sizeof(int); + if (put_user(len, optlen)) + return -EFAULT; + + if (copy_to_user(optval, &val, len)) + return -EFAULT; + break; + case IRLMP_IAS_GET: + /* The user want an object from our local IAS database. + * We just need to query the IAS and return the value + * that we found */ + + /* Check that the user has allocated the right space for us */ + if (len != sizeof(struct irda_ias_set)) + return -EINVAL; + + ias_opt = kmalloc(sizeof(struct irda_ias_set), GFP_ATOMIC); + if (ias_opt == NULL) + return -ENOMEM; + + /* Copy query to the driver. */ + if (copy_from_user(ias_opt, optval, len)) { + kfree(ias_opt); + return -EFAULT; + } + + /* Find the object we target. + * If the user gives us an empty string, we use the object + * associated with this socket. This will workaround + * duplicated class name - Jean II */ + if(ias_opt->irda_class_name[0] == '\0') + ias_obj = self->ias_obj; + else + ias_obj = irias_find_object(ias_opt->irda_class_name); + if(ias_obj == (struct ias_object *) NULL) { + kfree(ias_opt); + return -EINVAL; + } + + /* Find the attribute (in the object) we target */ + ias_attr = irias_find_attrib(ias_obj, + ias_opt->irda_attrib_name); + if(ias_attr == (struct ias_attrib *) NULL) { + kfree(ias_opt); + return -EINVAL; + } + + /* Translate from internal to user structure */ + err = irda_extract_ias_value(ias_opt, ias_attr->value); + if(err) { + kfree(ias_opt); + return err; + } + + /* Copy reply to the user */ + if (copy_to_user(optval, ias_opt, + sizeof(struct irda_ias_set))) { + kfree(ias_opt); + return -EFAULT; + } + /* Note : don't need to put optlen, we checked it */ + kfree(ias_opt); + break; + case IRLMP_IAS_QUERY: + /* The user want an object from a remote IAS database. + * We need to use IAP to query the remote database and + * then wait for the answer to come back. */ + + /* Check that the user has allocated the right space for us */ + if (len != sizeof(struct irda_ias_set)) + return -EINVAL; + + ias_opt = kmalloc(sizeof(struct irda_ias_set), GFP_ATOMIC); + if (ias_opt == NULL) + return -ENOMEM; + + /* Copy query to the driver. */ + if (copy_from_user(ias_opt, optval, len)) { + kfree(ias_opt); + return -EFAULT; + } + + /* At this point, there are two cases... + * 1) the socket is connected - that's the easy case, we + * just query the device we are connected to... + * 2) the socket is not connected - the user doesn't want + * to connect and/or may not have a valid service name + * (so can't create a fake connection). In this case, + * we assume that the user pass us a valid destination + * address in the requesting structure... + */ + if(self->daddr != DEV_ADDR_ANY) { + /* We are connected - reuse known daddr */ + daddr = self->daddr; + } else { + /* We are not connected, we must specify a valid + * destination address */ + daddr = ias_opt->daddr; + if((!daddr) || (daddr == DEV_ADDR_ANY)) { + kfree(ias_opt); + return -EINVAL; + } + } + + /* Check that we can proceed with IAP */ + if (self->iriap) { + IRDA_WARNING("%s: busy with a previous query\n", + __FUNCTION__); + kfree(ias_opt); + return -EBUSY; + } + + self->iriap = iriap_open(LSAP_ANY, IAS_CLIENT, self, + irda_getvalue_confirm); + + if (self->iriap == NULL) { + kfree(ias_opt); + return -ENOMEM; + } + + /* Treat unexpected wakeup as disconnect */ + self->errno = -EHOSTUNREACH; + + /* Query remote LM-IAS */ + iriap_getvaluebyclass_request(self->iriap, + self->saddr, daddr, + ias_opt->irda_class_name, + ias_opt->irda_attrib_name); + + /* Wait for answer, if not yet finished (or failed) */ + if (wait_event_interruptible(self->query_wait, + (self->iriap == NULL))) { + /* pending request uses copy of ias_opt-content + * we can free it regardless! */ + kfree(ias_opt); + /* Treat signals as disconnect */ + return -EHOSTUNREACH; + } + + /* Check what happened */ + if (self->errno) + { + kfree(ias_opt); + /* Requested object/attribute doesn't exist */ + if((self->errno == IAS_CLASS_UNKNOWN) || + (self->errno == IAS_ATTRIB_UNKNOWN)) + return (-EADDRNOTAVAIL); + else + return (-EHOSTUNREACH); + } + + /* Translate from internal to user structure */ + err = irda_extract_ias_value(ias_opt, self->ias_result); + if (self->ias_result) + irias_delete_value(self->ias_result); + if (err) { + kfree(ias_opt); + return err; + } + + /* Copy reply to the user */ + if (copy_to_user(optval, ias_opt, + sizeof(struct irda_ias_set))) { + kfree(ias_opt); + return -EFAULT; + } + /* Note : don't need to put optlen, we checked it */ + kfree(ias_opt); + break; + case IRLMP_WAITDEVICE: + /* This function is just another way of seeing life ;-) + * IRLMP_ENUMDEVICES assumes that you have a static network, + * and that you just want to pick one of the devices present. + * On the other hand, in here we assume that no device is + * present and that at some point in the future a device will + * come into range. When this device arrive, we just wake + * up the caller, so that he has time to connect to it before + * the device goes away... + * Note : once the node has been discovered for more than a + * few second, it won't trigger this function, unless it + * goes away and come back changes its hint bits (so we + * might call it IRLMP_WAITNEWDEVICE). + */ + + /* Check that the user is passing us an int */ + if (len != sizeof(int)) + return -EINVAL; + /* Get timeout in ms (max time we block the caller) */ + if (get_user(val, (int __user *)optval)) + return -EFAULT; + + /* Tell IrLMP we want to be notified */ + irlmp_update_client(self->ckey, self->mask.word, + irda_selective_discovery_indication, + NULL, (void *) self); + + /* Do some discovery (and also return cached results) */ + irlmp_discovery_request(self->nslots); + + /* Wait until a node is discovered */ + if (!self->cachedaddr) { + int ret = 0; + + IRDA_DEBUG(1, "%s(), nothing discovered yet, going to sleep...\n", __FUNCTION__); + + /* Set watchdog timer to expire in <val> ms. */ + self->errno = 0; + init_timer(&self->watchdog); + self->watchdog.function = irda_discovery_timeout; + self->watchdog.data = (unsigned long) self; + self->watchdog.expires = jiffies + (val * HZ/1000); + add_timer(&(self->watchdog)); + + /* Wait for IR-LMP to call us back */ + __wait_event_interruptible(self->query_wait, + (self->cachedaddr != 0 || self->errno == -ETIME), + ret); + + /* If watchdog is still activated, kill it! */ + if(timer_pending(&(self->watchdog))) + del_timer(&(self->watchdog)); + + IRDA_DEBUG(1, "%s(), ...waking up !\n", __FUNCTION__); + + if (ret != 0) + return ret; + } + else + IRDA_DEBUG(1, "%s(), found immediately !\n", + __FUNCTION__); + + /* Tell IrLMP that we have been notified */ + irlmp_update_client(self->ckey, self->mask.word, + NULL, NULL, NULL); + + /* Check if the we got some results */ + if (!self->cachedaddr) + return -EAGAIN; /* Didn't find any devices */ + daddr = self->cachedaddr; + /* Cleanup */ + self->cachedaddr = 0; + + /* We return the daddr of the device that trigger the + * wakeup. As irlmp pass us only the new devices, we + * are sure that it's not an old device. + * If the user want more details, he should query + * the whole discovery log and pick one device... + */ + if (put_user(daddr, (int __user *)optval)) + return -EFAULT; + + break; + default: + return -ENOPROTOOPT; + } + + return 0; +} + +static struct net_proto_family irda_family_ops = { + .family = PF_IRDA, + .create = irda_create, + .owner = THIS_MODULE, +}; + +static struct proto_ops SOCKOPS_WRAPPED(irda_stream_ops) = { + .family = PF_IRDA, + .owner = THIS_MODULE, + .release = irda_release, + .bind = irda_bind, + .connect = irda_connect, + .socketpair = sock_no_socketpair, + .accept = irda_accept, + .getname = irda_getname, + .poll = irda_poll, + .ioctl = irda_ioctl, + .listen = irda_listen, + .shutdown = irda_shutdown, + .setsockopt = irda_setsockopt, + .getsockopt = irda_getsockopt, + .sendmsg = irda_sendmsg, + .recvmsg = irda_recvmsg_stream, + .mmap = sock_no_mmap, + .sendpage = sock_no_sendpage, +}; + +static struct proto_ops SOCKOPS_WRAPPED(irda_seqpacket_ops) = { + .family = PF_IRDA, + .owner = THIS_MODULE, + .release = irda_release, + .bind = irda_bind, + .connect = irda_connect, + .socketpair = sock_no_socketpair, + .accept = irda_accept, + .getname = irda_getname, + .poll = datagram_poll, + .ioctl = irda_ioctl, + .listen = irda_listen, + .shutdown = irda_shutdown, + .setsockopt = irda_setsockopt, + .getsockopt = irda_getsockopt, + .sendmsg = irda_sendmsg, + .recvmsg = irda_recvmsg_dgram, + .mmap = sock_no_mmap, + .sendpage = sock_no_sendpage, +}; + +static struct proto_ops SOCKOPS_WRAPPED(irda_dgram_ops) = { + .family = PF_IRDA, + .owner = THIS_MODULE, + .release = irda_release, + .bind = irda_bind, + .connect = irda_connect, + .socketpair = sock_no_socketpair, + .accept = irda_accept, + .getname = irda_getname, + .poll = datagram_poll, + .ioctl = irda_ioctl, + .listen = irda_listen, + .shutdown = irda_shutdown, + .setsockopt = irda_setsockopt, + .getsockopt = irda_getsockopt, + .sendmsg = irda_sendmsg_dgram, + .recvmsg = irda_recvmsg_dgram, + .mmap = sock_no_mmap, + .sendpage = sock_no_sendpage, +}; + +#ifdef CONFIG_IRDA_ULTRA +static struct proto_ops SOCKOPS_WRAPPED(irda_ultra_ops) = { + .family = PF_IRDA, + .owner = THIS_MODULE, + .release = irda_release, + .bind = irda_bind, + .connect = sock_no_connect, + .socketpair = sock_no_socketpair, + .accept = sock_no_accept, + .getname = irda_getname, + .poll = datagram_poll, + .ioctl = irda_ioctl, + .listen = sock_no_listen, + .shutdown = irda_shutdown, + .setsockopt = irda_setsockopt, + .getsockopt = irda_getsockopt, + .sendmsg = irda_sendmsg_ultra, + .recvmsg = irda_recvmsg_dgram, + .mmap = sock_no_mmap, + .sendpage = sock_no_sendpage, +}; +#endif /* CONFIG_IRDA_ULTRA */ + +#include <linux/smp_lock.h> +SOCKOPS_WRAP(irda_stream, PF_IRDA); +SOCKOPS_WRAP(irda_seqpacket, PF_IRDA); +SOCKOPS_WRAP(irda_dgram, PF_IRDA); +#ifdef CONFIG_IRDA_ULTRA +SOCKOPS_WRAP(irda_ultra, PF_IRDA); +#endif /* CONFIG_IRDA_ULTRA */ + +/* + * Function irsock_init (pro) + * + * Initialize IrDA protocol + * + */ +int __init irsock_init(void) +{ + int rc = proto_register(&irda_proto, 0); + + if (rc == 0) + rc = sock_register(&irda_family_ops); + + return rc; +} + +/* + * Function irsock_cleanup (void) + * + * Remove IrDA protocol + * + */ +void __exit irsock_cleanup(void) +{ + sock_unregister(PF_IRDA); + proto_unregister(&irda_proto); +} diff --git a/net/irda/discovery.c b/net/irda/discovery.c new file mode 100644 index 000000000000..c4ba5fa1446a --- /dev/null +++ b/net/irda/discovery.c @@ -0,0 +1,419 @@ +/********************************************************************* + * + * Filename: discovery.c + * Version: 0.1 + * Description: Routines for handling discoveries at the IrLMP layer + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Tue Apr 6 15:33:50 1999 + * Modified at: Sat Oct 9 17:11:31 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * Modified at: Fri May 28 3:11 CST 1999 + * Modified by: Horst von Brand <vonbrand@sleipnir.valparaiso.cl> + * + * Copyright (c) 1999 Dag Brattli, 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + ********************************************************************/ + +#include <linux/string.h> +#include <linux/socket.h> +#include <linux/seq_file.h> + +#include <net/irda/irda.h> +#include <net/irda/irlmp.h> + +#include <net/irda/discovery.h> + +/* + * Function irlmp_add_discovery (cachelog, discovery) + * + * Add a new discovery to the cachelog, and remove any old discoveries + * from the same device + * + * Note : we try to preserve the time this device was *first* discovered + * (as opposed to the time of last discovery used for cleanup). This is + * used by clients waiting for discovery events to tell if the device + * discovered is "new" or just the same old one. They can't rely there + * on a binary flag (new/old), because not all discovery events are + * propagated to them, and they might not always listen, so they would + * miss some new devices popping up... + * Jean II + */ +void irlmp_add_discovery(hashbin_t *cachelog, discovery_t *new) +{ + discovery_t *discovery, *node; + unsigned long flags; + + /* Set time of first discovery if node is new (see below) */ + new->firststamp = new->timestamp; + + spin_lock_irqsave(&cachelog->hb_spinlock, flags); + + /* + * Remove all discoveries of devices that has previously been + * discovered on the same link with the same name (info), or the + * same daddr. We do this since some devices (mostly PDAs) change + * their device address between every discovery. + */ + discovery = (discovery_t *) hashbin_get_first(cachelog); + while (discovery != NULL ) { + node = discovery; + + /* Be sure to stay one item ahead */ + discovery = (discovery_t *) hashbin_get_next(cachelog); + + if ((node->data.saddr == new->data.saddr) && + ((node->data.daddr == new->data.daddr) || + (strcmp(node->data.info, new->data.info) == 0))) + { + /* This discovery is a previous discovery + * from the same device, so just remove it + */ + hashbin_remove_this(cachelog, (irda_queue_t *) node); + /* Check if hints bits are unchanged */ + if(u16ho(node->data.hints) == u16ho(new->data.hints)) + /* Set time of first discovery for this node */ + new->firststamp = node->firststamp; + kfree(node); + } + } + + /* Insert the new and updated version */ + hashbin_insert(cachelog, (irda_queue_t *) new, new->data.daddr, NULL); + + spin_unlock_irqrestore(&cachelog->hb_spinlock, flags); +} + +/* + * Function irlmp_add_discovery_log (cachelog, log) + * + * Merge a disovery log into the cachelog. + * + */ +void irlmp_add_discovery_log(hashbin_t *cachelog, hashbin_t *log) +{ + discovery_t *discovery; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + /* + * If log is missing this means that IrLAP was unable to perform the + * discovery, so restart discovery again with just the half timeout + * of the normal one. + */ + /* Well... It means that there was nobody out there - Jean II */ + if (log == NULL) { + /* irlmp_start_discovery_timer(irlmp, 150); */ + return; + } + + /* + * Locking : we are the only owner of this discovery log, so + * no need to lock it. + * We just need to lock the global log in irlmp_add_discovery(). + */ + discovery = (discovery_t *) hashbin_remove_first(log); + while (discovery != NULL) { + irlmp_add_discovery(cachelog, discovery); + + discovery = (discovery_t *) hashbin_remove_first(log); + } + + /* Delete the now empty log */ + hashbin_delete(log, (FREE_FUNC) kfree); +} + +/* + * Function irlmp_expire_discoveries (log, saddr, force) + * + * Go through all discoveries and expire all that has stayed too long + * + * Note : this assume that IrLAP won't change its saddr, which + * currently is a valid assumption... + */ +void irlmp_expire_discoveries(hashbin_t *log, __u32 saddr, int force) +{ + discovery_t * discovery; + discovery_t * curr; + unsigned long flags; + discinfo_t * buffer = NULL; + int n; /* Size of the full log */ + int i = 0; /* How many we expired */ + + IRDA_ASSERT(log != NULL, return;); + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + spin_lock_irqsave(&log->hb_spinlock, flags); + + discovery = (discovery_t *) hashbin_get_first(log); + while (discovery != NULL) { + /* Be sure to be one item ahead */ + curr = discovery; + discovery = (discovery_t *) hashbin_get_next(log); + + /* Test if it's time to expire this discovery */ + if ((curr->data.saddr == saddr) && + (force || + ((jiffies - curr->timestamp) > DISCOVERY_EXPIRE_TIMEOUT))) + { + /* Create buffer as needed. + * As this function get called a lot and most time + * we don't have anything to put in the log (we are + * quite picky), we can save a lot of overhead + * by not calling kmalloc. Jean II */ + if(buffer == NULL) { + /* Create the client specific buffer */ + n = HASHBIN_GET_SIZE(log); + buffer = kmalloc(n * sizeof(struct irda_device_info), GFP_ATOMIC); + if (buffer == NULL) { + spin_unlock_irqrestore(&log->hb_spinlock, flags); + return; + } + + } + + /* Copy discovery information */ + memcpy(&(buffer[i]), &(curr->data), + sizeof(discinfo_t)); + i++; + + /* Remove it from the log */ + curr = hashbin_remove_this(log, (irda_queue_t *) curr); + if (curr) + kfree(curr); + } + } + + /* Drop the spinlock before calling the higher layers, as + * we can't guarantee they won't call us back and create a + * deadlock. We will work on our own private data, so we + * don't care to be interupted. - Jean II */ + spin_unlock_irqrestore(&log->hb_spinlock, flags); + + if(buffer == NULL) + return; + + /* Tell IrLMP and registered clients about it */ + irlmp_discovery_expiry(buffer, i); + + /* Free up our buffer */ + kfree(buffer); +} + +#if 0 +/* + * Function irlmp_dump_discoveries (log) + * + * Print out all discoveries in log + * + */ +void irlmp_dump_discoveries(hashbin_t *log) +{ + discovery_t *discovery; + + IRDA_ASSERT(log != NULL, return;); + + discovery = (discovery_t *) hashbin_get_first(log); + while (discovery != NULL) { + IRDA_DEBUG(0, "Discovery:\n"); + IRDA_DEBUG(0, " daddr=%08x\n", discovery->data.daddr); + IRDA_DEBUG(0, " saddr=%08x\n", discovery->data.saddr); + IRDA_DEBUG(0, " nickname=%s\n", discovery->data.info); + + discovery = (discovery_t *) hashbin_get_next(log); + } +} +#endif + +/* + * Function irlmp_copy_discoveries (log, pn, mask) + * + * Copy all discoveries in a buffer + * + * This function implement a safe way for lmp clients to access the + * discovery log. The basic problem is that we don't want the log + * to change (add/remove) while the client is reading it. If the + * lmp client manipulate directly the hashbin, he is sure to get + * into troubles... + * The idea is that we copy all the current discovery log in a buffer + * which is specific to the client and pass this copy to him. As we + * do this operation with the spinlock grabbed, we are safe... + * Note : we don't want those clients to grab the spinlock, because + * we have no control on how long they will hold it... + * Note : we choose to copy the log in "struct irda_device_info" to + * save space... + * Note : the client must kfree himself() the log... + * Jean II + */ +struct irda_device_info *irlmp_copy_discoveries(hashbin_t *log, int *pn, + __u16 mask, int old_entries) +{ + discovery_t * discovery; + unsigned long flags; + discinfo_t * buffer = NULL; + int j_timeout = (sysctl_discovery_timeout * HZ); + int n; /* Size of the full log */ + int i = 0; /* How many we picked */ + + IRDA_ASSERT(pn != NULL, return NULL;); + IRDA_ASSERT(log != NULL, return NULL;); + + /* Save spin lock */ + spin_lock_irqsave(&log->hb_spinlock, flags); + + discovery = (discovery_t *) hashbin_get_first(log); + while (discovery != NULL) { + /* Mask out the ones we don't want : + * We want to match the discovery mask, and to get only + * the most recent one (unless we want old ones) */ + if ((u16ho(discovery->data.hints) & mask) && + ((old_entries) || + ((jiffies - discovery->firststamp) < j_timeout)) ) { + /* Create buffer as needed. + * As this function get called a lot and most time + * we don't have anything to put in the log (we are + * quite picky), we can save a lot of overhead + * by not calling kmalloc. Jean II */ + if(buffer == NULL) { + /* Create the client specific buffer */ + n = HASHBIN_GET_SIZE(log); + buffer = kmalloc(n * sizeof(struct irda_device_info), GFP_ATOMIC); + if (buffer == NULL) { + spin_unlock_irqrestore(&log->hb_spinlock, flags); + return NULL; + } + + } + + /* Copy discovery information */ + memcpy(&(buffer[i]), &(discovery->data), + sizeof(discinfo_t)); + i++; + } + discovery = (discovery_t *) hashbin_get_next(log); + } + + spin_unlock_irqrestore(&log->hb_spinlock, flags); + + /* Get the actual number of device in the buffer and return */ + *pn = i; + return(buffer); +} + +#ifdef CONFIG_PROC_FS +static inline discovery_t *discovery_seq_idx(loff_t pos) + +{ + discovery_t *discovery; + + for (discovery = (discovery_t *) hashbin_get_first(irlmp->cachelog); + discovery != NULL; + discovery = (discovery_t *) hashbin_get_next(irlmp->cachelog)) { + if (pos-- == 0) + break; + } + + return discovery; +} + +static void *discovery_seq_start(struct seq_file *seq, loff_t *pos) +{ + spin_lock_irq(&irlmp->cachelog->hb_spinlock); + return *pos ? discovery_seq_idx(*pos - 1) : SEQ_START_TOKEN; +} + +static void *discovery_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + ++*pos; + return (v == SEQ_START_TOKEN) + ? (void *) hashbin_get_first(irlmp->cachelog) + : (void *) hashbin_get_next(irlmp->cachelog); +} + +static void discovery_seq_stop(struct seq_file *seq, void *v) +{ + spin_unlock_irq(&irlmp->cachelog->hb_spinlock); +} + +static int discovery_seq_show(struct seq_file *seq, void *v) +{ + if (v == SEQ_START_TOKEN) + seq_puts(seq, "IrLMP: Discovery log:\n\n"); + else { + const discovery_t *discovery = v; + + seq_printf(seq, "nickname: %s, hint: 0x%02x%02x", + discovery->data.info, + discovery->data.hints[0], + discovery->data.hints[1]); +#if 0 + if ( discovery->data.hints[0] & HINT_PNP) + seq_puts(seq, "PnP Compatible "); + if ( discovery->data.hints[0] & HINT_PDA) + seq_puts(seq, "PDA/Palmtop "); + if ( discovery->data.hints[0] & HINT_COMPUTER) + seq_puts(seq, "Computer "); + if ( discovery->data.hints[0] & HINT_PRINTER) + seq_puts(seq, "Printer "); + if ( discovery->data.hints[0] & HINT_MODEM) + seq_puts(seq, "Modem "); + if ( discovery->data.hints[0] & HINT_FAX) + seq_puts(seq, "Fax "); + if ( discovery->data.hints[0] & HINT_LAN) + seq_puts(seq, "LAN Access "); + + if ( discovery->data.hints[1] & HINT_TELEPHONY) + seq_puts(seq, "Telephony "); + if ( discovery->data.hints[1] & HINT_FILE_SERVER) + seq_puts(seq, "File Server "); + if ( discovery->data.hints[1] & HINT_COMM) + seq_puts(seq, "IrCOMM "); + if ( discovery->data.hints[1] & HINT_OBEX) + seq_puts(seq, "IrOBEX "); +#endif + seq_printf(seq,", saddr: 0x%08x, daddr: 0x%08x\n\n", + discovery->data.saddr, + discovery->data.daddr); + + seq_putc(seq, '\n'); + } + return 0; +} + +static struct seq_operations discovery_seq_ops = { + .start = discovery_seq_start, + .next = discovery_seq_next, + .stop = discovery_seq_stop, + .show = discovery_seq_show, +}; + +static int discovery_seq_open(struct inode *inode, struct file *file) +{ + IRDA_ASSERT(irlmp != NULL, return -EINVAL;); + + return seq_open(file, &discovery_seq_ops); +} + +struct file_operations discovery_seq_fops = { + .owner = THIS_MODULE, + .open = discovery_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; +#endif diff --git a/net/irda/ircomm/Kconfig b/net/irda/ircomm/Kconfig new file mode 100644 index 000000000000..2d4c6b4a78d6 --- /dev/null +++ b/net/irda/ircomm/Kconfig @@ -0,0 +1,12 @@ +config IRCOMM + tristate "IrCOMM protocol" + depends on IRDA + help + Say Y here if you want to build support for the IrCOMM protocol. + To compile it as modules, choose M here: the modules will be + called ircomm and ircomm_tty. + IrCOMM implements serial port emulation, and makes it possible to + use all existing applications that understands TTY's with an + infrared link. Thus you should be able to use application like PPP, + minicom and others. + diff --git a/net/irda/ircomm/Makefile b/net/irda/ircomm/Makefile new file mode 100644 index 000000000000..48689458c086 --- /dev/null +++ b/net/irda/ircomm/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the Linux IrDA IrCOMM protocol layer. +# + +obj-$(CONFIG_IRCOMM) += ircomm.o ircomm-tty.o + +ircomm-objs := ircomm_core.o ircomm_event.o ircomm_lmp.o ircomm_ttp.o +ircomm-tty-objs := ircomm_tty.o ircomm_tty_attach.o ircomm_tty_ioctl.o ircomm_param.o diff --git a/net/irda/ircomm/ircomm_core.c b/net/irda/ircomm/ircomm_core.c new file mode 100644 index 000000000000..286881978858 --- /dev/null +++ b/net/irda/ircomm/ircomm_core.c @@ -0,0 +1,587 @@ +/********************************************************************* + * + * Filename: ircomm_core.c + * Version: 1.0 + * Description: IrCOMM service interface + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sun Jun 6 20:37:34 1999 + * Modified at: Tue Dec 21 13:26:41 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1999 Dag Brattli, All Rights Reserved. + * Copyright (c) 2000-2003 Jean Tourrilhes <jt@hpl.hp.com> + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + ********************************************************************/ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/init.h> + +#include <net/irda/irda.h> +#include <net/irda/irmod.h> +#include <net/irda/irlmp.h> +#include <net/irda/iriap.h> +#include <net/irda/irttp.h> +#include <net/irda/irias_object.h> + +#include <net/irda/ircomm_event.h> +#include <net/irda/ircomm_lmp.h> +#include <net/irda/ircomm_ttp.h> +#include <net/irda/ircomm_param.h> +#include <net/irda/ircomm_core.h> + +static int __ircomm_close(struct ircomm_cb *self); +static void ircomm_control_indication(struct ircomm_cb *self, + struct sk_buff *skb, int clen); + +#ifdef CONFIG_PROC_FS +extern struct proc_dir_entry *proc_irda; +static int ircomm_seq_open(struct inode *, struct file *); + +static struct file_operations ircomm_proc_fops = { + .owner = THIS_MODULE, + .open = ircomm_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; +#endif /* CONFIG_PROC_FS */ + +hashbin_t *ircomm = NULL; + +static int __init ircomm_init(void) +{ + ircomm = hashbin_new(HB_LOCK); + if (ircomm == NULL) { + IRDA_ERROR("%s(), can't allocate hashbin!\n", __FUNCTION__); + return -ENOMEM; + } + +#ifdef CONFIG_PROC_FS + { struct proc_dir_entry *ent; + ent = create_proc_entry("ircomm", 0, proc_irda); + if (ent) + ent->proc_fops = &ircomm_proc_fops; + } +#endif /* CONFIG_PROC_FS */ + + IRDA_MESSAGE("IrCOMM protocol (Dag Brattli)\n"); + + return 0; +} + +static void __exit ircomm_cleanup(void) +{ + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + hashbin_delete(ircomm, (FREE_FUNC) __ircomm_close); + +#ifdef CONFIG_PROC_FS + remove_proc_entry("ircomm", proc_irda); +#endif /* CONFIG_PROC_FS */ +} + +/* + * Function ircomm_open (client_notify) + * + * Start a new IrCOMM instance + * + */ +struct ircomm_cb *ircomm_open(notify_t *notify, __u8 service_type, int line) +{ + struct ircomm_cb *self = NULL; + int ret; + + IRDA_DEBUG(2, "%s(), service_type=0x%02x\n", __FUNCTION__ , + service_type); + + IRDA_ASSERT(ircomm != NULL, return NULL;); + + self = kmalloc(sizeof(struct ircomm_cb), GFP_ATOMIC); + if (self == NULL) + return NULL; + + memset(self, 0, sizeof(struct ircomm_cb)); + + self->notify = *notify; + self->magic = IRCOMM_MAGIC; + + /* Check if we should use IrLMP or IrTTP */ + if (service_type & IRCOMM_3_WIRE_RAW) { + self->flow_status = FLOW_START; + ret = ircomm_open_lsap(self); + } else + ret = ircomm_open_tsap(self); + + if (ret < 0) { + kfree(self); + return NULL; + } + + self->service_type = service_type; + self->line = line; + + hashbin_insert(ircomm, (irda_queue_t *) self, line, NULL); + + ircomm_next_state(self, IRCOMM_IDLE); + + return self; +} + +EXPORT_SYMBOL(ircomm_open); + +/* + * Function ircomm_close_instance (self) + * + * Remove IrCOMM instance + * + */ +static int __ircomm_close(struct ircomm_cb *self) +{ + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + /* Disconnect link if any */ + ircomm_do_event(self, IRCOMM_DISCONNECT_REQUEST, NULL, NULL); + + /* Remove TSAP */ + if (self->tsap) { + irttp_close_tsap(self->tsap); + self->tsap = NULL; + } + + /* Remove LSAP */ + if (self->lsap) { + irlmp_close_lsap(self->lsap); + self->lsap = NULL; + } + self->magic = 0; + + kfree(self); + + return 0; +} + +/* + * Function ircomm_close (self) + * + * Closes and removes the specified IrCOMM instance + * + */ +int ircomm_close(struct ircomm_cb *self) +{ + struct ircomm_cb *entry; + + IRDA_ASSERT(self != NULL, return -EIO;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return -EIO;); + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + entry = hashbin_remove(ircomm, self->line, NULL); + + IRDA_ASSERT(entry == self, return -1;); + + return __ircomm_close(self); +} + +EXPORT_SYMBOL(ircomm_close); + +/* + * Function ircomm_connect_request (self, service_type) + * + * Impl. of this function is differ from one of the reference. This + * function does discovery as well as sending connect request + * + */ +int ircomm_connect_request(struct ircomm_cb *self, __u8 dlsap_sel, + __u32 saddr, __u32 daddr, struct sk_buff *skb, + __u8 service_type) +{ + struct ircomm_info info; + int ret; + + IRDA_DEBUG(2 , "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return -1;); + + self->service_type= service_type; + + info.dlsap_sel = dlsap_sel; + info.saddr = saddr; + info.daddr = daddr; + + ret = ircomm_do_event(self, IRCOMM_CONNECT_REQUEST, skb, &info); + + return ret; +} + +EXPORT_SYMBOL(ircomm_connect_request); + +/* + * Function ircomm_connect_indication (self, qos, skb) + * + * Notify user layer about the incoming connection + * + */ +void ircomm_connect_indication(struct ircomm_cb *self, struct sk_buff *skb, + struct ircomm_info *info) +{ + int clen = 0; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + /* Check if the packet contains data on the control channel */ + if (skb->len > 0) + clen = skb->data[0]; + + /* + * If there are any data hiding in the control channel, we must + * deliver it first. The side effect is that the control channel + * will be removed from the skb + */ + if (self->notify.connect_indication) + self->notify.connect_indication(self->notify.instance, self, + info->qos, info->max_data_size, + info->max_header_size, skb); + else { + IRDA_DEBUG(0, "%s(), missing handler\n", __FUNCTION__ ); + } +} + +/* + * Function ircomm_connect_response (self, userdata, max_sdu_size) + * + * User accepts connection + * + */ +int ircomm_connect_response(struct ircomm_cb *self, struct sk_buff *userdata) +{ + int ret; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return -1;); + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + ret = ircomm_do_event(self, IRCOMM_CONNECT_RESPONSE, userdata, NULL); + + return ret; +} + +EXPORT_SYMBOL(ircomm_connect_response); + +/* + * Function connect_confirm (self, skb) + * + * Notify user layer that the link is now connected + * + */ +void ircomm_connect_confirm(struct ircomm_cb *self, struct sk_buff *skb, + struct ircomm_info *info) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + if (self->notify.connect_confirm ) + self->notify.connect_confirm(self->notify.instance, + self, info->qos, + info->max_data_size, + info->max_header_size, skb); + else { + IRDA_DEBUG(0, "%s(), missing handler\n", __FUNCTION__ ); + } +} + +/* + * Function ircomm_data_request (self, userdata) + * + * Send IrCOMM data to peer device + * + */ +int ircomm_data_request(struct ircomm_cb *self, struct sk_buff *skb) +{ + int ret; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -EFAULT;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return -EFAULT;); + IRDA_ASSERT(skb != NULL, return -EFAULT;); + + ret = ircomm_do_event(self, IRCOMM_DATA_REQUEST, skb, NULL); + + return ret; +} + +EXPORT_SYMBOL(ircomm_data_request); + +/* + * Function ircomm_data_indication (self, skb) + * + * Data arrived, so deliver it to user + * + */ +void ircomm_data_indication(struct ircomm_cb *self, struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(skb->len > 0, return;); + + if (self->notify.data_indication) + self->notify.data_indication(self->notify.instance, self, skb); + else { + IRDA_DEBUG(0, "%s(), missing handler\n", __FUNCTION__ ); + } +} + +/* + * Function ircomm_process_data (self, skb) + * + * Data arrived which may contain control channel data + * + */ +void ircomm_process_data(struct ircomm_cb *self, struct sk_buff *skb) +{ + int clen; + + IRDA_ASSERT(skb->len > 0, return;); + + clen = skb->data[0]; + + /* + * If there are any data hiding in the control channel, we must + * deliver it first. The side effect is that the control channel + * will be removed from the skb + */ + if (clen > 0) + ircomm_control_indication(self, skb, clen); + + /* Remove control channel from data channel */ + skb_pull(skb, clen+1); + + if (skb->len) + ircomm_data_indication(self, skb); + else { + IRDA_DEBUG(4, "%s(), data was control info only!\n", + __FUNCTION__ ); + } +} + +/* + * Function ircomm_control_request (self, params) + * + * Send control data to peer device + * + */ +int ircomm_control_request(struct ircomm_cb *self, struct sk_buff *skb) +{ + int ret; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -EFAULT;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return -EFAULT;); + IRDA_ASSERT(skb != NULL, return -EFAULT;); + + ret = ircomm_do_event(self, IRCOMM_CONTROL_REQUEST, skb, NULL); + + return ret; +} + +EXPORT_SYMBOL(ircomm_control_request); + +/* + * Function ircomm_control_indication (self, skb) + * + * Data has arrived on the control channel + * + */ +static void ircomm_control_indication(struct ircomm_cb *self, + struct sk_buff *skb, int clen) +{ + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + /* Use udata for delivering data on the control channel */ + if (self->notify.udata_indication) { + struct sk_buff *ctrl_skb; + + /* We don't own the skb, so clone it */ + ctrl_skb = skb_clone(skb, GFP_ATOMIC); + if (!ctrl_skb) + return; + + /* Remove data channel from control channel */ + skb_trim(ctrl_skb, clen+1); + + self->notify.udata_indication(self->notify.instance, self, + ctrl_skb); + + /* Drop reference count - + * see ircomm_tty_control_indication(). */ + dev_kfree_skb(ctrl_skb); + } else { + IRDA_DEBUG(0, "%s(), missing handler\n", __FUNCTION__ ); + } +} + +/* + * Function ircomm_disconnect_request (self, userdata, priority) + * + * User layer wants to disconnect the IrCOMM connection + * + */ +int ircomm_disconnect_request(struct ircomm_cb *self, struct sk_buff *userdata) +{ + struct ircomm_info info; + int ret; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return -1;); + + ret = ircomm_do_event(self, IRCOMM_DISCONNECT_REQUEST, userdata, + &info); + return ret; +} + +EXPORT_SYMBOL(ircomm_disconnect_request); + +/* + * Function disconnect_indication (self, skb) + * + * Tell user that the link has been disconnected + * + */ +void ircomm_disconnect_indication(struct ircomm_cb *self, struct sk_buff *skb, + struct ircomm_info *info) +{ + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(info != NULL, return;); + + if (self->notify.disconnect_indication) { + self->notify.disconnect_indication(self->notify.instance, self, + info->reason, skb); + } else { + IRDA_DEBUG(0, "%s(), missing handler\n", __FUNCTION__ ); + } +} + +/* + * Function ircomm_flow_request (self, flow) + * + * + * + */ +void ircomm_flow_request(struct ircomm_cb *self, LOCAL_FLOW flow) +{ + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return;); + + if (self->service_type == IRCOMM_3_WIRE_RAW) + return; + + irttp_flow_request(self->tsap, flow); +} + +EXPORT_SYMBOL(ircomm_flow_request); + +#ifdef CONFIG_PROC_FS +static void *ircomm_seq_start(struct seq_file *seq, loff_t *pos) +{ + struct ircomm_cb *self; + loff_t off = 0; + + spin_lock_irq(&ircomm->hb_spinlock); + + for (self = (struct ircomm_cb *) hashbin_get_first(ircomm); + self != NULL; + self = (struct ircomm_cb *) hashbin_get_next(ircomm)) { + if (off++ == *pos) + break; + + } + return self; +} + +static void *ircomm_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + ++*pos; + + return (void *) hashbin_get_next(ircomm); +} + +static void ircomm_seq_stop(struct seq_file *seq, void *v) +{ + spin_unlock_irq(&ircomm->hb_spinlock); +} + +static int ircomm_seq_show(struct seq_file *seq, void *v) +{ + const struct ircomm_cb *self = v; + + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return -EINVAL; ); + + if(self->line < 0x10) + seq_printf(seq, "ircomm%d", self->line); + else + seq_printf(seq, "irlpt%d", self->line - 0x10); + + seq_printf(seq, + " state: %s, slsap_sel: %#02x, dlsap_sel: %#02x, mode:", + ircomm_state[ self->state], + self->slsap_sel, self->dlsap_sel); + + if(self->service_type & IRCOMM_3_WIRE_RAW) + seq_printf(seq, " 3-wire-raw"); + if(self->service_type & IRCOMM_3_WIRE) + seq_printf(seq, " 3-wire"); + if(self->service_type & IRCOMM_9_WIRE) + seq_printf(seq, " 9-wire"); + if(self->service_type & IRCOMM_CENTRONICS) + seq_printf(seq, " Centronics"); + seq_putc(seq, '\n'); + + return 0; +} + +static struct seq_operations ircomm_seq_ops = { + .start = ircomm_seq_start, + .next = ircomm_seq_next, + .stop = ircomm_seq_stop, + .show = ircomm_seq_show, +}; + +static int ircomm_seq_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &ircomm_seq_ops); +} +#endif /* CONFIG_PROC_FS */ + +MODULE_AUTHOR("Dag Brattli <dag@brattli.net>"); +MODULE_DESCRIPTION("IrCOMM protocol"); +MODULE_LICENSE("GPL"); + +module_init(ircomm_init); +module_exit(ircomm_cleanup); diff --git a/net/irda/ircomm/ircomm_event.c b/net/irda/ircomm/ircomm_event.c new file mode 100644 index 000000000000..01f4e801a1ba --- /dev/null +++ b/net/irda/ircomm/ircomm_event.c @@ -0,0 +1,251 @@ +/********************************************************************* + * + * Filename: ircomm_event.c + * Version: 1.0 + * Description: IrCOMM layer state machine + * Status: Stable + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sun Jun 6 20:33:11 1999 + * Modified at: Sun Dec 12 13:44:32 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1999 Dag Brattli, 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + ********************************************************************/ + +#include <linux/sched.h> +#include <linux/proc_fs.h> +#include <linux/init.h> + +#include <net/irda/irda.h> +#include <net/irda/irlmp.h> +#include <net/irda/iriap.h> +#include <net/irda/irttp.h> +#include <net/irda/irias_object.h> + +#include <net/irda/ircomm_core.h> +#include <net/irda/ircomm_event.h> + +static int ircomm_state_idle(struct ircomm_cb *self, IRCOMM_EVENT event, + struct sk_buff *skb, struct ircomm_info *info); +static int ircomm_state_waiti(struct ircomm_cb *self, IRCOMM_EVENT event, + struct sk_buff *skb, struct ircomm_info *info); +static int ircomm_state_waitr(struct ircomm_cb *self, IRCOMM_EVENT event, + struct sk_buff *skb, struct ircomm_info *info); +static int ircomm_state_conn(struct ircomm_cb *self, IRCOMM_EVENT event, + struct sk_buff *skb, struct ircomm_info *info); + +char *ircomm_state[] = { + "IRCOMM_IDLE", + "IRCOMM_WAITI", + "IRCOMM_WAITR", + "IRCOMM_CONN", +}; + +#ifdef CONFIG_IRDA_DEBUG +static char *ircomm_event[] = { + "IRCOMM_CONNECT_REQUEST", + "IRCOMM_CONNECT_RESPONSE", + "IRCOMM_TTP_CONNECT_INDICATION", + "IRCOMM_LMP_CONNECT_INDICATION", + "IRCOMM_TTP_CONNECT_CONFIRM", + "IRCOMM_LMP_CONNECT_CONFIRM", + + "IRCOMM_LMP_DISCONNECT_INDICATION", + "IRCOMM_TTP_DISCONNECT_INDICATION", + "IRCOMM_DISCONNECT_REQUEST", + + "IRCOMM_TTP_DATA_INDICATION", + "IRCOMM_LMP_DATA_INDICATION", + "IRCOMM_DATA_REQUEST", + "IRCOMM_CONTROL_REQUEST", + "IRCOMM_CONTROL_INDICATION", +}; +#endif /* CONFIG_IRDA_DEBUG */ + +static int (*state[])(struct ircomm_cb *self, IRCOMM_EVENT event, + struct sk_buff *skb, struct ircomm_info *info) = +{ + ircomm_state_idle, + ircomm_state_waiti, + ircomm_state_waitr, + ircomm_state_conn, +}; + +/* + * Function ircomm_state_idle (self, event, skb) + * + * IrCOMM is currently idle + * + */ +static int ircomm_state_idle(struct ircomm_cb *self, IRCOMM_EVENT event, + struct sk_buff *skb, struct ircomm_info *info) +{ + int ret = 0; + + switch (event) { + case IRCOMM_CONNECT_REQUEST: + ircomm_next_state(self, IRCOMM_WAITI); + ret = self->issue.connect_request(self, skb, info); + break; + case IRCOMM_TTP_CONNECT_INDICATION: + case IRCOMM_LMP_CONNECT_INDICATION: + ircomm_next_state(self, IRCOMM_WAITR); + ircomm_connect_indication(self, skb, info); + break; + default: + IRDA_DEBUG(4, "%s(), unknown event: %s\n", __FUNCTION__ , + ircomm_event[event]); + ret = -EINVAL; + } + return ret; +} + +/* + * Function ircomm_state_waiti (self, event, skb) + * + * The IrCOMM user has requested an IrCOMM connection to the remote + * device and is awaiting confirmation + */ +static int ircomm_state_waiti(struct ircomm_cb *self, IRCOMM_EVENT event, + struct sk_buff *skb, struct ircomm_info *info) +{ + int ret = 0; + + switch (event) { + case IRCOMM_TTP_CONNECT_CONFIRM: + case IRCOMM_LMP_CONNECT_CONFIRM: + ircomm_next_state(self, IRCOMM_CONN); + ircomm_connect_confirm(self, skb, info); + break; + case IRCOMM_TTP_DISCONNECT_INDICATION: + case IRCOMM_LMP_DISCONNECT_INDICATION: + ircomm_next_state(self, IRCOMM_IDLE); + ircomm_disconnect_indication(self, skb, info); + break; + default: + IRDA_DEBUG(0, "%s(), unknown event: %s\n", __FUNCTION__ , + ircomm_event[event]); + ret = -EINVAL; + } + return ret; +} + +/* + * Function ircomm_state_waitr (self, event, skb) + * + * IrCOMM has received an incoming connection request and is awaiting + * response from the user + */ +static int ircomm_state_waitr(struct ircomm_cb *self, IRCOMM_EVENT event, + struct sk_buff *skb, struct ircomm_info *info) +{ + int ret = 0; + + switch (event) { + case IRCOMM_CONNECT_RESPONSE: + ircomm_next_state(self, IRCOMM_CONN); + ret = self->issue.connect_response(self, skb); + break; + case IRCOMM_DISCONNECT_REQUEST: + ircomm_next_state(self, IRCOMM_IDLE); + ret = self->issue.disconnect_request(self, skb, info); + break; + case IRCOMM_TTP_DISCONNECT_INDICATION: + case IRCOMM_LMP_DISCONNECT_INDICATION: + ircomm_next_state(self, IRCOMM_IDLE); + ircomm_disconnect_indication(self, skb, info); + break; + default: + IRDA_DEBUG(0, "%s(), unknown event = %s\n", __FUNCTION__ , + ircomm_event[event]); + ret = -EINVAL; + } + return ret; +} + +/* + * Function ircomm_state_conn (self, event, skb) + * + * IrCOMM is connected to the peer IrCOMM device + * + */ +static int ircomm_state_conn(struct ircomm_cb *self, IRCOMM_EVENT event, + struct sk_buff *skb, struct ircomm_info *info) +{ + int ret = 0; + + switch (event) { + case IRCOMM_DATA_REQUEST: + ret = self->issue.data_request(self, skb, 0); + break; + case IRCOMM_TTP_DATA_INDICATION: + ircomm_process_data(self, skb); + break; + case IRCOMM_LMP_DATA_INDICATION: + ircomm_data_indication(self, skb); + break; + case IRCOMM_CONTROL_REQUEST: + /* Just send a separate frame for now */ + ret = self->issue.data_request(self, skb, skb->len); + break; + case IRCOMM_TTP_DISCONNECT_INDICATION: + case IRCOMM_LMP_DISCONNECT_INDICATION: + ircomm_next_state(self, IRCOMM_IDLE); + ircomm_disconnect_indication(self, skb, info); + break; + case IRCOMM_DISCONNECT_REQUEST: + ircomm_next_state(self, IRCOMM_IDLE); + ret = self->issue.disconnect_request(self, skb, info); + break; + default: + IRDA_DEBUG(0, "%s(), unknown event = %s\n", __FUNCTION__ , + ircomm_event[event]); + ret = -EINVAL; + } + return ret; +} + +/* + * Function ircomm_do_event (self, event, skb) + * + * Process event + * + */ +int ircomm_do_event(struct ircomm_cb *self, IRCOMM_EVENT event, + struct sk_buff *skb, struct ircomm_info *info) +{ + IRDA_DEBUG(4, "%s: state=%s, event=%s\n", __FUNCTION__ , + ircomm_state[self->state], ircomm_event[event]); + + return (*state[self->state])(self, event, skb, info); +} + +/* + * Function ircomm_next_state (self, state) + * + * Switch state + * + */ +void ircomm_next_state(struct ircomm_cb *self, IRCOMM_STATE state) +{ + self->state = state; + + IRDA_DEBUG(4, "%s: next state=%s, service type=%d\n", __FUNCTION__ , + ircomm_state[self->state], self->service_type); +} diff --git a/net/irda/ircomm/ircomm_lmp.c b/net/irda/ircomm/ircomm_lmp.c new file mode 100644 index 000000000000..d9097207aed3 --- /dev/null +++ b/net/irda/ircomm/ircomm_lmp.c @@ -0,0 +1,372 @@ +/********************************************************************* + * + * Filename: ircomm_lmp.c + * Version: 1.0 + * Description: Interface between IrCOMM and IrLMP + * Status: Stable + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sun Jun 6 20:48:27 1999 + * Modified at: Sun Dec 12 13:44:17 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * Sources: Previous IrLPT work by Thomas Davis + * + * Copyright (c) 1999 Dag Brattli, All Rights Reserved. + * Copyright (c) 2000-2003 Jean Tourrilhes <jt@hpl.hp.com> + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + ********************************************************************/ + +#include <linux/sched.h> +#include <linux/init.h> + +#include <net/irda/irda.h> +#include <net/irda/irlmp.h> +#include <net/irda/iriap.h> +#include <net/irda/irda_device.h> /* struct irda_skb_cb */ + +#include <net/irda/ircomm_event.h> +#include <net/irda/ircomm_lmp.h> + + +/* + * Function ircomm_lmp_connect_request (self, userdata) + * + * + * + */ +static int ircomm_lmp_connect_request(struct ircomm_cb *self, + struct sk_buff *userdata, + struct ircomm_info *info) +{ + int ret = 0; + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + /* Don't forget to refcount it - should be NULL anyway */ + if(userdata) + skb_get(userdata); + + ret = irlmp_connect_request(self->lsap, info->dlsap_sel, + info->saddr, info->daddr, NULL, userdata); + return ret; +} + +/* + * Function ircomm_lmp_connect_response (self, skb) + * + * + * + */ +static int ircomm_lmp_connect_response(struct ircomm_cb *self, + struct sk_buff *userdata) +{ + struct sk_buff *tx_skb; + int ret; + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + /* Any userdata supplied? */ + if (userdata == NULL) { + tx_skb = dev_alloc_skb(64); + if (!tx_skb) + return -ENOMEM; + + /* Reserve space for MUX and LAP header */ + skb_reserve(tx_skb, LMP_MAX_HEADER); + } else { + /* + * Check that the client has reserved enough space for + * headers + */ + IRDA_ASSERT(skb_headroom(userdata) >= LMP_MAX_HEADER, + return -1;); + + /* Don't forget to refcount it - should be NULL anyway */ + skb_get(userdata); + tx_skb = userdata; + } + + ret = irlmp_connect_response(self->lsap, tx_skb); + + return 0; +} + +static int ircomm_lmp_disconnect_request(struct ircomm_cb *self, + struct sk_buff *userdata, + struct ircomm_info *info) +{ + struct sk_buff *tx_skb; + int ret; + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + if (!userdata) { + tx_skb = dev_alloc_skb(64); + if (!tx_skb) + return -ENOMEM; + + /* Reserve space for MUX and LAP header */ + skb_reserve(tx_skb, LMP_MAX_HEADER); + userdata = tx_skb; + } else { + /* Don't forget to refcount it - should be NULL anyway */ + skb_get(userdata); + } + + ret = irlmp_disconnect_request(self->lsap, userdata); + + return ret; +} + +/* + * Function ircomm_lmp_flow_control (skb) + * + * This function is called when a data frame we have sent to IrLAP has + * been deallocated. We do this to make sure we don't flood IrLAP with + * frames, since we are not using the IrTTP flow control mechanism + */ +static void ircomm_lmp_flow_control(struct sk_buff *skb) +{ + struct irda_skb_cb *cb; + struct ircomm_cb *self; + int line; + + IRDA_ASSERT(skb != NULL, return;); + + cb = (struct irda_skb_cb *) skb->cb; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + line = cb->line; + + self = (struct ircomm_cb *) hashbin_lock_find(ircomm, line, NULL); + if (!self) { + IRDA_DEBUG(2, "%s(), didn't find myself\n", __FUNCTION__ ); + return; + } + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return;); + + self->pkt_count--; + + if ((self->pkt_count < 2) && (self->flow_status == FLOW_STOP)) { + IRDA_DEBUG(2, "%s(), asking TTY to start again!\n", __FUNCTION__ ); + self->flow_status = FLOW_START; + if (self->notify.flow_indication) + self->notify.flow_indication(self->notify.instance, + self, FLOW_START); + } +} + +/* + * Function ircomm_lmp_data_request (self, userdata) + * + * Send data frame to peer device + * + */ +static int ircomm_lmp_data_request(struct ircomm_cb *self, + struct sk_buff *skb, + int not_used) +{ + struct irda_skb_cb *cb; + int ret; + + IRDA_ASSERT(skb != NULL, return -1;); + + cb = (struct irda_skb_cb *) skb->cb; + + cb->line = self->line; + + IRDA_DEBUG(4, "%s(), sending frame\n", __FUNCTION__ ); + + /* Don't forget to refcount it - see ircomm_tty_do_softint() */ + skb_get(skb); + + skb->destructor = ircomm_lmp_flow_control; + + if ((self->pkt_count++ > 7) && (self->flow_status == FLOW_START)) { + IRDA_DEBUG(2, "%s(), asking TTY to slow down!\n", __FUNCTION__ ); + self->flow_status = FLOW_STOP; + if (self->notify.flow_indication) + self->notify.flow_indication(self->notify.instance, + self, FLOW_STOP); + } + ret = irlmp_data_request(self->lsap, skb); + if (ret) { + IRDA_ERROR("%s(), failed\n", __FUNCTION__); + /* irlmp_data_request already free the packet */ + } + + return ret; +} + +/* + * Function ircomm_lmp_data_indication (instance, sap, skb) + * + * Incoming data which we must deliver to the state machine, to check + * we are still connected. + */ +static int ircomm_lmp_data_indication(void *instance, void *sap, + struct sk_buff *skb) +{ + struct ircomm_cb *self = (struct ircomm_cb *) instance; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return -1;); + IRDA_ASSERT(skb != NULL, return -1;); + + ircomm_do_event(self, IRCOMM_LMP_DATA_INDICATION, skb, NULL); + + /* Drop reference count - see ircomm_tty_data_indication(). */ + dev_kfree_skb(skb); + + return 0; +} + +/* + * Function ircomm_lmp_connect_confirm (instance, sap, qos, max_sdu_size, + * max_header_size, skb) + * + * Connection has been confirmed by peer device + * + */ +static void ircomm_lmp_connect_confirm(void *instance, void *sap, + struct qos_info *qos, + __u32 max_seg_size, + __u8 max_header_size, + struct sk_buff *skb) +{ + struct ircomm_cb *self = (struct ircomm_cb *) instance; + struct ircomm_info info; + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return;); + IRDA_ASSERT(skb != NULL, return;); + IRDA_ASSERT(qos != NULL, return;); + + info.max_data_size = max_seg_size; + info.max_header_size = max_header_size; + info.qos = qos; + + ircomm_do_event(self, IRCOMM_LMP_CONNECT_CONFIRM, skb, &info); + + /* Drop reference count - see ircomm_tty_connect_confirm(). */ + dev_kfree_skb(skb); +} + +/* + * Function ircomm_lmp_connect_indication (instance, sap, qos, max_sdu_size, + * max_header_size, skb) + * + * Peer device wants to make a connection with us + * + */ +static void ircomm_lmp_connect_indication(void *instance, void *sap, + struct qos_info *qos, + __u32 max_seg_size, + __u8 max_header_size, + struct sk_buff *skb) +{ + struct ircomm_cb *self = (struct ircomm_cb *)instance; + struct ircomm_info info; + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return;); + IRDA_ASSERT(skb != NULL, return;); + IRDA_ASSERT(qos != NULL, return;); + + info.max_data_size = max_seg_size; + info.max_header_size = max_header_size; + info.qos = qos; + + ircomm_do_event(self, IRCOMM_LMP_CONNECT_INDICATION, skb, &info); + + /* Drop reference count - see ircomm_tty_connect_indication(). */ + dev_kfree_skb(skb); +} + +/* + * Function ircomm_lmp_disconnect_indication (instance, sap, reason, skb) + * + * Peer device has closed the connection, or the link went down for some + * other reason + */ +static void ircomm_lmp_disconnect_indication(void *instance, void *sap, + LM_REASON reason, + struct sk_buff *skb) +{ + struct ircomm_cb *self = (struct ircomm_cb *) instance; + struct ircomm_info info; + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return;); + + info.reason = reason; + + ircomm_do_event(self, IRCOMM_LMP_DISCONNECT_INDICATION, skb, &info); + + /* Drop reference count - see ircomm_tty_disconnect_indication(). */ + if(skb) + dev_kfree_skb(skb); +} +/* + * Function ircomm_open_lsap (self) + * + * Open LSAP. This function will only be used when using "raw" services + * + */ +int ircomm_open_lsap(struct ircomm_cb *self) +{ + notify_t notify; + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + /* Register callbacks */ + irda_notify_init(¬ify); + notify.data_indication = ircomm_lmp_data_indication; + notify.connect_confirm = ircomm_lmp_connect_confirm; + notify.connect_indication = ircomm_lmp_connect_indication; + notify.disconnect_indication = ircomm_lmp_disconnect_indication; + notify.instance = self; + strlcpy(notify.name, "IrCOMM", sizeof(notify.name)); + + self->lsap = irlmp_open_lsap(LSAP_ANY, ¬ify, 0); + if (!self->lsap) { + IRDA_DEBUG(0,"%sfailed to allocate tsap\n", __FUNCTION__ ); + return -1; + } + self->slsap_sel = self->lsap->slsap_sel; + + /* + * Initialize the call-table for issuing commands + */ + self->issue.data_request = ircomm_lmp_data_request; + self->issue.connect_request = ircomm_lmp_connect_request; + self->issue.connect_response = ircomm_lmp_connect_response; + self->issue.disconnect_request = ircomm_lmp_disconnect_request; + + return 0; +} diff --git a/net/irda/ircomm/ircomm_param.c b/net/irda/ircomm/ircomm_param.c new file mode 100644 index 000000000000..6009bab05091 --- /dev/null +++ b/net/irda/ircomm/ircomm_param.c @@ -0,0 +1,511 @@ +/********************************************************************* + * + * Filename: ircomm_param.c + * Version: 1.0 + * Description: Parameter handling for the IrCOMM protocol + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Mon Jun 7 10:25:11 1999 + * Modified at: Sun Jan 30 14:32:03 2000 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1999-2000 Dag Brattli, 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + ********************************************************************/ + +#include <linux/sched.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> + +#include <net/irda/irda.h> +#include <net/irda/parameters.h> + +#include <net/irda/ircomm_core.h> +#include <net/irda/ircomm_tty_attach.h> +#include <net/irda/ircomm_tty.h> + +#include <net/irda/ircomm_param.h> + +static int ircomm_param_service_type(void *instance, irda_param_t *param, + int get); +static int ircomm_param_port_type(void *instance, irda_param_t *param, + int get); +static int ircomm_param_port_name(void *instance, irda_param_t *param, + int get); +static int ircomm_param_service_type(void *instance, irda_param_t *param, + int get); +static int ircomm_param_data_rate(void *instance, irda_param_t *param, + int get); +static int ircomm_param_data_format(void *instance, irda_param_t *param, + int get); +static int ircomm_param_flow_control(void *instance, irda_param_t *param, + int get); +static int ircomm_param_xon_xoff(void *instance, irda_param_t *param, int get); +static int ircomm_param_enq_ack(void *instance, irda_param_t *param, int get); +static int ircomm_param_line_status(void *instance, irda_param_t *param, + int get); +static int ircomm_param_dte(void *instance, irda_param_t *param, int get); +static int ircomm_param_dce(void *instance, irda_param_t *param, int get); +static int ircomm_param_poll(void *instance, irda_param_t *param, int get); + +static pi_minor_info_t pi_minor_call_table_common[] = { + { ircomm_param_service_type, PV_INT_8_BITS }, + { ircomm_param_port_type, PV_INT_8_BITS }, + { ircomm_param_port_name, PV_STRING } +}; +static pi_minor_info_t pi_minor_call_table_non_raw[] = { + { ircomm_param_data_rate, PV_INT_32_BITS | PV_BIG_ENDIAN }, + { ircomm_param_data_format, PV_INT_8_BITS }, + { ircomm_param_flow_control, PV_INT_8_BITS }, + { ircomm_param_xon_xoff, PV_INT_16_BITS }, + { ircomm_param_enq_ack, PV_INT_16_BITS }, + { ircomm_param_line_status, PV_INT_8_BITS } +}; +static pi_minor_info_t pi_minor_call_table_9_wire[] = { + { ircomm_param_dte, PV_INT_8_BITS }, + { ircomm_param_dce, PV_INT_8_BITS }, + { ircomm_param_poll, PV_NO_VALUE }, +}; + +static pi_major_info_t pi_major_call_table[] = { + { pi_minor_call_table_common, 3 }, + { pi_minor_call_table_non_raw, 6 }, + { pi_minor_call_table_9_wire, 3 } +/* { pi_minor_call_table_centronics } */ +}; + +pi_param_info_t ircomm_param_info = { pi_major_call_table, 3, 0x0f, 4 }; + +/* + * Function ircomm_param_request (self, pi, flush) + * + * Queue a parameter for the control channel + * + */ +int ircomm_param_request(struct ircomm_tty_cb *self, __u8 pi, int flush) +{ + struct tty_struct *tty; + unsigned long flags; + struct sk_buff *skb; + int count; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + tty = self->tty; + if (!tty) + return 0; + + /* Make sure we don't send parameters for raw mode */ + if (self->service_type == IRCOMM_3_WIRE_RAW) + return 0; + + spin_lock_irqsave(&self->spinlock, flags); + + skb = self->ctrl_skb; + if (!skb) { + skb = dev_alloc_skb(256); + if (!skb) { + spin_unlock_irqrestore(&self->spinlock, flags); + return -ENOMEM; + } + + skb_reserve(skb, self->max_header_size); + self->ctrl_skb = skb; + } + /* + * Inserting is a little bit tricky since we don't know how much + * room we will need. But this should hopefully work OK + */ + count = irda_param_insert(self, pi, skb->tail, skb_tailroom(skb), + &ircomm_param_info); + if (count < 0) { + IRDA_WARNING("%s(), no room for parameter!\n", __FUNCTION__); + spin_unlock_irqrestore(&self->spinlock, flags); + return -1; + } + skb_put(skb, count); + + spin_unlock_irqrestore(&self->spinlock, flags); + + IRDA_DEBUG(2, "%s(), skb->len=%d\n", __FUNCTION__ , skb->len); + + if (flush) { + /* ircomm_tty_do_softint will take care of the rest */ + schedule_work(&self->tqueue); + } + + return count; +} + +/* + * Function ircomm_param_service_type (self, buf, len) + * + * Handle service type, this function will both be called after the LM-IAS + * query and then the remote device sends its initial parameters + * + */ +static int ircomm_param_service_type(void *instance, irda_param_t *param, + int get) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + __u8 service_type = (__u8) param->pv.i; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + if (get) { + param->pv.i = self->settings.service_type; + return 0; + } + + /* Find all common service types */ + service_type &= self->service_type; + if (!service_type) { + IRDA_DEBUG(2, + "%s(), No common service type to use!\n", __FUNCTION__ ); + return -1; + } + IRDA_DEBUG(0, "%s(), services in common=%02x\n", __FUNCTION__ , + service_type); + + /* + * Now choose a preferred service type of those available + */ + if (service_type & IRCOMM_CENTRONICS) + self->settings.service_type = IRCOMM_CENTRONICS; + else if (service_type & IRCOMM_9_WIRE) + self->settings.service_type = IRCOMM_9_WIRE; + else if (service_type & IRCOMM_3_WIRE) + self->settings.service_type = IRCOMM_3_WIRE; + else if (service_type & IRCOMM_3_WIRE_RAW) + self->settings.service_type = IRCOMM_3_WIRE_RAW; + + IRDA_DEBUG(0, "%s(), resulting service type=0x%02x\n", __FUNCTION__ , + self->settings.service_type); + + /* + * Now the line is ready for some communication. Check if we are a + * server, and send over some initial parameters. + * Client do it in ircomm_tty_state_setup(). + * Note : we may get called from ircomm_tty_getvalue_confirm(), + * therefore before we even have open any socket. And self->client + * is initialised to TRUE only later. So, we check if the link is + * really initialised. - Jean II + */ + if ((self->max_header_size != IRCOMM_TTY_HDR_UNINITIALISED) && + (!self->client) && + (self->settings.service_type != IRCOMM_3_WIRE_RAW)) + { + /* Init connection */ + ircomm_tty_send_initial_parameters(self); + ircomm_tty_link_established(self); + } + + return 0; +} + +/* + * Function ircomm_param_port_type (self, param) + * + * The port type parameter tells if the devices are serial or parallel. + * Since we only advertise serial service, this parameter should only + * be equal to IRCOMM_SERIAL. + */ +static int ircomm_param_port_type(void *instance, irda_param_t *param, int get) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + if (get) + param->pv.i = IRCOMM_SERIAL; + else { + self->settings.port_type = (__u8) param->pv.i; + + IRDA_DEBUG(0, "%s(), port type=%d\n", __FUNCTION__ , + self->settings.port_type); + } + return 0; +} + +/* + * Function ircomm_param_port_name (self, param) + * + * Exchange port name + * + */ +static int ircomm_param_port_name(void *instance, irda_param_t *param, int get) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + if (get) { + IRDA_DEBUG(0, "%s(), not imp!\n", __FUNCTION__ ); + } else { + IRDA_DEBUG(0, "%s(), port-name=%s\n", __FUNCTION__ , param->pv.c); + strncpy(self->settings.port_name, param->pv.c, 32); + } + + return 0; +} + +/* + * Function ircomm_param_data_rate (self, param) + * + * Exchange data rate to be used in this settings + * + */ +static int ircomm_param_data_rate(void *instance, irda_param_t *param, int get) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + if (get) + param->pv.i = self->settings.data_rate; + else + self->settings.data_rate = param->pv.i; + + IRDA_DEBUG(2, "%s(), data rate = %d\n", __FUNCTION__ , param->pv.i); + + return 0; +} + +/* + * Function ircomm_param_data_format (self, param) + * + * Exchange data format to be used in this settings + * + */ +static int ircomm_param_data_format(void *instance, irda_param_t *param, + int get) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + if (get) + param->pv.i = self->settings.data_format; + else + self->settings.data_format = (__u8) param->pv.i; + + return 0; +} + +/* + * Function ircomm_param_flow_control (self, param) + * + * Exchange flow control settings to be used in this settings + * + */ +static int ircomm_param_flow_control(void *instance, irda_param_t *param, + int get) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + if (get) + param->pv.i = self->settings.flow_control; + else + self->settings.flow_control = (__u8) param->pv.i; + + IRDA_DEBUG(1, "%s(), flow control = 0x%02x\n", __FUNCTION__ , (__u8) param->pv.i); + + return 0; +} + +/* + * Function ircomm_param_xon_xoff (self, param) + * + * Exchange XON/XOFF characters + * + */ +static int ircomm_param_xon_xoff(void *instance, irda_param_t *param, int get) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + if (get) { + param->pv.i = self->settings.xonxoff[0]; + param->pv.i |= self->settings.xonxoff[1] << 8; + } else { + self->settings.xonxoff[0] = (__u16) param->pv.i & 0xff; + self->settings.xonxoff[1] = (__u16) param->pv.i >> 8; + } + + IRDA_DEBUG(0, "%s(), XON/XOFF = 0x%02x,0x%02x\n", __FUNCTION__ , + param->pv.i & 0xff, param->pv.i >> 8); + + return 0; +} + +/* + * Function ircomm_param_enq_ack (self, param) + * + * Exchange ENQ/ACK characters + * + */ +static int ircomm_param_enq_ack(void *instance, irda_param_t *param, int get) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + if (get) { + param->pv.i = self->settings.enqack[0]; + param->pv.i |= self->settings.enqack[1] << 8; + } else { + self->settings.enqack[0] = (__u16) param->pv.i & 0xff; + self->settings.enqack[1] = (__u16) param->pv.i >> 8; + } + + IRDA_DEBUG(0, "%s(), ENQ/ACK = 0x%02x,0x%02x\n", __FUNCTION__ , + param->pv.i & 0xff, param->pv.i >> 8); + + return 0; +} + +/* + * Function ircomm_param_line_status (self, param) + * + * + * + */ +static int ircomm_param_line_status(void *instance, irda_param_t *param, + int get) +{ + IRDA_DEBUG(2, "%s(), not impl.\n", __FUNCTION__ ); + + return 0; +} + +/* + * Function ircomm_param_dte (instance, param) + * + * If we get here, there must be some sort of null-modem connection, and + * we are probably working in server mode as well. + */ +static int ircomm_param_dte(void *instance, irda_param_t *param, int get) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + __u8 dte; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + if (get) + param->pv.i = self->settings.dte; + else { + dte = (__u8) param->pv.i; + + self->settings.dce = 0; + + if (dte & IRCOMM_DELTA_DTR) + self->settings.dce |= (IRCOMM_DELTA_DSR| + IRCOMM_DELTA_RI | + IRCOMM_DELTA_CD); + if (dte & IRCOMM_DTR) + self->settings.dce |= (IRCOMM_DSR| + IRCOMM_RI | + IRCOMM_CD); + + if (dte & IRCOMM_DELTA_RTS) + self->settings.dce |= IRCOMM_DELTA_CTS; + if (dte & IRCOMM_RTS) + self->settings.dce |= IRCOMM_CTS; + + /* Take appropriate actions */ + ircomm_tty_check_modem_status(self); + + /* Null modem cable emulator */ + self->settings.null_modem = TRUE; + } + + return 0; +} + +/* + * Function ircomm_param_dce (instance, param) + * + * + * + */ +static int ircomm_param_dce(void *instance, irda_param_t *param, int get) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + __u8 dce; + + IRDA_DEBUG(1, "%s(), dce = 0x%02x\n", __FUNCTION__ , (__u8) param->pv.i); + + dce = (__u8) param->pv.i; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + self->settings.dce = dce; + + /* Check if any of the settings have changed */ + if (dce & 0x0f) { + if (dce & IRCOMM_DELTA_CTS) { + IRDA_DEBUG(2, "%s(), CTS \n", __FUNCTION__ ); + } + } + + ircomm_tty_check_modem_status(self); + + return 0; +} + +/* + * Function ircomm_param_poll (instance, param) + * + * Called when the peer device is polling for the line settings + * + */ +static int ircomm_param_poll(void *instance, irda_param_t *param, int get) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + /* Poll parameters are always of lenght 0 (just a signal) */ + if (!get) { + /* Respond with DTE line settings */ + ircomm_param_request(self, IRCOMM_DTE, TRUE); + } + return 0; +} + + + + + diff --git a/net/irda/ircomm/ircomm_ttp.c b/net/irda/ircomm/ircomm_ttp.c new file mode 100644 index 000000000000..d98bf3570d29 --- /dev/null +++ b/net/irda/ircomm/ircomm_ttp.c @@ -0,0 +1,369 @@ +/********************************************************************* + * + * Filename: ircomm_ttp.c + * Version: 1.0 + * Description: Interface between IrCOMM and IrTTP + * Status: Stable + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sun Jun 6 20:48:27 1999 + * Modified at: Mon Dec 13 11:35:13 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1999 Dag Brattli, All Rights Reserved. + * Copyright (c) 2000-2003 Jean Tourrilhes <jt@hpl.hp.com> + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + ********************************************************************/ + +#include <linux/sched.h> +#include <linux/init.h> + +#include <net/irda/irda.h> +#include <net/irda/irlmp.h> +#include <net/irda/iriap.h> +#include <net/irda/irttp.h> + +#include <net/irda/ircomm_event.h> +#include <net/irda/ircomm_ttp.h> + +static int ircomm_ttp_data_indication(void *instance, void *sap, + struct sk_buff *skb); +static void ircomm_ttp_connect_confirm(void *instance, void *sap, + struct qos_info *qos, + __u32 max_sdu_size, + __u8 max_header_size, + struct sk_buff *skb); +static void ircomm_ttp_connect_indication(void *instance, void *sap, + struct qos_info *qos, + __u32 max_sdu_size, + __u8 max_header_size, + struct sk_buff *skb); +static void ircomm_ttp_flow_indication(void *instance, void *sap, + LOCAL_FLOW cmd); +static void ircomm_ttp_disconnect_indication(void *instance, void *sap, + LM_REASON reason, + struct sk_buff *skb); +static int ircomm_ttp_data_request(struct ircomm_cb *self, + struct sk_buff *skb, + int clen); +static int ircomm_ttp_connect_request(struct ircomm_cb *self, + struct sk_buff *userdata, + struct ircomm_info *info); +static int ircomm_ttp_connect_response(struct ircomm_cb *self, + struct sk_buff *userdata); +static int ircomm_ttp_disconnect_request(struct ircomm_cb *self, + struct sk_buff *userdata, + struct ircomm_info *info); + +/* + * Function ircomm_open_tsap (self) + * + * + * + */ +int ircomm_open_tsap(struct ircomm_cb *self) +{ + notify_t notify; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + /* Register callbacks */ + irda_notify_init(¬ify); + notify.data_indication = ircomm_ttp_data_indication; + notify.connect_confirm = ircomm_ttp_connect_confirm; + notify.connect_indication = ircomm_ttp_connect_indication; + notify.flow_indication = ircomm_ttp_flow_indication; + notify.disconnect_indication = ircomm_ttp_disconnect_indication; + notify.instance = self; + strlcpy(notify.name, "IrCOMM", sizeof(notify.name)); + + self->tsap = irttp_open_tsap(LSAP_ANY, DEFAULT_INITIAL_CREDIT, + ¬ify); + if (!self->tsap) { + IRDA_DEBUG(0, "%sfailed to allocate tsap\n", __FUNCTION__ ); + return -1; + } + self->slsap_sel = self->tsap->stsap_sel; + + /* + * Initialize the call-table for issuing commands + */ + self->issue.data_request = ircomm_ttp_data_request; + self->issue.connect_request = ircomm_ttp_connect_request; + self->issue.connect_response = ircomm_ttp_connect_response; + self->issue.disconnect_request = ircomm_ttp_disconnect_request; + + return 0; +} + +/* + * Function ircomm_ttp_connect_request (self, userdata) + * + * + * + */ +static int ircomm_ttp_connect_request(struct ircomm_cb *self, + struct sk_buff *userdata, + struct ircomm_info *info) +{ + int ret = 0; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + /* Don't forget to refcount it - should be NULL anyway */ + if(userdata) + skb_get(userdata); + + ret = irttp_connect_request(self->tsap, info->dlsap_sel, + info->saddr, info->daddr, NULL, + TTP_SAR_DISABLE, userdata); + + return ret; +} + +/* + * Function ircomm_ttp_connect_response (self, skb) + * + * + * + */ +static int ircomm_ttp_connect_response(struct ircomm_cb *self, + struct sk_buff *userdata) +{ + int ret; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + /* Don't forget to refcount it - should be NULL anyway */ + if(userdata) + skb_get(userdata); + + ret = irttp_connect_response(self->tsap, TTP_SAR_DISABLE, userdata); + + return ret; +} + +/* + * Function ircomm_ttp_data_request (self, userdata) + * + * Send IrCOMM data to IrTTP layer. Currently we do not try to combine + * control data with pure data, so they will be sent as separate frames. + * Should not be a big problem though, since control frames are rare. But + * some of them are sent after connection establishment, so this can + * increase the latency a bit. + */ +static int ircomm_ttp_data_request(struct ircomm_cb *self, + struct sk_buff *skb, + int clen) +{ + int ret; + + IRDA_ASSERT(skb != NULL, return -1;); + + IRDA_DEBUG(2, "%s(), clen=%d\n", __FUNCTION__ , clen); + + /* + * Insert clen field, currently we either send data only, or control + * only frames, to make things easier and avoid queueing + */ + IRDA_ASSERT(skb_headroom(skb) >= IRCOMM_HEADER_SIZE, return -1;); + + /* Don't forget to refcount it - see ircomm_tty_do_softint() */ + skb_get(skb); + + skb_push(skb, IRCOMM_HEADER_SIZE); + + skb->data[0] = clen; + + ret = irttp_data_request(self->tsap, skb); + if (ret) { + IRDA_ERROR("%s(), failed\n", __FUNCTION__); + /* irttp_data_request already free the packet */ + } + + return ret; +} + +/* + * Function ircomm_ttp_data_indication (instance, sap, skb) + * + * Incoming data + * + */ +static int ircomm_ttp_data_indication(void *instance, void *sap, + struct sk_buff *skb) +{ + struct ircomm_cb *self = (struct ircomm_cb *) instance; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return -1;); + IRDA_ASSERT(skb != NULL, return -1;); + + ircomm_do_event(self, IRCOMM_TTP_DATA_INDICATION, skb, NULL); + + /* Drop reference count - see ircomm_tty_data_indication(). */ + dev_kfree_skb(skb); + + return 0; +} + +static void ircomm_ttp_connect_confirm(void *instance, void *sap, + struct qos_info *qos, + __u32 max_sdu_size, + __u8 max_header_size, + struct sk_buff *skb) +{ + struct ircomm_cb *self = (struct ircomm_cb *) instance; + struct ircomm_info info; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return;); + IRDA_ASSERT(skb != NULL, return;); + IRDA_ASSERT(qos != NULL, goto out;); + + if (max_sdu_size != TTP_SAR_DISABLE) { + IRDA_ERROR("%s(), SAR not allowed for IrCOMM!\n", + __FUNCTION__); + goto out; + } + + info.max_data_size = irttp_get_max_seg_size(self->tsap) + - IRCOMM_HEADER_SIZE; + info.max_header_size = max_header_size + IRCOMM_HEADER_SIZE; + info.qos = qos; + + ircomm_do_event(self, IRCOMM_TTP_CONNECT_CONFIRM, skb, &info); + +out: + /* Drop reference count - see ircomm_tty_connect_confirm(). */ + dev_kfree_skb(skb); +} + +/* + * Function ircomm_ttp_connect_indication (instance, sap, qos, max_sdu_size, + * max_header_size, skb) + * + * + * + */ +static void ircomm_ttp_connect_indication(void *instance, void *sap, + struct qos_info *qos, + __u32 max_sdu_size, + __u8 max_header_size, + struct sk_buff *skb) +{ + struct ircomm_cb *self = (struct ircomm_cb *)instance; + struct ircomm_info info; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return;); + IRDA_ASSERT(skb != NULL, return;); + IRDA_ASSERT(qos != NULL, goto out;); + + if (max_sdu_size != TTP_SAR_DISABLE) { + IRDA_ERROR("%s(), SAR not allowed for IrCOMM!\n", + __FUNCTION__); + goto out; + } + + info.max_data_size = irttp_get_max_seg_size(self->tsap) + - IRCOMM_HEADER_SIZE; + info.max_header_size = max_header_size + IRCOMM_HEADER_SIZE; + info.qos = qos; + + ircomm_do_event(self, IRCOMM_TTP_CONNECT_INDICATION, skb, &info); + +out: + /* Drop reference count - see ircomm_tty_connect_indication(). */ + dev_kfree_skb(skb); +} + +/* + * Function ircomm_ttp_disconnect_request (self, userdata, info) + * + * + * + */ +static int ircomm_ttp_disconnect_request(struct ircomm_cb *self, + struct sk_buff *userdata, + struct ircomm_info *info) +{ + int ret; + + /* Don't forget to refcount it - should be NULL anyway */ + if(userdata) + skb_get(userdata); + + ret = irttp_disconnect_request(self->tsap, userdata, P_NORMAL); + + return ret; +} + +/* + * Function ircomm_ttp_disconnect_indication (instance, sap, reason, skb) + * + * + * + */ +static void ircomm_ttp_disconnect_indication(void *instance, void *sap, + LM_REASON reason, + struct sk_buff *skb) +{ + struct ircomm_cb *self = (struct ircomm_cb *) instance; + struct ircomm_info info; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return;); + + info.reason = reason; + + ircomm_do_event(self, IRCOMM_TTP_DISCONNECT_INDICATION, skb, &info); + + /* Drop reference count - see ircomm_tty_disconnect_indication(). */ + if(skb) + dev_kfree_skb(skb); +} + +/* + * Function ircomm_ttp_flow_indication (instance, sap, cmd) + * + * Layer below is telling us to start or stop the flow of data + * + */ +static void ircomm_ttp_flow_indication(void *instance, void *sap, + LOCAL_FLOW cmd) +{ + struct ircomm_cb *self = (struct ircomm_cb *) instance; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return;); + + if (self->notify.flow_indication) + self->notify.flow_indication(self->notify.instance, self, cmd); +} + + diff --git a/net/irda/ircomm/ircomm_tty.c b/net/irda/ircomm/ircomm_tty.c new file mode 100644 index 000000000000..5d1e61168eb7 --- /dev/null +++ b/net/irda/ircomm/ircomm_tty.c @@ -0,0 +1,1405 @@ +/********************************************************************* + * + * Filename: ircomm_tty.c + * Version: 1.0 + * Description: IrCOMM serial TTY driver + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sun Jun 6 21:00:56 1999 + * Modified at: Wed Feb 23 00:09:02 2000 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * Sources: serial.c and previous IrCOMM work by Takahide Higuchi + * + * Copyright (c) 1999-2000 Dag Brattli, All Rights Reserved. + * Copyright (c) 2000-2003 Jean Tourrilhes <jt@hpl.hp.com> + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + ********************************************************************/ + +#include <linux/config.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/sched.h> +#include <linux/termios.h> +#include <linux/tty.h> +#include <linux/interrupt.h> +#include <linux/device.h> /* for MODULE_ALIAS_CHARDEV_MAJOR */ + +#include <asm/uaccess.h> + +#include <net/irda/irda.h> +#include <net/irda/irmod.h> + +#include <net/irda/ircomm_core.h> +#include <net/irda/ircomm_param.h> +#include <net/irda/ircomm_tty_attach.h> +#include <net/irda/ircomm_tty.h> + +static int ircomm_tty_open(struct tty_struct *tty, struct file *filp); +static void ircomm_tty_close(struct tty_struct * tty, struct file *filp); +static int ircomm_tty_write(struct tty_struct * tty, + const unsigned char *buf, int count); +static int ircomm_tty_write_room(struct tty_struct *tty); +static void ircomm_tty_throttle(struct tty_struct *tty); +static void ircomm_tty_unthrottle(struct tty_struct *tty); +static int ircomm_tty_chars_in_buffer(struct tty_struct *tty); +static void ircomm_tty_flush_buffer(struct tty_struct *tty); +static void ircomm_tty_send_xchar(struct tty_struct *tty, char ch); +static void ircomm_tty_wait_until_sent(struct tty_struct *tty, int timeout); +static void ircomm_tty_hangup(struct tty_struct *tty); +static void ircomm_tty_do_softint(void *private_); +static void ircomm_tty_shutdown(struct ircomm_tty_cb *self); +static void ircomm_tty_stop(struct tty_struct *tty); + +static int ircomm_tty_data_indication(void *instance, void *sap, + struct sk_buff *skb); +static int ircomm_tty_control_indication(void *instance, void *sap, + struct sk_buff *skb); +static void ircomm_tty_flow_indication(void *instance, void *sap, + LOCAL_FLOW cmd); +#ifdef CONFIG_PROC_FS +static int ircomm_tty_read_proc(char *buf, char **start, off_t offset, int len, + int *eof, void *unused); +#endif /* CONFIG_PROC_FS */ +static struct tty_driver *driver; + +hashbin_t *ircomm_tty = NULL; + +static struct tty_operations ops = { + .open = ircomm_tty_open, + .close = ircomm_tty_close, + .write = ircomm_tty_write, + .write_room = ircomm_tty_write_room, + .chars_in_buffer = ircomm_tty_chars_in_buffer, + .flush_buffer = ircomm_tty_flush_buffer, + .ioctl = ircomm_tty_ioctl, /* ircomm_tty_ioctl.c */ + .tiocmget = ircomm_tty_tiocmget, /* ircomm_tty_ioctl.c */ + .tiocmset = ircomm_tty_tiocmset, /* ircomm_tty_ioctl.c */ + .throttle = ircomm_tty_throttle, + .unthrottle = ircomm_tty_unthrottle, + .send_xchar = ircomm_tty_send_xchar, + .set_termios = ircomm_tty_set_termios, + .stop = ircomm_tty_stop, + .start = ircomm_tty_start, + .hangup = ircomm_tty_hangup, + .wait_until_sent = ircomm_tty_wait_until_sent, +#ifdef CONFIG_PROC_FS + .read_proc = ircomm_tty_read_proc, +#endif /* CONFIG_PROC_FS */ +}; + +/* + * Function ircomm_tty_init() + * + * Init IrCOMM TTY layer/driver + * + */ +static int __init ircomm_tty_init(void) +{ + driver = alloc_tty_driver(IRCOMM_TTY_PORTS); + if (!driver) + return -ENOMEM; + ircomm_tty = hashbin_new(HB_LOCK); + if (ircomm_tty == NULL) { + IRDA_ERROR("%s(), can't allocate hashbin!\n", __FUNCTION__); + put_tty_driver(driver); + return -ENOMEM; + } + + driver->owner = THIS_MODULE; + driver->driver_name = "ircomm"; + driver->name = "ircomm"; + driver->devfs_name = "ircomm"; + driver->major = IRCOMM_TTY_MAJOR; + driver->minor_start = IRCOMM_TTY_MINOR; + driver->type = TTY_DRIVER_TYPE_SERIAL; + driver->subtype = SERIAL_TYPE_NORMAL; + driver->init_termios = tty_std_termios; + driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + driver->flags = TTY_DRIVER_REAL_RAW; + tty_set_operations(driver, &ops); + if (tty_register_driver(driver)) { + IRDA_ERROR("%s(): Couldn't register serial driver\n", + __FUNCTION__); + put_tty_driver(driver); + return -1; + } + return 0; +} + +static void __exit __ircomm_tty_cleanup(struct ircomm_tty_cb *self) +{ + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + ircomm_tty_shutdown(self); + + self->magic = 0; + kfree(self); +} + +/* + * Function ircomm_tty_cleanup () + * + * Remove IrCOMM TTY layer/driver + * + */ +static void __exit ircomm_tty_cleanup(void) +{ + int ret; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + ret = tty_unregister_driver(driver); + if (ret) { + IRDA_ERROR("%s(), failed to unregister driver\n", + __FUNCTION__); + return; + } + + hashbin_delete(ircomm_tty, (FREE_FUNC) __ircomm_tty_cleanup); + put_tty_driver(driver); +} + +/* + * Function ircomm_startup (self) + * + * + * + */ +static int ircomm_tty_startup(struct ircomm_tty_cb *self) +{ + notify_t notify; + int ret = -ENODEV; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + /* Check if already open */ + if (test_and_set_bit(ASYNC_B_INITIALIZED, &self->flags)) { + IRDA_DEBUG(2, "%s(), already open so break out!\n", __FUNCTION__ ); + return 0; + } + + /* Register with IrCOMM */ + irda_notify_init(¬ify); + /* These callbacks we must handle ourselves */ + notify.data_indication = ircomm_tty_data_indication; + notify.udata_indication = ircomm_tty_control_indication; + notify.flow_indication = ircomm_tty_flow_indication; + + /* Use the ircomm_tty interface for these ones */ + notify.disconnect_indication = ircomm_tty_disconnect_indication; + notify.connect_confirm = ircomm_tty_connect_confirm; + notify.connect_indication = ircomm_tty_connect_indication; + strlcpy(notify.name, "ircomm_tty", sizeof(notify.name)); + notify.instance = self; + + if (!self->ircomm) { + self->ircomm = ircomm_open(¬ify, self->service_type, + self->line); + } + if (!self->ircomm) + goto err; + + self->slsap_sel = self->ircomm->slsap_sel; + + /* Connect IrCOMM link with remote device */ + ret = ircomm_tty_attach_cable(self); + if (ret < 0) { + IRDA_ERROR("%s(), error attaching cable!\n", __FUNCTION__); + goto err; + } + + return 0; +err: + clear_bit(ASYNC_B_INITIALIZED, &self->flags); + return ret; +} + +/* + * Function ircomm_block_til_ready (self, filp) + * + * + * + */ +static int ircomm_tty_block_til_ready(struct ircomm_tty_cb *self, + struct file *filp) +{ + DECLARE_WAITQUEUE(wait, current); + int retval; + int do_clocal = 0, extra_count = 0; + unsigned long flags; + struct tty_struct *tty; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + tty = self->tty; + + /* + * If non-blocking mode is set, or the port is not enabled, + * then make the check up front and then exit. + */ + if (filp->f_flags & O_NONBLOCK || tty->flags & (1 << TTY_IO_ERROR)){ + /* nonblock mode is set or port is not enabled */ + self->flags |= ASYNC_NORMAL_ACTIVE; + IRDA_DEBUG(1, "%s(), O_NONBLOCK requested!\n", __FUNCTION__ ); + return 0; + } + + if (tty->termios->c_cflag & CLOCAL) { + IRDA_DEBUG(1, "%s(), doing CLOCAL!\n", __FUNCTION__ ); + do_clocal = 1; + } + + /* Wait for carrier detect and the line to become + * free (i.e., not in use by the callout). While we are in + * this loop, self->open_count is dropped by one, so that + * mgsl_close() knows when to free things. We restore it upon + * exit, either normal or abnormal. + */ + + retval = 0; + add_wait_queue(&self->open_wait, &wait); + + IRDA_DEBUG(2, "%s(%d):block_til_ready before block on %s open_count=%d\n", + __FILE__,__LINE__, tty->driver->name, self->open_count ); + + /* As far as I can see, we protect open_count - Jean II */ + spin_lock_irqsave(&self->spinlock, flags); + if (!tty_hung_up_p(filp)) { + extra_count = 1; + self->open_count--; + } + spin_unlock_irqrestore(&self->spinlock, flags); + self->blocked_open++; + + while (1) { + if (tty->termios->c_cflag & CBAUD) { + /* Here, we use to lock those two guys, but + * as ircomm_param_request() does it itself, + * I don't see the point (and I see the deadlock). + * Jean II */ + self->settings.dte |= IRCOMM_RTS + IRCOMM_DTR; + + ircomm_param_request(self, IRCOMM_DTE, TRUE); + } + + current->state = TASK_INTERRUPTIBLE; + + if (tty_hung_up_p(filp) || + !test_bit(ASYNC_B_INITIALIZED, &self->flags)) { + retval = (self->flags & ASYNC_HUP_NOTIFY) ? + -EAGAIN : -ERESTARTSYS; + break; + } + + /* + * Check if link is ready now. Even if CLOCAL is + * specified, we cannot return before the IrCOMM link is + * ready + */ + if (!test_bit(ASYNC_B_CLOSING, &self->flags) && + (do_clocal || (self->settings.dce & IRCOMM_CD)) && + self->state == IRCOMM_TTY_READY) + { + break; + } + + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + + IRDA_DEBUG(1, "%s(%d):block_til_ready blocking on %s open_count=%d\n", + __FILE__,__LINE__, tty->driver->name, self->open_count ); + + schedule(); + } + + __set_current_state(TASK_RUNNING); + remove_wait_queue(&self->open_wait, &wait); + + if (extra_count) { + /* ++ is not atomic, so this should be protected - Jean II */ + spin_lock_irqsave(&self->spinlock, flags); + self->open_count++; + spin_unlock_irqrestore(&self->spinlock, flags); + } + self->blocked_open--; + + IRDA_DEBUG(1, "%s(%d):block_til_ready after blocking on %s open_count=%d\n", + __FILE__,__LINE__, tty->driver->name, self->open_count); + + if (!retval) + self->flags |= ASYNC_NORMAL_ACTIVE; + + return retval; +} + +/* + * Function ircomm_tty_open (tty, filp) + * + * This routine is called when a particular tty device is opened. This + * routine is mandatory; if this routine is not filled in, the attempted + * open will fail with ENODEV. + */ +static int ircomm_tty_open(struct tty_struct *tty, struct file *filp) +{ + struct ircomm_tty_cb *self; + unsigned int line; + unsigned long flags; + int ret; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + line = tty->index; + if ((line < 0) || (line >= IRCOMM_TTY_PORTS)) { + return -ENODEV; + } + + /* Check if instance already exists */ + self = hashbin_lock_find(ircomm_tty, line, NULL); + if (!self) { + /* No, so make new instance */ + self = kmalloc(sizeof(struct ircomm_tty_cb), GFP_KERNEL); + if (self == NULL) { + IRDA_ERROR("%s(), kmalloc failed!\n", __FUNCTION__); + return -ENOMEM; + } + memset(self, 0, sizeof(struct ircomm_tty_cb)); + + self->magic = IRCOMM_TTY_MAGIC; + self->flow = FLOW_STOP; + + self->line = line; + INIT_WORK(&self->tqueue, ircomm_tty_do_softint, self); + self->max_header_size = IRCOMM_TTY_HDR_UNINITIALISED; + self->max_data_size = IRCOMM_TTY_DATA_UNINITIALISED; + self->close_delay = 5*HZ/10; + self->closing_wait = 30*HZ; + + /* Init some important stuff */ + init_timer(&self->watchdog_timer); + init_waitqueue_head(&self->open_wait); + init_waitqueue_head(&self->close_wait); + spin_lock_init(&self->spinlock); + + /* + * Force TTY into raw mode by default which is usually what + * we want for IrCOMM and IrLPT. This way applications will + * not have to twiddle with printcap etc. + */ + tty->termios->c_iflag = 0; + tty->termios->c_oflag = 0; + + /* Insert into hash */ + hashbin_insert(ircomm_tty, (irda_queue_t *) self, line, NULL); + } + /* ++ is not atomic, so this should be protected - Jean II */ + spin_lock_irqsave(&self->spinlock, flags); + self->open_count++; + + tty->driver_data = self; + self->tty = tty; + spin_unlock_irqrestore(&self->spinlock, flags); + + IRDA_DEBUG(1, "%s(), %s%d, count = %d\n", __FUNCTION__ , tty->driver->name, + self->line, self->open_count); + + /* Not really used by us, but lets do it anyway */ + self->tty->low_latency = (self->flags & ASYNC_LOW_LATENCY) ? 1 : 0; + + /* + * If the port is the middle of closing, bail out now + */ + if (tty_hung_up_p(filp) || + test_bit(ASYNC_B_CLOSING, &self->flags)) { + + /* Hm, why are we blocking on ASYNC_CLOSING if we + * do return -EAGAIN/-ERESTARTSYS below anyway? + * IMHO it's either not needed in the first place + * or for some reason we need to make sure the async + * closing has been finished - if so, wouldn't we + * probably better sleep uninterruptible? + */ + + if (wait_event_interruptible(self->close_wait, !test_bit(ASYNC_B_CLOSING, &self->flags))) { + IRDA_WARNING("%s - got signal while blocking on ASYNC_CLOSING!\n", + __FUNCTION__); + return -ERESTARTSYS; + } + +#ifdef SERIAL_DO_RESTART + return ((self->flags & ASYNC_HUP_NOTIFY) ? + -EAGAIN : -ERESTARTSYS); +#else + return -EAGAIN; +#endif + } + + /* Check if this is a "normal" ircomm device, or an irlpt device */ + if (line < 0x10) { + self->service_type = IRCOMM_3_WIRE | IRCOMM_9_WIRE; + self->settings.service_type = IRCOMM_9_WIRE; /* 9 wire as default */ + /* Jan Kiszka -> add DSR/RI -> Conform to IrCOMM spec */ + self->settings.dce = IRCOMM_CTS | IRCOMM_CD | IRCOMM_DSR | IRCOMM_RI; /* Default line settings */ + IRDA_DEBUG(2, "%s(), IrCOMM device\n", __FUNCTION__ ); + } else { + IRDA_DEBUG(2, "%s(), IrLPT device\n", __FUNCTION__ ); + self->service_type = IRCOMM_3_WIRE_RAW; + self->settings.service_type = IRCOMM_3_WIRE_RAW; /* Default */ + } + + ret = ircomm_tty_startup(self); + if (ret) + return ret; + + ret = ircomm_tty_block_til_ready(self, filp); + if (ret) { + IRDA_DEBUG(2, + "%s(), returning after block_til_ready with %d\n", __FUNCTION__ , + ret); + + return ret; + } + return 0; +} + +/* + * Function ircomm_tty_close (tty, filp) + * + * This routine is called when a particular tty device is closed. + * + */ +static void ircomm_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data; + unsigned long flags; + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + if (!tty) + return; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + spin_lock_irqsave(&self->spinlock, flags); + + if (tty_hung_up_p(filp)) { + spin_unlock_irqrestore(&self->spinlock, flags); + + IRDA_DEBUG(0, "%s(), returning 1\n", __FUNCTION__ ); + return; + } + + if ((tty->count == 1) && (self->open_count != 1)) { + /* + * Uh, oh. tty->count is 1, which means that the tty + * structure will be freed. state->count should always + * be one in these conditions. If it's greater than + * one, we've got real problems, since it means the + * serial port won't be shutdown. + */ + IRDA_DEBUG(0, "%s(), bad serial port count; " + "tty->count is 1, state->count is %d\n", __FUNCTION__ , + self->open_count); + self->open_count = 1; + } + + if (--self->open_count < 0) { + IRDA_ERROR("%s(), bad serial port count for ttys%d: %d\n", + __FUNCTION__, self->line, self->open_count); + self->open_count = 0; + } + if (self->open_count) { + spin_unlock_irqrestore(&self->spinlock, flags); + + IRDA_DEBUG(0, "%s(), open count > 0\n", __FUNCTION__ ); + return; + } + + /* Hum... Should be test_and_set_bit ??? - Jean II */ + set_bit(ASYNC_B_CLOSING, &self->flags); + + /* We need to unlock here (we were unlocking at the end of this + * function), because tty_wait_until_sent() may schedule. + * I don't know if the rest should be protected somehow, + * so someone should check. - Jean II */ + spin_unlock_irqrestore(&self->spinlock, flags); + + /* + * Now we wait for the transmit buffer to clear; and we notify + * the line discipline to only process XON/XOFF characters. + */ + tty->closing = 1; + if (self->closing_wait != ASYNC_CLOSING_WAIT_NONE) + tty_wait_until_sent(tty, self->closing_wait); + + ircomm_tty_shutdown(self); + + if (tty->driver->flush_buffer) + tty->driver->flush_buffer(tty); + if (tty->ldisc.flush_buffer) + tty->ldisc.flush_buffer(tty); + + tty->closing = 0; + self->tty = NULL; + + if (self->blocked_open) { + if (self->close_delay) { + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(self->close_delay); + } + wake_up_interruptible(&self->open_wait); + } + + self->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING); + wake_up_interruptible(&self->close_wait); +} + +/* + * Function ircomm_tty_flush_buffer (tty) + * + * + * + */ +static void ircomm_tty_flush_buffer(struct tty_struct *tty) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + /* + * Let do_softint() do this to avoid race condition with + * do_softint() ;-) + */ + schedule_work(&self->tqueue); +} + +/* + * Function ircomm_tty_do_softint (private_) + * + * We use this routine to give the write wakeup to the user at at a + * safe time (as fast as possible after write have completed). This + * can be compared to the Tx interrupt. + */ +static void ircomm_tty_do_softint(void *private_) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) private_; + struct tty_struct *tty; + unsigned long flags; + struct sk_buff *skb, *ctrl_skb; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + if (!self || self->magic != IRCOMM_TTY_MAGIC) + return; + + tty = self->tty; + if (!tty) + return; + + /* Unlink control buffer */ + spin_lock_irqsave(&self->spinlock, flags); + + ctrl_skb = self->ctrl_skb; + self->ctrl_skb = NULL; + + spin_unlock_irqrestore(&self->spinlock, flags); + + /* Flush control buffer if any */ + if(ctrl_skb) { + if(self->flow == FLOW_START) + ircomm_control_request(self->ircomm, ctrl_skb); + /* Drop reference count - see ircomm_ttp_data_request(). */ + dev_kfree_skb(ctrl_skb); + } + + if (tty->hw_stopped) + return; + + /* Unlink transmit buffer */ + spin_lock_irqsave(&self->spinlock, flags); + + skb = self->tx_skb; + self->tx_skb = NULL; + + spin_unlock_irqrestore(&self->spinlock, flags); + + /* Flush transmit buffer if any */ + if (skb) { + ircomm_tty_do_event(self, IRCOMM_TTY_DATA_REQUEST, skb, NULL); + /* Drop reference count - see ircomm_ttp_data_request(). */ + dev_kfree_skb(skb); + } + + /* Check if user (still) wants to be waken up */ + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + { + (tty->ldisc.write_wakeup)(tty); + } + wake_up_interruptible(&tty->write_wait); +} + +/* + * Function ircomm_tty_write (tty, buf, count) + * + * This routine is called by the kernel to write a series of characters + * to the tty device. The characters may come from user space or kernel + * space. This routine will return the number of characters actually + * accepted for writing. This routine is mandatory. + */ +static int ircomm_tty_write(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data; + unsigned long flags; + struct sk_buff *skb; + int tailroom = 0; + int len = 0; + int size; + + IRDA_DEBUG(2, "%s(), count=%d, hw_stopped=%d\n", __FUNCTION__ , count, + tty->hw_stopped); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + /* We may receive packets from the TTY even before we have finished + * our setup. Not cool. + * The problem is that we don't know the final header and data size + * to create the proper skb, so any skb we would create would have + * bogus header and data size, so need care. + * We use a bogus header size to safely detect this condition. + * Another problem is that hw_stopped was set to 0 way before it + * should be, so we would drop this skb. It should now be fixed. + * One option is to not accept data until we are properly setup. + * But, I suspect that when it happens, the ppp line discipline + * just "drops" the data, which might screw up connect scripts. + * The second option is to create a "safe skb", with large header + * and small size (see ircomm_tty_open() for values). + * We just need to make sure that when the real values get filled, + * we don't mess up the original "safe skb" (see tx_data_size). + * Jean II */ + if (self->max_header_size == IRCOMM_TTY_HDR_UNINITIALISED) { + IRDA_DEBUG(1, "%s() : not initialised\n", __FUNCTION__); +#ifdef IRCOMM_NO_TX_BEFORE_INIT + /* We didn't consume anything, TTY will retry */ + return 0; +#endif + } + + if (count < 1) + return 0; + + /* Protect our manipulation of self->tx_skb and related */ + spin_lock_irqsave(&self->spinlock, flags); + + /* Fetch current transmit buffer */ + skb = self->tx_skb; + + /* + * Send out all the data we get, possibly as multiple fragmented + * frames, but this will only happen if the data is larger than the + * max data size. The normal case however is just the opposite, and + * this function may be called multiple times, and will then actually + * defragment the data and send it out as one packet as soon as + * possible, but at a safer point in time + */ + while (count) { + size = count; + + /* Adjust data size to the max data size */ + if (size > self->max_data_size) + size = self->max_data_size; + + /* + * Do we already have a buffer ready for transmit, or do + * we need to allocate a new frame + */ + if (skb) { + /* + * Any room for more data at the end of the current + * transmit buffer? Cannot use skb_tailroom, since + * dev_alloc_skb gives us a larger skb than we + * requested + * Note : use tx_data_size, because max_data_size + * may have changed and we don't want to overwrite + * the skb. - Jean II + */ + if ((tailroom = (self->tx_data_size - skb->len)) > 0) { + /* Adjust data to tailroom */ + if (size > tailroom) + size = tailroom; + } else { + /* + * Current transmit frame is full, so break + * out, so we can send it as soon as possible + */ + break; + } + } else { + /* Prepare a full sized frame */ + skb = dev_alloc_skb(self->max_data_size+ + self->max_header_size); + if (!skb) { + spin_unlock_irqrestore(&self->spinlock, flags); + return -ENOBUFS; + } + skb_reserve(skb, self->max_header_size); + self->tx_skb = skb; + /* Remember skb size because max_data_size may + * change later on - Jean II */ + self->tx_data_size = self->max_data_size; + } + + /* Copy data */ + memcpy(skb_put(skb,size), buf + len, size); + + count -= size; + len += size; + } + + spin_unlock_irqrestore(&self->spinlock, flags); + + /* + * Schedule a new thread which will transmit the frame as soon + * as possible, but at a safe point in time. We do this so the + * "user" can give us data multiple times, as PPP does (because of + * its 256 byte tx buffer). We will then defragment and send out + * all this data as one single packet. + */ + schedule_work(&self->tqueue); + + return len; +} + +/* + * Function ircomm_tty_write_room (tty) + * + * This routine returns the numbers of characters the tty driver will + * accept for queuing to be written. This number is subject to change as + * output buffers get emptied, or if the output flow control is acted. + */ +static int ircomm_tty_write_room(struct tty_struct *tty) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data; + unsigned long flags; + int ret; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + +#ifdef IRCOMM_NO_TX_BEFORE_INIT + /* max_header_size tells us if the channel is initialised or not. */ + if (self->max_header_size == IRCOMM_TTY_HDR_UNINITIALISED) + /* Don't bother us yet */ + return 0; +#endif + + /* Check if we are allowed to transmit any data. + * hw_stopped is the regular flow control. + * Jean II */ + if (tty->hw_stopped) + ret = 0; + else { + spin_lock_irqsave(&self->spinlock, flags); + if (self->tx_skb) + ret = self->tx_data_size - self->tx_skb->len; + else + ret = self->max_data_size; + spin_unlock_irqrestore(&self->spinlock, flags); + } + IRDA_DEBUG(2, "%s(), ret=%d\n", __FUNCTION__ , ret); + + return ret; +} + +/* + * Function ircomm_tty_wait_until_sent (tty, timeout) + * + * This routine waits until the device has written out all of the + * characters in its transmitter FIFO. + */ +static void ircomm_tty_wait_until_sent(struct tty_struct *tty, int timeout) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data; + unsigned long orig_jiffies, poll_time; + unsigned long flags; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + orig_jiffies = jiffies; + + /* Set poll time to 200 ms */ + poll_time = IRDA_MIN(timeout, msecs_to_jiffies(200)); + + spin_lock_irqsave(&self->spinlock, flags); + while (self->tx_skb && self->tx_skb->len) { + spin_unlock_irqrestore(&self->spinlock, flags); + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(poll_time); + spin_lock_irqsave(&self->spinlock, flags); + if (signal_pending(current)) + break; + if (timeout && time_after(jiffies, orig_jiffies + timeout)) + break; + } + spin_unlock_irqrestore(&self->spinlock, flags); + current->state = TASK_RUNNING; +} + +/* + * Function ircomm_tty_throttle (tty) + * + * This routine notifies the tty driver that input buffers for the line + * discipline are close to full, and it should somehow signal that no + * more characters should be sent to the tty. + */ +static void ircomm_tty_throttle(struct tty_struct *tty) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + /* Software flow control? */ + if (I_IXOFF(tty)) + ircomm_tty_send_xchar(tty, STOP_CHAR(tty)); + + /* Hardware flow control? */ + if (tty->termios->c_cflag & CRTSCTS) { + self->settings.dte &= ~IRCOMM_RTS; + self->settings.dte |= IRCOMM_DELTA_RTS; + + ircomm_param_request(self, IRCOMM_DTE, TRUE); + } + + ircomm_flow_request(self->ircomm, FLOW_STOP); +} + +/* + * Function ircomm_tty_unthrottle (tty) + * + * This routine notifies the tty drivers that it should signals that + * characters can now be sent to the tty without fear of overrunning the + * input buffers of the line disciplines. + */ +static void ircomm_tty_unthrottle(struct tty_struct *tty) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + /* Using software flow control? */ + if (I_IXOFF(tty)) { + ircomm_tty_send_xchar(tty, START_CHAR(tty)); + } + + /* Using hardware flow control? */ + if (tty->termios->c_cflag & CRTSCTS) { + self->settings.dte |= (IRCOMM_RTS|IRCOMM_DELTA_RTS); + + ircomm_param_request(self, IRCOMM_DTE, TRUE); + IRDA_DEBUG(1, "%s(), FLOW_START\n", __FUNCTION__ ); + } + ircomm_flow_request(self->ircomm, FLOW_START); +} + +/* + * Function ircomm_tty_chars_in_buffer (tty) + * + * Indicates if there are any data in the buffer + * + */ +static int ircomm_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data; + unsigned long flags; + int len = 0; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + spin_lock_irqsave(&self->spinlock, flags); + + if (self->tx_skb) + len = self->tx_skb->len; + + spin_unlock_irqrestore(&self->spinlock, flags); + + return len; +} + +static void ircomm_tty_shutdown(struct ircomm_tty_cb *self) +{ + unsigned long flags; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + if (!test_and_clear_bit(ASYNC_B_INITIALIZED, &self->flags)) + return; + + ircomm_tty_detach_cable(self); + + spin_lock_irqsave(&self->spinlock, flags); + + del_timer(&self->watchdog_timer); + + /* Free parameter buffer */ + if (self->ctrl_skb) { + dev_kfree_skb(self->ctrl_skb); + self->ctrl_skb = NULL; + } + + /* Free transmit buffer */ + if (self->tx_skb) { + dev_kfree_skb(self->tx_skb); + self->tx_skb = NULL; + } + + if (self->ircomm) { + ircomm_close(self->ircomm); + self->ircomm = NULL; + } + + spin_unlock_irqrestore(&self->spinlock, flags); +} + +/* + * Function ircomm_tty_hangup (tty) + * + * This routine notifies the tty driver that it should hangup the tty + * device. + * + */ +static void ircomm_tty_hangup(struct tty_struct *tty) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data; + unsigned long flags; + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + if (!tty) + return; + + /* ircomm_tty_flush_buffer(tty); */ + ircomm_tty_shutdown(self); + + /* I guess we need to lock here - Jean II */ + spin_lock_irqsave(&self->spinlock, flags); + self->flags &= ~ASYNC_NORMAL_ACTIVE; + self->tty = NULL; + self->open_count = 0; + spin_unlock_irqrestore(&self->spinlock, flags); + + wake_up_interruptible(&self->open_wait); +} + +/* + * Function ircomm_tty_send_xchar (tty, ch) + * + * This routine is used to send a high-priority XON/XOFF character to + * the device. + */ +static void ircomm_tty_send_xchar(struct tty_struct *tty, char ch) +{ + IRDA_DEBUG(0, "%s(), not impl\n", __FUNCTION__ ); +} + +/* + * Function ircomm_tty_start (tty) + * + * This routine notifies the tty driver that it resume sending + * characters to the tty device. + */ +void ircomm_tty_start(struct tty_struct *tty) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data; + + ircomm_flow_request(self->ircomm, FLOW_START); +} + +/* + * Function ircomm_tty_stop (tty) + * + * This routine notifies the tty driver that it should stop outputting + * characters to the tty device. + */ +static void ircomm_tty_stop(struct tty_struct *tty) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + ircomm_flow_request(self->ircomm, FLOW_STOP); +} + +/* + * Function ircomm_check_modem_status (self) + * + * Check for any changes in the DCE's line settings. This function should + * be called whenever the dce parameter settings changes, to update the + * flow control settings and other things + */ +void ircomm_tty_check_modem_status(struct ircomm_tty_cb *self) +{ + struct tty_struct *tty; + int status; + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + tty = self->tty; + + status = self->settings.dce; + + if (status & IRCOMM_DCE_DELTA_ANY) { + /*wake_up_interruptible(&self->delta_msr_wait);*/ + } + if ((self->flags & ASYNC_CHECK_CD) && (status & IRCOMM_DELTA_CD)) { + IRDA_DEBUG(2, + "%s(), ircomm%d CD now %s...\n", __FUNCTION__ , self->line, + (status & IRCOMM_CD) ? "on" : "off"); + + if (status & IRCOMM_CD) { + wake_up_interruptible(&self->open_wait); + } else { + IRDA_DEBUG(2, + "%s(), Doing serial hangup..\n", __FUNCTION__ ); + if (tty) + tty_hangup(tty); + + /* Hangup will remote the tty, so better break out */ + return; + } + } + if (self->flags & ASYNC_CTS_FLOW) { + if (tty->hw_stopped) { + if (status & IRCOMM_CTS) { + IRDA_DEBUG(2, + "%s(), CTS tx start...\n", __FUNCTION__ ); + tty->hw_stopped = 0; + + /* Wake up processes blocked on open */ + wake_up_interruptible(&self->open_wait); + + schedule_work(&self->tqueue); + return; + } + } else { + if (!(status & IRCOMM_CTS)) { + IRDA_DEBUG(2, + "%s(), CTS tx stop...\n", __FUNCTION__ ); + tty->hw_stopped = 1; + } + } + } +} + +/* + * Function ircomm_tty_data_indication (instance, sap, skb) + * + * Handle incoming data, and deliver it to the line discipline + * + */ +static int ircomm_tty_data_indication(void *instance, void *sap, + struct sk_buff *skb) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + IRDA_ASSERT(skb != NULL, return -1;); + + if (!self->tty) { + IRDA_DEBUG(0, "%s(), no tty!\n", __FUNCTION__ ); + return 0; + } + + /* + * If we receive data when hardware is stopped then something is wrong. + * We try to poll the peers line settings to check if we are up todate. + * Devices like WinCE can do this, and since they don't send any + * params, we can just as well declare the hardware for running. + */ + if (self->tty->hw_stopped && (self->flow == FLOW_START)) { + IRDA_DEBUG(0, "%s(), polling for line settings!\n", __FUNCTION__ ); + ircomm_param_request(self, IRCOMM_POLL, TRUE); + + /* We can just as well declare the hardware for running */ + ircomm_tty_send_initial_parameters(self); + ircomm_tty_link_established(self); + } + + /* + * Just give it over to the line discipline. There is no need to + * involve the flip buffers, since we are not running in an interrupt + * handler + */ + self->tty->ldisc.receive_buf(self->tty, skb->data, NULL, skb->len); + + /* No need to kfree_skb - see ircomm_ttp_data_indication() */ + + return 0; +} + +/* + * Function ircomm_tty_control_indication (instance, sap, skb) + * + * Parse all incoming parameters (easy!) + * + */ +static int ircomm_tty_control_indication(void *instance, void *sap, + struct sk_buff *skb) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + int clen; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + IRDA_ASSERT(skb != NULL, return -1;); + + clen = skb->data[0]; + + irda_param_extract_all(self, skb->data+1, IRDA_MIN(skb->len-1, clen), + &ircomm_param_info); + + /* No need to kfree_skb - see ircomm_control_indication() */ + + return 0; +} + +/* + * Function ircomm_tty_flow_indication (instance, sap, cmd) + * + * This function is called by IrTTP when it wants us to slow down the + * transmission of data. We just mark the hardware as stopped, and wait + * for IrTTP to notify us that things are OK again. + */ +static void ircomm_tty_flow_indication(void *instance, void *sap, + LOCAL_FLOW cmd) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + struct tty_struct *tty; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + tty = self->tty; + + switch (cmd) { + case FLOW_START: + IRDA_DEBUG(2, "%s(), hw start!\n", __FUNCTION__ ); + tty->hw_stopped = 0; + + /* ircomm_tty_do_softint will take care of the rest */ + schedule_work(&self->tqueue); + break; + default: /* If we get here, something is very wrong, better stop */ + case FLOW_STOP: + IRDA_DEBUG(2, "%s(), hw stopped!\n", __FUNCTION__ ); + tty->hw_stopped = 1; + break; + } + self->flow = cmd; +} + +static int ircomm_tty_line_info(struct ircomm_tty_cb *self, char *buf) +{ + int ret=0; + + ret += sprintf(buf+ret, "State: %s\n", ircomm_tty_state[self->state]); + + ret += sprintf(buf+ret, "Service type: "); + if (self->service_type & IRCOMM_9_WIRE) + ret += sprintf(buf+ret, "9_WIRE"); + else if (self->service_type & IRCOMM_3_WIRE) + ret += sprintf(buf+ret, "3_WIRE"); + else if (self->service_type & IRCOMM_3_WIRE_RAW) + ret += sprintf(buf+ret, "3_WIRE_RAW"); + else + ret += sprintf(buf+ret, "No common service type!\n"); + ret += sprintf(buf+ret, "\n"); + + ret += sprintf(buf+ret, "Port name: %s\n", self->settings.port_name); + + ret += sprintf(buf+ret, "DTE status: "); + if (self->settings.dte & IRCOMM_RTS) + ret += sprintf(buf+ret, "RTS|"); + if (self->settings.dte & IRCOMM_DTR) + ret += sprintf(buf+ret, "DTR|"); + if (self->settings.dte) + ret--; /* remove the last | */ + ret += sprintf(buf+ret, "\n"); + + ret += sprintf(buf+ret, "DCE status: "); + if (self->settings.dce & IRCOMM_CTS) + ret += sprintf(buf+ret, "CTS|"); + if (self->settings.dce & IRCOMM_DSR) + ret += sprintf(buf+ret, "DSR|"); + if (self->settings.dce & IRCOMM_CD) + ret += sprintf(buf+ret, "CD|"); + if (self->settings.dce & IRCOMM_RI) + ret += sprintf(buf+ret, "RI|"); + if (self->settings.dce) + ret--; /* remove the last | */ + ret += sprintf(buf+ret, "\n"); + + ret += sprintf(buf+ret, "Configuration: "); + if (!self->settings.null_modem) + ret += sprintf(buf+ret, "DTE <-> DCE\n"); + else + ret += sprintf(buf+ret, + "DTE <-> DTE (null modem emulation)\n"); + + ret += sprintf(buf+ret, "Data rate: %d\n", self->settings.data_rate); + + ret += sprintf(buf+ret, "Flow control: "); + if (self->settings.flow_control & IRCOMM_XON_XOFF_IN) + ret += sprintf(buf+ret, "XON_XOFF_IN|"); + if (self->settings.flow_control & IRCOMM_XON_XOFF_OUT) + ret += sprintf(buf+ret, "XON_XOFF_OUT|"); + if (self->settings.flow_control & IRCOMM_RTS_CTS_IN) + ret += sprintf(buf+ret, "RTS_CTS_IN|"); + if (self->settings.flow_control & IRCOMM_RTS_CTS_OUT) + ret += sprintf(buf+ret, "RTS_CTS_OUT|"); + if (self->settings.flow_control & IRCOMM_DSR_DTR_IN) + ret += sprintf(buf+ret, "DSR_DTR_IN|"); + if (self->settings.flow_control & IRCOMM_DSR_DTR_OUT) + ret += sprintf(buf+ret, "DSR_DTR_OUT|"); + if (self->settings.flow_control & IRCOMM_ENQ_ACK_IN) + ret += sprintf(buf+ret, "ENQ_ACK_IN|"); + if (self->settings.flow_control & IRCOMM_ENQ_ACK_OUT) + ret += sprintf(buf+ret, "ENQ_ACK_OUT|"); + if (self->settings.flow_control) + ret--; /* remove the last | */ + ret += sprintf(buf+ret, "\n"); + + ret += sprintf(buf+ret, "Flags: "); + if (self->flags & ASYNC_CTS_FLOW) + ret += sprintf(buf+ret, "ASYNC_CTS_FLOW|"); + if (self->flags & ASYNC_CHECK_CD) + ret += sprintf(buf+ret, "ASYNC_CHECK_CD|"); + if (self->flags & ASYNC_INITIALIZED) + ret += sprintf(buf+ret, "ASYNC_INITIALIZED|"); + if (self->flags & ASYNC_LOW_LATENCY) + ret += sprintf(buf+ret, "ASYNC_LOW_LATENCY|"); + if (self->flags & ASYNC_CLOSING) + ret += sprintf(buf+ret, "ASYNC_CLOSING|"); + if (self->flags & ASYNC_NORMAL_ACTIVE) + ret += sprintf(buf+ret, "ASYNC_NORMAL_ACTIVE|"); + if (self->flags) + ret--; /* remove the last | */ + ret += sprintf(buf+ret, "\n"); + + ret += sprintf(buf+ret, "Role: %s\n", self->client ? + "client" : "server"); + ret += sprintf(buf+ret, "Open count: %d\n", self->open_count); + ret += sprintf(buf+ret, "Max data size: %d\n", self->max_data_size); + ret += sprintf(buf+ret, "Max header size: %d\n", self->max_header_size); + + if (self->tty) + ret += sprintf(buf+ret, "Hardware: %s\n", + self->tty->hw_stopped ? "Stopped" : "Running"); + + ret += sprintf(buf+ret, "\n"); + return ret; +} + + +/* + * Function ircomm_tty_read_proc (buf, start, offset, len, eof, unused) + * + * + * + */ +#ifdef CONFIG_PROC_FS +static int ircomm_tty_read_proc(char *buf, char **start, off_t offset, int len, + int *eof, void *unused) +{ + struct ircomm_tty_cb *self; + int count = 0, l; + off_t begin = 0; + unsigned long flags; + + spin_lock_irqsave(&ircomm_tty->hb_spinlock, flags); + + self = (struct ircomm_tty_cb *) hashbin_get_first(ircomm_tty); + while ((self != NULL) && (count < 4000)) { + if (self->magic != IRCOMM_TTY_MAGIC) + break; + + l = ircomm_tty_line_info(self, buf + count); + count += l; + if (count+begin > offset+len) + goto done; + if (count+begin < offset) { + begin += count; + count = 0; + } + + self = (struct ircomm_tty_cb *) hashbin_get_next(ircomm_tty); + } + *eof = 1; +done: + spin_unlock_irqrestore(&ircomm_tty->hb_spinlock, flags); + + if (offset >= count+begin) + return 0; + *start = buf + (offset-begin); + return ((len < begin+count-offset) ? len : begin+count-offset); +} +#endif /* CONFIG_PROC_FS */ + +MODULE_AUTHOR("Dag Brattli <dagb@cs.uit.no>"); +MODULE_DESCRIPTION("IrCOMM serial TTY driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_CHARDEV_MAJOR(IRCOMM_TTY_MAJOR); + +module_init(ircomm_tty_init); +module_exit(ircomm_tty_cleanup); diff --git a/net/irda/ircomm/ircomm_tty_attach.c b/net/irda/ircomm/ircomm_tty_attach.c new file mode 100644 index 000000000000..99f5eddbb4b7 --- /dev/null +++ b/net/irda/ircomm/ircomm_tty_attach.c @@ -0,0 +1,1006 @@ +/********************************************************************* + * + * Filename: ircomm_tty_attach.c + * Version: + * Description: Code for attaching the serial driver to IrCOMM + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sat Jun 5 17:42:00 1999 + * Modified at: Tue Jan 4 14:20:49 2000 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1999-2000 Dag Brattli, All Rights Reserved. + * Copyright (c) 2000-2003 Jean Tourrilhes <jt@hpl.hp.com> + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + ********************************************************************/ + +#include <linux/sched.h> +#include <linux/init.h> + +#include <net/irda/irda.h> +#include <net/irda/irlmp.h> +#include <net/irda/iriap.h> +#include <net/irda/irttp.h> +#include <net/irda/irias_object.h> +#include <net/irda/parameters.h> + +#include <net/irda/ircomm_core.h> +#include <net/irda/ircomm_param.h> +#include <net/irda/ircomm_event.h> + +#include <net/irda/ircomm_tty.h> +#include <net/irda/ircomm_tty_attach.h> + +static void ircomm_tty_ias_register(struct ircomm_tty_cb *self); +static void ircomm_tty_discovery_indication(discinfo_t *discovery, + DISCOVERY_MODE mode, + void *priv); +static void ircomm_tty_getvalue_confirm(int result, __u16 obj_id, + struct ias_value *value, void *priv); +static void ircomm_tty_start_watchdog_timer(struct ircomm_tty_cb *self, + int timeout); +static void ircomm_tty_watchdog_timer_expired(void *data); + +static int ircomm_tty_state_idle(struct ircomm_tty_cb *self, + IRCOMM_TTY_EVENT event, + struct sk_buff *skb, + struct ircomm_tty_info *info); +static int ircomm_tty_state_search(struct ircomm_tty_cb *self, + IRCOMM_TTY_EVENT event, + struct sk_buff *skb, + struct ircomm_tty_info *info); +static int ircomm_tty_state_query_parameters(struct ircomm_tty_cb *self, + IRCOMM_TTY_EVENT event, + struct sk_buff *skb, + struct ircomm_tty_info *info); +static int ircomm_tty_state_query_lsap_sel(struct ircomm_tty_cb *self, + IRCOMM_TTY_EVENT event, + struct sk_buff *skb, + struct ircomm_tty_info *info); +static int ircomm_tty_state_setup(struct ircomm_tty_cb *self, + IRCOMM_TTY_EVENT event, + struct sk_buff *skb, + struct ircomm_tty_info *info); +static int ircomm_tty_state_ready(struct ircomm_tty_cb *self, + IRCOMM_TTY_EVENT event, + struct sk_buff *skb, + struct ircomm_tty_info *info); + +char *ircomm_tty_state[] = { + "IRCOMM_TTY_IDLE", + "IRCOMM_TTY_SEARCH", + "IRCOMM_TTY_QUERY_PARAMETERS", + "IRCOMM_TTY_QUERY_LSAP_SEL", + "IRCOMM_TTY_SETUP", + "IRCOMM_TTY_READY", + "*** ERROR *** ", +}; + +#ifdef CONFIG_IRDA_DEBUG +static char *ircomm_tty_event[] = { + "IRCOMM_TTY_ATTACH_CABLE", + "IRCOMM_TTY_DETACH_CABLE", + "IRCOMM_TTY_DATA_REQUEST", + "IRCOMM_TTY_DATA_INDICATION", + "IRCOMM_TTY_DISCOVERY_REQUEST", + "IRCOMM_TTY_DISCOVERY_INDICATION", + "IRCOMM_TTY_CONNECT_CONFIRM", + "IRCOMM_TTY_CONNECT_INDICATION", + "IRCOMM_TTY_DISCONNECT_REQUEST", + "IRCOMM_TTY_DISCONNECT_INDICATION", + "IRCOMM_TTY_WD_TIMER_EXPIRED", + "IRCOMM_TTY_GOT_PARAMETERS", + "IRCOMM_TTY_GOT_LSAPSEL", + "*** ERROR ****", +}; +#endif /* CONFIG_IRDA_DEBUG */ + +static int (*state[])(struct ircomm_tty_cb *self, IRCOMM_TTY_EVENT event, + struct sk_buff *skb, struct ircomm_tty_info *info) = +{ + ircomm_tty_state_idle, + ircomm_tty_state_search, + ircomm_tty_state_query_parameters, + ircomm_tty_state_query_lsap_sel, + ircomm_tty_state_setup, + ircomm_tty_state_ready, +}; + +/* + * Function ircomm_tty_attach_cable (driver) + * + * Try to attach cable (IrCOMM link). This function will only return + * when the link has been connected, or if an error condition occurs. + * If success, the return value is the resulting service type. + */ +int ircomm_tty_attach_cable(struct ircomm_tty_cb *self) +{ + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + /* Check if somebody has already connected to us */ + if (ircomm_is_connected(self->ircomm)) { + IRDA_DEBUG(0, "%s(), already connected!\n", __FUNCTION__ ); + return 0; + } + + /* Make sure nobody tries to write before the link is up */ + self->tty->hw_stopped = 1; + + ircomm_tty_ias_register(self); + + ircomm_tty_do_event(self, IRCOMM_TTY_ATTACH_CABLE, NULL, NULL); + + return 0; +} + +/* + * Function ircomm_detach_cable (driver) + * + * Detach cable, or cable has been detached by peer + * + */ +void ircomm_tty_detach_cable(struct ircomm_tty_cb *self) +{ + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + del_timer(&self->watchdog_timer); + + /* Remove discovery handler */ + if (self->ckey) { + irlmp_unregister_client(self->ckey); + self->ckey = NULL; + } + /* Remove IrCOMM hint bits */ + if (self->skey) { + irlmp_unregister_service(self->skey); + self->skey = NULL; + } + + if (self->iriap) { + iriap_close(self->iriap); + self->iriap = NULL; + } + + /* Remove LM-IAS object */ + if (self->obj) { + irias_delete_object(self->obj); + self->obj = NULL; + } + + ircomm_tty_do_event(self, IRCOMM_TTY_DETACH_CABLE, NULL, NULL); + + /* Reset some values */ + self->daddr = self->saddr = 0; + self->dlsap_sel = self->slsap_sel = 0; + + memset(&self->settings, 0, sizeof(struct ircomm_params)); +} + +/* + * Function ircomm_tty_ias_register (self) + * + * Register with LM-IAS depending on which service type we are + * + */ +static void ircomm_tty_ias_register(struct ircomm_tty_cb *self) +{ + __u8 oct_seq[6]; + __u16 hints; + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + /* Compute hint bits based on service */ + hints = irlmp_service_to_hint(S_COMM); + if (self->service_type & IRCOMM_3_WIRE_RAW) + hints |= irlmp_service_to_hint(S_PRINTER); + + /* Advertise IrCOMM hint bit in discovery */ + if (!self->skey) + self->skey = irlmp_register_service(hints); + /* Set up a discovery handler */ + if (!self->ckey) + self->ckey = irlmp_register_client(hints, + ircomm_tty_discovery_indication, + NULL, (void *) self); + + /* If already done, no need to do it again */ + if (self->obj) + return; + + if (self->service_type & IRCOMM_3_WIRE_RAW) { + /* Register IrLPT with LM-IAS */ + self->obj = irias_new_object("IrLPT", IAS_IRLPT_ID); + irias_add_integer_attrib(self->obj, "IrDA:IrLMP:LsapSel", + self->slsap_sel, IAS_KERNEL_ATTR); + } else { + /* Register IrCOMM with LM-IAS */ + self->obj = irias_new_object("IrDA:IrCOMM", IAS_IRCOMM_ID); + irias_add_integer_attrib(self->obj, "IrDA:TinyTP:LsapSel", + self->slsap_sel, IAS_KERNEL_ATTR); + + /* Code the parameters into the buffer */ + irda_param_pack(oct_seq, "bbbbbb", + IRCOMM_SERVICE_TYPE, 1, self->service_type, + IRCOMM_PORT_TYPE, 1, IRCOMM_SERIAL); + + /* Register parameters with LM-IAS */ + irias_add_octseq_attrib(self->obj, "Parameters", oct_seq, 6, + IAS_KERNEL_ATTR); + } + irias_insert_object(self->obj); +} + +/* + * Function ircomm_tty_ias_unregister (self) + * + * Remove our IAS object and client hook while connected. + * + */ +static void ircomm_tty_ias_unregister(struct ircomm_tty_cb *self) +{ + /* Remove LM-IAS object now so it is not reused. + * IrCOMM deals very poorly with multiple incoming connections. + * It should looks a lot more like IrNET, and "dup" a server TSAP + * to the application TSAP (based on various rules). + * This is a cheap workaround allowing multiple clients to + * connect to us. It will not always work. + * Each IrCOMM socket has an IAS entry. Incoming connection will + * pick the first one found. So, when we are fully connected, + * we remove our IAS entries so that the next IAS entry is used. + * We do that for *both* client and server, because a server + * can also create client instances. + * Jean II */ + if (self->obj) { + irias_delete_object(self->obj); + self->obj = NULL; + } + +#if 0 + /* Remove discovery handler. + * While we are connected, we no longer need to receive + * discovery events. This would be the case if there is + * multiple IrLAP interfaces. Jean II */ + if (self->ckey) { + irlmp_unregister_client(self->ckey); + self->ckey = NULL; + } +#endif +} + +/* + * Function ircomm_send_initial_parameters (self) + * + * Send initial parameters to the remote IrCOMM device. These parameters + * must be sent before any data. + */ +int ircomm_tty_send_initial_parameters(struct ircomm_tty_cb *self) +{ + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + if (self->service_type & IRCOMM_3_WIRE_RAW) + return 0; + + /* + * Set default values, but only if the application for some reason + * haven't set them already + */ + IRDA_DEBUG(2, "%s(), data-rate = %d\n", __FUNCTION__ , + self->settings.data_rate); + if (!self->settings.data_rate) + self->settings.data_rate = 9600; + IRDA_DEBUG(2, "%s(), data-format = %d\n", __FUNCTION__ , + self->settings.data_format); + if (!self->settings.data_format) + self->settings.data_format = IRCOMM_WSIZE_8; /* 8N1 */ + + IRDA_DEBUG(2, "%s(), flow-control = %d\n", __FUNCTION__ , + self->settings.flow_control); + /*self->settings.flow_control = IRCOMM_RTS_CTS_IN|IRCOMM_RTS_CTS_OUT;*/ + + /* Do not set delta values for the initial parameters */ + self->settings.dte = IRCOMM_DTR | IRCOMM_RTS; + + /* Only send service type parameter when we are the client */ + if (self->client) + ircomm_param_request(self, IRCOMM_SERVICE_TYPE, FALSE); + ircomm_param_request(self, IRCOMM_DATA_RATE, FALSE); + ircomm_param_request(self, IRCOMM_DATA_FORMAT, FALSE); + + /* For a 3 wire service, we just flush the last parameter and return */ + if (self->settings.service_type == IRCOMM_3_WIRE) { + ircomm_param_request(self, IRCOMM_FLOW_CONTROL, TRUE); + return 0; + } + + /* Only 9-wire service types continue here */ + ircomm_param_request(self, IRCOMM_FLOW_CONTROL, FALSE); +#if 0 + ircomm_param_request(self, IRCOMM_XON_XOFF, FALSE); + ircomm_param_request(self, IRCOMM_ENQ_ACK, FALSE); +#endif + /* Notify peer that we are ready to receive data */ + ircomm_param_request(self, IRCOMM_DTE, TRUE); + + return 0; +} + +/* + * Function ircomm_tty_discovery_indication (discovery) + * + * Remote device is discovered, try query the remote IAS to see which + * device it is, and which services it has. + * + */ +static void ircomm_tty_discovery_indication(discinfo_t *discovery, + DISCOVERY_MODE mode, + void *priv) +{ + struct ircomm_tty_cb *self; + struct ircomm_tty_info info; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + /* Important note : + * We need to drop all passive discoveries. + * The LSAP management of IrComm is deficient and doesn't deal + * with the case of two instance connecting to each other + * simultaneously (it will deadlock in LMP). + * The proper fix would be to use the same technique as in IrNET, + * to have one server socket and separate instances for the + * connecting/connected socket. + * The workaround is to drop passive discovery, which drastically + * reduce the probability of this happening. + * Jean II */ + if(mode == DISCOVERY_PASSIVE) + return; + + info.daddr = discovery->daddr; + info.saddr = discovery->saddr; + + /* FIXME. We have a locking problem on the hashbin here. + * We probably need to use hashbin_find_next(), but we first + * need to ensure that "line" is unique. - Jean II */ + self = (struct ircomm_tty_cb *) hashbin_get_first(ircomm_tty); + while (self != NULL) { + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + ircomm_tty_do_event(self, IRCOMM_TTY_DISCOVERY_INDICATION, + NULL, &info); + + self = (struct ircomm_tty_cb *) hashbin_get_next(ircomm_tty); + } +} + +/* + * Function ircomm_tty_disconnect_indication (instance, sap, reason, skb) + * + * Link disconnected + * + */ +void ircomm_tty_disconnect_indication(void *instance, void *sap, + LM_REASON reason, + struct sk_buff *skb) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + if (!self->tty) + return; + + /* This will stop control data transfers */ + self->flow = FLOW_STOP; + + /* Stop data transfers */ + self->tty->hw_stopped = 1; + + ircomm_tty_do_event(self, IRCOMM_TTY_DISCONNECT_INDICATION, NULL, + NULL); +} + +/* + * Function ircomm_tty_getvalue_confirm (result, obj_id, value, priv) + * + * Got result from the IAS query we make + * + */ +static void ircomm_tty_getvalue_confirm(int result, __u16 obj_id, + struct ias_value *value, + void *priv) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) priv; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + /* We probably don't need to make any more queries */ + iriap_close(self->iriap); + self->iriap = NULL; + + /* Check if request succeeded */ + if (result != IAS_SUCCESS) { + IRDA_DEBUG(4, "%s(), got NULL value!\n", __FUNCTION__ ); + return; + } + + switch (value->type) { + case IAS_OCT_SEQ: + IRDA_DEBUG(2, "%s(), got octet sequence\n", __FUNCTION__ ); + + irda_param_extract_all(self, value->t.oct_seq, value->len, + &ircomm_param_info); + + ircomm_tty_do_event(self, IRCOMM_TTY_GOT_PARAMETERS, NULL, + NULL); + break; + case IAS_INTEGER: + /* Got LSAP selector */ + IRDA_DEBUG(2, "%s(), got lsapsel = %d\n", __FUNCTION__ , + value->t.integer); + + if (value->t.integer == -1) { + IRDA_DEBUG(0, "%s(), invalid value!\n", __FUNCTION__ ); + } else + self->dlsap_sel = value->t.integer; + + ircomm_tty_do_event(self, IRCOMM_TTY_GOT_LSAPSEL, NULL, NULL); + break; + case IAS_MISSING: + IRDA_DEBUG(0, "%s(), got IAS_MISSING\n", __FUNCTION__ ); + break; + default: + IRDA_DEBUG(0, "%s(), got unknown type!\n", __FUNCTION__ ); + break; + } + irias_delete_value(value); +} + +/* + * Function ircomm_tty_connect_confirm (instance, sap, qos, max_sdu_size, skb) + * + * Connection confirmed + * + */ +void ircomm_tty_connect_confirm(void *instance, void *sap, + struct qos_info *qos, + __u32 max_data_size, + __u8 max_header_size, + struct sk_buff *skb) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + self->client = TRUE; + self->max_data_size = max_data_size; + self->max_header_size = max_header_size; + self->flow = FLOW_START; + + ircomm_tty_do_event(self, IRCOMM_TTY_CONNECT_CONFIRM, NULL, NULL); + + /* No need to kfree_skb - see ircomm_ttp_connect_confirm() */ +} + +/* + * Function ircomm_tty_connect_indication (instance, sap, qos, max_sdu_size, + * skb) + * + * we are discovered and being requested to connect by remote device ! + * + */ +void ircomm_tty_connect_indication(void *instance, void *sap, + struct qos_info *qos, + __u32 max_data_size, + __u8 max_header_size, + struct sk_buff *skb) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance; + int clen; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + self->client = FALSE; + self->max_data_size = max_data_size; + self->max_header_size = max_header_size; + self->flow = FLOW_START; + + clen = skb->data[0]; + if (clen) + irda_param_extract_all(self, skb->data+1, + IRDA_MIN(skb->len, clen), + &ircomm_param_info); + + ircomm_tty_do_event(self, IRCOMM_TTY_CONNECT_INDICATION, NULL, NULL); + + /* No need to kfree_skb - see ircomm_ttp_connect_indication() */ +} + +/* + * Function ircomm_tty_link_established (self) + * + * Called when the IrCOMM link is established + * + */ +void ircomm_tty_link_established(struct ircomm_tty_cb *self) +{ + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + if (!self->tty) + return; + + del_timer(&self->watchdog_timer); + + /* + * IrCOMM link is now up, and if we are not using hardware + * flow-control, then declare the hardware as running. Otherwise we + * will have to wait for the peer device (DCE) to raise the CTS + * line. + */ + if ((self->flags & ASYNC_CTS_FLOW) && ((self->settings.dce & IRCOMM_CTS) == 0)) { + IRDA_DEBUG(0, "%s(), waiting for CTS ...\n", __FUNCTION__ ); + return; + } else { + IRDA_DEBUG(1, "%s(), starting hardware!\n", __FUNCTION__ ); + + self->tty->hw_stopped = 0; + + /* Wake up processes blocked on open */ + wake_up_interruptible(&self->open_wait); + } + + schedule_work(&self->tqueue); +} + +/* + * Function ircomm_tty_start_watchdog_timer (self, timeout) + * + * Start the watchdog timer. This timer is used to make sure that any + * connection attempt is successful, and if not, we will retry after + * the timeout + */ +static void ircomm_tty_start_watchdog_timer(struct ircomm_tty_cb *self, + int timeout) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + irda_start_timer(&self->watchdog_timer, timeout, (void *) self, + ircomm_tty_watchdog_timer_expired); +} + +/* + * Function ircomm_tty_watchdog_timer_expired (data) + * + * Called when the connect procedure have taken to much time. + * + */ +static void ircomm_tty_watchdog_timer_expired(void *data) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) data; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + ircomm_tty_do_event(self, IRCOMM_TTY_WD_TIMER_EXPIRED, NULL, NULL); +} + + +/* + * Function ircomm_tty_do_event (self, event, skb) + * + * Process event + * + */ +int ircomm_tty_do_event(struct ircomm_tty_cb *self, IRCOMM_TTY_EVENT event, + struct sk_buff *skb, struct ircomm_tty_info *info) +{ + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + IRDA_DEBUG(2, "%s: state=%s, event=%s\n", __FUNCTION__ , + ircomm_tty_state[self->state], ircomm_tty_event[event]); + + return (*state[self->state])(self, event, skb, info); +} + +/* + * Function ircomm_tty_next_state (self, state) + * + * Switch state + * + */ +static inline void ircomm_tty_next_state(struct ircomm_tty_cb *self, IRCOMM_TTY_STATE state) +{ + /* + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;); + + IRDA_DEBUG(2, "%s: next state=%s, service type=%d\n", __FUNCTION__ , + ircomm_tty_state[self->state], self->service_type); + */ + self->state = state; +} + +/* + * Function ircomm_tty_state_idle (self, event, skb, info) + * + * Just hanging around + * + */ +static int ircomm_tty_state_idle(struct ircomm_tty_cb *self, + IRCOMM_TTY_EVENT event, + struct sk_buff *skb, + struct ircomm_tty_info *info) +{ + int ret = 0; + + IRDA_DEBUG(2, "%s: state=%s, event=%s\n", __FUNCTION__ , + ircomm_tty_state[self->state], ircomm_tty_event[event]); + switch (event) { + case IRCOMM_TTY_ATTACH_CABLE: + /* Try to discover any remote devices */ + ircomm_tty_start_watchdog_timer(self, 3*HZ); + ircomm_tty_next_state(self, IRCOMM_TTY_SEARCH); + + irlmp_discovery_request(DISCOVERY_DEFAULT_SLOTS); + break; + case IRCOMM_TTY_DISCOVERY_INDICATION: + self->daddr = info->daddr; + self->saddr = info->saddr; + + if (self->iriap) { + IRDA_WARNING("%s(), busy with a previous query\n", + __FUNCTION__); + return -EBUSY; + } + + self->iriap = iriap_open(LSAP_ANY, IAS_CLIENT, self, + ircomm_tty_getvalue_confirm); + + iriap_getvaluebyclass_request(self->iriap, + self->saddr, self->daddr, + "IrDA:IrCOMM", "Parameters"); + + ircomm_tty_start_watchdog_timer(self, 3*HZ); + ircomm_tty_next_state(self, IRCOMM_TTY_QUERY_PARAMETERS); + break; + case IRCOMM_TTY_CONNECT_INDICATION: + del_timer(&self->watchdog_timer); + + /* Accept connection */ + ircomm_connect_response(self->ircomm, NULL); + ircomm_tty_next_state(self, IRCOMM_TTY_READY); + break; + case IRCOMM_TTY_WD_TIMER_EXPIRED: + /* Just stay idle */ + break; + case IRCOMM_TTY_DETACH_CABLE: + ircomm_tty_next_state(self, IRCOMM_TTY_IDLE); + break; + default: + IRDA_DEBUG(2, "%s(), unknown event: %s\n", __FUNCTION__ , + ircomm_tty_event[event]); + ret = -EINVAL; + } + return ret; +} + +/* + * Function ircomm_tty_state_search (self, event, skb, info) + * + * Trying to discover an IrCOMM device + * + */ +static int ircomm_tty_state_search(struct ircomm_tty_cb *self, + IRCOMM_TTY_EVENT event, + struct sk_buff *skb, + struct ircomm_tty_info *info) +{ + int ret = 0; + + IRDA_DEBUG(2, "%s: state=%s, event=%s\n", __FUNCTION__ , + ircomm_tty_state[self->state], ircomm_tty_event[event]); + + switch (event) { + case IRCOMM_TTY_DISCOVERY_INDICATION: + self->daddr = info->daddr; + self->saddr = info->saddr; + + if (self->iriap) { + IRDA_WARNING("%s(), busy with a previous query\n", + __FUNCTION__); + return -EBUSY; + } + + self->iriap = iriap_open(LSAP_ANY, IAS_CLIENT, self, + ircomm_tty_getvalue_confirm); + + if (self->service_type == IRCOMM_3_WIRE_RAW) { + iriap_getvaluebyclass_request(self->iriap, self->saddr, + self->daddr, "IrLPT", + "IrDA:IrLMP:LsapSel"); + ircomm_tty_next_state(self, IRCOMM_TTY_QUERY_LSAP_SEL); + } else { + iriap_getvaluebyclass_request(self->iriap, self->saddr, + self->daddr, + "IrDA:IrCOMM", + "Parameters"); + + ircomm_tty_next_state(self, IRCOMM_TTY_QUERY_PARAMETERS); + } + ircomm_tty_start_watchdog_timer(self, 3*HZ); + break; + case IRCOMM_TTY_CONNECT_INDICATION: + del_timer(&self->watchdog_timer); + ircomm_tty_ias_unregister(self); + + /* Accept connection */ + ircomm_connect_response(self->ircomm, NULL); + ircomm_tty_next_state(self, IRCOMM_TTY_READY); + break; + case IRCOMM_TTY_WD_TIMER_EXPIRED: +#if 1 + /* Give up */ +#else + /* Try to discover any remote devices */ + ircomm_tty_start_watchdog_timer(self, 3*HZ); + irlmp_discovery_request(DISCOVERY_DEFAULT_SLOTS); +#endif + break; + case IRCOMM_TTY_DETACH_CABLE: + ircomm_tty_next_state(self, IRCOMM_TTY_IDLE); + break; + default: + IRDA_DEBUG(2, "%s(), unknown event: %s\n", __FUNCTION__ , + ircomm_tty_event[event]); + ret = -EINVAL; + } + return ret; +} + +/* + * Function ircomm_tty_state_query (self, event, skb, info) + * + * Querying the remote LM-IAS for IrCOMM parameters + * + */ +static int ircomm_tty_state_query_parameters(struct ircomm_tty_cb *self, + IRCOMM_TTY_EVENT event, + struct sk_buff *skb, + struct ircomm_tty_info *info) +{ + int ret = 0; + + IRDA_DEBUG(2, "%s: state=%s, event=%s\n", __FUNCTION__ , + ircomm_tty_state[self->state], ircomm_tty_event[event]); + + switch (event) { + case IRCOMM_TTY_GOT_PARAMETERS: + if (self->iriap) { + IRDA_WARNING("%s(), busy with a previous query\n", + __FUNCTION__); + return -EBUSY; + } + + self->iriap = iriap_open(LSAP_ANY, IAS_CLIENT, self, + ircomm_tty_getvalue_confirm); + + iriap_getvaluebyclass_request(self->iriap, self->saddr, + self->daddr, "IrDA:IrCOMM", + "IrDA:TinyTP:LsapSel"); + + ircomm_tty_start_watchdog_timer(self, 3*HZ); + ircomm_tty_next_state(self, IRCOMM_TTY_QUERY_LSAP_SEL); + break; + case IRCOMM_TTY_WD_TIMER_EXPIRED: + /* Go back to search mode */ + ircomm_tty_next_state(self, IRCOMM_TTY_SEARCH); + ircomm_tty_start_watchdog_timer(self, 3*HZ); + break; + case IRCOMM_TTY_CONNECT_INDICATION: + del_timer(&self->watchdog_timer); + ircomm_tty_ias_unregister(self); + + /* Accept connection */ + ircomm_connect_response(self->ircomm, NULL); + ircomm_tty_next_state(self, IRCOMM_TTY_READY); + break; + case IRCOMM_TTY_DETACH_CABLE: + ircomm_tty_next_state(self, IRCOMM_TTY_IDLE); + break; + default: + IRDA_DEBUG(2, "%s(), unknown event: %s\n", __FUNCTION__ , + ircomm_tty_event[event]); + ret = -EINVAL; + } + return ret; +} + +/* + * Function ircomm_tty_state_query_lsap_sel (self, event, skb, info) + * + * Query remote LM-IAS for the LSAP selector which we can connect to + * + */ +static int ircomm_tty_state_query_lsap_sel(struct ircomm_tty_cb *self, + IRCOMM_TTY_EVENT event, + struct sk_buff *skb, + struct ircomm_tty_info *info) +{ + int ret = 0; + + IRDA_DEBUG(2, "%s: state=%s, event=%s\n", __FUNCTION__ , + ircomm_tty_state[self->state], ircomm_tty_event[event]); + + switch (event) { + case IRCOMM_TTY_GOT_LSAPSEL: + /* Connect to remote device */ + ret = ircomm_connect_request(self->ircomm, self->dlsap_sel, + self->saddr, self->daddr, + NULL, self->service_type); + ircomm_tty_start_watchdog_timer(self, 3*HZ); + ircomm_tty_next_state(self, IRCOMM_TTY_SETUP); + break; + case IRCOMM_TTY_WD_TIMER_EXPIRED: + /* Go back to search mode */ + ircomm_tty_next_state(self, IRCOMM_TTY_SEARCH); + ircomm_tty_start_watchdog_timer(self, 3*HZ); + break; + case IRCOMM_TTY_CONNECT_INDICATION: + del_timer(&self->watchdog_timer); + ircomm_tty_ias_unregister(self); + + /* Accept connection */ + ircomm_connect_response(self->ircomm, NULL); + ircomm_tty_next_state(self, IRCOMM_TTY_READY); + break; + case IRCOMM_TTY_DETACH_CABLE: + ircomm_tty_next_state(self, IRCOMM_TTY_IDLE); + break; + default: + IRDA_DEBUG(2, "%s(), unknown event: %s\n", __FUNCTION__ , + ircomm_tty_event[event]); + ret = -EINVAL; + } + return ret; +} + +/* + * Function ircomm_tty_state_setup (self, event, skb, info) + * + * Trying to connect + * + */ +static int ircomm_tty_state_setup(struct ircomm_tty_cb *self, + IRCOMM_TTY_EVENT event, + struct sk_buff *skb, + struct ircomm_tty_info *info) +{ + int ret = 0; + + IRDA_DEBUG(2, "%s: state=%s, event=%s\n", __FUNCTION__ , + ircomm_tty_state[self->state], ircomm_tty_event[event]); + + switch (event) { + case IRCOMM_TTY_CONNECT_CONFIRM: + del_timer(&self->watchdog_timer); + ircomm_tty_ias_unregister(self); + + /* + * Send initial parameters. This will also send out queued + * parameters waiting for the connection to come up + */ + ircomm_tty_send_initial_parameters(self); + ircomm_tty_link_established(self); + ircomm_tty_next_state(self, IRCOMM_TTY_READY); + break; + case IRCOMM_TTY_CONNECT_INDICATION: + del_timer(&self->watchdog_timer); + ircomm_tty_ias_unregister(self); + + /* Accept connection */ + ircomm_connect_response(self->ircomm, NULL); + ircomm_tty_next_state(self, IRCOMM_TTY_READY); + break; + case IRCOMM_TTY_WD_TIMER_EXPIRED: + /* Go back to search mode */ + ircomm_tty_next_state(self, IRCOMM_TTY_SEARCH); + ircomm_tty_start_watchdog_timer(self, 3*HZ); + break; + case IRCOMM_TTY_DETACH_CABLE: + /* ircomm_disconnect_request(self->ircomm, NULL); */ + ircomm_tty_next_state(self, IRCOMM_TTY_IDLE); + break; + default: + IRDA_DEBUG(2, "%s(), unknown event: %s\n", __FUNCTION__ , + ircomm_tty_event[event]); + ret = -EINVAL; + } + return ret; +} + +/* + * Function ircomm_tty_state_ready (self, event, skb, info) + * + * IrCOMM is now connected + * + */ +static int ircomm_tty_state_ready(struct ircomm_tty_cb *self, + IRCOMM_TTY_EVENT event, + struct sk_buff *skb, + struct ircomm_tty_info *info) +{ + int ret = 0; + + switch (event) { + case IRCOMM_TTY_DATA_REQUEST: + ret = ircomm_data_request(self->ircomm, skb); + break; + case IRCOMM_TTY_DETACH_CABLE: + ircomm_disconnect_request(self->ircomm, NULL); + ircomm_tty_next_state(self, IRCOMM_TTY_IDLE); + break; + case IRCOMM_TTY_DISCONNECT_INDICATION: + ircomm_tty_ias_register(self); + ircomm_tty_next_state(self, IRCOMM_TTY_SEARCH); + ircomm_tty_start_watchdog_timer(self, 3*HZ); + + if (self->flags & ASYNC_CHECK_CD) { + /* Drop carrier */ + self->settings.dce = IRCOMM_DELTA_CD; + ircomm_tty_check_modem_status(self); + } else { + IRDA_DEBUG(0, "%s(), hanging up!\n", __FUNCTION__ ); + if (self->tty) + tty_hangup(self->tty); + } + break; + default: + IRDA_DEBUG(2, "%s(), unknown event: %s\n", __FUNCTION__ , + ircomm_tty_event[event]); + ret = -EINVAL; + } + return ret; +} + diff --git a/net/irda/ircomm/ircomm_tty_ioctl.c b/net/irda/ircomm/ircomm_tty_ioctl.c new file mode 100644 index 000000000000..197e3e7ed7e2 --- /dev/null +++ b/net/irda/ircomm/ircomm_tty_ioctl.c @@ -0,0 +1,428 @@ +/********************************************************************* + * + * Filename: ircomm_tty_ioctl.c + * Version: + * Description: + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Thu Jun 10 14:39:09 1999 + * Modified at: Wed Jan 5 14:45:43 2000 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1999-2000 Dag Brattli, 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + ********************************************************************/ + +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/sched.h> +#include <linux/termios.h> +#include <linux/tty.h> +#include <linux/serial.h> + +#include <asm/uaccess.h> + +#include <net/irda/irda.h> +#include <net/irda/irmod.h> + +#include <net/irda/ircomm_core.h> +#include <net/irda/ircomm_param.h> +#include <net/irda/ircomm_tty_attach.h> +#include <net/irda/ircomm_tty.h> + +#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) + +/* + * Function ircomm_tty_change_speed (driver) + * + * Change speed of the driver. If the remote device is a DCE, then this + * should make it change the speed of its serial port + */ +static void ircomm_tty_change_speed(struct ircomm_tty_cb *self) +{ + unsigned cflag, cval; + int baud; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + if (!self->tty || !self->tty->termios || !self->ircomm) + return; + + cflag = self->tty->termios->c_cflag; + + /* byte size and parity */ + switch (cflag & CSIZE) { + case CS5: cval = IRCOMM_WSIZE_5; break; + case CS6: cval = IRCOMM_WSIZE_6; break; + case CS7: cval = IRCOMM_WSIZE_7; break; + case CS8: cval = IRCOMM_WSIZE_8; break; + default: cval = IRCOMM_WSIZE_5; break; + } + if (cflag & CSTOPB) + cval |= IRCOMM_2_STOP_BIT; + + if (cflag & PARENB) + cval |= IRCOMM_PARITY_ENABLE; + if (!(cflag & PARODD)) + cval |= IRCOMM_PARITY_EVEN; + + /* Determine divisor based on baud rate */ + baud = tty_get_baud_rate(self->tty); + if (!baud) + baud = 9600; /* B0 transition handled in rs_set_termios */ + + self->settings.data_rate = baud; + ircomm_param_request(self, IRCOMM_DATA_RATE, FALSE); + + /* CTS flow control flag and modem status interrupts */ + if (cflag & CRTSCTS) { + self->flags |= ASYNC_CTS_FLOW; + self->settings.flow_control |= IRCOMM_RTS_CTS_IN; + /* This got me. Bummer. Jean II */ + if (self->service_type == IRCOMM_3_WIRE_RAW) + IRDA_WARNING("%s(), enabling RTS/CTS on link that doesn't support it (3-wire-raw)\n", __FUNCTION__); + } else { + self->flags &= ~ASYNC_CTS_FLOW; + self->settings.flow_control &= ~IRCOMM_RTS_CTS_IN; + } + if (cflag & CLOCAL) + self->flags &= ~ASYNC_CHECK_CD; + else + self->flags |= ASYNC_CHECK_CD; +#if 0 + /* + * Set up parity check flag + */ + + if (I_INPCK(self->tty)) + driver->read_status_mask |= LSR_FE | LSR_PE; + if (I_BRKINT(driver->tty) || I_PARMRK(driver->tty)) + driver->read_status_mask |= LSR_BI; + + /* + * Characters to ignore + */ + driver->ignore_status_mask = 0; + if (I_IGNPAR(driver->tty)) + driver->ignore_status_mask |= LSR_PE | LSR_FE; + + if (I_IGNBRK(self->tty)) { + self->ignore_status_mask |= LSR_BI; + /* + * If we're ignore parity and break indicators, ignore + * overruns too. (For real raw support). + */ + if (I_IGNPAR(self->tty)) + self->ignore_status_mask |= LSR_OE; + } +#endif + self->settings.data_format = cval; + + ircomm_param_request(self, IRCOMM_DATA_FORMAT, FALSE); + ircomm_param_request(self, IRCOMM_FLOW_CONTROL, TRUE); +} + +/* + * Function ircomm_tty_set_termios (tty, old_termios) + * + * This routine allows the tty driver to be notified when device's + * termios settings have changed. Note that a well-designed tty driver + * should be prepared to accept the case where old == NULL, and try to + * do something rational. + */ +void ircomm_tty_set_termios(struct tty_struct *tty, + struct termios *old_termios) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data; + unsigned int cflag = tty->termios->c_cflag; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + if ((cflag == old_termios->c_cflag) && + (RELEVANT_IFLAG(tty->termios->c_iflag) == + RELEVANT_IFLAG(old_termios->c_iflag))) + { + return; + } + + ircomm_tty_change_speed(self); + + /* Handle transition to B0 status */ + if ((old_termios->c_cflag & CBAUD) && + !(cflag & CBAUD)) { + self->settings.dte &= ~(IRCOMM_DTR|IRCOMM_RTS); + ircomm_param_request(self, IRCOMM_DTE, TRUE); + } + + /* Handle transition away from B0 status */ + if (!(old_termios->c_cflag & CBAUD) && + (cflag & CBAUD)) { + self->settings.dte |= IRCOMM_DTR; + if (!(tty->termios->c_cflag & CRTSCTS) || + !test_bit(TTY_THROTTLED, &tty->flags)) { + self->settings.dte |= IRCOMM_RTS; + } + ircomm_param_request(self, IRCOMM_DTE, TRUE); + } + + /* Handle turning off CRTSCTS */ + if ((old_termios->c_cflag & CRTSCTS) && + !(tty->termios->c_cflag & CRTSCTS)) + { + tty->hw_stopped = 0; + ircomm_tty_start(tty); + } +} + +/* + * Function ircomm_tty_tiocmget (tty, file) + * + * + * + */ +int ircomm_tty_tiocmget(struct tty_struct *tty, struct file *file) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data; + unsigned int result; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + if (tty->flags & (1 << TTY_IO_ERROR)) + return -EIO; + + result = ((self->settings.dte & IRCOMM_RTS) ? TIOCM_RTS : 0) + | ((self->settings.dte & IRCOMM_DTR) ? TIOCM_DTR : 0) + | ((self->settings.dce & IRCOMM_CD) ? TIOCM_CAR : 0) + | ((self->settings.dce & IRCOMM_RI) ? TIOCM_RNG : 0) + | ((self->settings.dce & IRCOMM_DSR) ? TIOCM_DSR : 0) + | ((self->settings.dce & IRCOMM_CTS) ? TIOCM_CTS : 0); + return result; +} + +/* + * Function ircomm_tty_tiocmset (tty, file, set, clear) + * + * + * + */ +int ircomm_tty_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + if (tty->flags & (1 << TTY_IO_ERROR)) + return -EIO; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;); + + if (set & TIOCM_RTS) + self->settings.dte |= IRCOMM_RTS; + if (set & TIOCM_DTR) + self->settings.dte |= IRCOMM_DTR; + + if (clear & TIOCM_RTS) + self->settings.dte &= ~IRCOMM_RTS; + if (clear & TIOCM_DTR) + self->settings.dte &= ~IRCOMM_DTR; + + if ((set|clear) & TIOCM_RTS) + self->settings.dte |= IRCOMM_DELTA_RTS; + if ((set|clear) & TIOCM_DTR) + self->settings.dte |= IRCOMM_DELTA_DTR; + + ircomm_param_request(self, IRCOMM_DTE, TRUE); + + return 0; +} + +/* + * Function get_serial_info (driver, retinfo) + * + * + * + */ +static int ircomm_tty_get_serial_info(struct ircomm_tty_cb *self, + struct serial_struct __user *retinfo) +{ + struct serial_struct info; + + if (!retinfo) + return -EFAULT; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + memset(&info, 0, sizeof(info)); + info.line = self->line; + info.flags = self->flags; + info.baud_base = self->settings.data_rate; + info.close_delay = self->close_delay; + info.closing_wait = self->closing_wait; + + /* For compatibility */ + info.type = PORT_16550A; + info.port = 0; + info.irq = 0; + info.xmit_fifo_size = 0; + info.hub6 = 0; + info.custom_divisor = 0; + + if (copy_to_user(retinfo, &info, sizeof(*retinfo))) + return -EFAULT; + + return 0; +} + +/* + * Function set_serial_info (driver, new_info) + * + * + * + */ +static int ircomm_tty_set_serial_info(struct ircomm_tty_cb *self, + struct serial_struct __user *new_info) +{ +#if 0 + struct serial_struct new_serial; + struct ircomm_tty_cb old_state, *state; + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + if (copy_from_user(&new_serial,new_info,sizeof(new_serial))) + return -EFAULT; + + + state = self + old_state = *self; + + if (!capable(CAP_SYS_ADMIN)) { + if ((new_serial.baud_base != state->settings.data_rate) || + (new_serial.close_delay != state->close_delay) || + ((new_serial.flags & ~ASYNC_USR_MASK) != + (self->flags & ~ASYNC_USR_MASK))) + return -EPERM; + state->flags = ((state->flags & ~ASYNC_USR_MASK) | + (new_serial.flags & ASYNC_USR_MASK)); + self->flags = ((self->flags & ~ASYNC_USR_MASK) | + (new_serial.flags & ASYNC_USR_MASK)); + /* self->custom_divisor = new_serial.custom_divisor; */ + goto check_and_exit; + } + + /* + * OK, past this point, all the error checking has been done. + * At this point, we start making changes..... + */ + + if (self->settings.data_rate != new_serial.baud_base) { + self->settings.data_rate = new_serial.baud_base; + ircomm_param_request(self, IRCOMM_DATA_RATE, TRUE); + } + + self->close_delay = new_serial.close_delay * HZ/100; + self->closing_wait = new_serial.closing_wait * HZ/100; + /* self->custom_divisor = new_serial.custom_divisor; */ + + self->flags = ((self->flags & ~ASYNC_FLAGS) | + (new_serial.flags & ASYNC_FLAGS)); + self->tty->low_latency = (self->flags & ASYNC_LOW_LATENCY) ? 1 : 0; + + check_and_exit: + + if (self->flags & ASYNC_INITIALIZED) { + if (((old_state.flags & ASYNC_SPD_MASK) != + (self->flags & ASYNC_SPD_MASK)) || + (old_driver.custom_divisor != driver->custom_divisor)) { + if ((driver->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI) + driver->tty->alt_speed = 57600; + if ((driver->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI) + driver->tty->alt_speed = 115200; + if ((driver->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI) + driver->tty->alt_speed = 230400; + if ((driver->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP) + driver->tty->alt_speed = 460800; + ircomm_tty_change_speed(driver); + } + } +#endif + return 0; +} + +/* + * Function ircomm_tty_ioctl (tty, file, cmd, arg) + * + * + * + */ +int ircomm_tty_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data; + int ret = 0; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) && + (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGSTRUCT) && + (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) { + if (tty->flags & (1 << TTY_IO_ERROR)) + return -EIO; + } + + switch (cmd) { + case TIOCGSERIAL: + ret = ircomm_tty_get_serial_info(self, (struct serial_struct __user *) arg); + break; + case TIOCSSERIAL: + ret = ircomm_tty_set_serial_info(self, (struct serial_struct __user *) arg); + break; + case TIOCMIWAIT: + IRDA_DEBUG(0, "(), TIOCMIWAIT, not impl!\n"); + break; + + case TIOCGICOUNT: + IRDA_DEBUG(0, "%s(), TIOCGICOUNT not impl!\n", __FUNCTION__ ); +#if 0 + save_flags(flags); cli(); + cnow = driver->icount; + restore_flags(flags); + p_cuser = (struct serial_icounter_struct __user *) arg; + if (put_user(cnow.cts, &p_cuser->cts) || + put_user(cnow.dsr, &p_cuser->dsr) || + put_user(cnow.rng, &p_cuser->rng) || + put_user(cnow.dcd, &p_cuser->dcd) || + put_user(cnow.rx, &p_cuser->rx) || + put_user(cnow.tx, &p_cuser->tx) || + put_user(cnow.frame, &p_cuser->frame) || + put_user(cnow.overrun, &p_cuser->overrun) || + put_user(cnow.parity, &p_cuser->parity) || + put_user(cnow.brk, &p_cuser->brk) || + put_user(cnow.buf_overrun, &p_cuser->buf_overrun)) + return -EFAULT; +#endif + return 0; + default: + ret = -ENOIOCTLCMD; /* ioctls which we must ignore */ + } + return ret; +} + + + diff --git a/net/irda/irda_device.c b/net/irda/irda_device.c new file mode 100644 index 000000000000..fda299e300c0 --- /dev/null +++ b/net/irda/irda_device.c @@ -0,0 +1,489 @@ +/********************************************************************* + * + * Filename: irda_device.c + * Version: 0.9 + * Description: Utility functions used by the device drivers + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sat Oct 9 09:22:27 1999 + * Modified at: Sun Jan 23 17:41:24 2000 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1999-2000 Dag Brattli, All Rights Reserved. + * Copyright (c) 2000-2001 Jean Tourrilhes <jt@hpl.hp.com> + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + ********************************************************************/ + +#include <linux/config.h> +#include <linux/string.h> +#include <linux/proc_fs.h> +#include <linux/skbuff.h> +#include <linux/if.h> +#include <linux/if_ether.h> +#include <linux/if_arp.h> +#include <linux/netdevice.h> +#include <linux/init.h> +#include <linux/tty.h> +#include <linux/kmod.h> +#include <linux/spinlock.h> + +#include <asm/ioctls.h> +#include <asm/uaccess.h> +#include <asm/dma.h> +#include <asm/io.h> + +#include <net/irda/irda_device.h> +#include <net/irda/irlap.h> +#include <net/irda/timer.h> +#include <net/irda/wrapper.h> + +static void __irda_task_delete(struct irda_task *task); + +static hashbin_t *dongles = NULL; +static hashbin_t *tasks = NULL; + +#ifdef CONFIG_IRDA_DEBUG +static const char *task_state[] = { + "IRDA_TASK_INIT", + "IRDA_TASK_DONE", + "IRDA_TASK_WAIT", + "IRDA_TASK_WAIT1", + "IRDA_TASK_WAIT2", + "IRDA_TASK_WAIT3", + "IRDA_TASK_CHILD_INIT", + "IRDA_TASK_CHILD_WAIT", + "IRDA_TASK_CHILD_DONE", +}; +#endif /* CONFIG_IRDA_DEBUG */ + +static void irda_task_timer_expired(void *data); + +int __init irda_device_init( void) +{ + dongles = hashbin_new(HB_NOLOCK); + if (dongles == NULL) { + IRDA_WARNING("IrDA: Can't allocate dongles hashbin!\n"); + return -ENOMEM; + } + spin_lock_init(&dongles->hb_spinlock); + + tasks = hashbin_new(HB_LOCK); + if (tasks == NULL) { + IRDA_WARNING("IrDA: Can't allocate tasks hashbin!\n"); + hashbin_delete(dongles, NULL); + return -ENOMEM; + } + + /* We no longer initialise the driver ourselves here, we let + * the system do it for us... - Jean II */ + + return 0; +} + +static void __exit leftover_dongle(void *arg) +{ + struct dongle_reg *reg = arg; + IRDA_WARNING("IrDA: Dongle type %x not unregistered\n", + reg->type); +} + +void __exit irda_device_cleanup(void) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + hashbin_delete(tasks, (FREE_FUNC) __irda_task_delete); + + hashbin_delete(dongles, leftover_dongle); +} + +/* + * Function irda_device_set_media_busy (self, status) + * + * Called when we have detected that another station is transmitting + * in contention mode. + */ +void irda_device_set_media_busy(struct net_device *dev, int status) +{ + struct irlap_cb *self; + + IRDA_DEBUG(4, "%s(%s)\n", __FUNCTION__, status ? "TRUE" : "FALSE"); + + self = (struct irlap_cb *) dev->atalk_ptr; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + if (status) { + self->media_busy = TRUE; + if (status == SMALL) + irlap_start_mbusy_timer(self, SMALLBUSY_TIMEOUT); + else + irlap_start_mbusy_timer(self, MEDIABUSY_TIMEOUT); + IRDA_DEBUG( 4, "Media busy!\n"); + } else { + self->media_busy = FALSE; + irlap_stop_mbusy_timer(self); + } +} +EXPORT_SYMBOL(irda_device_set_media_busy); + + +/* + * Function irda_device_is_receiving (dev) + * + * Check if the device driver is currently receiving data + * + */ +int irda_device_is_receiving(struct net_device *dev) +{ + struct if_irda_req req; + int ret; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + if (!dev->do_ioctl) { + IRDA_ERROR("%s: do_ioctl not impl. by device driver\n", + __FUNCTION__); + return -1; + } + + ret = dev->do_ioctl(dev, (struct ifreq *) &req, SIOCGRECEIVING); + if (ret < 0) + return ret; + + return req.ifr_receiving; +} + +void irda_task_next_state(struct irda_task *task, IRDA_TASK_STATE state) +{ + IRDA_DEBUG(2, "%s(), state = %s\n", __FUNCTION__, task_state[state]); + + task->state = state; +} +EXPORT_SYMBOL(irda_task_next_state); + +static void __irda_task_delete(struct irda_task *task) +{ + del_timer(&task->timer); + + kfree(task); +} + +void irda_task_delete(struct irda_task *task) +{ + /* Unregister task */ + hashbin_remove(tasks, (long) task, NULL); + + __irda_task_delete(task); +} +EXPORT_SYMBOL(irda_task_delete); + +/* + * Function irda_task_kick (task) + * + * Tries to execute a task possible multiple times until the task is either + * finished, or askes for a timeout. When a task is finished, we do post + * processing, and notify the parent task, that is waiting for this task + * to complete. + */ +static int irda_task_kick(struct irda_task *task) +{ + int finished = TRUE; + int count = 0; + int timeout; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(task != NULL, return -1;); + IRDA_ASSERT(task->magic == IRDA_TASK_MAGIC, return -1;); + + /* Execute task until it's finished, or askes for a timeout */ + do { + timeout = task->function(task); + if (count++ > 100) { + IRDA_ERROR("%s: error in task handler!\n", + __FUNCTION__); + irda_task_delete(task); + return TRUE; + } + } while ((timeout == 0) && (task->state != IRDA_TASK_DONE)); + + if (timeout < 0) { + IRDA_ERROR("%s: Error executing task!\n", __FUNCTION__); + irda_task_delete(task); + return TRUE; + } + + /* Check if we are finished */ + if (task->state == IRDA_TASK_DONE) { + del_timer(&task->timer); + + /* Do post processing */ + if (task->finished) + task->finished(task); + + /* Notify parent */ + if (task->parent) { + /* Check if parent is waiting for us to complete */ + if (task->parent->state == IRDA_TASK_CHILD_WAIT) { + task->parent->state = IRDA_TASK_CHILD_DONE; + + /* Stop timer now that we are here */ + del_timer(&task->parent->timer); + + /* Kick parent task */ + irda_task_kick(task->parent); + } + } + irda_task_delete(task); + } else if (timeout > 0) { + irda_start_timer(&task->timer, timeout, (void *) task, + irda_task_timer_expired); + finished = FALSE; + } else { + IRDA_DEBUG(0, "%s(), not finished, and no timeout!\n", + __FUNCTION__); + finished = FALSE; + } + + return finished; +} + +/* + * Function irda_task_execute (instance, function, finished) + * + * This function registers and tries to execute tasks that may take some + * time to complete. We do it this hairy way since we may have been + * called from interrupt context, so it's not possible to use + * schedule_timeout() + * Two important notes : + * o Make sure you irda_task_delete(task); in case you delete the + * calling instance. + * o No real need to lock when calling this function, but you may + * want to lock within the task handler. + * Jean II + */ +struct irda_task *irda_task_execute(void *instance, + IRDA_TASK_CALLBACK function, + IRDA_TASK_CALLBACK finished, + struct irda_task *parent, void *param) +{ + struct irda_task *task; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + task = kmalloc(sizeof(struct irda_task), GFP_ATOMIC); + if (!task) + return NULL; + + task->state = IRDA_TASK_INIT; + task->instance = instance; + task->function = function; + task->finished = finished; + task->parent = parent; + task->param = param; + task->magic = IRDA_TASK_MAGIC; + + init_timer(&task->timer); + + /* Register task */ + hashbin_insert(tasks, (irda_queue_t *) task, (long) task, NULL); + + /* No time to waste, so lets get going! */ + return irda_task_kick(task) ? NULL : task; +} +EXPORT_SYMBOL(irda_task_execute); + +/* + * Function irda_task_timer_expired (data) + * + * Task time has expired. We now try to execute task (again), and restart + * the timer if the task has not finished yet + */ +static void irda_task_timer_expired(void *data) +{ + struct irda_task *task; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + task = (struct irda_task *) data; + + irda_task_kick(task); +} + +/* + * Function irda_device_setup (dev) + * + * This function should be used by low level device drivers in a similar way + * as ether_setup() is used by normal network device drivers + */ +static void irda_device_setup(struct net_device *dev) +{ + dev->hard_header_len = 0; + dev->addr_len = 0; + + dev->type = ARPHRD_IRDA; + dev->tx_queue_len = 8; /* Window size + 1 s-frame */ + + memset(dev->broadcast, 0xff, 4); + + dev->mtu = 2048; + dev->flags = IFF_NOARP; +} + +/* + * Funciton alloc_irdadev + * Allocates and sets up an IRDA device in a manner similar to + * alloc_etherdev. + */ +struct net_device *alloc_irdadev(int sizeof_priv) +{ + return alloc_netdev(sizeof_priv, "irda%d", irda_device_setup); +} +EXPORT_SYMBOL(alloc_irdadev); + +/* + * Function irda_device_init_dongle (self, type, qos) + * + * Initialize attached dongle. + * + * Important : request_module require us to call this function with + * a process context and irq enabled. - Jean II + */ +dongle_t *irda_device_dongle_init(struct net_device *dev, int type) +{ + struct dongle_reg *reg; + dongle_t *dongle = NULL; + + might_sleep(); + + spin_lock(&dongles->hb_spinlock); + reg = hashbin_find(dongles, type, NULL); + +#ifdef CONFIG_KMOD + /* Try to load the module needed */ + if (!reg && capable(CAP_SYS_MODULE)) { + spin_unlock(&dongles->hb_spinlock); + + request_module("irda-dongle-%d", type); + + spin_lock(&dongles->hb_spinlock); + reg = hashbin_find(dongles, type, NULL); + } +#endif + + if (!reg || !try_module_get(reg->owner) ) { + IRDA_ERROR("IrDA: Unable to find requested dongle type %x\n", + type); + goto out; + } + + /* Allocate dongle info for this instance */ + dongle = kmalloc(sizeof(dongle_t), GFP_KERNEL); + if (!dongle) + goto out; + + memset(dongle, 0, sizeof(dongle_t)); + + /* Bind the registration info to this particular instance */ + dongle->issue = reg; + dongle->dev = dev; + + out: + spin_unlock(&dongles->hb_spinlock); + return dongle; +} +EXPORT_SYMBOL(irda_device_dongle_init); + +/* + * Function irda_device_dongle_cleanup (dongle) + */ +int irda_device_dongle_cleanup(dongle_t *dongle) +{ + IRDA_ASSERT(dongle != NULL, return -1;); + + dongle->issue->close(dongle); + module_put(dongle->issue->owner); + kfree(dongle); + + return 0; +} +EXPORT_SYMBOL(irda_device_dongle_cleanup); + +/* + * Function irda_device_register_dongle (dongle) + */ +int irda_device_register_dongle(struct dongle_reg *new) +{ + spin_lock(&dongles->hb_spinlock); + /* Check if this dongle has been registered before */ + if (hashbin_find(dongles, new->type, NULL)) { + IRDA_MESSAGE("%s: Dongle type %x already registered\n", + __FUNCTION__, new->type); + } else { + /* Insert IrDA dongle into hashbin */ + hashbin_insert(dongles, (irda_queue_t *) new, new->type, NULL); + } + spin_unlock(&dongles->hb_spinlock); + + return 0; +} +EXPORT_SYMBOL(irda_device_register_dongle); + +/* + * Function irda_device_unregister_dongle (dongle) + * + * Unregister dongle, and remove dongle from list of registered dongles + * + */ +void irda_device_unregister_dongle(struct dongle_reg *dongle) +{ + struct dongle *node; + + spin_lock(&dongles->hb_spinlock); + node = hashbin_remove(dongles, dongle->type, NULL); + if (!node) + IRDA_ERROR("%s: dongle not found!\n", __FUNCTION__); + spin_unlock(&dongles->hb_spinlock); +} +EXPORT_SYMBOL(irda_device_unregister_dongle); + +#ifdef CONFIG_ISA +/* + * Function setup_dma (idev, buffer, count, mode) + * + * Setup the DMA channel. Commonly used by ISA FIR drivers + * + */ +void irda_setup_dma(int channel, dma_addr_t buffer, int count, int mode) +{ + unsigned long flags; + + flags = claim_dma_lock(); + + disable_dma(channel); + clear_dma_ff(channel); + set_dma_mode(channel, mode); + set_dma_addr(channel, buffer); + set_dma_count(channel, count); + enable_dma(channel); + + release_dma_lock(flags); +} +EXPORT_SYMBOL(irda_setup_dma); +#endif diff --git a/net/irda/iriap.c b/net/irda/iriap.c new file mode 100644 index 000000000000..b8bb78af8b8a --- /dev/null +++ b/net/irda/iriap.c @@ -0,0 +1,1089 @@ +/********************************************************************* + * + * Filename: iriap.c + * Version: 0.8 + * Description: Information Access Protocol (IAP) + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Thu Aug 21 00:02:07 1997 + * Modified at: Sat Dec 25 16:42:42 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1998-1999 Dag Brattli <dagb@cs.uit.no>, + * All Rights Reserved. + * Copyright (c) 2000-2003 Jean Tourrilhes <jt@hpl.hp.com> + * + * 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/skbuff.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/seq_file.h> + +#include <asm/byteorder.h> +#include <asm/unaligned.h> + +#include <net/irda/irda.h> +#include <net/irda/irttp.h> +#include <net/irda/irlmp.h> +#include <net/irda/irias_object.h> +#include <net/irda/iriap_event.h> +#include <net/irda/iriap.h> + +#ifdef CONFIG_IRDA_DEBUG +/* FIXME: This one should go in irlmp.c */ +static const char *ias_charset_types[] = { + "CS_ASCII", + "CS_ISO_8859_1", + "CS_ISO_8859_2", + "CS_ISO_8859_3", + "CS_ISO_8859_4", + "CS_ISO_8859_5", + "CS_ISO_8859_6", + "CS_ISO_8859_7", + "CS_ISO_8859_8", + "CS_ISO_8859_9", + "CS_UNICODE" +}; +#endif /* CONFIG_IRDA_DEBUG */ + +static hashbin_t *iriap = NULL; +static void *service_handle; + +static void __iriap_close(struct iriap_cb *self); +static int iriap_register_lsap(struct iriap_cb *self, __u8 slsap_sel, int mode); +static void iriap_disconnect_indication(void *instance, void *sap, + LM_REASON reason, struct sk_buff *skb); +static void iriap_connect_indication(void *instance, void *sap, + struct qos_info *qos, __u32 max_sdu_size, + __u8 max_header_size, + struct sk_buff *skb); +static void iriap_connect_confirm(void *instance, void *sap, + struct qos_info *qos, + __u32 max_sdu_size, __u8 max_header_size, + struct sk_buff *skb); +static int iriap_data_indication(void *instance, void *sap, + struct sk_buff *skb); + +static void iriap_watchdog_timer_expired(void *data); + +static inline void iriap_start_watchdog_timer(struct iriap_cb *self, + int timeout) +{ + irda_start_timer(&self->watchdog_timer, timeout, self, + iriap_watchdog_timer_expired); +} + +/* + * Function iriap_init (void) + * + * Initializes the IrIAP layer, called by the module initialization code + * in irmod.c + */ +int __init iriap_init(void) +{ + struct ias_object *obj; + struct iriap_cb *server; + __u8 oct_seq[6]; + __u16 hints; + + /* Allocate master array */ + iriap = hashbin_new(HB_LOCK); + if (!iriap) + return -ENOMEM; + + /* Object repository - defined in irias_object.c */ + irias_objects = hashbin_new(HB_LOCK); + if (!irias_objects) { + IRDA_WARNING("%s: Can't allocate irias_objects hashbin!\n", + __FUNCTION__); + hashbin_delete(iriap, NULL); + return -ENOMEM; + } + + /* + * Register some default services for IrLMP + */ + hints = irlmp_service_to_hint(S_COMPUTER); + service_handle = irlmp_register_service(hints); + + /* Register the Device object with LM-IAS */ + obj = irias_new_object("Device", IAS_DEVICE_ID); + irias_add_string_attrib(obj, "DeviceName", "Linux", IAS_KERNEL_ATTR); + + oct_seq[0] = 0x01; /* Version 1 */ + oct_seq[1] = 0x00; /* IAS support bits */ + oct_seq[2] = 0x00; /* LM-MUX support bits */ +#ifdef CONFIG_IRDA_ULTRA + oct_seq[2] |= 0x04; /* Connectionless Data support */ +#endif + irias_add_octseq_attrib(obj, "IrLMPSupport", oct_seq, 3, + IAS_KERNEL_ATTR); + irias_insert_object(obj); + + /* + * Register server support with IrLMP so we can accept incoming + * connections + */ + server = iriap_open(LSAP_IAS, IAS_SERVER, NULL, NULL); + if (!server) { + IRDA_DEBUG(0, "%s(), unable to open server\n", __FUNCTION__); + return -1; + } + iriap_register_lsap(server, LSAP_IAS, IAS_SERVER); + + return 0; +} + +/* + * Function iriap_cleanup (void) + * + * Initializes the IrIAP layer, called by the module cleanup code in + * irmod.c + */ +void __exit iriap_cleanup(void) +{ + irlmp_unregister_service(service_handle); + + hashbin_delete(iriap, (FREE_FUNC) __iriap_close); + hashbin_delete(irias_objects, (FREE_FUNC) __irias_delete_object); +} + +/* + * Function iriap_open (void) + * + * Opens an instance of the IrIAP layer, and registers with IrLMP + */ +struct iriap_cb *iriap_open(__u8 slsap_sel, int mode, void *priv, + CONFIRM_CALLBACK callback) +{ + struct iriap_cb *self; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + self = kmalloc(sizeof(struct iriap_cb), GFP_ATOMIC); + if (!self) { + IRDA_WARNING("%s: Unable to kmalloc!\n", __FUNCTION__); + return NULL; + } + + /* + * Initialize instance + */ + memset(self, 0, sizeof(struct iriap_cb)); + + self->magic = IAS_MAGIC; + self->mode = mode; + if (mode == IAS_CLIENT) + iriap_register_lsap(self, slsap_sel, mode); + + self->confirm = callback; + self->priv = priv; + + /* iriap_getvaluebyclass_request() will construct packets before + * we connect, so this must have a sane value... Jean II */ + self->max_header_size = LMP_MAX_HEADER; + + init_timer(&self->watchdog_timer); + + hashbin_insert(iriap, (irda_queue_t *) self, (long) self, NULL); + + /* Initialize state machines */ + iriap_next_client_state(self, S_DISCONNECT); + iriap_next_call_state(self, S_MAKE_CALL); + iriap_next_server_state(self, R_DISCONNECT); + iriap_next_r_connect_state(self, R_WAITING); + + return self; +} +EXPORT_SYMBOL(iriap_open); + +/* + * Function __iriap_close (self) + * + * Removes (deallocates) the IrIAP instance + * + */ +static void __iriap_close(struct iriap_cb *self) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + del_timer(&self->watchdog_timer); + + if (self->request_skb) + dev_kfree_skb(self->request_skb); + + self->magic = 0; + + kfree(self); +} + +/* + * Function iriap_close (void) + * + * Closes IrIAP and deregisters with IrLMP + */ +void iriap_close(struct iriap_cb *self) +{ + struct iriap_cb *entry; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + if (self->lsap) { + irlmp_close_lsap(self->lsap); + self->lsap = NULL; + } + + entry = (struct iriap_cb *) hashbin_remove(iriap, (long) self, NULL); + IRDA_ASSERT(entry == self, return;); + + __iriap_close(self); +} +EXPORT_SYMBOL(iriap_close); + +static int iriap_register_lsap(struct iriap_cb *self, __u8 slsap_sel, int mode) +{ + notify_t notify; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + irda_notify_init(¬ify); + notify.connect_confirm = iriap_connect_confirm; + notify.connect_indication = iriap_connect_indication; + notify.disconnect_indication = iriap_disconnect_indication; + notify.data_indication = iriap_data_indication; + notify.instance = self; + if (mode == IAS_CLIENT) + strcpy(notify.name, "IrIAS cli"); + else + strcpy(notify.name, "IrIAS srv"); + + self->lsap = irlmp_open_lsap(slsap_sel, ¬ify, 0); + if (self->lsap == NULL) { + IRDA_ERROR("%s: Unable to allocated LSAP!\n", __FUNCTION__); + return -1; + } + self->slsap_sel = self->lsap->slsap_sel; + + return 0; +} + +/* + * Function iriap_disconnect_indication (handle, reason) + * + * Got disconnect, so clean up everything associated with this connection + * + */ +static void iriap_disconnect_indication(void *instance, void *sap, + LM_REASON reason, + struct sk_buff *skb) +{ + struct iriap_cb *self; + + IRDA_DEBUG(4, "%s(), reason=%s\n", __FUNCTION__, irlmp_reasons[reason]); + + self = (struct iriap_cb *) instance; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + IRDA_ASSERT(iriap != NULL, return;); + + del_timer(&self->watchdog_timer); + + /* Not needed */ + if (skb) + dev_kfree_skb(skb); + + if (self->mode == IAS_CLIENT) { + IRDA_DEBUG(4, "%s(), disconnect as client\n", __FUNCTION__); + + + iriap_do_client_event(self, IAP_LM_DISCONNECT_INDICATION, + NULL); + /* + * Inform service user that the request failed by sending + * it a NULL value. Warning, the client might close us, so + * remember no to use self anymore after calling confirm + */ + if (self->confirm) + self->confirm(IAS_DISCONNECT, 0, NULL, self->priv); + } else { + IRDA_DEBUG(4, "%s(), disconnect as server\n", __FUNCTION__); + iriap_do_server_event(self, IAP_LM_DISCONNECT_INDICATION, + NULL); + iriap_close(self); + } +} + +/* + * Function iriap_disconnect_request (handle) + */ +static void iriap_disconnect_request(struct iriap_cb *self) +{ + struct sk_buff *tx_skb; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + tx_skb = dev_alloc_skb(64); + if (tx_skb == NULL) { + IRDA_DEBUG(0, "%s(), Could not allocate an sk_buff of length %d\n", + __FUNCTION__, 64); + return; + } + + /* + * Reserve space for MUX control and LAP header + */ + skb_reserve(tx_skb, LMP_MAX_HEADER); + + irlmp_disconnect_request(self->lsap, tx_skb); +} + +/* + * Function iriap_getvaluebyclass (addr, name, attr) + * + * Retreive all values from attribute in all objects with given class + * name + */ +int iriap_getvaluebyclass_request(struct iriap_cb *self, + __u32 saddr, __u32 daddr, + char *name, char *attr) +{ + struct sk_buff *tx_skb; + int name_len, attr_len, skb_len; + __u8 *frame; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return -1;); + + /* Client must supply the destination device address */ + if (!daddr) + return -1; + + self->daddr = daddr; + self->saddr = saddr; + + /* + * Save operation, so we know what the later indication is about + */ + self->operation = GET_VALUE_BY_CLASS; + + /* Give ourselves 10 secs to finish this operation */ + iriap_start_watchdog_timer(self, 10*HZ); + + name_len = strlen(name); /* Up to IAS_MAX_CLASSNAME = 60 */ + attr_len = strlen(attr); /* Up to IAS_MAX_ATTRIBNAME = 60 */ + + skb_len = self->max_header_size+2+name_len+1+attr_len+4; + tx_skb = dev_alloc_skb(skb_len); + if (!tx_skb) + return -ENOMEM; + + /* Reserve space for MUX and LAP header */ + skb_reserve(tx_skb, self->max_header_size); + skb_put(tx_skb, 3+name_len+attr_len); + frame = tx_skb->data; + + /* Build frame */ + frame[0] = IAP_LST | GET_VALUE_BY_CLASS; + frame[1] = name_len; /* Insert length of name */ + memcpy(frame+2, name, name_len); /* Insert name */ + frame[2+name_len] = attr_len; /* Insert length of attr */ + memcpy(frame+3+name_len, attr, attr_len); /* Insert attr */ + + iriap_do_client_event(self, IAP_CALL_REQUEST_GVBC, tx_skb); + + /* Drop reference count - see state_s_disconnect(). */ + dev_kfree_skb(tx_skb); + + return 0; +} +EXPORT_SYMBOL(iriap_getvaluebyclass_request); + +/* + * Function iriap_getvaluebyclass_confirm (self, skb) + * + * Got result from GetValueByClass command. Parse it and return result + * to service user. + * + */ +static void iriap_getvaluebyclass_confirm(struct iriap_cb *self, + struct sk_buff *skb) +{ + struct ias_value *value; + int charset; + __u32 value_len; + __u32 tmp_cpu32; + __u16 obj_id; + __u16 len; + __u8 type; + __u8 *fp; + int n; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + IRDA_ASSERT(skb != NULL, return;); + + /* Initialize variables */ + fp = skb->data; + n = 2; + + /* Get length, MSB first */ + len = be16_to_cpu(get_unaligned((__u16 *)(fp+n))); n += 2; + + IRDA_DEBUG(4, "%s(), len=%d\n", __FUNCTION__, len); + + /* Get object ID, MSB first */ + obj_id = be16_to_cpu(get_unaligned((__u16 *)(fp+n))); n += 2; + + type = fp[n++]; + IRDA_DEBUG(4, "%s(), Value type = %d\n", __FUNCTION__, type); + + switch (type) { + case IAS_INTEGER: + memcpy(&tmp_cpu32, fp+n, 4); n += 4; + be32_to_cpus(&tmp_cpu32); + value = irias_new_integer_value(tmp_cpu32); + + /* Legal values restricted to 0x01-0x6f, page 15 irttp */ + IRDA_DEBUG(4, "%s(), lsap=%d\n", __FUNCTION__, value->t.integer); + break; + case IAS_STRING: + charset = fp[n++]; + + switch (charset) { + case CS_ASCII: + break; +/* case CS_ISO_8859_1: */ +/* case CS_ISO_8859_2: */ +/* case CS_ISO_8859_3: */ +/* case CS_ISO_8859_4: */ +/* case CS_ISO_8859_5: */ +/* case CS_ISO_8859_6: */ +/* case CS_ISO_8859_7: */ +/* case CS_ISO_8859_8: */ +/* case CS_ISO_8859_9: */ +/* case CS_UNICODE: */ + default: + IRDA_DEBUG(0, "%s(), charset %s, not supported\n", + __FUNCTION__, ias_charset_types[charset]); + + /* Aborting, close connection! */ + iriap_disconnect_request(self); + return; + /* break; */ + } + value_len = fp[n++]; + IRDA_DEBUG(4, "%s(), strlen=%d\n", __FUNCTION__, value_len); + + /* Make sure the string is null-terminated */ + fp[n+value_len] = 0x00; + IRDA_DEBUG(4, "Got string %s\n", fp+n); + + /* Will truncate to IAS_MAX_STRING bytes */ + value = irias_new_string_value(fp+n); + break; + case IAS_OCT_SEQ: + value_len = be16_to_cpu(get_unaligned((__u16 *)(fp+n))); + n += 2; + + /* Will truncate to IAS_MAX_OCTET_STRING bytes */ + value = irias_new_octseq_value(fp+n, value_len); + break; + default: + value = irias_new_missing_value(); + break; + } + + /* Finished, close connection! */ + iriap_disconnect_request(self); + + /* Warning, the client might close us, so remember no to use self + * anymore after calling confirm + */ + if (self->confirm) + self->confirm(IAS_SUCCESS, obj_id, value, self->priv); + else { + IRDA_DEBUG(0, "%s(), missing handler!\n", __FUNCTION__); + irias_delete_value(value); + } +} + +/* + * Function iriap_getvaluebyclass_response () + * + * Send answer back to remote LM-IAS + * + */ +static void iriap_getvaluebyclass_response(struct iriap_cb *self, + __u16 obj_id, + __u8 ret_code, + struct ias_value *value) +{ + struct sk_buff *tx_skb; + int n; + __u32 tmp_be32, tmp_be16; + __u8 *fp; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + IRDA_ASSERT(value != NULL, return;); + IRDA_ASSERT(value->len <= 1024, return;); + + /* Initialize variables */ + n = 0; + + /* + * We must adjust the size of the response after the length of the + * value. We add 32 bytes because of the 6 bytes for the frame and + * max 5 bytes for the value coding. + */ + tx_skb = dev_alloc_skb(value->len + self->max_header_size + 32); + if (!tx_skb) + return; + + /* Reserve space for MUX and LAP header */ + skb_reserve(tx_skb, self->max_header_size); + skb_put(tx_skb, 6); + + fp = tx_skb->data; + + /* Build frame */ + fp[n++] = GET_VALUE_BY_CLASS | IAP_LST; + fp[n++] = ret_code; + + /* Insert list length (MSB first) */ + tmp_be16 = __constant_htons(0x0001); + memcpy(fp+n, &tmp_be16, 2); n += 2; + + /* Insert object identifier ( MSB first) */ + tmp_be16 = cpu_to_be16(obj_id); + memcpy(fp+n, &tmp_be16, 2); n += 2; + + switch (value->type) { + case IAS_STRING: + skb_put(tx_skb, 3 + value->len); + fp[n++] = value->type; + fp[n++] = 0; /* ASCII */ + fp[n++] = (__u8) value->len; + memcpy(fp+n, value->t.string, value->len); n+=value->len; + break; + case IAS_INTEGER: + skb_put(tx_skb, 5); + fp[n++] = value->type; + + tmp_be32 = cpu_to_be32(value->t.integer); + memcpy(fp+n, &tmp_be32, 4); n += 4; + break; + case IAS_OCT_SEQ: + skb_put(tx_skb, 3 + value->len); + fp[n++] = value->type; + + tmp_be16 = cpu_to_be16(value->len); + memcpy(fp+n, &tmp_be16, 2); n += 2; + memcpy(fp+n, value->t.oct_seq, value->len); n+=value->len; + break; + case IAS_MISSING: + IRDA_DEBUG( 3, "%s: sending IAS_MISSING\n", __FUNCTION__); + skb_put(tx_skb, 1); + fp[n++] = value->type; + break; + default: + IRDA_DEBUG(0, "%s(), type not implemented!\n", __FUNCTION__); + break; + } + iriap_do_r_connect_event(self, IAP_CALL_RESPONSE, tx_skb); + + /* Drop reference count - see state_r_execute(). */ + dev_kfree_skb(tx_skb); +} + +/* + * Function iriap_getvaluebyclass_indication (self, skb) + * + * getvaluebyclass is requested from peer LM-IAS + * + */ +static void iriap_getvaluebyclass_indication(struct iriap_cb *self, + struct sk_buff *skb) +{ + struct ias_object *obj; + struct ias_attrib *attrib; + int name_len; + int attr_len; + char name[IAS_MAX_CLASSNAME + 1]; /* 60 bytes */ + char attr[IAS_MAX_ATTRIBNAME + 1]; /* 60 bytes */ + __u8 *fp; + int n; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + IRDA_ASSERT(skb != NULL, return;); + + fp = skb->data; + n = 1; + + name_len = fp[n++]; + memcpy(name, fp+n, name_len); n+=name_len; + name[name_len] = '\0'; + + attr_len = fp[n++]; + memcpy(attr, fp+n, attr_len); n+=attr_len; + attr[attr_len] = '\0'; + + IRDA_DEBUG(4, "LM-IAS: Looking up %s: %s\n", name, attr); + obj = irias_find_object(name); + + if (obj == NULL) { + IRDA_DEBUG(2, "LM-IAS: Object %s not found\n", name); + iriap_getvaluebyclass_response(self, 0x1235, IAS_CLASS_UNKNOWN, + &irias_missing); + return; + } + IRDA_DEBUG(4, "LM-IAS: found %s, id=%d\n", obj->name, obj->id); + + attrib = irias_find_attrib(obj, attr); + if (attrib == NULL) { + IRDA_DEBUG(2, "LM-IAS: Attribute %s not found\n", attr); + iriap_getvaluebyclass_response(self, obj->id, + IAS_ATTRIB_UNKNOWN, + &irias_missing); + return; + } + + /* We have a match; send the value. */ + iriap_getvaluebyclass_response(self, obj->id, IAS_SUCCESS, + attrib->value); + + return; +} + +/* + * Function iriap_send_ack (void) + * + * Currently not used + * + */ +void iriap_send_ack(struct iriap_cb *self) +{ + struct sk_buff *tx_skb; + __u8 *frame; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + tx_skb = dev_alloc_skb(64); + if (!tx_skb) + return; + + /* Reserve space for MUX and LAP header */ + skb_reserve(tx_skb, self->max_header_size); + skb_put(tx_skb, 1); + frame = tx_skb->data; + + /* Build frame */ + frame[0] = IAP_LST | IAP_ACK | self->operation; + + irlmp_data_request(self->lsap, tx_skb); +} + +void iriap_connect_request(struct iriap_cb *self) +{ + int ret; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + ret = irlmp_connect_request(self->lsap, LSAP_IAS, + self->saddr, self->daddr, + NULL, NULL); + if (ret < 0) { + IRDA_DEBUG(0, "%s(), connect failed!\n", __FUNCTION__); + self->confirm(IAS_DISCONNECT, 0, NULL, self->priv); + } +} + +/* + * Function iriap_connect_confirm (handle, skb) + * + * LSAP connection confirmed! + * + */ +static void iriap_connect_confirm(void *instance, void *sap, + struct qos_info *qos, __u32 max_seg_size, + __u8 max_header_size, + struct sk_buff *skb) +{ + struct iriap_cb *self; + + self = (struct iriap_cb *) instance; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + IRDA_ASSERT(skb != NULL, return;); + + self->max_data_size = max_seg_size; + self->max_header_size = max_header_size; + + del_timer(&self->watchdog_timer); + + iriap_do_client_event(self, IAP_LM_CONNECT_CONFIRM, skb); + + /* Drop reference count - see state_s_make_call(). */ + dev_kfree_skb(skb); +} + +/* + * Function iriap_connect_indication ( handle, skb) + * + * Remote LM-IAS is requesting connection + * + */ +static void iriap_connect_indication(void *instance, void *sap, + struct qos_info *qos, __u32 max_seg_size, + __u8 max_header_size, + struct sk_buff *skb) +{ + struct iriap_cb *self, *new; + + IRDA_DEBUG(1, "%s()\n", __FUNCTION__); + + self = (struct iriap_cb *) instance; + + IRDA_ASSERT(skb != NULL, return;); + IRDA_ASSERT(self != NULL, goto out;); + IRDA_ASSERT(self->magic == IAS_MAGIC, goto out;); + + /* Start new server */ + new = iriap_open(LSAP_IAS, IAS_SERVER, NULL, NULL); + if (!new) { + IRDA_DEBUG(0, "%s(), open failed\n", __FUNCTION__); + goto out; + } + + /* Now attach up the new "socket" */ + new->lsap = irlmp_dup(self->lsap, new); + if (!new->lsap) { + IRDA_DEBUG(0, "%s(), dup failed!\n", __FUNCTION__); + goto out; + } + + new->max_data_size = max_seg_size; + new->max_header_size = max_header_size; + + /* Clean up the original one to keep it in listen state */ + irlmp_listen(self->lsap); + + iriap_do_server_event(new, IAP_LM_CONNECT_INDICATION, skb); + +out: + /* Drop reference count - see state_r_disconnect(). */ + dev_kfree_skb(skb); +} + +/* + * Function iriap_data_indication (handle, skb) + * + * Receives data from connection identified by handle from IrLMP + * + */ +static int iriap_data_indication(void *instance, void *sap, + struct sk_buff *skb) +{ + struct iriap_cb *self; + __u8 *frame; + __u8 opcode; + + IRDA_DEBUG(3, "%s()\n", __FUNCTION__); + + self = (struct iriap_cb *) instance; + + IRDA_ASSERT(skb != NULL, return 0;); + IRDA_ASSERT(self != NULL, goto out;); + IRDA_ASSERT(self->magic == IAS_MAGIC, goto out;); + + frame = skb->data; + + if (self->mode == IAS_SERVER) { + /* Call server */ + IRDA_DEBUG(4, "%s(), Calling server!\n", __FUNCTION__); + iriap_do_r_connect_event(self, IAP_RECV_F_LST, skb); + goto out; + } + opcode = frame[0]; + if (~opcode & IAP_LST) { + IRDA_WARNING("%s:, IrIAS multiframe commands or " + "results is not implemented yet!\n", + __FUNCTION__); + goto out; + } + + /* Check for ack frames since they don't contain any data */ + if (opcode & IAP_ACK) { + IRDA_DEBUG(0, "%s() Got ack frame!\n", __FUNCTION__); + goto out; + } + + opcode &= ~IAP_LST; /* Mask away LST bit */ + + switch (opcode) { + case GET_INFO_BASE: + IRDA_DEBUG(0, "IrLMP GetInfoBaseDetails not implemented!\n"); + break; + case GET_VALUE_BY_CLASS: + iriap_do_call_event(self, IAP_RECV_F_LST, NULL); + + switch (frame[1]) { + case IAS_SUCCESS: + iriap_getvaluebyclass_confirm(self, skb); + break; + case IAS_CLASS_UNKNOWN: + IRDA_DEBUG(1, "%s(), No such class!\n", __FUNCTION__); + /* Finished, close connection! */ + iriap_disconnect_request(self); + + /* + * Warning, the client might close us, so remember + * no to use self anymore after calling confirm + */ + if (self->confirm) + self->confirm(IAS_CLASS_UNKNOWN, 0, NULL, + self->priv); + break; + case IAS_ATTRIB_UNKNOWN: + IRDA_DEBUG(1, "%s(), No such attribute!\n", __FUNCTION__); + /* Finished, close connection! */ + iriap_disconnect_request(self); + + /* + * Warning, the client might close us, so remember + * no to use self anymore after calling confirm + */ + if (self->confirm) + self->confirm(IAS_ATTRIB_UNKNOWN, 0, NULL, + self->priv); + break; + } + break; + default: + IRDA_DEBUG(0, "%s(), Unknown op-code: %02x\n", __FUNCTION__, + opcode); + break; + } + +out: + /* Cleanup - sub-calls will have done skb_get() as needed. */ + dev_kfree_skb(skb); + return 0; +} + +/* + * Function iriap_call_indication (self, skb) + * + * Received call to server from peer LM-IAS + * + */ +void iriap_call_indication(struct iriap_cb *self, struct sk_buff *skb) +{ + __u8 *fp; + __u8 opcode; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + IRDA_ASSERT(skb != NULL, return;); + + fp = skb->data; + + opcode = fp[0]; + if (~opcode & 0x80) { + IRDA_WARNING("%s: IrIAS multiframe commands or results" + "is not implemented yet!\n", __FUNCTION__); + return; + } + opcode &= 0x7f; /* Mask away LST bit */ + + switch (opcode) { + case GET_INFO_BASE: + IRDA_WARNING("%s: GetInfoBaseDetails not implemented yet!\n", + __FUNCTION__); + break; + case GET_VALUE_BY_CLASS: + iriap_getvaluebyclass_indication(self, skb); + break; + } + /* skb will be cleaned up in iriap_data_indication */ +} + +/* + * Function iriap_watchdog_timer_expired (data) + * + * Query has taken too long time, so abort + * + */ +static void iriap_watchdog_timer_expired(void *data) +{ + struct iriap_cb *self = (struct iriap_cb *) data; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + /* iriap_close(self); */ +} + +#ifdef CONFIG_PROC_FS + +static const char *ias_value_types[] = { + "IAS_MISSING", + "IAS_INTEGER", + "IAS_OCT_SEQ", + "IAS_STRING" +}; + +static inline struct ias_object *irias_seq_idx(loff_t pos) +{ + struct ias_object *obj; + + for (obj = (struct ias_object *) hashbin_get_first(irias_objects); + obj; obj = (struct ias_object *) hashbin_get_next(irias_objects)) { + if (pos-- == 0) + break; + } + + return obj; +} + +static void *irias_seq_start(struct seq_file *seq, loff_t *pos) +{ + spin_lock_irq(&irias_objects->hb_spinlock); + + return *pos ? irias_seq_idx(*pos - 1) : SEQ_START_TOKEN; +} + +static void *irias_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + ++*pos; + + return (v == SEQ_START_TOKEN) + ? (void *) hashbin_get_first(irias_objects) + : (void *) hashbin_get_next(irias_objects); +} + +static void irias_seq_stop(struct seq_file *seq, void *v) +{ + spin_unlock_irq(&irias_objects->hb_spinlock); +} + +static int irias_seq_show(struct seq_file *seq, void *v) +{ + if (v == SEQ_START_TOKEN) + seq_puts(seq, "LM-IAS Objects:\n"); + else { + struct ias_object *obj = v; + struct ias_attrib *attrib; + + IRDA_ASSERT(obj->magic == IAS_OBJECT_MAGIC, return -EINVAL;); + + seq_printf(seq, "name: %s, id=%d\n", + obj->name, obj->id); + + /* Careful for priority inversions here ! + * All other uses of attrib spinlock are independent of + * the object spinlock, so we are safe. Jean II */ + spin_lock(&obj->attribs->hb_spinlock); + + /* List all attributes for this object */ + for (attrib = (struct ias_attrib *) hashbin_get_first(obj->attribs); + attrib != NULL; + attrib = (struct ias_attrib *) hashbin_get_next(obj->attribs)) { + + IRDA_ASSERT(attrib->magic == IAS_ATTRIB_MAGIC, + goto outloop; ); + + seq_printf(seq, " - Attribute name: \"%s\", ", + attrib->name); + seq_printf(seq, "value[%s]: ", + ias_value_types[attrib->value->type]); + + switch (attrib->value->type) { + case IAS_INTEGER: + seq_printf(seq, "%d\n", + attrib->value->t.integer); + break; + case IAS_STRING: + seq_printf(seq, "\"%s\"\n", + attrib->value->t.string); + break; + case IAS_OCT_SEQ: + seq_printf(seq, "octet sequence (%d bytes)\n", + attrib->value->len); + break; + case IAS_MISSING: + seq_puts(seq, "missing\n"); + break; + default: + seq_printf(seq, "type %d?\n", + attrib->value->type); + } + seq_putc(seq, '\n'); + + } + IRDA_ASSERT_LABEL(outloop:) + spin_unlock(&obj->attribs->hb_spinlock); + } + + return 0; +} + +static struct seq_operations irias_seq_ops = { + .start = irias_seq_start, + .next = irias_seq_next, + .stop = irias_seq_stop, + .show = irias_seq_show, +}; + +static int irias_seq_open(struct inode *inode, struct file *file) +{ + IRDA_ASSERT( irias_objects != NULL, return -EINVAL;); + + return seq_open(file, &irias_seq_ops); +} + +struct file_operations irias_seq_fops = { + .owner = THIS_MODULE, + .open = irias_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +#endif /* PROC_FS */ diff --git a/net/irda/iriap_event.c b/net/irda/iriap_event.c new file mode 100644 index 000000000000..a73607450de1 --- /dev/null +++ b/net/irda/iriap_event.c @@ -0,0 +1,502 @@ +/********************************************************************* + * + * Filename: iriap_event.c + * Version: 0.1 + * Description: IAP Finite State Machine + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Thu Aug 21 00:02:07 1997 + * Modified at: Wed Mar 1 11:28:34 2000 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1997, 1999-2000 Dag Brattli <dagb@cs.uit.no>, + * All Rights Reserved. + * Copyright (c) 2000-2003 Jean Tourrilhes <jt@hpl.hp.com> + * + * 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <net/irda/irda.h> +#include <net/irda/irlmp.h> +#include <net/irda/iriap.h> +#include <net/irda/iriap_event.h> + +static void state_s_disconnect (struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); +static void state_s_connecting (struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); +static void state_s_call (struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); + +static void state_s_make_call (struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); +static void state_s_calling (struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); +static void state_s_outstanding (struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); +static void state_s_replying (struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); +static void state_s_wait_for_call(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); +static void state_s_wait_active (struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); + +static void state_r_disconnect (struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); +static void state_r_call (struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); +static void state_r_waiting (struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); +static void state_r_wait_active (struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); +static void state_r_receiving (struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); +static void state_r_execute (struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); +static void state_r_returning (struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb); + +static void (*iriap_state[])(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) = { + /* Client FSM */ + state_s_disconnect, + state_s_connecting, + state_s_call, + + /* S-Call FSM */ + state_s_make_call, + state_s_calling, + state_s_outstanding, + state_s_replying, + state_s_wait_for_call, + state_s_wait_active, + + /* Server FSM */ + state_r_disconnect, + state_r_call, + + /* R-Connect FSM */ + state_r_waiting, + state_r_wait_active, + state_r_receiving, + state_r_execute, + state_r_returning, +}; + +void iriap_next_client_state(struct iriap_cb *self, IRIAP_STATE state) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + self->client_state = state; +} + +void iriap_next_call_state(struct iriap_cb *self, IRIAP_STATE state) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + self->call_state = state; +} + +void iriap_next_server_state(struct iriap_cb *self, IRIAP_STATE state) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + self->server_state = state; +} + +void iriap_next_r_connect_state(struct iriap_cb *self, IRIAP_STATE state) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + self->r_connect_state = state; +} + +void iriap_do_client_event(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + (*iriap_state[ self->client_state]) (self, event, skb); +} + +void iriap_do_call_event(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + (*iriap_state[ self->call_state]) (self, event, skb); +} + +void iriap_do_server_event(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + (*iriap_state[ self->server_state]) (self, event, skb); +} + +void iriap_do_r_connect_event(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + (*iriap_state[ self->r_connect_state]) (self, event, skb); +} + + +/* + * Function state_s_disconnect (event, skb) + * + * S-Disconnect, The device has no LSAP connection to a particular + * remote device. + */ +static void state_s_disconnect(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + switch (event) { + case IAP_CALL_REQUEST_GVBC: + iriap_next_client_state(self, S_CONNECTING); + IRDA_ASSERT(self->request_skb == NULL, return;); + /* Don't forget to refcount it - + * see iriap_getvaluebyclass_request(). */ + skb_get(skb); + self->request_skb = skb; + iriap_connect_request(self); + break; + case IAP_LM_DISCONNECT_INDICATION: + break; + default: + IRDA_DEBUG(0, "%s(), Unknown event %d\n", __FUNCTION__, event); + break; + } +} + +/* + * Function state_s_connecting (self, event, skb) + * + * S-Connecting + * + */ +static void state_s_connecting(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + switch (event) { + case IAP_LM_CONNECT_CONFIRM: + /* + * Jump to S-Call FSM + */ + iriap_do_call_event(self, IAP_CALL_REQUEST, skb); + /* iriap_call_request(self, 0,0,0); */ + iriap_next_client_state(self, S_CALL); + break; + case IAP_LM_DISCONNECT_INDICATION: + /* Abort calls */ + iriap_next_call_state(self, S_MAKE_CALL); + iriap_next_client_state(self, S_DISCONNECT); + break; + default: + IRDA_DEBUG(0, "%s(), Unknown event %d\n", __FUNCTION__, event); + break; + } +} + +/* + * Function state_s_call (self, event, skb) + * + * S-Call, The device can process calls to a specific remote + * device. Whenever the LSAP connection is disconnected, this state + * catches that event and clears up + */ +static void state_s_call(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_ASSERT(self != NULL, return;); + + switch (event) { + case IAP_LM_DISCONNECT_INDICATION: + /* Abort calls */ + iriap_next_call_state(self, S_MAKE_CALL); + iriap_next_client_state(self, S_DISCONNECT); + break; + default: + IRDA_DEBUG(0, "state_s_call: Unknown event %d\n", event); + break; + } +} + +/* + * Function state_s_make_call (event, skb) + * + * S-Make-Call + * + */ +static void state_s_make_call(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + struct sk_buff *tx_skb; + + IRDA_ASSERT(self != NULL, return;); + + switch (event) { + case IAP_CALL_REQUEST: + /* Already refcounted - see state_s_disconnect() */ + tx_skb = self->request_skb; + self->request_skb = NULL; + + irlmp_data_request(self->lsap, tx_skb); + iriap_next_call_state(self, S_OUTSTANDING); + break; + default: + IRDA_DEBUG(0, "%s(), Unknown event %d\n", __FUNCTION__, event); + break; + } +} + +/* + * Function state_s_calling (event, skb) + * + * S-Calling + * + */ +static void state_s_calling(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(0, "%s(), Not implemented\n", __FUNCTION__); +} + +/* + * Function state_s_outstanding (event, skb) + * + * S-Outstanding, The device is waiting for a response to a command + * + */ +static void state_s_outstanding(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_ASSERT(self != NULL, return;); + + switch (event) { + case IAP_RECV_F_LST: + /*iriap_send_ack(self);*/ + /*LM_Idle_request(idle); */ + + iriap_next_call_state(self, S_WAIT_FOR_CALL); + break; + default: + IRDA_DEBUG(0, "%s(), Unknown event %d\n", __FUNCTION__, event); + break; + } +} + +/* + * Function state_s_replying (event, skb) + * + * S-Replying, The device is collecting a multiple part response + */ +static void state_s_replying(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(0, "%s(), Not implemented\n", __FUNCTION__); +} + +/* + * Function state_s_wait_for_call (event, skb) + * + * S-Wait-for-Call + * + */ +static void state_s_wait_for_call(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(0, "%s(), Not implemented\n", __FUNCTION__); +} + + +/* + * Function state_s_wait_active (event, skb) + * + * S-Wait-Active + * + */ +static void state_s_wait_active(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(0, "%s(), Not implemented\n", __FUNCTION__); +} + +/************************************************************************** + * + * Server FSM + * + **************************************************************************/ + +/* + * Function state_r_disconnect (self, event, skb) + * + * LM-IAS server is disconnected (not processing any requests!) + * + */ +static void state_r_disconnect(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + struct sk_buff *tx_skb; + + switch (event) { + case IAP_LM_CONNECT_INDICATION: + tx_skb = dev_alloc_skb(64); + if (tx_skb == NULL) { + IRDA_WARNING("%s: unable to malloc!\n", __FUNCTION__); + return; + } + + /* Reserve space for MUX_CONTROL and LAP header */ + skb_reserve(tx_skb, LMP_MAX_HEADER); + + irlmp_connect_response(self->lsap, tx_skb); + /*LM_Idle_request(idle); */ + + iriap_next_server_state(self, R_CALL); + + /* + * Jump to R-Connect FSM, we skip R-Waiting since we do not + * care about LM_Idle_request()! + */ + iriap_next_r_connect_state(self, R_RECEIVING); + break; + default: + IRDA_DEBUG(0, "%s(), unknown event %d\n", __FUNCTION__, event); + break; + } +} + +/* + * Function state_r_call (self, event, skb) + */ +static void state_r_call(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + switch (event) { + case IAP_LM_DISCONNECT_INDICATION: + /* Abort call */ + iriap_next_server_state(self, R_DISCONNECT); + iriap_next_r_connect_state(self, R_WAITING); + break; + default: + IRDA_DEBUG(0, "%s(), unknown event!\n", __FUNCTION__); + break; + } +} + +/* + * R-Connect FSM + */ + +/* + * Function state_r_waiting (self, event, skb) + */ +static void state_r_waiting(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(0, "%s(), Not implemented\n", __FUNCTION__); +} + +static void state_r_wait_active(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(0, "%s(), Not implemented\n", __FUNCTION__); +} + +/* + * Function state_r_receiving (self, event, skb) + * + * We are receiving a command + * + */ +static void state_r_receiving(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + switch (event) { + case IAP_RECV_F_LST: + iriap_next_r_connect_state(self, R_EXECUTE); + + iriap_call_indication(self, skb); + break; + default: + IRDA_DEBUG(0, "%s(), unknown event!\n", __FUNCTION__); + break; + } +} + +/* + * Function state_r_execute (self, event, skb) + * + * The server is processing the request + * + */ +static void state_r_execute(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(skb != NULL, return;); + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IAS_MAGIC, return;); + + switch (event) { + case IAP_CALL_RESPONSE: + /* + * Since we don't implement the Waiting state, we return + * to state Receiving instead, DB. + */ + iriap_next_r_connect_state(self, R_RECEIVING); + + /* Don't forget to refcount it - see + * iriap_getvaluebyclass_response(). */ + skb_get(skb); + + irlmp_data_request(self->lsap, skb); + break; + default: + IRDA_DEBUG(0, "%s(), unknown event!\n", __FUNCTION__); + break; + } +} + +static void state_r_returning(struct iriap_cb *self, IRIAP_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(0, "%s(), event=%d\n", __FUNCTION__, event); + + switch (event) { + case IAP_RECV_F_LST: + break; + default: + break; + } +} diff --git a/net/irda/irias_object.c b/net/irda/irias_object.c new file mode 100644 index 000000000000..6fec428b4512 --- /dev/null +++ b/net/irda/irias_object.c @@ -0,0 +1,580 @@ +/********************************************************************* + * + * Filename: irias_object.c + * Version: 0.3 + * Description: IAS object database and functions + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Thu Oct 1 22:50:04 1998 + * Modified at: Wed Dec 15 11:23:16 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1998-1999 Dag Brattli, 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <linux/string.h> +#include <linux/socket.h> +#include <linux/module.h> + +#include <net/irda/irda.h> +#include <net/irda/irias_object.h> + +hashbin_t *irias_objects; + +/* + * Used when a missing value needs to be returned + */ +struct ias_value irias_missing = { IAS_MISSING, 0, 0, 0, {0}}; + +/* + * Function strndup (str, max) + * + * My own kernel version of strndup! + * + * Faster, check boundary... Jean II + */ +static char *strndup(char *str, int max) +{ + char *new_str; + int len; + + /* Check string */ + if (str == NULL) + return NULL; + /* Check length, truncate */ + len = strlen(str); + if(len > max) + len = max; + + /* Allocate new string */ + new_str = kmalloc(len + 1, GFP_ATOMIC); + if (new_str == NULL) { + IRDA_WARNING("%s: Unable to kmalloc!\n", __FUNCTION__); + return NULL; + } + + /* Copy and truncate */ + memcpy(new_str, str, len); + new_str[len] = '\0'; + + return new_str; +} + +/* + * Function ias_new_object (name, id) + * + * Create a new IAS object + * + */ +struct ias_object *irias_new_object( char *name, int id) +{ + struct ias_object *obj; + + IRDA_DEBUG( 4, "%s()\n", __FUNCTION__); + + obj = (struct ias_object *) kmalloc(sizeof(struct ias_object), + GFP_ATOMIC); + if (obj == NULL) { + IRDA_WARNING("%s(), Unable to allocate object!\n", + __FUNCTION__); + return NULL; + } + memset(obj, 0, sizeof( struct ias_object)); + + obj->magic = IAS_OBJECT_MAGIC; + obj->name = strndup(name, IAS_MAX_CLASSNAME); + obj->id = id; + + /* Locking notes : the attrib spinlock has lower precendence + * than the objects spinlock. Never grap the objects spinlock + * while holding any attrib spinlock (risk of deadlock). Jean II */ + obj->attribs = hashbin_new(HB_LOCK); + + if (obj->attribs == NULL) { + IRDA_WARNING("%s(), Unable to allocate attribs!\n", + __FUNCTION__); + kfree(obj); + return NULL; + } + + return obj; +} +EXPORT_SYMBOL(irias_new_object); + +/* + * Function irias_delete_attrib (attrib) + * + * Delete given attribute and deallocate all its memory + * + */ +static void __irias_delete_attrib(struct ias_attrib *attrib) +{ + IRDA_ASSERT(attrib != NULL, return;); + IRDA_ASSERT(attrib->magic == IAS_ATTRIB_MAGIC, return;); + + if (attrib->name) + kfree(attrib->name); + + irias_delete_value(attrib->value); + attrib->magic = ~IAS_ATTRIB_MAGIC; + + kfree(attrib); +} + +void __irias_delete_object(struct ias_object *obj) +{ + IRDA_ASSERT(obj != NULL, return;); + IRDA_ASSERT(obj->magic == IAS_OBJECT_MAGIC, return;); + + if (obj->name) + kfree(obj->name); + + hashbin_delete(obj->attribs, (FREE_FUNC) __irias_delete_attrib); + + obj->magic = ~IAS_OBJECT_MAGIC; + + kfree(obj); +} + +/* + * Function irias_delete_object (obj) + * + * Remove object from hashbin and deallocate all attributes associated with + * with this object and the object itself + * + */ +int irias_delete_object(struct ias_object *obj) +{ + struct ias_object *node; + + IRDA_ASSERT(obj != NULL, return -1;); + IRDA_ASSERT(obj->magic == IAS_OBJECT_MAGIC, return -1;); + + /* Remove from list */ + node = hashbin_remove_this(irias_objects, (irda_queue_t *) obj); + if (!node) + IRDA_DEBUG( 0, "%s(), object already removed!\n", + __FUNCTION__); + + /* Destroy */ + __irias_delete_object(obj); + + return 0; +} +EXPORT_SYMBOL(irias_delete_object); + +/* + * Function irias_delete_attrib (obj) + * + * Remove attribute from hashbin and, if it was the last attribute of + * the object, remove the object as well. + * + */ +int irias_delete_attrib(struct ias_object *obj, struct ias_attrib *attrib, + int cleanobject) +{ + struct ias_attrib *node; + + IRDA_ASSERT(obj != NULL, return -1;); + IRDA_ASSERT(obj->magic == IAS_OBJECT_MAGIC, return -1;); + IRDA_ASSERT(attrib != NULL, return -1;); + + /* Remove attribute from object */ + node = hashbin_remove_this(obj->attribs, (irda_queue_t *) attrib); + if (!node) + return 0; /* Already removed or non-existent */ + + /* Deallocate attribute */ + __irias_delete_attrib(node); + + /* Check if object has still some attributes, destroy it if none. + * At first glance, this look dangerous, as the kernel reference + * various IAS objects. However, we only use this function on + * user attributes, not kernel attributes, so there is no risk + * of deleting a kernel object this way. Jean II */ + node = (struct ias_attrib *) hashbin_get_first(obj->attribs); + if (cleanobject && !node) + irias_delete_object(obj); + + return 0; +} + +/* + * Function irias_insert_object (obj) + * + * Insert an object into the LM-IAS database + * + */ +void irias_insert_object(struct ias_object *obj) +{ + IRDA_ASSERT(obj != NULL, return;); + IRDA_ASSERT(obj->magic == IAS_OBJECT_MAGIC, return;); + + hashbin_insert(irias_objects, (irda_queue_t *) obj, 0, obj->name); +} +EXPORT_SYMBOL(irias_insert_object); + +/* + * Function irias_find_object (name) + * + * Find object with given name + * + */ +struct ias_object *irias_find_object(char *name) +{ + IRDA_ASSERT(name != NULL, return NULL;); + + /* Unsafe (locking), object might change */ + return hashbin_lock_find(irias_objects, 0, name); +} +EXPORT_SYMBOL(irias_find_object); + +/* + * Function irias_find_attrib (obj, name) + * + * Find named attribute in object + * + */ +struct ias_attrib *irias_find_attrib(struct ias_object *obj, char *name) +{ + struct ias_attrib *attrib; + + IRDA_ASSERT(obj != NULL, return NULL;); + IRDA_ASSERT(obj->magic == IAS_OBJECT_MAGIC, return NULL;); + IRDA_ASSERT(name != NULL, return NULL;); + + attrib = hashbin_lock_find(obj->attribs, 0, name); + if (attrib == NULL) + return NULL; + + /* Unsafe (locking), attrib might change */ + return attrib; +} +EXPORT_SYMBOL(irias_find_attrib); + +/* + * Function irias_add_attribute (obj, attrib) + * + * Add attribute to object + * + */ +static void irias_add_attrib(struct ias_object *obj, struct ias_attrib *attrib, + int owner) +{ + IRDA_ASSERT(obj != NULL, return;); + IRDA_ASSERT(obj->magic == IAS_OBJECT_MAGIC, return;); + + IRDA_ASSERT(attrib != NULL, return;); + IRDA_ASSERT(attrib->magic == IAS_ATTRIB_MAGIC, return;); + + /* Set if attrib is owned by kernel or user space */ + attrib->value->owner = owner; + + hashbin_insert(obj->attribs, (irda_queue_t *) attrib, 0, attrib->name); +} + +/* + * Function irias_object_change_attribute (obj_name, attrib_name, new_value) + * + * Change the value of an objects attribute. + * + */ +int irias_object_change_attribute(char *obj_name, char *attrib_name, + struct ias_value *new_value) +{ + struct ias_object *obj; + struct ias_attrib *attrib; + unsigned long flags; + + /* Find object */ + obj = hashbin_lock_find(irias_objects, 0, obj_name); + if (obj == NULL) { + IRDA_WARNING("%s: Unable to find object: %s\n", __FUNCTION__, + obj_name); + return -1; + } + + /* Slightly unsafe (obj might get removed under us) */ + spin_lock_irqsave(&obj->attribs->hb_spinlock, flags); + + /* Find attribute */ + attrib = hashbin_find(obj->attribs, 0, attrib_name); + if (attrib == NULL) { + IRDA_WARNING("%s: Unable to find attribute: %s\n", + __FUNCTION__, attrib_name); + spin_unlock_irqrestore(&obj->attribs->hb_spinlock, flags); + return -1; + } + + if ( attrib->value->type != new_value->type) { + IRDA_DEBUG( 0, "%s(), changing value type not allowed!\n", + __FUNCTION__); + spin_unlock_irqrestore(&obj->attribs->hb_spinlock, flags); + return -1; + } + + /* Delete old value */ + irias_delete_value(attrib->value); + + /* Insert new value */ + attrib->value = new_value; + + /* Success */ + spin_unlock_irqrestore(&obj->attribs->hb_spinlock, flags); + return 0; +} +EXPORT_SYMBOL(irias_object_change_attribute); + +/* + * Function irias_object_add_integer_attrib (obj, name, value) + * + * Add an integer attribute to an LM-IAS object + * + */ +void irias_add_integer_attrib(struct ias_object *obj, char *name, int value, + int owner) +{ + struct ias_attrib *attrib; + + IRDA_ASSERT(obj != NULL, return;); + IRDA_ASSERT(obj->magic == IAS_OBJECT_MAGIC, return;); + IRDA_ASSERT(name != NULL, return;); + + attrib = (struct ias_attrib *) kmalloc(sizeof(struct ias_attrib), + GFP_ATOMIC); + if (attrib == NULL) { + IRDA_WARNING("%s: Unable to allocate attribute!\n", + __FUNCTION__); + return; + } + memset(attrib, 0, sizeof( struct ias_attrib)); + + attrib->magic = IAS_ATTRIB_MAGIC; + attrib->name = strndup(name, IAS_MAX_ATTRIBNAME); + + /* Insert value */ + attrib->value = irias_new_integer_value(value); + + irias_add_attrib(obj, attrib, owner); +} +EXPORT_SYMBOL(irias_add_integer_attrib); + + /* + * Function irias_add_octseq_attrib (obj, name, octet_seq, len) + * + * Add a octet sequence attribute to an LM-IAS object + * + */ + +void irias_add_octseq_attrib(struct ias_object *obj, char *name, __u8 *octets, + int len, int owner) +{ + struct ias_attrib *attrib; + + IRDA_ASSERT(obj != NULL, return;); + IRDA_ASSERT(obj->magic == IAS_OBJECT_MAGIC, return;); + + IRDA_ASSERT(name != NULL, return;); + IRDA_ASSERT(octets != NULL, return;); + + attrib = (struct ias_attrib *) kmalloc(sizeof(struct ias_attrib), + GFP_ATOMIC); + if (attrib == NULL) { + IRDA_WARNING("%s: Unable to allocate attribute!\n", + __FUNCTION__); + return; + } + memset(attrib, 0, sizeof( struct ias_attrib)); + + attrib->magic = IAS_ATTRIB_MAGIC; + attrib->name = strndup(name, IAS_MAX_ATTRIBNAME); + + attrib->value = irias_new_octseq_value( octets, len); + + irias_add_attrib(obj, attrib, owner); +} +EXPORT_SYMBOL(irias_add_octseq_attrib); + +/* + * Function irias_object_add_string_attrib (obj, string) + * + * Add a string attribute to an LM-IAS object + * + */ +void irias_add_string_attrib(struct ias_object *obj, char *name, char *value, + int owner) +{ + struct ias_attrib *attrib; + + IRDA_ASSERT(obj != NULL, return;); + IRDA_ASSERT(obj->magic == IAS_OBJECT_MAGIC, return;); + + IRDA_ASSERT(name != NULL, return;); + IRDA_ASSERT(value != NULL, return;); + + attrib = (struct ias_attrib *) kmalloc(sizeof( struct ias_attrib), + GFP_ATOMIC); + if (attrib == NULL) { + IRDA_WARNING("%s: Unable to allocate attribute!\n", + __FUNCTION__); + return; + } + memset(attrib, 0, sizeof( struct ias_attrib)); + + attrib->magic = IAS_ATTRIB_MAGIC; + attrib->name = strndup(name, IAS_MAX_ATTRIBNAME); + + attrib->value = irias_new_string_value(value); + + irias_add_attrib(obj, attrib, owner); +} +EXPORT_SYMBOL(irias_add_string_attrib); + +/* + * Function irias_new_integer_value (integer) + * + * Create new IAS integer value + * + */ +struct ias_value *irias_new_integer_value(int integer) +{ + struct ias_value *value; + + value = kmalloc(sizeof(struct ias_value), GFP_ATOMIC); + if (value == NULL) { + IRDA_WARNING("%s: Unable to kmalloc!\n", __FUNCTION__); + return NULL; + } + memset(value, 0, sizeof(struct ias_value)); + + value->type = IAS_INTEGER; + value->len = 4; + value->t.integer = integer; + + return value; +} +EXPORT_SYMBOL(irias_new_integer_value); + +/* + * Function irias_new_string_value (string) + * + * Create new IAS string value + * + * Per IrLMP 1.1, 4.3.3.2, strings are up to 256 chars - Jean II + */ +struct ias_value *irias_new_string_value(char *string) +{ + struct ias_value *value; + + value = kmalloc(sizeof(struct ias_value), GFP_ATOMIC); + if (value == NULL) { + IRDA_WARNING("%s: Unable to kmalloc!\n", __FUNCTION__); + return NULL; + } + memset( value, 0, sizeof( struct ias_value)); + + value->type = IAS_STRING; + value->charset = CS_ASCII; + value->t.string = strndup(string, IAS_MAX_STRING); + value->len = strlen(value->t.string); + + return value; +} +EXPORT_SYMBOL(irias_new_string_value); + +/* + * Function irias_new_octseq_value (octets, len) + * + * Create new IAS octet-sequence value + * + * Per IrLMP 1.1, 4.3.3.2, octet-sequence are up to 1024 bytes - Jean II + */ +struct ias_value *irias_new_octseq_value(__u8 *octseq , int len) +{ + struct ias_value *value; + + value = kmalloc(sizeof(struct ias_value), GFP_ATOMIC); + if (value == NULL) { + IRDA_WARNING("%s: Unable to kmalloc!\n", __FUNCTION__); + return NULL; + } + memset(value, 0, sizeof(struct ias_value)); + + value->type = IAS_OCT_SEQ; + /* Check length */ + if(len > IAS_MAX_OCTET_STRING) + len = IAS_MAX_OCTET_STRING; + value->len = len; + + value->t.oct_seq = kmalloc(len, GFP_ATOMIC); + if (value->t.oct_seq == NULL){ + IRDA_WARNING("%s: Unable to kmalloc!\n", __FUNCTION__); + kfree(value); + return NULL; + } + memcpy(value->t.oct_seq, octseq , len); + return value; +} +EXPORT_SYMBOL(irias_new_octseq_value); + +struct ias_value *irias_new_missing_value(void) +{ + struct ias_value *value; + + value = kmalloc(sizeof(struct ias_value), GFP_ATOMIC); + if (value == NULL) { + IRDA_WARNING("%s: Unable to kmalloc!\n", __FUNCTION__); + return NULL; + } + memset(value, 0, sizeof(struct ias_value)); + + value->type = IAS_MISSING; + value->len = 0; + + return value; +} + +/* + * Function irias_delete_value (value) + * + * Delete IAS value + * + */ +void irias_delete_value(struct ias_value *value) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(value != NULL, return;); + + switch (value->type) { + case IAS_INTEGER: /* Fallthrough */ + case IAS_MISSING: + /* No need to deallocate */ + break; + case IAS_STRING: + /* If string, deallocate string */ + if (value->t.string != NULL) + kfree(value->t.string); + break; + case IAS_OCT_SEQ: + /* If byte stream, deallocate byte stream */ + if (value->t.oct_seq != NULL) + kfree(value->t.oct_seq); + break; + default: + IRDA_DEBUG(0, "%s(), Unknown value type!\n", __FUNCTION__); + break; + } + kfree(value); +} +EXPORT_SYMBOL(irias_delete_value); diff --git a/net/irda/irlan/Kconfig b/net/irda/irlan/Kconfig new file mode 100644 index 000000000000..951abc2e3a7f --- /dev/null +++ b/net/irda/irlan/Kconfig @@ -0,0 +1,14 @@ +config IRLAN + tristate "IrLAN protocol" + depends on IRDA + help + Say Y here if you want to build support for the IrLAN protocol. + To compile it as a module, choose M here: the module will be called + irlan. IrLAN emulates an Ethernet and makes it possible to put up + a wireless LAN using infrared beams. + + The IrLAN protocol can be used to talk with infrared access points + like the HP NetbeamIR, or the ESI JetEye NET. You can also connect + to another Linux machine running the IrLAN protocol for ad-hoc + networking! + diff --git a/net/irda/irlan/Makefile b/net/irda/irlan/Makefile new file mode 100644 index 000000000000..77549bc8641b --- /dev/null +++ b/net/irda/irlan/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Linux IrDA IrLAN protocol layer. +# + +obj-$(CONFIG_IRLAN) += irlan.o + +irlan-objs := irlan_common.o irlan_eth.o irlan_event.o irlan_client.o irlan_provider.o irlan_filter.o irlan_provider_event.o irlan_client_event.o diff --git a/net/irda/irlan/irlan_client.c b/net/irda/irlan/irlan_client.c new file mode 100644 index 000000000000..f8e6cb0db04b --- /dev/null +++ b/net/irda/irlan/irlan_client.c @@ -0,0 +1,576 @@ +/********************************************************************* + * + * Filename: irlan_client.c + * Version: 0.9 + * Description: IrDA LAN Access Protocol (IrLAN) Client + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sun Aug 31 20:14:37 1997 + * Modified at: Tue Dec 14 15:47:02 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * Sources: skeleton.c by Donald Becker <becker@CESDIS.gsfc.nasa.gov> + * slip.c by Laurence Culhane, <loz@holmes.demon.co.uk> + * Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org> + * + * Copyright (c) 1998-1999 Dag Brattli <dagb@cs.uit.no>, + * 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/if_arp.h> +#include <linux/bitops.h> +#include <net/arp.h> + +#include <asm/system.h> +#include <asm/byteorder.h> + +#include <net/irda/irda.h> +#include <net/irda/irttp.h> +#include <net/irda/irlmp.h> +#include <net/irda/irias_object.h> +#include <net/irda/iriap.h> +#include <net/irda/timer.h> + +#include <net/irda/irlan_common.h> +#include <net/irda/irlan_event.h> +#include <net/irda/irlan_eth.h> +#include <net/irda/irlan_provider.h> +#include <net/irda/irlan_client.h> + +#undef CONFIG_IRLAN_GRATUITOUS_ARP + +static void irlan_client_ctrl_disconnect_indication(void *instance, void *sap, + LM_REASON reason, + struct sk_buff *); +static int irlan_client_ctrl_data_indication(void *instance, void *sap, + struct sk_buff *skb); +static void irlan_client_ctrl_connect_confirm(void *instance, void *sap, + struct qos_info *qos, + __u32 max_sdu_size, + __u8 max_header_size, + struct sk_buff *); +static void irlan_check_response_param(struct irlan_cb *self, char *param, + char *value, int val_len); +static void irlan_client_open_ctrl_tsap(struct irlan_cb *self); + +static void irlan_client_kick_timer_expired(void *data) +{ + struct irlan_cb *self = (struct irlan_cb *) data; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + /* + * If we are in peer mode, the client may not have got the discovery + * indication it needs to make progress. If the client is still in + * IDLE state, we must kick it to, but only if the provider is not IDLE + */ + if ((self->provider.access_type == ACCESS_PEER) && + (self->client.state == IRLAN_IDLE) && + (self->provider.state != IRLAN_IDLE)) { + irlan_client_wakeup(self, self->saddr, self->daddr); + } +} + +static void irlan_client_start_kick_timer(struct irlan_cb *self, int timeout) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + irda_start_timer(&self->client.kick_timer, timeout, (void *) self, + irlan_client_kick_timer_expired); +} + +/* + * Function irlan_client_wakeup (self, saddr, daddr) + * + * Wake up client + * + */ +void irlan_client_wakeup(struct irlan_cb *self, __u32 saddr, __u32 daddr) +{ + IRDA_DEBUG(1, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + /* + * Check if we are already awake, or if we are a provider in direct + * mode (in that case we must leave the client idle + */ + if ((self->client.state != IRLAN_IDLE) || + (self->provider.access_type == ACCESS_DIRECT)) + { + IRDA_DEBUG(0, "%s(), already awake!\n", __FUNCTION__ ); + return; + } + + /* Addresses may have changed! */ + self->saddr = saddr; + self->daddr = daddr; + + if (self->disconnect_reason == LM_USER_REQUEST) { + IRDA_DEBUG(0, "%s(), still stopped by user\n", __FUNCTION__ ); + return; + } + + /* Open TSAPs */ + irlan_client_open_ctrl_tsap(self); + irlan_open_data_tsap(self); + + irlan_do_client_event(self, IRLAN_DISCOVERY_INDICATION, NULL); + + /* Start kick timer */ + irlan_client_start_kick_timer(self, 2*HZ); +} + +/* + * Function irlan_discovery_indication (daddr) + * + * Remote device with IrLAN server support discovered + * + */ +void irlan_client_discovery_indication(discinfo_t *discovery, + DISCOVERY_MODE mode, + void *priv) +{ + struct irlan_cb *self; + __u32 saddr, daddr; + + IRDA_DEBUG(1, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(discovery != NULL, return;); + + /* + * I didn't check it, but I bet that IrLAN suffer from the same + * deficiency as IrComm and doesn't handle two instances + * simultaneously connecting to each other. + * Same workaround, drop passive discoveries. + * Jean II */ + if(mode == DISCOVERY_PASSIVE) + return; + + saddr = discovery->saddr; + daddr = discovery->daddr; + + /* Find instance */ + rcu_read_lock(); + self = irlan_get_any(); + if (self) { + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + IRDA_DEBUG(1, "%s(), Found instance (%08x)!\n", __FUNCTION__ , + daddr); + + irlan_client_wakeup(self, saddr, daddr); + } + rcu_read_unlock(); +} + +/* + * Function irlan_client_data_indication (handle, skb) + * + * This function gets the data that is received on the control channel + * + */ +static int irlan_client_ctrl_data_indication(void *instance, void *sap, + struct sk_buff *skb) +{ + struct irlan_cb *self; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + self = (struct irlan_cb *) instance; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return -1;); + IRDA_ASSERT(skb != NULL, return -1;); + + irlan_do_client_event(self, IRLAN_DATA_INDICATION, skb); + + /* Ready for a new command */ + IRDA_DEBUG(2, "%s(), clearing tx_busy\n", __FUNCTION__ ); + self->client.tx_busy = FALSE; + + /* Check if we have some queued commands waiting to be sent */ + irlan_run_ctrl_tx_queue(self); + + return 0; +} + +static void irlan_client_ctrl_disconnect_indication(void *instance, void *sap, + LM_REASON reason, + struct sk_buff *userdata) +{ + struct irlan_cb *self; + struct tsap_cb *tsap; + struct sk_buff *skb; + + IRDA_DEBUG(4, "%s(), reason=%d\n", __FUNCTION__ , reason); + + self = (struct irlan_cb *) instance; + tsap = (struct tsap_cb *) sap; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + IRDA_ASSERT(tsap != NULL, return;); + IRDA_ASSERT(tsap->magic == TTP_TSAP_MAGIC, return;); + + IRDA_ASSERT(tsap == self->client.tsap_ctrl, return;); + + /* Remove frames queued on the control channel */ + while ((skb = skb_dequeue(&self->client.txq)) != NULL) { + dev_kfree_skb(skb); + } + self->client.tx_busy = FALSE; + + irlan_do_client_event(self, IRLAN_LMP_DISCONNECT, NULL); +} + +/* + * Function irlan_client_open_tsaps (self) + * + * Initialize callbacks and open IrTTP TSAPs + * + */ +static void irlan_client_open_ctrl_tsap(struct irlan_cb *self) +{ + struct tsap_cb *tsap; + notify_t notify; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + /* Check if already open */ + if (self->client.tsap_ctrl) + return; + + irda_notify_init(¬ify); + + /* Set up callbacks */ + notify.data_indication = irlan_client_ctrl_data_indication; + notify.connect_confirm = irlan_client_ctrl_connect_confirm; + notify.disconnect_indication = irlan_client_ctrl_disconnect_indication; + notify.instance = self; + strlcpy(notify.name, "IrLAN ctrl (c)", sizeof(notify.name)); + + tsap = irttp_open_tsap(LSAP_ANY, DEFAULT_INITIAL_CREDIT, ¬ify); + if (!tsap) { + IRDA_DEBUG(2, "%s(), Got no tsap!\n", __FUNCTION__ ); + return; + } + self->client.tsap_ctrl = tsap; +} + +/* + * Function irlan_client_connect_confirm (handle, skb) + * + * Connection to peer IrLAN laye confirmed + * + */ +static void irlan_client_ctrl_connect_confirm(void *instance, void *sap, + struct qos_info *qos, + __u32 max_sdu_size, + __u8 max_header_size, + struct sk_buff *skb) +{ + struct irlan_cb *self; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + self = (struct irlan_cb *) instance; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + self->client.max_sdu_size = max_sdu_size; + self->client.max_header_size = max_header_size; + + /* TODO: we could set the MTU depending on the max_sdu_size */ + + irlan_do_client_event(self, IRLAN_CONNECT_COMPLETE, NULL); +} + +/* + * Function print_ret_code (code) + * + * Print return code of request to peer IrLAN layer. + * + */ +static void print_ret_code(__u8 code) +{ + switch(code) { + case 0: + printk(KERN_INFO "Success\n"); + break; + case 1: + IRDA_WARNING("IrLAN: Insufficient resources\n"); + break; + case 2: + IRDA_WARNING("IrLAN: Invalid command format\n"); + break; + case 3: + IRDA_WARNING("IrLAN: Command not supported\n"); + break; + case 4: + IRDA_WARNING("IrLAN: Parameter not supported\n"); + break; + case 5: + IRDA_WARNING("IrLAN: Value not supported\n"); + break; + case 6: + IRDA_WARNING("IrLAN: Not open\n"); + break; + case 7: + IRDA_WARNING("IrLAN: Authentication required\n"); + break; + case 8: + IRDA_WARNING("IrLAN: Invalid password\n"); + break; + case 9: + IRDA_WARNING("IrLAN: Protocol error\n"); + break; + case 255: + IRDA_WARNING("IrLAN: Asynchronous status\n"); + break; + } +} + +/* + * Function irlan_client_parse_response (self, skb) + * + * Extract all parameters from received buffer, then feed them to + * check_params for parsing + */ +void irlan_client_parse_response(struct irlan_cb *self, struct sk_buff *skb) +{ + __u8 *frame; + __u8 *ptr; + int count; + int ret; + __u16 val_len; + int i; + char *name; + char *value; + + IRDA_ASSERT(skb != NULL, return;); + + IRDA_DEBUG(4, "%s() skb->len=%d\n", __FUNCTION__ , (int) skb->len); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + if (!skb) { + IRDA_ERROR("%s(), Got NULL skb!\n", __FUNCTION__); + return; + } + frame = skb->data; + + /* + * Check return code and print it if not success + */ + if (frame[0]) { + print_ret_code(frame[0]); + return; + } + + name = kmalloc(255, GFP_ATOMIC); + if (!name) + return; + value = kmalloc(1016, GFP_ATOMIC); + if (!value) { + kfree(name); + return; + } + + /* How many parameters? */ + count = frame[1]; + + IRDA_DEBUG(4, "%s(), got %d parameters\n", __FUNCTION__ , count); + + ptr = frame+2; + + /* For all parameters */ + for (i=0; i<count;i++) { + ret = irlan_extract_param(ptr, name, value, &val_len); + if (ret < 0) { + IRDA_DEBUG(2, "%s(), IrLAN, Error!\n", __FUNCTION__ ); + break; + } + ptr += ret; + irlan_check_response_param(self, name, value, val_len); + } + /* Cleanup */ + kfree(name); + kfree(value); +} + +/* + * Function irlan_check_response_param (self, param, value, val_len) + * + * Check which parameter is received and update local variables + * + */ +static void irlan_check_response_param(struct irlan_cb *self, char *param, + char *value, int val_len) +{ + __u16 tmp_cpu; /* Temporary value in host order */ + __u8 *bytes; + int i; + + IRDA_DEBUG(4, "%s(), parm=%s\n", __FUNCTION__ , param); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + /* Media type */ + if (strcmp(param, "MEDIA") == 0) { + if (strcmp(value, "802.3") == 0) + self->media = MEDIA_802_3; + else + self->media = MEDIA_802_5; + return; + } + if (strcmp(param, "FILTER_TYPE") == 0) { + if (strcmp(value, "DIRECTED") == 0) + self->client.filter_type |= IRLAN_DIRECTED; + else if (strcmp(value, "FUNCTIONAL") == 0) + self->client.filter_type |= IRLAN_FUNCTIONAL; + else if (strcmp(value, "GROUP") == 0) + self->client.filter_type |= IRLAN_GROUP; + else if (strcmp(value, "MAC_FRAME") == 0) + self->client.filter_type |= IRLAN_MAC_FRAME; + else if (strcmp(value, "MULTICAST") == 0) + self->client.filter_type |= IRLAN_MULTICAST; + else if (strcmp(value, "BROADCAST") == 0) + self->client.filter_type |= IRLAN_BROADCAST; + else if (strcmp(value, "IPX_SOCKET") == 0) + self->client.filter_type |= IRLAN_IPX_SOCKET; + + } + if (strcmp(param, "ACCESS_TYPE") == 0) { + if (strcmp(value, "DIRECT") == 0) + self->client.access_type = ACCESS_DIRECT; + else if (strcmp(value, "PEER") == 0) + self->client.access_type = ACCESS_PEER; + else if (strcmp(value, "HOSTED") == 0) + self->client.access_type = ACCESS_HOSTED; + else { + IRDA_DEBUG(2, "%s(), unknown access type!\n", __FUNCTION__ ); + } + } + /* IRLAN version */ + if (strcmp(param, "IRLAN_VER") == 0) { + IRDA_DEBUG(4, "IrLAN version %d.%d\n", (__u8) value[0], + (__u8) value[1]); + + self->version[0] = value[0]; + self->version[1] = value[1]; + return; + } + /* Which remote TSAP to use for data channel */ + if (strcmp(param, "DATA_CHAN") == 0) { + self->dtsap_sel_data = value[0]; + IRDA_DEBUG(4, "Data TSAP = %02x\n", self->dtsap_sel_data); + return; + } + if (strcmp(param, "CON_ARB") == 0) { + memcpy(&tmp_cpu, value, 2); /* Align value */ + le16_to_cpus(&tmp_cpu); /* Convert to host order */ + self->client.recv_arb_val = tmp_cpu; + IRDA_DEBUG(2, "%s(), receive arb val=%d\n", __FUNCTION__ , + self->client.recv_arb_val); + } + if (strcmp(param, "MAX_FRAME") == 0) { + memcpy(&tmp_cpu, value, 2); /* Align value */ + le16_to_cpus(&tmp_cpu); /* Convert to host order */ + self->client.max_frame = tmp_cpu; + IRDA_DEBUG(4, "%s(), max frame=%d\n", __FUNCTION__ , + self->client.max_frame); + } + + /* RECONNECT_KEY, in case the link goes down! */ + if (strcmp(param, "RECONNECT_KEY") == 0) { + IRDA_DEBUG(4, "Got reconnect key: "); + /* for (i = 0; i < val_len; i++) */ +/* printk("%02x", value[i]); */ + memcpy(self->client.reconnect_key, value, val_len); + self->client.key_len = val_len; + IRDA_DEBUG(4, "\n"); + } + /* FILTER_ENTRY, have we got an ethernet address? */ + if (strcmp(param, "FILTER_ENTRY") == 0) { + bytes = value; + IRDA_DEBUG(4, "Ethernet address = %02x:%02x:%02x:%02x:%02x:%02x\n", + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], + bytes[5]); + for (i = 0; i < 6; i++) + self->dev->dev_addr[i] = bytes[i]; + } +} + +/* + * Function irlan_client_get_value_confirm (obj_id, value) + * + * Got results from remote LM-IAS + * + */ +void irlan_client_get_value_confirm(int result, __u16 obj_id, + struct ias_value *value, void *priv) +{ + struct irlan_cb *self; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(priv != NULL, return;); + + self = (struct irlan_cb *) priv; + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + /* We probably don't need to make any more queries */ + iriap_close(self->client.iriap); + self->client.iriap = NULL; + + /* Check if request succeeded */ + if (result != IAS_SUCCESS) { + IRDA_DEBUG(2, "%s(), got NULL value!\n", __FUNCTION__ ); + irlan_do_client_event(self, IRLAN_IAS_PROVIDER_NOT_AVAIL, + NULL); + return; + } + + switch (value->type) { + case IAS_INTEGER: + self->dtsap_sel_ctrl = value->t.integer; + + if (value->t.integer != -1) { + irlan_do_client_event(self, IRLAN_IAS_PROVIDER_AVAIL, + NULL); + return; + } + irias_delete_value(value); + break; + default: + IRDA_DEBUG(2, "%s(), unknown type!\n", __FUNCTION__ ); + break; + } + irlan_do_client_event(self, IRLAN_IAS_PROVIDER_NOT_AVAIL, NULL); +} diff --git a/net/irda/irlan/irlan_client_event.c b/net/irda/irlan/irlan_client_event.c new file mode 100644 index 000000000000..ce943b69e996 --- /dev/null +++ b/net/irda/irlan/irlan_client_event.c @@ -0,0 +1,533 @@ +/********************************************************************* + * + * Filename: irlan_client_event.c + * Version: 0.9 + * Description: IrLAN client state machine + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sun Aug 31 20:14:37 1997 + * Modified at: Sun Dec 26 21:52:24 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1998-1999 Dag Brattli <dagb@cs.uit.no>, + * 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <linux/skbuff.h> + +#include <net/irda/irda.h> +#include <net/irda/timer.h> +#include <net/irda/irmod.h> +#include <net/irda/iriap.h> +#include <net/irda/irlmp.h> +#include <net/irda/irttp.h> + +#include <net/irda/irlan_common.h> +#include <net/irda/irlan_client.h> +#include <net/irda/irlan_event.h> + +static int irlan_client_state_idle (struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb); +static int irlan_client_state_query(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb); +static int irlan_client_state_conn (struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb); +static int irlan_client_state_info (struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb); +static int irlan_client_state_media(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb); +static int irlan_client_state_open (struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb); +static int irlan_client_state_wait (struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb); +static int irlan_client_state_arb (struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb); +static int irlan_client_state_data (struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb); +static int irlan_client_state_close(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb); +static int irlan_client_state_sync (struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb); + +static int (*state[])(struct irlan_cb *, IRLAN_EVENT event, struct sk_buff *) = +{ + irlan_client_state_idle, + irlan_client_state_query, + irlan_client_state_conn, + irlan_client_state_info, + irlan_client_state_media, + irlan_client_state_open, + irlan_client_state_wait, + irlan_client_state_arb, + irlan_client_state_data, + irlan_client_state_close, + irlan_client_state_sync +}; + +void irlan_do_client_event(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + (*state[ self->client.state]) (self, event, skb); +} + +/* + * Function irlan_client_state_idle (event, skb, info) + * + * IDLE, We are waiting for an indication that there is a provider + * available. + */ +static int irlan_client_state_idle(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return -1;); + + switch (event) { + case IRLAN_DISCOVERY_INDICATION: + if (self->client.iriap) { + IRDA_WARNING("%s(), busy with a previous query\n", + __FUNCTION__); + return -EBUSY; + } + + self->client.iriap = iriap_open(LSAP_ANY, IAS_CLIENT, self, + irlan_client_get_value_confirm); + /* Get some values from peer IAS */ + irlan_next_client_state(self, IRLAN_QUERY); + iriap_getvaluebyclass_request(self->client.iriap, + self->saddr, self->daddr, + "IrLAN", "IrDA:TinyTP:LsapSel"); + break; + case IRLAN_WATCHDOG_TIMEOUT: + IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__ ); + break; + default: + IRDA_DEBUG(4, "%s(), Unknown event %d\n", __FUNCTION__ , event); + break; + } + if (skb) + dev_kfree_skb(skb); + + return 0; +} + +/* + * Function irlan_client_state_query (event, skb, info) + * + * QUERY, We have queryed the remote IAS and is ready to connect + * to provider, just waiting for the confirm. + * + */ +static int irlan_client_state_query(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return -1;); + + switch(event) { + case IRLAN_IAS_PROVIDER_AVAIL: + IRDA_ASSERT(self->dtsap_sel_ctrl != 0, return -1;); + + self->client.open_retries = 0; + + irttp_connect_request(self->client.tsap_ctrl, + self->dtsap_sel_ctrl, + self->saddr, self->daddr, NULL, + IRLAN_MTU, NULL); + irlan_next_client_state(self, IRLAN_CONN); + break; + case IRLAN_IAS_PROVIDER_NOT_AVAIL: + IRDA_DEBUG(2, "%s(), IAS_PROVIDER_NOT_AVAIL\n", __FUNCTION__ ); + irlan_next_client_state(self, IRLAN_IDLE); + + /* Give the client a kick! */ + if ((self->provider.access_type == ACCESS_PEER) && + (self->provider.state != IRLAN_IDLE)) + irlan_client_wakeup(self, self->saddr, self->daddr); + break; + case IRLAN_LMP_DISCONNECT: + case IRLAN_LAP_DISCONNECT: + irlan_next_client_state(self, IRLAN_IDLE); + break; + case IRLAN_WATCHDOG_TIMEOUT: + IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__ ); + break; + default: + IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__ , event); + break; + } + if (skb) + dev_kfree_skb(skb); + + return 0; +} + +/* + * Function irlan_client_state_conn (event, skb, info) + * + * CONN, We have connected to a provider but has not issued any + * commands yet. + * + */ +static int irlan_client_state_conn(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + + switch (event) { + case IRLAN_CONNECT_COMPLETE: + /* Send getinfo cmd */ + irlan_get_provider_info(self); + irlan_next_client_state(self, IRLAN_INFO); + break; + case IRLAN_LMP_DISCONNECT: + case IRLAN_LAP_DISCONNECT: + irlan_next_client_state(self, IRLAN_IDLE); + break; + case IRLAN_WATCHDOG_TIMEOUT: + IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__ ); + break; + default: + IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__ , event); + break; + } + if (skb) + dev_kfree_skb(skb); + + return 0; +} + +/* + * Function irlan_client_state_info (self, event, skb, info) + * + * INFO, We have issued a GetInfo command and is awaiting a reply. + */ +static int irlan_client_state_info(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + + switch (event) { + case IRLAN_DATA_INDICATION: + IRDA_ASSERT(skb != NULL, return -1;); + + irlan_client_parse_response(self, skb); + + irlan_next_client_state(self, IRLAN_MEDIA); + + irlan_get_media_char(self); + break; + + case IRLAN_LMP_DISCONNECT: + case IRLAN_LAP_DISCONNECT: + irlan_next_client_state(self, IRLAN_IDLE); + break; + case IRLAN_WATCHDOG_TIMEOUT: + IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__ ); + break; + default: + IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__ , event); + break; + } + if (skb) + dev_kfree_skb(skb); + + return 0; +} + +/* + * Function irlan_client_state_media (self, event, skb, info) + * + * MEDIA, The irlan_client has issued a GetMedia command and is awaiting a + * reply. + * + */ +static int irlan_client_state_media(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + + switch(event) { + case IRLAN_DATA_INDICATION: + irlan_client_parse_response(self, skb); + irlan_open_data_channel(self); + irlan_next_client_state(self, IRLAN_OPEN); + break; + case IRLAN_LMP_DISCONNECT: + case IRLAN_LAP_DISCONNECT: + irlan_next_client_state(self, IRLAN_IDLE); + break; + case IRLAN_WATCHDOG_TIMEOUT: + IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__ ); + break; + default: + IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__ , event); + break; + } + if (skb) + dev_kfree_skb(skb); + + return 0; +} + +/* + * Function irlan_client_state_open (self, event, skb, info) + * + * OPEN, The irlan_client has issued a OpenData command and is awaiting a + * reply + * + */ +static int irlan_client_state_open(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + struct qos_info qos; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + + switch(event) { + case IRLAN_DATA_INDICATION: + irlan_client_parse_response(self, skb); + + /* + * Check if we have got the remote TSAP for data + * communications + */ + IRDA_ASSERT(self->dtsap_sel_data != 0, return -1;); + + /* Check which access type we are dealing with */ + switch (self->client.access_type) { + case ACCESS_PEER: + if (self->provider.state == IRLAN_OPEN) { + + irlan_next_client_state(self, IRLAN_ARB); + irlan_do_client_event(self, IRLAN_CHECK_CON_ARB, + NULL); + } else { + + irlan_next_client_state(self, IRLAN_WAIT); + } + break; + case ACCESS_DIRECT: + case ACCESS_HOSTED: + qos.link_disc_time.bits = 0x01; /* 3 secs */ + + irttp_connect_request(self->tsap_data, + self->dtsap_sel_data, + self->saddr, self->daddr, &qos, + IRLAN_MTU, NULL); + + irlan_next_client_state(self, IRLAN_DATA); + break; + default: + IRDA_DEBUG(2, "%s(), unknown access type!\n", __FUNCTION__ ); + break; + } + break; + case IRLAN_LMP_DISCONNECT: + case IRLAN_LAP_DISCONNECT: + irlan_next_client_state(self, IRLAN_IDLE); + break; + case IRLAN_WATCHDOG_TIMEOUT: + IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__ ); + break; + default: + IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__ , event); + break; + } + + if (skb) + dev_kfree_skb(skb); + + return 0; +} + +/* + * Function irlan_client_state_wait (self, event, skb, info) + * + * WAIT, The irlan_client is waiting for the local provider to enter the + * provider OPEN state. + * + */ +static int irlan_client_state_wait(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + + switch(event) { + case IRLAN_PROVIDER_SIGNAL: + irlan_next_client_state(self, IRLAN_ARB); + irlan_do_client_event(self, IRLAN_CHECK_CON_ARB, NULL); + break; + case IRLAN_LMP_DISCONNECT: + case IRLAN_LAP_DISCONNECT: + irlan_next_client_state(self, IRLAN_IDLE); + break; + case IRLAN_WATCHDOG_TIMEOUT: + IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__ ); + break; + default: + IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__ , event); + break; + } + if (skb) + dev_kfree_skb(skb); + + return 0; +} + +static int irlan_client_state_arb(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + struct qos_info qos; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + + switch(event) { + case IRLAN_CHECK_CON_ARB: + if (self->client.recv_arb_val == self->provider.send_arb_val) { + irlan_next_client_state(self, IRLAN_CLOSE); + irlan_close_data_channel(self); + } else if (self->client.recv_arb_val < + self->provider.send_arb_val) + { + qos.link_disc_time.bits = 0x01; /* 3 secs */ + + irlan_next_client_state(self, IRLAN_DATA); + irttp_connect_request(self->tsap_data, + self->dtsap_sel_data, + self->saddr, self->daddr, &qos, + IRLAN_MTU, NULL); + } else if (self->client.recv_arb_val > + self->provider.send_arb_val) + { + IRDA_DEBUG(2, "%s(), lost the battle :-(\n", __FUNCTION__ ); + } + break; + case IRLAN_DATA_CONNECT_INDICATION: + irlan_next_client_state(self, IRLAN_DATA); + break; + case IRLAN_LMP_DISCONNECT: + case IRLAN_LAP_DISCONNECT: + irlan_next_client_state(self, IRLAN_IDLE); + break; + case IRLAN_WATCHDOG_TIMEOUT: + IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__ ); + break; + default: + IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__ , event); + break; + } + if (skb) + dev_kfree_skb(skb); + + return 0; +} + +/* + * Function irlan_client_state_data (self, event, skb, info) + * + * DATA, The data channel is connected, allowing data transfers between + * the local and remote machines. + * + */ +static int irlan_client_state_data(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return -1;); + + switch(event) { + case IRLAN_DATA_INDICATION: + irlan_client_parse_response(self, skb); + break; + case IRLAN_LMP_DISCONNECT: /* FALLTHROUGH */ + case IRLAN_LAP_DISCONNECT: + irlan_next_client_state(self, IRLAN_IDLE); + break; + default: + IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__ , event); + break; + } + if (skb) + dev_kfree_skb(skb); + + return 0; +} + +/* + * Function irlan_client_state_close (self, event, skb, info) + * + * + * + */ +static int irlan_client_state_close(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + if (skb) + dev_kfree_skb(skb); + + return 0; +} + +/* + * Function irlan_client_state_sync (self, event, skb, info) + * + * + * + */ +static int irlan_client_state_sync(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + if (skb) + dev_kfree_skb(skb); + + return 0; +} + + + + + + + + + + + + + diff --git a/net/irda/irlan/irlan_common.c b/net/irda/irlan/irlan_common.c new file mode 100644 index 000000000000..657d12210578 --- /dev/null +++ b/net/irda/irlan/irlan_common.c @@ -0,0 +1,1200 @@ +/********************************************************************* + * + * Filename: irlan_common.c + * Version: 0.9 + * Description: IrDA LAN Access Protocol Implementation + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sun Aug 31 20:14:37 1997 + * Modified at: Sun Dec 26 21:53:10 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1997, 1999 Dag Brattli <dagb@cs.uit.no>, + * 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/random.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/rtnetlink.h> +#include <linux/moduleparam.h> +#include <linux/bitops.h> + +#include <asm/system.h> +#include <asm/byteorder.h> + +#include <net/irda/irda.h> +#include <net/irda/irttp.h> +#include <net/irda/irlmp.h> +#include <net/irda/iriap.h> +#include <net/irda/timer.h> + +#include <net/irda/irlan_common.h> +#include <net/irda/irlan_client.h> +#include <net/irda/irlan_provider.h> +#include <net/irda/irlan_eth.h> +#include <net/irda/irlan_filter.h> + + +/* + * Send gratuitous ARP when connected to a new AP or not. May be a clever + * thing to do, but for some reason the machine crashes if you use DHCP. So + * lets not use it by default. + */ +#undef CONFIG_IRLAN_SEND_GRATUITOUS_ARP + +/* extern char sysctl_devname[]; */ + +/* + * Master structure + */ +static LIST_HEAD(irlans); + +static void *ckey; +static void *skey; + +/* Module parameters */ +static int eth; /* Use "eth" or "irlan" name for devices */ +static int access = ACCESS_PEER; /* PEER, DIRECT or HOSTED */ + +#ifdef CONFIG_PROC_FS +static const char *irlan_access[] = { + "UNKNOWN", + "DIRECT", + "PEER", + "HOSTED" +}; + +static const char *irlan_media[] = { + "UNKNOWN", + "802.3", + "802.5" +}; + +extern struct proc_dir_entry *proc_irda; + +static int irlan_seq_open(struct inode *inode, struct file *file); + +static struct file_operations irlan_fops = { + .owner = THIS_MODULE, + .open = irlan_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +extern struct proc_dir_entry *proc_irda; +#endif /* CONFIG_PROC_FS */ + +static struct irlan_cb *irlan_open(__u32 saddr, __u32 daddr); +static void __irlan_close(struct irlan_cb *self); +static int __irlan_insert_param(struct sk_buff *skb, char *param, int type, + __u8 value_byte, __u16 value_short, + __u8 *value_array, __u16 value_len); +static void irlan_open_unicast_addr(struct irlan_cb *self); +static void irlan_get_unicast_addr(struct irlan_cb *self); +void irlan_close_tsaps(struct irlan_cb *self); + +/* + * Function irlan_init (void) + * + * Initialize IrLAN layer + * + */ +static int __init irlan_init(void) +{ + struct irlan_cb *new; + __u16 hints; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + +#ifdef CONFIG_PROC_FS + { struct proc_dir_entry *proc; + proc = create_proc_entry("irlan", 0, proc_irda); + if (!proc) { + printk(KERN_ERR "irlan_init: can't create /proc entry!\n"); + return -ENODEV; + } + + proc->proc_fops = &irlan_fops; + } +#endif /* CONFIG_PROC_FS */ + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + hints = irlmp_service_to_hint(S_LAN); + + /* Register with IrLMP as a client */ + ckey = irlmp_register_client(hints, &irlan_client_discovery_indication, + NULL, NULL); + + /* Register with IrLMP as a service */ + skey = irlmp_register_service(hints); + + /* Start the master IrLAN instance (the only one for now) */ + new = irlan_open(DEV_ADDR_ANY, DEV_ADDR_ANY); + + /* The master will only open its (listen) control TSAP */ + irlan_provider_open_ctrl_tsap(new); + + /* Do some fast discovery! */ + irlmp_discovery_request(DISCOVERY_DEFAULT_SLOTS); + + return 0; +} + +static void __exit irlan_cleanup(void) +{ + struct irlan_cb *self, *next; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + irlmp_unregister_client(ckey); + irlmp_unregister_service(skey); + +#ifdef CONFIG_PROC_FS + remove_proc_entry("irlan", proc_irda); +#endif /* CONFIG_PROC_FS */ + + /* Cleanup any leftover network devices */ + rtnl_lock(); + list_for_each_entry_safe(self, next, &irlans, dev_list) { + __irlan_close(self); + } + rtnl_unlock(); +} + +/* + * Function irlan_open (void) + * + * Open new instance of a client/provider, we should only register the + * network device if this instance is ment for a particular client/provider + */ +static struct irlan_cb *irlan_open(__u32 saddr, __u32 daddr) +{ + struct net_device *dev; + struct irlan_cb *self; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + /* Create network device with irlan */ + dev = alloc_irlandev(eth ? "eth%d" : "irlan%d"); + if (!dev) + return NULL; + + self = dev->priv; + self->dev = dev; + + /* + * Initialize local device structure + */ + self->magic = IRLAN_MAGIC; + self->saddr = saddr; + self->daddr = daddr; + + /* Provider access can only be PEER, DIRECT, or HOSTED */ + self->provider.access_type = access; + if (access == ACCESS_DIRECT) { + /* + * Since we are emulating an IrLAN sever we will have to + * give ourself an ethernet address! + */ + dev->dev_addr[0] = 0x40; + dev->dev_addr[1] = 0x00; + dev->dev_addr[2] = 0x00; + dev->dev_addr[3] = 0x00; + get_random_bytes(dev->dev_addr+4, 1); + get_random_bytes(dev->dev_addr+5, 1); + } + + self->media = MEDIA_802_3; + self->disconnect_reason = LM_USER_REQUEST; + init_timer(&self->watchdog_timer); + init_timer(&self->client.kick_timer); + init_waitqueue_head(&self->open_wait); + + skb_queue_head_init(&self->client.txq); + + irlan_next_client_state(self, IRLAN_IDLE); + irlan_next_provider_state(self, IRLAN_IDLE); + + if (register_netdev(dev)) { + IRDA_DEBUG(2, "%s(), register_netdev() failed!\n", + __FUNCTION__ ); + self = NULL; + free_netdev(dev); + } else { + rtnl_lock(); + list_add_rcu(&self->dev_list, &irlans); + rtnl_unlock(); + } + + return self; +} +/* + * Function __irlan_close (self) + * + * This function closes and deallocates the IrLAN client instances. Be + * aware that other functions which calls client_close() must + * remove self from irlans list first. + */ +static void __irlan_close(struct irlan_cb *self) +{ + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + ASSERT_RTNL(); + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + del_timer_sync(&self->watchdog_timer); + del_timer_sync(&self->client.kick_timer); + + /* Close all open connections and remove TSAPs */ + irlan_close_tsaps(self); + + if (self->client.iriap) + iriap_close(self->client.iriap); + + /* Remove frames queued on the control channel */ + skb_queue_purge(&self->client.txq); + + /* Unregister and free self via destructor */ + unregister_netdevice(self->dev); +} + +/* Find any instance of irlan, used for client discovery wakeup */ +struct irlan_cb *irlan_get_any(void) +{ + struct irlan_cb *self; + + list_for_each_entry_rcu(self, &irlans, dev_list) { + return self; + } + return NULL; +} + +/* + * Function irlan_connect_indication (instance, sap, qos, max_sdu_size, skb) + * + * Here we receive the connect indication for the data channel + * + */ +static void irlan_connect_indication(void *instance, void *sap, + struct qos_info *qos, + __u32 max_sdu_size, + __u8 max_header_size, + struct sk_buff *skb) +{ + struct irlan_cb *self; + struct tsap_cb *tsap; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + self = (struct irlan_cb *) instance; + tsap = (struct tsap_cb *) sap; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + IRDA_ASSERT(tsap == self->tsap_data,return;); + + self->max_sdu_size = max_sdu_size; + self->max_header_size = max_header_size; + + IRDA_DEBUG(0, "%s: We are now connected!\n", __FUNCTION__); + + del_timer(&self->watchdog_timer); + + /* If you want to pass the skb to *both* state machines, you will + * need to skb_clone() it, so that you don't free it twice. + * As the state machines don't need it, git rid of it here... + * Jean II */ + if (skb) + dev_kfree_skb(skb); + + irlan_do_provider_event(self, IRLAN_DATA_CONNECT_INDICATION, NULL); + irlan_do_client_event(self, IRLAN_DATA_CONNECT_INDICATION, NULL); + + if (self->provider.access_type == ACCESS_PEER) { + /* + * Data channel is open, so we are now allowed to + * configure the remote filter + */ + irlan_get_unicast_addr(self); + irlan_open_unicast_addr(self); + } + /* Ready to transfer Ethernet frames (at last) */ + netif_start_queue(self->dev); /* Clear reason */ +} + +static void irlan_connect_confirm(void *instance, void *sap, + struct qos_info *qos, + __u32 max_sdu_size, + __u8 max_header_size, + struct sk_buff *skb) +{ + struct irlan_cb *self; + + self = (struct irlan_cb *) instance; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + self->max_sdu_size = max_sdu_size; + self->max_header_size = max_header_size; + + /* TODO: we could set the MTU depending on the max_sdu_size */ + + IRDA_DEBUG(0, "%s: We are now connected!\n", __FUNCTION__); + del_timer(&self->watchdog_timer); + + /* + * Data channel is open, so we are now allowed to configure the remote + * filter + */ + irlan_get_unicast_addr(self); + irlan_open_unicast_addr(self); + + /* Open broadcast and multicast filter by default */ + irlan_set_broadcast_filter(self, TRUE); + irlan_set_multicast_filter(self, TRUE); + + /* Ready to transfer Ethernet frames */ + netif_start_queue(self->dev); + self->disconnect_reason = 0; /* Clear reason */ +#ifdef CONFIG_IRLAN_SEND_GRATUITOUS_ARP + irlan_eth_send_gratuitous_arp(&self->dev); +#endif + wake_up_interruptible(&self->open_wait); +} + +/* + * Function irlan_client_disconnect_indication (handle) + * + * Callback function for the IrTTP layer. Indicates a disconnection of + * the specified connection (handle) + */ +static void irlan_disconnect_indication(void *instance, + void *sap, LM_REASON reason, + struct sk_buff *userdata) +{ + struct irlan_cb *self; + struct tsap_cb *tsap; + + IRDA_DEBUG(0, "%s(), reason=%d\n", __FUNCTION__ , reason); + + self = (struct irlan_cb *) instance; + tsap = (struct tsap_cb *) sap; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + IRDA_ASSERT(tsap != NULL, return;); + IRDA_ASSERT(tsap->magic == TTP_TSAP_MAGIC, return;); + + IRDA_ASSERT(tsap == self->tsap_data, return;); + + IRDA_DEBUG(2, "IrLAN, data channel disconnected by peer!\n"); + + /* Save reason so we know if we should try to reconnect or not */ + self->disconnect_reason = reason; + + switch (reason) { + case LM_USER_REQUEST: /* User request */ + IRDA_DEBUG(2, "%s(), User requested\n", __FUNCTION__ ); + break; + case LM_LAP_DISCONNECT: /* Unexpected IrLAP disconnect */ + IRDA_DEBUG(2, "%s(), Unexpected IrLAP disconnect\n", __FUNCTION__ ); + break; + case LM_CONNECT_FAILURE: /* Failed to establish IrLAP connection */ + IRDA_DEBUG(2, "%s(), IrLAP connect failed\n", __FUNCTION__ ); + break; + case LM_LAP_RESET: /* IrLAP reset */ + IRDA_DEBUG(2, "%s(), IrLAP reset\n", __FUNCTION__ ); + break; + case LM_INIT_DISCONNECT: + IRDA_DEBUG(2, "%s(), IrLMP connect failed\n", __FUNCTION__ ); + break; + default: + IRDA_ERROR("%s(), Unknown disconnect reason\n", __FUNCTION__); + break; + } + + /* If you want to pass the skb to *both* state machines, you will + * need to skb_clone() it, so that you don't free it twice. + * As the state machines don't need it, git rid of it here... + * Jean II */ + if (userdata) + dev_kfree_skb(userdata); + + irlan_do_client_event(self, IRLAN_LMP_DISCONNECT, NULL); + irlan_do_provider_event(self, IRLAN_LMP_DISCONNECT, NULL); + + wake_up_interruptible(&self->open_wait); +} + +void irlan_open_data_tsap(struct irlan_cb *self) +{ + struct tsap_cb *tsap; + notify_t notify; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + /* Check if already open */ + if (self->tsap_data) + return; + + irda_notify_init(¬ify); + + notify.data_indication = irlan_eth_receive; + notify.udata_indication = irlan_eth_receive; + notify.connect_indication = irlan_connect_indication; + notify.connect_confirm = irlan_connect_confirm; + notify.flow_indication = irlan_eth_flow_indication; + notify.disconnect_indication = irlan_disconnect_indication; + notify.instance = self; + strlcpy(notify.name, "IrLAN data", sizeof(notify.name)); + + tsap = irttp_open_tsap(LSAP_ANY, DEFAULT_INITIAL_CREDIT, ¬ify); + if (!tsap) { + IRDA_DEBUG(2, "%s(), Got no tsap!\n", __FUNCTION__ ); + return; + } + self->tsap_data = tsap; + + /* + * This is the data TSAP selector which we will pass to the client + * when the client ask for it. + */ + self->stsap_sel_data = self->tsap_data->stsap_sel; +} + +void irlan_close_tsaps(struct irlan_cb *self) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + /* Disconnect and close all open TSAP connections */ + if (self->tsap_data) { + irttp_disconnect_request(self->tsap_data, NULL, P_NORMAL); + irttp_close_tsap(self->tsap_data); + self->tsap_data = NULL; + } + if (self->client.tsap_ctrl) { + irttp_disconnect_request(self->client.tsap_ctrl, NULL, + P_NORMAL); + irttp_close_tsap(self->client.tsap_ctrl); + self->client.tsap_ctrl = NULL; + } + if (self->provider.tsap_ctrl) { + irttp_disconnect_request(self->provider.tsap_ctrl, NULL, + P_NORMAL); + irttp_close_tsap(self->provider.tsap_ctrl); + self->provider.tsap_ctrl = NULL; + } + self->disconnect_reason = LM_USER_REQUEST; +} + +/* + * Function irlan_ias_register (self, tsap_sel) + * + * Register with LM-IAS + * + */ +void irlan_ias_register(struct irlan_cb *self, __u8 tsap_sel) +{ + struct ias_object *obj; + struct ias_value *new_value; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + /* + * Check if object has already been registered by a previous provider. + * If that is the case, we just change the value of the attribute + */ + if (!irias_find_object("IrLAN")) { + obj = irias_new_object("IrLAN", IAS_IRLAN_ID); + irias_add_integer_attrib(obj, "IrDA:TinyTP:LsapSel", tsap_sel, + IAS_KERNEL_ATTR); + irias_insert_object(obj); + } else { + new_value = irias_new_integer_value(tsap_sel); + irias_object_change_attribute("IrLAN", "IrDA:TinyTP:LsapSel", + new_value); + } + + /* Register PnP object only if not registered before */ + if (!irias_find_object("PnP")) { + obj = irias_new_object("PnP", IAS_PNP_ID); +#if 0 + irias_add_string_attrib(obj, "Name", sysctl_devname, + IAS_KERNEL_ATTR); +#else + irias_add_string_attrib(obj, "Name", "Linux", IAS_KERNEL_ATTR); +#endif + irias_add_string_attrib(obj, "DeviceID", "HWP19F0", + IAS_KERNEL_ATTR); + irias_add_integer_attrib(obj, "CompCnt", 1, IAS_KERNEL_ATTR); + if (self->provider.access_type == ACCESS_PEER) + irias_add_string_attrib(obj, "Comp#01", "PNP8389", + IAS_KERNEL_ATTR); + else + irias_add_string_attrib(obj, "Comp#01", "PNP8294", + IAS_KERNEL_ATTR); + + irias_add_string_attrib(obj, "Manufacturer", + "Linux-IrDA Project", IAS_KERNEL_ATTR); + irias_insert_object(obj); + } +} + +/* + * Function irlan_run_ctrl_tx_queue (self) + * + * Try to send the next command in the control transmit queue + * + */ +int irlan_run_ctrl_tx_queue(struct irlan_cb *self) +{ + struct sk_buff *skb; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + if (irda_lock(&self->client.tx_busy) == FALSE) + return -EBUSY; + + skb = skb_dequeue(&self->client.txq); + if (!skb) { + self->client.tx_busy = FALSE; + return 0; + } + + /* Check that it's really possible to send commands */ + if ((self->client.tsap_ctrl == NULL) || + (self->client.state == IRLAN_IDLE)) + { + self->client.tx_busy = FALSE; + dev_kfree_skb(skb); + return -1; + } + IRDA_DEBUG(2, "%s(), sending ...\n", __FUNCTION__ ); + + return irttp_data_request(self->client.tsap_ctrl, skb); +} + +/* + * Function irlan_ctrl_data_request (self, skb) + * + * This function makes sure that commands on the control channel is being + * sent in a command/response fashion + */ +static void irlan_ctrl_data_request(struct irlan_cb *self, struct sk_buff *skb) +{ + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + /* Queue command */ + skb_queue_tail(&self->client.txq, skb); + + /* Try to send command */ + irlan_run_ctrl_tx_queue(self); +} + +/* + * Function irlan_get_provider_info (self) + * + * Send Get Provider Information command to peer IrLAN layer + * + */ +void irlan_get_provider_info(struct irlan_cb *self) +{ + struct sk_buff *skb; + __u8 *frame; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + skb = dev_alloc_skb(64); + if (!skb) + return; + + /* Reserve space for TTP, LMP, and LAP header */ + skb_reserve(skb, self->client.max_header_size); + skb_put(skb, 2); + + frame = skb->data; + + frame[0] = CMD_GET_PROVIDER_INFO; + frame[1] = 0x00; /* Zero parameters */ + + irlan_ctrl_data_request(self, skb); +} + +/* + * Function irlan_open_data_channel (self) + * + * Send an Open Data Command to provider + * + */ +void irlan_open_data_channel(struct irlan_cb *self) +{ + struct sk_buff *skb; + __u8 *frame; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + skb = dev_alloc_skb(64); + if (!skb) + return; + + skb_reserve(skb, self->client.max_header_size); + skb_put(skb, 2); + + frame = skb->data; + + /* Build frame */ + frame[0] = CMD_OPEN_DATA_CHANNEL; + frame[1] = 0x02; /* Two parameters */ + + irlan_insert_string_param(skb, "MEDIA", "802.3"); + irlan_insert_string_param(skb, "ACCESS_TYPE", "DIRECT"); + /* irlan_insert_string_param(skb, "MODE", "UNRELIABLE"); */ + +/* self->use_udata = TRUE; */ + + irlan_ctrl_data_request(self, skb); +} + +void irlan_close_data_channel(struct irlan_cb *self) +{ + struct sk_buff *skb; + __u8 *frame; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + /* Check if the TSAP is still there */ + if (self->client.tsap_ctrl == NULL) + return; + + skb = dev_alloc_skb(64); + if (!skb) + return; + + skb_reserve(skb, self->client.max_header_size); + skb_put(skb, 2); + + frame = skb->data; + + /* Build frame */ + frame[0] = CMD_CLOSE_DATA_CHAN; + frame[1] = 0x01; /* Two parameters */ + + irlan_insert_byte_param(skb, "DATA_CHAN", self->dtsap_sel_data); + + irlan_ctrl_data_request(self, skb); +} + +/* + * Function irlan_open_unicast_addr (self) + * + * Make IrLAN provider accept ethernet frames addressed to the unicast + * address. + * + */ +static void irlan_open_unicast_addr(struct irlan_cb *self) +{ + struct sk_buff *skb; + __u8 *frame; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + skb = dev_alloc_skb(128); + if (!skb) + return; + + /* Reserve space for TTP, LMP, and LAP header */ + skb_reserve(skb, self->max_header_size); + skb_put(skb, 2); + + frame = skb->data; + + frame[0] = CMD_FILTER_OPERATION; + frame[1] = 0x03; /* Three parameters */ + irlan_insert_byte_param(skb, "DATA_CHAN" , self->dtsap_sel_data); + irlan_insert_string_param(skb, "FILTER_TYPE", "DIRECTED"); + irlan_insert_string_param(skb, "FILTER_MODE", "FILTER"); + + irlan_ctrl_data_request(self, skb); +} + +/* + * Function irlan_set_broadcast_filter (self, status) + * + * Make IrLAN provider accept ethernet frames addressed to the broadcast + * address. Be careful with the use of this one, since there may be a lot + * of broadcast traffic out there. We can still function without this + * one but then _we_ have to initiate all communication with other + * hosts, since ARP request for this host will not be answered. + */ +void irlan_set_broadcast_filter(struct irlan_cb *self, int status) +{ + struct sk_buff *skb; + __u8 *frame; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + skb = dev_alloc_skb(128); + if (!skb) + return; + + /* Reserve space for TTP, LMP, and LAP header */ + skb_reserve(skb, self->client.max_header_size); + skb_put(skb, 2); + + frame = skb->data; + + frame[0] = CMD_FILTER_OPERATION; + frame[1] = 0x03; /* Three parameters */ + irlan_insert_byte_param(skb, "DATA_CHAN", self->dtsap_sel_data); + irlan_insert_string_param(skb, "FILTER_TYPE", "BROADCAST"); + if (status) + irlan_insert_string_param(skb, "FILTER_MODE", "FILTER"); + else + irlan_insert_string_param(skb, "FILTER_MODE", "NONE"); + + irlan_ctrl_data_request(self, skb); +} + +/* + * Function irlan_set_multicast_filter (self, status) + * + * Make IrLAN provider accept ethernet frames addressed to the multicast + * address. + * + */ +void irlan_set_multicast_filter(struct irlan_cb *self, int status) +{ + struct sk_buff *skb; + __u8 *frame; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + skb = dev_alloc_skb(128); + if (!skb) + return; + + /* Reserve space for TTP, LMP, and LAP header */ + skb_reserve(skb, self->client.max_header_size); + skb_put(skb, 2); + + frame = skb->data; + + frame[0] = CMD_FILTER_OPERATION; + frame[1] = 0x03; /* Three parameters */ + irlan_insert_byte_param(skb, "DATA_CHAN", self->dtsap_sel_data); + irlan_insert_string_param(skb, "FILTER_TYPE", "MULTICAST"); + if (status) + irlan_insert_string_param(skb, "FILTER_MODE", "ALL"); + else + irlan_insert_string_param(skb, "FILTER_MODE", "NONE"); + + irlan_ctrl_data_request(self, skb); +} + +/* + * Function irlan_get_unicast_addr (self) + * + * Retrieves the unicast address from the IrLAN provider. This address + * will be inserted into the devices structure, so the ethernet layer + * can construct its packets. + * + */ +static void irlan_get_unicast_addr(struct irlan_cb *self) +{ + struct sk_buff *skb; + __u8 *frame; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + skb = dev_alloc_skb(128); + if (!skb) + return; + + /* Reserve space for TTP, LMP, and LAP header */ + skb_reserve(skb, self->client.max_header_size); + skb_put(skb, 2); + + frame = skb->data; + + frame[0] = CMD_FILTER_OPERATION; + frame[1] = 0x03; /* Three parameters */ + irlan_insert_byte_param(skb, "DATA_CHAN", self->dtsap_sel_data); + irlan_insert_string_param(skb, "FILTER_TYPE", "DIRECTED"); + irlan_insert_string_param(skb, "FILTER_OPERATION", "DYNAMIC"); + + irlan_ctrl_data_request(self, skb); +} + +/* + * Function irlan_get_media_char (self) + * + * + * + */ +void irlan_get_media_char(struct irlan_cb *self) +{ + struct sk_buff *skb; + __u8 *frame; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + skb = dev_alloc_skb(64); + if (!skb) + return; + + /* Reserve space for TTP, LMP, and LAP header */ + skb_reserve(skb, self->client.max_header_size); + skb_put(skb, 2); + + frame = skb->data; + + /* Build frame */ + frame[0] = CMD_GET_MEDIA_CHAR; + frame[1] = 0x01; /* One parameter */ + + irlan_insert_string_param(skb, "MEDIA", "802.3"); + irlan_ctrl_data_request(self, skb); +} + +/* + * Function insert_byte_param (skb, param, value) + * + * Insert byte parameter into frame + * + */ +int irlan_insert_byte_param(struct sk_buff *skb, char *param, __u8 value) +{ + return __irlan_insert_param(skb, param, IRLAN_BYTE, value, 0, NULL, 0); +} + +int irlan_insert_short_param(struct sk_buff *skb, char *param, __u16 value) +{ + return __irlan_insert_param(skb, param, IRLAN_SHORT, 0, value, NULL, 0); +} + +/* + * Function insert_string (skb, param, value) + * + * Insert string parameter into frame + * + */ +int irlan_insert_string_param(struct sk_buff *skb, char *param, char *string) +{ + int string_len = strlen(string); + + return __irlan_insert_param(skb, param, IRLAN_ARRAY, 0, 0, string, + string_len); +} + +/* + * Function insert_array_param(skb, param, value, len_value) + * + * Insert array parameter into frame + * + */ +int irlan_insert_array_param(struct sk_buff *skb, char *name, __u8 *array, + __u16 array_len) +{ + return __irlan_insert_param(skb, name, IRLAN_ARRAY, 0, 0, array, + array_len); +} + +/* + * Function insert_param (skb, param, value, byte) + * + * Insert parameter at end of buffer, structure of a parameter is: + * + * ----------------------------------------------------------------------- + * | Name Length[1] | Param Name[1..255] | Val Length[2] | Value[0..1016]| + * ----------------------------------------------------------------------- + */ +static int __irlan_insert_param(struct sk_buff *skb, char *param, int type, + __u8 value_byte, __u16 value_short, + __u8 *value_array, __u16 value_len) +{ + __u8 *frame; + __u8 param_len; + __u16 tmp_le; /* Temporary value in little endian format */ + int n=0; + + if (skb == NULL) { + IRDA_DEBUG(2, "%s(), Got NULL skb\n", __FUNCTION__ ); + return 0; + } + + param_len = strlen(param); + switch (type) { + case IRLAN_BYTE: + value_len = 1; + break; + case IRLAN_SHORT: + value_len = 2; + break; + case IRLAN_ARRAY: + IRDA_ASSERT(value_array != NULL, return 0;); + IRDA_ASSERT(value_len > 0, return 0;); + break; + default: + IRDA_DEBUG(2, "%s(), Unknown parameter type!\n", __FUNCTION__ ); + return 0; + break; + } + + /* Insert at end of sk-buffer */ + frame = skb->tail; + + /* Make space for data */ + if (skb_tailroom(skb) < (param_len+value_len+3)) { + IRDA_DEBUG(2, "%s(), No more space at end of skb\n", __FUNCTION__ ); + return 0; + } + skb_put(skb, param_len+value_len+3); + + /* Insert parameter length */ + frame[n++] = param_len; + + /* Insert parameter */ + memcpy(frame+n, param, param_len); n += param_len; + + /* Insert value length (2 byte little endian format, LSB first) */ + tmp_le = cpu_to_le16(value_len); + memcpy(frame+n, &tmp_le, 2); n += 2; /* To avoid alignment problems */ + + /* Insert value */ + switch (type) { + case IRLAN_BYTE: + frame[n++] = value_byte; + break; + case IRLAN_SHORT: + tmp_le = cpu_to_le16(value_short); + memcpy(frame+n, &tmp_le, 2); n += 2; + break; + case IRLAN_ARRAY: + memcpy(frame+n, value_array, value_len); n+=value_len; + break; + default: + break; + } + IRDA_ASSERT(n == (param_len+value_len+3), return 0;); + + return param_len+value_len+3; +} + +/* + * Function irlan_extract_param (buf, name, value, len) + * + * Extracts a single parameter name/value pair from buffer and updates + * the buffer pointer to point to the next name/value pair. + */ +int irlan_extract_param(__u8 *buf, char *name, char *value, __u16 *len) +{ + __u8 name_len; + __u16 val_len; + int n=0; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + /* get length of parameter name (1 byte) */ + name_len = buf[n++]; + + if (name_len > 254) { + IRDA_DEBUG(2, "%s(), name_len > 254\n", __FUNCTION__ ); + return -RSP_INVALID_COMMAND_FORMAT; + } + + /* get parameter name */ + memcpy(name, buf+n, name_len); + name[name_len] = '\0'; + n+=name_len; + + /* + * Get length of parameter value (2 bytes in little endian + * format) + */ + memcpy(&val_len, buf+n, 2); /* To avoid alignment problems */ + le16_to_cpus(&val_len); n+=2; + + if (val_len > 1016) { + IRDA_DEBUG(2, "%s(), parameter length to long\n", __FUNCTION__ ); + return -RSP_INVALID_COMMAND_FORMAT; + } + *len = val_len; + + /* get parameter value */ + memcpy(value, buf+n, val_len); + value[val_len] = '\0'; + n+=val_len; + + IRDA_DEBUG(4, "Parameter: %s ", name); + IRDA_DEBUG(4, "Value: %s\n", value); + + return n; +} + +#ifdef CONFIG_PROC_FS + +/* + * Start of reading /proc entries. + * Return entry at pos, + * or start_token to indicate print header line + * or NULL if end of file + */ +static void *irlan_seq_start(struct seq_file *seq, loff_t *pos) +{ + int i = 1; + struct irlan_cb *self; + + rcu_read_lock(); + if (*pos == 0) + return SEQ_START_TOKEN; + + list_for_each_entry(self, &irlans, dev_list) { + if (*pos == i) + return self; + ++i; + } + return NULL; +} + +/* Return entry after v, and increment pos */ +static void *irlan_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + struct list_head *nxt; + + ++*pos; + if (v == SEQ_START_TOKEN) + nxt = irlans.next; + else + nxt = ((struct irlan_cb *)v)->dev_list.next; + + return (nxt == &irlans) ? NULL + : list_entry(nxt, struct irlan_cb, dev_list); +} + +/* End of reading /proc file */ +static void irlan_seq_stop(struct seq_file *seq, void *v) +{ + rcu_read_unlock(); +} + + +/* + * Show one entry in /proc file. + */ +static int irlan_seq_show(struct seq_file *seq, void *v) +{ + if (v == SEQ_START_TOKEN) + seq_puts(seq, "IrLAN instances:\n"); + else { + struct irlan_cb *self = v; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return -1;); + + seq_printf(seq,"ifname: %s,\n", + self->dev->name); + seq_printf(seq,"client state: %s, ", + irlan_state[ self->client.state]); + seq_printf(seq,"provider state: %s,\n", + irlan_state[ self->provider.state]); + seq_printf(seq,"saddr: %#08x, ", + self->saddr); + seq_printf(seq,"daddr: %#08x\n", + self->daddr); + seq_printf(seq,"version: %d.%d,\n", + self->version[1], self->version[0]); + seq_printf(seq,"access type: %s\n", + irlan_access[self->client.access_type]); + seq_printf(seq,"media: %s\n", + irlan_media[self->media]); + + seq_printf(seq,"local filter:\n"); + seq_printf(seq,"remote filter: "); + irlan_print_filter(seq, self->client.filter_type); + seq_printf(seq,"tx busy: %s\n", + netif_queue_stopped(self->dev) ? "TRUE" : "FALSE"); + + seq_putc(seq,'\n'); + } + return 0; +} + +static struct seq_operations irlan_seq_ops = { + .start = irlan_seq_start, + .next = irlan_seq_next, + .stop = irlan_seq_stop, + .show = irlan_seq_show, +}; + +static int irlan_seq_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &irlan_seq_ops); +} +#endif + +MODULE_AUTHOR("Dag Brattli <dagb@cs.uit.no>"); +MODULE_DESCRIPTION("The Linux IrDA LAN protocol"); +MODULE_LICENSE("GPL"); + +module_param(eth, bool, 0); +MODULE_PARM_DESC(eth, "Name devices ethX (0) or irlanX (1)"); +module_param(access, int, 0); +MODULE_PARM_DESC(access, "Access type DIRECT=1, PEER=2, HOSTED=3"); + +module_init(irlan_init); +module_exit(irlan_cleanup); + diff --git a/net/irda/irlan/irlan_eth.c b/net/irda/irlan/irlan_eth.c new file mode 100644 index 000000000000..071cd2cefd8a --- /dev/null +++ b/net/irda/irlan/irlan_eth.c @@ -0,0 +1,387 @@ +/********************************************************************* + * + * Filename: irlan_eth.c + * Version: + * Description: + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Thu Oct 15 08:37:58 1998 + * Modified at: Tue Mar 21 09:06:41 2000 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * Sources: skeleton.c by Donald Becker <becker@CESDIS.gsfc.nasa.gov> + * slip.c by Laurence Culhane, <loz@holmes.demon.co.uk> + * Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org> + * + * Copyright (c) 1998-2000 Dag Brattli, 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <linux/config.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/inetdevice.h> +#include <linux/if_arp.h> +#include <linux/module.h> +#include <net/arp.h> + +#include <net/irda/irda.h> +#include <net/irda/irmod.h> +#include <net/irda/irlan_common.h> +#include <net/irda/irlan_client.h> +#include <net/irda/irlan_event.h> +#include <net/irda/irlan_eth.h> + +static int irlan_eth_open(struct net_device *dev); +static int irlan_eth_close(struct net_device *dev); +static int irlan_eth_xmit(struct sk_buff *skb, struct net_device *dev); +static void irlan_eth_set_multicast_list( struct net_device *dev); +static struct net_device_stats *irlan_eth_get_stats(struct net_device *dev); + +/* + * Function irlan_eth_setup (dev) + * + * The network device initialization function. + * + */ +static void irlan_eth_setup(struct net_device *dev) +{ + dev->open = irlan_eth_open; + dev->stop = irlan_eth_close; + dev->hard_start_xmit = irlan_eth_xmit; + dev->get_stats = irlan_eth_get_stats; + dev->set_multicast_list = irlan_eth_set_multicast_list; + dev->destructor = free_netdev; + + SET_MODULE_OWNER(dev); + + ether_setup(dev); + + /* + * Lets do all queueing in IrTTP instead of this device driver. + * Queueing here as well can introduce some strange latency + * problems, which we will avoid by setting the queue size to 0. + */ + /* + * The bugs in IrTTP and IrLAN that created this latency issue + * have now been fixed, and we can propagate flow control properly + * to the network layer. However, this requires a minimal queue of + * packets for the device. + * Without flow control, the Tx Queue is 14 (ttp) + 0 (dev) = 14 + * With flow control, the Tx Queue is 7 (ttp) + 4 (dev) = 11 + * See irlan_eth_flow_indication()... + * Note : this number was randomly selected and would need to + * be adjusted. + * Jean II */ + dev->tx_queue_len = 4; +} + +/* + * Function alloc_irlandev + * + * Allocate network device and control block + * + */ +struct net_device *alloc_irlandev(const char *name) +{ + return alloc_netdev(sizeof(struct irlan_cb), name, + irlan_eth_setup); +} + +/* + * Function irlan_eth_open (dev) + * + * Network device has been opened by user + * + */ +static int irlan_eth_open(struct net_device *dev) +{ + struct irlan_cb *self = netdev_priv(dev); + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + /* Ready to play! */ + netif_stop_queue(dev); /* Wait until data link is ready */ + + /* We are now open, so time to do some work */ + self->disconnect_reason = 0; + irlan_client_wakeup(self, self->saddr, self->daddr); + + /* Make sure we have a hardware address before we return, + so DHCP clients gets happy */ + return wait_event_interruptible(self->open_wait, + !self->tsap_data->connected); +} + +/* + * Function irlan_eth_close (dev) + * + * Stop the ether network device, his function will usually be called by + * ifconfig down. We should now disconnect the link, We start the + * close timer, so that the instance will be removed if we are unable + * to discover the remote device after the disconnect. + */ +static int irlan_eth_close(struct net_device *dev) +{ + struct irlan_cb *self = netdev_priv(dev); + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + /* Stop device */ + netif_stop_queue(dev); + + irlan_close_data_channel(self); + irlan_close_tsaps(self); + + irlan_do_client_event(self, IRLAN_LMP_DISCONNECT, NULL); + irlan_do_provider_event(self, IRLAN_LMP_DISCONNECT, NULL); + + /* Remove frames queued on the control channel */ + skb_queue_purge(&self->client.txq); + + self->client.tx_busy = 0; + + return 0; +} + +/* + * Function irlan_eth_tx (skb) + * + * Transmits ethernet frames over IrDA link. + * + */ +static int irlan_eth_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct irlan_cb *self = netdev_priv(dev); + int ret; + + /* skb headroom large enough to contain all IrDA-headers? */ + if ((skb_headroom(skb) < self->max_header_size) || (skb_shared(skb))) { + struct sk_buff *new_skb = + skb_realloc_headroom(skb, self->max_header_size); + + /* We have to free the original skb anyway */ + dev_kfree_skb(skb); + + /* Did the realloc succeed? */ + if (new_skb == NULL) + return 0; + + /* Use the new skb instead */ + skb = new_skb; + } + + dev->trans_start = jiffies; + + /* Now queue the packet in the transport layer */ + if (self->use_udata) + ret = irttp_udata_request(self->tsap_data, skb); + else + ret = irttp_data_request(self->tsap_data, skb); + + if (ret < 0) { + /* + * IrTTPs tx queue is full, so we just have to + * drop the frame! You might think that we should + * just return -1 and don't deallocate the frame, + * but that is dangerous since it's possible that + * we have replaced the original skb with a new + * one with larger headroom, and that would really + * confuse do_dev_queue_xmit() in dev.c! I have + * tried :-) DB + */ + /* irttp_data_request already free the packet */ + self->stats.tx_dropped++; + } else { + self->stats.tx_packets++; + self->stats.tx_bytes += skb->len; + } + + return 0; +} + +/* + * Function irlan_eth_receive (handle, skb) + * + * This function gets the data that is received on the data channel + * + */ +int irlan_eth_receive(void *instance, void *sap, struct sk_buff *skb) +{ + struct irlan_cb *self = instance; + + if (skb == NULL) { + ++self->stats.rx_dropped; + return 0; + } + if (skb->len < ETH_HLEN) { + IRDA_DEBUG(0, "%s() : IrLAN frame too short (%d)\n", + __FUNCTION__, skb->len); + ++self->stats.rx_dropped; + dev_kfree_skb(skb); + return 0; + } + + /* + * Adopt this frame! Important to set all these fields since they + * might have been previously set by the low level IrDA network + * device driver + */ + skb->dev = self->dev; + skb->protocol=eth_type_trans(skb, skb->dev); /* Remove eth header */ + + self->stats.rx_packets++; + self->stats.rx_bytes += skb->len; + + netif_rx(skb); /* Eat it! */ + + return 0; +} + +/* + * Function irlan_eth_flow (status) + * + * Do flow control between IP/Ethernet and IrLAN/IrTTP. This is done by + * controlling the queue stop/start. + * + * The IrDA link layer has the advantage to have flow control, and + * IrTTP now properly handles that. Flow controlling the higher layers + * prevent us to drop Tx packets in here (up to 15% for a TCP socket, + * more for UDP socket). + * Also, this allow us to reduce the overall transmit queue, which means + * less latency in case of mixed traffic. + * Jean II + */ +void irlan_eth_flow_indication(void *instance, void *sap, LOCAL_FLOW flow) +{ + struct irlan_cb *self; + struct net_device *dev; + + self = (struct irlan_cb *) instance; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + dev = self->dev; + + IRDA_ASSERT(dev != NULL, return;); + + IRDA_DEBUG(0, "%s() : flow %s ; running %d\n", __FUNCTION__, + flow == FLOW_STOP ? "FLOW_STOP" : "FLOW_START", + netif_running(dev)); + + switch (flow) { + case FLOW_STOP: + /* IrTTP is full, stop higher layers */ + netif_stop_queue(dev); + break; + case FLOW_START: + default: + /* Tell upper layers that its time to transmit frames again */ + /* Schedule network layer */ + netif_wake_queue(dev); + break; + } +} + +/* + * Function irlan_etc_send_gratuitous_arp (dev) + * + * Send gratuitous ARP to announce that we have changed + * hardware address, so that all peers updates their ARP tables + */ +void irlan_eth_send_gratuitous_arp(struct net_device *dev) +{ + struct in_device *in_dev; + + /* + * When we get a new MAC address do a gratuitous ARP. This + * is useful if we have changed access points on the same + * subnet. + */ +#ifdef CONFIG_INET + IRDA_DEBUG(4, "IrLAN: Sending gratuitous ARP\n"); + rcu_read_lock(); + in_dev = __in_dev_get(dev); + if (in_dev == NULL) + goto out; + if (in_dev->ifa_list) + + arp_send(ARPOP_REQUEST, ETH_P_ARP, + in_dev->ifa_list->ifa_address, + dev, + in_dev->ifa_list->ifa_address, + NULL, dev->dev_addr, NULL); +out: + rcu_read_unlock(); +#endif /* CONFIG_INET */ +} + +/* + * Function set_multicast_list (dev) + * + * Configure the filtering of the device + * + */ +#define HW_MAX_ADDRS 4 /* Must query to get it! */ +static void irlan_eth_set_multicast_list(struct net_device *dev) +{ + struct irlan_cb *self = netdev_priv(dev); + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__ ); + + /* Check if data channel has been connected yet */ + if (self->client.state != IRLAN_DATA) { + IRDA_DEBUG(1, "%s(), delaying!\n", __FUNCTION__ ); + return; + } + + if (dev->flags & IFF_PROMISC) { + /* Enable promiscuous mode */ + IRDA_WARNING("Promiscous mode not implemented by IrLAN!\n"); + } + else if ((dev->flags & IFF_ALLMULTI) || dev->mc_count > HW_MAX_ADDRS) { + /* Disable promiscuous mode, use normal mode. */ + IRDA_DEBUG(4, "%s(), Setting multicast filter\n", __FUNCTION__ ); + /* hardware_set_filter(NULL); */ + + irlan_set_multicast_filter(self, TRUE); + } + else if (dev->mc_count) { + IRDA_DEBUG(4, "%s(), Setting multicast filter\n", __FUNCTION__ ); + /* Walk the address list, and load the filter */ + /* hardware_set_filter(dev->mc_list); */ + + irlan_set_multicast_filter(self, TRUE); + } + else { + IRDA_DEBUG(4, "%s(), Clearing multicast filter\n", __FUNCTION__ ); + irlan_set_multicast_filter(self, FALSE); + } + + if (dev->flags & IFF_BROADCAST) + irlan_set_broadcast_filter(self, TRUE); + else + irlan_set_broadcast_filter(self, FALSE); +} + +/* + * Function irlan_get_stats (dev) + * + * Get the current statistics for this device + * + */ +static struct net_device_stats *irlan_eth_get_stats(struct net_device *dev) +{ + struct irlan_cb *self = netdev_priv(dev); + + return &self->stats; +} diff --git a/net/irda/irlan/irlan_event.c b/net/irda/irlan/irlan_event.c new file mode 100644 index 000000000000..2778d8c6aa31 --- /dev/null +++ b/net/irda/irlan/irlan_event.c @@ -0,0 +1,60 @@ +/********************************************************************* + * + * Filename: irlan_event.c + * Version: + * Description: + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Tue Oct 20 09:10:16 1998 + * Modified at: Sat Oct 30 12:59:01 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1998-1999 Dag Brattli, 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <net/irda/irlan_event.h> + +char *irlan_state[] = { + "IRLAN_IDLE", + "IRLAN_QUERY", + "IRLAN_CONN", + "IRLAN_INFO", + "IRLAN_MEDIA", + "IRLAN_OPEN", + "IRLAN_WAIT", + "IRLAN_ARB", + "IRLAN_DATA", + "IRLAN_CLOSE", + "IRLAN_SYNC", +}; + +void irlan_next_client_state(struct irlan_cb *self, IRLAN_STATE state) +{ + IRDA_DEBUG(2, "%s(), %s\n", __FUNCTION__ , irlan_state[state]); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + self->client.state = state; +} + +void irlan_next_provider_state(struct irlan_cb *self, IRLAN_STATE state) +{ + IRDA_DEBUG(2, "%s(), %s\n", __FUNCTION__ , irlan_state[state]); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + self->provider.state = state; +} + diff --git a/net/irda/irlan/irlan_filter.c b/net/irda/irlan/irlan_filter.c new file mode 100644 index 000000000000..343c5d4a1a1d --- /dev/null +++ b/net/irda/irlan/irlan_filter.c @@ -0,0 +1,246 @@ +/********************************************************************* + * + * Filename: irlan_filter.c + * Version: + * Description: + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Fri Jan 29 11:16:38 1999 + * Modified at: Sat Oct 30 12:58:45 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1998-1999 Dag Brattli, 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <linux/skbuff.h> +#include <linux/random.h> +#include <linux/seq_file.h> + +#include <net/irda/irlan_common.h> + +/* + * Function irlan_filter_request (self, skb) + * + * Handle filter request from client peer device + * + */ +void irlan_filter_request(struct irlan_cb *self, struct sk_buff *skb) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + if ((self->provider.filter_type == IRLAN_DIRECTED) && + (self->provider.filter_operation == DYNAMIC)) + { + IRDA_DEBUG(0, "Giving peer a dynamic Ethernet address\n"); + self->provider.mac_address[0] = 0x40; + self->provider.mac_address[1] = 0x00; + self->provider.mac_address[2] = 0x00; + self->provider.mac_address[3] = 0x00; + + /* Use arbitration value to generate MAC address */ + if (self->provider.access_type == ACCESS_PEER) { + self->provider.mac_address[4] = + self->provider.send_arb_val & 0xff; + self->provider.mac_address[5] = + (self->provider.send_arb_val >> 8) & 0xff; + } else { + /* Just generate something for now */ + get_random_bytes(self->provider.mac_address+4, 1); + get_random_bytes(self->provider.mac_address+5, 1); + } + + skb->data[0] = 0x00; /* Success */ + skb->data[1] = 0x03; + irlan_insert_string_param(skb, "FILTER_MODE", "NONE"); + irlan_insert_short_param(skb, "MAX_ENTRY", 0x0001); + irlan_insert_array_param(skb, "FILTER_ENTRY", + self->provider.mac_address, 6); + return; + } + + if ((self->provider.filter_type == IRLAN_DIRECTED) && + (self->provider.filter_mode == FILTER)) + { + IRDA_DEBUG(0, "Directed filter on\n"); + skb->data[0] = 0x00; /* Success */ + skb->data[1] = 0x00; + return; + } + if ((self->provider.filter_type == IRLAN_DIRECTED) && + (self->provider.filter_mode == NONE)) + { + IRDA_DEBUG(0, "Directed filter off\n"); + skb->data[0] = 0x00; /* Success */ + skb->data[1] = 0x00; + return; + } + + if ((self->provider.filter_type == IRLAN_BROADCAST) && + (self->provider.filter_mode == FILTER)) + { + IRDA_DEBUG(0, "Broadcast filter on\n"); + skb->data[0] = 0x00; /* Success */ + skb->data[1] = 0x00; + return; + } + if ((self->provider.filter_type == IRLAN_BROADCAST) && + (self->provider.filter_mode == NONE)) + { + IRDA_DEBUG(0, "Broadcast filter off\n"); + skb->data[0] = 0x00; /* Success */ + skb->data[1] = 0x00; + return; + } + if ((self->provider.filter_type == IRLAN_MULTICAST) && + (self->provider.filter_mode == FILTER)) + { + IRDA_DEBUG(0, "Multicast filter on\n"); + skb->data[0] = 0x00; /* Success */ + skb->data[1] = 0x00; + return; + } + if ((self->provider.filter_type == IRLAN_MULTICAST) && + (self->provider.filter_mode == NONE)) + { + IRDA_DEBUG(0, "Multicast filter off\n"); + skb->data[0] = 0x00; /* Success */ + skb->data[1] = 0x00; + return; + } + if ((self->provider.filter_type == IRLAN_MULTICAST) && + (self->provider.filter_operation == GET)) + { + IRDA_DEBUG(0, "Multicast filter get\n"); + skb->data[0] = 0x00; /* Success? */ + skb->data[1] = 0x02; + irlan_insert_string_param(skb, "FILTER_MODE", "NONE"); + irlan_insert_short_param(skb, "MAX_ENTRY", 16); + return; + } + skb->data[0] = 0x00; /* Command not supported */ + skb->data[1] = 0x00; + + IRDA_DEBUG(0, "Not implemented!\n"); +} + +/* + * Function check_request_param (self, param, value) + * + * Check parameters in request from peer device + * + */ +void irlan_check_command_param(struct irlan_cb *self, char *param, char *value) +{ + __u8 *bytes; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + bytes = value; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + IRDA_DEBUG(4, "%s, %s\n", param, value); + + /* + * This is experimental!! DB. + */ + if (strcmp(param, "MODE") == 0) { + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + self->use_udata = TRUE; + return; + } + + /* + * FILTER_TYPE + */ + if (strcmp(param, "FILTER_TYPE") == 0) { + if (strcmp(value, "DIRECTED") == 0) { + self->provider.filter_type = IRLAN_DIRECTED; + return; + } + if (strcmp(value, "MULTICAST") == 0) { + self->provider.filter_type = IRLAN_MULTICAST; + return; + } + if (strcmp(value, "BROADCAST") == 0) { + self->provider.filter_type = IRLAN_BROADCAST; + return; + } + } + /* + * FILTER_MODE + */ + if (strcmp(param, "FILTER_MODE") == 0) { + if (strcmp(value, "ALL") == 0) { + self->provider.filter_mode = ALL; + return; + } + if (strcmp(value, "FILTER") == 0) { + self->provider.filter_mode = FILTER; + return; + } + if (strcmp(value, "NONE") == 0) { + self->provider.filter_mode = FILTER; + return; + } + } + /* + * FILTER_OPERATION + */ + if (strcmp(param, "FILTER_OPERATION") == 0) { + if (strcmp(value, "DYNAMIC") == 0) { + self->provider.filter_operation = DYNAMIC; + return; + } + if (strcmp(value, "GET") == 0) { + self->provider.filter_operation = GET; + return; + } + } +} + +/* + * Function irlan_print_filter (filter_type, buf) + * + * Print status of filter. Used by /proc file system + * + */ +#ifdef CONFIG_PROC_FS +#define MASK2STR(m,s) { .mask = m, .str = s } + +void irlan_print_filter(struct seq_file *seq, int filter_type) +{ + static struct { + int mask; + const char *str; + } filter_mask2str[] = { + MASK2STR(IRLAN_DIRECTED, "DIRECTED"), + MASK2STR(IRLAN_FUNCTIONAL, "FUNCTIONAL"), + MASK2STR(IRLAN_GROUP, "GROUP"), + MASK2STR(IRLAN_MAC_FRAME, "MAC_FRAME"), + MASK2STR(IRLAN_MULTICAST, "MULTICAST"), + MASK2STR(IRLAN_BROADCAST, "BROADCAST"), + MASK2STR(IRLAN_IPX_SOCKET, "IPX_SOCKET"), + MASK2STR(0, NULL) + }, *p; + + for (p = filter_mask2str; p->str; p++) { + if (filter_type & p->mask) + seq_printf(seq, "%s ", p->str); + } + seq_putc(seq, '\n'); +} +#undef MASK2STR +#endif diff --git a/net/irda/irlan/irlan_provider.c b/net/irda/irlan/irlan_provider.c new file mode 100644 index 000000000000..39c202d1c374 --- /dev/null +++ b/net/irda/irlan/irlan_provider.c @@ -0,0 +1,413 @@ +/********************************************************************* + * + * Filename: irlan_provider.c + * Version: 0.9 + * Description: IrDA LAN Access Protocol Implementation + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sun Aug 31 20:14:37 1997 + * Modified at: Sat Oct 30 12:52:10 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * Sources: skeleton.c by Donald Becker <becker@CESDIS.gsfc.nasa.gov> + * slip.c by Laurence Culhane, <loz@holmes.demon.co.uk> + * Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org> + * + * Copyright (c) 1998-1999 Dag Brattli <dagb@cs.uit.no>, + * 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/init.h> +#include <linux/random.h> +#include <linux/bitops.h> + +#include <asm/system.h> +#include <asm/byteorder.h> + +#include <net/irda/irda.h> +#include <net/irda/irttp.h> +#include <net/irda/irlmp.h> +#include <net/irda/irias_object.h> +#include <net/irda/iriap.h> +#include <net/irda/timer.h> + +#include <net/irda/irlan_common.h> +#include <net/irda/irlan_eth.h> +#include <net/irda/irlan_event.h> +#include <net/irda/irlan_provider.h> +#include <net/irda/irlan_filter.h> +#include <net/irda/irlan_client.h> + +static void irlan_provider_connect_indication(void *instance, void *sap, + struct qos_info *qos, + __u32 max_sdu_size, + __u8 max_header_size, + struct sk_buff *skb); + +/* + * Function irlan_provider_control_data_indication (handle, skb) + * + * This function gets the data that is received on the control channel + * + */ +static int irlan_provider_data_indication(void *instance, void *sap, + struct sk_buff *skb) +{ + struct irlan_cb *self; + __u8 code; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + self = (struct irlan_cb *) instance; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return -1;); + + IRDA_ASSERT(skb != NULL, return -1;); + + code = skb->data[0]; + switch(code) { + case CMD_GET_PROVIDER_INFO: + IRDA_DEBUG(4, "Got GET_PROVIDER_INFO command!\n"); + irlan_do_provider_event(self, IRLAN_GET_INFO_CMD, skb); + break; + + case CMD_GET_MEDIA_CHAR: + IRDA_DEBUG(4, "Got GET_MEDIA_CHAR command!\n"); + irlan_do_provider_event(self, IRLAN_GET_MEDIA_CMD, skb); + break; + case CMD_OPEN_DATA_CHANNEL: + IRDA_DEBUG(4, "Got OPEN_DATA_CHANNEL command!\n"); + irlan_do_provider_event(self, IRLAN_OPEN_DATA_CMD, skb); + break; + case CMD_FILTER_OPERATION: + IRDA_DEBUG(4, "Got FILTER_OPERATION command!\n"); + irlan_do_provider_event(self, IRLAN_FILTER_CONFIG_CMD, skb); + break; + case CMD_RECONNECT_DATA_CHAN: + IRDA_DEBUG(2, "%s(), Got RECONNECT_DATA_CHAN command\n", __FUNCTION__ ); + IRDA_DEBUG(2, "%s(), NOT IMPLEMENTED\n", __FUNCTION__ ); + break; + case CMD_CLOSE_DATA_CHAN: + IRDA_DEBUG(2, "Got CLOSE_DATA_CHAN command!\n"); + IRDA_DEBUG(2, "%s(), NOT IMPLEMENTED\n", __FUNCTION__ ); + break; + default: + IRDA_DEBUG(2, "%s(), Unknown command!\n", __FUNCTION__ ); + break; + } + return 0; +} + +/* + * Function irlan_provider_connect_indication (handle, skb, priv) + * + * Got connection from peer IrLAN client + * + */ +static void irlan_provider_connect_indication(void *instance, void *sap, + struct qos_info *qos, + __u32 max_sdu_size, + __u8 max_header_size, + struct sk_buff *skb) +{ + struct irlan_cb *self; + struct tsap_cb *tsap; + __u32 saddr, daddr; + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__ ); + + self = (struct irlan_cb *) instance; + tsap = (struct tsap_cb *) sap; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + IRDA_ASSERT(tsap == self->provider.tsap_ctrl,return;); + IRDA_ASSERT(self->provider.state == IRLAN_IDLE, return;); + + daddr = irttp_get_daddr(tsap); + saddr = irttp_get_saddr(tsap); + self->provider.max_sdu_size = max_sdu_size; + self->provider.max_header_size = max_header_size; + + irlan_do_provider_event(self, IRLAN_CONNECT_INDICATION, NULL); + + /* + * If we are in peer mode, the client may not have got the discovery + * indication it needs to make progress. If the client is still in + * IDLE state, we must kick it. + */ + if ((self->provider.access_type == ACCESS_PEER) && + (self->client.state == IRLAN_IDLE)) + { + irlan_client_wakeup(self, self->saddr, self->daddr); + } +} + +/* + * Function irlan_provider_connect_response (handle) + * + * Accept incoming connection + * + */ +void irlan_provider_connect_response(struct irlan_cb *self, + struct tsap_cb *tsap) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + /* Just accept */ + irttp_connect_response(tsap, IRLAN_MTU, NULL); +} + +static void irlan_provider_disconnect_indication(void *instance, void *sap, + LM_REASON reason, + struct sk_buff *userdata) +{ + struct irlan_cb *self; + struct tsap_cb *tsap; + + IRDA_DEBUG(4, "%s(), reason=%d\n", __FUNCTION__ , reason); + + self = (struct irlan_cb *) instance; + tsap = (struct tsap_cb *) sap; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + IRDA_ASSERT(tsap != NULL, return;); + IRDA_ASSERT(tsap->magic == TTP_TSAP_MAGIC, return;); + + IRDA_ASSERT(tsap == self->provider.tsap_ctrl, return;); + + irlan_do_provider_event(self, IRLAN_LMP_DISCONNECT, NULL); +} + +/* + * Function irlan_parse_open_data_cmd (self, skb) + * + * + * + */ +int irlan_parse_open_data_cmd(struct irlan_cb *self, struct sk_buff *skb) +{ + int ret; + + ret = irlan_provider_parse_command(self, CMD_OPEN_DATA_CHANNEL, skb); + + /* Open data channel */ + irlan_open_data_tsap(self); + + return ret; +} + +/* + * Function parse_command (skb) + * + * Extract all parameters from received buffer, then feed them to + * check_params for parsing + * + */ +int irlan_provider_parse_command(struct irlan_cb *self, int cmd, + struct sk_buff *skb) +{ + __u8 *frame; + __u8 *ptr; + int count; + __u16 val_len; + int i; + char *name; + char *value; + int ret = RSP_SUCCESS; + + IRDA_ASSERT(skb != NULL, return -RSP_PROTOCOL_ERROR;); + + IRDA_DEBUG(4, "%s(), skb->len=%d\n", __FUNCTION__ , (int)skb->len); + + IRDA_ASSERT(self != NULL, return -RSP_PROTOCOL_ERROR;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return -RSP_PROTOCOL_ERROR;); + + if (!skb) + return -RSP_PROTOCOL_ERROR; + + frame = skb->data; + + name = kmalloc(255, GFP_ATOMIC); + if (!name) + return -RSP_INSUFFICIENT_RESOURCES; + value = kmalloc(1016, GFP_ATOMIC); + if (!value) { + kfree(name); + return -RSP_INSUFFICIENT_RESOURCES; + } + + /* How many parameters? */ + count = frame[1]; + + IRDA_DEBUG(4, "Got %d parameters\n", count); + + ptr = frame+2; + + /* For all parameters */ + for (i=0; i<count;i++) { + ret = irlan_extract_param(ptr, name, value, &val_len); + if (ret < 0) { + IRDA_DEBUG(2, "%s(), IrLAN, Error!\n", __FUNCTION__ ); + break; + } + ptr+=ret; + ret = RSP_SUCCESS; + irlan_check_command_param(self, name, value); + } + /* Cleanup */ + kfree(name); + kfree(value); + + return ret; +} + +/* + * Function irlan_provider_send_reply (self, info) + * + * Send reply to query to peer IrLAN layer + * + */ +void irlan_provider_send_reply(struct irlan_cb *self, int command, + int ret_code) +{ + struct sk_buff *skb; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;); + + skb = dev_alloc_skb(128); + if (!skb) + return; + + /* Reserve space for TTP, LMP, and LAP header */ + skb_reserve(skb, self->provider.max_header_size); + skb_put(skb, 2); + + switch (command) { + case CMD_GET_PROVIDER_INFO: + skb->data[0] = 0x00; /* Success */ + skb->data[1] = 0x02; /* 2 parameters */ + switch (self->media) { + case MEDIA_802_3: + irlan_insert_string_param(skb, "MEDIA", "802.3"); + break; + case MEDIA_802_5: + irlan_insert_string_param(skb, "MEDIA", "802.5"); + break; + default: + IRDA_DEBUG(2, "%s(), unknown media type!\n", __FUNCTION__ ); + break; + } + irlan_insert_short_param(skb, "IRLAN_VER", 0x0101); + break; + + case CMD_GET_MEDIA_CHAR: + skb->data[0] = 0x00; /* Success */ + skb->data[1] = 0x05; /* 5 parameters */ + irlan_insert_string_param(skb, "FILTER_TYPE", "DIRECTED"); + irlan_insert_string_param(skb, "FILTER_TYPE", "BROADCAST"); + irlan_insert_string_param(skb, "FILTER_TYPE", "MULTICAST"); + + switch (self->provider.access_type) { + case ACCESS_DIRECT: + irlan_insert_string_param(skb, "ACCESS_TYPE", "DIRECT"); + break; + case ACCESS_PEER: + irlan_insert_string_param(skb, "ACCESS_TYPE", "PEER"); + break; + case ACCESS_HOSTED: + irlan_insert_string_param(skb, "ACCESS_TYPE", "HOSTED"); + break; + default: + IRDA_DEBUG(2, "%s(), Unknown access type\n", __FUNCTION__ ); + break; + } + irlan_insert_short_param(skb, "MAX_FRAME", 0x05ee); + break; + case CMD_OPEN_DATA_CHANNEL: + skb->data[0] = 0x00; /* Success */ + if (self->provider.send_arb_val) { + skb->data[1] = 0x03; /* 3 parameters */ + irlan_insert_short_param(skb, "CON_ARB", + self->provider.send_arb_val); + } else + skb->data[1] = 0x02; /* 2 parameters */ + irlan_insert_byte_param(skb, "DATA_CHAN", self->stsap_sel_data); + irlan_insert_array_param(skb, "RECONNECT_KEY", "LINUX RULES!", + 12); + break; + case CMD_FILTER_OPERATION: + irlan_filter_request(self, skb); + break; + default: + IRDA_DEBUG(2, "%s(), Unknown command!\n", __FUNCTION__ ); + break; + } + + irttp_data_request(self->provider.tsap_ctrl, skb); +} + +/* + * Function irlan_provider_register(void) + * + * Register provider support so we can accept incoming connections. + * + */ +int irlan_provider_open_ctrl_tsap(struct irlan_cb *self) +{ + struct tsap_cb *tsap; + notify_t notify; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return -1;); + + /* Check if already open */ + if (self->provider.tsap_ctrl) + return -1; + + /* + * First register well known control TSAP + */ + irda_notify_init(¬ify); + notify.data_indication = irlan_provider_data_indication; + notify.connect_indication = irlan_provider_connect_indication; + notify.disconnect_indication = irlan_provider_disconnect_indication; + notify.instance = self; + strlcpy(notify.name, "IrLAN ctrl (p)", sizeof(notify.name)); + + tsap = irttp_open_tsap(LSAP_ANY, 1, ¬ify); + if (!tsap) { + IRDA_DEBUG(2, "%s(), Got no tsap!\n", __FUNCTION__ ); + return -1; + } + self->provider.tsap_ctrl = tsap; + + /* Register with LM-IAS */ + irlan_ias_register(self, tsap->stsap_sel); + + return 0; +} + diff --git a/net/irda/irlan/irlan_provider_event.c b/net/irda/irlan/irlan_provider_event.c new file mode 100644 index 000000000000..5a086f9827ed --- /dev/null +++ b/net/irda/irlan/irlan_provider_event.c @@ -0,0 +1,241 @@ +/********************************************************************* + * + * Filename: irlan_provider_event.c + * Version: 0.9 + * Description: IrLAN provider state machine) + * Status: Experimental. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sun Aug 31 20:14:37 1997 + * Modified at: Sat Oct 30 12:52:41 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1998-1999 Dag Brattli <dagb@cs.uit.no>, 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <net/irda/irda.h> +#include <net/irda/iriap.h> +#include <net/irda/irlmp.h> +#include <net/irda/irttp.h> + +#include <net/irda/irlan_provider.h> +#include <net/irda/irlan_event.h> + +static int irlan_provider_state_idle(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb); +static int irlan_provider_state_info(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb); +static int irlan_provider_state_open(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb); +static int irlan_provider_state_data(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb); + +static int (*state[])(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) = +{ + irlan_provider_state_idle, + NULL, /* Query */ + NULL, /* Info */ + irlan_provider_state_info, + NULL, /* Media */ + irlan_provider_state_open, + NULL, /* Wait */ + NULL, /* Arb */ + irlan_provider_state_data, + NULL, /* Close */ + NULL, /* Sync */ +}; + +void irlan_do_provider_event(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + IRDA_ASSERT(*state[ self->provider.state] != NULL, return;); + + (*state[self->provider.state]) (self, event, skb); +} + +/* + * Function irlan_provider_state_idle (event, skb, info) + * + * IDLE, We are waiting for an indication that there is a provider + * available. + */ +static int irlan_provider_state_idle(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + + switch(event) { + case IRLAN_CONNECT_INDICATION: + irlan_provider_connect_response( self, self->provider.tsap_ctrl); + irlan_next_provider_state( self, IRLAN_INFO); + break; + default: + IRDA_DEBUG(4, "%s(), Unknown event %d\n", __FUNCTION__ , event); + break; + } + if (skb) + dev_kfree_skb(skb); + + return 0; +} + +/* + * Function irlan_provider_state_info (self, event, skb, info) + * + * INFO, We have issued a GetInfo command and is awaiting a reply. + */ +static int irlan_provider_state_info(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + int ret; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + + switch(event) { + case IRLAN_GET_INFO_CMD: + /* Be sure to use 802.3 in case of peer mode */ + if (self->provider.access_type == ACCESS_PEER) { + self->media = MEDIA_802_3; + + /* Check if client has started yet */ + if (self->client.state == IRLAN_IDLE) { + /* This should get the client going */ + irlmp_discovery_request(8); + } + } + + irlan_provider_send_reply(self, CMD_GET_PROVIDER_INFO, + RSP_SUCCESS); + /* Keep state */ + break; + case IRLAN_GET_MEDIA_CMD: + irlan_provider_send_reply(self, CMD_GET_MEDIA_CHAR, + RSP_SUCCESS); + /* Keep state */ + break; + case IRLAN_OPEN_DATA_CMD: + ret = irlan_parse_open_data_cmd(self, skb); + if (self->provider.access_type == ACCESS_PEER) { + /* FIXME: make use of random functions! */ + self->provider.send_arb_val = (jiffies & 0xffff); + } + irlan_provider_send_reply(self, CMD_OPEN_DATA_CHANNEL, ret); + + if (ret == RSP_SUCCESS) { + irlan_next_provider_state(self, IRLAN_OPEN); + + /* Signal client that we are now open */ + irlan_do_client_event(self, IRLAN_PROVIDER_SIGNAL, NULL); + } + break; + case IRLAN_LMP_DISCONNECT: /* FALLTHROUGH */ + case IRLAN_LAP_DISCONNECT: + irlan_next_provider_state(self, IRLAN_IDLE); + break; + default: + IRDA_DEBUG( 0, "%s(), Unknown event %d\n", __FUNCTION__ , event); + break; + } + if (skb) + dev_kfree_skb(skb); + + return 0; +} + +/* + * Function irlan_provider_state_open (self, event, skb, info) + * + * OPEN, The client has issued a OpenData command and is awaiting a + * reply + * + */ +static int irlan_provider_state_open(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + + switch(event) { + case IRLAN_FILTER_CONFIG_CMD: + irlan_provider_parse_command(self, CMD_FILTER_OPERATION, skb); + irlan_provider_send_reply(self, CMD_FILTER_OPERATION, + RSP_SUCCESS); + /* Keep state */ + break; + case IRLAN_DATA_CONNECT_INDICATION: + irlan_next_provider_state(self, IRLAN_DATA); + irlan_provider_connect_response(self, self->tsap_data); + break; + case IRLAN_LMP_DISCONNECT: /* FALLTHROUGH */ + case IRLAN_LAP_DISCONNECT: + irlan_next_provider_state(self, IRLAN_IDLE); + break; + default: + IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__ , event); + break; + } + if (skb) + dev_kfree_skb(skb); + + return 0; +} + +/* + * Function irlan_provider_state_data (self, event, skb, info) + * + * DATA, The data channel is connected, allowing data transfers between + * the local and remote machines. + * + */ +static int irlan_provider_state_data(struct irlan_cb *self, IRLAN_EVENT event, + struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__ ); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == IRLAN_MAGIC, return -1;); + + switch(event) { + case IRLAN_FILTER_CONFIG_CMD: + irlan_provider_parse_command(self, CMD_FILTER_OPERATION, skb); + irlan_provider_send_reply(self, CMD_FILTER_OPERATION, + RSP_SUCCESS); + break; + case IRLAN_LMP_DISCONNECT: /* FALLTHROUGH */ + case IRLAN_LAP_DISCONNECT: + irlan_next_provider_state(self, IRLAN_IDLE); + break; + default: + IRDA_DEBUG( 0, "%s(), Unknown event %d\n", __FUNCTION__ , event); + break; + } + if (skb) + dev_kfree_skb(skb); + + return 0; +} + + + + + + + + + + diff --git a/net/irda/irlap.c b/net/irda/irlap.c new file mode 100644 index 000000000000..046ad0750e48 --- /dev/null +++ b/net/irda/irlap.c @@ -0,0 +1,1258 @@ +/********************************************************************* + * + * Filename: irlap.c + * Version: 1.0 + * Description: IrLAP implementation for Linux + * Status: Stable + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Mon Aug 4 20:40:53 1997 + * Modified at: Tue Dec 14 09:26:44 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1998-1999 Dag Brattli, All Rights Reserved. + * Copyright (c) 2000-2003 Jean Tourrilhes <jt@hpl.hp.com> + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + ********************************************************************/ + +#include <linux/config.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/skbuff.h> +#include <linux/delay.h> +#include <linux/proc_fs.h> +#include <linux/init.h> +#include <linux/random.h> +#include <linux/module.h> +#include <linux/seq_file.h> + +#include <net/irda/irda.h> +#include <net/irda/irda_device.h> +#include <net/irda/irqueue.h> +#include <net/irda/irlmp.h> +#include <net/irda/irlmp_frame.h> +#include <net/irda/irlap_frame.h> +#include <net/irda/irlap.h> +#include <net/irda/timer.h> +#include <net/irda/qos.h> + +static hashbin_t *irlap = NULL; +int sysctl_slot_timeout = SLOT_TIMEOUT * 1000 / HZ; + +/* This is the delay of missed pf period before generating an event + * to the application. The spec mandate 3 seconds, but in some cases + * it's way too long. - Jean II */ +int sysctl_warn_noreply_time = 3; + +extern void irlap_queue_xmit(struct irlap_cb *self, struct sk_buff *skb); +static void __irlap_close(struct irlap_cb *self); +static void irlap_init_qos_capabilities(struct irlap_cb *self, + struct qos_info *qos_user); + +#ifdef CONFIG_IRDA_DEBUG +static char *lap_reasons[] = { + "ERROR, NOT USED", + "LAP_DISC_INDICATION", + "LAP_NO_RESPONSE", + "LAP_RESET_INDICATION", + "LAP_FOUND_NONE", + "LAP_MEDIA_BUSY", + "LAP_PRIMARY_CONFLICT", + "ERROR, NOT USED", +}; +#endif /* CONFIG_IRDA_DEBUG */ + +int __init irlap_init(void) +{ + /* Check if the compiler did its job properly. + * May happen on some ARM configuration, check with Russell King. */ + IRDA_ASSERT(sizeof(struct xid_frame) == 14, ;); + IRDA_ASSERT(sizeof(struct test_frame) == 10, ;); + IRDA_ASSERT(sizeof(struct ua_frame) == 10, ;); + IRDA_ASSERT(sizeof(struct snrm_frame) == 11, ;); + + /* Allocate master array */ + irlap = hashbin_new(HB_LOCK); + if (irlap == NULL) { + IRDA_ERROR("%s: can't allocate irlap hashbin!\n", + __FUNCTION__); + return -ENOMEM; + } + + return 0; +} + +void __exit irlap_cleanup(void) +{ + IRDA_ASSERT(irlap != NULL, return;); + + hashbin_delete(irlap, (FREE_FUNC) __irlap_close); +} + +/* + * Function irlap_open (driver) + * + * Initialize IrLAP layer + * + */ +struct irlap_cb *irlap_open(struct net_device *dev, struct qos_info *qos, + const char *hw_name) +{ + struct irlap_cb *self; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + /* Initialize the irlap structure. */ + self = kmalloc(sizeof(struct irlap_cb), GFP_KERNEL); + if (self == NULL) + return NULL; + + memset(self, 0, sizeof(struct irlap_cb)); + self->magic = LAP_MAGIC; + + /* Make a binding between the layers */ + self->netdev = dev; + self->qos_dev = qos; + /* Copy hardware name */ + if(hw_name != NULL) { + strlcpy(self->hw_name, hw_name, sizeof(self->hw_name)); + } else { + self->hw_name[0] = '\0'; + } + + /* FIXME: should we get our own field? */ + dev->atalk_ptr = self; + + self->state = LAP_OFFLINE; + + /* Initialize transmit queue */ + skb_queue_head_init(&self->txq); + skb_queue_head_init(&self->txq_ultra); + skb_queue_head_init(&self->wx_list); + + /* My unique IrLAP device address! */ + /* We don't want the broadcast address, neither the NULL address + * (most often used to signify "invalid"), and we don't want an + * address already in use (otherwise connect won't be able + * to select the proper link). - Jean II */ + do { + get_random_bytes(&self->saddr, sizeof(self->saddr)); + } while ((self->saddr == 0x0) || (self->saddr == BROADCAST) || + (hashbin_lock_find(irlap, self->saddr, NULL)) ); + /* Copy to the driver */ + memcpy(dev->dev_addr, &self->saddr, 4); + + init_timer(&self->slot_timer); + init_timer(&self->query_timer); + init_timer(&self->discovery_timer); + init_timer(&self->final_timer); + init_timer(&self->poll_timer); + init_timer(&self->wd_timer); + init_timer(&self->backoff_timer); + init_timer(&self->media_busy_timer); + + irlap_apply_default_connection_parameters(self); + + self->N3 = 3; /* # connections attemts to try before giving up */ + + self->state = LAP_NDM; + + hashbin_insert(irlap, (irda_queue_t *) self, self->saddr, NULL); + + irlmp_register_link(self, self->saddr, &self->notify); + + return self; +} +EXPORT_SYMBOL(irlap_open); + +/* + * Function __irlap_close (self) + * + * Remove IrLAP and all allocated memory. Stop any pending timers. + * + */ +static void __irlap_close(struct irlap_cb *self) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + /* Stop timers */ + del_timer(&self->slot_timer); + del_timer(&self->query_timer); + del_timer(&self->discovery_timer); + del_timer(&self->final_timer); + del_timer(&self->poll_timer); + del_timer(&self->wd_timer); + del_timer(&self->backoff_timer); + del_timer(&self->media_busy_timer); + + irlap_flush_all_queues(self); + + self->magic = 0; + + kfree(self); +} + +/* + * Function irlap_close (self) + * + * Remove IrLAP instance + * + */ +void irlap_close(struct irlap_cb *self) +{ + struct irlap_cb *lap; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + /* We used to send a LAP_DISC_INDICATION here, but this was + * racy. This has been move within irlmp_unregister_link() + * itself. Jean II */ + + /* Kill the LAP and all LSAPs on top of it */ + irlmp_unregister_link(self->saddr); + self->notify.instance = NULL; + + /* Be sure that we manage to remove ourself from the hash */ + lap = hashbin_remove(irlap, self->saddr, NULL); + if (!lap) { + IRDA_DEBUG(1, "%s(), Didn't find myself!\n", __FUNCTION__); + return; + } + __irlap_close(lap); +} +EXPORT_SYMBOL(irlap_close); + +/* + * Function irlap_connect_indication (self, skb) + * + * Another device is attempting to make a connection + * + */ +void irlap_connect_indication(struct irlap_cb *self, struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + irlap_init_qos_capabilities(self, NULL); /* No user QoS! */ + + irlmp_link_connect_indication(self->notify.instance, self->saddr, + self->daddr, &self->qos_tx, skb); +} + +/* + * Function irlap_connect_response (self, skb) + * + * Service user has accepted incoming connection + * + */ +void irlap_connect_response(struct irlap_cb *self, struct sk_buff *userdata) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + irlap_do_event(self, CONNECT_RESPONSE, userdata, NULL); +} + +/* + * Function irlap_connect_request (self, daddr, qos_user, sniff) + * + * Request connection with another device, sniffing is not implemented + * yet. + * + */ +void irlap_connect_request(struct irlap_cb *self, __u32 daddr, + struct qos_info *qos_user, int sniff) +{ + IRDA_DEBUG(3, "%s(), daddr=0x%08x\n", __FUNCTION__, daddr); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + self->daddr = daddr; + + /* + * If the service user specifies QoS values for this connection, + * then use them + */ + irlap_init_qos_capabilities(self, qos_user); + + if ((self->state == LAP_NDM) && !self->media_busy) + irlap_do_event(self, CONNECT_REQUEST, NULL, NULL); + else + self->connect_pending = TRUE; +} + +/* + * Function irlap_connect_confirm (self, skb) + * + * Connection request has been accepted + * + */ +void irlap_connect_confirm(struct irlap_cb *self, struct sk_buff *skb) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + irlmp_link_connect_confirm(self->notify.instance, &self->qos_tx, skb); +} + +/* + * Function irlap_data_indication (self, skb) + * + * Received data frames from IR-port, so we just pass them up to + * IrLMP for further processing + * + */ +void irlap_data_indication(struct irlap_cb *self, struct sk_buff *skb, + int unreliable) +{ + /* Hide LAP header from IrLMP layer */ + skb_pull(skb, LAP_ADDR_HEADER+LAP_CTRL_HEADER); + + irlmp_link_data_indication(self->notify.instance, skb, unreliable); +} + + +/* + * Function irlap_data_request (self, skb) + * + * Queue data for transmission, must wait until XMIT state + * + */ +void irlap_data_request(struct irlap_cb *self, struct sk_buff *skb, + int unreliable) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + IRDA_DEBUG(3, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(skb_headroom(skb) >= (LAP_ADDR_HEADER+LAP_CTRL_HEADER), + return;); + skb_push(skb, LAP_ADDR_HEADER+LAP_CTRL_HEADER); + + /* + * Must set frame format now so that the rest of the code knows + * if its dealing with an I or an UI frame + */ + if (unreliable) + skb->data[1] = UI_FRAME; + else + skb->data[1] = I_FRAME; + + /* Don't forget to refcount it - see irlmp_connect_request(). */ + skb_get(skb); + + /* Add at the end of the queue (keep ordering) - Jean II */ + skb_queue_tail(&self->txq, skb); + + /* + * Send event if this frame only if we are in the right state + * FIXME: udata should be sent first! (skb_queue_head?) + */ + if ((self->state == LAP_XMIT_P) || (self->state == LAP_XMIT_S)) { + /* If we are not already processing the Tx queue, trigger + * transmission immediately - Jean II */ + if((skb_queue_len(&self->txq) <= 1) && (!self->local_busy)) + irlap_do_event(self, DATA_REQUEST, skb, NULL); + /* Otherwise, the packets will be sent normally at the + * next pf-poll - Jean II */ + } +} + +/* + * Function irlap_unitdata_request (self, skb) + * + * Send Ultra data. This is data that must be sent outside any connection + * + */ +#ifdef CONFIG_IRDA_ULTRA +void irlap_unitdata_request(struct irlap_cb *self, struct sk_buff *skb) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + IRDA_DEBUG(3, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(skb_headroom(skb) >= (LAP_ADDR_HEADER+LAP_CTRL_HEADER), + return;); + skb_push(skb, LAP_ADDR_HEADER+LAP_CTRL_HEADER); + + skb->data[0] = CBROADCAST; + skb->data[1] = UI_FRAME; + + /* Don't need to refcount, see irlmp_connless_data_request() */ + + skb_queue_tail(&self->txq_ultra, skb); + + irlap_do_event(self, SEND_UI_FRAME, NULL, NULL); +} +#endif /*CONFIG_IRDA_ULTRA */ + +/* + * Function irlap_udata_indication (self, skb) + * + * Receive Ultra data. This is data that is received outside any connection + * + */ +#ifdef CONFIG_IRDA_ULTRA +void irlap_unitdata_indication(struct irlap_cb *self, struct sk_buff *skb) +{ + IRDA_DEBUG(1, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + IRDA_ASSERT(skb != NULL, return;); + + /* Hide LAP header from IrLMP layer */ + skb_pull(skb, LAP_ADDR_HEADER+LAP_CTRL_HEADER); + + irlmp_link_unitdata_indication(self->notify.instance, skb); +} +#endif /* CONFIG_IRDA_ULTRA */ + +/* + * Function irlap_disconnect_request (void) + * + * Request to disconnect connection by service user + */ +void irlap_disconnect_request(struct irlap_cb *self) +{ + IRDA_DEBUG(3, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + /* Don't disconnect until all data frames are successfully sent */ + if (skb_queue_len(&self->txq) > 0) { + self->disconnect_pending = TRUE; + + return; + } + + /* Check if we are in the right state for disconnecting */ + switch (self->state) { + case LAP_XMIT_P: /* FALLTROUGH */ + case LAP_XMIT_S: /* FALLTROUGH */ + case LAP_CONN: /* FALLTROUGH */ + case LAP_RESET_WAIT: /* FALLTROUGH */ + case LAP_RESET_CHECK: + irlap_do_event(self, DISCONNECT_REQUEST, NULL, NULL); + break; + default: + IRDA_DEBUG(2, "%s(), disconnect pending!\n", __FUNCTION__); + self->disconnect_pending = TRUE; + break; + } +} + +/* + * Function irlap_disconnect_indication (void) + * + * Disconnect request from other device + * + */ +void irlap_disconnect_indication(struct irlap_cb *self, LAP_REASON reason) +{ + IRDA_DEBUG(1, "%s(), reason=%s\n", __FUNCTION__, lap_reasons[reason]); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + /* Flush queues */ + irlap_flush_all_queues(self); + + switch (reason) { + case LAP_RESET_INDICATION: + IRDA_DEBUG(1, "%s(), Sending reset request!\n", __FUNCTION__); + irlap_do_event(self, RESET_REQUEST, NULL, NULL); + break; + case LAP_NO_RESPONSE: /* FALLTROUGH */ + case LAP_DISC_INDICATION: /* FALLTROUGH */ + case LAP_FOUND_NONE: /* FALLTROUGH */ + case LAP_MEDIA_BUSY: + irlmp_link_disconnect_indication(self->notify.instance, self, + reason, NULL); + break; + default: + IRDA_ERROR("%s: Unknown reason %d\n", __FUNCTION__, reason); + } +} + +/* + * Function irlap_discovery_request (gen_addr_bit) + * + * Start one single discovery operation. + * + */ +void irlap_discovery_request(struct irlap_cb *self, discovery_t *discovery) +{ + struct irlap_info info; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + IRDA_ASSERT(discovery != NULL, return;); + + IRDA_DEBUG(4, "%s(), nslots = %d\n", __FUNCTION__, discovery->nslots); + + IRDA_ASSERT((discovery->nslots == 1) || (discovery->nslots == 6) || + (discovery->nslots == 8) || (discovery->nslots == 16), + return;); + + /* Discovery is only possible in NDM mode */ + if (self->state != LAP_NDM) { + IRDA_DEBUG(4, "%s(), discovery only possible in NDM mode\n", + __FUNCTION__); + irlap_discovery_confirm(self, NULL); + /* Note : in theory, if we are not in NDM, we could postpone + * the discovery like we do for connection request. + * In practice, it's not worth it. If the media was busy, + * it's likely next time around it won't be busy. If we are + * in REPLY state, we will get passive discovery info & event. + * Jean II */ + return; + } + + /* Check if last discovery request finished in time, or if + * it was aborted due to the media busy flag. */ + if (self->discovery_log != NULL) { + hashbin_delete(self->discovery_log, (FREE_FUNC) kfree); + self->discovery_log = NULL; + } + + /* All operations will occur at predictable time, no need to lock */ + self->discovery_log = hashbin_new(HB_NOLOCK); + + if (self->discovery_log == NULL) { + IRDA_WARNING("%s(), Unable to allocate discovery log!\n", + __FUNCTION__); + return; + } + + info.S = discovery->nslots; /* Number of slots */ + info.s = 0; /* Current slot */ + + self->discovery_cmd = discovery; + info.discovery = discovery; + + /* sysctl_slot_timeout bounds are checked in irsysctl.c - Jean II */ + self->slot_timeout = sysctl_slot_timeout * HZ / 1000; + + irlap_do_event(self, DISCOVERY_REQUEST, NULL, &info); +} + +/* + * Function irlap_discovery_confirm (log) + * + * A device has been discovered in front of this station, we + * report directly to LMP. + */ +void irlap_discovery_confirm(struct irlap_cb *self, hashbin_t *discovery_log) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + IRDA_ASSERT(self->notify.instance != NULL, return;); + + /* + * Check for successful discovery, since we are then allowed to clear + * the media busy condition (IrLAP 6.13.4 - p.94). This should allow + * us to make connection attempts much faster and easier (i.e. no + * collisions). + * Setting media busy to false will also generate an event allowing + * to process pending events in NDM state machine. + * Note : the spec doesn't define what's a successful discovery is. + * If we want Ultra to work, it's successful even if there is + * nobody discovered - Jean II + */ + if (discovery_log) + irda_device_set_media_busy(self->netdev, FALSE); + + /* Inform IrLMP */ + irlmp_link_discovery_confirm(self->notify.instance, discovery_log); +} + +/* + * Function irlap_discovery_indication (log) + * + * Somebody is trying to discover us! + * + */ +void irlap_discovery_indication(struct irlap_cb *self, discovery_t *discovery) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + IRDA_ASSERT(discovery != NULL, return;); + + IRDA_ASSERT(self->notify.instance != NULL, return;); + + /* A device is very likely to connect immediately after it performs + * a successful discovery. This means that in our case, we are much + * more likely to receive a connection request over the medium. + * So, we backoff to avoid collisions. + * IrLAP spec 6.13.4 suggest 100ms... + * Note : this little trick actually make a *BIG* difference. If I set + * my Linux box with discovery enabled and one Ultra frame sent every + * second, my Palm has no trouble connecting to it every time ! + * Jean II */ + irda_device_set_media_busy(self->netdev, SMALL); + + irlmp_link_discovery_indication(self->notify.instance, discovery); +} + +/* + * Function irlap_status_indication (quality_of_link) + */ +void irlap_status_indication(struct irlap_cb *self, int quality_of_link) +{ + switch (quality_of_link) { + case STATUS_NO_ACTIVITY: + IRDA_MESSAGE("IrLAP, no activity on link!\n"); + break; + case STATUS_NOISY: + IRDA_MESSAGE("IrLAP, noisy link!\n"); + break; + default: + break; + } + irlmp_status_indication(self->notify.instance, + quality_of_link, LOCK_NO_CHANGE); +} + +/* + * Function irlap_reset_indication (void) + */ +void irlap_reset_indication(struct irlap_cb *self) +{ + IRDA_DEBUG(1, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + if (self->state == LAP_RESET_WAIT) + irlap_do_event(self, RESET_REQUEST, NULL, NULL); + else + irlap_do_event(self, RESET_RESPONSE, NULL, NULL); +} + +/* + * Function irlap_reset_confirm (void) + */ +void irlap_reset_confirm(void) +{ + IRDA_DEBUG(1, "%s()\n", __FUNCTION__); +} + +/* + * Function irlap_generate_rand_time_slot (S, s) + * + * Generate a random time slot between s and S-1 where + * S = Number of slots (0 -> S-1) + * s = Current slot + */ +int irlap_generate_rand_time_slot(int S, int s) +{ + static int rand; + int slot; + + IRDA_ASSERT((S - s) > 0, return 0;); + + rand += jiffies; + rand ^= (rand << 12); + rand ^= (rand >> 20); + + slot = s + rand % (S-s); + + IRDA_ASSERT((slot >= s) || (slot < S), return 0;); + + return slot; +} + +/* + * Function irlap_update_nr_received (nr) + * + * Remove all acknowledged frames in current window queue. This code is + * not intuitive and you should not try to change it. If you think it + * contains bugs, please mail a patch to the author instead. + */ +void irlap_update_nr_received(struct irlap_cb *self, int nr) +{ + struct sk_buff *skb = NULL; + int count = 0; + + /* + * Remove all the ack-ed frames from the window queue. + */ + + /* + * Optimize for the common case. It is most likely that the receiver + * will acknowledge all the frames we have sent! So in that case we + * delete all frames stored in window. + */ + if (nr == self->vs) { + while ((skb = skb_dequeue(&self->wx_list)) != NULL) { + dev_kfree_skb(skb); + } + /* The last acked frame is the next to send minus one */ + self->va = nr - 1; + } else { + /* Remove all acknowledged frames in current window */ + while ((skb_peek(&self->wx_list) != NULL) && + (((self->va+1) % 8) != nr)) + { + skb = skb_dequeue(&self->wx_list); + dev_kfree_skb(skb); + + self->va = (self->va + 1) % 8; + count++; + } + } + + /* Advance window */ + self->window = self->window_size - skb_queue_len(&self->wx_list); +} + +/* + * Function irlap_validate_ns_received (ns) + * + * Validate the next to send (ns) field from received frame. + */ +int irlap_validate_ns_received(struct irlap_cb *self, int ns) +{ + /* ns as expected? */ + if (ns == self->vr) + return NS_EXPECTED; + /* + * Stations are allowed to treat invalid NS as unexpected NS + * IrLAP, Recv ... with-invalid-Ns. p. 84 + */ + return NS_UNEXPECTED; + + /* return NR_INVALID; */ +} +/* + * Function irlap_validate_nr_received (nr) + * + * Validate the next to receive (nr) field from received frame. + * + */ +int irlap_validate_nr_received(struct irlap_cb *self, int nr) +{ + /* nr as expected? */ + if (nr == self->vs) { + IRDA_DEBUG(4, "%s(), expected!\n", __FUNCTION__); + return NR_EXPECTED; + } + + /* + * unexpected nr? (but within current window), first we check if the + * ns numbers of the frames in the current window wrap. + */ + if (self->va < self->vs) { + if ((nr >= self->va) && (nr <= self->vs)) + return NR_UNEXPECTED; + } else { + if ((nr >= self->va) || (nr <= self->vs)) + return NR_UNEXPECTED; + } + + /* Invalid nr! */ + return NR_INVALID; +} + +/* + * Function irlap_initiate_connection_state () + * + * Initialize the connection state parameters + * + */ +void irlap_initiate_connection_state(struct irlap_cb *self) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + /* Next to send and next to receive */ + self->vs = self->vr = 0; + + /* Last frame which got acked (0 - 1) % 8 */ + self->va = 7; + + self->window = 1; + + self->remote_busy = FALSE; + self->retry_count = 0; +} + +/* + * Function irlap_wait_min_turn_around (self, qos) + * + * Wait negotiated minimum turn around time, this function actually sets + * the number of BOS's that must be sent before the next transmitted + * frame in order to delay for the specified amount of time. This is + * done to avoid using timers, and the forbidden udelay! + */ +void irlap_wait_min_turn_around(struct irlap_cb *self, struct qos_info *qos) +{ + __u32 min_turn_time; + __u32 speed; + + /* Get QoS values. */ + speed = qos->baud_rate.value; + min_turn_time = qos->min_turn_time.value; + + /* No need to calculate XBOFs for speeds over 115200 bps */ + if (speed > 115200) { + self->mtt_required = min_turn_time; + return; + } + + /* + * Send additional BOF's for the next frame for the requested + * min turn time, so now we must calculate how many chars (XBOF's) we + * must send for the requested time period (min turn time) + */ + self->xbofs_delay = irlap_min_turn_time_in_bytes(speed, min_turn_time); +} + +/* + * Function irlap_flush_all_queues (void) + * + * Flush all queues + * + */ +void irlap_flush_all_queues(struct irlap_cb *self) +{ + struct sk_buff* skb; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + /* Free transmission queue */ + while ((skb = skb_dequeue(&self->txq)) != NULL) + dev_kfree_skb(skb); + + while ((skb = skb_dequeue(&self->txq_ultra)) != NULL) + dev_kfree_skb(skb); + + /* Free sliding window buffered packets */ + while ((skb = skb_dequeue(&self->wx_list)) != NULL) + dev_kfree_skb(skb); +} + +/* + * Function irlap_setspeed (self, speed) + * + * Change the speed of the IrDA port + * + */ +static void irlap_change_speed(struct irlap_cb *self, __u32 speed, int now) +{ + struct sk_buff *skb; + + IRDA_DEBUG(0, "%s(), setting speed to %d\n", __FUNCTION__, speed); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + self->speed = speed; + + /* Change speed now, or just piggyback speed on frames */ + if (now) { + /* Send down empty frame to trigger speed change */ + skb = dev_alloc_skb(0); + irlap_queue_xmit(self, skb); + } +} + +/* + * Function irlap_init_qos_capabilities (self, qos) + * + * Initialize QoS for this IrLAP session, What we do is to compute the + * intersection of the QoS capabilities for the user, driver and for + * IrLAP itself. Normally, IrLAP will not specify any values, but it can + * be used to restrict certain values. + */ +static void irlap_init_qos_capabilities(struct irlap_cb *self, + struct qos_info *qos_user) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + IRDA_ASSERT(self->netdev != NULL, return;); + + /* Start out with the maximum QoS support possible */ + irda_init_max_qos_capabilies(&self->qos_rx); + + /* Apply drivers QoS capabilities */ + irda_qos_compute_intersection(&self->qos_rx, self->qos_dev); + + /* + * Check for user supplied QoS parameters. The service user is only + * allowed to supply these values. We check each parameter since the + * user may not have set all of them. + */ + if (qos_user) { + IRDA_DEBUG(1, "%s(), Found user specified QoS!\n", __FUNCTION__); + + if (qos_user->baud_rate.bits) + self->qos_rx.baud_rate.bits &= qos_user->baud_rate.bits; + + if (qos_user->max_turn_time.bits) + self->qos_rx.max_turn_time.bits &= qos_user->max_turn_time.bits; + if (qos_user->data_size.bits) + self->qos_rx.data_size.bits &= qos_user->data_size.bits; + + if (qos_user->link_disc_time.bits) + self->qos_rx.link_disc_time.bits &= qos_user->link_disc_time.bits; + } + + /* Use 500ms in IrLAP for now */ + self->qos_rx.max_turn_time.bits &= 0x01; + + /* Set data size */ + /*self->qos_rx.data_size.bits &= 0x03;*/ + + irda_qos_bits_to_value(&self->qos_rx); +} + +/* + * Function irlap_apply_default_connection_parameters (void, now) + * + * Use the default connection and transmission parameters + */ +void irlap_apply_default_connection_parameters(struct irlap_cb *self) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + /* xbofs : Default value in NDM */ + self->next_bofs = 12; + self->bofs_count = 12; + + /* NDM Speed is 9600 */ + irlap_change_speed(self, 9600, TRUE); + + /* Set mbusy when going to NDM state */ + irda_device_set_media_busy(self->netdev, TRUE); + + /* + * Generate random connection address for this session, which must + * be 7 bits wide and different from 0x00 and 0xfe + */ + while ((self->caddr == 0x00) || (self->caddr == 0xfe)) { + get_random_bytes(&self->caddr, sizeof(self->caddr)); + self->caddr &= 0xfe; + } + + /* Use default values until connection has been negitiated */ + self->slot_timeout = sysctl_slot_timeout; + self->final_timeout = FINAL_TIMEOUT; + self->poll_timeout = POLL_TIMEOUT; + self->wd_timeout = WD_TIMEOUT; + + /* Set some default values */ + self->qos_tx.baud_rate.value = 9600; + self->qos_rx.baud_rate.value = 9600; + self->qos_tx.max_turn_time.value = 0; + self->qos_rx.max_turn_time.value = 0; + self->qos_tx.min_turn_time.value = 0; + self->qos_rx.min_turn_time.value = 0; + self->qos_tx.data_size.value = 64; + self->qos_rx.data_size.value = 64; + self->qos_tx.window_size.value = 1; + self->qos_rx.window_size.value = 1; + self->qos_tx.additional_bofs.value = 12; + self->qos_rx.additional_bofs.value = 12; + self->qos_tx.link_disc_time.value = 0; + self->qos_rx.link_disc_time.value = 0; + + irlap_flush_all_queues(self); + + self->disconnect_pending = FALSE; + self->connect_pending = FALSE; +} + +/* + * Function irlap_apply_connection_parameters (qos, now) + * + * Initialize IrLAP with the negotiated QoS values + * + * If 'now' is false, the speed and xbofs will be changed after the next + * frame is sent. + * If 'now' is true, the speed and xbofs is changed immediately + */ +void irlap_apply_connection_parameters(struct irlap_cb *self, int now) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + /* Set the negotiated xbofs value */ + self->next_bofs = self->qos_tx.additional_bofs.value; + if (now) + self->bofs_count = self->next_bofs; + + /* Set the negotiated link speed (may need the new xbofs value) */ + irlap_change_speed(self, self->qos_tx.baud_rate.value, now); + + self->window_size = self->qos_tx.window_size.value; + self->window = self->qos_tx.window_size.value; + +#ifdef CONFIG_IRDA_DYNAMIC_WINDOW + /* + * Calculate how many bytes it is possible to transmit before the + * link must be turned around + */ + self->line_capacity = + irlap_max_line_capacity(self->qos_tx.baud_rate.value, + self->qos_tx.max_turn_time.value); + self->bytes_left = self->line_capacity; +#endif /* CONFIG_IRDA_DYNAMIC_WINDOW */ + + + /* + * Initialize timeout values, some of the rules are listed on + * page 92 in IrLAP. + */ + IRDA_ASSERT(self->qos_tx.max_turn_time.value != 0, return;); + IRDA_ASSERT(self->qos_rx.max_turn_time.value != 0, return;); + /* The poll timeout applies only to the primary station. + * It defines the maximum time the primary stay in XMIT mode + * before timeout and turning the link around (sending a RR). + * Or, this is how much we can keep the pf bit in primary mode. + * Therefore, it must be lower or equal than our *OWN* max turn around. + * Jean II */ + self->poll_timeout = self->qos_tx.max_turn_time.value * HZ / 1000; + /* The Final timeout applies only to the primary station. + * It defines the maximum time the primary wait (mostly in RECV mode) + * for an answer from the secondary station before polling it again. + * Therefore, it must be greater or equal than our *PARTNER* + * max turn around time - Jean II */ + self->final_timeout = self->qos_rx.max_turn_time.value * HZ / 1000; + /* The Watchdog Bit timeout applies only to the secondary station. + * It defines the maximum time the secondary wait (mostly in RECV mode) + * for poll from the primary station before getting annoyed. + * Therefore, it must be greater or equal than our *PARTNER* + * max turn around time - Jean II */ + self->wd_timeout = self->final_timeout * 2; + + /* + * N1 and N2 are maximum retry count for *both* the final timer + * and the wd timer (with a factor 2) as defined above. + * After N1 retry of a timer, we give a warning to the user. + * After N2 retry, we consider the link dead and disconnect it. + * Jean II + */ + + /* + * Set N1 to 0 if Link Disconnect/Threshold Time = 3 and set it to + * 3 seconds otherwise. See page 71 in IrLAP for more details. + * Actually, it's not always 3 seconds, as we allow to set + * it via sysctl... Max maxtt is 500ms, and N1 need to be multiple + * of 2, so 1 second is minimum we can allow. - Jean II + */ + if (self->qos_tx.link_disc_time.value == sysctl_warn_noreply_time) + /* + * If we set N1 to 0, it will trigger immediately, which is + * not what we want. What we really want is to disable it, + * Jean II + */ + self->N1 = -2; /* Disable - Need to be multiple of 2*/ + else + self->N1 = sysctl_warn_noreply_time * 1000 / + self->qos_rx.max_turn_time.value; + + IRDA_DEBUG(4, "Setting N1 = %d\n", self->N1); + + /* Set N2 to match our own disconnect time */ + self->N2 = self->qos_tx.link_disc_time.value * 1000 / + self->qos_rx.max_turn_time.value; + IRDA_DEBUG(4, "Setting N2 = %d\n", self->N2); +} + +#ifdef CONFIG_PROC_FS +struct irlap_iter_state { + int id; +}; + +static void *irlap_seq_start(struct seq_file *seq, loff_t *pos) +{ + struct irlap_iter_state *iter = seq->private; + struct irlap_cb *self; + + /* Protect our access to the tsap list */ + spin_lock_irq(&irlap->hb_spinlock); + iter->id = 0; + + for (self = (struct irlap_cb *) hashbin_get_first(irlap); + self; self = (struct irlap_cb *) hashbin_get_next(irlap)) { + if (iter->id == *pos) + break; + ++iter->id; + } + + return self; +} + +static void *irlap_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + struct irlap_iter_state *iter = seq->private; + + ++*pos; + ++iter->id; + return (void *) hashbin_get_next(irlap); +} + +static void irlap_seq_stop(struct seq_file *seq, void *v) +{ + spin_unlock_irq(&irlap->hb_spinlock); +} + +static int irlap_seq_show(struct seq_file *seq, void *v) +{ + const struct irlap_iter_state *iter = seq->private; + const struct irlap_cb *self = v; + + IRDA_ASSERT(self->magic == LAP_MAGIC, return -EINVAL;); + + seq_printf(seq, "irlap%d ", iter->id); + seq_printf(seq, "state: %s\n", + irlap_state[self->state]); + + seq_printf(seq, " device name: %s, ", + (self->netdev) ? self->netdev->name : "bug"); + seq_printf(seq, "hardware name: %s\n", self->hw_name); + + seq_printf(seq, " caddr: %#02x, ", self->caddr); + seq_printf(seq, "saddr: %#08x, ", self->saddr); + seq_printf(seq, "daddr: %#08x\n", self->daddr); + + seq_printf(seq, " win size: %d, ", + self->window_size); + seq_printf(seq, "win: %d, ", self->window); +#ifdef CONFIG_IRDA_DYNAMIC_WINDOW + seq_printf(seq, "line capacity: %d, ", + self->line_capacity); + seq_printf(seq, "bytes left: %d\n", self->bytes_left); +#endif /* CONFIG_IRDA_DYNAMIC_WINDOW */ + seq_printf(seq, " tx queue len: %d ", + skb_queue_len(&self->txq)); + seq_printf(seq, "win queue len: %d ", + skb_queue_len(&self->wx_list)); + seq_printf(seq, "rbusy: %s", self->remote_busy ? + "TRUE" : "FALSE"); + seq_printf(seq, " mbusy: %s\n", self->media_busy ? + "TRUE" : "FALSE"); + + seq_printf(seq, " retrans: %d ", self->retry_count); + seq_printf(seq, "vs: %d ", self->vs); + seq_printf(seq, "vr: %d ", self->vr); + seq_printf(seq, "va: %d\n", self->va); + + seq_printf(seq, " qos\tbps\tmaxtt\tdsize\twinsize\taddbofs\tmintt\tldisc\tcomp\n"); + + seq_printf(seq, " tx\t%d\t", + self->qos_tx.baud_rate.value); + seq_printf(seq, "%d\t", + self->qos_tx.max_turn_time.value); + seq_printf(seq, "%d\t", + self->qos_tx.data_size.value); + seq_printf(seq, "%d\t", + self->qos_tx.window_size.value); + seq_printf(seq, "%d\t", + self->qos_tx.additional_bofs.value); + seq_printf(seq, "%d\t", + self->qos_tx.min_turn_time.value); + seq_printf(seq, "%d\t", + self->qos_tx.link_disc_time.value); + seq_printf(seq, "\n"); + + seq_printf(seq, " rx\t%d\t", + self->qos_rx.baud_rate.value); + seq_printf(seq, "%d\t", + self->qos_rx.max_turn_time.value); + seq_printf(seq, "%d\t", + self->qos_rx.data_size.value); + seq_printf(seq, "%d\t", + self->qos_rx.window_size.value); + seq_printf(seq, "%d\t", + self->qos_rx.additional_bofs.value); + seq_printf(seq, "%d\t", + self->qos_rx.min_turn_time.value); + seq_printf(seq, "%d\n", + self->qos_rx.link_disc_time.value); + + return 0; +} + +static struct seq_operations irlap_seq_ops = { + .start = irlap_seq_start, + .next = irlap_seq_next, + .stop = irlap_seq_stop, + .show = irlap_seq_show, +}; + +static int irlap_seq_open(struct inode *inode, struct file *file) +{ + struct seq_file *seq; + int rc = -ENOMEM; + struct irlap_iter_state *s = kmalloc(sizeof(*s), GFP_KERNEL); + + if (!s) + goto out; + + if (irlap == NULL) { + rc = -EINVAL; + goto out_kfree; + } + + rc = seq_open(file, &irlap_seq_ops); + if (rc) + goto out_kfree; + + seq = file->private_data; + seq->private = s; + memset(s, 0, sizeof(*s)); +out: + return rc; +out_kfree: + kfree(s); + goto out; +} + +struct file_operations irlap_seq_fops = { + .owner = THIS_MODULE, + .open = irlap_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release_private, +}; + +#endif /* CONFIG_PROC_FS */ diff --git a/net/irda/irlap_event.c b/net/irda/irlap_event.c new file mode 100644 index 000000000000..1cd89f5f3b75 --- /dev/null +++ b/net/irda/irlap_event.c @@ -0,0 +1,2334 @@ +/********************************************************************* + * + * Filename: irlap_event.c + * Version: 0.9 + * Description: IrLAP state machine implementation + * Status: Experimental. + * Author: Dag Brattli <dag@brattli.net> + * Created at: Sat Aug 16 00:59:29 1997 + * Modified at: Sat Dec 25 21:07:57 1999 + * Modified by: Dag Brattli <dag@brattli.net> + * + * Copyright (c) 1998-2000 Dag Brattli <dag@brattli.net>, + * Copyright (c) 1998 Thomas Davis <ratbert@radiks.net> + * All Rights Reserved. + * Copyright (c) 2000-2003 Jean Tourrilhes <jt@hpl.hp.com> + * + * 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <linux/config.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/skbuff.h> + +#include <net/irda/irda.h> +#include <net/irda/irlap_event.h> + +#include <net/irda/timer.h> +#include <net/irda/irlap.h> +#include <net/irda/irlap_frame.h> +#include <net/irda/qos.h> +#include <net/irda/parameters.h> +#include <net/irda/irlmp.h> /* irlmp_flow_indication(), ... */ + +#include <net/irda/irda_device.h> + +#ifdef CONFIG_IRDA_FAST_RR +int sysctl_fast_poll_increase = 50; +#endif + +static int irlap_state_ndm (struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info); +static int irlap_state_query (struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info); +static int irlap_state_reply (struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info); +static int irlap_state_conn (struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info); +static int irlap_state_setup (struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info); +static int irlap_state_offline(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info); +static int irlap_state_xmit_p (struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info); +static int irlap_state_pclose (struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info); +static int irlap_state_nrm_p (struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info); +static int irlap_state_reset_wait(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info); +static int irlap_state_reset (struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info); +static int irlap_state_nrm_s (struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info); +static int irlap_state_xmit_s (struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info); +static int irlap_state_sclose (struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info); +static int irlap_state_reset_check(struct irlap_cb *, IRLAP_EVENT event, + struct sk_buff *, struct irlap_info *); + +#ifdef CONFIG_IRDA_DEBUG +static const char *irlap_event[] = { + "DISCOVERY_REQUEST", + "CONNECT_REQUEST", + "CONNECT_RESPONSE", + "DISCONNECT_REQUEST", + "DATA_REQUEST", + "RESET_REQUEST", + "RESET_RESPONSE", + "SEND_I_CMD", + "SEND_UI_FRAME", + "RECV_DISCOVERY_XID_CMD", + "RECV_DISCOVERY_XID_RSP", + "RECV_SNRM_CMD", + "RECV_TEST_CMD", + "RECV_TEST_RSP", + "RECV_UA_RSP", + "RECV_DM_RSP", + "RECV_RD_RSP", + "RECV_I_CMD", + "RECV_I_RSP", + "RECV_UI_FRAME", + "RECV_FRMR_RSP", + "RECV_RR_CMD", + "RECV_RR_RSP", + "RECV_RNR_CMD", + "RECV_RNR_RSP", + "RECV_REJ_CMD", + "RECV_REJ_RSP", + "RECV_SREJ_CMD", + "RECV_SREJ_RSP", + "RECV_DISC_CMD", + "SLOT_TIMER_EXPIRED", + "QUERY_TIMER_EXPIRED", + "FINAL_TIMER_EXPIRED", + "POLL_TIMER_EXPIRED", + "DISCOVERY_TIMER_EXPIRED", + "WD_TIMER_EXPIRED", + "BACKOFF_TIMER_EXPIRED", + "MEDIA_BUSY_TIMER_EXPIRED", +}; +#endif /* CONFIG_IRDA_DEBUG */ + +const char *irlap_state[] = { + "LAP_NDM", + "LAP_QUERY", + "LAP_REPLY", + "LAP_CONN", + "LAP_SETUP", + "LAP_OFFLINE", + "LAP_XMIT_P", + "LAP_PCLOSE", + "LAP_NRM_P", + "LAP_RESET_WAIT", + "LAP_RESET", + "LAP_NRM_S", + "LAP_XMIT_S", + "LAP_SCLOSE", + "LAP_RESET_CHECK", +}; + +static int (*state[])(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) = +{ + irlap_state_ndm, + irlap_state_query, + irlap_state_reply, + irlap_state_conn, + irlap_state_setup, + irlap_state_offline, + irlap_state_xmit_p, + irlap_state_pclose, + irlap_state_nrm_p, + irlap_state_reset_wait, + irlap_state_reset, + irlap_state_nrm_s, + irlap_state_xmit_s, + irlap_state_sclose, + irlap_state_reset_check, +}; + +/* + * Function irda_poll_timer_expired (data) + * + * Poll timer has expired. Normally we must now send a RR frame to the + * remote device + */ +static void irlap_poll_timer_expired(void *data) +{ + struct irlap_cb *self = (struct irlap_cb *) data; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + irlap_do_event(self, POLL_TIMER_EXPIRED, NULL, NULL); +} + +/* + * Calculate and set time before we will have to send back the pf bit + * to the peer. Use in primary. + * Make sure that state is XMIT_P/XMIT_S when calling this function + * (and that nobody messed up with the state). - Jean II + */ +static void irlap_start_poll_timer(struct irlap_cb *self, int timeout) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + +#ifdef CONFIG_IRDA_FAST_RR + /* + * Send out the RR frames faster if our own transmit queue is empty, or + * if the peer is busy. The effect is a much faster conversation + */ + if ((skb_queue_len(&self->txq) == 0) || (self->remote_busy)) { + if (self->fast_RR == TRUE) { + /* + * Assert that the fast poll timer has not reached the + * normal poll timer yet + */ + if (self->fast_RR_timeout < timeout) { + /* + * FIXME: this should be a more configurable + * function + */ + self->fast_RR_timeout += + (sysctl_fast_poll_increase * HZ/1000); + + /* Use this fast(er) timeout instead */ + timeout = self->fast_RR_timeout; + } + } else { + self->fast_RR = TRUE; + + /* Start with just 0 ms */ + self->fast_RR_timeout = 0; + timeout = 0; + } + } else + self->fast_RR = FALSE; + + IRDA_DEBUG(3, "%s(), timeout=%d (%ld)\n", __FUNCTION__, timeout, jiffies); +#endif /* CONFIG_IRDA_FAST_RR */ + + if (timeout == 0) + irlap_do_event(self, POLL_TIMER_EXPIRED, NULL, NULL); + else + irda_start_timer(&self->poll_timer, timeout, self, + irlap_poll_timer_expired); +} + +/* + * Function irlap_do_event (event, skb, info) + * + * Rushes through the state machine without any delay. If state == XMIT + * then send queued data frames. + */ +void irlap_do_event(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) +{ + int ret; + + if (!self || self->magic != LAP_MAGIC) + return; + + IRDA_DEBUG(3, "%s(), event = %s, state = %s\n", __FUNCTION__, + irlap_event[event], irlap_state[self->state]); + + ret = (*state[self->state])(self, event, skb, info); + + /* + * Check if there are any pending events that needs to be executed + */ + switch (self->state) { + case LAP_XMIT_P: /* FALLTHROUGH */ + case LAP_XMIT_S: + /* + * We just received the pf bit and are at the beginning + * of a new LAP transmit window. + * Check if there are any queued data frames, and do not + * try to disconnect link if we send any data frames, since + * that will change the state away form XMIT + */ + IRDA_DEBUG(2, "%s() : queue len = %d\n", __FUNCTION__, + skb_queue_len(&self->txq)); + + if (skb_queue_len(&self->txq)) { + /* Prevent race conditions with irlap_data_request() */ + self->local_busy = TRUE; + + /* Theory of operation. + * We send frames up to when we fill the window or + * reach line capacity. Those frames will queue up + * in the device queue, and the driver will slowly + * send them. + * After each frame that we send, we poll the higher + * layer for more data. It's the right time to do + * that because the link layer need to perform the mtt + * and then send the first frame, so we can afford + * to send a bit of time in kernel space. + * The explicit flow indication allow to minimise + * buffers (== lower latency), to avoid higher layer + * polling via timers (== less context switches) and + * to implement a crude scheduler - Jean II */ + + /* Try to send away all queued data frames */ + while ((skb = skb_dequeue(&self->txq)) != NULL) { + /* Send one frame */ + ret = (*state[self->state])(self, SEND_I_CMD, + skb, NULL); + /* Drop reference count. + * It will be increase as needed in + * irlap_send_data_xxx() */ + kfree_skb(skb); + + /* Poll the higher layers for one more frame */ + irlmp_flow_indication(self->notify.instance, + FLOW_START); + + if (ret == -EPROTO) + break; /* Try again later! */ + } + /* Finished transmitting */ + self->local_busy = FALSE; + } else if (self->disconnect_pending) { + self->disconnect_pending = FALSE; + + ret = (*state[self->state])(self, DISCONNECT_REQUEST, + NULL, NULL); + } + break; +/* case LAP_NDM: */ +/* case LAP_CONN: */ +/* case LAP_RESET_WAIT: */ +/* case LAP_RESET_CHECK: */ + default: + break; + } +} + +/* + * Function irlap_next_state (self, state) + * + * Switches state and provides debug information + * + */ +static inline void irlap_next_state(struct irlap_cb *self, IRLAP_STATE state) +{ + /* + if (!self || self->magic != LAP_MAGIC) + return; + + IRDA_DEBUG(4, "next LAP state = %s\n", irlap_state[state]); + */ + self->state = state; +} + +/* + * Function irlap_state_ndm (event, skb, frame) + * + * NDM (Normal Disconnected Mode) state + * + */ +static int irlap_state_ndm(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) +{ + discovery_t *discovery_rsp; + int ret = 0; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return -1;); + + switch (event) { + case CONNECT_REQUEST: + IRDA_ASSERT(self->netdev != NULL, return -1;); + + if (self->media_busy) { + /* Note : this will never happen, because we test + * media busy in irlap_connect_request() and + * postpone the event... - Jean II */ + IRDA_DEBUG(0, "%s(), CONNECT_REQUEST: media busy!\n", + __FUNCTION__); + + /* Always switch state before calling upper layers */ + irlap_next_state(self, LAP_NDM); + + irlap_disconnect_indication(self, LAP_MEDIA_BUSY); + } else { + irlap_send_snrm_frame(self, &self->qos_rx); + + /* Start Final-bit timer */ + irlap_start_final_timer(self, self->final_timeout); + + self->retry_count = 0; + irlap_next_state(self, LAP_SETUP); + } + break; + case RECV_SNRM_CMD: + /* Check if the frame contains and I field */ + if (info) { + self->daddr = info->daddr; + self->caddr = info->caddr; + + irlap_next_state(self, LAP_CONN); + + irlap_connect_indication(self, skb); + } else { + IRDA_DEBUG(0, "%s(), SNRM frame does not " + "contain an I field!\n", __FUNCTION__); + } + break; + case DISCOVERY_REQUEST: + IRDA_ASSERT(info != NULL, return -1;); + + if (self->media_busy) { + IRDA_DEBUG(1, "%s(), DISCOVERY_REQUEST: media busy!\n", + __FUNCTION__); + /* irlap->log.condition = MEDIA_BUSY; */ + + /* This will make IrLMP try again */ + irlap_discovery_confirm(self, NULL); + /* Note : the discovery log is not cleaned up here, + * it will be done in irlap_discovery_request() + * Jean II */ + return 0; + } + + self->S = info->S; + self->s = info->s; + irlap_send_discovery_xid_frame(self, info->S, info->s, TRUE, + info->discovery); + self->frame_sent = FALSE; + self->s++; + + irlap_start_slot_timer(self, self->slot_timeout); + irlap_next_state(self, LAP_QUERY); + break; + case RECV_DISCOVERY_XID_CMD: + IRDA_ASSERT(info != NULL, return -1;); + + /* Assert that this is not the final slot */ + if (info->s <= info->S) { + self->slot = irlap_generate_rand_time_slot(info->S, + info->s); + if (self->slot == info->s) { + discovery_rsp = irlmp_get_discovery_response(); + discovery_rsp->data.daddr = info->daddr; + + irlap_send_discovery_xid_frame(self, info->S, + self->slot, + FALSE, + discovery_rsp); + self->frame_sent = TRUE; + } else + self->frame_sent = FALSE; + + /* + * Go to reply state until end of discovery to + * inhibit our own transmissions. Set the timer + * to not stay forever there... Jean II + */ + irlap_start_query_timer(self, info->S, info->s); + irlap_next_state(self, LAP_REPLY); + } else { + /* This is the final slot. How is it possible ? + * This would happen is both discoveries are just slightly + * offset (if they are in sync, all packets are lost). + * Most often, all the discovery requests will be received + * in QUERY state (see my comment there), except for the + * last frame that will come here. + * The big trouble when it happen is that active discovery + * doesn't happen, because nobody answer the discoveries + * frame of the other guy, so the log shows up empty. + * What should we do ? + * Not much. It's too late to answer those discovery frames, + * so we just pass the info to IrLMP who will put it in the + * log (and post an event). + * Another cause would be devices that do discovery much + * slower than us, however the latest fixes should minimise + * those cases... + * Jean II + */ + IRDA_DEBUG(1, "%s(), Receiving final discovery request, missed the discovery slots :-(\n", __FUNCTION__); + + /* Last discovery request -> in the log */ + irlap_discovery_indication(self, info->discovery); + } + break; + case MEDIA_BUSY_TIMER_EXPIRED: + /* A bunch of events may be postponed because the media is + * busy (usually immediately after we close a connection), + * or while we are doing discovery (state query/reply). + * In all those cases, the media busy flag will be cleared + * when it's OK for us to process those postponed events. + * This event is not mentioned in the state machines in the + * IrLAP spec. It's because they didn't consider Ultra and + * postponing connection request is optional. + * Jean II */ +#ifdef CONFIG_IRDA_ULTRA + /* Send any pending Ultra frames if any */ + if (!skb_queue_empty(&self->txq_ultra)) { + /* We don't send the frame, just post an event. + * Also, previously this code was in timer.c... + * Jean II */ + ret = (*state[self->state])(self, SEND_UI_FRAME, + NULL, NULL); + } +#endif /* CONFIG_IRDA_ULTRA */ + /* Check if we should try to connect. + * This code was previously in irlap_do_event() */ + if (self->connect_pending) { + self->connect_pending = FALSE; + + /* This one *should* not pend in this state, except + * if a socket try to connect and immediately + * disconnect. - clear - Jean II */ + if (self->disconnect_pending) + irlap_disconnect_indication(self, LAP_DISC_INDICATION); + else + ret = (*state[self->state])(self, + CONNECT_REQUEST, + NULL, NULL); + self->disconnect_pending = FALSE; + } + /* Note : one way to test if this code works well (including + * media busy and small busy) is to create a user space + * application generating an Ultra packet every 3.05 sec (or + * 2.95 sec) and to see how it interact with discovery. + * It's fairly easy to check that no packet is lost, that the + * packets are postponed during discovery and that after + * discovery indication you have a 100ms "gap". + * As connection request and Ultra are now processed the same + * way, this avoid the tedious job of trying IrLAP connection + * in all those cases... + * Jean II */ + break; +#ifdef CONFIG_IRDA_ULTRA + case SEND_UI_FRAME: + { + int i; + /* Only allowed to repeat an operation twice */ + for (i=0; ((i<2) && (self->media_busy == FALSE)); i++) { + skb = skb_dequeue(&self->txq_ultra); + if (skb) + irlap_send_ui_frame(self, skb, CBROADCAST, + CMD_FRAME); + else + break; + /* irlap_send_ui_frame() won't increase skb reference + * count, so no dev_kfree_skb() - Jean II */ + } + if (i == 2) { + /* Force us to listen 500 ms again */ + irda_device_set_media_busy(self->netdev, TRUE); + } + break; + } + case RECV_UI_FRAME: + /* Only accept broadcast frames in NDM mode */ + if (info->caddr != CBROADCAST) { + IRDA_DEBUG(0, "%s(), not a broadcast frame!\n", + __FUNCTION__); + } else + irlap_unitdata_indication(self, skb); + break; +#endif /* CONFIG_IRDA_ULTRA */ + case RECV_TEST_CMD: + /* Remove test frame header */ + skb_pull(skb, sizeof(struct test_frame)); + + /* + * Send response. This skb will not be sent out again, and + * will only be used to send out the same info as the cmd + */ + irlap_send_test_frame(self, CBROADCAST, info->daddr, skb); + break; + case RECV_TEST_RSP: + IRDA_DEBUG(0, "%s() not implemented!\n", __FUNCTION__); + break; + default: + IRDA_DEBUG(2, "%s(), Unknown event %s\n", __FUNCTION__, + irlap_event[event]); + + ret = -1; + break; + } + return ret; +} + +/* + * Function irlap_state_query (event, skb, info) + * + * QUERY state + * + */ +static int irlap_state_query(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) +{ + int ret = 0; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return -1;); + + switch (event) { + case RECV_DISCOVERY_XID_RSP: + IRDA_ASSERT(info != NULL, return -1;); + IRDA_ASSERT(info->discovery != NULL, return -1;); + + IRDA_DEBUG(4, "%s(), daddr=%08x\n", __FUNCTION__, + info->discovery->data.daddr); + + if (!self->discovery_log) { + IRDA_WARNING("%s: discovery log is gone! " + "maybe the discovery timeout has been set" + " to short?\n", __FUNCTION__); + break; + } + hashbin_insert(self->discovery_log, + (irda_queue_t *) info->discovery, + info->discovery->data.daddr, NULL); + + /* Keep state */ + /* irlap_next_state(self, LAP_QUERY); */ + + break; + case RECV_DISCOVERY_XID_CMD: + /* Yes, it is possible to receive those frames in this mode. + * Note that most often the last discovery request won't + * occur here but in NDM state (see my comment there). + * What should we do ? + * Not much. We are currently performing our own discovery, + * therefore we can't answer those frames. We don't want + * to change state either. We just pass the info to + * IrLMP who will put it in the log (and post an event). + * Jean II + */ + + IRDA_ASSERT(info != NULL, return -1;); + + IRDA_DEBUG(1, "%s(), Receiving discovery request (s = %d) while performing discovery :-(\n", __FUNCTION__, info->s); + + /* Last discovery request ? */ + if (info->s == 0xff) + irlap_discovery_indication(self, info->discovery); + break; + case SLOT_TIMER_EXPIRED: + /* + * Wait a little longer if we detect an incoming frame. This + * is not mentioned in the spec, but is a good thing to do, + * since we want to work even with devices that violate the + * timing requirements. + */ + if (irda_device_is_receiving(self->netdev) && !self->add_wait) { + IRDA_DEBUG(2, "%s(), device is slow to answer, " + "waiting some more!\n", __FUNCTION__); + irlap_start_slot_timer(self, msecs_to_jiffies(10)); + self->add_wait = TRUE; + return ret; + } + self->add_wait = FALSE; + + if (self->s < self->S) { + irlap_send_discovery_xid_frame(self, self->S, + self->s, TRUE, + self->discovery_cmd); + self->s++; + irlap_start_slot_timer(self, self->slot_timeout); + + /* Keep state */ + irlap_next_state(self, LAP_QUERY); + } else { + /* This is the final slot! */ + irlap_send_discovery_xid_frame(self, self->S, 0xff, + TRUE, + self->discovery_cmd); + + /* Always switch state before calling upper layers */ + irlap_next_state(self, LAP_NDM); + + /* + * We are now finished with the discovery procedure, + * so now we must return the results + */ + irlap_discovery_confirm(self, self->discovery_log); + + /* IrLMP should now have taken care of the log */ + self->discovery_log = NULL; + } + break; + default: + IRDA_DEBUG(2, "%s(), Unknown event %s\n", __FUNCTION__, + irlap_event[event]); + + ret = -1; + break; + } + return ret; +} + +/* + * Function irlap_state_reply (self, event, skb, info) + * + * REPLY, we have received a XID discovery frame from a device and we + * are waiting for the right time slot to send a response XID frame + * + */ +static int irlap_state_reply(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) +{ + discovery_t *discovery_rsp; + int ret=0; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return -1;); + + switch (event) { + case QUERY_TIMER_EXPIRED: + IRDA_DEBUG(0, "%s(), QUERY_TIMER_EXPIRED <%ld>\n", + __FUNCTION__, jiffies); + irlap_next_state(self, LAP_NDM); + break; + case RECV_DISCOVERY_XID_CMD: + IRDA_ASSERT(info != NULL, return -1;); + /* Last frame? */ + if (info->s == 0xff) { + del_timer(&self->query_timer); + + /* info->log.condition = REMOTE; */ + + /* Always switch state before calling upper layers */ + irlap_next_state(self, LAP_NDM); + + irlap_discovery_indication(self, info->discovery); + } else { + /* If it's our slot, send our reply */ + if ((info->s >= self->slot) && (!self->frame_sent)) { + discovery_rsp = irlmp_get_discovery_response(); + discovery_rsp->data.daddr = info->daddr; + + irlap_send_discovery_xid_frame(self, info->S, + self->slot, + FALSE, + discovery_rsp); + + self->frame_sent = TRUE; + } + /* Readjust our timer to accomodate devices + * doing faster or slower discovery than us... + * Jean II */ + irlap_start_query_timer(self, info->S, info->s); + + /* Keep state */ + //irlap_next_state(self, LAP_REPLY); + } + break; + default: + IRDA_DEBUG(1, "%s(), Unknown event %d, %s\n", __FUNCTION__, + event, irlap_event[event]); + + ret = -1; + break; + } + return ret; +} + +/* + * Function irlap_state_conn (event, skb, info) + * + * CONN, we have received a SNRM command and is waiting for the upper + * layer to accept or refuse connection + * + */ +static int irlap_state_conn(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) +{ + int ret = 0; + + IRDA_DEBUG(4, "%s(), event=%s\n", __FUNCTION__, irlap_event[ event]); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return -1;); + + switch (event) { + case CONNECT_RESPONSE: + skb_pull(skb, sizeof(struct snrm_frame)); + + IRDA_ASSERT(self->netdev != NULL, return -1;); + + irlap_qos_negotiate(self, skb); + + irlap_initiate_connection_state(self); + + /* + * Applying the parameters now will make sure we change speed + * *after* we have sent the next frame + */ + irlap_apply_connection_parameters(self, FALSE); + + /* + * Sending this frame will force a speed change after it has + * been sent (i.e. the frame will be sent at 9600). + */ + irlap_send_ua_response_frame(self, &self->qos_rx); + +#if 0 + /* + * We are allowed to send two frames, but this may increase + * the connect latency, so lets not do it for now. + */ + /* This is full of good intentions, but doesn't work in + * practice. + * After sending the first UA response, we switch the + * dongle to the negotiated speed, which is usually + * different than 9600 kb/s. + * From there, there is two solutions : + * 1) The other end has received the first UA response : + * it will set up the connection, move to state LAP_NRM_P, + * and will ignore and drop the second UA response. + * Actually, it's even worse : the other side will almost + * immediately send a RR that will likely collide with the + * UA response (depending on negotiated turnaround). + * 2) The other end has not received the first UA response, + * will stay at 9600 and will never see the second UA response. + * Jean II */ + irlap_send_ua_response_frame(self, &self->qos_rx); +#endif + + /* + * The WD-timer could be set to the duration of the P-timer + * for this case, but it is recommended to use twice the + * value (note 3 IrLAP p. 60). + */ + irlap_start_wd_timer(self, self->wd_timeout); + irlap_next_state(self, LAP_NRM_S); + + break; + case RECV_DISCOVERY_XID_CMD: + IRDA_DEBUG(3, "%s(), event RECV_DISCOVER_XID_CMD!\n", + __FUNCTION__); + irlap_next_state(self, LAP_NDM); + + break; + case DISCONNECT_REQUEST: + IRDA_DEBUG(0, "%s(), Disconnect request!\n", __FUNCTION__); + irlap_send_dm_frame(self); + irlap_next_state( self, LAP_NDM); + irlap_disconnect_indication(self, LAP_DISC_INDICATION); + break; + default: + IRDA_DEBUG(1, "%s(), Unknown event %d, %s\n", __FUNCTION__, + event, irlap_event[event]); + + ret = -1; + break; + } + + return ret; +} + +/* + * Function irlap_state_setup (event, skb, frame) + * + * SETUP state, The local layer has transmitted a SNRM command frame to + * a remote peer layer and is awaiting a reply . + * + */ +static int irlap_state_setup(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) +{ + int ret = 0; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return -1;); + + switch (event) { + case FINAL_TIMER_EXPIRED: + if (self->retry_count < self->N3) { +/* + * Perform random backoff, Wait a random number of time units, minimum + * duration half the time taken to transmitt a SNRM frame, maximum duration + * 1.5 times the time taken to transmit a SNRM frame. So this time should + * between 15 msecs and 45 msecs. + */ + irlap_start_backoff_timer(self, msecs_to_jiffies(20 + + (jiffies % 30))); + } else { + /* Always switch state before calling upper layers */ + irlap_next_state(self, LAP_NDM); + + irlap_disconnect_indication(self, LAP_FOUND_NONE); + } + break; + case BACKOFF_TIMER_EXPIRED: + irlap_send_snrm_frame(self, &self->qos_rx); + irlap_start_final_timer(self, self->final_timeout); + self->retry_count++; + break; + case RECV_SNRM_CMD: + IRDA_DEBUG(4, "%s(), SNRM battle!\n", __FUNCTION__); + + IRDA_ASSERT(skb != NULL, return 0;); + IRDA_ASSERT(info != NULL, return 0;); + + /* + * The device with the largest device address wins the battle + * (both have sent a SNRM command!) + */ + if (info &&(info->daddr > self->saddr)) { + del_timer(&self->final_timer); + irlap_initiate_connection_state(self); + + IRDA_ASSERT(self->netdev != NULL, return -1;); + + skb_pull(skb, sizeof(struct snrm_frame)); + + irlap_qos_negotiate(self, skb); + + /* Send UA frame and then change link settings */ + irlap_apply_connection_parameters(self, FALSE); + irlap_send_ua_response_frame(self, &self->qos_rx); + + irlap_next_state(self, LAP_NRM_S); + irlap_connect_confirm(self, skb); + + /* + * The WD-timer could be set to the duration of the + * P-timer for this case, but it is recommended + * to use twice the value (note 3 IrLAP p. 60). + */ + irlap_start_wd_timer(self, self->wd_timeout); + } else { + /* We just ignore the other device! */ + irlap_next_state(self, LAP_SETUP); + } + break; + case RECV_UA_RSP: + /* Stop F-timer */ + del_timer(&self->final_timer); + + /* Initiate connection state */ + irlap_initiate_connection_state(self); + + /* Negotiate connection parameters */ + IRDA_ASSERT(skb->len > 10, return -1;); + + skb_pull(skb, sizeof(struct ua_frame)); + + IRDA_ASSERT(self->netdev != NULL, return -1;); + + irlap_qos_negotiate(self, skb); + + /* Set the new link setting *now* (before the rr frame) */ + irlap_apply_connection_parameters(self, TRUE); + self->retry_count = 0; + + /* Wait for turnaround time to give a chance to the other + * device to be ready to receive us. + * Note : the time to switch speed is typically larger + * than the turnaround time, but as we don't have the other + * side speed switch time, that's our best guess... + * Jean II */ + irlap_wait_min_turn_around(self, &self->qos_tx); + + /* This frame will actually be sent at the new speed */ + irlap_send_rr_frame(self, CMD_FRAME); + + /* The timer is set to half the normal timer to quickly + * detect a failure to negociate the new connection + * parameters. IrLAP 6.11.3.2, note 3. + * Note that currently we don't process this failure + * properly, as we should do a quick disconnect. + * Jean II */ + irlap_start_final_timer(self, self->final_timeout/2); + irlap_next_state(self, LAP_NRM_P); + + irlap_connect_confirm(self, skb); + break; + case RECV_DM_RSP: /* FALLTHROUGH */ + case RECV_DISC_CMD: + del_timer(&self->final_timer); + irlap_next_state(self, LAP_NDM); + + irlap_disconnect_indication(self, LAP_DISC_INDICATION); + break; + default: + IRDA_DEBUG(1, "%s(), Unknown event %d, %s\n", __FUNCTION__, + event, irlap_event[event]); + + ret = -1; + break; + } + return ret; +} + +/* + * Function irlap_state_offline (self, event, skb, info) + * + * OFFLINE state, not used for now! + * + */ +static int irlap_state_offline(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) +{ + IRDA_DEBUG( 0, "%s(), Unknown event\n", __FUNCTION__); + + return -1; +} + +/* + * Function irlap_state_xmit_p (self, event, skb, info) + * + * XMIT, Only the primary station has right to transmit, and we + * therefore do not expect to receive any transmissions from other + * stations. + * + */ +static int irlap_state_xmit_p(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) +{ + int ret = 0; + + switch (event) { + case SEND_I_CMD: + /* + * Only send frame if send-window > 0. + */ + if ((self->window > 0) && (!self->remote_busy)) { + int nextfit; +#ifdef CONFIG_IRDA_DYNAMIC_WINDOW + struct sk_buff *skb_next; + + /* With DYNAMIC_WINDOW, we keep the window size + * maximum, and adapt on the packets we are sending. + * At 115k, we can send only 2 packets of 2048 bytes + * in a 500 ms turnaround. Without this option, we + * would always limit the window to 2. With this + * option, if we send smaller packets, we can send + * up to 7 of them (always depending on QoS). + * Jean II */ + + /* Look at the next skb. This is safe, as we are + * the only consumer of the Tx queue (if we are not, + * we have other problems) - Jean II */ + skb_next = skb_peek(&self->txq); + + /* Check if a subsequent skb exist and would fit in + * the current window (with respect to turnaround + * time). + * This allow us to properly mark the current packet + * with the pf bit, to avoid falling back on the + * second test below, and avoid waiting the + * end of the window and sending a extra RR. + * Note : (skb_next != NULL) <=> (skb_queue_len() > 0) + * Jean II */ + nextfit = ((skb_next != NULL) && + ((skb_next->len + skb->len) <= + self->bytes_left)); + + /* + * The current packet may not fit ! Because of test + * above, this should not happen any more !!! + * Test if we have transmitted more bytes over the + * link than its possible to do with the current + * speed and turn-around-time. + */ + if((!nextfit) && (skb->len > self->bytes_left)) { + IRDA_DEBUG(0, "%s(), Not allowed to transmit" + " more bytes!\n", __FUNCTION__); + /* Requeue the skb */ + skb_queue_head(&self->txq, skb_get(skb)); + /* + * We should switch state to LAP_NRM_P, but + * that is not possible since we must be sure + * that we poll the other side. Since we have + * used up our time, the poll timer should + * trigger anyway now, so we just wait for it + * DB + */ + /* + * Sorry, but that's not totally true. If + * we send 2000B packets, we may wait another + * 1000B until our turnaround expire. That's + * why we need to be proactive in avoiding + * coming here. - Jean II + */ + return -EPROTO; + } + + /* Substract space used by this skb */ + self->bytes_left -= skb->len; +#else /* CONFIG_IRDA_DYNAMIC_WINDOW */ + /* Window has been adjusted for the max packet + * size, so much simpler... - Jean II */ + nextfit = (skb_queue_len(&self->txq) > 0); +#endif /* CONFIG_IRDA_DYNAMIC_WINDOW */ + /* + * Send data with poll bit cleared only if window > 1 + * and there is more frames after this one to be sent + */ + if ((self->window > 1) && (nextfit)) { + /* More packet to send in current window */ + irlap_send_data_primary(self, skb); + irlap_next_state(self, LAP_XMIT_P); + } else { + /* Final packet of window */ + irlap_send_data_primary_poll(self, skb); + irlap_next_state(self, LAP_NRM_P); + + /* + * Make sure state machine does not try to send + * any more frames + */ + ret = -EPROTO; + } +#ifdef CONFIG_IRDA_FAST_RR + /* Peer may want to reply immediately */ + self->fast_RR = FALSE; +#endif /* CONFIG_IRDA_FAST_RR */ + } else { + IRDA_DEBUG(4, "%s(), Unable to send! remote busy?\n", + __FUNCTION__); + skb_queue_head(&self->txq, skb_get(skb)); + + /* + * The next ret is important, because it tells + * irlap_next_state _not_ to deliver more frames + */ + ret = -EPROTO; + } + break; + case POLL_TIMER_EXPIRED: + IRDA_DEBUG(3, "%s(), POLL_TIMER_EXPIRED <%ld>\n", + __FUNCTION__, jiffies); + irlap_send_rr_frame(self, CMD_FRAME); + /* Return to NRM properly - Jean II */ + self->window = self->window_size; +#ifdef CONFIG_IRDA_DYNAMIC_WINDOW + /* Allowed to transmit a maximum number of bytes again. */ + self->bytes_left = self->line_capacity; +#endif /* CONFIG_IRDA_DYNAMIC_WINDOW */ + irlap_start_final_timer(self, self->final_timeout); + irlap_next_state(self, LAP_NRM_P); + break; + case DISCONNECT_REQUEST: + del_timer(&self->poll_timer); + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_disc_frame(self); + irlap_flush_all_queues(self); + irlap_start_final_timer(self, self->final_timeout); + self->retry_count = 0; + irlap_next_state(self, LAP_PCLOSE); + break; + case DATA_REQUEST: + /* Nothing to do, irlap_do_event() will send the packet + * when we return... - Jean II */ + break; + default: + IRDA_DEBUG(0, "%s(), Unknown event %s\n", + __FUNCTION__, irlap_event[event]); + + ret = -EINVAL; + break; + } + return ret; +} + +/* + * Function irlap_state_pclose (event, skb, info) + * + * PCLOSE state + */ +static int irlap_state_pclose(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) +{ + int ret = 0; + + IRDA_DEBUG(1, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return -1;); + + switch (event) { + case RECV_UA_RSP: /* FALLTHROUGH */ + case RECV_DM_RSP: + del_timer(&self->final_timer); + + /* Set new link parameters */ + irlap_apply_default_connection_parameters(self); + + /* Always switch state before calling upper layers */ + irlap_next_state(self, LAP_NDM); + + irlap_disconnect_indication(self, LAP_DISC_INDICATION); + break; + case FINAL_TIMER_EXPIRED: + if (self->retry_count < self->N3) { + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_disc_frame(self); + irlap_start_final_timer(self, self->final_timeout); + self->retry_count++; + /* Keep state */ + } else { + irlap_apply_default_connection_parameters(self); + + /* Always switch state before calling upper layers */ + irlap_next_state(self, LAP_NDM); + + irlap_disconnect_indication(self, LAP_NO_RESPONSE); + } + break; + default: + IRDA_DEBUG(1, "%s(), Unknown event %d\n", __FUNCTION__, event); + + ret = -1; + break; + } + return ret; +} + +/* + * Function irlap_state_nrm_p (self, event, skb, info) + * + * NRM_P (Normal Response Mode as Primary), The primary station has given + * permissions to a secondary station to transmit IrLAP resonse frames + * (by sending a frame with the P bit set). The primary station will not + * transmit any frames and is expecting to receive frames only from the + * secondary to which transmission permissions has been given. + */ +static int irlap_state_nrm_p(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) +{ + int ret = 0; + int ns_status; + int nr_status; + + switch (event) { + case RECV_I_RSP: /* Optimize for the common case */ + /* FIXME: must check for remote_busy below */ +#ifdef CONFIG_IRDA_FAST_RR + /* + * Reset the fast_RR so we can use the fast RR code with + * full speed the next time since peer may have more frames + * to transmitt + */ + self->fast_RR = FALSE; +#endif /* CONFIG_IRDA_FAST_RR */ + IRDA_ASSERT( info != NULL, return -1;); + + ns_status = irlap_validate_ns_received(self, info->ns); + nr_status = irlap_validate_nr_received(self, info->nr); + + /* + * Check for expected I(nformation) frame + */ + if ((ns_status == NS_EXPECTED) && (nr_status == NR_EXPECTED)) { + + /* Update Vr (next frame for us to receive) */ + self->vr = (self->vr + 1) % 8; + + /* Update Nr received, cleanup our retry queue */ + irlap_update_nr_received(self, info->nr); + + /* + * Got expected NR, so reset the + * retry_count. This is not done by IrLAP spec, + * which is strange! + */ + self->retry_count = 0; + self->ack_required = TRUE; + + /* poll bit cleared? */ + if (!info->pf) { + /* Keep state, do not move this line */ + irlap_next_state(self, LAP_NRM_P); + + irlap_data_indication(self, skb, FALSE); + } else { + /* No longer waiting for pf */ + del_timer(&self->final_timer); + + irlap_wait_min_turn_around(self, &self->qos_tx); + + /* Call higher layer *before* changing state + * to give them a chance to send data in the + * next LAP frame. + * Jean II */ + irlap_data_indication(self, skb, FALSE); + + /* XMIT states are the most dangerous state + * to be in, because user requests are + * processed directly and may change state. + * On the other hand, in NDM_P, those + * requests are queued and we will process + * them when we return to irlap_do_event(). + * Jean II + */ + irlap_next_state(self, LAP_XMIT_P); + + /* This is the last frame. + * Make sure it's always called in XMIT state. + * - Jean II */ + irlap_start_poll_timer(self, self->poll_timeout); + } + break; + + } + /* Unexpected next to send (Ns) */ + if ((ns_status == NS_UNEXPECTED) && (nr_status == NR_EXPECTED)) + { + if (!info->pf) { + irlap_update_nr_received(self, info->nr); + + /* + * Wait until the last frame before doing + * anything + */ + + /* Keep state */ + irlap_next_state(self, LAP_NRM_P); + } else { + IRDA_DEBUG(4, + "%s(), missing or duplicate frame!\n", + __FUNCTION__); + + /* Update Nr received */ + irlap_update_nr_received(self, info->nr); + + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_rr_frame(self, CMD_FRAME); + + self->ack_required = FALSE; + + irlap_start_final_timer(self, self->final_timeout); + irlap_next_state(self, LAP_NRM_P); + } + break; + } + /* + * Unexpected next to receive (Nr) + */ + if ((ns_status == NS_EXPECTED) && (nr_status == NR_UNEXPECTED)) + { + if (info->pf) { + self->vr = (self->vr + 1) % 8; + + /* Update Nr received */ + irlap_update_nr_received(self, info->nr); + + /* Resend rejected frames */ + irlap_resend_rejected_frames(self, CMD_FRAME); + + self->ack_required = FALSE; + + /* Make sure we account for the time + * to transmit our frames. See comemnts + * in irlap_send_data_primary_poll(). + * Jean II */ + irlap_start_final_timer(self, 2 * self->final_timeout); + + /* Keep state, do not move this line */ + irlap_next_state(self, LAP_NRM_P); + + irlap_data_indication(self, skb, FALSE); + } else { + /* + * Do not resend frames until the last + * frame has arrived from the other + * device. This is not documented in + * IrLAP!! + */ + self->vr = (self->vr + 1) % 8; + + /* Update Nr received */ + irlap_update_nr_received(self, info->nr); + + self->ack_required = FALSE; + + /* Keep state, do not move this line!*/ + irlap_next_state(self, LAP_NRM_P); + + irlap_data_indication(self, skb, FALSE); + } + break; + } + /* + * Unexpected next to send (Ns) and next to receive (Nr) + * Not documented by IrLAP! + */ + if ((ns_status == NS_UNEXPECTED) && + (nr_status == NR_UNEXPECTED)) + { + IRDA_DEBUG(4, "%s(), unexpected nr and ns!\n", + __FUNCTION__); + if (info->pf) { + /* Resend rejected frames */ + irlap_resend_rejected_frames(self, CMD_FRAME); + + /* Give peer some time to retransmit! + * But account for our own Tx. */ + irlap_start_final_timer(self, 2 * self->final_timeout); + + /* Keep state, do not move this line */ + irlap_next_state(self, LAP_NRM_P); + } else { + /* Update Nr received */ + /* irlap_update_nr_received( info->nr); */ + + self->ack_required = FALSE; + } + break; + } + + /* + * Invalid NR or NS + */ + if ((nr_status == NR_INVALID) || (ns_status == NS_INVALID)) { + if (info->pf) { + del_timer(&self->final_timer); + + irlap_next_state(self, LAP_RESET_WAIT); + + irlap_disconnect_indication(self, LAP_RESET_INDICATION); + self->xmitflag = TRUE; + } else { + del_timer(&self->final_timer); + + irlap_disconnect_indication(self, LAP_RESET_INDICATION); + + self->xmitflag = FALSE; + } + break; + } + IRDA_DEBUG(1, "%s(), Not implemented!\n", __FUNCTION__); + IRDA_DEBUG(1, "%s(), event=%s, ns_status=%d, nr_status=%d\n", + __FUNCTION__, irlap_event[event], ns_status, nr_status); + break; + case RECV_UI_FRAME: + /* Poll bit cleared? */ + if (!info->pf) { + irlap_data_indication(self, skb, TRUE); + irlap_next_state(self, LAP_NRM_P); + } else { + del_timer(&self->final_timer); + irlap_data_indication(self, skb, TRUE); + irlap_next_state(self, LAP_XMIT_P); + IRDA_DEBUG(1, "%s: RECV_UI_FRAME: next state %s\n", __FUNCTION__, irlap_state[self->state]); + irlap_start_poll_timer(self, self->poll_timeout); + } + break; + case RECV_RR_RSP: + /* + * If you get a RR, the remote isn't busy anymore, + * no matter what the NR + */ + self->remote_busy = FALSE; + + /* + * Nr as expected? + */ + ret = irlap_validate_nr_received(self, info->nr); + if (ret == NR_EXPECTED) { + /* Stop final timer */ + del_timer(&self->final_timer); + + /* Update Nr received */ + irlap_update_nr_received(self, info->nr); + + /* + * Got expected NR, so reset the retry_count. This + * is not done by the IrLAP standard , which is + * strange! DB. + */ + self->retry_count = 0; + irlap_wait_min_turn_around(self, &self->qos_tx); + + irlap_next_state(self, LAP_XMIT_P); + + /* Start poll timer */ + irlap_start_poll_timer(self, self->poll_timeout); + } else if (ret == NR_UNEXPECTED) { + IRDA_ASSERT(info != NULL, return -1;); + /* + * Unexpected nr! + */ + + /* Update Nr received */ + irlap_update_nr_received(self, info->nr); + + IRDA_DEBUG(4, "RECV_RR_FRAME: Retrans:%d, nr=%d, va=%d, " + "vs=%d, vr=%d\n", + self->retry_count, info->nr, self->va, + self->vs, self->vr); + + /* Resend rejected frames */ + irlap_resend_rejected_frames(self, CMD_FRAME); + + /* Final timer ??? Jean II */ + + irlap_next_state(self, LAP_NRM_P); + } else if (ret == NR_INVALID) { + IRDA_DEBUG(1, "%s(), Received RR with " + "invalid nr !\n", __FUNCTION__); + del_timer(&self->final_timer); + + irlap_next_state(self, LAP_RESET_WAIT); + + irlap_disconnect_indication(self, LAP_RESET_INDICATION); + self->xmitflag = TRUE; + } + break; + case RECV_RNR_RSP: + IRDA_ASSERT(info != NULL, return -1;); + + /* Stop final timer */ + del_timer(&self->final_timer); + self->remote_busy = TRUE; + + /* Update Nr received */ + irlap_update_nr_received(self, info->nr); + irlap_next_state(self, LAP_XMIT_P); + + /* Start poll timer */ + irlap_start_poll_timer(self, self->poll_timeout); + break; + case RECV_FRMR_RSP: + del_timer(&self->final_timer); + self->xmitflag = TRUE; + irlap_next_state(self, LAP_RESET_WAIT); + irlap_reset_indication(self); + break; + case FINAL_TIMER_EXPIRED: + /* + * We are allowed to wait for additional 300 ms if + * final timer expires when we are in the middle + * of receiving a frame (page 45, IrLAP). Check that + * we only do this once for each frame. + */ + if (irda_device_is_receiving(self->netdev) && !self->add_wait) { + IRDA_DEBUG(1, "FINAL_TIMER_EXPIRED when receiving a " + "frame! Waiting a little bit more!\n"); + irlap_start_final_timer(self, msecs_to_jiffies(300)); + + /* + * Don't allow this to happen one more time in a row, + * or else we can get a pretty tight loop here if + * if we only receive half a frame. DB. + */ + self->add_wait = TRUE; + break; + } + self->add_wait = FALSE; + + /* N2 is the disconnect timer. Until we reach it, we retry */ + if (self->retry_count < self->N2) { + /* Retry sending the pf bit to the secondary */ + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_rr_frame(self, CMD_FRAME); + + irlap_start_final_timer(self, self->final_timeout); + self->retry_count++; + IRDA_DEBUG(4, "irlap_state_nrm_p: FINAL_TIMER_EXPIRED:" + " retry_count=%d\n", self->retry_count); + + /* Early warning event. I'm using a pretty liberal + * interpretation of the spec and generate an event + * every time the timer is multiple of N1 (and not + * only the first time). This allow application + * to know precisely if connectivity restart... + * Jean II */ + if((self->retry_count % self->N1) == 0) + irlap_status_indication(self, + STATUS_NO_ACTIVITY); + + /* Keep state */ + } else { + irlap_apply_default_connection_parameters(self); + + /* Always switch state before calling upper layers */ + irlap_next_state(self, LAP_NDM); + irlap_disconnect_indication(self, LAP_NO_RESPONSE); + } + break; + case RECV_REJ_RSP: + irlap_update_nr_received(self, info->nr); + if (self->remote_busy) { + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_rr_frame(self, CMD_FRAME); + } else + irlap_resend_rejected_frames(self, CMD_FRAME); + irlap_start_final_timer(self, 2 * self->final_timeout); + break; + case RECV_SREJ_RSP: + irlap_update_nr_received(self, info->nr); + if (self->remote_busy) { + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_rr_frame(self, CMD_FRAME); + } else + irlap_resend_rejected_frame(self, CMD_FRAME); + irlap_start_final_timer(self, 2 * self->final_timeout); + break; + case RECV_RD_RSP: + IRDA_DEBUG(1, "%s(), RECV_RD_RSP\n", __FUNCTION__); + + irlap_flush_all_queues(self); + irlap_next_state(self, LAP_XMIT_P); + /* Call back the LAP state machine to do a proper disconnect */ + irlap_disconnect_request(self); + break; + default: + IRDA_DEBUG(1, "%s(), Unknown event %s\n", + __FUNCTION__, irlap_event[event]); + + ret = -1; + break; + } + return ret; +} + +/* + * Function irlap_state_reset_wait (event, skb, info) + * + * We have informed the service user of a reset condition, and is + * awaiting reset of disconnect request. + * + */ +static int irlap_state_reset_wait(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) +{ + int ret = 0; + + IRDA_DEBUG(3, "%s(), event = %s\n", __FUNCTION__, irlap_event[event]); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return -1;); + + switch (event) { + case RESET_REQUEST: + if (self->xmitflag) { + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_snrm_frame(self, NULL); + irlap_start_final_timer(self, self->final_timeout); + irlap_next_state(self, LAP_RESET); + } else { + irlap_start_final_timer(self, self->final_timeout); + irlap_next_state(self, LAP_RESET); + } + break; + case DISCONNECT_REQUEST: + irlap_wait_min_turn_around( self, &self->qos_tx); + irlap_send_disc_frame( self); + irlap_flush_all_queues( self); + irlap_start_final_timer( self, self->final_timeout); + self->retry_count = 0; + irlap_next_state( self, LAP_PCLOSE); + break; + default: + IRDA_DEBUG(2, "%s(), Unknown event %s\n", __FUNCTION__, + irlap_event[event]); + + ret = -1; + break; + } + return ret; +} + +/* + * Function irlap_state_reset (self, event, skb, info) + * + * We have sent a SNRM reset command to the peer layer, and is awaiting + * reply. + * + */ +static int irlap_state_reset(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) +{ + int ret = 0; + + IRDA_DEBUG(3, "%s(), event = %s\n", __FUNCTION__, irlap_event[event]); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return -1;); + + switch (event) { + case RECV_DISC_CMD: + del_timer(&self->final_timer); + + irlap_apply_default_connection_parameters(self); + + /* Always switch state before calling upper layers */ + irlap_next_state(self, LAP_NDM); + + irlap_disconnect_indication(self, LAP_NO_RESPONSE); + + break; + case RECV_UA_RSP: + del_timer(&self->final_timer); + + /* Initiate connection state */ + irlap_initiate_connection_state(self); + + irlap_reset_confirm(); + + self->remote_busy = FALSE; + + irlap_next_state(self, LAP_XMIT_P); + + irlap_start_poll_timer(self, self->poll_timeout); + + break; + case FINAL_TIMER_EXPIRED: + if (self->retry_count < 3) { + irlap_wait_min_turn_around(self, &self->qos_tx); + + IRDA_ASSERT(self->netdev != NULL, return -1;); + irlap_send_snrm_frame(self, self->qos_dev); + + self->retry_count++; /* Experimental!! */ + + irlap_start_final_timer(self, self->final_timeout); + irlap_next_state(self, LAP_RESET); + } else if (self->retry_count >= self->N3) { + irlap_apply_default_connection_parameters(self); + + /* Always switch state before calling upper layers */ + irlap_next_state(self, LAP_NDM); + + irlap_disconnect_indication(self, LAP_NO_RESPONSE); + } + break; + case RECV_SNRM_CMD: + /* + * SNRM frame is not allowed to contain an I-field in this + * state + */ + if (!info) { + IRDA_DEBUG(3, "%s(), RECV_SNRM_CMD\n", __FUNCTION__); + irlap_initiate_connection_state(self); + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_ua_response_frame(self, &self->qos_rx); + irlap_reset_confirm(); + irlap_start_wd_timer(self, self->wd_timeout); + irlap_next_state(self, LAP_NDM); + } else { + IRDA_DEBUG(0, + "%s(), SNRM frame contained an I field!\n", + __FUNCTION__); + } + break; + default: + IRDA_DEBUG(1, "%s(), Unknown event %s\n", + __FUNCTION__, irlap_event[event]); + + ret = -1; + break; + } + return ret; +} + +/* + * Function irlap_state_xmit_s (event, skb, info) + * + * XMIT_S, The secondary station has been given the right to transmit, + * and we therefor do not expect to receive any transmissions from other + * stations. + */ +static int irlap_state_xmit_s(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) +{ + int ret = 0; + + IRDA_DEBUG(4, "%s(), event=%s\n", __FUNCTION__, irlap_event[event]); + + IRDA_ASSERT(self != NULL, return -ENODEV;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return -EBADR;); + + switch (event) { + case SEND_I_CMD: + /* + * Send frame only if send window > 0 + */ + if ((self->window > 0) && (!self->remote_busy)) { + int nextfit; +#ifdef CONFIG_IRDA_DYNAMIC_WINDOW + struct sk_buff *skb_next; + + /* + * Same deal as in irlap_state_xmit_p(), so see + * the comments at that point. + * We are the secondary, so there are only subtle + * differences. - Jean II + */ + + /* Check if a subsequent skb exist and would fit in + * the current window (with respect to turnaround + * time). - Jean II */ + skb_next = skb_peek(&self->txq); + nextfit = ((skb_next != NULL) && + ((skb_next->len + skb->len) <= + self->bytes_left)); + + /* + * Test if we have transmitted more bytes over the + * link than its possible to do with the current + * speed and turn-around-time. + */ + if((!nextfit) && (skb->len > self->bytes_left)) { + IRDA_DEBUG(0, "%s(), Not allowed to transmit" + " more bytes!\n", __FUNCTION__); + /* Requeue the skb */ + skb_queue_head(&self->txq, skb_get(skb)); + + /* + * Switch to NRM_S, this is only possible + * when we are in secondary mode, since we + * must be sure that we don't miss any RR + * frames + */ + self->window = self->window_size; + self->bytes_left = self->line_capacity; + irlap_start_wd_timer(self, self->wd_timeout); + + irlap_next_state(self, LAP_NRM_S); + /* Slight difference with primary : + * here we would wait for the other side to + * expire the turnaround. - Jean II */ + + return -EPROTO; /* Try again later */ + } + /* Substract space used by this skb */ + self->bytes_left -= skb->len; +#else /* CONFIG_IRDA_DYNAMIC_WINDOW */ + /* Window has been adjusted for the max packet + * size, so much simpler... - Jean II */ + nextfit = (skb_queue_len(&self->txq) > 0); +#endif /* CONFIG_IRDA_DYNAMIC_WINDOW */ + /* + * Send data with final bit cleared only if window > 1 + * and there is more frames to be sent + */ + if ((self->window > 1) && (nextfit)) { + irlap_send_data_secondary(self, skb); + irlap_next_state(self, LAP_XMIT_S); + } else { + irlap_send_data_secondary_final(self, skb); + irlap_next_state(self, LAP_NRM_S); + + /* + * Make sure state machine does not try to send + * any more frames + */ + ret = -EPROTO; + } + } else { + IRDA_DEBUG(2, "%s(), Unable to send!\n", __FUNCTION__); + skb_queue_head(&self->txq, skb_get(skb)); + ret = -EPROTO; + } + break; + case DISCONNECT_REQUEST: + irlap_send_rd_frame(self); + irlap_flush_all_queues(self); + irlap_start_wd_timer(self, self->wd_timeout); + irlap_next_state(self, LAP_SCLOSE); + break; + case DATA_REQUEST: + /* Nothing to do, irlap_do_event() will send the packet + * when we return... - Jean II */ + break; + default: + IRDA_DEBUG(2, "%s(), Unknown event %s\n", __FUNCTION__, + irlap_event[event]); + + ret = -EINVAL; + break; + } + return ret; +} + +/* + * Function irlap_state_nrm_s (event, skb, info) + * + * NRM_S (Normal Response Mode as Secondary) state, in this state we are + * expecting to receive frames from the primary station + * + */ +static int irlap_state_nrm_s(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) +{ + int ns_status; + int nr_status; + int ret = 0; + + IRDA_DEBUG(4, "%s(), event=%s\n", __FUNCTION__, irlap_event[ event]); + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return -1;); + + switch (event) { + case RECV_I_CMD: /* Optimize for the common case */ + /* FIXME: must check for remote_busy below */ + IRDA_DEBUG(4, "%s(), event=%s nr=%d, vs=%d, ns=%d, " + "vr=%d, pf=%d\n", __FUNCTION__, + irlap_event[event], info->nr, + self->vs, info->ns, self->vr, info->pf); + + self->retry_count = 0; + + ns_status = irlap_validate_ns_received(self, info->ns); + nr_status = irlap_validate_nr_received(self, info->nr); + /* + * Check for expected I(nformation) frame + */ + if ((ns_status == NS_EXPECTED) && (nr_status == NR_EXPECTED)) { + + /* Update Vr (next frame for us to receive) */ + self->vr = (self->vr + 1) % 8; + + /* Update Nr received */ + irlap_update_nr_received(self, info->nr); + + /* + * poll bit cleared? + */ + if (!info->pf) { + + self->ack_required = TRUE; + + /* + * Starting WD-timer here is optional, but + * not recommended. Note 6 IrLAP p. 83 + */ +#if 0 + irda_start_timer(WD_TIMER, self->wd_timeout); +#endif + /* Keep state, do not move this line */ + irlap_next_state(self, LAP_NRM_S); + + irlap_data_indication(self, skb, FALSE); + break; + } else { + /* + * We should wait before sending RR, and + * also before changing to XMIT_S + * state. (note 1, IrLAP p. 82) + */ + irlap_wait_min_turn_around(self, &self->qos_tx); + + /* + * Give higher layers a chance to + * immediately reply with some data before + * we decide if we should send a RR frame + * or not + */ + irlap_data_indication(self, skb, FALSE); + + /* Any pending data requests? */ + if ((skb_queue_len(&self->txq) > 0) && + (self->window > 0)) + { + self->ack_required = TRUE; + + del_timer(&self->wd_timer); + + irlap_next_state(self, LAP_XMIT_S); + } else { + irlap_send_rr_frame(self, RSP_FRAME); + irlap_start_wd_timer(self, + self->wd_timeout); + + /* Keep the state */ + irlap_next_state(self, LAP_NRM_S); + } + break; + } + } + /* + * Check for Unexpected next to send (Ns) + */ + if ((ns_status == NS_UNEXPECTED) && (nr_status == NR_EXPECTED)) + { + /* Unexpected next to send, with final bit cleared */ + if (!info->pf) { + irlap_update_nr_received(self, info->nr); + + irlap_start_wd_timer(self, self->wd_timeout); + } else { + /* Update Nr received */ + irlap_update_nr_received(self, info->nr); + + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_rr_frame(self, RSP_FRAME); + + irlap_start_wd_timer(self, self->wd_timeout); + } + break; + } + + /* + * Unexpected Next to Receive(NR) ? + */ + if ((ns_status == NS_EXPECTED) && (nr_status == NR_UNEXPECTED)) + { + if (info->pf) { + IRDA_DEBUG(4, "RECV_I_RSP: frame(s) lost\n"); + + self->vr = (self->vr + 1) % 8; + + /* Update Nr received */ + irlap_update_nr_received(self, info->nr); + + /* Resend rejected frames */ + irlap_resend_rejected_frames(self, RSP_FRAME); + + /* Keep state, do not move this line */ + irlap_next_state(self, LAP_NRM_S); + + irlap_data_indication(self, skb, FALSE); + irlap_start_wd_timer(self, self->wd_timeout); + break; + } + /* + * This is not documented in IrLAP!! Unexpected NR + * with poll bit cleared + */ + if (!info->pf) { + self->vr = (self->vr + 1) % 8; + + /* Update Nr received */ + irlap_update_nr_received(self, info->nr); + + /* Keep state, do not move this line */ + irlap_next_state(self, LAP_NRM_S); + + irlap_data_indication(self, skb, FALSE); + irlap_start_wd_timer(self, self->wd_timeout); + } + break; + } + + if (ret == NR_INVALID) { + IRDA_DEBUG(0, "NRM_S, NR_INVALID not implemented!\n"); + } + if (ret == NS_INVALID) { + IRDA_DEBUG(0, "NRM_S, NS_INVALID not implemented!\n"); + } + break; + case RECV_UI_FRAME: + /* + * poll bit cleared? + */ + if (!info->pf) { + irlap_data_indication(self, skb, TRUE); + irlap_next_state(self, LAP_NRM_S); /* Keep state */ + } else { + /* + * Any pending data requests? + */ + if ((skb_queue_len(&self->txq) > 0) && + (self->window > 0) && !self->remote_busy) + { + irlap_data_indication(self, skb, TRUE); + + del_timer(&self->wd_timer); + + irlap_next_state(self, LAP_XMIT_S); + } else { + irlap_data_indication(self, skb, TRUE); + + irlap_wait_min_turn_around(self, &self->qos_tx); + + irlap_send_rr_frame(self, RSP_FRAME); + self->ack_required = FALSE; + + irlap_start_wd_timer(self, self->wd_timeout); + + /* Keep the state */ + irlap_next_state(self, LAP_NRM_S); + } + } + break; + case RECV_RR_CMD: + self->retry_count = 0; + + /* + * Nr as expected? + */ + nr_status = irlap_validate_nr_received(self, info->nr); + if (nr_status == NR_EXPECTED) { + if ((skb_queue_len( &self->txq) > 0) && + (self->window > 0)) { + self->remote_busy = FALSE; + + /* Update Nr received */ + irlap_update_nr_received(self, info->nr); + del_timer(&self->wd_timer); + + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_next_state(self, LAP_XMIT_S); + } else { + self->remote_busy = FALSE; + /* Update Nr received */ + irlap_update_nr_received(self, info->nr); + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_start_wd_timer(self, self->wd_timeout); + + /* Note : if the link is idle (this case), + * we never go in XMIT_S, so we never get a + * chance to process any DISCONNECT_REQUEST. + * Do it now ! - Jean II */ + if (self->disconnect_pending) { + /* Disconnect */ + irlap_send_rd_frame(self); + irlap_flush_all_queues(self); + + irlap_next_state(self, LAP_SCLOSE); + } else { + /* Just send back pf bit */ + irlap_send_rr_frame(self, RSP_FRAME); + + irlap_next_state(self, LAP_NRM_S); + } + } + } else if (nr_status == NR_UNEXPECTED) { + self->remote_busy = FALSE; + irlap_update_nr_received(self, info->nr); + irlap_resend_rejected_frames(self, RSP_FRAME); + + irlap_start_wd_timer(self, self->wd_timeout); + + /* Keep state */ + irlap_next_state(self, LAP_NRM_S); + } else { + IRDA_DEBUG(1, "%s(), invalid nr not implemented!\n", + __FUNCTION__); + } + break; + case RECV_SNRM_CMD: + /* SNRM frame is not allowed to contain an I-field */ + if (!info) { + del_timer(&self->wd_timer); + IRDA_DEBUG(1, "%s(), received SNRM cmd\n", __FUNCTION__); + irlap_next_state(self, LAP_RESET_CHECK); + + irlap_reset_indication(self); + } else { + IRDA_DEBUG(0, + "%s(), SNRM frame contained an I-field!\n", + __FUNCTION__); + + } + break; + case RECV_REJ_CMD: + irlap_update_nr_received(self, info->nr); + if (self->remote_busy) { + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_rr_frame(self, RSP_FRAME); + } else + irlap_resend_rejected_frames(self, RSP_FRAME); + irlap_start_wd_timer(self, self->wd_timeout); + break; + case RECV_SREJ_CMD: + irlap_update_nr_received(self, info->nr); + if (self->remote_busy) { + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_rr_frame(self, RSP_FRAME); + } else + irlap_resend_rejected_frame(self, RSP_FRAME); + irlap_start_wd_timer(self, self->wd_timeout); + break; + case WD_TIMER_EXPIRED: + /* + * Wait until retry_count * n matches negotiated threshold/ + * disconnect time (note 2 in IrLAP p. 82) + * + * Similar to irlap_state_nrm_p() -> FINAL_TIMER_EXPIRED + * Note : self->wd_timeout = (self->final_timeout * 2), + * which explain why we use (self->N2 / 2) here !!! + * Jean II + */ + IRDA_DEBUG(1, "%s(), retry_count = %d\n", __FUNCTION__, + self->retry_count); + + if (self->retry_count < (self->N2 / 2)) { + /* No retry, just wait for primary */ + irlap_start_wd_timer(self, self->wd_timeout); + self->retry_count++; + + if((self->retry_count % (self->N1 / 2)) == 0) + irlap_status_indication(self, + STATUS_NO_ACTIVITY); + } else { + irlap_apply_default_connection_parameters(self); + + /* Always switch state before calling upper layers */ + irlap_next_state(self, LAP_NDM); + irlap_disconnect_indication(self, LAP_NO_RESPONSE); + } + break; + case RECV_DISC_CMD: + /* Always switch state before calling upper layers */ + irlap_next_state(self, LAP_NDM); + + /* Send disconnect response */ + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_ua_response_frame(self, NULL); + + del_timer(&self->wd_timer); + irlap_flush_all_queues(self); + /* Set default link parameters */ + irlap_apply_default_connection_parameters(self); + + irlap_disconnect_indication(self, LAP_DISC_INDICATION); + break; + case RECV_DISCOVERY_XID_CMD: + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_rr_frame(self, RSP_FRAME); + self->ack_required = TRUE; + irlap_start_wd_timer(self, self->wd_timeout); + irlap_next_state(self, LAP_NRM_S); + + break; + case RECV_TEST_CMD: + /* Remove test frame header (only LAP header in NRM) */ + skb_pull(skb, LAP_ADDR_HEADER + LAP_CTRL_HEADER); + + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_start_wd_timer(self, self->wd_timeout); + + /* Send response (info will be copied) */ + irlap_send_test_frame(self, self->caddr, info->daddr, skb); + break; + default: + IRDA_DEBUG(1, "%s(), Unknown event %d, (%s)\n", __FUNCTION__, + event, irlap_event[event]); + + ret = -EINVAL; + break; + } + return ret; +} + +/* + * Function irlap_state_sclose (self, event, skb, info) + */ +static int irlap_state_sclose(struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, struct irlap_info *info) +{ + int ret = 0; + + IRDA_DEBUG(1, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return -ENODEV;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return -EBADR;); + + switch (event) { + case RECV_DISC_CMD: + /* Always switch state before calling upper layers */ + irlap_next_state(self, LAP_NDM); + + /* Send disconnect response */ + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_ua_response_frame(self, NULL); + + del_timer(&self->wd_timer); + /* Set default link parameters */ + irlap_apply_default_connection_parameters(self); + + irlap_disconnect_indication(self, LAP_DISC_INDICATION); + break; + case RECV_DM_RSP: + /* IrLAP-1.1 p.82: in SCLOSE, S and I type RSP frames + * shall take us down into default NDM state, like DM_RSP + */ + case RECV_RR_RSP: + case RECV_RNR_RSP: + case RECV_REJ_RSP: + case RECV_SREJ_RSP: + case RECV_I_RSP: + /* Always switch state before calling upper layers */ + irlap_next_state(self, LAP_NDM); + + del_timer(&self->wd_timer); + irlap_apply_default_connection_parameters(self); + + irlap_disconnect_indication(self, LAP_DISC_INDICATION); + break; + case WD_TIMER_EXPIRED: + /* Always switch state before calling upper layers */ + irlap_next_state(self, LAP_NDM); + + irlap_apply_default_connection_parameters(self); + + irlap_disconnect_indication(self, LAP_DISC_INDICATION); + break; + default: + /* IrLAP-1.1 p.82: in SCLOSE, basically any received frame + * with pf=1 shall restart the wd-timer and resend the rd:rsp + */ + if (info != NULL && info->pf) { + del_timer(&self->wd_timer); + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_rd_frame(self); + irlap_start_wd_timer(self, self->wd_timeout); + break; /* stay in SCLOSE */ + } + + IRDA_DEBUG(1, "%s(), Unknown event %d, (%s)\n", __FUNCTION__, + event, irlap_event[event]); + + ret = -EINVAL; + break; + } + + return -1; +} + +static int irlap_state_reset_check( struct irlap_cb *self, IRLAP_EVENT event, + struct sk_buff *skb, + struct irlap_info *info) +{ + int ret = 0; + + IRDA_DEBUG(1, "%s(), event=%s\n", __FUNCTION__, irlap_event[event]); + + IRDA_ASSERT(self != NULL, return -ENODEV;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return -EBADR;); + + switch (event) { + case RESET_RESPONSE: + irlap_send_ua_response_frame(self, &self->qos_rx); + irlap_initiate_connection_state(self); + irlap_start_wd_timer(self, WD_TIMEOUT); + irlap_flush_all_queues(self); + + irlap_next_state(self, LAP_NRM_S); + break; + case DISCONNECT_REQUEST: + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_send_rd_frame(self); + irlap_start_wd_timer(self, WD_TIMEOUT); + irlap_next_state(self, LAP_SCLOSE); + break; + default: + IRDA_DEBUG(1, "%s(), Unknown event %d, (%s)\n", __FUNCTION__, + event, irlap_event[event]); + + ret = -EINVAL; + break; + } + return ret; +} diff --git a/net/irda/irlap_frame.c b/net/irda/irlap_frame.c new file mode 100644 index 000000000000..040abe714aa3 --- /dev/null +++ b/net/irda/irlap_frame.c @@ -0,0 +1,1437 @@ +/********************************************************************* + * + * Filename: irlap_frame.c + * Version: 1.0 + * Description: Build and transmit IrLAP frames + * Status: Stable + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Tue Aug 19 10:27:26 1997 + * Modified at: Wed Jan 5 08:59:04 2000 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1998-2000 Dag Brattli <dagb@cs.uit.no>, + * All Rights Reserved. + * Copyright (c) 2000-2003 Jean Tourrilhes <jt@hpl.hp.com> + * + * 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <linux/skbuff.h> +#include <linux/if.h> +#include <linux/if_ether.h> +#include <linux/netdevice.h> +#include <linux/irda.h> + +#include <net/pkt_sched.h> +#include <net/sock.h> + +#include <asm/byteorder.h> + +#include <net/irda/irda.h> +#include <net/irda/irda_device.h> +#include <net/irda/irlap.h> +#include <net/irda/wrapper.h> +#include <net/irda/timer.h> +#include <net/irda/irlap_frame.h> +#include <net/irda/qos.h> + +static void irlap_send_i_frame(struct irlap_cb *self, struct sk_buff *skb, + int command); + +/* + * Function irlap_insert_info (self, skb) + * + * Insert minimum turnaround time and speed information into the skb. We + * need to do this since it's per packet relevant information. Safe to + * have this function inlined since it's only called from one place + */ +static inline void irlap_insert_info(struct irlap_cb *self, + struct sk_buff *skb) +{ + struct irda_skb_cb *cb = (struct irda_skb_cb *) skb->cb; + + /* + * Insert MTT (min. turn time) and speed into skb, so that the + * device driver knows which settings to use + */ + cb->magic = LAP_MAGIC; + cb->mtt = self->mtt_required; + cb->next_speed = self->speed; + + /* Reset */ + self->mtt_required = 0; + + /* + * Delay equals negotiated BOFs count, plus the number of BOFs to + * force the negotiated minimum turnaround time + */ + cb->xbofs = self->bofs_count; + cb->next_xbofs = self->next_bofs; + cb->xbofs_delay = self->xbofs_delay; + + /* Reset XBOF's delay (used only for getting min turn time) */ + self->xbofs_delay = 0; + /* Put the correct xbofs value for the next packet */ + self->bofs_count = self->next_bofs; +} + +/* + * Function irlap_queue_xmit (self, skb) + * + * A little wrapper for dev_queue_xmit, so we can insert some common + * code into it. + */ +void irlap_queue_xmit(struct irlap_cb *self, struct sk_buff *skb) +{ + /* Some common init stuff */ + skb->dev = self->netdev; + skb->h.raw = skb->nh.raw = skb->mac.raw = skb->data; + skb->protocol = htons(ETH_P_IRDA); + skb->priority = TC_PRIO_BESTEFFORT; + + irlap_insert_info(self, skb); + + dev_queue_xmit(skb); +} + +/* + * Function irlap_send_snrm_cmd (void) + * + * Transmits a connect SNRM command frame + */ +void irlap_send_snrm_frame(struct irlap_cb *self, struct qos_info *qos) +{ + struct sk_buff *tx_skb; + struct snrm_frame *frame; + int ret; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + /* Allocate frame */ + tx_skb = dev_alloc_skb(64); + if (!tx_skb) + return; + + frame = (struct snrm_frame *) skb_put(tx_skb, 2); + + /* Insert connection address field */ + if (qos) + frame->caddr = CMD_FRAME | CBROADCAST; + else + frame->caddr = CMD_FRAME | self->caddr; + + /* Insert control field */ + frame->control = SNRM_CMD | PF_BIT; + + /* + * If we are establishing a connection then insert QoS paramerters + */ + if (qos) { + skb_put(tx_skb, 9); /* 21 left */ + frame->saddr = cpu_to_le32(self->saddr); + frame->daddr = cpu_to_le32(self->daddr); + + frame->ncaddr = self->caddr; + + ret = irlap_insert_qos_negotiation_params(self, tx_skb); + if (ret < 0) { + dev_kfree_skb(tx_skb); + return; + } + } + irlap_queue_xmit(self, tx_skb); +} + +/* + * Function irlap_recv_snrm_cmd (skb, info) + * + * Received SNRM (Set Normal Response Mode) command frame + * + */ +static void irlap_recv_snrm_cmd(struct irlap_cb *self, struct sk_buff *skb, + struct irlap_info *info) +{ + struct snrm_frame *frame; + + if (pskb_may_pull(skb,sizeof(struct snrm_frame))) { + frame = (struct snrm_frame *) skb->data; + + /* Copy the new connection address ignoring the C/R bit */ + info->caddr = frame->ncaddr & 0xFE; + + /* Check if the new connection address is valid */ + if ((info->caddr == 0x00) || (info->caddr == 0xfe)) { + IRDA_DEBUG(3, "%s(), invalid connection address!\n", + __FUNCTION__); + return; + } + + /* Copy peer device address */ + info->daddr = le32_to_cpu(frame->saddr); + info->saddr = le32_to_cpu(frame->daddr); + + /* Only accept if addressed directly to us */ + if (info->saddr != self->saddr) { + IRDA_DEBUG(2, "%s(), not addressed to us!\n", + __FUNCTION__); + return; + } + irlap_do_event(self, RECV_SNRM_CMD, skb, info); + } else { + /* Signal that this SNRM frame does not contain and I-field */ + irlap_do_event(self, RECV_SNRM_CMD, skb, NULL); + } +} + +/* + * Function irlap_send_ua_response_frame (qos) + * + * Send UA (Unnumbered Acknowledgement) frame + * + */ +void irlap_send_ua_response_frame(struct irlap_cb *self, struct qos_info *qos) +{ + struct sk_buff *tx_skb; + struct ua_frame *frame; + int ret; + + IRDA_DEBUG(2, "%s() <%ld>\n", __FUNCTION__, jiffies); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + /* Allocate frame */ + tx_skb = dev_alloc_skb(64); + if (!tx_skb) + return; + + frame = (struct ua_frame *) skb_put(tx_skb, 10); + + /* Build UA response */ + frame->caddr = self->caddr; + frame->control = UA_RSP | PF_BIT; + + frame->saddr = cpu_to_le32(self->saddr); + frame->daddr = cpu_to_le32(self->daddr); + + /* Should we send QoS negotiation parameters? */ + if (qos) { + ret = irlap_insert_qos_negotiation_params(self, tx_skb); + if (ret < 0) { + dev_kfree_skb(tx_skb); + return; + } + } + + irlap_queue_xmit(self, tx_skb); +} + + +/* + * Function irlap_send_dm_frame (void) + * + * Send disconnected mode (DM) frame + * + */ +void irlap_send_dm_frame( struct irlap_cb *self) +{ + struct sk_buff *tx_skb = NULL; + __u8 *frame; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + tx_skb = dev_alloc_skb(32); + if (!tx_skb) + return; + + frame = skb_put(tx_skb, 2); + + if (self->state == LAP_NDM) + frame[0] = CBROADCAST; + else + frame[0] = self->caddr; + + frame[1] = DM_RSP | PF_BIT; + + irlap_queue_xmit(self, tx_skb); +} + +/* + * Function irlap_send_disc_frame (void) + * + * Send disconnect (DISC) frame + * + */ +void irlap_send_disc_frame(struct irlap_cb *self) +{ + struct sk_buff *tx_skb = NULL; + __u8 *frame; + + IRDA_DEBUG(3, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + tx_skb = dev_alloc_skb(16); + if (!tx_skb) + return; + + frame = skb_put(tx_skb, 2); + + frame[0] = self->caddr | CMD_FRAME; + frame[1] = DISC_CMD | PF_BIT; + + irlap_queue_xmit(self, tx_skb); +} + +/* + * Function irlap_send_discovery_xid_frame (S, s, command) + * + * Build and transmit a XID (eXchange station IDentifier) discovery + * frame. + */ +void irlap_send_discovery_xid_frame(struct irlap_cb *self, int S, __u8 s, + __u8 command, discovery_t *discovery) +{ + struct sk_buff *tx_skb = NULL; + struct xid_frame *frame; + __u32 bcast = BROADCAST; + __u8 *info; + + IRDA_DEBUG(4, "%s(), s=%d, S=%d, command=%d\n", __FUNCTION__, + s, S, command); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + IRDA_ASSERT(discovery != NULL, return;); + + tx_skb = dev_alloc_skb(64); + if (!tx_skb) + return; + + skb_put(tx_skb, 14); + frame = (struct xid_frame *) tx_skb->data; + + if (command) { + frame->caddr = CBROADCAST | CMD_FRAME; + frame->control = XID_CMD | PF_BIT; + } else { + frame->caddr = CBROADCAST; + frame->control = XID_RSP | PF_BIT; + } + frame->ident = XID_FORMAT; + + frame->saddr = cpu_to_le32(self->saddr); + + if (command) + frame->daddr = cpu_to_le32(bcast); + else + frame->daddr = cpu_to_le32(discovery->data.daddr); + + switch (S) { + case 1: + frame->flags = 0x00; + break; + case 6: + frame->flags = 0x01; + break; + case 8: + frame->flags = 0x02; + break; + case 16: + frame->flags = 0x03; + break; + default: + frame->flags = 0x02; + break; + } + + frame->slotnr = s; + frame->version = 0x00; + + /* + * Provide info for final slot only in commands, and for all + * responses. Send the second byte of the hint only if the + * EXTENSION bit is set in the first byte. + */ + if (!command || (frame->slotnr == 0xff)) { + int len; + + if (discovery->data.hints[0] & HINT_EXTENSION) { + info = skb_put(tx_skb, 2); + info[0] = discovery->data.hints[0]; + info[1] = discovery->data.hints[1]; + } else { + info = skb_put(tx_skb, 1); + info[0] = discovery->data.hints[0]; + } + info = skb_put(tx_skb, 1); + info[0] = discovery->data.charset; + + len = IRDA_MIN(discovery->name_len, skb_tailroom(tx_skb)); + info = skb_put(tx_skb, len); + memcpy(info, discovery->data.info, len); + } + irlap_queue_xmit(self, tx_skb); +} + +/* + * Function irlap_recv_discovery_xid_rsp (skb, info) + * + * Received a XID discovery response + * + */ +static void irlap_recv_discovery_xid_rsp(struct irlap_cb *self, + struct sk_buff *skb, + struct irlap_info *info) +{ + struct xid_frame *xid; + discovery_t *discovery = NULL; + __u8 *discovery_info; + char *text; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + if (!pskb_may_pull(skb, sizeof(struct xid_frame))) { + IRDA_ERROR("%s: frame to short!\n", __FUNCTION__); + return; + } + + xid = (struct xid_frame *) skb->data; + + info->daddr = le32_to_cpu(xid->saddr); + info->saddr = le32_to_cpu(xid->daddr); + + /* Make sure frame is addressed to us */ + if ((info->saddr != self->saddr) && (info->saddr != BROADCAST)) { + IRDA_DEBUG(0, "%s(), frame is not addressed to us!\n", + __FUNCTION__); + return; + } + + if ((discovery = kmalloc(sizeof(discovery_t), GFP_ATOMIC)) == NULL) { + IRDA_WARNING("%s: kmalloc failed!\n", __FUNCTION__); + return; + } + memset(discovery, 0, sizeof(discovery_t)); + + discovery->data.daddr = info->daddr; + discovery->data.saddr = self->saddr; + discovery->timestamp = jiffies; + + IRDA_DEBUG(4, "%s(), daddr=%08x\n", __FUNCTION__, + discovery->data.daddr); + + discovery_info = skb_pull(skb, sizeof(struct xid_frame)); + + /* Get info returned from peer */ + discovery->data.hints[0] = discovery_info[0]; + if (discovery_info[0] & HINT_EXTENSION) { + IRDA_DEBUG(4, "EXTENSION\n"); + discovery->data.hints[1] = discovery_info[1]; + discovery->data.charset = discovery_info[2]; + text = (char *) &discovery_info[3]; + } else { + discovery->data.hints[1] = 0; + discovery->data.charset = discovery_info[1]; + text = (char *) &discovery_info[2]; + } + /* + * Terminate info string, should be safe since this is where the + * FCS bytes resides. + */ + skb->data[skb->len] = '\0'; + strncpy(discovery->data.info, text, NICKNAME_MAX_LEN); + discovery->name_len = strlen(discovery->data.info); + + info->discovery = discovery; + + irlap_do_event(self, RECV_DISCOVERY_XID_RSP, skb, info); +} + +/* + * Function irlap_recv_discovery_xid_cmd (skb, info) + * + * Received a XID discovery command + * + */ +static void irlap_recv_discovery_xid_cmd(struct irlap_cb *self, + struct sk_buff *skb, + struct irlap_info *info) +{ + struct xid_frame *xid; + discovery_t *discovery = NULL; + __u8 *discovery_info; + char *text; + + if (!pskb_may_pull(skb, sizeof(struct xid_frame))) { + IRDA_ERROR("%s: frame to short!\n", __FUNCTION__); + return; + } + + xid = (struct xid_frame *) skb->data; + + info->daddr = le32_to_cpu(xid->saddr); + info->saddr = le32_to_cpu(xid->daddr); + + /* Make sure frame is addressed to us */ + if ((info->saddr != self->saddr) && (info->saddr != BROADCAST)) { + IRDA_DEBUG(0, "%s(), frame is not addressed to us!\n", + __FUNCTION__); + return; + } + + switch (xid->flags & 0x03) { + case 0x00: + info->S = 1; + break; + case 0x01: + info->S = 6; + break; + case 0x02: + info->S = 8; + break; + case 0x03: + info->S = 16; + break; + default: + /* Error!! */ + return; + } + info->s = xid->slotnr; + + discovery_info = skb_pull(skb, sizeof(struct xid_frame)); + + /* + * Check if last frame + */ + if (info->s == 0xff) { + /* Check if things are sane at this point... */ + if((discovery_info == NULL) || + !pskb_may_pull(skb, 3)) { + IRDA_ERROR("%s: discovery frame to short!\n", + __FUNCTION__); + return; + } + + /* + * We now have some discovery info to deliver! + */ + discovery = kmalloc(sizeof(discovery_t), GFP_ATOMIC); + if (!discovery) { + IRDA_WARNING("%s: unable to malloc!\n", __FUNCTION__); + return; + } + + discovery->data.daddr = info->daddr; + discovery->data.saddr = self->saddr; + discovery->timestamp = jiffies; + + discovery->data.hints[0] = discovery_info[0]; + if (discovery_info[0] & HINT_EXTENSION) { + discovery->data.hints[1] = discovery_info[1]; + discovery->data.charset = discovery_info[2]; + text = (char *) &discovery_info[3]; + } else { + discovery->data.hints[1] = 0; + discovery->data.charset = discovery_info[1]; + text = (char *) &discovery_info[2]; + } + /* + * Terminate string, should be safe since this is where the + * FCS bytes resides. + */ + skb->data[skb->len] = '\0'; + strncpy(discovery->data.info, text, NICKNAME_MAX_LEN); + discovery->name_len = strlen(discovery->data.info); + + info->discovery = discovery; + } else + info->discovery = NULL; + + irlap_do_event(self, RECV_DISCOVERY_XID_CMD, skb, info); +} + +/* + * Function irlap_send_rr_frame (self, command) + * + * Build and transmit RR (Receive Ready) frame. Notice that it is currently + * only possible to send RR frames with the poll bit set. + */ +void irlap_send_rr_frame(struct irlap_cb *self, int command) +{ + struct sk_buff *tx_skb; + __u8 *frame; + + tx_skb = dev_alloc_skb(16); + if (!tx_skb) + return; + + frame = skb_put(tx_skb, 2); + + frame[0] = self->caddr; + frame[0] |= (command) ? CMD_FRAME : 0; + + frame[1] = RR | PF_BIT | (self->vr << 5); + + irlap_queue_xmit(self, tx_skb); +} + +/* + * Function irlap_send_rd_frame (self) + * + * Request disconnect. Used by a secondary station to request the + * disconnection of the link. + */ +void irlap_send_rd_frame(struct irlap_cb *self) +{ + struct sk_buff *tx_skb; + __u8 *frame; + + tx_skb = dev_alloc_skb(16); + if (!tx_skb) + return; + + frame = skb_put(tx_skb, 2); + + frame[0] = self->caddr; + frame[1] = RD_RSP | PF_BIT; + + irlap_queue_xmit(self, tx_skb); +} + +/* + * Function irlap_recv_rr_frame (skb, info) + * + * Received RR (Receive Ready) frame from peer station, no harm in + * making it inline since its called only from one single place + * (irlap_driver_rcv). + */ +static inline void irlap_recv_rr_frame(struct irlap_cb *self, + struct sk_buff *skb, + struct irlap_info *info, int command) +{ + info->nr = skb->data[1] >> 5; + + /* Check if this is a command or a response frame */ + if (command) + irlap_do_event(self, RECV_RR_CMD, skb, info); + else + irlap_do_event(self, RECV_RR_RSP, skb, info); +} + +/* + * Function irlap_recv_rnr_frame (self, skb, info) + * + * Received RNR (Receive Not Ready) frame from peer station + * + */ +static void irlap_recv_rnr_frame(struct irlap_cb *self, struct sk_buff *skb, + struct irlap_info *info, int command) +{ + info->nr = skb->data[1] >> 5; + + IRDA_DEBUG(4, "%s(), nr=%d, %ld\n", __FUNCTION__, info->nr, jiffies); + + if (command) + irlap_do_event(self, RECV_RNR_CMD, skb, info); + else + irlap_do_event(self, RECV_RNR_RSP, skb, info); +} + +static void irlap_recv_rej_frame(struct irlap_cb *self, struct sk_buff *skb, + struct irlap_info *info, int command) +{ + IRDA_DEBUG(0, "%s()\n", __FUNCTION__); + + info->nr = skb->data[1] >> 5; + + /* Check if this is a command or a response frame */ + if (command) + irlap_do_event(self, RECV_REJ_CMD, skb, info); + else + irlap_do_event(self, RECV_REJ_RSP, skb, info); +} + +static void irlap_recv_srej_frame(struct irlap_cb *self, struct sk_buff *skb, + struct irlap_info *info, int command) +{ + IRDA_DEBUG(0, "%s()\n", __FUNCTION__); + + info->nr = skb->data[1] >> 5; + + /* Check if this is a command or a response frame */ + if (command) + irlap_do_event(self, RECV_SREJ_CMD, skb, info); + else + irlap_do_event(self, RECV_SREJ_RSP, skb, info); +} + +static void irlap_recv_disc_frame(struct irlap_cb *self, struct sk_buff *skb, + struct irlap_info *info, int command) +{ + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + /* Check if this is a command or a response frame */ + if (command) + irlap_do_event(self, RECV_DISC_CMD, skb, info); + else + irlap_do_event(self, RECV_RD_RSP, skb, info); +} + +/* + * Function irlap_recv_ua_frame (skb, frame) + * + * Received UA (Unnumbered Acknowledgement) frame + * + */ +static inline void irlap_recv_ua_frame(struct irlap_cb *self, + struct sk_buff *skb, + struct irlap_info *info) +{ + irlap_do_event(self, RECV_UA_RSP, skb, info); +} + +/* + * Function irlap_send_data_primary(self, skb) + * + * Send I-frames as the primary station but without the poll bit set + * + */ +void irlap_send_data_primary(struct irlap_cb *self, struct sk_buff *skb) +{ + struct sk_buff *tx_skb; + + if (skb->data[1] == I_FRAME) { + + /* + * Insert frame sequence number (Vs) in control field before + * inserting into transmit window queue. + */ + skb->data[1] = I_FRAME | (self->vs << 1); + + /* + * Insert frame in store, in case of retransmissions + * Increase skb reference count, see irlap_do_event() + */ + skb_get(skb); + skb_queue_tail(&self->wx_list, skb); + + /* Copy buffer */ + tx_skb = skb_clone(skb, GFP_ATOMIC); + if (tx_skb == NULL) { + return; + } + + self->vs = (self->vs + 1) % 8; + self->ack_required = FALSE; + self->window -= 1; + + irlap_send_i_frame( self, tx_skb, CMD_FRAME); + } else { + IRDA_DEBUG(4, "%s(), sending unreliable frame\n", __FUNCTION__); + irlap_send_ui_frame(self, skb_get(skb), self->caddr, CMD_FRAME); + self->window -= 1; + } +} +/* + * Function irlap_send_data_primary_poll (self, skb) + * + * Send I(nformation) frame as primary with poll bit set + */ +void irlap_send_data_primary_poll(struct irlap_cb *self, struct sk_buff *skb) +{ + struct sk_buff *tx_skb; + int transmission_time; + + /* Stop P timer */ + del_timer(&self->poll_timer); + + /* Is this reliable or unreliable data? */ + if (skb->data[1] == I_FRAME) { + + /* + * Insert frame sequence number (Vs) in control field before + * inserting into transmit window queue. + */ + skb->data[1] = I_FRAME | (self->vs << 1); + + /* + * Insert frame in store, in case of retransmissions + * Increase skb reference count, see irlap_do_event() + */ + skb_get(skb); + skb_queue_tail(&self->wx_list, skb); + + /* Copy buffer */ + tx_skb = skb_clone(skb, GFP_ATOMIC); + if (tx_skb == NULL) { + return; + } + + /* + * Set poll bit if necessary. We do this to the copied + * skb, since retransmitted need to set or clear the poll + * bit depending on when they are sent. + */ + tx_skb->data[1] |= PF_BIT; + + self->vs = (self->vs + 1) % 8; + self->ack_required = FALSE; + + irlap_send_i_frame(self, tx_skb, CMD_FRAME); + } else { + IRDA_DEBUG(4, "%s(), sending unreliable frame\n", __FUNCTION__); + + if (self->ack_required) { + irlap_send_ui_frame(self, skb_get(skb), self->caddr, CMD_FRAME); + irlap_send_rr_frame(self, CMD_FRAME); + self->ack_required = FALSE; + } else { + skb->data[1] |= PF_BIT; + irlap_send_ui_frame(self, skb_get(skb), self->caddr, CMD_FRAME); + } + } + + /* How much time we took for transmission of all frames. + * We don't know, so let assume we used the full window. Jean II */ + transmission_time = self->final_timeout; + + /* Reset parameter so that we can fill next window */ + self->window = self->window_size; + +#ifdef CONFIG_IRDA_DYNAMIC_WINDOW + /* Remove what we have not used. Just do a prorata of the + * bytes left in window to window capacity. + * See max_line_capacities[][] in qos.c for details. Jean II */ + transmission_time -= (self->final_timeout * self->bytes_left + / self->line_capacity); + IRDA_DEBUG(4, "%s() adjusting transmission_time : ft=%d, bl=%d, lc=%d -> tt=%d\n", __FUNCTION__, self->final_timeout, self->bytes_left, self->line_capacity, transmission_time); + + /* We are allowed to transmit a maximum number of bytes again. */ + self->bytes_left = self->line_capacity; +#endif /* CONFIG_IRDA_DYNAMIC_WINDOW */ + + /* + * The network layer has a intermediate buffer between IrLAP + * and the IrDA driver which can contain 8 frames. So, even + * though IrLAP is currently sending the *last* frame of the + * tx-window, the driver most likely has only just started + * sending the *first* frame of the same tx-window. + * I.e. we are always at the very begining of or Tx window. + * Now, we are supposed to set the final timer from the end + * of our tx-window to let the other peer reply. So, we need + * to add extra time to compensate for the fact that we + * are really at the start of tx-window, otherwise the final timer + * might expire before he can answer... + * Jean II + */ + irlap_start_final_timer(self, self->final_timeout + transmission_time); + + /* + * The clever amongst you might ask why we do this adjustement + * only here, and not in all the other cases in irlap_event.c. + * In all those other case, we only send a very short management + * frame (few bytes), so the adjustement would be lost in the + * noise... + * The exception of course is irlap_resend_rejected_frame(). + * Jean II */ +} + +/* + * Function irlap_send_data_secondary_final (self, skb) + * + * Send I(nformation) frame as secondary with final bit set + * + */ +void irlap_send_data_secondary_final(struct irlap_cb *self, + struct sk_buff *skb) +{ + struct sk_buff *tx_skb = NULL; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + IRDA_ASSERT(skb != NULL, return;); + + /* Is this reliable or unreliable data? */ + if (skb->data[1] == I_FRAME) { + + /* + * Insert frame sequence number (Vs) in control field before + * inserting into transmit window queue. + */ + skb->data[1] = I_FRAME | (self->vs << 1); + + /* + * Insert frame in store, in case of retransmissions + * Increase skb reference count, see irlap_do_event() + */ + skb_get(skb); + skb_queue_tail(&self->wx_list, skb); + + tx_skb = skb_clone(skb, GFP_ATOMIC); + if (tx_skb == NULL) { + return; + } + + tx_skb->data[1] |= PF_BIT; + + self->vs = (self->vs + 1) % 8; + self->ack_required = FALSE; + + irlap_send_i_frame(self, tx_skb, RSP_FRAME); + } else { + if (self->ack_required) { + irlap_send_ui_frame(self, skb_get(skb), self->caddr, RSP_FRAME); + irlap_send_rr_frame(self, RSP_FRAME); + self->ack_required = FALSE; + } else { + skb->data[1] |= PF_BIT; + irlap_send_ui_frame(self, skb_get(skb), self->caddr, RSP_FRAME); + } + } + + self->window = self->window_size; +#ifdef CONFIG_IRDA_DYNAMIC_WINDOW + /* We are allowed to transmit a maximum number of bytes again. */ + self->bytes_left = self->line_capacity; +#endif /* CONFIG_IRDA_DYNAMIC_WINDOW */ + + irlap_start_wd_timer(self, self->wd_timeout); +} + +/* + * Function irlap_send_data_secondary (self, skb) + * + * Send I(nformation) frame as secondary without final bit set + * + */ +void irlap_send_data_secondary(struct irlap_cb *self, struct sk_buff *skb) +{ + struct sk_buff *tx_skb = NULL; + + /* Is this reliable or unreliable data? */ + if (skb->data[1] == I_FRAME) { + + /* + * Insert frame sequence number (Vs) in control field before + * inserting into transmit window queue. + */ + skb->data[1] = I_FRAME | (self->vs << 1); + + /* + * Insert frame in store, in case of retransmissions + * Increase skb reference count, see irlap_do_event() + */ + skb_get(skb); + skb_queue_tail(&self->wx_list, skb); + + tx_skb = skb_clone(skb, GFP_ATOMIC); + if (tx_skb == NULL) { + return; + } + + self->vs = (self->vs + 1) % 8; + self->ack_required = FALSE; + self->window -= 1; + + irlap_send_i_frame(self, tx_skb, RSP_FRAME); + } else { + irlap_send_ui_frame(self, skb_get(skb), self->caddr, RSP_FRAME); + self->window -= 1; + } +} + +/* + * Function irlap_resend_rejected_frames (nr) + * + * Resend frames which has not been acknowledged. Should be safe to + * traverse the list without locking it since this function will only be + * called from interrupt context (BH) + */ +void irlap_resend_rejected_frames(struct irlap_cb *self, int command) +{ + struct sk_buff *tx_skb; + struct sk_buff *skb; + int count; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + /* Initialize variables */ + count = skb_queue_len(&self->wx_list); + + /* Resend unacknowledged frame(s) */ + skb = skb_peek(&self->wx_list); + while (skb != NULL) { + irlap_wait_min_turn_around(self, &self->qos_tx); + + /* We copy the skb to be retransmitted since we will have to + * modify it. Cloning will confuse packet sniffers + */ + /* tx_skb = skb_clone( skb, GFP_ATOMIC); */ + tx_skb = skb_copy(skb, GFP_ATOMIC); + if (!tx_skb) { + IRDA_DEBUG(0, "%s(), unable to copy\n", __FUNCTION__); + return; + } + /* Unlink tx_skb from list */ + tx_skb->next = tx_skb->prev = NULL; + tx_skb->list = NULL; + + /* Clear old Nr field + poll bit */ + tx_skb->data[1] &= 0x0f; + + /* + * Set poll bit on the last frame retransmitted + */ + if (count-- == 1) + tx_skb->data[1] |= PF_BIT; /* Set p/f bit */ + else + tx_skb->data[1] &= ~PF_BIT; /* Clear p/f bit */ + + irlap_send_i_frame(self, tx_skb, command); + + /* + * If our skb is the last buffer in the list, then + * we are finished, if not, move to the next sk-buffer + */ + if (skb == skb_peek_tail(&self->wx_list)) + skb = NULL; + else + skb = skb->next; + } +#if 0 /* Not yet */ + /* + * We can now fill the window with additional data frames + */ + while (skb_queue_len( &self->txq) > 0) { + + IRDA_DEBUG(0, "%s(), sending additional frames!\n", __FUNCTION__); + if ((skb_queue_len( &self->txq) > 0) && + (self->window > 0)) { + skb = skb_dequeue( &self->txq); + IRDA_ASSERT(skb != NULL, return;); + + /* + * If send window > 1 then send frame with pf + * bit cleared + */ + if ((self->window > 1) && + skb_queue_len(&self->txq) > 0) + { + irlap_send_data_primary(self, skb); + } else { + irlap_send_data_primary_poll(self, skb); + } + kfree_skb(skb); + } + } +#endif +} + +void irlap_resend_rejected_frame(struct irlap_cb *self, int command) +{ + struct sk_buff *tx_skb; + struct sk_buff *skb; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + + /* Resend unacknowledged frame(s) */ + skb = skb_peek(&self->wx_list); + if (skb != NULL) { + irlap_wait_min_turn_around(self, &self->qos_tx); + + /* We copy the skb to be retransmitted since we will have to + * modify it. Cloning will confuse packet sniffers + */ + /* tx_skb = skb_clone( skb, GFP_ATOMIC); */ + tx_skb = skb_copy(skb, GFP_ATOMIC); + if (!tx_skb) { + IRDA_DEBUG(0, "%s(), unable to copy\n", __FUNCTION__); + return; + } + /* Unlink tx_skb from list */ + tx_skb->next = tx_skb->prev = NULL; + tx_skb->list = NULL; + + /* Clear old Nr field + poll bit */ + tx_skb->data[1] &= 0x0f; + + /* Set poll/final bit */ + tx_skb->data[1] |= PF_BIT; /* Set p/f bit */ + + irlap_send_i_frame(self, tx_skb, command); + } +} + +/* + * Function irlap_send_ui_frame (self, skb, command) + * + * Contruct and transmit an Unnumbered Information (UI) frame + * + */ +void irlap_send_ui_frame(struct irlap_cb *self, struct sk_buff *skb, + __u8 caddr, int command) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + IRDA_ASSERT(skb != NULL, return;); + + /* Insert connection address */ + skb->data[0] = caddr | ((command) ? CMD_FRAME : 0); + + irlap_queue_xmit(self, skb); +} + +/* + * Function irlap_send_i_frame (skb) + * + * Contruct and transmit Information (I) frame + */ +static void irlap_send_i_frame(struct irlap_cb *self, struct sk_buff *skb, + int command) +{ + /* Insert connection address */ + skb->data[0] = self->caddr; + skb->data[0] |= (command) ? CMD_FRAME : 0; + + /* Insert next to receive (Vr) */ + skb->data[1] |= (self->vr << 5); /* insert nr */ + + irlap_queue_xmit(self, skb); +} + +/* + * Function irlap_recv_i_frame (skb, frame) + * + * Receive and parse an I (Information) frame, no harm in making it inline + * since it's called only from one single place (irlap_driver_rcv). + */ +static inline void irlap_recv_i_frame(struct irlap_cb *self, + struct sk_buff *skb, + struct irlap_info *info, int command) +{ + info->nr = skb->data[1] >> 5; /* Next to receive */ + info->pf = skb->data[1] & PF_BIT; /* Final bit */ + info->ns = (skb->data[1] >> 1) & 0x07; /* Next to send */ + + /* Check if this is a command or a response frame */ + if (command) + irlap_do_event(self, RECV_I_CMD, skb, info); + else + irlap_do_event(self, RECV_I_RSP, skb, info); +} + +/* + * Function irlap_recv_ui_frame (self, skb, info) + * + * Receive and parse an Unnumbered Information (UI) frame + * + */ +static void irlap_recv_ui_frame(struct irlap_cb *self, struct sk_buff *skb, + struct irlap_info *info) +{ + IRDA_DEBUG( 4, "%s()\n", __FUNCTION__); + + info->pf = skb->data[1] & PF_BIT; /* Final bit */ + + irlap_do_event(self, RECV_UI_FRAME, skb, info); +} + +/* + * Function irlap_recv_frmr_frame (skb, frame) + * + * Received Frame Reject response. + * + */ +static void irlap_recv_frmr_frame(struct irlap_cb *self, struct sk_buff *skb, + struct irlap_info *info) +{ + __u8 *frame; + int w, x, y, z; + + IRDA_DEBUG(0, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LAP_MAGIC, return;); + IRDA_ASSERT(skb != NULL, return;); + IRDA_ASSERT(info != NULL, return;); + + if (!pskb_may_pull(skb, 4)) { + IRDA_ERROR("%s: frame to short!\n", __FUNCTION__); + return; + } + + frame = skb->data; + + info->nr = frame[2] >> 5; /* Next to receive */ + info->pf = frame[2] & PF_BIT; /* Final bit */ + info->ns = (frame[2] >> 1) & 0x07; /* Next to send */ + + w = frame[3] & 0x01; + x = frame[3] & 0x02; + y = frame[3] & 0x04; + z = frame[3] & 0x08; + + if (w) { + IRDA_DEBUG(0, "Rejected control field is undefined or not " + "implemented.\n"); + } + if (x) { + IRDA_DEBUG(0, "Rejected control field was invalid because it " + "contained a non permitted I field.\n"); + } + if (y) { + IRDA_DEBUG(0, "Received I field exceeded the maximum negotiated " + "for the existing connection or exceeded the maximum " + "this station supports if no connection exists.\n"); + } + if (z) { + IRDA_DEBUG(0, "Rejected control field control field contained an " + "invalid Nr count.\n"); + } + irlap_do_event(self, RECV_FRMR_RSP, skb, info); +} + +/* + * Function irlap_send_test_frame (self, daddr) + * + * Send a test frame response + * + */ +void irlap_send_test_frame(struct irlap_cb *self, __u8 caddr, __u32 daddr, + struct sk_buff *cmd) +{ + struct sk_buff *tx_skb; + struct test_frame *frame; + __u8 *info; + + tx_skb = dev_alloc_skb(cmd->len+sizeof(struct test_frame)); + if (!tx_skb) + return; + + /* Broadcast frames must include saddr and daddr fields */ + if (caddr == CBROADCAST) { + frame = (struct test_frame *) + skb_put(tx_skb, sizeof(struct test_frame)); + + /* Insert the swapped addresses */ + frame->saddr = cpu_to_le32(self->saddr); + frame->daddr = cpu_to_le32(daddr); + } else + frame = (struct test_frame *) skb_put(tx_skb, LAP_ADDR_HEADER + LAP_CTRL_HEADER); + + frame->caddr = caddr; + frame->control = TEST_RSP | PF_BIT; + + /* Copy info */ + info = skb_put(tx_skb, cmd->len); + memcpy(info, cmd->data, cmd->len); + + /* Return to sender */ + irlap_wait_min_turn_around(self, &self->qos_tx); + irlap_queue_xmit(self, tx_skb); +} + +/* + * Function irlap_recv_test_frame (self, skb) + * + * Receive a test frame + * + */ +static void irlap_recv_test_frame(struct irlap_cb *self, struct sk_buff *skb, + struct irlap_info *info, int command) +{ + struct test_frame *frame; + + IRDA_DEBUG(2, "%s()\n", __FUNCTION__); + + if (!pskb_may_pull(skb, sizeof(*frame))) { + IRDA_ERROR("%s: frame to short!\n", __FUNCTION__); + return; + } + frame = (struct test_frame *) skb->data; + + /* Broadcast frames must carry saddr and daddr fields */ + if (info->caddr == CBROADCAST) { + if (skb->len < sizeof(struct test_frame)) { + IRDA_DEBUG(0, "%s() test frame to short!\n", + __FUNCTION__); + return; + } + + /* Read and swap addresses */ + info->daddr = le32_to_cpu(frame->saddr); + info->saddr = le32_to_cpu(frame->daddr); + + /* Make sure frame is addressed to us */ + if ((info->saddr != self->saddr) && + (info->saddr != BROADCAST)) { + return; + } + } + + if (command) + irlap_do_event(self, RECV_TEST_CMD, skb, info); + else + irlap_do_event(self, RECV_TEST_RSP, skb, info); +} + +/* + * Function irlap_driver_rcv (skb, netdev, ptype) + * + * Called when a frame is received. Dispatches the right receive function + * for processing of the frame. + * + * Note on skb management : + * After calling the higher layers of the IrDA stack, we always + * kfree() the skb, which drop the reference count (and potentially + * destroy it). + * If a higher layer of the stack want to keep the skb around (to put + * in a queue or pass it to the higher layer), it will need to use + * skb_get() to keep a reference on it. This is usually done at the + * LMP level in irlmp.c. + * Jean II + */ +int irlap_driver_rcv(struct sk_buff *skb, struct net_device *dev, + struct packet_type *ptype) +{ + struct irlap_info info; + struct irlap_cb *self; + int command; + __u8 control; + + /* FIXME: should we get our own field? */ + self = (struct irlap_cb *) dev->atalk_ptr; + + /* If the net device is down, then IrLAP is gone! */ + if (!self || self->magic != LAP_MAGIC) { + dev_kfree_skb(skb); + return -1; + } + + /* We are no longer an "old" protocol, so we need to handle + * share and non linear skbs. This should never happen, so + * we don't need to be clever about it. Jean II */ + if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) { + IRDA_ERROR("%s: can't clone shared skb!\n", __FUNCTION__); + dev_kfree_skb(skb); + return -1; + } + + /* Check if frame is large enough for parsing */ + if (!pskb_may_pull(skb, 2)) { + IRDA_ERROR("%s: frame to short!\n", __FUNCTION__); + dev_kfree_skb(skb); + return -1; + } + + command = skb->data[0] & CMD_FRAME; + info.caddr = skb->data[0] & CBROADCAST; + + info.pf = skb->data[1] & PF_BIT; + info.control = skb->data[1] & ~PF_BIT; /* Mask away poll/final bit */ + + control = info.control; + + /* First we check if this frame has a valid connection address */ + if ((info.caddr != self->caddr) && (info.caddr != CBROADCAST)) { + IRDA_DEBUG(0, "%s(), wrong connection address!\n", + __FUNCTION__); + goto out; + } + /* + * Optimize for the common case and check if the frame is an + * I(nformation) frame. Only I-frames have bit 0 set to 0 + */ + if (~control & 0x01) { + irlap_recv_i_frame(self, skb, &info, command); + goto out; + } + /* + * We now check is the frame is an S(upervisory) frame. Only + * S-frames have bit 0 set to 1 and bit 1 set to 0 + */ + if (~control & 0x02) { + /* + * Received S(upervisory) frame, check which frame type it is + * only the first nibble is of interest + */ + switch (control & 0x0f) { + case RR: + irlap_recv_rr_frame(self, skb, &info, command); + break; + case RNR: + irlap_recv_rnr_frame(self, skb, &info, command); + break; + case REJ: + irlap_recv_rej_frame(self, skb, &info, command); + break; + case SREJ: + irlap_recv_srej_frame(self, skb, &info, command); + break; + default: + IRDA_WARNING("%s: Unknown S-frame %02x received!\n", + __FUNCTION__, info.control); + break; + } + goto out; + } + /* + * This must be a C(ontrol) frame + */ + switch (control) { + case XID_RSP: + irlap_recv_discovery_xid_rsp(self, skb, &info); + break; + case XID_CMD: + irlap_recv_discovery_xid_cmd(self, skb, &info); + break; + case SNRM_CMD: + irlap_recv_snrm_cmd(self, skb, &info); + break; + case DM_RSP: + irlap_do_event(self, RECV_DM_RSP, skb, &info); + break; + case DISC_CMD: /* And RD_RSP since they have the same value */ + irlap_recv_disc_frame(self, skb, &info, command); + break; + case TEST_CMD: + irlap_recv_test_frame(self, skb, &info, command); + break; + case UA_RSP: + irlap_recv_ua_frame(self, skb, &info); + break; + case FRMR_RSP: + irlap_recv_frmr_frame(self, skb, &info); + break; + case UI_FRAME: + irlap_recv_ui_frame(self, skb, &info); + break; + default: + IRDA_WARNING("%s: Unknown frame %02x received!\n", + __FUNCTION__, info.control); + break; + } +out: + /* Always drop our reference on the skb */ + dev_kfree_skb(skb); + return 0; +} diff --git a/net/irda/irlmp.c b/net/irda/irlmp.c new file mode 100644 index 000000000000..7a4a4d7fbe66 --- /dev/null +++ b/net/irda/irlmp.c @@ -0,0 +1,2041 @@ +/********************************************************************* + * + * Filename: irlmp.c + * Version: 1.0 + * Description: IrDA Link Management Protocol (LMP) layer + * Status: Stable. + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Sun Aug 17 20:54:32 1997 + * Modified at: Wed Jan 5 11:26:03 2000 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1998-2000 Dag Brattli <dagb@cs.uit.no>, + * All Rights Reserved. + * Copyright (c) 2000-2003 Jean Tourrilhes <jt@hpl.hp.com> + * + * 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. + * + * Neither Dag Brattli nor University of Tromsų admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + ********************************************************************/ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/skbuff.h> +#include <linux/types.h> +#include <linux/proc_fs.h> +#include <linux/init.h> +#include <linux/kmod.h> +#include <linux/random.h> +#include <linux/seq_file.h> + +#include <net/irda/irda.h> +#include <net/irda/timer.h> +#include <net/irda/qos.h> +#include <net/irda/irlap.h> +#include <net/irda/iriap.h> +#include <net/irda/irlmp.h> +#include <net/irda/irlmp_frame.h> + +static __u8 irlmp_find_free_slsap(void); +static int irlmp_slsap_inuse(__u8 slsap_sel); + +/* Master structure */ +struct irlmp_cb *irlmp = NULL; + +/* These can be altered by the sysctl interface */ +int sysctl_discovery = 0; +int sysctl_discovery_timeout = 3; /* 3 seconds by default */ +EXPORT_SYMBOL(sysctl_discovery_timeout); +int sysctl_discovery_slots = 6; /* 6 slots by default */ +int sysctl_lap_keepalive_time = LM_IDLE_TIMEOUT * 1000 / HZ; +char sysctl_devname[65]; + +const char *irlmp_reasons[] = { + "ERROR, NOT USED", + "LM_USER_REQUEST", + "LM_LAP_DISCONNECT", + "LM_CONNECT_FAILURE", + "LM_LAP_RESET", + "LM_INIT_DISCONNECT", + "ERROR, NOT USED", +}; +EXPORT_SYMBOL(irlmp_reasons); + +/* + * Function irlmp_init (void) + * + * Create (allocate) the main IrLMP structure + * + */ +int __init irlmp_init(void) +{ + IRDA_DEBUG(1, "%s()\n", __FUNCTION__); + /* Initialize the irlmp structure. */ + irlmp = kmalloc( sizeof(struct irlmp_cb), GFP_KERNEL); + if (irlmp == NULL) + return -ENOMEM; + memset(irlmp, 0, sizeof(struct irlmp_cb)); + + irlmp->magic = LMP_MAGIC; + + irlmp->clients = hashbin_new(HB_LOCK); + irlmp->services = hashbin_new(HB_LOCK); + irlmp->links = hashbin_new(HB_LOCK); + irlmp->unconnected_lsaps = hashbin_new(HB_LOCK); + irlmp->cachelog = hashbin_new(HB_NOLOCK); + + if ((irlmp->clients == NULL) || + (irlmp->services == NULL) || + (irlmp->links == NULL) || + (irlmp->unconnected_lsaps == NULL) || + (irlmp->cachelog == NULL)) { + return -ENOMEM; + } + + spin_lock_init(&irlmp->cachelog->hb_spinlock); + + irlmp->last_lsap_sel = 0x0f; /* Reserved 0x00-0x0f */ + strcpy(sysctl_devname, "Linux"); + + /* Do discovery every 3 seconds */ + init_timer(&irlmp->discovery_timer); + irlmp_start_discovery_timer(irlmp, sysctl_discovery_timeout*HZ); + + return 0; +} + +/* + * Function irlmp_cleanup (void) + * + * Remove IrLMP layer + * + */ +void __exit irlmp_cleanup(void) +{ + /* Check for main structure */ + IRDA_ASSERT(irlmp != NULL, return;); + IRDA_ASSERT(irlmp->magic == LMP_MAGIC, return;); + + del_timer(&irlmp->discovery_timer); + + hashbin_delete(irlmp->links, (FREE_FUNC) kfree); + hashbin_delete(irlmp->unconnected_lsaps, (FREE_FUNC) kfree); + hashbin_delete(irlmp->clients, (FREE_FUNC) kfree); + hashbin_delete(irlmp->services, (FREE_FUNC) kfree); + hashbin_delete(irlmp->cachelog, (FREE_FUNC) kfree); + + /* De-allocate main structure */ + kfree(irlmp); + irlmp = NULL; +} + +/* + * Function irlmp_open_lsap (slsap, notify) + * + * Register with IrLMP and create a local LSAP, + * returns handle to LSAP. + */ +struct lsap_cb *irlmp_open_lsap(__u8 slsap_sel, notify_t *notify, __u8 pid) +{ + struct lsap_cb *self; + + IRDA_ASSERT(notify != NULL, return NULL;); + IRDA_ASSERT(irlmp != NULL, return NULL;); + IRDA_ASSERT(irlmp->magic == LMP_MAGIC, return NULL;); + IRDA_ASSERT(notify->instance != NULL, return NULL;); + + /* Does the client care which Source LSAP selector it gets? */ + if (slsap_sel == LSAP_ANY) { + slsap_sel = irlmp_find_free_slsap(); + if (!slsap_sel) + return NULL; + } else if (irlmp_slsap_inuse(slsap_sel)) + return NULL; + + /* Allocate new instance of a LSAP connection */ + self = kmalloc(sizeof(struct lsap_cb), GFP_ATOMIC); + if (self == NULL) { + IRDA_ERROR("%s: can't allocate memory\n", __FUNCTION__); + return NULL; + } + memset(self, 0, sizeof(struct lsap_cb)); + + self->magic = LMP_LSAP_MAGIC; + self->slsap_sel = slsap_sel; + + /* Fix connectionless LSAP's */ + if (slsap_sel == LSAP_CONNLESS) { +#ifdef CONFIG_IRDA_ULTRA + self->dlsap_sel = LSAP_CONNLESS; + self->pid = pid; +#endif /* CONFIG_IRDA_ULTRA */ + } else + self->dlsap_sel = LSAP_ANY; + /* self->connected = FALSE; -> already NULL via memset() */ + + init_timer(&self->watchdog_timer); + + self->notify = *notify; + + self->lsap_state = LSAP_DISCONNECTED; + + /* Insert into queue of unconnected LSAPs */ + hashbin_insert(irlmp->unconnected_lsaps, (irda_queue_t *) self, + (long) self, NULL); + + return self; +} +EXPORT_SYMBOL(irlmp_open_lsap); + +/* + * Function __irlmp_close_lsap (self) + * + * Remove an instance of LSAP + */ +static void __irlmp_close_lsap(struct lsap_cb *self) +{ + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LMP_LSAP_MAGIC, return;); + + /* + * Set some of the variables to preset values + */ + self->magic = 0; + del_timer(&self->watchdog_timer); /* Important! */ + + if (self->conn_skb) + dev_kfree_skb(self->conn_skb); + + kfree(self); +} + +/* + * Function irlmp_close_lsap (self) + * + * Close and remove LSAP + * + */ +void irlmp_close_lsap(struct lsap_cb *self) +{ + struct lap_cb *lap; + struct lsap_cb *lsap = NULL; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LMP_LSAP_MAGIC, return;); + + /* + * Find out if we should remove this LSAP from a link or from the + * list of unconnected lsaps (not associated with a link) + */ + lap = self->lap; + if (lap) { + IRDA_ASSERT(lap->magic == LMP_LAP_MAGIC, return;); + /* We might close a LSAP before it has completed the + * connection setup. In those case, higher layers won't + * send a proper disconnect request. Harmless, except + * that we will forget to close LAP... - Jean II */ + if(self->lsap_state != LSAP_DISCONNECTED) { + self->lsap_state = LSAP_DISCONNECTED; + irlmp_do_lap_event(self->lap, + LM_LAP_DISCONNECT_REQUEST, NULL); + } + /* Now, remove from the link */ + lsap = hashbin_remove(lap->lsaps, (long) self, NULL); +#ifdef CONFIG_IRDA_CACHE_LAST_LSAP + lap->cache.valid = FALSE; +#endif + } + self->lap = NULL; + /* Check if we found the LSAP! If not then try the unconnected lsaps */ + if (!lsap) { + lsap = hashbin_remove(irlmp->unconnected_lsaps, (long) self, + NULL); + } + if (!lsap) { + IRDA_DEBUG(0, + "%s(), Looks like somebody has removed me already!\n", + __FUNCTION__); + return; + } + __irlmp_close_lsap(self); +} +EXPORT_SYMBOL(irlmp_close_lsap); + +/* + * Function irlmp_register_irlap (saddr, notify) + * + * Register IrLAP layer with IrLMP. There is possible to have multiple + * instances of the IrLAP layer, each connected to different IrDA ports + * + */ +void irlmp_register_link(struct irlap_cb *irlap, __u32 saddr, notify_t *notify) +{ + struct lap_cb *lap; + + IRDA_ASSERT(irlmp != NULL, return;); + IRDA_ASSERT(irlmp->magic == LMP_MAGIC, return;); + IRDA_ASSERT(notify != NULL, return;); + + /* + * Allocate new instance of a LSAP connection + */ + lap = kmalloc(sizeof(struct lap_cb), GFP_KERNEL); + if (lap == NULL) { + IRDA_ERROR("%s: unable to kmalloc\n", __FUNCTION__); + return; + } + memset(lap, 0, sizeof(struct lap_cb)); + + lap->irlap = irlap; + lap->magic = LMP_LAP_MAGIC; + lap->saddr = saddr; + lap->daddr = DEV_ADDR_ANY; +#ifdef CONFIG_IRDA_CACHE_LAST_LSAP + lap->cache.valid = FALSE; +#endif + lap->lsaps = hashbin_new(HB_LOCK); + if (lap->lsaps == NULL) { + IRDA_WARNING("%s(), unable to kmalloc lsaps\n", __FUNCTION__); + kfree(lap); + return; + } + + lap->lap_state = LAP_STANDBY; + + init_timer(&lap->idle_timer); + + /* + * Insert into queue of LMP links + */ + hashbin_insert(irlmp->links, (irda_queue_t *) lap, lap->saddr, NULL); + + /* + * We set only this variable so IrLAP can tell us on which link the + * different events happened on + */ + irda_notify_init(notify); + notify->instance = lap; +} + +/* + * Function irlmp_unregister_irlap (saddr) + * + * IrLAP layer has been removed! + * + */ +void irlmp_unregister_link(__u32 saddr) +{ + struct lap_cb *link; + + IRDA_DEBUG(4, "%s()\n", __FUNCTION__); + + /* We must remove ourselves from the hashbin *first*. This ensure + * that no more LSAPs will be open on this link and no discovery + * will be triggered anymore. Jean II */ + link = hashbin_remove(irlmp->links, saddr, NULL); + if (link) { + IRDA_ASSERT(link->magic == LMP_LAP_MAGIC, return;); + + /* Kill all the LSAPs on this link. Jean II */ + link->reason = LAP_DISC_INDICATION; + link->daddr = DEV_ADDR_ANY; + irlmp_do_lap_event(link, LM_LAP_DISCONNECT_INDICATION, NULL); + + /* Remove all discoveries discovered at this link */ + irlmp_expire_discoveries(irlmp->cachelog, link->saddr, TRUE); + + /* Final cleanup */ + del_timer(&link->idle_timer); + link->magic = 0; + kfree(link); + } +} + +/* + * Function irlmp_connect_request (handle, dlsap, userdata) + * + * Connect with a peer LSAP + * + */ +int irlmp_connect_request(struct lsap_cb *self, __u8 dlsap_sel, + __u32 saddr, __u32 daddr, + struct qos_info *qos, struct sk_buff *userdata) +{ + struct sk_buff *tx_skb = userdata; + struct lap_cb *lap; + struct lsap_cb *lsap; + int ret; + + IRDA_ASSERT(self != NULL, return -EBADR;); + IRDA_ASSERT(self->magic == LMP_LSAP_MAGIC, return -EBADR;); + + IRDA_DEBUG(2, + "%s(), slsap_sel=%02x, dlsap_sel=%02x, saddr=%08x, daddr=%08x\n", + __FUNCTION__, self->slsap_sel, dlsap_sel, saddr, daddr); + + if (test_bit(0, &self->connected)) { + ret = -EISCONN; + goto err; + } + + /* Client must supply destination device address */ + if (!daddr) { + ret = -EINVAL; + goto err; + } + + /* Any userdata? */ + if (tx_skb == NULL) { + tx_skb = dev_alloc_skb(64); + if (!tx_skb) + return -ENOMEM; + + skb_reserve(tx_skb, LMP_MAX_HEADER); + } + + /* Make room for MUX control header (3 bytes) */ + IRDA_ASSERT(skb_headroom(tx_skb) >= LMP_CONTROL_HEADER, return -1;); + skb_push(tx_skb, LMP_CONTROL_HEADER); + + self->dlsap_sel = dlsap_sel; + + /* + * Find the link to where we should try to connect since there may + * be more than one IrDA port on this machine. If the client has + * passed us the saddr (and already knows which link to use), then + * we use that to find the link, if not then we have to look in the + * discovery log and check if any of the links has discovered a + * device with the given daddr + */ + if ((!saddr) || (saddr == DEV_ADDR_ANY)) { + discovery_t *discovery; + unsigned long flags; + + spin_lock_irqsave(&irlmp->cachelog->hb_spinlock, flags); + if (daddr != DEV_ADDR_ANY) + discovery = hashbin_find(irlmp->cachelog, daddr, NULL); + else { + IRDA_DEBUG(2, "%s(), no daddr\n", __FUNCTION__); + discovery = (discovery_t *) + hashbin_get_first(irlmp->cachelog); + } + + if (discovery) { + saddr = discovery->data.saddr; + daddr = discovery->data.daddr; + } + spin_unlock_irqrestore(&irlmp->cachelog->hb_spinlock, flags); + } + lap = hashbin_lock_find(irlmp->links, saddr, NULL); + if (lap == NULL) { + IRDA_DEBUG(1, "%s(), Unable to find a usable link!\n", __FUNCTION__); + ret = -EHOSTUNREACH; + goto err; + } + + /* Check if LAP is disconnected or already connected */ + if (lap->daddr == DEV_ADDR_ANY) + lap->daddr = daddr; + else if (lap->daddr != daddr) { + /* Check if some LSAPs are active on this LAP */ + if (HASHBIN_GET_SIZE(lap->lsaps) == 0) { + /* No active connection, but LAP hasn't been + * disconnected yet (waiting for timeout in LAP). + * Maybe we could give LAP a bit of help in this case. + */ + IRDA_DEBUG(0, "%s(), sorry, but I'm waiting for LAP to timeout!\n", __FUNCTION__); + ret = -EAGAIN; + goto err; + } + + /* LAP is already connected to a different node, and LAP + * can only talk to one node at a time */ + IRDA_DEBUG(0, "%s(), sorry, but link is busy!\n", __FUNCTION__); + ret = -EBUSY; + goto err; + } + + self->lap = lap; + + /* + * Remove LSAP from list of unconnected LSAPs and insert it into the + * list of connected LSAPs for the particular link + */ + lsap = hashbin_remove(irlmp->unconnected_lsaps, (long) self, NULL); + + IRDA_ASSERT(lsap != NULL, return -1;); + IRDA_ASSERT(lsap->magic == LMP_LSAP_MAGIC, return -1;); + IRDA_ASSERT(lsap->lap != NULL, return -1;); + IRDA_ASSERT(lsap->lap->magic == LMP_LAP_MAGIC, return -1;); + + hashbin_insert(self->lap->lsaps, (irda_queue_t *) self, (long) self, + NULL); + + set_bit(0, &self->connected); /* TRUE */ + + /* + * User supplied qos specifications? + */ + if (qos) + self->qos = *qos; + + irlmp_do_lsap_event(self, LM_CONNECT_REQUEST, tx_skb); + + /* Drop reference count - see irlap_data_request(). */ + dev_kfree_skb(tx_skb); + + return 0; + +err: + /* Cleanup */ + if(tx_skb) + dev_kfree_skb(tx_skb); + return ret; +} +EXPORT_SYMBOL(irlmp_connect_request); + +/* + * Function irlmp_connect_indication (self) + * + * Incoming connection + * + */ +void irlmp_connect_indication(struct lsap_cb *self, struct sk_buff *skb) +{ |