diff options
Diffstat (limited to 'kernel/net/irda/irlmp.c')
-rw-r--r-- | kernel/net/irda/irlmp.c | 1996 |
1 files changed, 1996 insertions, 0 deletions
diff --git a/kernel/net/irda/irlmp.c b/kernel/net/irda/irlmp.c new file mode 100644 index 000000000..a26c401ef --- /dev/null +++ b/kernel/net/irda/irlmp.c @@ -0,0 +1,1996 @@ +/********************************************************************* + * + * 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/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> + +#include <asm/unaligned.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 */ +int sysctl_discovery_slots = 6; /* 6 slots by default */ +int sysctl_lap_keepalive_time = LM_IDLE_TIMEOUT * 1000 / HZ; +char sysctl_devname[65]; + +static 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", + "UNKNOWN", +}; + +const char *irlmp_reason_str(LM_REASON reason) +{ + reason = min_t(size_t, reason, ARRAY_SIZE(irlmp_reasons) - 1); + return irlmp_reasons[reason]; +} + +/* + * Function irlmp_init (void) + * + * Create (allocate) the main IrLMP structure + * + */ +int __init irlmp_init(void) +{ + /* Initialize the irlmp structure. */ + irlmp = kzalloc( sizeof(struct irlmp_cb), GFP_KERNEL); + if (irlmp == NULL) + return -ENOMEM; + + 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"); + + init_timer(&irlmp->discovery_timer); + + /* Do discovery every 3 seconds, conditionally */ + if (sysctl_discovery) + irlmp_start_discovery_timer(irlmp, + sysctl_discovery_timeout*HZ); + + return 0; +} + +/* + * Function irlmp_cleanup (void) + * + * Remove IrLMP layer + * + */ +void 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 = kzalloc(sizeof(struct lsap_cb), GFP_ATOMIC); + if (self == NULL) + return NULL; + + 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_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) { + pr_debug("%s(), Looks like somebody has removed me already!\n", + __func__); + 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 = kzalloc(sizeof(struct lap_cb), GFP_KERNEL); + if (lap == NULL) + return; + + 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) { + net_warn_ratelimited("%s(), unable to kmalloc lsaps\n", + __func__); + 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; + + /* 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; + hashbin_delete(link->lsaps, (FREE_FUNC) __irlmp_close_lsap); + 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;); + + pr_debug("%s(), slsap_sel=%02x, dlsap_sel=%02x, saddr=%08x, daddr=%08x\n", + __func__, 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 = alloc_skb(LMP_MAX_HEADER, GFP_ATOMIC); + 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 { + pr_debug("%s(), no daddr\n", __func__); + 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) { + pr_debug("%s(), Unable to find a usable link!\n", __func__); + 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. + */ + pr_debug("%s(), sorry, but I'm waiting for LAP to timeout!\n", + __func__); + ret = -EAGAIN; + goto err; + } + + /* LAP is already connected to a different node, and LAP + * can only talk to one node at a time */ + pr_debug("%s(), sorry, but link is busy!\n", __func__); + 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) +{ + int max_seg_size; + int lap_header_size; + int max_header_size; + + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LMP_LSAP_MAGIC, return;); + IRDA_ASSERT(skb != NULL, return;); + IRDA_ASSERT(self->lap != NULL, return;); + + pr_debug("%s(), slsap_sel=%02x, dlsap_sel=%02x\n", + __func__, self->slsap_sel, self->dlsap_sel); + + /* Note : self->lap is set in irlmp_link_data_indication(), + * (case CONNECT_CMD:) because we have no way to set it here. + * Similarly, self->dlsap_sel is usually set in irlmp_find_lsap(). + * Jean II */ + + self->qos = *self->lap->qos; + + max_seg_size = self->lap->qos->data_size.value-LMP_HEADER; + lap_header_size = IRLAP_GET_HEADER_SIZE(self->lap->irlap); + max_header_size = LMP_HEADER + lap_header_size; + + /* Hide LMP_CONTROL_HEADER header from layer above */ + skb_pull(skb, LMP_CONTROL_HEADER); + + if (self->notify.connect_indication) { + /* Don't forget to refcount it - see irlap_driver_rcv(). */ + skb_get(skb); + self->notify.connect_indication(self->notify.instance, self, + &self->qos, max_seg_size, + max_header_size, skb); + } +} + +/* + * Function irlmp_connect_response (handle, userdata) + * + * Service user is accepting connection + * + */ +int irlmp_connect_response(struct lsap_cb *self, struct sk_buff *userdata) +{ + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == LMP_LSAP_MAGIC, return -1;); + IRDA_ASSERT(userdata != NULL, return -1;); + + /* We set the connected bit and move the lsap to the connected list + * in the state machine itself. Jean II */ + + pr_debug("%s(), slsap_sel=%02x, dlsap_sel=%02x\n", + __func__, self->slsap_sel, self->dlsap_sel); + + /* Make room for MUX control header (3 bytes) */ + IRDA_ASSERT(skb_headroom(userdata) >= LMP_CONTROL_HEADER, return -1;); + skb_push(userdata, LMP_CONTROL_HEADER); + + irlmp_do_lsap_event(self, LM_CONNECT_RESPONSE, userdata); + + /* Drop reference count - see irlap_data_request(). */ + dev_kfree_skb(userdata); + + return 0; +} +EXPORT_SYMBOL(irlmp_connect_response); + +/* + * Function irlmp_connect_confirm (handle, skb) + * + * LSAP connection confirmed peer device! + */ +void irlmp_connect_confirm(struct lsap_cb *self, struct sk_buff *skb) +{ + int max_header_size; + int lap_header_size; + int max_seg_size; + + IRDA_ASSERT(skb != NULL, return;); + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LMP_LSAP_MAGIC, return;); + IRDA_ASSERT(self->lap != NULL, return;); + + self->qos = *self->lap->qos; + + max_seg_size = self->lap->qos->data_size.value-LMP_HEADER; + lap_header_size = IRLAP_GET_HEADER_SIZE(self->lap->irlap); + max_header_size = LMP_HEADER + lap_header_size; + + pr_debug("%s(), max_header_size=%d\n", + __func__, max_header_size); + + /* Hide LMP_CONTROL_HEADER header from layer above */ + skb_pull(skb, LMP_CONTROL_HEADER); + + if (self->notify.connect_confirm) { + /* Don't forget to refcount it - see irlap_driver_rcv() */ + skb_get(skb); + self->notify.connect_confirm(self->notify.instance, self, + &self->qos, max_seg_size, + max_header_size, skb); + } +} + +/* + * Function irlmp_dup (orig, instance) + * + * Duplicate LSAP, can be used by servers to confirm a connection on a + * new LSAP so it can keep listening on the old one. + * + */ +struct lsap_cb *irlmp_dup(struct lsap_cb *orig, void *instance) +{ + struct lsap_cb *new; + unsigned long flags; + + spin_lock_irqsave(&irlmp->unconnected_lsaps->hb_spinlock, flags); + + /* Only allowed to duplicate unconnected LSAP's, and only LSAPs + * that have received a connect indication. Jean II */ + if ((!hashbin_find(irlmp->unconnected_lsaps, (long) orig, NULL)) || + (orig->lap == NULL)) { + pr_debug("%s(), invalid LSAP (wrong state)\n", + __func__); + spin_unlock_irqrestore(&irlmp->unconnected_lsaps->hb_spinlock, + flags); + return NULL; + } + + /* Allocate a new instance */ + new = kmemdup(orig, sizeof(*new), GFP_ATOMIC); + if (!new) { + pr_debug("%s(), unable to kmalloc\n", __func__); + spin_unlock_irqrestore(&irlmp->unconnected_lsaps->hb_spinlock, + flags); + return NULL; + } + /* new->lap = orig->lap; => done in the memcpy() */ + /* new->slsap_sel = orig->slsap_sel; => done in the memcpy() */ + new->conn_skb = NULL; + + spin_unlock_irqrestore(&irlmp->unconnected_lsaps->hb_spinlock, flags); + + /* Not everything is the same */ + new->notify.instance = instance; + + init_timer(&new->watchdog_timer); + + hashbin_insert(irlmp->unconnected_lsaps, (irda_queue_t *) new, + (long) new, NULL); + +#ifdef CONFIG_IRDA_CACHE_LAST_LSAP + /* Make sure that we invalidate the LSAP cache */ + new->lap->cache.valid = FALSE; +#endif /* CONFIG_IRDA_CACHE_LAST_LSAP */ + + return new; +} + +/* + * Function irlmp_disconnect_request (handle, userdata) + * + * The service user is requesting disconnection, this will not remove the + * LSAP, but only mark it as disconnected + */ +int irlmp_disconnect_request(struct lsap_cb *self, struct sk_buff *userdata) +{ + struct lsap_cb *lsap; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == LMP_LSAP_MAGIC, return -1;); + IRDA_ASSERT(userdata != NULL, return -1;); + + /* Already disconnected ? + * There is a race condition between irlmp_disconnect_indication() + * and us that might mess up the hashbins below. This fixes it. + * Jean II */ + if (! test_and_clear_bit(0, &self->connected)) { + pr_debug("%s(), already disconnected!\n", __func__); + dev_kfree_skb(userdata); + return -1; + } + + skb_push(userdata, LMP_CONTROL_HEADER); + + /* + * Do the event before the other stuff since we must know + * which lap layer that the frame should be transmitted on + */ + irlmp_do_lsap_event(self, LM_DISCONNECT_REQUEST, userdata); + + /* Drop reference count - see irlap_data_request(). */ + dev_kfree_skb(userdata); + + /* + * Remove LSAP from list of connected LSAPs for the particular link + * and insert it into the list of unconnected LSAPs + */ + IRDA_ASSERT(self->lap != NULL, return -1;); + IRDA_ASSERT(self->lap->magic == LMP_LAP_MAGIC, return -1;); + IRDA_ASSERT(self->lap->lsaps != NULL, return -1;); + + lsap = hashbin_remove(self->lap->lsaps, (long) self, NULL); +#ifdef CONFIG_IRDA_CACHE_LAST_LSAP + self->lap->cache.valid = FALSE; +#endif + + IRDA_ASSERT(lsap != NULL, return -1;); + IRDA_ASSERT(lsap->magic == LMP_LSAP_MAGIC, return -1;); + IRDA_ASSERT(lsap == self, return -1;); + + hashbin_insert(irlmp->unconnected_lsaps, (irda_queue_t *) self, + (long) self, NULL); + + /* Reset some values */ + self->dlsap_sel = LSAP_ANY; + self->lap = NULL; + + return 0; +} +EXPORT_SYMBOL(irlmp_disconnect_request); + +/* + * Function irlmp_disconnect_indication (reason, userdata) + * + * LSAP is being closed! + */ +void irlmp_disconnect_indication(struct lsap_cb *self, LM_REASON reason, + struct sk_buff *skb) +{ + struct lsap_cb *lsap; + + pr_debug("%s(), reason=%s [%d]\n", __func__, + irlmp_reason_str(reason), reason); + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LMP_LSAP_MAGIC, return;); + + pr_debug("%s(), slsap_sel=%02x, dlsap_sel=%02x\n", + __func__, self->slsap_sel, self->dlsap_sel); + + /* Already disconnected ? + * There is a race condition between irlmp_disconnect_request() + * and us that might mess up the hashbins below. This fixes it. + * Jean II */ + if (! test_and_clear_bit(0, &self->connected)) { + pr_debug("%s(), already disconnected!\n", __func__); + return; + } + + /* + * Remove association between this LSAP and the link it used + */ + IRDA_ASSERT(self->lap != NULL, return;); + IRDA_ASSERT(self->lap->lsaps != NULL, return;); + + lsap = hashbin_remove(self->lap->lsaps, (long) self, NULL); +#ifdef CONFIG_IRDA_CACHE_LAST_LSAP + self->lap->cache.valid = FALSE; +#endif + + IRDA_ASSERT(lsap != NULL, return;); + IRDA_ASSERT(lsap == self, return;); + hashbin_insert(irlmp->unconnected_lsaps, (irda_queue_t *) lsap, + (long) lsap, NULL); + + self->dlsap_sel = LSAP_ANY; + self->lap = NULL; + + /* + * Inform service user + */ + if (self->notify.disconnect_indication) { + /* Don't forget to refcount it - see irlap_driver_rcv(). */ + if(skb) + skb_get(skb); + self->notify.disconnect_indication(self->notify.instance, + self, reason, skb); + } else { + pr_debug("%s(), no handler\n", __func__); + } +} + +/* + * Function irlmp_do_expiry (void) + * + * Do a cleanup of the discovery log (remove old entries) + * + * Note : separate from irlmp_do_discovery() so that we can handle + * passive discovery properly. + */ +void irlmp_do_expiry(void) +{ + struct lap_cb *lap; + + /* + * Expire discovery on all links which are *not* connected. + * On links which are connected, we can't do discovery + * anymore and can't refresh the log, so we freeze the + * discovery log to keep info about the device we are + * connected to. + * This info is mandatory if we want irlmp_connect_request() + * to work properly. - Jean II + */ + lap = (struct lap_cb *) hashbin_get_first(irlmp->links); + while (lap != NULL) { + IRDA_ASSERT(lap->magic == LMP_LAP_MAGIC, return;); + + if (lap->lap_state == LAP_STANDBY) { + /* Expire discoveries discovered on this link */ + irlmp_expire_discoveries(irlmp->cachelog, lap->saddr, + FALSE); + } + lap = (struct lap_cb *) hashbin_get_next(irlmp->links); + } +} + +/* + * Function irlmp_do_discovery (nslots) + * + * Do some discovery on all links + * + * Note : log expiry is done above. + */ +void irlmp_do_discovery(int nslots) +{ + struct lap_cb *lap; + __u16 *data_hintsp; + + /* Make sure the value is sane */ + if ((nslots != 1) && (nslots != 6) && (nslots != 8) && (nslots != 16)){ + net_warn_ratelimited("%s: invalid value for number of slots!\n", + __func__); + nslots = sysctl_discovery_slots = 8; + } + + /* Construct new discovery info to be used by IrLAP, */ + data_hintsp = (__u16 *) irlmp->discovery_cmd.data.hints; + put_unaligned(irlmp->hints.word, data_hintsp); + + /* + * Set character set for device name (we use ASCII), and + * copy device name. Remember to make room for a \0 at the + * end + */ + irlmp->discovery_cmd.data.charset = CS_ASCII; + strncpy(irlmp->discovery_cmd.data.info, sysctl_devname, + NICKNAME_MAX_LEN); + irlmp->discovery_cmd.name_len = strlen(irlmp->discovery_cmd.data.info); + irlmp->discovery_cmd.nslots = nslots; + + /* + * Try to send discovery packets on all links + */ + lap = (struct lap_cb *) hashbin_get_first(irlmp->links); + while (lap != NULL) { + IRDA_ASSERT(lap->magic == LMP_LAP_MAGIC, return;); + + if (lap->lap_state == LAP_STANDBY) { + /* Try to discover */ + irlmp_do_lap_event(lap, LM_LAP_DISCOVERY_REQUEST, + NULL); + } + lap = (struct lap_cb *) hashbin_get_next(irlmp->links); + } +} + +/* + * Function irlmp_discovery_request (nslots) + * + * Do a discovery of devices in front of the computer + * + * If the caller has registered a client discovery callback, this + * allow him to receive the full content of the discovery log through + * this callback (as normally he will receive only new discoveries). + */ +void irlmp_discovery_request(int nslots) +{ + /* Return current cached discovery log (in full) */ + irlmp_discovery_confirm(irlmp->cachelog, DISCOVERY_LOG); + + /* + * Start a single discovery operation if discovery is not already + * running + */ + if (!sysctl_discovery) { + /* Check if user wants to override the default */ + if (nslots == DISCOVERY_DEFAULT_SLOTS) + nslots = sysctl_discovery_slots; + + irlmp_do_discovery(nslots); + /* Note : we never do expiry here. Expiry will run on the + * discovery timer regardless of the state of sysctl_discovery + * Jean II */ + } +} +EXPORT_SYMBOL(irlmp_discovery_request); + +/* + * Function irlmp_get_discoveries (pn, mask, slots) + * + * Return the current discovery log + * + * If discovery is not enabled, you should call this function again + * after 1 or 2 seconds (i.e. after discovery has been done). + */ +struct irda_device_info *irlmp_get_discoveries(int *pn, __u16 mask, int nslots) +{ + /* If discovery is not enabled, it's likely that the discovery log + * will be empty. So, we trigger a single discovery, so that next + * time the user call us there might be some results in the log. + * Jean II + */ + if (!sysctl_discovery) { + /* Check if user wants to override the default */ + if (nslots == DISCOVERY_DEFAULT_SLOTS) + nslots = sysctl_discovery_slots; + + /* Start discovery - will complete sometime later */ + irlmp_do_discovery(nslots); + /* Note : we never do expiry here. Expiry will run on the + * discovery timer regardless of the state of sysctl_discovery + * Jean II */ + } + + /* Return current cached discovery log */ + return irlmp_copy_discoveries(irlmp->cachelog, pn, mask, TRUE); +} +EXPORT_SYMBOL(irlmp_get_discoveries); + +/* + * Function irlmp_notify_client (log) + * + * Notify all about discovered devices + * + * Clients registered with IrLMP are : + * o IrComm + * o IrLAN + * o Any socket (in any state - ouch, that may be a lot !) + * The client may have defined a callback to be notified in case of + * partial/selective discovery based on the hints that it passed to IrLMP. + */ +static inline void +irlmp_notify_client(irlmp_client_t *client, + hashbin_t *log, DISCOVERY_MODE mode) +{ + discinfo_t *discoveries; /* Copy of the discovery log */ + int number; /* Number of nodes in the log */ + int i; + + /* Check if client wants or not partial/selective log (optimisation) */ + if (!client->disco_callback) + return; + + /* + * Locking notes : + * the old code was manipulating the log directly, which was + * very racy. Now, we use copy_discoveries, that protects + * itself while dumping the log for us. + * The overhead of the copy is compensated by the fact that + * we only pass new discoveries in normal mode and don't + * pass the same old entry every 3s to the caller as we used + * to do (virtual function calling is expensive). + * Jean II + */ + + /* + * Now, check all discovered devices (if any), and notify client + * only about the services that the client is interested in + * We also notify only about the new devices unless the caller + * explicitly request a dump of the log. Jean II + */ + discoveries = irlmp_copy_discoveries(log, &number, + client->hint_mask.word, + (mode == DISCOVERY_LOG)); + /* Check if the we got some results */ + if (discoveries == NULL) + return; /* No nodes discovered */ + + /* Pass all entries to the listener */ + for(i = 0; i < number; i++) + client->disco_callback(&(discoveries[i]), mode, client->priv); + + /* Free up our buffer */ + kfree(discoveries); +} + +/* + * Function irlmp_discovery_confirm ( self, log) + * + * Some device(s) answered to our discovery request! Check to see which + * device it is, and give indication to the client(s) + * + */ +void irlmp_discovery_confirm(hashbin_t *log, DISCOVERY_MODE mode) +{ + irlmp_client_t *client; + irlmp_client_t *client_next; + + IRDA_ASSERT(log != NULL, return;); + + if (!(HASHBIN_GET_SIZE(log))) + return; + + /* For each client - notify callback may touch client list */ + client = (irlmp_client_t *) hashbin_get_first(irlmp->clients); + while (NULL != hashbin_find_next(irlmp->clients, (long) client, NULL, + (void *) &client_next) ) { + /* Check if we should notify client */ + irlmp_notify_client(client, log, mode); + + client = client_next; + } +} + +/* + * Function irlmp_discovery_expiry (expiry) + * + * This device is no longer been discovered, and therefore it is being + * purged from the discovery log. Inform all clients who have + * registered for this event... + * + * Note : called exclusively from discovery.c + * Note : this is no longer called under discovery spinlock, so the + * client can do whatever he wants in the callback. + */ +void irlmp_discovery_expiry(discinfo_t *expiries, int number) +{ + irlmp_client_t *client; + irlmp_client_t *client_next; + int i; + + IRDA_ASSERT(expiries != NULL, return;); + + /* For each client - notify callback may touch client list */ + client = (irlmp_client_t *) hashbin_get_first(irlmp->clients); + while (NULL != hashbin_find_next(irlmp->clients, (long) client, NULL, + (void *) &client_next) ) { + + /* Pass all entries to the listener */ + for(i = 0; i < number; i++) { + /* Check if we should notify client */ + if ((client->expir_callback) && + (client->hint_mask.word & + get_unaligned((__u16 *)expiries[i].hints) + & 0x7f7f) ) + client->expir_callback(&(expiries[i]), + EXPIRY_TIMEOUT, + client->priv); + } + + /* Next client */ + client = client_next; + } +} + +/* + * Function irlmp_get_discovery_response () + * + * Used by IrLAP to get the discovery info it needs when answering + * discovery requests by other devices. + */ +discovery_t *irlmp_get_discovery_response(void) +{ + IRDA_ASSERT(irlmp != NULL, return NULL;); + + put_unaligned(irlmp->hints.word, (__u16 *)irlmp->discovery_rsp.data.hints); + + /* + * Set character set for device name (we use ASCII), and + * copy device name. Remember to make room for a \0 at the + * end + */ + irlmp->discovery_rsp.data.charset = CS_ASCII; + + strncpy(irlmp->discovery_rsp.data.info, sysctl_devname, + NICKNAME_MAX_LEN); + irlmp->discovery_rsp.name_len = strlen(irlmp->discovery_rsp.data.info); + + return &irlmp->discovery_rsp; +} + +/* + * Function irlmp_data_request (self, skb) + * + * Send some data to peer device + * + * Note on skb management : + * After calling the lower layers of the IrDA stack, we always + * kfree() the skb, which drop the reference count (and potentially + * destroy it). + * IrLMP and IrLAP may queue the packet, and in those cases will need + * to use skb_get() to keep it around. + * Jean II + */ +int irlmp_data_request(struct lsap_cb *self, struct sk_buff *userdata) +{ + int ret; + + IRDA_ASSERT(self != NULL, return -1;); + IRDA_ASSERT(self->magic == LMP_LSAP_MAGIC, return -1;); + + /* Make room for MUX header */ + IRDA_ASSERT(skb_headroom(userdata) >= LMP_HEADER, return -1;); + skb_push(userdata, LMP_HEADER); + + ret = irlmp_do_lsap_event(self, LM_DATA_REQUEST, userdata); + + /* Drop reference count - see irlap_data_request(). */ + dev_kfree_skb(userdata); + + return ret; +} +EXPORT_SYMBOL(irlmp_data_request); + +/* + * Function irlmp_data_indication (handle, skb) + * + * Got data from LAP layer so pass it up to upper layer + * + */ +void irlmp_data_indication(struct lsap_cb *self, struct sk_buff *skb) +{ + /* Hide LMP header from layer above */ + skb_pull(skb, LMP_HEADER); + + if (self->notify.data_indication) { + /* Don't forget to refcount it - see irlap_driver_rcv(). */ + skb_get(skb); + self->notify.data_indication(self->notify.instance, self, skb); + } +} + +/* + * Function irlmp_udata_request (self, skb) + */ +int irlmp_udata_request(struct lsap_cb *self, struct sk_buff *userdata) +{ + int ret; + + IRDA_ASSERT(userdata != NULL, return -1;); + + /* Make room for MUX header */ + IRDA_ASSERT(skb_headroom(userdata) >= LMP_HEADER, return -1;); + skb_push(userdata, LMP_HEADER); + + ret = irlmp_do_lsap_event(self, LM_UDATA_REQUEST, userdata); + + /* Drop reference count - see irlap_data_request(). */ + dev_kfree_skb(userdata); + + return ret; +} + +/* + * Function irlmp_udata_indication (self, skb) + * + * Send unreliable data (but still within the connection) + * + */ +void irlmp_udata_indication(struct lsap_cb *self, struct sk_buff *skb) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LMP_LSAP_MAGIC, return;); + IRDA_ASSERT(skb != NULL, return;); + + /* Hide LMP header from layer above */ + skb_pull(skb, LMP_HEADER); + + if (self->notify.udata_indication) { + /* Don't forget to refcount it - see irlap_driver_rcv(). */ + skb_get(skb); + self->notify.udata_indication(self->notify.instance, self, + skb); + } +} + +/* + * Function irlmp_connless_data_request (self, skb) + */ +#ifdef CONFIG_IRDA_ULTRA +int irlmp_connless_data_request(struct lsap_cb *self, struct sk_buff *userdata, + __u8 pid) +{ + struct sk_buff *clone_skb; + struct lap_cb *lap; + + IRDA_ASSERT(userdata != NULL, return -1;); + + /* Make room for MUX and PID header */ + IRDA_ASSERT(skb_headroom(userdata) >= LMP_HEADER+LMP_PID_HEADER, + return -1;); + + /* Insert protocol identifier */ + skb_push(userdata, LMP_PID_HEADER); + if(self != NULL) + userdata->data[0] = self->pid; + else + userdata->data[0] = pid; + + /* Connectionless sockets must use 0x70 */ + skb_push(userdata, LMP_HEADER); + userdata->data[0] = userdata->data[1] = LSAP_CONNLESS; + + /* Try to send Connectionless packets out on all links */ + lap = (struct lap_cb *) hashbin_get_first(irlmp->links); + while (lap != NULL) { + IRDA_ASSERT(lap->magic == LMP_LAP_MAGIC, return -1;); + + clone_skb = skb_clone(userdata, GFP_ATOMIC); + if (!clone_skb) { + dev_kfree_skb(userdata); + return -ENOMEM; + } + + irlap_unitdata_request(lap->irlap, clone_skb); + /* irlap_unitdata_request() don't increase refcount, + * so no dev_kfree_skb() - Jean II */ + + lap = (struct lap_cb *) hashbin_get_next(irlmp->links); + } + dev_kfree_skb(userdata); + + return 0; +} +#endif /* CONFIG_IRDA_ULTRA */ + +/* + * Function irlmp_connless_data_indication (self, skb) + * + * Receive unreliable data outside any connection. Mostly used by Ultra + * + */ +#ifdef CONFIG_IRDA_ULTRA +void irlmp_connless_data_indication(struct lsap_cb *self, struct sk_buff *skb) +{ + IRDA_ASSERT(self != NULL, return;); + IRDA_ASSERT(self->magic == LMP_LSAP_MAGIC, return;); + IRDA_ASSERT(skb != NULL, return;); + + /* Hide LMP and PID header from layer above */ + skb_pull(skb, LMP_HEADER+LMP_PID_HEADER); + + if (self->notify.udata_indication) { + /* Don't forget to refcount it - see irlap_driver_rcv(). */ + skb_get(skb); + self->notify.udata_indication(self->notify.instance, self, + skb); + } +} +#endif /* CONFIG_IRDA_ULTRA */ + +/* + * Propagate status indication from LAP to LSAPs (via LMP) + * This don't trigger any change of state in lap_cb, lmp_cb or lsap_cb, + * and the event is stateless, therefore we can bypass both state machines + * and send the event direct to the LSAP user. + * Jean II + */ +void irlmp_status_indication(struct lap_cb *self, + LINK_STATUS link, LOCK_STATUS lock) +{ + struct lsap_cb *next; + struct lsap_cb *curr; + + /* Send status_indication to all LSAPs using this link */ + curr = (struct lsap_cb *) hashbin_get_first( self->lsaps); + while (NULL != hashbin_find_next(self->lsaps, (long) curr, NULL, + (void *) &next) ) { + IRDA_ASSERT(curr->magic == LMP_LSAP_MAGIC, return;); + /* + * Inform service user if he has requested it + */ + if (curr->notify.status_indication != NULL) + curr->notify.status_indication(curr->notify.instance, + link, lock); + else + pr_debug("%s(), no handler\n", __func__); + + curr = next; + } +} + +/* + * Receive flow control indication from LAP. + * LAP want us to send it one more frame. We implement a simple round + * robin scheduler between the active sockets so that we get a bit of + * fairness. Note that the round robin is far from perfect, but it's + * better than nothing. + * We then poll the selected socket so that we can do synchronous + * refilling of IrLAP (which allow to minimise the number of buffers). + * Jean II + */ +void irlmp_flow_indication(struct lap_cb *self, LOCAL_FLOW flow) +{ + struct lsap_cb *next; + struct lsap_cb *curr; + int lsap_todo; + + IRDA_ASSERT(self->magic == LMP_LAP_MAGIC, return;); + IRDA_ASSERT(flow == FLOW_START, return;); + + /* Get the number of lsap. That's the only safe way to know + * that we have looped around... - Jean II */ + lsap_todo = HASHBIN_GET_SIZE(self->lsaps); + pr_debug("%s() : %d lsaps to scan\n", __func__, lsap_todo); + + /* Poll lsap in order until the queue is full or until we + * tried them all. + * Most often, the current LSAP will have something to send, + * so we will go through this loop only once. - Jean II */ + while((lsap_todo--) && + (IRLAP_GET_TX_QUEUE_LEN(self->irlap) < LAP_HIGH_THRESHOLD)) { + /* Try to find the next lsap we should poll. */ + next = self->flow_next; + /* If we have no lsap, restart from first one */ + if(next == NULL) + next = (struct lsap_cb *) hashbin_get_first(self->lsaps); + /* Verify current one and find the next one */ + curr = hashbin_find_next(self->lsaps, (long) next, NULL, + (void *) &self->flow_next); + /* Uh-oh... Paranoia */ + if(curr == NULL) + break; + pr_debug("%s() : curr is %p, next was %p and is now %p, still %d to go - queue len = %d\n", + __func__, curr, next, self->flow_next, lsap_todo, + IRLAP_GET_TX_QUEUE_LEN(self->irlap)); + + /* Inform lsap user that it can send one more packet. */ + if (curr->notify.flow_indication != NULL) + curr->notify.flow_indication(curr->notify.instance, + curr, flow); + else + pr_debug("%s(), no handler\n", __func__); + } +} + +#if 0 +/* + * Function irlmp_hint_to_service (hint) + * + * Returns a list of all servics contained in the given hint bits. This + * function assumes that the hint bits have the size of two bytes only + */ +__u8 *irlmp_hint_to_service(__u8 *hint) +{ + __u8 *service; + int i = 0; + + /* + * Allocate array to store services in. 16 entries should be safe + * since we currently only support 2 hint bytes + */ + service = kmalloc(16, GFP_ATOMIC); + if (!service) + return NULL; + + if (!hint[0]) { + pr_debug("<None>\n"); + kfree(service); + return NULL; + } + if (hint[0] & HINT_PNP) + pr_debug("PnP Compatible "); + if (hint[0] & HINT_PDA) + pr_debug("PDA/Palmtop "); + if (hint[0] & HINT_COMPUTER) + pr_debug("Computer "); + if (hint[0] & HINT_PRINTER) { + pr_debug("Printer "); + service[i++] = S_PRINTER; + } + if (hint[0] & HINT_MODEM) + pr_debug("Modem "); + if (hint[0] & HINT_FAX) + pr_debug("Fax "); + if (hint[0] & HINT_LAN) { + pr_debug("LAN Access "); + service[i++] = S_LAN; + } + /* + * Test if extension byte exists. This byte will usually be + * there, but this is not really required by the standard. + * (IrLMP p. 29) + */ + if (hint[0] & HINT_EXTENSION) { + if (hint[1] & HINT_TELEPHONY) { + pr_debug("Telephony "); + service[i++] = S_TELEPHONY; + } + if (hint[1] & HINT_FILE_SERVER) + pr_debug("File Server "); + + if (hint[1] & HINT_COMM) { + pr_debug("IrCOMM "); + service[i++] = S_COMM; + } + if (hint[1] & HINT_OBEX) { + pr_debug("IrOBEX "); + service[i++] = S_OBEX; + } + } + pr_debug("\n"); + + /* So that client can be notified about any discovery */ + service[i++] = S_ANY; + + service[i] = S_END; + + return service; +} +#endif + +static const __u16 service_hint_mapping[S_END][2] = { + { HINT_PNP, 0 }, /* S_PNP */ + { HINT_PDA, 0 }, /* S_PDA */ + { HINT_COMPUTER, 0 }, /* S_COMPUTER */ + { HINT_PRINTER, 0 }, /* S_PRINTER */ + { HINT_MODEM, 0 }, /* S_MODEM */ + { HINT_FAX, 0 }, /* S_FAX */ + { HINT_LAN, 0 }, /* S_LAN */ + { HINT_EXTENSION, HINT_TELEPHONY }, /* S_TELEPHONY */ + { HINT_EXTENSION, HINT_COMM }, /* S_COMM */ + { HINT_EXTENSION, HINT_OBEX }, /* S_OBEX */ + { 0xFF, 0xFF }, /* S_ANY */ +}; + +/* + * Function irlmp_service_to_hint (service) + * + * Converts a service type, to a hint bit + * + * Returns: a 16 bit hint value, with the service bit set + */ +__u16 irlmp_service_to_hint(int service) +{ + __u16_host_order hint; + + hint.byte[0] = service_hint_mapping[service][0]; + hint.byte[1] = service_hint_mapping[service][1]; + + return hint.word; +} +EXPORT_SYMBOL(irlmp_service_to_hint); + +/* + * Function irlmp_register_service (service) + * + * Register local service with IrLMP + * + */ +void *irlmp_register_service(__u16 hints) +{ + irlmp_service_t *service; + + pr_debug("%s(), hints = %04x\n", __func__, hints); + + /* Make a new registration */ + service = kmalloc(sizeof(irlmp_service_t), GFP_ATOMIC); + if (!service) + return NULL; + + service->hints.word = hints; + hashbin_insert(irlmp->services, (irda_queue_t *) service, + (long) service, NULL); + + irlmp->hints.word |= hints; + + return (void *)service; +} +EXPORT_SYMBOL(irlmp_register_service); + +/* + * Function irlmp_unregister_service (handle) + * + * Unregister service with IrLMP. + * + * Returns: 0 on success, -1 on error + */ +int irlmp_unregister_service(void *handle) +{ + irlmp_service_t *service; + unsigned long flags; + + if (!handle) + return -1; + + /* Caller may call with invalid handle (it's legal) - Jean II */ + service = hashbin_lock_find(irlmp->services, (long) handle, NULL); + if (!service) { + pr_debug("%s(), Unknown service!\n", __func__); + return -1; + } + + hashbin_remove_this(irlmp->services, (irda_queue_t *) service); + kfree(service); + + /* Remove old hint bits */ + irlmp->hints.word = 0; + + /* Refresh current hint bits */ + spin_lock_irqsave(&irlmp->services->hb_spinlock, flags); + service = (irlmp_service_t *) hashbin_get_first(irlmp->services); + while (service) { + irlmp->hints.word |= service->hints.word; + + service = (irlmp_service_t *)hashbin_get_next(irlmp->services); + } + spin_unlock_irqrestore(&irlmp->services->hb_spinlock, flags); + return 0; +} +EXPORT_SYMBOL(irlmp_unregister_service); + +/* + * Function irlmp_register_client (hint_mask, callback1, callback2) + * + * Register a local client with IrLMP + * First callback is selective discovery (based on hints) + * Second callback is for selective discovery expiries + * + * Returns: handle > 0 on success, 0 on error + */ +void *irlmp_register_client(__u16 hint_mask, DISCOVERY_CALLBACK1 disco_clb, + DISCOVERY_CALLBACK2 expir_clb, void *priv) +{ + irlmp_client_t *client; + + IRDA_ASSERT(irlmp != NULL, return NULL;); + + /* Make a new registration */ + client = kmalloc(sizeof(irlmp_client_t), GFP_ATOMIC); + if (!client) + return NULL; + + /* Register the details */ + client->hint_mask.word = hint_mask; + client->disco_callback = disco_clb; + client->expir_callback = expir_clb; + client->priv = priv; + + hashbin_insert(irlmp->clients, (irda_queue_t *) client, + (long) client, NULL); + + return (void *) client; +} +EXPORT_SYMBOL(irlmp_register_client); + +/* + * Function irlmp_update_client (handle, hint_mask, callback1, callback2) + * + * Updates specified client (handle) with possibly new hint_mask and + * callback + * + * Returns: 0 on success, -1 on error + */ +int irlmp_update_client(void *handle, __u16 hint_mask, + DISCOVERY_CALLBACK1 disco_clb, + DISCOVERY_CALLBACK2 expir_clb, void *priv) +{ + irlmp_client_t *client; + + if (!handle) + return -1; + + client = hashbin_lock_find(irlmp->clients, (long) handle, NULL); + if (!client) { + pr_debug("%s(), Unknown client!\n", __func__); + return -1; + } + + client->hint_mask.word = hint_mask; + client->disco_callback = disco_clb; + client->expir_callback = expir_clb; + client->priv = priv; + + return 0; +} +EXPORT_SYMBOL(irlmp_update_client); + +/* + * Function irlmp_unregister_client (handle) + * + * Returns: 0 on success, -1 on error + * + */ +int irlmp_unregister_client(void *handle) +{ + struct irlmp_client *client; + + if (!handle) + return -1; + + /* Caller may call with invalid handle (it's legal) - Jean II */ + client = hashbin_lock_find(irlmp->clients, (long) handle, NULL); + if (!client) { + pr_debug("%s(), Unknown client!\n", __func__); + return -1; + } + + pr_debug("%s(), removing client!\n", __func__); + hashbin_remove_this(irlmp->clients, (irda_queue_t *) client); + kfree(client); + + return 0; +} +EXPORT_SYMBOL(irlmp_unregister_client); + +/* + * Function irlmp_slsap_inuse (slsap) + * + * Check if the given source LSAP selector is in use + * + * This function is clearly not very efficient. On the mitigating side, the + * stack make sure that in 99% of the cases, we are called only once + * for each socket allocation. We could probably keep a bitmap + * of the allocated LSAP, but I'm not sure the complexity is worth it. + * Jean II + */ +static int irlmp_slsap_inuse(__u8 slsap_sel) +{ + struct lsap_cb *self; + struct lap_cb *lap; + unsigned long flags; + + IRDA_ASSERT(irlmp != NULL, return TRUE;); + IRDA_ASSERT(irlmp->magic == LMP_MAGIC, return TRUE;); + IRDA_ASSERT(slsap_sel != LSAP_ANY, return TRUE;); + +#ifdef CONFIG_IRDA_ULTRA + /* Accept all bindings to the connectionless LSAP */ + if (slsap_sel == LSAP_CONNLESS) + return FALSE; +#endif /* CONFIG_IRDA_ULTRA */ + + /* Valid values are between 0 and 127 (0x0-0x6F) */ + if (slsap_sel > LSAP_MAX) + return TRUE; + + /* + * Check if slsap is already in use. To do this we have to loop over + * every IrLAP connection and check every LSAP associated with each + * the connection. + */ + spin_lock_irqsave_nested(&irlmp->links->hb_spinlock, flags, + SINGLE_DEPTH_NESTING); + lap = (struct lap_cb *) hashbin_get_first(irlmp->links); + while (lap != NULL) { + IRDA_ASSERT(lap->magic == LMP_LAP_MAGIC, goto errlap;); + + /* Careful for priority inversions here ! + * irlmp->links is never taken while another IrDA + * spinlock is held, so we are safe. Jean II */ + spin_lock(&lap->lsaps->hb_spinlock); + + /* For this IrLAP, check all the LSAPs */ + self = (struct lsap_cb *) hashbin_get_first(lap->lsaps); + while (self != NULL) { + IRDA_ASSERT(self->magic == LMP_LSAP_MAGIC, + goto errlsap;); + + if ((self->slsap_sel == slsap_sel)) { + pr_debug("Source LSAP selector=%02x in use\n", + self->slsap_sel); + goto errlsap; + } + self = (struct lsap_cb*) hashbin_get_next(lap->lsaps); + } + spin_unlock(&lap->lsaps->hb_spinlock); + + /* Next LAP */ + lap = (struct lap_cb *) hashbin_get_next(irlmp->links); + } + spin_unlock_irqrestore(&irlmp->links->hb_spinlock, flags); + + /* + * Server sockets are typically waiting for connections and + * therefore reside in the unconnected list. We don't want + * to give out their LSAPs for obvious reasons... + * Jean II + */ + spin_lock_irqsave(&irlmp->unconnected_lsaps->hb_spinlock, flags); + + self = (struct lsap_cb *) hashbin_get_first(irlmp->unconnected_lsaps); + while (self != NULL) { + IRDA_ASSERT(self->magic == LMP_LSAP_MAGIC, goto erruncon;); + if ((self->slsap_sel == slsap_sel)) { + pr_debug("Source LSAP selector=%02x in use (unconnected)\n", + self->slsap_sel); + goto erruncon; + } + self = (struct lsap_cb*) hashbin_get_next(irlmp->unconnected_lsaps); + } + spin_unlock_irqrestore(&irlmp->unconnected_lsaps->hb_spinlock, flags); + + return FALSE; + + /* Error exit from within one of the two nested loops. + * Make sure we release the right spinlock in the righ order. + * Jean II */ +errlsap: + spin_unlock(&lap->lsaps->hb_spinlock); +IRDA_ASSERT_LABEL(errlap:) + spin_unlock_irqrestore(&irlmp->links->hb_spinlock, flags); + return TRUE; + + /* Error exit from within the unconnected loop. + * Just one spinlock to release... Jean II */ +erruncon: + spin_unlock_irqrestore(&irlmp->unconnected_lsaps->hb_spinlock, flags); + return TRUE; +} + +/* + * Function irlmp_find_free_slsap () + * + * Find a free source LSAP to use. This function is called if the service + * user has requested a source LSAP equal to LM_ANY + */ +static __u8 irlmp_find_free_slsap(void) +{ + __u8 lsap_sel; + int wrapped = 0; + + IRDA_ASSERT(irlmp != NULL, return -1;); + IRDA_ASSERT(irlmp->magic == LMP_MAGIC, return -1;); + + /* Most users don't really care which LSAPs they are given, + * and therefore we automatically give them a free LSAP. + * This function try to find a suitable LSAP, i.e. which is + * not in use and is within the acceptable range. Jean II */ + + do { + /* Always increment to LSAP number before using it. + * In theory, we could reuse the last LSAP number, as long + * as it is no longer in use. Some IrDA stack do that. + * However, the previous socket may be half closed, i.e. + * we closed it, we think it's no longer in use, but the + * other side did not receive our close and think it's + * active and still send data on it. + * This is similar to what is done with PIDs and TCP ports. + * Also, this reduce the number of calls to irlmp_slsap_inuse() + * which is an expensive function to call. + * Jean II */ + irlmp->last_lsap_sel++; + + /* Check if we need to wraparound (0x70-0x7f are reserved) */ + if (irlmp->last_lsap_sel > LSAP_MAX) { + /* 0x00-0x10 are also reserved for well know ports */ + irlmp->last_lsap_sel = 0x10; + + /* Make sure we terminate the loop */ + if (wrapped++) { + net_err_ratelimited("%s: no more free LSAPs !\n", + __func__); + return 0; + } + } + + /* If the LSAP is in use, try the next one. + * Despite the autoincrement, we need to check if the lsap + * is really in use or not, first because LSAP may be + * directly allocated in irlmp_open_lsap(), and also because + * we may wraparound on old sockets. Jean II */ + } while (irlmp_slsap_inuse(irlmp->last_lsap_sel)); + + /* Got it ! */ + lsap_sel = irlmp->last_lsap_sel; + pr_debug("%s(), found free lsap_sel=%02x\n", + __func__, lsap_sel); + + return lsap_sel; +} + +/* + * Function irlmp_convert_lap_reason (lap_reason) + * + * Converts IrLAP disconnect reason codes to IrLMP disconnect reason + * codes + * + */ +LM_REASON irlmp_convert_lap_reason( LAP_REASON lap_reason) +{ + int reason = LM_LAP_DISCONNECT; + + switch (lap_reason) { + case LAP_DISC_INDICATION: /* Received a disconnect request from peer */ + pr_debug("%s(), LAP_DISC_INDICATION\n", __func__); + reason = LM_USER_REQUEST; + break; + case LAP_NO_RESPONSE: /* To many retransmits without response */ + pr_debug("%s(), LAP_NO_RESPONSE\n", __func__); + reason = LM_LAP_DISCONNECT; + break; + case LAP_RESET_INDICATION: + pr_debug("%s(), LAP_RESET_INDICATION\n", __func__); + reason = LM_LAP_RESET; + break; + case LAP_FOUND_NONE: + case LAP_MEDIA_BUSY: + case LAP_PRIMARY_CONFLICT: + pr_debug("%s(), LAP_FOUND_NONE, LAP_MEDIA_BUSY or LAP_PRIMARY_CONFLICT\n", + __func__); + reason = LM_CONNECT_FAILURE; + break; + default: + pr_debug("%s(), Unknown IrLAP disconnect reason %d!\n", + __func__, lap_reason); + reason = LM_LAP_DISCONNECT; + break; + } + + return reason; +} + +#ifdef CONFIG_PROC_FS + +struct irlmp_iter_state { + hashbin_t *hashbin; +}; + +#define LSAP_START_TOKEN ((void *)1) +#define LINK_START_TOKEN ((void *)2) + +static void *irlmp_seq_hb_idx(struct irlmp_iter_state *iter, loff_t *off) +{ + void *element; + + spin_lock_irq(&iter->hashbin->hb_spinlock); + for (element = hashbin_get_first(iter->hashbin); + element != NULL; + element = hashbin_get_next(iter->hashbin)) { + if (!off || *off-- == 0) { + /* NB: hashbin left locked */ + return element; + } + } + spin_unlock_irq(&iter->hashbin->hb_spinlock); + iter->hashbin = NULL; + return NULL; +} + + +static void *irlmp_seq_start(struct seq_file *seq, loff_t *pos) +{ + struct irlmp_iter_state *iter = seq->private; + void *v; + loff_t off = *pos; + + iter->hashbin = NULL; + if (off-- == 0) + return LSAP_START_TOKEN; + + iter->hashbin = irlmp->unconnected_lsaps; + v = irlmp_seq_hb_idx(iter, &off); + if (v) + return v; + + if (off-- == 0) + return LINK_START_TOKEN; + + iter->hashbin = irlmp->links; + return irlmp_seq_hb_idx(iter, &off); +} + +static void *irlmp_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + struct irlmp_iter_state *iter = seq->private; + + ++*pos; + + if (v == LSAP_START_TOKEN) { /* start of list of lsaps */ + iter->hashbin = irlmp->unconnected_lsaps; + v = irlmp_seq_hb_idx(iter, NULL); + return v ? v : LINK_START_TOKEN; + } + + if (v == LINK_START_TOKEN) { /* start of list of links */ + iter->hashbin = irlmp->links; + return irlmp_seq_hb_idx(iter, NULL); + } + + v = hashbin_get_next(iter->hashbin); + + if (v == NULL) { /* no more in this hash bin */ + spin_unlock_irq(&iter->hashbin->hb_spinlock); + + if (iter->hashbin == irlmp->unconnected_lsaps) + v = LINK_START_TOKEN; + + iter->hashbin = NULL; + } + return v; +} + +static void irlmp_seq_stop(struct seq_file *seq, void *v) +{ + struct irlmp_iter_state *iter = seq->private; + + if (iter->hashbin) + spin_unlock_irq(&iter->hashbin->hb_spinlock); +} + +static int irlmp_seq_show(struct seq_file *seq, void *v) +{ + const struct irlmp_iter_state *iter = seq->private; + struct lsap_cb *self = v; + + if (v == LSAP_START_TOKEN) + seq_puts(seq, "Unconnected LSAPs:\n"); + else if (v == LINK_START_TOKEN) + seq_puts(seq, "\nRegistered Link Layers:\n"); + else if (iter->hashbin == irlmp->unconnected_lsaps) { + self = v; + IRDA_ASSERT(self->magic == LMP_LSAP_MAGIC, return -EINVAL; ); + seq_printf(seq, "lsap state: %s, ", + irlsap_state[ self->lsap_state]); + seq_printf(seq, + "slsap_sel: %#02x, dlsap_sel: %#02x, ", + self->slsap_sel, self->dlsap_sel); + seq_printf(seq, "(%s)", self->notify.name); + seq_printf(seq, "\n"); + } else if (iter->hashbin == irlmp->links) { + struct lap_cb *lap = v; + + seq_printf(seq, "lap state: %s, ", + irlmp_state[lap->lap_state]); + + seq_printf(seq, "saddr: %#08x, daddr: %#08x, ", + lap->saddr, lap->daddr); + seq_printf(seq, "num lsaps: %d", + HASHBIN_GET_SIZE(lap->lsaps)); + seq_printf(seq, "\n"); + + /* 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(&lap->lsaps->hb_spinlock); + + seq_printf(seq, "\n Connected LSAPs:\n"); + for (self = (struct lsap_cb *) hashbin_get_first(lap->lsaps); + self != NULL; + self = (struct lsap_cb *)hashbin_get_next(lap->lsaps)) { + IRDA_ASSERT(self->magic == LMP_LSAP_MAGIC, + goto outloop;); + seq_printf(seq, " lsap state: %s, ", + irlsap_state[ self->lsap_state]); + seq_printf(seq, + "slsap_sel: %#02x, dlsap_sel: %#02x, ", + self->slsap_sel, self->dlsap_sel); + seq_printf(seq, "(%s)", self->notify.name); + seq_putc(seq, '\n'); + + } + IRDA_ASSERT_LABEL(outloop:) + spin_unlock(&lap->lsaps->hb_spinlock); + seq_putc(seq, '\n'); + } else + return -EINVAL; + + return 0; +} + +static const struct seq_operations irlmp_seq_ops = { + .start = irlmp_seq_start, + .next = irlmp_seq_next, + .stop = irlmp_seq_stop, + .show = irlmp_seq_show, +}; + +static int irlmp_seq_open(struct inode *inode, struct file *file) +{ + IRDA_ASSERT(irlmp != NULL, return -EINVAL;); + + return seq_open_private(file, &irlmp_seq_ops, + sizeof(struct irlmp_iter_state)); +} + +const struct file_operations irlmp_seq_fops = { + .owner = THIS_MODULE, + .open = irlmp_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release_private, +}; + +#endif /* PROC_FS */ |