diff options
Diffstat (limited to 'net/irda/irnet/irnet_ppp.c')
-rw-r--r-- | net/irda/irnet/irnet_ppp.c | 1052 |
1 files changed, 1052 insertions, 0 deletions
diff --git a/net/irda/irnet/irnet_ppp.c b/net/irda/irnet/irnet_ppp.c new file mode 100644 index 000000000..96a5c4114 --- /dev/null +++ b/net/irda/irnet/irnet_ppp.c @@ -0,0 +1,1052 @@ +/* + * IrNET protocol module : Synchronous PPP over an IrDA socket. + * + * Jean II - HPL `00 - <jt@hpl.hp.com> + * + * This file implement the PPP interface and /dev/irnet character device. + * The PPP interface hook to the ppp_generic module, handle all our + * relationship to the PPP code in the kernel (and by extension to pppd), + * and exchange PPP frames with this module (send/receive). + * The /dev/irnet device is used primarily for 2 functions : + * 1) as a stub for pppd (the ppp daemon), so that we can appropriately + * generate PPP sessions (we pretend we are a tty). + * 2) as a control channel (write commands, read events) + */ + +#include "irnet_ppp.h" /* Private header */ + +/************************* CONTROL CHANNEL *************************/ +/* + * When a pppd instance is not active on /dev/irnet, it acts as a control + * channel. + * Writting allow to set up the IrDA destination of the IrNET channel, + * and any application may be read events happening in IrNET... + */ + +/*------------------------------------------------------------------*/ +/* + * Write is used to send a command to configure a IrNET channel + * before it is open by pppd. The syntax is : "command argument" + * Currently there is only two defined commands : + * o name : set the requested IrDA nickname of the IrNET peer. + * o addr : set the requested IrDA address of the IrNET peer. + * Note : the code is crude, but effective... + */ +static inline ssize_t +irnet_ctrl_write(irnet_socket * ap, + const char * buf, + size_t count) +{ + char command[5 + NICKNAME_MAX_LEN + 2]; + int length = count; + + DENTER(CTRL_TRACE, "(ap=0x%X, count=%d)\n", (unsigned int) ap, count); + + /* Check for overflow... */ + DABORT(count > (5 + NICKNAME_MAX_LEN + 1), -ENOMEM, + CTRL_ERROR, "Too much data !!!\n"); + + /* Get the data in the driver */ + if(copy_from_user(command, buf, count)) + { + DERROR(CTRL_ERROR, "Invalid user space pointer.\n"); + return -EFAULT; + } + + /* Strip out '\n' if needed, and safe terminate the string */ + if(command[length - 1] == '\0') + length--; + if(command[length - 1] == '\n') + length--; + command[length] = '\0'; + DEBUG(CTRL_INFO, "Command received is ``%s'' (%d-%d).\n", + command, length, count); + + /* Check if we recognised the command */ + /* First command : name */ + if(!strncmp(command, "name", 4)) + { + /* Copy the name only if is included and not "any" */ + if((length > 5) && (strcmp(command + 5, "any"))) + { + /* Copy the name for later reuse (including the '/0') */ + memcpy(ap->rname, command + 5, length - 5 + 1); + } + else + ap->rname[0] = '\0'; + DEXIT(CTRL_TRACE, " - rname = ``%s''\n", ap->rname); + return(count); + } + + /* Second command : addr */ + if(!strncmp(command, "addr", 4)) + { + /* Copy the address only if is included and not "any" */ + if((length > 5) && (strcmp(command + 5, "any"))) + { + char * endp; + __u32 daddr; + + /* Convert argument to a number (last arg is the base) */ + daddr = simple_strtoul(command + 5, &endp, 16); + /* Has it worked ? (endp should be command + count) */ + DABORT(endp <= (command + 5), -EINVAL, + CTRL_ERROR, "Invalid address.\n"); + /* Save it */ + ap->raddr = daddr; + } + else + ap->raddr = DEV_ADDR_ANY; + DEXIT(CTRL_TRACE, " - raddr = %08x\n", ap->raddr); + return(count); + } + + /* Other possible command : connect N (number of retries) */ + + /* Failed... */ + DABORT(1, -EINVAL, CTRL_ERROR, "Not a recognised IrNET command.\n"); +} + +#ifdef INITIAL_DISCOVERY +/*------------------------------------------------------------------*/ +/* + * Function irnet_read_discovery_log (self) + * + * Read the content on the discovery log + * + * This function dump the current content of the discovery log + * at the startup of the event channel. + * Return 1 if written on the control channel... + * + * State of the ap->disco_XXX variables : + * at socket creation : disco_index = 0 ; disco_number = 0 + * while reading : disco_index = X ; disco_number = Y + * After reading : disco_index = Y ; disco_number = -1 + */ +static inline int +irnet_read_discovery_log(irnet_socket * ap, + char * event) +{ + int done_event = 0; + + DENTER(CTRL_TRACE, "(ap=0x%X, event=0x%X)\n", + (unsigned int) ap, (unsigned int) event); + + /* Test if we have some work to do or we have already finished */ + if(ap->disco_number == -1) + { + DEBUG(CTRL_INFO, "Already done\n"); + return 0; + } + + /* Test if it's the first time and therefore we need to get the log */ + if(ap->disco_index == 0) + { + __u16 mask = irlmp_service_to_hint(S_LAN); + + /* Ask IrLMP for the current discovery log */ + ap->discoveries = irlmp_get_discoveries(&ap->disco_number, mask); + /* Check if the we got some results */ + if(ap->discoveries == NULL) + ap->disco_number = -1; + DEBUG(CTRL_INFO, "Got the log (0x%X), size is %d\n", + (unsigned int) ap->discoveries, ap->disco_number); + } + + /* Check if we have more item to dump */ + if(ap->disco_index < ap->disco_number) + { + /* Write an event */ + sprintf(event, "Found %08x (%s)\n", + ap->discoveries[ap->disco_index].daddr, + ap->discoveries[ap->disco_index].info); + DEBUG(CTRL_INFO, "Writing discovery %d : %s\n", + ap->disco_index, ap->discoveries[ap->disco_index].info); + + /* We have an event */ + done_event = 1; + /* Next discovery */ + ap->disco_index++; + } + + /* Check if we have done the last item */ + if(ap->disco_index >= ap->disco_number) + { + /* No more items : remove the log and signal termination */ + DEBUG(CTRL_INFO, "Cleaning up log (0x%X)\n", + (unsigned int) ap->discoveries); + if(ap->discoveries != NULL) + { + /* Cleanup our copy of the discovery log */ + kfree(ap->discoveries); + ap->discoveries = NULL; + } + ap->disco_number = -1; + } + + return done_event; +} +#endif INITIAL_DISCOVERY + +/*------------------------------------------------------------------*/ +/* + * Read is used to get IrNET events + */ +static inline ssize_t +irnet_ctrl_read(irnet_socket * ap, + struct file * file, + char * buf, + size_t count) +{ + DECLARE_WAITQUEUE(wait, current); + char event[64]; /* Max event is 61 char */ + ssize_t ret = 0; + + DENTER(CTRL_TRACE, "(ap=0x%X, count=%d)\n", (unsigned int) ap, count); + + /* Check if we can write an event out in one go */ + DABORT(count < sizeof(event), -EOVERFLOW, CTRL_ERROR, "Buffer to small.\n"); + +#ifdef INITIAL_DISCOVERY + /* Check if we have read the log */ + if(irnet_read_discovery_log(ap, event)) + { + /* We have an event !!! Copy it to the user */ + if(copy_to_user(buf, event, strlen(event))) + { + DERROR(CTRL_ERROR, "Invalid user space pointer.\n"); + return -EFAULT; + } + + DEXIT(CTRL_TRACE, "\n"); + return(strlen(event)); + } +#endif INITIAL_DISCOVERY + + /* Put ourselves on the wait queue to be woken up */ + add_wait_queue(&irnet_events.rwait, &wait); + current->state = TASK_INTERRUPTIBLE; + for(;;) + { + /* If there is unread events */ + ret = 0; + if(ap->event_index != irnet_events.index) + break; + ret = -EAGAIN; + if(file->f_flags & O_NONBLOCK) + break; + ret = -ERESTARTSYS; + if(signal_pending(current)) + break; + /* Yield and wait to be woken up */ + schedule(); + } + current->state = TASK_RUNNING; + remove_wait_queue(&irnet_events.rwait, &wait); + + /* Did we got it ? */ + if(ret != 0) + { + /* No, return the error code */ + DEXIT(CTRL_TRACE, " - ret %d\n", ret); + return ret; + } + + /* Which event is it ? */ + switch(irnet_events.log[ap->event_index].event) + { + case IRNET_DISCOVER: + sprintf(event, "Discovered %08x (%s)\n", + irnet_events.log[ap->event_index].addr, + irnet_events.log[ap->event_index].name); + break; + case IRNET_EXPIRE: + sprintf(event, "Expired %08x (%s)\n", + irnet_events.log[ap->event_index].addr, + irnet_events.log[ap->event_index].name); + break; + case IRNET_CONNECT_TO: + sprintf(event, "Connected to %08x (%s) on ppp%d\n", + irnet_events.log[ap->event_index].addr, + irnet_events.log[ap->event_index].name, + irnet_events.log[ap->event_index].unit); + break; + case IRNET_CONNECT_FROM: + sprintf(event, "Connection from %08x (%s) on ppp%d\n", + irnet_events.log[ap->event_index].addr, + irnet_events.log[ap->event_index].name, + irnet_events.log[ap->event_index].unit); + break; + case IRNET_REQUEST_FROM: + sprintf(event, "Request from %08x (%s)\n", + irnet_events.log[ap->event_index].addr, + irnet_events.log[ap->event_index].name); + break; + case IRNET_NOANSWER_FROM: + sprintf(event, "No-answer from %08x (%s) on ppp%d\n", + irnet_events.log[ap->event_index].addr, + irnet_events.log[ap->event_index].name, + irnet_events.log[ap->event_index].unit); + break; + case IRNET_BLOCKED_LINK: + sprintf(event, "Blocked link with %08x (%s) on ppp%d\n", + irnet_events.log[ap->event_index].addr, + irnet_events.log[ap->event_index].name, + irnet_events.log[ap->event_index].unit); + break; + case IRNET_DISCONNECT_FROM: + sprintf(event, "Disconnection from %08x (%s) on ppp%d\n", + irnet_events.log[ap->event_index].addr, + irnet_events.log[ap->event_index].name, + irnet_events.log[ap->event_index].unit); + break; + case IRNET_DISCONNECT_TO: + sprintf(event, "Disconnected to %08x (%s)\n", + irnet_events.log[ap->event_index].addr, + irnet_events.log[ap->event_index].name); + break; + default: + sprintf(event, "Bug\n"); + } + /* Increment our event index */ + ap->event_index = (ap->event_index + 1) % IRNET_MAX_EVENTS; + + DEBUG(CTRL_INFO, "Event is :%s", event); + + /* Copy it to the user */ + if(copy_to_user(buf, event, strlen(event))) + { + DERROR(CTRL_ERROR, "Invalid user space pointer.\n"); + return -EFAULT; + } + + DEXIT(CTRL_TRACE, "\n"); + return(strlen(event)); +} + +/*------------------------------------------------------------------*/ +/* + * Poll : called when someone do a select on /dev/irnet. + * Just check if there are new events... + */ +static inline unsigned int +irnet_ctrl_poll(irnet_socket * ap, + struct file * file, + poll_table * wait) +{ + unsigned int mask; + + DENTER(CTRL_TRACE, "(ap=0x%X)\n", (unsigned int) ap); + + poll_wait(file, &irnet_events.rwait, wait); + mask = POLLOUT | POLLWRNORM; + /* If there is unread events */ + if(ap->event_index != irnet_events.index) + mask |= POLLIN | POLLRDNORM; +#ifdef INITIAL_DISCOVERY + if(ap->disco_number != -1) + mask |= POLLIN | POLLRDNORM; +#endif INITIAL_DISCOVERY + + DEXIT(CTRL_TRACE, " - mask=0x%X\n", mask); + return mask; +} + + +/*********************** FILESYSTEM CALLBACKS ***********************/ +/* + * Implement the usual open, read, write functions that will be called + * by the file system when some action is performed on /dev/irnet. + * Most of those actions will in fact be performed by "pppd" or + * the control channel, we just act as a redirector... + */ + +/*------------------------------------------------------------------*/ +/* + * Open : when somebody open /dev/irnet + * We basically create a new instance of irnet and initialise it. + */ +static int +dev_irnet_open(struct inode * inode, + struct file * file) +{ + struct irnet_socket * ap; + int err; + + DENTER(FS_TRACE, "(file=0x%X)\n", (unsigned int) file); + +#ifdef SECURE_DEVIRNET + /* This could (should?) be enforced by the permissions on /dev/irnet. */ + if(!capable(CAP_NET_ADMIN)) + return -EPERM; +#endif SECURE_DEVIRNET + + /* Allocate a private structure for this IrNET instance */ + ap = kmalloc(sizeof(*ap), GFP_KERNEL); + DABORT(ap == NULL, -ENOMEM, FS_ERROR, "Can't allocate struct irnet...\n"); + + MOD_INC_USE_COUNT; + + /* initialize the irnet structure */ + memset(ap, 0, sizeof(*ap)); + ap->file = file; + + /* PPP channel setup */ + ap->ppp_open = 0; + ap->chan.private = ap; + /* PPP parameters */ + ap->mru = PPP_MRU; + ap->xaccm[0] = ~0U; + ap->xaccm[3] = 0x60000000U; + ap->raccm = ~0U; + + /* Setup the IrDA part... */ + err = irda_irnet_create(ap); + if(err) + { + DERROR(FS_ERROR, "Can't setup IrDA link...\n"); + kfree(ap); + MOD_DEC_USE_COUNT; + return err; + } + + /* For the control channel */ + ap->event_index = irnet_events.index; /* Cancel all past events */ + + /* Put our stuff where we will be able to find it later */ + file->private_data = ap; + + DEXIT(FS_TRACE, " - ap=0x%X\n", (unsigned int) ap); + return 0; +} + + +/*------------------------------------------------------------------*/ +/* + * Close : when somebody close /dev/irnet + * Destroy the instance of /dev/irnet + */ +static int +dev_irnet_close(struct inode * inode, + struct file * file) +{ + irnet_socket * ap = (struct irnet_socket *) file->private_data; + + DENTER(FS_TRACE, "(file=0x%X, ap=0x%X)\n", + (unsigned int) file, (unsigned int) ap); + DABORT(ap == NULL, 0, FS_ERROR, "ap is NULL !!!\n"); + + /* Detach ourselves */ + file->private_data = NULL; + + /* Close IrDA stuff */ + irda_irnet_destroy(ap); + + /* Disconnect from the generic PPP layer if not already done */ + if(ap->ppp_open) + { + DERROR(FS_ERROR, "Channel still registered - deregistering !\n"); + ppp_unregister_channel(&ap->chan); + ap->ppp_open = 0; + } + + kfree(ap); + MOD_DEC_USE_COUNT; + + DEXIT(FS_TRACE, "\n"); + return 0; +} + +/*------------------------------------------------------------------*/ +/* + * Write does nothing. + * (we receive packet from ppp_generic through ppp_irnet_send()) + */ +static ssize_t +dev_irnet_write(struct file * file, + const char * buf, + size_t count, + loff_t * ppos) +{ + irnet_socket * ap = (struct irnet_socket *) file->private_data; + + DPASS(FS_TRACE, "(file=0x%X, ap=0x%X, count=%d)\n", + (unsigned int) file, (unsigned int) ap, count); + DABORT(ap == NULL, -ENXIO, FS_ERROR, "ap is NULL !!!\n"); + + /* If we are connected to ppp_generic, let it handle the job */ + if(ap->ppp_open) + return ppp_channel_write(&ap->chan, buf, count); + else + return irnet_ctrl_write(ap, buf, count); +} + +/*------------------------------------------------------------------*/ +/* + * Read doesn't do much either. + * (pppd poll us, but ultimately reads through /dev/ppp) + */ +static ssize_t +dev_irnet_read(struct file * file, + char * buf, + size_t count, + loff_t * ppos) +{ + irnet_socket * ap = (struct irnet_socket *) file->private_data; + + DPASS(FS_TRACE, "(file=0x%X, ap=0x%X, count=%d)\n", + (unsigned int) file, (unsigned int) ap, count); + DABORT(ap == NULL, -ENXIO, FS_ERROR, "ap is NULL !!!\n"); + + /* If we are connected to ppp_generic, let it handle the job */ + if(ap->ppp_open) + return ppp_channel_read(&ap->chan, file, buf, count); + else + return irnet_ctrl_read(ap, file, buf, count); +} + +/*------------------------------------------------------------------*/ +/* + * Poll : called when someone do a select on /dev/irnet + */ +static unsigned int +dev_irnet_poll(struct file * file, + poll_table * wait) +{ + irnet_socket * ap = (struct irnet_socket *) file->private_data; + unsigned int mask; + + DENTER(FS_TRACE, "(file=0x%X, ap=0x%X)\n", + (unsigned int) file, (unsigned int) ap); + + mask = POLLOUT | POLLWRNORM; + DABORT(ap == NULL, mask, FS_ERROR, "ap is NULL !!!\n"); + + /* If we are connected to ppp_generic, let it handle the job */ + if(ap->ppp_open) + mask |= ppp_channel_poll(&ap->chan, file, wait); + else + mask |= irnet_ctrl_poll(ap, file, wait); + + DEXIT(FS_TRACE, " - mask=0x%X\n", mask); + return(mask); +} + +/*------------------------------------------------------------------*/ +/* + * IOCtl : Called when someone does some ioctls on /dev/irnet + * This is the way pppd configure us and control us while the PPP + * instance is active. + */ +static int +dev_irnet_ioctl(struct inode * inode, + struct file * file, + unsigned int cmd, + unsigned long arg) +{ + irnet_socket * ap = (struct irnet_socket *) file->private_data; + int err; + int val; + + DENTER(FS_TRACE, "(file=0x%X, ap=0x%X, cmd=0x%X)\n", + (unsigned int) file, (unsigned int) ap, cmd); + + /* Basic checks... */ + DASSERT(ap != NULL, -ENXIO, PPP_ERROR, "ap is NULL...\n"); +#ifdef SECURE_DEVIRNET + if(!capable(CAP_NET_ADMIN)) + return -EPERM; +#endif SECURE_DEVIRNET + + err = -EFAULT; + switch(cmd) + { + /* Set discipline (should be N_SYNC_PPP or N_TTY) */ + case TIOCSETD: + if(get_user(val, (int *) arg)) + break; + if((val == N_SYNC_PPP) || (val == N_PPP)) + { + DEBUG(FS_INFO, "Entering PPP discipline.\n"); + /* PPP channel setup */ + ap->chan.private = ap; + ap->chan.ops = &irnet_ppp_ops; + ap->chan.mtu = PPP_MRU; + err = ppp_register_channel(&ap->chan); + if(err == 0) + { + /* Our ppp side is active */ + ap->ppp_open = 1; + + DEBUG(FS_INFO, "Trying to establish a connection.\n"); + /* Setup the IrDA link now - may fail... */ + irda_irnet_connect(ap); + } + else + DERROR(FS_ERROR, "Can't setup PPP channel...\n"); + } + else + { + /* In theory, should be N_TTY */ + DEBUG(FS_INFO, "Exiting PPP discipline.\n"); + /* Disconnect from the generic PPP layer */ + if(ap->ppp_open) + ppp_unregister_channel(&ap->chan); + else + DERROR(FS_ERROR, "Channel not registered !\n"); + ap->ppp_open = 0; + err = 0; + } + break; + + /* Attach this PPP instance to the PPP driver (set it active) */ + case PPPIOCATTACH: + case PPPIOCDETACH: + if(ap->ppp_open) + err = ppp_channel_ioctl(&ap->chan, cmd, arg); + else + DERROR(FS_ERROR, "Channel not registered !\n"); + break; + + /* Query PPP channel and unit number */ + case PPPIOCGCHAN: + if(!ap->ppp_open) + break; + if(put_user(ppp_channel_index(&ap->chan), (int *) arg)) + break; + DEBUG(FS_INFO, "Query channel.\n"); + err = 0; + break; + case PPPIOCGUNIT: + if(!ap->ppp_open) + break; + if(put_user(ppp_unit_number(&ap->chan), (int *) arg)) + break; + DEBUG(FS_INFO, "Query unit number.\n"); + err = 0; + break; + + /* All these ioctls can be passed both directly and from ppp_generic, + * so we just deal with them in one place... + */ + case PPPIOCGFLAGS: + case PPPIOCSFLAGS: + case PPPIOCGASYNCMAP: + case PPPIOCSASYNCMAP: + case PPPIOCGRASYNCMAP: + case PPPIOCSRASYNCMAP: + case PPPIOCGXASYNCMAP: + case PPPIOCSXASYNCMAP: + case PPPIOCGMRU: + case PPPIOCSMRU: + DEBUG(FS_INFO, "Standard PPP ioctl.\n"); + if(!capable(CAP_NET_ADMIN)) + err = -EPERM; + else + err = ppp_irnet_ioctl(&ap->chan, cmd, arg); + break; + + /* TTY IOCTLs : Pretend that we are a tty, to keep pppd happy */ + /* Get termios */ + case TCGETS: + DEBUG(FS_INFO, "Get termios.\n"); + if(kernel_termios_to_user_termios((struct termios *)arg, &ap->termios)) + break; + err = 0; + break; + /* Set termios */ + case TCSETSF: + DEBUG(FS_INFO, "Set termios.\n"); + if(user_termios_to_kernel_termios(&ap->termios, (struct termios *) arg)) + break; + err = 0; + break; + + /* Set DTR/RTS */ + case TIOCMBIS: + case TIOCMBIC: + /* Set exclusive/non-exclusive mode */ + case TIOCEXCL: + case TIOCNXCL: + DEBUG(FS_INFO, "TTY compatibility.\n"); + err = 0; + break; + + case TCGETA: + DEBUG(FS_INFO, "TCGETA\n"); + break; + + case TCFLSH: + DEBUG(FS_INFO, "TCFLSH\n"); + /* Note : this will flush buffers in PPP, so it *must* be done + * We should also worry that we don't accept junk here and that + * we get rid of our own buffers */ +#ifdef FLUSH_TO_PPP + ppp_output_wakeup(&ap->chan); +#endif FLUSH_TO_PPP + err = 0; + break; + + case FIONREAD: + DEBUG(FS_INFO, "FIONREAD\n"); + val = 0; + if(put_user(val, (int *) arg)) + break; + err = 0; + break; + + default: + DERROR(FS_ERROR, "Unsupported ioctl (0x%X)\n", cmd); + err = -ENOIOCTLCMD; + } + + DEXIT(FS_TRACE, " - err = 0x%X\n", err); + return err; +} + +/************************** PPP CALLBACKS **************************/ +/* + * This are the functions that the generic PPP driver in the kernel + * will call to communicate to us. + */ + +/*------------------------------------------------------------------*/ +/* + * Prepare the ppp frame for transmission over the IrDA socket. + * We make sure that the header space is enough, and we change ppp header + * according to flags passed by pppd. + * This is not a callback, but just a helper function used in ppp_irnet_send() + */ +static inline struct sk_buff * +irnet_prepare_skb(irnet_socket * ap, + struct sk_buff * skb) +{ + unsigned char * data; + int proto; /* PPP protocol */ + int islcp; /* Protocol == LCP */ + int needaddr; /* Need PPP address */ + + DENTER(PPP_TRACE, "(ap=0x%X, skb=0x%X)\n", + (unsigned int) ap, (unsigned int) skb); + + /* Extract PPP protocol from the frame */ + data = skb->data; + proto = (data[0] << 8) + data[1]; + + /* LCP packets with codes between 1 (configure-request) + * and 7 (code-reject) must be sent as though no options + * have been negotiated. */ + islcp = (proto == PPP_LCP) && (1 <= data[2]) && (data[2] <= 7); + + /* compress protocol field if option enabled */ + if((data[0] == 0) && (ap->flags & SC_COMP_PROT) && (!islcp)) + skb_pull(skb,1); + + /* Check if we need address/control fields */ + needaddr = 2*((ap->flags & SC_COMP_AC) == 0 || islcp); + + /* Is the skb headroom large enough to contain all IrDA-headers? */ + if((skb_headroom(skb) < (ap->max_header_size + needaddr)) || + (skb_shared(skb))) + { + struct sk_buff * new_skb; + + DEBUG(PPP_INFO, "Reallocating skb\n"); + + /* Create a new skb */ + new_skb = skb_realloc_headroom(skb, ap->max_header_size + needaddr); + + /* We have to free the original skb anyway */ + dev_kfree_skb(skb); + + /* Did the realloc succeed ? */ + DABORT(new_skb == NULL, NULL, PPP_ERROR, "Could not realloc skb\n"); + + /* Use the new skb instead */ + skb = new_skb; + } + + /* prepend address/control fields if necessary */ + if(needaddr) + { + skb_push(skb,2); + skb->data[0] = PPP_ALLSTATIONS; + skb->data[1] = PPP_UI; + } + + DEXIT(PPP_TRACE, "\n"); + + return skb; +} + +/*------------------------------------------------------------------*/ +/* + * Send a packet to the peer over the IrTTP connection. + * Returns 1 iff the packet was accepted. + * Returns 0 iff packet was not consumed. + * If the packet was not accepted, we will call ppp_output_wakeup + * at some later time to reactivate flow control in ppp_generic. + */ +static int +ppp_irnet_send(struct ppp_channel * chan, + struct sk_buff * skb) +{ + irnet_socket * self = (struct irnet_socket *) chan->private; + int ret; + + DENTER(PPP_TRACE, "(channel=0x%X, ap/self=0x%X)\n", + (unsigned int) chan, (unsigned int) self); + + /* Check if things are somewhat valid... */ + DASSERT(self != NULL, 0, PPP_ERROR, "Self is NULL !!!\n"); + + /* Check if we are connected */ + if(self->ttp_open == 0) + { +#ifdef CONNECT_IN_SEND + /* Let's try to connect one more time... */ + /* Note : we won't connect fully yet, but we should be ready for + * next packet... */ + /* Note : we can't do that, we need to have a process context to + * go through interruptible_sleep_on() in irnet_find_lsap_sel() + * We need to find another way... */ + irda_irnet_connect(self); +#endif CONNECT_IN_SEND + + DEBUG(PPP_INFO, "IrTTP not ready ! (%d-0x%X)\n", + self->ttp_open, (unsigned int) self->tsap); + + /* Note : we can either drop the packet or block the packet. + * + * Blocking the packet allow us a better connection time, + * because by calling ppp_output_wakeup() we can have + * ppp_generic resending the LCP request immediately to us, + * rather than waiting for one of pppd periodic transmission of + * LCP request. + * + * On the other hand, if we block all packet, all those periodic + * transmissions of pppd accumulate in ppp_generic, creating a + * backlog of LCP request. When we eventually connect later on, + * we have to transmit all this backlog before we can connect + * proper (if we don't timeout before). + * + * The current strategy is as follow : + * While we are attempting to connect, we block packets to get + * a better connection time. + * If we fail to connect, we drain the queue and start dropping packets + */ +#ifdef BLOCK_WHEN_CONNECT + /* If we are attempting to connect */ + if(self->tsap) + { + /* Blocking packet, ppp_generic will retry later */ + return 0; + } +#endif BLOCK_WHEN_CONNECT + + /* Dropping packet, pppd will retry later */ + dev_kfree_skb(skb); + return 1; + } + + /* Check if the queue can accept any packet, otherwise block */ + if(self->tx_flow != FLOW_START) + DRETURN(0, PPP_INFO, "IrTTP queue full (%d skbs)...\n", + skb_queue_len(&self->tsap->tx_queue)); + + /* Prepare ppp frame for transmission */ + skb = irnet_prepare_skb(self, skb); + DABORT(skb == NULL, 1, PPP_ERROR, "Prepare skb for Tx failed.\n"); + + /* Send the packet to IrTTP */ + ret = irttp_data_request(self->tsap, 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 + * Correction : we verify the flow control above (self->tx_flow), + * so we come here only if IrTTP doesn't like the packet (empty, + * too large, IrTTP not connected). In those rare cases, it's ok + * to drop it, we don't want to see it here again... + * Jean II + */ + DERROR(PPP_ERROR, "IrTTP doesn't like this packet !!! (0x%X)\n", ret); + dev_kfree_skb(skb); + } + + DEXIT(PPP_TRACE, "\n"); + return 1; /* Packet has been consumed */ +} + +/*------------------------------------------------------------------*/ +/* + * Take care of the ioctls that ppp_generic doesn't want to deal with... + * Note : we are also called from dev_irnet_ioctl(). + */ +static int +ppp_irnet_ioctl(struct ppp_channel * chan, + unsigned int cmd, + unsigned long arg) +{ + irnet_socket * ap = (struct irnet_socket *) chan->private; + int err; + int val; + u32 accm[8]; + + DENTER(PPP_TRACE, "(channel=0x%X, ap=0x%X, cmd=0x%X)\n", + (unsigned int) chan, (unsigned int) ap, cmd); + + /* Basic checks... */ + DASSERT(ap != NULL, -ENXIO, PPP_ERROR, "ap is NULL...\n"); + + err = -EFAULT; + switch(cmd) + { + /* PPP flags */ + case PPPIOCGFLAGS: + val = ap->flags | ap->rbits; + if(put_user(val, (int *) arg)) + break; + err = 0; + break; + case PPPIOCSFLAGS: + if(get_user(val, (int *) arg)) + break; + ap->flags = val & ~SC_RCV_BITS; + ap->rbits = val & SC_RCV_BITS; + err = 0; + break; + + /* Async map stuff - all dummy to please pppd */ + case PPPIOCGASYNCMAP: + if(put_user(ap->xaccm[0], (u32 *) arg)) + break; + err = 0; + break; + case PPPIOCSASYNCMAP: + if(get_user(ap->xaccm[0], (u32 *) arg)) + break; + err = 0; + break; + case PPPIOCGRASYNCMAP: + if(put_user(ap->raccm, (u32 *) arg)) + break; + err = 0; + break; + case PPPIOCSRASYNCMAP: + if(get_user(ap->raccm, (u32 *) arg)) + break; + err = 0; + break; + case PPPIOCGXASYNCMAP: + if(copy_to_user((void *) arg, ap->xaccm, sizeof(ap->xaccm))) + break; + err = 0; + break; + case PPPIOCSXASYNCMAP: + if(copy_from_user(accm, (void *) arg, sizeof(accm))) + break; + accm[2] &= ~0x40000000U; /* can't escape 0x5e */ + accm[3] |= 0x60000000U; /* must escape 0x7d, 0x7e */ + memcpy(ap->xaccm, accm, sizeof(ap->xaccm)); + err = 0; + break; + + /* Max PPP frame size */ + case PPPIOCGMRU: + if(put_user(ap->mru, (int *) arg)) + break; + err = 0; + break; + case PPPIOCSMRU: + if(get_user(val, (int *) arg)) + break; + if(val < PPP_MRU) + val = PPP_MRU; + ap->mru = val; + err = 0; + break; + + default: + DEBUG(PPP_INFO, "Unsupported ioctl (0x%X)\n", cmd); + err = -ENOIOCTLCMD; + } + + DEXIT(PPP_TRACE, " - err = 0x%X\n", err); + return err; +} + +/************************** INITIALISATION **************************/ +/* + * Module initialisation and all that jazz... + */ + +/*------------------------------------------------------------------*/ +/* + * Hook our device callbacks in the filesystem, to connect our code + * to /dev/irnet + */ +int +ppp_irnet_init(void) +{ + int err = 0; + + DENTER(MODULE_TRACE, "()\n"); + + /* Allocate ourselves as a minor in the misc range */ + err = misc_register(&irnet_misc_device); + + DEXIT(MODULE_TRACE, "\n"); + return err; +} + +/*------------------------------------------------------------------*/ +/* + * Cleanup at exit... + */ +void +ppp_irnet_cleanup(void) +{ + DENTER(MODULE_TRACE, "()\n"); + + /* De-allocate /dev/irnet minor in misc range */ + misc_deregister(&irnet_misc_device); + + DEXIT(MODULE_TRACE, "\n"); +} + +#ifdef MODULE +/*------------------------------------------------------------------*/ +/* + * Module main entry point + */ +int +init_module(void) +{ + int err; + + /* Initialise both parts... */ + err = irda_irnet_init(); + if(!err) + err = ppp_irnet_init(); + return err; +} + +/*------------------------------------------------------------------*/ +/* + * Module exit + */ +void +cleanup_module(void) +{ + irda_irnet_cleanup(); + return ppp_irnet_cleanup(); +} +#endif /* MODULE */ |