From 709c4d66e0b107ca606941b988bad717c0b45d9b Mon Sep 17 00:00:00 2001 From: Denys Dmytriyenko Date: Tue, 17 Mar 2009 14:32:59 -0400 Subject: rename packages/ to recipes/ per earlier agreement See links below for more details: http://thread.gmane.org/gmane.comp.handhelds.openembedded/21326 http://thread.gmane.org/gmane.comp.handhelds.openembedded/21816 Signed-off-by: Denys Dmytriyenko Acked-by: Mike Westerhof Acked-by: Philip Balister Acked-by: Khem Raj Acked-by: Marcin Juszkiewicz Acked-by: Koen Kooi Acked-by: Frans Meulenbroeks --- recipes/linux/files/ipaq-hal.init | 14 + recipes/linux/files/ir240_sys_max_tx-2.diff | 110 + recipes/linux/files/ir241_qos_param-2.diff | 164 + recipes/linux/files/iw240_we15-6.diff | 399 + recipes/linux/files/iw240_we18-5.diff | 421 + recipes/linux/files/iw241_we16-6.diff | 667 + recipes/linux/files/iw249_we16-6.diff | 670 + recipes/linux/files/iw249_we17-13.diff | 768 + recipes/linux/files/iw_handlers.w13-5.diff | 1513 + recipes/linux/files/iw_handlers.w14-5.diff | 838 + recipes/linux/files/linux-2.4-cpufreq.patch | 20 + recipes/linux/files/linux-2.4-mmc-debugging.patch | 15 + recipes/linux/files/linux-2.4-no-short-loads.patch | 18 + recipes/linux/files/linux-2.4-usb-gadget.patch | 29506 +++++++++++++++++++ recipes/linux/files/linux-2.4.18-list_move.patch | 32 + recipes/linux/files/mipv6-1.1-v2.4.25.patch | 19832 +++++++++++++ recipes/linux/files/mipv6-1.1-v2.4.27.patch | 19736 +++++++++++++ recipes/linux/files/usb-gadget-ether-compat.patch | 30 + 18 files changed, 74753 insertions(+) create mode 100644 recipes/linux/files/ipaq-hal.init create mode 100644 recipes/linux/files/ir240_sys_max_tx-2.diff create mode 100644 recipes/linux/files/ir241_qos_param-2.diff create mode 100644 recipes/linux/files/iw240_we15-6.diff create mode 100644 recipes/linux/files/iw240_we18-5.diff create mode 100644 recipes/linux/files/iw241_we16-6.diff create mode 100644 recipes/linux/files/iw249_we16-6.diff create mode 100644 recipes/linux/files/iw249_we17-13.diff create mode 100644 recipes/linux/files/iw_handlers.w13-5.diff create mode 100644 recipes/linux/files/iw_handlers.w14-5.diff create mode 100644 recipes/linux/files/linux-2.4-cpufreq.patch create mode 100644 recipes/linux/files/linux-2.4-mmc-debugging.patch create mode 100644 recipes/linux/files/linux-2.4-no-short-loads.patch create mode 100644 recipes/linux/files/linux-2.4-usb-gadget.patch create mode 100644 recipes/linux/files/linux-2.4.18-list_move.patch create mode 100644 recipes/linux/files/mipv6-1.1-v2.4.25.patch create mode 100644 recipes/linux/files/mipv6-1.1-v2.4.27.patch create mode 100644 recipes/linux/files/usb-gadget-ether-compat.patch (limited to 'recipes/linux/files') diff --git a/recipes/linux/files/ipaq-hal.init b/recipes/linux/files/ipaq-hal.init new file mode 100644 index 0000000000..4efb52ec97 --- /dev/null +++ b/recipes/linux/files/ipaq-hal.init @@ -0,0 +1,14 @@ +#!/bin/sh + +# make sure update-modules has been run +# since the calls below depend on aliases... +if [ ! -f /etc/modules.conf ]; then + update-modules || true +fi + +modprobe ipaq_hal || exit 0 + +if [ -d /proc/hal ]; then + model=`cat /proc/hal/model` + modprobe ipaq_hal_$model +fi diff --git a/recipes/linux/files/ir240_sys_max_tx-2.diff b/recipes/linux/files/ir240_sys_max_tx-2.diff new file mode 100644 index 0000000000..5f1307d7dc --- /dev/null +++ b/recipes/linux/files/ir240_sys_max_tx-2.diff @@ -0,0 +1,110 @@ +--- linux/net/irda/irsysctl.c.orig 2003-05-13 11:20:16.000000000 +0200 ++++ linux/net/irda/irsysctl.c 2005-01-22 18:39:40.496001712 +0100 +@@ -40,7 +40,8 @@ + + enum { DISCOVERY=1, DEVNAME, DEBUG, FAST_POLL, DISCOVERY_SLOTS, + DISCOVERY_TIMEOUT, SLOT_TIMEOUT, MAX_BAUD_RATE, MIN_TX_TURN_TIME, +- MAX_NOREPLY_TIME, WARN_NOREPLY_TIME, LAP_KEEPALIVE_TIME, SPECIFIC_DEV }; ++ MAX_TX_DATA_SIZE, MAX_NOREPLY_TIME, WARN_NOREPLY_TIME, LAP_KEEPALIVE_TIME, ++ SPECIFIC_DEV }; + + extern int sysctl_discovery; + extern int sysctl_discovery_slots; +@@ -51,6 +52,7 @@ + extern char sysctl_devname[]; + extern int sysctl_max_baud_rate; + extern int sysctl_min_tx_turn_time; ++extern int sysctl_max_tx_data_size; + extern int sysctl_max_noreply_time; + extern int sysctl_warn_noreply_time; + extern int sysctl_lap_keepalive_time; +@@ -71,6 +73,8 @@ + static int min_max_baud_rate = 2400; + static int max_min_tx_turn_time = 10000; /* See qos.c - IrLAP spec */ + static int min_min_tx_turn_time = 0; ++static int max_max_tx_data_size = 2048; /* See qos.c - IrLAP spec */ ++static int min_max_tx_data_size = 64; + static int max_max_noreply_time = 40; /* See qos.c - IrLAP spec */ + static int min_max_noreply_time = 3; + static int max_warn_noreply_time = 3; /* 3s == standard */ +@@ -128,6 +132,9 @@ + { MIN_TX_TURN_TIME, "min_tx_turn_time", &sysctl_min_tx_turn_time, + sizeof(int), 0644, NULL, &proc_dointvec_minmax, &sysctl_intvec, + NULL, &min_min_tx_turn_time, &max_min_tx_turn_time }, ++ { MAX_TX_DATA_SIZE, "max_tx_data_size", &sysctl_max_tx_data_size, ++ sizeof(int), 0644, NULL, &proc_dointvec_minmax, &sysctl_intvec, ++ NULL, &min_max_tx_data_size, &max_max_tx_data_size }, + { MAX_NOREPLY_TIME, "max_noreply_time", &sysctl_max_noreply_time, + sizeof(int), 0644, NULL, &proc_dointvec_minmax, &sysctl_intvec, + NULL, &min_max_noreply_time, &max_max_noreply_time }, +--- linux/net/irda/qos.c.orig 2003-05-13 11:20:16.000000000 +0200 ++++ linux/net/irda/qos.c 2005-01-22 18:36:46.759413688 +0100 +@@ -60,10 +60,26 @@ + * Nonzero values (usec) are used as lower limit to the per-connection + * mtt value which was announced by the other end during negotiation. + * Might be helpful if the peer device provides too short mtt. +- * Default is 10 which means using the unmodified value given by the peer +- * except if it's 0 (0 is likely a bug in the other stack). ++ * Default is 10us which means using the unmodified value given by the ++ * peer except if it's 0 (0 is likely a bug in the other stack). + */ + unsigned sysctl_min_tx_turn_time = 10; ++/* ++ * Maximum data size to be used in transmission in payload of LAP frame. ++ * There is a bit of confusion in the IrDA spec : ++ * The LAP spec defines the payload of a LAP frame (I field) to be ++ * 2048 bytes max (IrLAP 1.1, chapt 6.6.5, p40). ++ * On the other hand, the PHY mention frames of 2048 bytes max (IrPHY ++ * 1.2, chapt 5.3.2.1, p41). But, this number includes the LAP header ++ * (2 bytes), and CRC (32 bits at 4 Mb/s). So, for the I field (LAP ++ * payload), that's only 2042 bytes. Oups ! ++ * I've had trouble trouble transmitting 2048 bytes frames with USB ++ * dongles and nsc-ircc at 4 Mb/s, so adjust to 2042... I don't know ++ * if this bug applies only for 2048 bytes frames or all negociated ++ * frame sizes, but all hardware seem to support "2048 bytes" frames. ++ * You can use the sysctl to play with this value anyway. ++ * Jean II */ ++unsigned sysctl_max_tx_data_size = 2042; + + /* + * Specific device list limits some negotiation parameters at the connection +@@ -398,10 +414,10 @@ + while ((qos->data_size.value > line_capacity) && (index > 0)) { + qos->data_size.value = data_sizes[index--]; + IRDA_DEBUG(2, __FUNCTION__ +- "(), redusing data size to %d\n", ++ "(), reducing data size to %d\n", + qos->data_size.value); + } +-#else /* Use method descibed in section 6.6.11 of IrLAP */ ++#else /* Use method described in section 6.6.11 of IrLAP */ + while (irlap_requested_line_capacity(qos) > line_capacity) { + ASSERT(index != 0, return;); + +@@ -409,18 +425,24 @@ + if (qos->window_size.value > 1) { + qos->window_size.value--; + IRDA_DEBUG(2, __FUNCTION__ +- "(), redusing window size to %d\n", ++ "(), reducing window size to %d\n", + qos->window_size.value); + } else if (index > 1) { + qos->data_size.value = data_sizes[index--]; + IRDA_DEBUG(2, __FUNCTION__ +- "(), redusing data size to %d\n", ++ "(), reducing data size to %d\n", + qos->data_size.value); + } else { + WARNING(__FUNCTION__ "(), nothing more we can do!\n"); + } + } + #endif /* CONFIG_IRDA_DYNAMIC_WINDOW */ ++ /* ++ * Fix tx data size according to user limits - Jean II ++ */ ++ if (qos->data_size.value > sysctl_max_tx_data_size) ++ /* Allow non discrete adjustement to avoid loosing capacity */ ++ qos->data_size.value = sysctl_max_tx_data_size; + } + + /* diff --git a/recipes/linux/files/ir241_qos_param-2.diff b/recipes/linux/files/ir241_qos_param-2.diff new file mode 100644 index 0000000000..dfe77c52b0 --- /dev/null +++ b/recipes/linux/files/ir241_qos_param-2.diff @@ -0,0 +1,164 @@ +--- linux/net/irda/qos.c.orig 2005-01-22 19:19:56.013787192 +0100 ++++ linux/net/irda/qos.c 2005-01-22 19:21:13.493008560 +0100 +@@ -73,13 +73,18 @@ + * 1.2, chapt 5.3.2.1, p41). But, this number includes the LAP header + * (2 bytes), and CRC (32 bits at 4 Mb/s). So, for the I field (LAP + * payload), that's only 2042 bytes. Oups ! +- * I've had trouble trouble transmitting 2048 bytes frames with USB +- * dongles and nsc-ircc at 4 Mb/s, so adjust to 2042... I don't know +- * if this bug applies only for 2048 bytes frames or all negociated +- * frame sizes, but all hardware seem to support "2048 bytes" frames. +- * You can use the sysctl to play with this value anyway. ++ * My nsc-ircc hardware has troubles receiving 2048 bytes frames at 4 Mb/s, ++ * so adjust to 2042... I don't know if this bug applies only for 2048 ++ * bytes frames or all negociated frame sizes, but you can use the sysctl ++ * to play with this value anyway. + * Jean II */ + unsigned sysctl_max_tx_data_size = 2042; ++/* ++ * Maximum transmit window, i.e. number of LAP frames between turn-around. ++ * This allow to override what the peer told us. Some peers are buggy and ++ * don't always support what they tell us. ++ * Jean II */ ++unsigned sysctl_max_tx_window = 7; + + /* + * Specific device list limits some negotiation parameters at the connection +@@ -227,7 +232,19 @@ + { + __u16 msb = 0x8000; + int index = 15; /* Current MSB */ +- ++ ++ /* Check for buggy peers. ++ * Note : there is a small probability that it could be us, but I ++ * would expect driver authors to catch that pretty early and be ++ * able to check precisely what's going on. If a end user sees this, ++ * it's very likely the peer. - Jean II */ ++ if (word == 0) { ++ WARNING("%s(), Detected buggy peer, adjust null PV to 0x1!\n", ++ __FUNCTION__); ++ /* The only safe choice (we don't know the array size) */ ++ word = 0x1; ++ } ++ + while (msb) { + if (word & msb) + break; /* Found it! */ +@@ -378,10 +395,14 @@ + + /* + * Make sure the mintt is sensible. ++ * Main culprit : Ericsson T39. - Jean II + */ + if (sysctl_min_tx_turn_time > qos->min_turn_time.value) { + int i; + ++ WARNING("%s(), Detected buggy peer, adjust mtt to %dus!\n", ++ __FUNCTION__, sysctl_min_tx_turn_time); ++ + /* We don't really need bits, but easier this way */ + i = value_highest_bit(sysctl_min_tx_turn_time, min_turn_times, + 8, &qos->min_turn_time.bits); +@@ -443,6 +464,11 @@ + if (qos->data_size.value > sysctl_max_tx_data_size) + /* Allow non discrete adjustement to avoid loosing capacity */ + qos->data_size.value = sysctl_max_tx_data_size; ++ /* ++ * Override Tx window if user request it. - Jean II ++ */ ++ if (qos->window_size.value > sysctl_max_tx_window) ++ qos->window_size.value = sysctl_max_tx_window; + } + + /* +--- linux/net/irda/irsysctl.c.orig 2005-01-22 19:19:56.006788256 +0100 ++++ linux/net/irda/irsysctl.c 2005-01-22 19:24:31.273941288 +0100 +@@ -40,8 +40,8 @@ + + enum { DISCOVERY=1, DEVNAME, DEBUG, FAST_POLL, DISCOVERY_SLOTS, + DISCOVERY_TIMEOUT, SLOT_TIMEOUT, MAX_BAUD_RATE, MIN_TX_TURN_TIME, +- MAX_TX_DATA_SIZE, MAX_NOREPLY_TIME, WARN_NOREPLY_TIME, LAP_KEEPALIVE_TIME, +- SPECIFIC_DEV }; ++ MAX_TX_DATA_SIZE, MAX_TX_WINDOW, MAX_NOREPLY_TIME, WARN_NOREPLY_TIME, ++ LAP_KEEPALIVE_TIME, SPECIFIC_DEV }; + + extern int sysctl_discovery; + extern int sysctl_discovery_slots; +@@ -53,6 +53,7 @@ + extern int sysctl_max_baud_rate; + extern int sysctl_min_tx_turn_time; + extern int sysctl_max_tx_data_size; ++extern int sysctl_max_tx_window; + extern int sysctl_max_noreply_time; + extern int sysctl_warn_noreply_time; + extern int sysctl_lap_keepalive_time; +@@ -75,6 +76,8 @@ + static int min_min_tx_turn_time = 0; + static int max_max_tx_data_size = 2048; /* See qos.c - IrLAP spec */ + static int min_max_tx_data_size = 64; ++static int max_max_tx_window = 7; /* See qos.c - IrLAP spec */ ++static int min_max_tx_window = 1; + static int max_max_noreply_time = 40; /* See qos.c - IrLAP spec */ + static int min_max_noreply_time = 3; + static int max_warn_noreply_time = 3; /* 3s == standard */ +@@ -135,6 +138,9 @@ + { MAX_TX_DATA_SIZE, "max_tx_data_size", &sysctl_max_tx_data_size, + sizeof(int), 0644, NULL, &proc_dointvec_minmax, &sysctl_intvec, + NULL, &min_max_tx_data_size, &max_max_tx_data_size }, ++ { MAX_TX_WINDOW, "max_tx_window", &sysctl_max_tx_window, ++ sizeof(int), 0644, NULL, &proc_dointvec_minmax, &sysctl_intvec, ++ NULL, &min_max_tx_window, &max_max_tx_window }, + { MAX_NOREPLY_TIME, "max_noreply_time", &sysctl_max_noreply_time, + sizeof(int), 0644, NULL, &proc_dointvec_minmax, &sysctl_intvec, + NULL, &min_max_noreply_time, &max_max_noreply_time }, +--- linux/net/irda/parameters.c.orig 2003-05-13 11:20:16.000000000 +0200 ++++ linux/net/irda/parameters.c 2005-01-22 19:21:13.527003392 +0100 +@@ -204,11 +204,13 @@ + { + irda_param_t p; + int n = 0; ++ int extract_len; /* Real lenght we extract */ + int err; + + p.pi = pi; /* In case handler needs to know */ + p.pl = buf[1]; /* Extract lenght of value */ + p.pv.i = 0; /* Clear value */ ++ extract_len = p.pl; /* Default : extract all */ + + /* Check if buffer is long enough for parsing */ + if (len < (2+p.pl)) { +@@ -220,18 +222,30 @@ + /* + * Check that the integer length is what we expect it to be. If the + * handler want a 16 bits integer then a 32 bits is not good enough ++ * PV_INTEGER means that the handler is flexible. + */ + if (((type & PV_MASK) != PV_INTEGER) && ((type & PV_MASK) != p.pl)) { + ERROR(__FUNCTION__ "(), invalid parameter length! " + "Expected %d bytes, but value had %d bytes!\n", + type & PV_MASK, p.pl); + +- /* Skip parameter */ +- return p.pl+2; ++ /* Most parameters are bit/byte fields or little endian, ++ * so it's ok to only extract a subset of it (the subset ++ * that the handler expect). This is necessary, as some ++ * broken implementations seems to add extra undefined bits. ++ * If the parameter is shorter than we expect or is big ++ * endian, we can't play those tricks. Jean II */ ++ if((p.pl < (type & PV_MASK)) || (type & PV_BIG_ENDIAN)) { ++ /* Skip parameter */ ++ return p.pl+2; ++ } else { ++ /* Extract subset of it, fallthrough */ ++ extract_len = type & PV_MASK; ++ } + } + + +- switch (p.pl) { ++ switch (extract_len) { + case 1: + n += irda_param_unpack(buf+2, "b", &p.pv.i); + break; diff --git a/recipes/linux/files/iw240_we15-6.diff b/recipes/linux/files/iw240_we15-6.diff new file mode 100644 index 0000000000..2ebfd8ec12 --- /dev/null +++ b/recipes/linux/files/iw240_we15-6.diff @@ -0,0 +1,399 @@ +diff -u -p linux/include/linux/wireless.14.h linux/include/linux/wireless.h +--- linux/include/linux/wireless.14.h Mon Dec 2 18:51:00 2002 ++++ linux/include/linux/wireless.h Mon Dec 2 18:53:35 2002 +@@ -1,7 +1,7 @@ + /* + * This file define a set of standard wireless extensions + * +- * Version : 14 25.1.02 ++ * Version : 15 12.7.02 + * + * Authors : Jean Tourrilhes - HPL - + * Copyright (c) 1997-2002 Jean Tourrilhes, All Rights Reserved. +@@ -80,7 +80,7 @@ + * (there is some stuff that will be added in the future...) + * I just plan to increment with each new version. + */ +-#define WIRELESS_EXT 14 ++#define WIRELESS_EXT 15 + + /* + * Changes : +@@ -153,17 +153,32 @@ + * - Define additional specific event numbers + * - Add "addr" and "param" fields in union iwreq_data + * - AP scanning stuff (SIOCSIWSCAN and friends) ++ * ++ * V14 to V15 ++ * ---------- ++ * - Add IW_PRIV_TYPE_ADDR for struct sockaddr private arg ++ * - Make struct iw_freq signed (both m & e), add explicit padding ++ * - Add IWEVCUSTOM for driver specific event/scanning token ++ * - Add IW_MAX_GET_SPY for driver returning a lot of addresses ++ * - Add IW_TXPOW_RANGE for range of Tx Powers ++ * - Add IWEVREGISTERED & IWEVEXPIRED events for Access Points ++ * - Add IW_MODE_MONITOR for passive monitor + */ + + /**************************** CONSTANTS ****************************/ + + /* -------------------------- IOCTL LIST -------------------------- */ + +-/* Basic operations */ ++/* Wireless Identification */ + #define SIOCSIWCOMMIT 0x8B00 /* Commit pending changes to driver */ + #define SIOCGIWNAME 0x8B01 /* get name == wireless protocol */ +-#define SIOCSIWNWID 0x8B02 /* set network id (the cell) */ +-#define SIOCGIWNWID 0x8B03 /* get network id */ ++/* SIOCGIWNAME is used to verify the presence of Wireless Extensions. ++ * Common values : "IEEE 802.11-DS", "IEEE 802.11-FH", "IEEE 802.11b"... ++ * Don't put the name of your driver there, it's useless. */ ++ ++/* Basic operations */ ++#define SIOCSIWNWID 0x8B02 /* set network id (pre-802.11) */ ++#define SIOCGIWNWID 0x8B03 /* get network id (the cell) */ + #define SIOCSIWFREQ 0x8B04 /* set channel/frequency (Hz) */ + #define SIOCGIWFREQ 0x8B05 /* get channel/frequency (Hz) */ + #define SIOCSIWMODE 0x8B06 /* set operation mode */ +@@ -178,16 +193,18 @@ + #define SIOCGIWPRIV 0x8B0D /* get private ioctl interface info */ + #define SIOCSIWSTATS 0x8B0E /* Unused */ + #define SIOCGIWSTATS 0x8B0F /* Get /proc/net/wireless stats */ ++/* SIOCGIWSTATS is strictly used between user space and the kernel, and ++ * is never passed to the driver (i.e. the driver will never see it). */ + +-/* Mobile IP support */ ++/* Mobile IP support (statistics per MAC address) */ + #define SIOCSIWSPY 0x8B10 /* set spy addresses */ + #define SIOCGIWSPY 0x8B11 /* get spy info (quality of link) */ + + /* Access Point manipulation */ + #define SIOCSIWAP 0x8B14 /* set access point MAC addresses */ + #define SIOCGIWAP 0x8B15 /* get access point MAC addresses */ +-#define SIOCGIWAPLIST 0x8B17 /* get list of access point in range */ +-#define SIOCSIWSCAN 0x8B18 /* trigger scanning */ ++#define SIOCGIWAPLIST 0x8B17 /* Deprecated in favor of scanning */ ++#define SIOCSIWSCAN 0x8B18 /* trigger scanning (list cells) */ + #define SIOCGIWSCAN 0x8B19 /* get scanning results */ + + /* 802.11 specific support */ +@@ -197,9 +214,7 @@ + #define SIOCGIWNICKN 0x8B1D /* get node name/nickname */ + /* As the ESSID and NICKN are strings up to 32 bytes long, it doesn't fit + * within the 'iwreq' structure, so we need to use the 'data' member to +- * point to a string in user space, like it is done for RANGE... +- * The "flags" member indicate if the ESSID is active or not (promiscuous). +- */ ++ * point to a string in user space, like it is done for RANGE... */ + + /* Other parameters useful in 802.11 and some other devices */ + #define SIOCSIWRATE 0x8B20 /* set default bit rate (bps) */ +@@ -257,7 +272,10 @@ + /* Most events use the same identifier as ioctl requests */ + + #define IWEVTXDROP 0x8C00 /* Packet dropped to excessive retry */ +-#define IWEVQUAL 0x8C01 /* Quality part of statistics */ ++#define IWEVQUAL 0x8C01 /* Quality part of statistics (scan) */ ++#define IWEVCUSTOM 0x8C02 /* Driver specific ascii string */ ++#define IWEVREGISTERED 0x8C03 /* Discovered a new node (AP mode) */ ++#define IWEVEXPIRED 0x8C04 /* Expired a node (AP mode) */ + + #define IWEVFIRST 0x8C00 + +@@ -273,7 +291,8 @@ + #define IW_PRIV_TYPE_BYTE 0x1000 /* Char as number */ + #define IW_PRIV_TYPE_CHAR 0x2000 /* Char as character */ + #define IW_PRIV_TYPE_INT 0x4000 /* 32 bits int */ +-#define IW_PRIV_TYPE_FLOAT 0x5000 ++#define IW_PRIV_TYPE_FLOAT 0x5000 /* struct iw_freq */ ++#define IW_PRIV_TYPE_ADDR 0x6000 /* struct sockaddr */ + + #define IW_PRIV_SIZE_FIXED 0x0800 /* Variable or fixed nuber of args */ + +@@ -297,13 +316,16 @@ + + /* Maximum tx powers in the range struct */ + #define IW_MAX_TXPOWER 8 ++/* Note : if you more than 8 TXPowers, just set the max and min or ++ * a few of them in the struct iw_range. */ + + /* Maximum of address that you may set with SPY */ +-#define IW_MAX_SPY 8 ++#define IW_MAX_SPY 8 /* set */ ++#define IW_MAX_GET_SPY 64 /* get */ + + /* Maximum of address that you may get in the + list of access points in range */ +-#define IW_MAX_AP 8 ++#define IW_MAX_AP 64 + + /* Maximum size of the ESSID and NICKN strings */ + #define IW_ESSID_MAX_SIZE 32 +@@ -315,6 +337,7 @@ + #define IW_MODE_MASTER 3 /* Synchronisation master or Access Point */ + #define IW_MODE_REPEAT 4 /* Wireless Repeater (forwarder) */ + #define IW_MODE_SECOND 5 /* Secondary master/repeater (backup) */ ++#define IW_MODE_MONITOR 6 /* Passive monitor (listen only) */ + + /* Maximum number of size of encoding token available + * they are listed in the range structure */ +@@ -350,8 +373,10 @@ + #define IW_POWER_RELATIVE 0x0004 /* Value is not in seconds/ms/us */ + + /* Transmit Power flags available */ ++#define IW_TXPOW_TYPE 0x00FF /* Type of value */ + #define IW_TXPOW_DBM 0x0000 /* Value is in dBm */ + #define IW_TXPOW_MWATT 0x0001 /* Value is in mW */ ++#define IW_TXPOW_RANGE 0x1000 /* Range of value between min/max */ + + /* Retry limits and lifetime flags available */ + #define IW_RETRY_ON 0x0000 /* No details... */ +@@ -376,6 +401,9 @@ + /* Maximum size of returned data */ + #define IW_SCAN_MAX_DATA 4096 /* In bytes */ + ++/* Max number of char in custom event - use multiple of them if needed */ ++#define IW_CUSTOM_MAX 256 /* In bytes */ ++ + /****************************** TYPES ******************************/ + + /* --------------------------- SUBTYPES --------------------------- */ +@@ -411,9 +439,10 @@ struct iw_point + */ + struct iw_freq + { +- __u32 m; /* Mantissa */ +- __u16 e; /* Exponent */ ++ __s32 m; /* Mantissa */ ++ __s16 e; /* Exponent */ + __u8 i; /* List index (when in range struct) */ ++ __u8 pad; /* Unused - just for alignement */ + }; + + /* +diff -u -p linux/include/net/iw_handler.14.h linux/include/net/iw_handler.h +--- linux/include/net/iw_handler.14.h Mon Dec 2 18:51:17 2002 ++++ linux/include/net/iw_handler.h Mon Dec 2 18:54:51 2002 +@@ -1,7 +1,7 @@ + /* + * This file define the new driver API for Wireless Extensions + * +- * Version : 3 17.1.02 ++ * Version : 4 21.6.02 + * + * Authors : Jean Tourrilhes - HPL - + * Copyright (c) 2001-2002 Jean Tourrilhes, All Rights Reserved. +@@ -206,7 +206,7 @@ + * will be needed... + * I just plan to increment with each new version. + */ +-#define IW_HANDLER_VERSION 3 ++#define IW_HANDLER_VERSION 4 + + /* + * Changes : +@@ -217,6 +217,9 @@ + * - Add Wireless Event support : + * o wireless_send_event() prototype + * o iwe_stream_add_event/point() inline functions ++ * V3 to V4 ++ * -------- ++ * - Reshuffle IW_HEADER_TYPE_XXX to map IW_PRIV_TYPE_XXX changes + */ + + /**************************** CONSTANTS ****************************/ +@@ -233,10 +236,10 @@ + #define IW_HEADER_TYPE_CHAR 2 /* char [IFNAMSIZ] */ + #define IW_HEADER_TYPE_UINT 4 /* __u32 */ + #define IW_HEADER_TYPE_FREQ 5 /* struct iw_freq */ +-#define IW_HEADER_TYPE_POINT 6 /* struct iw_point */ +-#define IW_HEADER_TYPE_PARAM 7 /* struct iw_param */ +-#define IW_HEADER_TYPE_ADDR 8 /* struct sockaddr */ +-#define IW_HEADER_TYPE_QUAL 9 /* struct iw_quality */ ++#define IW_HEADER_TYPE_ADDR 6 /* struct sockaddr */ ++#define IW_HEADER_TYPE_POINT 8 /* struct iw_point */ ++#define IW_HEADER_TYPE_PARAM 9 /* struct iw_param */ ++#define IW_HEADER_TYPE_QUAL 10 /* struct iw_quality */ + + /* Handling flags */ + /* Most are not implemented. I just use them as a reminder of some +diff -u -p linux/net/core/wireless.14.c linux/net/core/wireless.c +--- linux/net/core/wireless.14.c Mon Dec 2 18:51:35 2002 ++++ linux/net/core/wireless.c Mon Dec 2 18:53:10 2002 +@@ -33,8 +33,16 @@ + * o Propagate events as rtnetlink IFLA_WIRELESS option + * o Generate event on selected SET requests + * +- * v4 - 18.04.01 - Jean II ++ * v4 - 18.04.02 - Jean II + * o Fix stupid off by one in iw_ioctl_description : IW_ESSID_MAX_SIZE + 1 ++ * ++ * v5 - 21.06.02 - Jean II ++ * o Add IW_PRIV_TYPE_ADDR in priv_type_size (+cleanup) ++ * o Reshuffle IW_HEADER_TYPE_XXX to map IW_PRIV_TYPE_XXX changes ++ * o Add IWEVCUSTOM for driver specific event/scanning token ++ * o Turn on WE_STRICT_WRITE by default + kernel warning ++ * o Fix WE_STRICT_WRITE in ioctl_export_private() (32 => iw_num) ++ * o Fix off-by-one in test (extra_size <= IFNAMSIZ) + */ + + /***************************** INCLUDES *****************************/ +@@ -50,8 +58,9 @@ + + /**************************** CONSTANTS ****************************/ + +-/* This will be turned on later on... */ +-#undef WE_STRICT_WRITE /* Check write buffer size */ ++/* Enough lenience, let's make sure things are proper... */ ++#define WE_STRICT_WRITE /* Check write buffer size */ ++/* I'll probably drop both the define and kernel message in the next version */ + + /* Debuging stuff */ + #undef WE_IOCTL_DEBUG /* Debug IOCTL API */ +@@ -106,7 +115,7 @@ static const struct iw_ioctl_description + /* SIOCSIWSPY */ + { IW_HEADER_TYPE_POINT, 0, sizeof(struct sockaddr), 0, IW_MAX_SPY, 0}, + /* SIOCGIWSPY */ +- { IW_HEADER_TYPE_POINT, 0, (sizeof(struct sockaddr) + sizeof(struct iw_quality)), 0, IW_MAX_SPY, 0}, ++ { IW_HEADER_TYPE_POINT, 0, (sizeof(struct sockaddr) + sizeof(struct iw_quality)), 0, IW_MAX_GET_SPY, 0}, + /* -- hole -- */ + { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, + /* -- hole -- */ +@@ -176,25 +185,41 @@ static const struct iw_ioctl_description + { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, 0}, + /* IWEVQUAL */ + { IW_HEADER_TYPE_QUAL, 0, 0, 0, 0, 0}, ++ /* IWEVCUSTOM */ ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_CUSTOM_MAX, 0}, ++ /* IWEVREGISTERED */ ++ { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, 0}, ++ /* IWEVEXPIRED */ ++ { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, 0}, + }; + static const int standard_event_num = (sizeof(standard_event) / + sizeof(struct iw_ioctl_description)); + + /* Size (in bytes) of the various private data types */ +-static const char priv_type_size[] = { 0, 1, 1, 0, 4, 4, 0, 0 }; ++static const char priv_type_size[] = { ++ 0, /* IW_PRIV_TYPE_NONE */ ++ 1, /* IW_PRIV_TYPE_BYTE */ ++ 1, /* IW_PRIV_TYPE_CHAR */ ++ 0, /* Not defined */ ++ sizeof(__u32), /* IW_PRIV_TYPE_INT */ ++ sizeof(struct iw_freq), /* IW_PRIV_TYPE_FLOAT */ ++ sizeof(struct sockaddr), /* IW_PRIV_TYPE_ADDR */ ++ 0, /* Not defined */ ++}; + + /* Size (in bytes) of various events */ + static const int event_type_size[] = { +- IW_EV_LCP_LEN, ++ IW_EV_LCP_LEN, /* IW_HEADER_TYPE_NULL */ ++ 0, ++ IW_EV_CHAR_LEN, /* IW_HEADER_TYPE_CHAR */ + 0, +- IW_EV_CHAR_LEN, ++ IW_EV_UINT_LEN, /* IW_HEADER_TYPE_UINT */ ++ IW_EV_FREQ_LEN, /* IW_HEADER_TYPE_FREQ */ ++ IW_EV_ADDR_LEN, /* IW_HEADER_TYPE_ADDR */ + 0, +- IW_EV_UINT_LEN, +- IW_EV_FREQ_LEN, + IW_EV_POINT_LEN, /* Without variable payload */ +- IW_EV_PARAM_LEN, +- IW_EV_ADDR_LEN, +- IW_EV_QUAL_LEN, ++ IW_EV_PARAM_LEN, /* IW_HEADER_TYPE_PARAM */ ++ IW_EV_QUAL_LEN, /* IW_HEADER_TYPE_QUAL */ + }; + + /************************ COMMON SUBROUTINES ************************/ +@@ -440,8 +465,10 @@ static inline int ioctl_export_private(s + return -EFAULT; + #ifdef WE_STRICT_WRITE + /* Check if there is enough buffer up there */ +- if(iwr->u.data.length < (SIOCIWLASTPRIV - SIOCIWFIRSTPRIV + 1)) ++ if(iwr->u.data.length < dev->wireless_handlers->num_private_args) { ++ printk(KERN_ERR "%s (WE) : Buffer for request SIOCGIWPRIV too small (%d<%d)\n", dev->name, iwr->u.data.length, dev->wireless_handlers->num_private_args); + return -E2BIG; ++ } + #endif /* WE_STRICT_WRITE */ + + /* Set the number of available ioctls. */ +@@ -471,6 +498,7 @@ static inline int ioctl_standard_call(st + const struct iw_ioctl_description * descr; + struct iw_request_info info; + int ret = -EINVAL; ++ int user_size = 0; + + /* Get the description of the IOCTL */ + if((cmd - SIOCIWFIRST) >= standard_ioctl_num) +@@ -518,11 +546,8 @@ static inline int ioctl_standard_call(st + /* Check NULL pointer */ + if(iwr->u.data.pointer == NULL) + return -EFAULT; +-#ifdef WE_STRICT_WRITE +- /* Check if there is enough buffer up there */ +- if(iwr->u.data.length < descr->max_tokens) +- return -E2BIG; +-#endif /* WE_STRICT_WRITE */ ++ /* Save user space buffer size for checking */ ++ user_size = iwr->u.data.length; + } + + #ifdef WE_IOCTL_DEBUG +@@ -559,6 +584,15 @@ static inline int ioctl_standard_call(st + + /* If we have something to return to the user */ + if (!ret && IW_IS_GET(cmd)) { ++#ifdef WE_STRICT_WRITE ++ /* Check if there is enough buffer up there */ ++ if(user_size < iwr->u.data.length) { ++ printk(KERN_ERR "%s (WE) : Buffer for request %04X too small (%d<%d)\n", dev->name, cmd, user_size, iwr->u.data.length); ++ kfree(extra); ++ return -E2BIG; ++ } ++#endif /* WE_STRICT_WRITE */ ++ + err = copy_to_user(iwr->u.data.pointer, extra, + iwr->u.data.length * + descr->token_size); +@@ -646,12 +680,18 @@ static inline int ioctl_private_call(str + /* Compute the size of the set/get arguments */ + if(descr != NULL) { + if(IW_IS_SET(cmd)) { ++ int offset = 0; /* For sub-ioctls */ ++ /* Check for sub-ioctl handler */ ++ if(descr->name[0] == '\0') ++ /* Reserve one int for sub-ioctl index */ ++ offset = sizeof(__u32); ++ + /* Size of set arguments */ + extra_size = get_priv_size(descr->set_args); + + /* Does it fits in iwr ? */ + if((descr->set_args & IW_PRIV_SIZE_FIXED) && +- (extra_size < IFNAMSIZ)) ++ ((extra_size + offset) <= IFNAMSIZ)) + extra_size = 0; + } else { + /* Size of set arguments */ +@@ -659,7 +699,7 @@ static inline int ioctl_private_call(str + + /* Does it fits in iwr ? */ + if((descr->get_args & IW_PRIV_SIZE_FIXED) && +- (extra_size < IFNAMSIZ)) ++ (extra_size <= IFNAMSIZ)) + extra_size = 0; + } + } +@@ -925,7 +965,7 @@ void wireless_send_event(struct net_devi + * The best the driver could do is to log an error message. + * We will do it ourselves instead... + */ +- printk(KERN_ERR "%s (WE) : Invalid Wireless Event (0x%04X)\n", ++ printk(KERN_ERR "%s (WE) : Invalid/Unknown Wireless Event (0x%04X)\n", + dev->name, cmd); + return; + } diff --git a/recipes/linux/files/iw240_we18-5.diff b/recipes/linux/files/iw240_we18-5.diff new file mode 100644 index 0000000000..f65987588d --- /dev/null +++ b/recipes/linux/files/iw240_we18-5.diff @@ -0,0 +1,421 @@ +diff -u -p linux/include/linux/wireless.we17.h linux/include/linux/wireless.h +--- linux/include/linux/wireless.we17.h 2005-05-20 11:25:49.000000000 -0700 ++++ linux/include/linux/wireless.h 2005-05-20 11:29:05.000000000 -0700 +@@ -1,10 +1,10 @@ + /* + * This file define a set of standard wireless extensions + * +- * Version : 17 21.6.04 ++ * Version : 18 12.3.05 + * + * Authors : Jean Tourrilhes - HPL - +- * Copyright (c) 1997-2004 Jean Tourrilhes, All Rights Reserved. ++ * Copyright (c) 1997-2005 Jean Tourrilhes, All Rights Reserved. + */ + + #ifndef _LINUX_WIRELESS_H +@@ -82,7 +82,7 @@ + * (there is some stuff that will be added in the future...) + * I just plan to increment with each new version. + */ +-#define WIRELESS_EXT 17 ++#define WIRELESS_EXT 18 + + /* + * Changes : +@@ -182,6 +182,21 @@ + * - Document (struct iw_quality *)->updated, add new flags (INVALID) + * - Wireless Event capability in struct iw_range + * - Add support for relative TxPower (yick !) ++ * ++ * V17 to V18 (From Jouni Malinen ) ++ * ---------- ++ * - Add support for WPA/WPA2 ++ * - Add extended encoding configuration (SIOCSIWENCODEEXT and ++ * SIOCGIWENCODEEXT) ++ * - Add SIOCSIWGENIE/SIOCGIWGENIE ++ * - Add SIOCSIWMLME ++ * - Add SIOCSIWPMKSA ++ * - Add struct iw_range bit field for supported encoding capabilities ++ * - Add optional scan request parameters for SIOCSIWSCAN ++ * - Add SIOCSIWAUTH/SIOCGIWAUTH for setting authentication and WPA ++ * related parameters (extensible up to 4096 parameter values) ++ * - Add wireless events: IWEVGENIE, IWEVMICHAELMICFAILURE, ++ * IWEVASSOCREQIE, IWEVASSOCRESPIE, IWEVPMKIDCAND + */ + + /**************************** CONSTANTS ****************************/ +@@ -256,6 +271,30 @@ + #define SIOCSIWPOWER 0x8B2C /* set Power Management settings */ + #define SIOCGIWPOWER 0x8B2D /* get Power Management settings */ + ++/* WPA : Generic IEEE 802.11 informatiom element (e.g., for WPA/RSN/WMM). ++ * This ioctl uses struct iw_point and data buffer that includes IE id and len ++ * fields. More than one IE may be included in the request. Setting the generic ++ * IE to empty buffer (len=0) removes the generic IE from the driver. Drivers ++ * are allowed to generate their own WPA/RSN IEs, but in these cases, drivers ++ * are required to report the used IE as a wireless event, e.g., when ++ * associating with an AP. */ ++#define SIOCSIWGENIE 0x8B30 /* set generic IE */ ++#define SIOCGIWGENIE 0x8B31 /* get generic IE */ ++ ++/* WPA : IEEE 802.11 MLME requests */ ++#define SIOCSIWMLME 0x8B16 /* request MLME operation; uses ++ * struct iw_mlme */ ++/* WPA : Authentication mode parameters */ ++#define SIOCSIWAUTH 0x8B32 /* set authentication mode params */ ++#define SIOCGIWAUTH 0x8B33 /* get authentication mode params */ ++ ++/* WPA : Extended version of encoding configuration */ ++#define SIOCSIWENCODEEXT 0x8B34 /* set encoding token & mode */ ++#define SIOCGIWENCODEEXT 0x8B35 /* get encoding token & mode */ ++ ++/* WPA2 : PMKSA cache management */ ++#define SIOCSIWPMKSA 0x8B36 /* PMKSA cache operation */ ++ + /* -------------------- DEV PRIVATE IOCTL LIST -------------------- */ + + /* These 32 ioctl are wireless device private, for 16 commands. +@@ -297,6 +336,34 @@ + #define IWEVCUSTOM 0x8C02 /* Driver specific ascii string */ + #define IWEVREGISTERED 0x8C03 /* Discovered a new node (AP mode) */ + #define IWEVEXPIRED 0x8C04 /* Expired a node (AP mode) */ ++#define IWEVGENIE 0x8C05 /* Generic IE (WPA, RSN, WMM, ..) ++ * (scan results); This includes id and ++ * length fields. One IWEVGENIE may ++ * contain more than one IE. Scan ++ * results may contain one or more ++ * IWEVGENIE events. */ ++#define IWEVMICHAELMICFAILURE 0x8C06 /* Michael MIC failure ++ * (struct iw_michaelmicfailure) ++ */ ++#define IWEVASSOCREQIE 0x8C07 /* IEs used in (Re)Association Request. ++ * The data includes id and length ++ * fields and may contain more than one ++ * IE. This event is required in ++ * Managed mode if the driver ++ * generates its own WPA/RSN IE. This ++ * should be sent just before ++ * IWEVREGISTERED event for the ++ * association. */ ++#define IWEVASSOCRESPIE 0x8C08 /* IEs used in (Re)Association ++ * Response. The data includes id and ++ * length fields and may contain more ++ * than one IE. This may be sent ++ * between IWEVASSOCREQIE and ++ * IWEVREGISTERED events for the ++ * association. */ ++#define IWEVPMKIDCAND 0x8C09 /* PMKID candidate for RSN ++ * pre-authentication ++ * (struct iw_pmkid_cand) */ + + #define IWEVFIRST 0x8C00 + +@@ -432,12 +499,94 @@ + #define IW_SCAN_THIS_MODE 0x0020 /* Scan only this Mode */ + #define IW_SCAN_ALL_RATE 0x0040 /* Scan all Bit-Rates */ + #define IW_SCAN_THIS_RATE 0x0080 /* Scan only this Bit-Rate */ ++/* struct iw_scan_req scan_type */ ++#define IW_SCAN_TYPE_ACTIVE 0 ++#define IW_SCAN_TYPE_PASSIVE 1 + /* Maximum size of returned data */ + #define IW_SCAN_MAX_DATA 4096 /* In bytes */ + + /* Max number of char in custom event - use multiple of them if needed */ + #define IW_CUSTOM_MAX 256 /* In bytes */ + ++/* Generic information element */ ++#define IW_GENERIC_IE_MAX 1024 ++ ++/* MLME requests (SIOCSIWMLME / struct iw_mlme) */ ++#define IW_MLME_DEAUTH 0 ++#define IW_MLME_DISASSOC 1 ++ ++/* SIOCSIWAUTH/SIOCGIWAUTH struct iw_param flags */ ++#define IW_AUTH_INDEX 0x0FFF ++#define IW_AUTH_FLAGS 0xF000 ++/* SIOCSIWAUTH/SIOCGIWAUTH parameters (0 .. 4095) ++ * (IW_AUTH_INDEX mask in struct iw_param flags; this is the index of the ++ * parameter that is being set/get to; value will be read/written to ++ * struct iw_param value field) */ ++#define IW_AUTH_WPA_VERSION 0 ++#define IW_AUTH_CIPHER_PAIRWISE 1 ++#define IW_AUTH_CIPHER_GROUP 2 ++#define IW_AUTH_KEY_MGMT 3 ++#define IW_AUTH_TKIP_COUNTERMEASURES 4 ++#define IW_AUTH_DROP_UNENCRYPTED 5 ++#define IW_AUTH_80211_AUTH_ALG 6 ++#define IW_AUTH_WPA_ENABLED 7 ++#define IW_AUTH_RX_UNENCRYPTED_EAPOL 8 ++#define IW_AUTH_ROAMING_CONTROL 9 ++#define IW_AUTH_PRIVACY_INVOKED 10 ++ ++/* IW_AUTH_WPA_VERSION values (bit field) */ ++#define IW_AUTH_WPA_VERSION_DISABLED 0x00000001 ++#define IW_AUTH_WPA_VERSION_WPA 0x00000002 ++#define IW_AUTH_WPA_VERSION_WPA2 0x00000004 ++ ++/* IW_AUTH_PAIRWISE_CIPHER and IW_AUTH_GROUP_CIPHER values (bit field) */ ++#define IW_AUTH_CIPHER_NONE 0x00000001 ++#define IW_AUTH_CIPHER_WEP40 0x00000002 ++#define IW_AUTH_CIPHER_TKIP 0x00000004 ++#define IW_AUTH_CIPHER_CCMP 0x00000008 ++#define IW_AUTH_CIPHER_WEP104 0x00000010 ++ ++/* IW_AUTH_KEY_MGMT values (bit field) */ ++#define IW_AUTH_KEY_MGMT_802_1X 1 ++#define IW_AUTH_KEY_MGMT_PSK 2 ++ ++/* IW_AUTH_80211_AUTH_ALG values (bit field) */ ++#define IW_AUTH_ALG_OPEN_SYSTEM 0x00000001 ++#define IW_AUTH_ALG_SHARED_KEY 0x00000002 ++#define IW_AUTH_ALG_LEAP 0x00000004 ++ ++/* IW_AUTH_ROAMING_CONTROL values */ ++#define IW_AUTH_ROAMING_ENABLE 0 /* driver/firmware based roaming */ ++#define IW_AUTH_ROAMING_DISABLE 1 /* user space program used for roaming ++ * control */ ++ ++/* SIOCSIWENCODEEXT definitions */ ++#define IW_ENCODE_SEQ_MAX_SIZE 8 ++/* struct iw_encode_ext ->alg */ ++#define IW_ENCODE_ALG_NONE 0 ++#define IW_ENCODE_ALG_WEP 1 ++#define IW_ENCODE_ALG_TKIP 2 ++#define IW_ENCODE_ALG_CCMP 3 ++/* struct iw_encode_ext ->ext_flags */ ++#define IW_ENCODE_EXT_TX_SEQ_VALID 0x00000001 ++#define IW_ENCODE_EXT_RX_SEQ_VALID 0x00000002 ++#define IW_ENCODE_EXT_GROUP_KEY 0x00000004 ++#define IW_ENCODE_EXT_SET_TX_KEY 0x00000008 ++ ++/* IWEVMICHAELMICFAILURE : struct iw_michaelmicfailure ->flags */ ++#define IW_MICFAILURE_KEY_ID 0x00000003 /* Key ID 0..3 */ ++#define IW_MICFAILURE_GROUP 0x00000004 ++#define IW_MICFAILURE_PAIRWISE 0x00000008 ++#define IW_MICFAILURE_STAKEY 0x00000010 ++#define IW_MICFAILURE_COUNT 0x00000060 /* 1 or 2 (0 = count not supported) ++ */ ++ ++/* Bit field values for enc_capa in struct iw_range */ ++#define IW_ENC_CAPA_WPA 0x00000001 ++#define IW_ENC_CAPA_WPA2 0x00000002 ++#define IW_ENC_CAPA_CIPHER_TKIP 0x00000004 ++#define IW_ENC_CAPA_CIPHER_CCMP 0x00000008 ++ + /* Event capability macros - in (struct iw_range *)->event_capa + * Because we have more than 32 possible events, we use an array of + * 32 bit bitmasks. Note : 32 bits = 0x20 = 2^5. */ +@@ -546,6 +695,132 @@ struct iw_thrspy + struct iw_quality high; /* High threshold */ + }; + ++/* ++ * Optional data for scan request ++ * ++ * Note: these optional parameters are controlling parameters for the ++ * scanning behavior, these do not apply to getting scan results ++ * (SIOCGIWSCAN). Drivers are expected to keep a local BSS table and ++ * provide a merged results with all BSSes even if the previous scan ++ * request limited scanning to a subset, e.g., by specifying an SSID. ++ * Especially, scan results are required to include an entry for the ++ * current BSS if the driver is in Managed mode and associated with an AP. ++ */ ++struct iw_scan_req ++{ ++ __u8 scan_type; /* IW_SCAN_TYPE_{ACTIVE,PASSIVE} */ ++ __u8 essid_len; ++ __u8 num_channels; /* num entries in channel_list; ++ * 0 = scan all allowed channels */ ++ __u8 flags; /* reserved as padding; use zero, this may ++ * be used in the future for adding flags ++ * to request different scan behavior */ ++ struct sockaddr bssid; /* ff:ff:ff:ff:ff:ff for broadcast BSSID or ++ * individual address of a specific BSS */ ++ ++ /* ++ * Use this ESSID if IW_SCAN_THIS_ESSID flag is used instead of using ++ * the current ESSID. This allows scan requests for specific ESSID ++ * without having to change the current ESSID and potentially breaking ++ * the current association. ++ */ ++ __u8 essid[IW_ESSID_MAX_SIZE]; ++ ++ /* ++ * Optional parameters for changing the default scanning behavior. ++ * These are based on the MLME-SCAN.request from IEEE Std 802.11. ++ * TU is 1.024 ms. If these are set to 0, driver is expected to use ++ * reasonable default values. min_channel_time defines the time that ++ * will be used to wait for the first reply on each channel. If no ++ * replies are received, next channel will be scanned after this. If ++ * replies are received, total time waited on the channel is defined by ++ * max_channel_time. ++ */ ++ __u32 min_channel_time; /* in TU */ ++ __u32 max_channel_time; /* in TU */ ++ ++ struct iw_freq channel_list[IW_MAX_FREQUENCIES]; ++}; ++ ++/* ------------------------- WPA SUPPORT ------------------------- */ ++ ++/* ++ * Extended data structure for get/set encoding (this is used with ++ * SIOCSIWENCODEEXT/SIOCGIWENCODEEXT. struct iw_point and IW_ENCODE_* ++ * flags are used in the same way as with SIOCSIWENCODE/SIOCGIWENCODE and ++ * only the data contents changes (key data -> this structure, including ++ * key data). ++ * ++ * If the new key is the first group key, it will be set as the default ++ * TX key. Otherwise, default TX key index is only changed if ++ * IW_ENCODE_EXT_SET_TX_KEY flag is set. ++ * ++ * Key will be changed with SIOCSIWENCODEEXT in all cases except for ++ * special "change TX key index" operation which is indicated by setting ++ * key_len = 0 and ext_flags |= IW_ENCODE_EXT_SET_TX_KEY. ++ * ++ * tx_seq/rx_seq are only used when respective ++ * IW_ENCODE_EXT_{TX,RX}_SEQ_VALID flag is set in ext_flags. Normal ++ * TKIP/CCMP operation is to set RX seq with SIOCSIWENCODEEXT and start ++ * TX seq from zero whenever key is changed. SIOCGIWENCODEEXT is normally ++ * used only by an Authenticator (AP or an IBSS station) to get the ++ * current TX sequence number. Using TX_SEQ_VALID for SIOCSIWENCODEEXT and ++ * RX_SEQ_VALID for SIOCGIWENCODEEXT are optional, but can be useful for ++ * debugging/testing. ++ */ ++struct iw_encode_ext ++{ ++ __u32 ext_flags; /* IW_ENCODE_EXT_* */ ++ __u8 tx_seq[IW_ENCODE_SEQ_MAX_SIZE]; /* LSB first */ ++ __u8 rx_seq[IW_ENCODE_SEQ_MAX_SIZE]; /* LSB first */ ++ struct sockaddr addr; /* ff:ff:ff:ff:ff:ff for broadcast/multicast ++ * (group) keys or unicast address for ++ * individual keys */ ++ __u16 alg; /* IW_ENCODE_ALG_* */ ++ __u16 key_len; ++ __u8 key[0]; ++}; ++ ++/* SIOCSIWMLME data */ ++struct iw_mlme ++{ ++ __u16 cmd; /* IW_MLME_* */ ++ __u16 reason_code; ++ struct sockaddr addr; ++}; ++ ++/* SIOCSIWPMKSA data */ ++#define IW_PMKSA_ADD 1 ++#define IW_PMKSA_REMOVE 2 ++#define IW_PMKSA_FLUSH 3 ++ ++#define IW_PMKID_LEN 16 ++ ++struct iw_pmksa ++{ ++ __u32 cmd; /* IW_PMKSA_* */ ++ struct sockaddr bssid; ++ __u8 pmkid[IW_PMKID_LEN]; ++}; ++ ++/* IWEVMICHAELMICFAILURE data */ ++struct iw_michaelmicfailure ++{ ++ __u32 flags; ++ struct sockaddr src_addr; ++ __u8 tsc[IW_ENCODE_SEQ_MAX_SIZE]; /* LSB first */ ++}; ++ ++/* IWEVPMKIDCAND data */ ++#define IW_PMKID_CAND_PREAUTH 0x00000001 /* RNS pre-authentication enabled */ ++struct iw_pmkid_cand ++{ ++ __u32 flags; /* IW_PMKID_CAND_* */ ++ __u32 index; /* the smaller the index, the higher the ++ * priority */ ++ struct sockaddr bssid; ++}; ++ + /* ------------------------ WIRELESS STATS ------------------------ */ + /* + * Wireless statistics (used for /proc/net/wireless) +@@ -725,6 +1000,8 @@ struct iw_range + struct iw_freq freq[IW_MAX_FREQUENCIES]; /* list */ + /* Note : this frequency list doesn't need to fit channel numbers, + * because each entry contain its channel index */ ++ ++ __u32 enc_capa; /* IW_ENC_CAPA_* bit field */ + }; + + /* +diff -u -p linux/net/core/wireless.we17.c linux/net/core/wireless.c +--- linux/net/core/wireless.we17.c 2005-05-20 11:26:11.000000000 -0700 ++++ linux/net/core/wireless.c 2005-05-20 15:37:09.000000000 -0700 +@@ -2,7 +2,7 @@ + * This file implement the Wireless Extensions APIs. + * + * Authors : Jean Tourrilhes - HPL - +- * Copyright (c) 1997-2004 Jean Tourrilhes, All Rights Reserved. ++ * Copyright (c) 1997-2005 Jean Tourrilhes, All Rights Reserved. + * + * (As all part of the Linux kernel, this file is GPL) + */ +@@ -137,12 +137,12 @@ static const struct iw_ioctl_description + { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, 0}, + /* SIOCGIWAP */ + { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, IW_DESCR_FLAG_DUMP}, +- /* -- hole -- */ +- { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* SIOCSIWMLME */ ++ { IW_HEADER_TYPE_POINT, 0, 1, sizeof(struct iw_mlme), sizeof(struct iw_mlme), 0}, + /* SIOCGIWAPLIST */ + { IW_HEADER_TYPE_POINT, 0, (sizeof(struct sockaddr) + sizeof(struct iw_quality)), 0, IW_MAX_AP, IW_DESCR_FLAG_NOMAX}, + /* SIOCSIWSCAN */ +- { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, sizeof(struct iw_scan_req), 0}, + /* SIOCGIWSCAN */ + { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_SCAN_MAX_DATA, IW_DESCR_FLAG_NOMAX}, + /* SIOCSIWESSID */ +@@ -185,6 +185,25 @@ static const struct iw_ioctl_description + { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, + /* SIOCGIWPOWER */ + { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* -- hole -- */ ++ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* -- hole -- */ ++ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* SIOCSIWGENIE */ ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_GENERIC_IE_MAX, 0}, ++ /* SIOCGIWGENIE */ ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_GENERIC_IE_MAX, 0}, ++ /* SIOCSIWAUTH */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCGIWAUTH */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCSIWENCODEEXT */ ++ { IW_HEADER_TYPE_POINT, 0, 1, sizeof(struct iw_encode_ext), sizeof(struct iw_encode_ext) + IW_ENCODING_TOKEN_MAX, 0}, ++ /* SIOCGIWENCODEEXT */ ++ { IW_HEADER_TYPE_POINT, 0, 1, sizeof(struct iw_encode_ext), sizeof(struct iw_encode_ext) + IW_ENCODING_TOKEN_MAX, 0}, ++ /* SIOCSIWPMKSA */ ++ { IW_HEADER_TYPE_POINT, 0, 1, sizeof(struct iw_pmksa), sizeof(struct iw_pmksa), 0}, ++ /* -- hole -- */ + }; + static const int standard_ioctl_num = (sizeof(standard_ioctl) / + sizeof(struct iw_ioctl_description)); +@@ -204,6 +223,16 @@ static const struct iw_ioctl_description + { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, 0}, + /* IWEVEXPIRED */ + { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, 0}, ++ /* IWEVGENIE */ ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_GENERIC_IE_MAX, 0}, ++ /* IWEVMICHAELMICFAILURE */ ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, sizeof(struct iw_michaelmicfailure), 0}, ++ /* IWEVASSOCREQIE */ ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_GENERIC_IE_MAX, 0}, ++ /* IWEVASSOCRESPIE */ ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_GENERIC_IE_MAX, 0}, ++ /* IWEVPMKIDCAND */ ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, sizeof(struct iw_pmkid_cand), 0}, + }; + static const int standard_event_num = (sizeof(standard_event) / + sizeof(struct iw_ioctl_description)); diff --git a/recipes/linux/files/iw241_we16-6.diff b/recipes/linux/files/iw241_we16-6.diff new file mode 100644 index 0000000000..71cb4c08f6 --- /dev/null +++ b/recipes/linux/files/iw241_we16-6.diff @@ -0,0 +1,667 @@ +diff -u -p linux/include/linux/wireless.15.h linux/include/linux/wireless.h +--- linux/include/linux/wireless.15.h Fri Jan 10 16:55:07 2003 ++++ linux/include/linux/wireless.h Wed Apr 2 16:33:31 2003 +@@ -1,7 +1,7 @@ + /* + * This file define a set of standard wireless extensions + * +- * Version : 15 12.7.02 ++ * Version : 16 2.4.03 + * + * Authors : Jean Tourrilhes - HPL - + * Copyright (c) 1997-2002 Jean Tourrilhes, All Rights Reserved. +@@ -69,6 +69,8 @@ + + /***************************** INCLUDES *****************************/ + ++/* To minimise problems in user space, I might remove those headers ++ * at some point. Jean II */ + #include /* for "caddr_t" et al */ + #include /* for "struct sockaddr" et al */ + #include /* for IFNAMSIZ and co... */ +@@ -80,7 +82,7 @@ + * (there is some stuff that will be added in the future...) + * I just plan to increment with each new version. + */ +-#define WIRELESS_EXT 15 ++#define WIRELESS_EXT 16 + + /* + * Changes : +@@ -163,6 +165,16 @@ + * - Add IW_TXPOW_RANGE for range of Tx Powers + * - Add IWEVREGISTERED & IWEVEXPIRED events for Access Points + * - Add IW_MODE_MONITOR for passive monitor ++ * ++ * V15 to V16 ++ * ---------- ++ * - Increase the number of bitrates in iw_range to 32 (for 802.11g) ++ * - Increase the number of frequencies in iw_range to 32 (for 802.11b+a) ++ * - Reshuffle struct iw_range for increases, add filler ++ * - Increase IW_MAX_AP to 64 for driver returning a lot of addresses ++ * - Remove IW_MAX_GET_SPY because conflict with enhanced spy support ++ * - Add SIOCSIWTHRSPY/SIOCGIWTHRSPY and "struct iw_thrspy" ++ * - Add IW_ENCODE_TEMP and iw_range->encoding_login_index + */ + + /**************************** CONSTANTS ****************************/ +@@ -196,9 +208,11 @@ + /* SIOCGIWSTATS is strictly used between user space and the kernel, and + * is never passed to the driver (i.e. the driver will never see it). */ + +-/* Mobile IP support (statistics per MAC address) */ ++/* Spy support (statistics per MAC address - used for Mobile IP support) */ + #define SIOCSIWSPY 0x8B10 /* set spy addresses */ + #define SIOCGIWSPY 0x8B11 /* get spy info (quality of link) */ ++#define SIOCSIWTHRSPY 0x8B12 /* set spy threshold (spy event) */ ++#define SIOCGIWTHRSPY 0x8B13 /* get spy threshold */ + + /* Access Point manipulation */ + #define SIOCSIWAP 0x8B14 /* set access point MAC addresses */ +@@ -294,7 +308,7 @@ + #define IW_PRIV_TYPE_FLOAT 0x5000 /* struct iw_freq */ + #define IW_PRIV_TYPE_ADDR 0x6000 /* struct sockaddr */ + +-#define IW_PRIV_SIZE_FIXED 0x0800 /* Variable or fixed nuber of args */ ++#define IW_PRIV_SIZE_FIXED 0x0800 /* Variable or fixed number of args */ + + #define IW_PRIV_SIZE_MASK 0x07FF /* Max number of those args */ + +@@ -306,13 +320,13 @@ + /* ----------------------- OTHER CONSTANTS ----------------------- */ + + /* Maximum frequencies in the range struct */ +-#define IW_MAX_FREQUENCIES 16 ++#define IW_MAX_FREQUENCIES 32 + /* Note : if you have something like 80 frequencies, + * don't increase this constant and don't fill the frequency list. + * The user will be able to set by channel anyway... */ + + /* Maximum bit rates in the range struct */ +-#define IW_MAX_BITRATES 8 ++#define IW_MAX_BITRATES 32 + + /* Maximum tx powers in the range struct */ + #define IW_MAX_TXPOWER 8 +@@ -320,8 +334,7 @@ + * a few of them in the struct iw_range. */ + + /* Maximum of address that you may set with SPY */ +-#define IW_MAX_SPY 8 /* set */ +-#define IW_MAX_GET_SPY 64 /* get */ ++#define IW_MAX_SPY 8 + + /* Maximum of address that you may get in the + list of access points in range */ +@@ -354,7 +367,8 @@ + #define IW_ENCODE_ENABLED 0x0000 /* Encoding enabled */ + #define IW_ENCODE_RESTRICTED 0x4000 /* Refuse non-encoded packets */ + #define IW_ENCODE_OPEN 0x2000 /* Accept non-encoded packets */ +-#define IW_ENCODE_NOKEY 0x0800 /* Key is write only, so not present */ ++#define IW_ENCODE_NOKEY 0x0800 /* Key is write only, so not present */ ++#define IW_ENCODE_TEMP 0x0400 /* Temporary key */ + + /* Power management flags available (along with the value, if any) */ + #define IW_POWER_ON 0x0000 /* No details... */ +@@ -482,6 +496,17 @@ struct iw_missed + __u32 beacon; /* Missed beacons/superframe */ + }; + ++/* ++ * Quality range (for spy threshold) ++ */ ++struct iw_thrspy ++{ ++ struct sockaddr addr; /* Source address (hw/mac) */ ++ struct iw_quality qual; /* Quality of the link */ ++ struct iw_quality low; /* Low threshold */ ++ struct iw_quality high; /* High threshold */ ++}; ++ + /* ------------------------ WIRELESS STATS ------------------------ */ + /* + * Wireless statistics (used for /proc/net/wireless) +@@ -534,7 +559,7 @@ union iwreq_data + struct iw_quality qual; /* Quality part of statistics */ + + struct sockaddr ap_addr; /* Access point address */ +- struct sockaddr addr; /* Destination address (hw) */ ++ struct sockaddr addr; /* Destination address (hw/mac) */ + + struct iw_param param; /* Other small parameters */ + struct iw_point data; /* Other large parameters */ +@@ -582,17 +607,31 @@ struct iw_range + __u32 min_nwid; /* Minimal NWID we are able to set */ + __u32 max_nwid; /* Maximal NWID we are able to set */ + +- /* Frequency */ +- __u16 num_channels; /* Number of channels [0; num - 1] */ +- __u8 num_frequency; /* Number of entry in the list */ +- struct iw_freq freq[IW_MAX_FREQUENCIES]; /* list */ +- /* Note : this frequency list doesn't need to fit channel numbers */ ++ /* Old Frequency (backward compat - moved lower ) */ ++ __u16 old_num_channels; ++ __u8 old_num_frequency; ++ /* Filler to keep "version" at the same offset */ ++ __s32 old_freq[6]; + + /* signal level threshold range */ + __s32 sensitivity; + + /* Quality of link & SNR stuff */ ++ /* Quality range (link, level, noise) ++ * If the quality is absolute, it will be in the range [0 ; max_qual], ++ * if the quality is dBm, it will be in the range [max_qual ; 0]. ++ * Don't forget that we use 8 bit arithmetics... */ + struct iw_quality max_qual; /* Quality of the link */ ++ /* This should contain the average/typical values of the quality ++ * indicator. This should be the threshold between a "good" and ++ * a "bad" link (example : monitor going from green to orange). ++ * Currently, user space apps like quality monitors don't have any ++ * way to calibrate the measurement. With this, they can split ++ * the range between 0 and max_qual in different quality level ++ * (using a geometric subdivision centered on the average). ++ * I expect that people doing the user space apps will feedback ++ * us on which value we need to put in each driver... */ ++ struct iw_quality avg_qual; /* Quality of the link */ + + /* Rates */ + __u8 num_bitrates; /* Number of entries in the list */ +@@ -619,6 +658,8 @@ struct iw_range + __u16 encoding_size[IW_MAX_ENCODING_SIZES]; /* Different token sizes */ + __u8 num_encoding_sizes; /* Number of entry in the list */ + __u8 max_encoding_tokens; /* Max number of tokens */ ++ /* For drivers that need a "login/passwd" form */ ++ __u8 encoding_login_index; /* token index for login token */ + + /* Transmit power */ + __u16 txpower_capa; /* What options are supported */ +@@ -638,18 +679,12 @@ struct iw_range + __s32 min_r_time; /* Minimal retry lifetime */ + __s32 max_r_time; /* Maximal retry lifetime */ + +- /* Average quality of link & SNR */ +- struct iw_quality avg_qual; /* Quality of the link */ +- /* This should contain the average/typical values of the quality +- * indicator. This should be the threshold between a "good" and +- * a "bad" link (example : monitor going from green to orange). +- * Currently, user space apps like quality monitors don't have any +- * way to calibrate the measurement. With this, they can split +- * the range between 0 and max_qual in different quality level +- * (using a geometric subdivision centered on the average). +- * I expect that people doing the user space apps will feedback +- * us on which value we need to put in each driver... +- */ ++ /* Frequency */ ++ __u16 num_channels; /* Number of channels [0; num - 1] */ ++ __u8 num_frequency; /* Number of entry in the list */ ++ struct iw_freq freq[IW_MAX_FREQUENCIES]; /* list */ ++ /* Note : this frequency list doesn't need to fit channel numbers, ++ * because each entry contain its channel index */ + }; + + /* +diff -u -p linux/include/net/iw_handler.15.h linux/include/net/iw_handler.h +--- linux/include/net/iw_handler.15.h Fri Jan 10 16:55:17 2003 ++++ linux/include/net/iw_handler.h Fri Jan 10 17:02:13 2003 +@@ -1,7 +1,7 @@ + /* + * This file define the new driver API for Wireless Extensions + * +- * Version : 4 21.6.02 ++ * Version : 5 4.12.02 + * + * Authors : Jean Tourrilhes - HPL - + * Copyright (c) 2001-2002 Jean Tourrilhes, All Rights Reserved. +@@ -206,7 +206,7 @@ + * will be needed... + * I just plan to increment with each new version. + */ +-#define IW_HANDLER_VERSION 4 ++#define IW_HANDLER_VERSION 5 + + /* + * Changes : +@@ -220,10 +220,18 @@ + * V3 to V4 + * -------- + * - Reshuffle IW_HEADER_TYPE_XXX to map IW_PRIV_TYPE_XXX changes ++ * ++ * V4 to V5 ++ * -------- ++ * - Add new spy support : struct iw_spy_data & prototypes + */ + + /**************************** CONSTANTS ****************************/ + ++/* Enable enhanced spy support. Disable to reduce footprint */ ++#define IW_WIRELESS_SPY ++#define IW_WIRELESS_THRSPY ++ + /* Special error message for the driver to indicate that we + * should do a commit after return from the iw_handler */ + #define EIWCOMMIT EINPROGRESS +@@ -315,6 +323,9 @@ struct iw_handler_def + * We will automatically export that to user space... */ + struct iw_priv_args * private_args; + ++ /* Driver enhanced spy support */ ++ long spy_offset; /* Spy data offset */ ++ + /* In the long term, get_wireless_stats will move from + * 'struct net_device' to here, to minimise bloat. */ + }; +@@ -350,6 +361,33 @@ struct iw_ioctl_description + + /* Need to think of short header translation table. Later. */ + ++/* --------------------- ENHANCED SPY SUPPORT --------------------- */ ++/* ++ * In the old days, the driver was handling spy support all by itself. ++ * Now, the driver can delegate this task to Wireless Extensions. ++ * It needs to include this struct in its private part and use the ++ * standard spy iw_handler. ++ */ ++ ++/* ++ * Instance specific spy data, i.e. addresses spied and quality for them. ++ */ ++struct iw_spy_data ++{ ++#ifdef IW_WIRELESS_SPY ++ /* --- Standard spy support --- */ ++ int spy_number; ++ u_char spy_address[IW_MAX_SPY][ETH_ALEN]; ++ struct iw_quality spy_stat[IW_MAX_SPY]; ++#ifdef IW_WIRELESS_THRSPY ++ /* --- Enhanced spy support (event) */ ++ struct iw_quality spy_thr_low; /* Low threshold */ ++ struct iw_quality spy_thr_high; /* High threshold */ ++ u_char spy_thr_under[IW_MAX_SPY]; ++#endif /* IW_WIRELESS_THRSPY */ ++#endif /* IW_WIRELESS_SPY */ ++}; ++ + /**************************** PROTOTYPES ****************************/ + /* + * Functions part of the Wireless Extensions (defined in net/core/wireless.c). +@@ -375,6 +413,31 @@ extern void wireless_send_event(struct n + + /* We may need a function to send a stream of events to user space. + * More on that later... */ ++ ++/* Standard handler for SIOCSIWSPY */ ++extern int iw_handler_set_spy(struct net_device * dev, ++ struct iw_request_info * info, ++ union iwreq_data * wrqu, ++ char * extra); ++/* Standard handler for SIOCGIWSPY */ ++extern int iw_handler_get_spy(struct net_device * dev, ++ struct iw_request_info * info, ++ union iwreq_data * wrqu, ++ char * extra); ++/* Standard handler for SIOCSIWTHRSPY */ ++extern int iw_handler_set_thrspy(struct net_device * dev, ++ struct iw_request_info *info, ++ union iwreq_data * wrqu, ++ char * extra); ++/* Standard handler for SIOCGIWTHRSPY */ ++extern int iw_handler_get_thrspy(struct net_device * dev, ++ struct iw_request_info *info, ++ union iwreq_data * wrqu, ++ char * extra); ++/* Driver call to update spy records */ ++extern void wireless_spy_update(struct net_device * dev, ++ unsigned char * address, ++ struct iw_quality * wstats); + + /************************* INLINE FUNTIONS *************************/ + /* +diff -u -p linux/net/core/wireless.15.c linux/net/core/wireless.c +--- linux/net/core/wireless.15.c Fri Jan 10 16:56:16 2003 ++++ linux/net/core/wireless.c Fri Jan 10 16:59:55 2003 +@@ -2,7 +2,7 @@ + * This file implement the Wireless Extensions APIs. + * + * Authors : Jean Tourrilhes - HPL - +- * Copyright (c) 1997-2002 Jean Tourrilhes, All Rights Reserved. ++ * Copyright (c) 1997-2003 Jean Tourrilhes, All Rights Reserved. + * + * (As all part of the Linux kernel, this file is GPL) + */ +@@ -43,6 +43,11 @@ + * o Turn on WE_STRICT_WRITE by default + kernel warning + * o Fix WE_STRICT_WRITE in ioctl_export_private() (32 => iw_num) + * o Fix off-by-one in test (extra_size <= IFNAMSIZ) ++ * ++ * v6 - 9.01.03 - Jean II ++ * o Add common spy support : iw_handler_set_spy(), wireless_spy_update() ++ * o Add enhanced spy support : iw_handler_set_thrspy() and event. ++ * o Add WIRELESS_EXT version display in /proc/net/wireless + */ + + /***************************** INCLUDES *****************************/ +@@ -52,6 +57,7 @@ + #include /* off_t */ + #include /* struct ifreq, dev_get_by_name() */ + #include /* rtnetlink stuff */ ++#include /* ARPHRD_ETHER */ + + #include /* Pretty obvious */ + #include /* New driver API */ +@@ -65,6 +71,7 @@ + /* Debuging stuff */ + #undef WE_IOCTL_DEBUG /* Debug IOCTL API */ + #undef WE_EVENT_DEBUG /* Debug Event dispatcher */ ++#undef WE_SPY_DEBUG /* Debug enhanced spy support */ + + /* Options */ + #define WE_EVENT_NETLINK /* Propagate events using rtnetlink */ +@@ -72,7 +79,7 @@ + + /************************* GLOBAL VARIABLES *************************/ + /* +- * You should not use global variables, because or re-entrancy. ++ * You should not use global variables, because of re-entrancy. + * On our case, it's only const, so it's OK... + */ + /* +@@ -115,11 +122,11 @@ static const struct iw_ioctl_description + /* SIOCSIWSPY */ + { IW_HEADER_TYPE_POINT, 0, sizeof(struct sockaddr), 0, IW_MAX_SPY, 0}, + /* SIOCGIWSPY */ +- { IW_HEADER_TYPE_POINT, 0, (sizeof(struct sockaddr) + sizeof(struct iw_quality)), 0, IW_MAX_GET_SPY, 0}, +- /* -- hole -- */ +- { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, +- /* -- hole -- */ +- { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ { IW_HEADER_TYPE_POINT, 0, (sizeof(struct sockaddr) + sizeof(struct iw_quality)), 0, IW_MAX_SPY, 0}, ++ /* SIOCSIWTHRSPY */ ++ { IW_HEADER_TYPE_POINT, 0, sizeof(struct iw_thrspy), 1, 1, 0}, ++ /* SIOCGIWTHRSPY */ ++ { IW_HEADER_TYPE_POINT, 0, sizeof(struct iw_thrspy), 1, 1, 0}, + /* SIOCSIWAP */ + { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, 0}, + /* SIOCGIWAP */ +@@ -377,9 +384,9 @@ int dev_get_wireless_info(char * buffer, + struct net_device * dev; + + size = sprintf(buffer, +- "Inter-| sta-| Quality | Discarded packets | Missed\n" +- " face | tus | link level noise | nwid crypt frag retry misc | beacon\n" +- ); ++ "Inter-| sta-| Quality | Discarded packets | Missed | WE\n" ++ " face | tus | link level noise | nwid crypt frag retry misc | beacon | %d\n", ++ WIRELESS_EXT); + + pos += size; + len += size; +@@ -1023,4 +1030,253 @@ void wireless_send_event(struct net_devi + kfree(event); + + return; /* Always success, I guess ;-) */ ++} ++ ++/********************** ENHANCED IWSPY SUPPORT **********************/ ++/* ++ * In the old days, the driver was handling spy support all by itself. ++ * Now, the driver can delegate this task to Wireless Extensions. ++ * It needs to use those standard spy iw_handler in struct iw_handler_def, ++ * push data to us via XXX and include struct iw_spy_data in its ++ * private part. ++ * One of the main advantage of centralising spy support here is that ++ * it becomes much easier to improve and extend it without having to touch ++ * the drivers. One example is the addition of the Spy-Threshold events. ++ * Note : IW_WIRELESS_SPY is defined in iw_handler.h ++ */ ++ ++/*------------------------------------------------------------------*/ ++/* ++ * Standard Wireless Handler : set Spy List ++ */ ++int iw_handler_set_spy(struct net_device * dev, ++ struct iw_request_info * info, ++ union iwreq_data * wrqu, ++ char * extra) ++{ ++#ifdef IW_WIRELESS_SPY ++ struct iw_spy_data * spydata = (dev->priv + ++ dev->wireless_handlers->spy_offset); ++ struct sockaddr * address = (struct sockaddr *) extra; ++ ++ /* Disable spy collection while we copy the addresses. ++ * As we don't disable interrupts, we need to do this to avoid races. ++ * As we are the only writer, this is good enough. */ ++ spydata->spy_number = 0; ++ ++ /* Are there are addresses to copy? */ ++ if(wrqu->data.length > 0) { ++ int i; ++ ++ /* Copy addresses */ ++ for(i = 0; i < wrqu->data.length; i++) ++ memcpy(spydata->spy_address[i], address[i].sa_data, ++ ETH_ALEN); ++ /* Reset stats */ ++ memset(spydata->spy_stat, 0, ++ sizeof(struct iw_quality) * IW_MAX_SPY); ++ ++#ifdef WE_SPY_DEBUG ++ printk(KERN_DEBUG "iw_handler_set_spy() : offset %ld, spydata %p, num %d\n", dev->wireless_handlers->spy_offset, spydata, wrqu->data.length); ++ for (i = 0; i < wrqu->data.length; i++) ++ printk(KERN_DEBUG ++ "%02X:%02X:%02X:%02X:%02X:%02X \n", ++ spydata->spy_address[i][0], ++ spydata->spy_address[i][1], ++ spydata->spy_address[i][2], ++ spydata->spy_address[i][3], ++ spydata->spy_address[i][4], ++ spydata->spy_address[i][5]); ++#endif /* WE_SPY_DEBUG */ ++ } ++ /* Enable addresses */ ++ spydata->spy_number = wrqu->data.length; ++ ++ return 0; ++#else /* IW_WIRELESS_SPY */ ++ return -EOPNOTSUPP; ++#endif /* IW_WIRELESS_SPY */ ++} ++ ++/*------------------------------------------------------------------*/ ++/* ++ * Standard Wireless Handler : get Spy List ++ */ ++int iw_handler_get_spy(struct net_device * dev, ++ struct iw_request_info * info, ++ union iwreq_data * wrqu, ++ char * extra) ++{ ++#ifdef IW_WIRELESS_SPY ++ struct iw_spy_data * spydata = (dev->priv + ++ dev->wireless_handlers->spy_offset); ++ struct sockaddr * address = (struct sockaddr *) extra; ++ int i; ++ ++ wrqu->data.length = spydata->spy_number; ++ ++ /* Copy addresses. */ ++ for(i = 0; i < spydata->spy_number; i++) { ++ memcpy(address[i].sa_data, spydata->spy_address[i], ETH_ALEN); ++ address[i].sa_family = AF_UNIX; ++ } ++ /* Copy stats to the user buffer (just after). */ ++ if(spydata->spy_number > 0) ++ memcpy(extra + (sizeof(struct sockaddr) *spydata->spy_number), ++ spydata->spy_stat, ++ sizeof(struct iw_quality) * spydata->spy_number); ++ /* Reset updated flags. */ ++ for(i = 0; i < spydata->spy_number; i++) ++ spydata->spy_stat[i].updated = 0; ++ return 0; ++#else /* IW_WIRELESS_SPY */ ++ return -EOPNOTSUPP; ++#endif /* IW_WIRELESS_SPY */ ++} ++ ++/*------------------------------------------------------------------*/ ++/* ++ * Standard Wireless Handler : set spy threshold ++ */ ++int iw_handler_set_thrspy(struct net_device * dev, ++ struct iw_request_info *info, ++ union iwreq_data * wrqu, ++ char * extra) ++{ ++#ifdef IW_WIRELESS_THRSPY ++ struct iw_spy_data * spydata = (dev->priv + ++ dev->wireless_handlers->spy_offset); ++ struct iw_thrspy * threshold = (struct iw_thrspy *) extra; ++ ++ /* Just do it */ ++ memcpy(&(spydata->spy_thr_low), &(threshold->low), ++ 2 * sizeof(struct iw_quality)); ++ ++ /* Clear flag */ ++ memset(spydata->spy_thr_under, '\0', sizeof(spydata->spy_thr_under)); ++ ++#ifdef WE_SPY_DEBUG ++ printk(KERN_DEBUG "iw_handler_set_thrspy() : low %d ; high %d\n", spydata->spy_thr_low.level, spydata->spy_thr_high.level); ++#endif /* WE_SPY_DEBUG */ ++ ++ return 0; ++#else /* IW_WIRELESS_THRSPY */ ++ return -EOPNOTSUPP; ++#endif /* IW_WIRELESS_THRSPY */ ++} ++ ++/*------------------------------------------------------------------*/ ++/* ++ * Standard Wireless Handler : get spy threshold ++ */ ++int iw_handler_get_thrspy(struct net_device * dev, ++ struct iw_request_info *info, ++ union iwreq_data * wrqu, ++ char * extra) ++{ ++#ifdef IW_WIRELESS_THRSPY ++ struct iw_spy_data * spydata = (dev->priv + ++ dev->wireless_handlers->spy_offset); ++ struct iw_thrspy * threshold = (struct iw_thrspy *) extra; ++ ++ /* Just do it */ ++ memcpy(&(threshold->low), &(spydata->spy_thr_low), ++ 2 * sizeof(struct iw_quality)); ++ ++ return 0; ++#else /* IW_WIRELESS_THRSPY */ ++ return -EOPNOTSUPP; ++#endif /* IW_WIRELESS_THRSPY */ ++} ++ ++#ifdef IW_WIRELESS_THRSPY ++/*------------------------------------------------------------------*/ ++/* ++ * Prepare and send a Spy Threshold event ++ */ ++static void iw_send_thrspy_event(struct net_device * dev, ++ struct iw_spy_data * spydata, ++ unsigned char * address, ++ struct iw_quality * wstats) ++{ ++ union iwreq_data wrqu; ++ struct iw_thrspy threshold; ++ ++ /* Init */ ++ wrqu.data.length = 1; ++ wrqu.data.flags = 0; ++ /* Copy address */ ++ memcpy(threshold.addr.sa_data, address, ETH_ALEN); ++ threshold.addr.sa_family = ARPHRD_ETHER; ++ /* Copy stats */ ++ memcpy(&(threshold.qual), wstats, sizeof(struct iw_quality)); ++ /* Copy also thresholds */ ++ memcpy(&(threshold.low), &(spydata->spy_thr_low), ++ 2 * sizeof(struct iw_quality)); ++ ++#ifdef WE_SPY_DEBUG ++ printk(KERN_DEBUG "iw_send_thrspy_event() : address %02X:%02X:%02X:%02X:%02X:%02X, level %d, up = %d\n", ++ threshold.addr.sa_data[0], ++ threshold.addr.sa_data[1], ++ threshold.addr.sa_data[2], ++ threshold.addr.sa_data[3], ++ threshold.addr.sa_data[4], ++ threshold.addr.sa_data[5], threshold.qual.level); ++#endif /* WE_SPY_DEBUG */ ++ ++ /* Send event to user space */ ++ wireless_send_event(dev, SIOCGIWTHRSPY, &wrqu, (char *) &threshold); ++} ++#endif /* IW_WIRELESS_THRSPY */ ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Call for the driver to update the spy data. ++ * For now, the spy data is a simple array. As the size of the array is ++ * small, this is good enough. If we wanted to support larger number of ++ * spy addresses, we should use something more efficient... ++ */ ++void wireless_spy_update(struct net_device * dev, ++ unsigned char * address, ++ struct iw_quality * wstats) ++{ ++#ifdef IW_WIRELESS_SPY ++ struct iw_spy_data * spydata = (dev->priv + ++ dev->wireless_handlers->spy_offset); ++ int i; ++ int match = -1; ++ ++#ifdef WE_SPY_DEBUG ++ printk(KERN_DEBUG "wireless_spy_update() : offset %ld, spydata %p, address %02X:%02X:%02X:%02X:%02X:%02X\n", dev->wireless_handlers->spy_offset, spydata, address[0], address[1], address[2], address[3], address[4], address[5]); ++#endif /* WE_SPY_DEBUG */ ++ ++ /* Update all records that match */ ++ for(i = 0; i < spydata->spy_number; i++) ++ if(!memcmp(address, spydata->spy_address[i], ETH_ALEN)) { ++ memcpy(&(spydata->spy_stat[i]), wstats, ++ sizeof(struct iw_quality)); ++ match = i; ++ } ++#ifdef IW_WIRELESS_THRSPY ++ /* Generate an event if we cross the spy threshold. ++ * To avoid event storms, we have a simple hysteresis : we generate ++ * event only when we go under the low threshold or above the ++ * high threshold. */ ++ if(match >= 0) { ++ if(spydata->spy_thr_under[match]) { ++ if(wstats->level > spydata->spy_thr_high.level) { ++ spydata->spy_thr_under[match] = 0; ++ iw_send_thrspy_event(dev, spydata, ++ address, wstats); ++ } ++ } else { ++ if(wstats->level < spydata->spy_thr_low.level) { ++ spydata->spy_thr_under[match] = 1; ++ iw_send_thrspy_event(dev, spydata, ++ address, wstats); ++ } ++ } ++ } ++#endif /* IW_WIRELESS_THRSPY */ ++#endif /* IW_WIRELESS_SPY */ + } +diff -u -p linux/net/netsyms.15.c linux/net/netsyms.c +--- linux/net/netsyms.15.c Fri Jan 10 16:56:32 2003 ++++ linux/net/netsyms.c Fri Jan 10 17:01:09 2003 +@@ -594,6 +594,11 @@ EXPORT_SYMBOL(softnet_data); + #if defined(CONFIG_NET_RADIO) || defined(CONFIG_NET_PCMCIA_RADIO) + #include + EXPORT_SYMBOL(wireless_send_event); ++EXPORT_SYMBOL(iw_handler_set_spy); ++EXPORT_SYMBOL(iw_handler_get_spy); ++EXPORT_SYMBOL(iw_handler_set_thrspy); ++EXPORT_SYMBOL(iw_handler_get_thrspy); ++EXPORT_SYMBOL(wireless_spy_update); + #endif /* CONFIG_NET_RADIO || CONFIG_NET_PCMCIA_RADIO */ + + #endif /* CONFIG_NET */ diff --git a/recipes/linux/files/iw249_we16-6.diff b/recipes/linux/files/iw249_we16-6.diff new file mode 100644 index 0000000000..0a5aaab954 --- /dev/null +++ b/recipes/linux/files/iw249_we16-6.diff @@ -0,0 +1,670 @@ +diff -u -p linux/include/linux/wireless.15.h linux/include/linux/wireless.h +--- linux/include/linux/wireless.15.h 2004-11-05 14:59:33.000000000 -0800 ++++ linux/include/linux/wireless.h 2004-11-05 15:00:42.000000000 -0800 +@@ -1,7 +1,7 @@ + /* + * This file define a set of standard wireless extensions + * +- * Version : 15 12.7.02 ++ * Version : 16 2.4.03 + * + * Authors : Jean Tourrilhes - HPL - + * Copyright (c) 1997-2002 Jean Tourrilhes, All Rights Reserved. +@@ -69,6 +69,8 @@ + + /***************************** INCLUDES *****************************/ + ++/* To minimise problems in user space, I might remove those headers ++ * at some point. Jean II */ + #include /* for "caddr_t" et al */ + #include /* for "struct sockaddr" et al */ + #include /* for IFNAMSIZ and co... */ +@@ -80,7 +82,7 @@ + * (there is some stuff that will be added in the future...) + * I just plan to increment with each new version. + */ +-#define WIRELESS_EXT 15 ++#define WIRELESS_EXT 16 + + /* + * Changes : +@@ -163,6 +165,16 @@ + * - Add IW_TXPOW_RANGE for range of Tx Powers + * - Add IWEVREGISTERED & IWEVEXPIRED events for Access Points + * - Add IW_MODE_MONITOR for passive monitor ++ * ++ * V15 to V16 ++ * ---------- ++ * - Increase the number of bitrates in iw_range to 32 (for 802.11g) ++ * - Increase the number of frequencies in iw_range to 32 (for 802.11b+a) ++ * - Reshuffle struct iw_range for increases, add filler ++ * - Increase IW_MAX_AP to 64 for driver returning a lot of addresses ++ * - Remove IW_MAX_GET_SPY because conflict with enhanced spy support ++ * - Add SIOCSIWTHRSPY/SIOCGIWTHRSPY and "struct iw_thrspy" ++ * - Add IW_ENCODE_TEMP and iw_range->encoding_login_index + */ + + /**************************** CONSTANTS ****************************/ +@@ -196,9 +208,11 @@ + /* SIOCGIWSTATS is strictly used between user space and the kernel, and + * is never passed to the driver (i.e. the driver will never see it). */ + +-/* Mobile IP support (statistics per MAC address) */ ++/* Spy support (statistics per MAC address - used for Mobile IP support) */ + #define SIOCSIWSPY 0x8B10 /* set spy addresses */ + #define SIOCGIWSPY 0x8B11 /* get spy info (quality of link) */ ++#define SIOCSIWTHRSPY 0x8B12 /* set spy threshold (spy event) */ ++#define SIOCGIWTHRSPY 0x8B13 /* get spy threshold */ + + /* Access Point manipulation */ + #define SIOCSIWAP 0x8B14 /* set access point MAC addresses */ +@@ -294,7 +308,7 @@ + #define IW_PRIV_TYPE_FLOAT 0x5000 /* struct iw_freq */ + #define IW_PRIV_TYPE_ADDR 0x6000 /* struct sockaddr */ + +-#define IW_PRIV_SIZE_FIXED 0x0800 /* Variable or fixed nuber of args */ ++#define IW_PRIV_SIZE_FIXED 0x0800 /* Variable or fixed number of args */ + + #define IW_PRIV_SIZE_MASK 0x07FF /* Max number of those args */ + +@@ -306,13 +320,13 @@ + /* ----------------------- OTHER CONSTANTS ----------------------- */ + + /* Maximum frequencies in the range struct */ +-#define IW_MAX_FREQUENCIES 16 ++#define IW_MAX_FREQUENCIES 32 + /* Note : if you have something like 80 frequencies, + * don't increase this constant and don't fill the frequency list. + * The user will be able to set by channel anyway... */ + + /* Maximum bit rates in the range struct */ +-#define IW_MAX_BITRATES 8 ++#define IW_MAX_BITRATES 32 + + /* Maximum tx powers in the range struct */ + #define IW_MAX_TXPOWER 8 +@@ -320,8 +334,7 @@ + * a few of them in the struct iw_range. */ + + /* Maximum of address that you may set with SPY */ +-#define IW_MAX_SPY 8 /* set */ +-#define IW_MAX_GET_SPY 64 /* get */ ++#define IW_MAX_SPY 8 + + /* Maximum of address that you may get in the + list of access points in range */ +@@ -354,7 +367,8 @@ + #define IW_ENCODE_ENABLED 0x0000 /* Encoding enabled */ + #define IW_ENCODE_RESTRICTED 0x4000 /* Refuse non-encoded packets */ + #define IW_ENCODE_OPEN 0x2000 /* Accept non-encoded packets */ +-#define IW_ENCODE_NOKEY 0x0800 /* Key is write only, so not present */ ++#define IW_ENCODE_NOKEY 0x0800 /* Key is write only, so not present */ ++#define IW_ENCODE_TEMP 0x0400 /* Temporary key */ + + /* Power management flags available (along with the value, if any) */ + #define IW_POWER_ON 0x0000 /* No details... */ +@@ -482,6 +496,17 @@ struct iw_missed + __u32 beacon; /* Missed beacons/superframe */ + }; + ++/* ++ * Quality range (for spy threshold) ++ */ ++struct iw_thrspy ++{ ++ struct sockaddr addr; /* Source address (hw/mac) */ ++ struct iw_quality qual; /* Quality of the link */ ++ struct iw_quality low; /* Low threshold */ ++ struct iw_quality high; /* High threshold */ ++}; ++ + /* ------------------------ WIRELESS STATS ------------------------ */ + /* + * Wireless statistics (used for /proc/net/wireless) +@@ -534,7 +559,7 @@ union iwreq_data + struct iw_quality qual; /* Quality part of statistics */ + + struct sockaddr ap_addr; /* Access point address */ +- struct sockaddr addr; /* Destination address (hw) */ ++ struct sockaddr addr; /* Destination address (hw/mac) */ + + struct iw_param param; /* Other small parameters */ + struct iw_point data; /* Other large parameters */ +@@ -582,17 +607,31 @@ struct iw_range + __u32 min_nwid; /* Minimal NWID we are able to set */ + __u32 max_nwid; /* Maximal NWID we are able to set */ + +- /* Frequency */ +- __u16 num_channels; /* Number of channels [0; num - 1] */ +- __u8 num_frequency; /* Number of entry in the list */ +- struct iw_freq freq[IW_MAX_FREQUENCIES]; /* list */ +- /* Note : this frequency list doesn't need to fit channel numbers */ ++ /* Old Frequency (backward compat - moved lower ) */ ++ __u16 old_num_channels; ++ __u8 old_num_frequency; ++ /* Filler to keep "version" at the same offset */ ++ __s32 old_freq[6]; + + /* signal level threshold range */ + __s32 sensitivity; + + /* Quality of link & SNR stuff */ ++ /* Quality range (link, level, noise) ++ * If the quality is absolute, it will be in the range [0 ; max_qual], ++ * if the quality is dBm, it will be in the range [max_qual ; 0]. ++ * Don't forget that we use 8 bit arithmetics... */ + struct iw_quality max_qual; /* Quality of the link */ ++ /* This should contain the average/typical values of the quality ++ * indicator. This should be the threshold between a "good" and ++ * a "bad" link (example : monitor going from green to orange). ++ * Currently, user space apps like quality monitors don't have any ++ * way to calibrate the measurement. With this, they can split ++ * the range between 0 and max_qual in different quality level ++ * (using a geometric subdivision centered on the average). ++ * I expect that people doing the user space apps will feedback ++ * us on which value we need to put in each driver... */ ++ struct iw_quality avg_qual; /* Quality of the link */ + + /* Rates */ + __u8 num_bitrates; /* Number of entries in the list */ +@@ -619,6 +658,8 @@ struct iw_range + __u16 encoding_size[IW_MAX_ENCODING_SIZES]; /* Different token sizes */ + __u8 num_encoding_sizes; /* Number of entry in the list */ + __u8 max_encoding_tokens; /* Max number of tokens */ ++ /* For drivers that need a "login/passwd" form */ ++ __u8 encoding_login_index; /* token index for login token */ + + /* Transmit power */ + __u16 txpower_capa; /* What options are supported */ +@@ -638,18 +679,12 @@ struct iw_range + __s32 min_r_time; /* Minimal retry lifetime */ + __s32 max_r_time; /* Maximal retry lifetime */ + +- /* Average quality of link & SNR */ +- struct iw_quality avg_qual; /* Quality of the link */ +- /* This should contain the average/typical values of the quality +- * indicator. This should be the threshold between a "good" and +- * a "bad" link (example : monitor going from green to orange). +- * Currently, user space apps like quality monitors don't have any +- * way to calibrate the measurement. With this, they can split +- * the range between 0 and max_qual in different quality level +- * (using a geometric subdivision centered on the average). +- * I expect that people doing the user space apps will feedback +- * us on which value we need to put in each driver... +- */ ++ /* Frequency */ ++ __u16 num_channels; /* Number of channels [0; num - 1] */ ++ __u8 num_frequency; /* Number of entry in the list */ ++ struct iw_freq freq[IW_MAX_FREQUENCIES]; /* list */ ++ /* Note : this frequency list doesn't need to fit channel numbers, ++ * because each entry contain its channel index */ + }; + + /* +diff -u -p linux/include/net/iw_handler.15.h linux/include/net/iw_handler.h +--- linux/include/net/iw_handler.15.h 2004-11-05 14:59:47.000000000 -0800 ++++ linux/include/net/iw_handler.h 2004-11-05 15:00:42.000000000 -0800 +@@ -1,7 +1,7 @@ + /* + * This file define the new driver API for Wireless Extensions + * +- * Version : 4 21.6.02 ++ * Version : 5 4.12.02 + * + * Authors : Jean Tourrilhes - HPL - + * Copyright (c) 2001-2002 Jean Tourrilhes, All Rights Reserved. +@@ -206,7 +206,7 @@ + * will be needed... + * I just plan to increment with each new version. + */ +-#define IW_HANDLER_VERSION 4 ++#define IW_HANDLER_VERSION 5 + + /* + * Changes : +@@ -220,10 +220,18 @@ + * V3 to V4 + * -------- + * - Reshuffle IW_HEADER_TYPE_XXX to map IW_PRIV_TYPE_XXX changes ++ * ++ * V4 to V5 ++ * -------- ++ * - Add new spy support : struct iw_spy_data & prototypes + */ + + /**************************** CONSTANTS ****************************/ + ++/* Enable enhanced spy support. Disable to reduce footprint */ ++#define IW_WIRELESS_SPY ++#define IW_WIRELESS_THRSPY ++ + /* Special error message for the driver to indicate that we + * should do a commit after return from the iw_handler */ + #define EIWCOMMIT EINPROGRESS +@@ -315,6 +323,9 @@ struct iw_handler_def + * We will automatically export that to user space... */ + struct iw_priv_args * private_args; + ++ /* Driver enhanced spy support */ ++ long spy_offset; /* Spy data offset */ ++ + /* In the long term, get_wireless_stats will move from + * 'struct net_device' to here, to minimise bloat. */ + }; +@@ -350,6 +361,33 @@ struct iw_ioctl_description + + /* Need to think of short header translation table. Later. */ + ++/* --------------------- ENHANCED SPY SUPPORT --------------------- */ ++/* ++ * In the old days, the driver was handling spy support all by itself. ++ * Now, the driver can delegate this task to Wireless Extensions. ++ * It needs to include this struct in its private part and use the ++ * standard spy iw_handler. ++ */ ++ ++/* ++ * Instance specific spy data, i.e. addresses spied and quality for them. ++ */ ++struct iw_spy_data ++{ ++#ifdef IW_WIRELESS_SPY ++ /* --- Standard spy support --- */ ++ int spy_number; ++ u_char spy_address[IW_MAX_SPY][ETH_ALEN]; ++ struct iw_quality spy_stat[IW_MAX_SPY]; ++#ifdef IW_WIRELESS_THRSPY ++ /* --- Enhanced spy support (event) */ ++ struct iw_quality spy_thr_low; /* Low threshold */ ++ struct iw_quality spy_thr_high; /* High threshold */ ++ u_char spy_thr_under[IW_MAX_SPY]; ++#endif /* IW_WIRELESS_THRSPY */ ++#endif /* IW_WIRELESS_SPY */ ++}; ++ + /**************************** PROTOTYPES ****************************/ + /* + * Functions part of the Wireless Extensions (defined in net/core/wireless.c). +@@ -376,6 +414,31 @@ extern void wireless_send_event(struct n + /* We may need a function to send a stream of events to user space. + * More on that later... */ + ++/* Standard handler for SIOCSIWSPY */ ++extern int iw_handler_set_spy(struct net_device * dev, ++ struct iw_request_info * info, ++ union iwreq_data * wrqu, ++ char * extra); ++/* Standard handler for SIOCGIWSPY */ ++extern int iw_handler_get_spy(struct net_device * dev, ++ struct iw_request_info * info, ++ union iwreq_data * wrqu, ++ char * extra); ++/* Standard handler for SIOCSIWTHRSPY */ ++extern int iw_handler_set_thrspy(struct net_device * dev, ++ struct iw_request_info *info, ++ union iwreq_data * wrqu, ++ char * extra); ++/* Standard handler for SIOCGIWTHRSPY */ ++extern int iw_handler_get_thrspy(struct net_device * dev, ++ struct iw_request_info *info, ++ union iwreq_data * wrqu, ++ char * extra); ++/* Driver call to update spy records */ ++extern void wireless_spy_update(struct net_device * dev, ++ unsigned char * address, ++ struct iw_quality * wstats); ++ + /************************* INLINE FUNTIONS *************************/ + /* + * Function that are so simple that it's more efficient inlining them +diff -u -p linux/net/core/wireless.15.c linux/net/core/wireless.c +--- linux/net/core/wireless.15.c 2004-11-05 15:00:11.000000000 -0800 ++++ linux/net/core/wireless.c 2004-11-05 15:00:42.000000000 -0800 +@@ -2,7 +2,7 @@ + * This file implement the Wireless Extensions APIs. + * + * Authors : Jean Tourrilhes - HPL - +- * Copyright (c) 1997-2002 Jean Tourrilhes, All Rights Reserved. ++ * Copyright (c) 1997-2003 Jean Tourrilhes, All Rights Reserved. + * + * (As all part of the Linux kernel, this file is GPL) + */ +@@ -43,6 +43,11 @@ + * o Turn on WE_STRICT_WRITE by default + kernel warning + * o Fix WE_STRICT_WRITE in ioctl_export_private() (32 => iw_num) + * o Fix off-by-one in test (extra_size <= IFNAMSIZ) ++ * ++ * v6 - 9.01.03 - Jean II ++ * o Add common spy support : iw_handler_set_spy(), wireless_spy_update() ++ * o Add enhanced spy support : iw_handler_set_thrspy() and event. ++ * o Add WIRELESS_EXT version display in /proc/net/wireless + */ + + /***************************** INCLUDES *****************************/ +@@ -52,6 +57,7 @@ + #include /* off_t */ + #include /* struct ifreq, dev_get_by_name() */ + #include /* rtnetlink stuff */ ++#include /* ARPHRD_ETHER */ + + #include /* Pretty obvious */ + #include /* New driver API */ +@@ -65,6 +71,7 @@ + /* Debuging stuff */ + #undef WE_IOCTL_DEBUG /* Debug IOCTL API */ + #undef WE_EVENT_DEBUG /* Debug Event dispatcher */ ++#undef WE_SPY_DEBUG /* Debug enhanced spy support */ + + /* Options */ + #define WE_EVENT_NETLINK /* Propagate events using rtnetlink */ +@@ -72,7 +79,7 @@ + + /************************* GLOBAL VARIABLES *************************/ + /* +- * You should not use global variables, because or re-entrancy. ++ * You should not use global variables, because of re-entrancy. + * On our case, it's only const, so it's OK... + */ + /* +@@ -115,11 +122,11 @@ static const struct iw_ioctl_description + /* SIOCSIWSPY */ + { IW_HEADER_TYPE_POINT, 0, sizeof(struct sockaddr), 0, IW_MAX_SPY, 0}, + /* SIOCGIWSPY */ +- { IW_HEADER_TYPE_POINT, 0, (sizeof(struct sockaddr) + sizeof(struct iw_quality)), 0, IW_MAX_GET_SPY, 0}, +- /* -- hole -- */ +- { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, +- /* -- hole -- */ +- { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ { IW_HEADER_TYPE_POINT, 0, (sizeof(struct sockaddr) + sizeof(struct iw_quality)), 0, IW_MAX_SPY, 0}, ++ /* SIOCSIWTHRSPY */ ++ { IW_HEADER_TYPE_POINT, 0, sizeof(struct iw_thrspy), 1, 1, 0}, ++ /* SIOCGIWTHRSPY */ ++ { IW_HEADER_TYPE_POINT, 0, sizeof(struct iw_thrspy), 1, 1, 0}, + /* SIOCSIWAP */ + { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, 0}, + /* SIOCGIWAP */ +@@ -377,9 +384,9 @@ int dev_get_wireless_info(char * buffer, + struct net_device * dev; + + size = sprintf(buffer, +- "Inter-| sta-| Quality | Discarded packets | Missed\n" +- " face | tus | link level noise | nwid crypt frag retry misc | beacon\n" +- ); ++ "Inter-| sta-| Quality | Discarded packets | Missed | WE\n" ++ " face | tus | link level noise | nwid crypt frag retry misc | beacon | %d\n", ++ WIRELESS_EXT); + + pos += size; + len += size; +@@ -1024,3 +1031,252 @@ void wireless_send_event(struct net_devi + + return; /* Always success, I guess ;-) */ + } ++ ++/********************** ENHANCED IWSPY SUPPORT **********************/ ++/* ++ * In the old days, the driver was handling spy support all by itself. ++ * Now, the driver can delegate this task to Wireless Extensions. ++ * It needs to use those standard spy iw_handler in struct iw_handler_def, ++ * push data to us via XXX and include struct iw_spy_data in its ++ * private part. ++ * One of the main advantage of centralising spy support here is that ++ * it becomes much easier to improve and extend it without having to touch ++ * the drivers. One example is the addition of the Spy-Threshold events. ++ * Note : IW_WIRELESS_SPY is defined in iw_handler.h ++ */ ++ ++/*------------------------------------------------------------------*/ ++/* ++ * Standard Wireless Handler : set Spy List ++ */ ++int iw_handler_set_spy(struct net_device * dev, ++ struct iw_request_info * info, ++ union iwreq_data * wrqu, ++ char * extra) ++{ ++#ifdef IW_WIRELESS_SPY ++ struct iw_spy_data * spydata = (dev->priv + ++ dev->wireless_handlers->spy_offset); ++ struct sockaddr * address = (struct sockaddr *) extra; ++ ++ /* Disable spy collection while we copy the addresses. ++ * As we don't disable interrupts, we need to do this to avoid races. ++ * As we are the only writer, this is good enough. */ ++ spydata->spy_number = 0; ++ ++ /* Are there are addresses to copy? */ ++ if(wrqu->data.length > 0) { ++ int i; ++ ++ /* Copy addresses */ ++ for(i = 0; i < wrqu->data.length; i++) ++ memcpy(spydata->spy_address[i], address[i].sa_data, ++ ETH_ALEN); ++ /* Reset stats */ ++ memset(spydata->spy_stat, 0, ++ sizeof(struct iw_quality) * IW_MAX_SPY); ++ ++#ifdef WE_SPY_DEBUG ++ printk(KERN_DEBUG "iw_handler_set_spy() : offset %ld, spydata %p, num %d\n", dev->wireless_handlers->spy_offset, spydata, wrqu->data.length); ++ for (i = 0; i < wrqu->data.length; i++) ++ printk(KERN_DEBUG ++ "%02X:%02X:%02X:%02X:%02X:%02X \n", ++ spydata->spy_address[i][0], ++ spydata->spy_address[i][1], ++ spydata->spy_address[i][2], ++ spydata->spy_address[i][3], ++ spydata->spy_address[i][4], ++ spydata->spy_address[i][5]); ++#endif /* WE_SPY_DEBUG */ ++ } ++ /* Enable addresses */ ++ spydata->spy_number = wrqu->data.length; ++ ++ return 0; ++#else /* IW_WIRELESS_SPY */ ++ return -EOPNOTSUPP; ++#endif /* IW_WIRELESS_SPY */ ++} ++ ++/*------------------------------------------------------------------*/ ++/* ++ * Standard Wireless Handler : get Spy List ++ */ ++int iw_handler_get_spy(struct net_device * dev, ++ struct iw_request_info * info, ++ union iwreq_data * wrqu, ++ char * extra) ++{ ++#ifdef IW_WIRELESS_SPY ++ struct iw_spy_data * spydata = (dev->priv + ++ dev->wireless_handlers->spy_offset); ++ struct sockaddr * address = (struct sockaddr *) extra; ++ int i; ++ ++ wrqu->data.length = spydata->spy_number; ++ ++ /* Copy addresses. */ ++ for(i = 0; i < spydata->spy_number; i++) { ++ memcpy(address[i].sa_data, spydata->spy_address[i], ETH_ALEN); ++ address[i].sa_family = AF_UNIX; ++ } ++ /* Copy stats to the user buffer (just after). */ ++ if(spydata->spy_number > 0) ++ memcpy(extra + (sizeof(struct sockaddr) *spydata->spy_number), ++ spydata->spy_stat, ++ sizeof(struct iw_quality) * spydata->spy_number); ++ /* Reset updated flags. */ ++ for(i = 0; i < spydata->spy_number; i++) ++ spydata->spy_stat[i].updated = 0; ++ return 0; ++#else /* IW_WIRELESS_SPY */ ++ return -EOPNOTSUPP; ++#endif /* IW_WIRELESS_SPY */ ++} ++ ++/*------------------------------------------------------------------*/ ++/* ++ * Standard Wireless Handler : set spy threshold ++ */ ++int iw_handler_set_thrspy(struct net_device * dev, ++ struct iw_request_info *info, ++ union iwreq_data * wrqu, ++ char * extra) ++{ ++#ifdef IW_WIRELESS_THRSPY ++ struct iw_spy_data * spydata = (dev->priv + ++ dev->wireless_handlers->spy_offset); ++ struct iw_thrspy * threshold = (struct iw_thrspy *) extra; ++ ++ /* Just do it */ ++ memcpy(&(spydata->spy_thr_low), &(threshold->low), ++ 2 * sizeof(struct iw_quality)); ++ ++ /* Clear flag */ ++ memset(spydata->spy_thr_under, '\0', sizeof(spydata->spy_thr_under)); ++ ++#ifdef WE_SPY_DEBUG ++ printk(KERN_DEBUG "iw_handler_set_thrspy() : low %d ; high %d\n", spydata->spy_thr_low.level, spydata->spy_thr_high.level); ++#endif /* WE_SPY_DEBUG */ ++ ++ return 0; ++#else /* IW_WIRELESS_THRSPY */ ++ return -EOPNOTSUPP; ++#endif /* IW_WIRELESS_THRSPY */ ++} ++ ++/*------------------------------------------------------------------*/ ++/* ++ * Standard Wireless Handler : get spy threshold ++ */ ++int iw_handler_get_thrspy(struct net_device * dev, ++ struct iw_request_info *info, ++ union iwreq_data * wrqu, ++ char * extra) ++{ ++#ifdef IW_WIRELESS_THRSPY ++ struct iw_spy_data * spydata = (dev->priv + ++ dev->wireless_handlers->spy_offset); ++ struct iw_thrspy * threshold = (struct iw_thrspy *) extra; ++ ++ /* Just do it */ ++ memcpy(&(threshold->low), &(spydata->spy_thr_low), ++ 2 * sizeof(struct iw_quality)); ++ ++ return 0; ++#else /* IW_WIRELESS_THRSPY */ ++ return -EOPNOTSUPP; ++#endif /* IW_WIRELESS_THRSPY */ ++} ++ ++#ifdef IW_WIRELESS_THRSPY ++/*------------------------------------------------------------------*/ ++/* ++ * Prepare and send a Spy Threshold event ++ */ ++static void iw_send_thrspy_event(struct net_device * dev, ++ struct iw_spy_data * spydata, ++ unsigned char * address, ++ struct iw_quality * wstats) ++{ ++ union iwreq_data wrqu; ++ struct iw_thrspy threshold; ++ ++ /* Init */ ++ wrqu.data.length = 1; ++ wrqu.data.flags = 0; ++ /* Copy address */ ++ memcpy(threshold.addr.sa_data, address, ETH_ALEN); ++ threshold.addr.sa_family = ARPHRD_ETHER; ++ /* Copy stats */ ++ memcpy(&(threshold.qual), wstats, sizeof(struct iw_quality)); ++ /* Copy also thresholds */ ++ memcpy(&(threshold.low), &(spydata->spy_thr_low), ++ 2 * sizeof(struct iw_quality)); ++ ++#ifdef WE_SPY_DEBUG ++ printk(KERN_DEBUG "iw_send_thrspy_event() : address %02X:%02X:%02X:%02X:%02X:%02X, level %d, up = %d\n", ++ threshold.addr.sa_data[0], ++ threshold.addr.sa_data[1], ++ threshold.addr.sa_data[2], ++ threshold.addr.sa_data[3], ++ threshold.addr.sa_data[4], ++ threshold.addr.sa_data[5], threshold.qual.level); ++#endif /* WE_SPY_DEBUG */ ++ ++ /* Send event to user space */ ++ wireless_send_event(dev, SIOCGIWTHRSPY, &wrqu, (char *) &threshold); ++} ++#endif /* IW_WIRELESS_THRSPY */ ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Call for the driver to update the spy data. ++ * For now, the spy data is a simple array. As the size of the array is ++ * small, this is good enough. If we wanted to support larger number of ++ * spy addresses, we should use something more efficient... ++ */ ++void wireless_spy_update(struct net_device * dev, ++ unsigned char * address, ++ struct iw_quality * wstats) ++{ ++#ifdef IW_WIRELESS_SPY ++ struct iw_spy_data * spydata = (dev->priv + ++ dev->wireless_handlers->spy_offset); ++ int i; ++ int match = -1; ++ ++#ifdef WE_SPY_DEBUG ++ printk(KERN_DEBUG "wireless_spy_update() : offset %ld, spydata %p, address %02X:%02X:%02X:%02X:%02X:%02X\n", dev->wireless_handlers->spy_offset, spydata, address[0], address[1], address[2], address[3], address[4], address[5]); ++#endif /* WE_SPY_DEBUG */ ++ ++ /* Update all records that match */ ++ for(i = 0; i < spydata->spy_number; i++) ++ if(!memcmp(address, spydata->spy_address[i], ETH_ALEN)) { ++ memcpy(&(spydata->spy_stat[i]), wstats, ++ sizeof(struct iw_quality)); ++ match = i; ++ } ++#ifdef IW_WIRELESS_THRSPY ++ /* Generate an event if we cross the spy threshold. ++ * To avoid event storms, we have a simple hysteresis : we generate ++ * event only when we go under the low threshold or above the ++ * high threshold. */ ++ if(match >= 0) { ++ if(spydata->spy_thr_under[match]) { ++ if(wstats->level > spydata->spy_thr_high.level) { ++ spydata->spy_thr_under[match] = 0; ++ iw_send_thrspy_event(dev, spydata, ++ address, wstats); ++ } ++ } else { ++ if(wstats->level < spydata->spy_thr_low.level) { ++ spydata->spy_thr_under[match] = 1; ++ iw_send_thrspy_event(dev, spydata, ++ address, wstats); ++ } ++ } ++ } ++#endif /* IW_WIRELESS_THRSPY */ ++#endif /* IW_WIRELESS_SPY */ ++} +diff -u -p linux/net/netsyms.15.c linux/net/netsyms.c +--- linux/net/netsyms.15.c 2004-11-05 15:00:25.000000000 -0800 ++++ linux/net/netsyms.c 2004-11-05 15:01:38.000000000 -0800 +@@ -589,9 +589,13 @@ EXPORT_SYMBOL(net_call_rx_atomic); + EXPORT_SYMBOL(softnet_data); + + #if defined(CONFIG_NET_RADIO) || defined(CONFIG_NET_PCMCIA_RADIO) +-/* Don't include the whole header mess for a single function */ +-extern void wireless_send_event(struct net_device *dev, unsigned int cmd, union iwreq_data *wrqu, char *extra); ++#include + EXPORT_SYMBOL(wireless_send_event); ++EXPORT_SYMBOL(iw_handler_set_spy); ++EXPORT_SYMBOL(iw_handler_get_spy); ++EXPORT_SYMBOL(iw_handler_set_thrspy); ++EXPORT_SYMBOL(iw_handler_get_thrspy); ++EXPORT_SYMBOL(wireless_spy_update); + #endif /* CONFIG_NET_RADIO || CONFIG_NET_PCMCIA_RADIO */ + + #endif /* CONFIG_NET */ diff --git a/recipes/linux/files/iw249_we17-13.diff b/recipes/linux/files/iw249_we17-13.diff new file mode 100644 index 0000000000..674f4ffbc0 --- /dev/null +++ b/recipes/linux/files/iw249_we17-13.diff @@ -0,0 +1,768 @@ +diff -u -p linux/include/linux/netdevice.we16.h linux/include/linux/netdevice.h +--- linux/include/linux/netdevice.we16.h 2005-02-03 14:54:56.000000000 -0800 ++++ linux/include/linux/netdevice.h 2005-02-03 15:43:30.000000000 -0800 +@@ -295,7 +295,9 @@ struct net_device + + /* List of functions to handle Wireless Extensions (instead of ioctl). + * See for details. Jean II */ +- struct iw_handler_def * wireless_handlers; ++ const struct iw_handler_def * wireless_handlers; ++ /* Instance data managed by the core of Wireless Extensions. */ ++ struct iw_public_data * wireless_data; + + struct ethtool_ops *ethtool_ops; + +diff -u -p linux/include/linux/wireless.we16.h linux/include/linux/wireless.h +--- linux/include/linux/wireless.we16.h 2005-02-03 14:55:04.000000000 -0800 ++++ linux/include/linux/wireless.h 2005-02-03 15:44:48.000000000 -0800 +@@ -1,10 +1,10 @@ + /* + * This file define a set of standard wireless extensions + * +- * Version : 16 2.4.03 ++ * Version : 17 21.6.04 + * + * Authors : Jean Tourrilhes - HPL - +- * Copyright (c) 1997-2002 Jean Tourrilhes, All Rights Reserved. ++ * Copyright (c) 1997-2004 Jean Tourrilhes, All Rights Reserved. + */ + + #ifndef _LINUX_WIRELESS_H +@@ -47,12 +47,12 @@ + * # include/net/iw_handler.h + * + * Note as well that /proc/net/wireless implementation has now moved in : +- * # include/linux/wireless.c ++ * # net/core/wireless.c + * + * Wireless Events (2002 -> onward) : + * -------------------------------- + * Events are defined at the end of this file, and implemented in : +- * # include/linux/wireless.c ++ * # net/core/wireless.c + * + * Other comments : + * -------------- +@@ -82,7 +82,7 @@ + * (there is some stuff that will be added in the future...) + * I just plan to increment with each new version. + */ +-#define WIRELESS_EXT 16 ++#define WIRELESS_EXT 17 + + /* + * Changes : +@@ -175,6 +175,13 @@ + * - Remove IW_MAX_GET_SPY because conflict with enhanced spy support + * - Add SIOCSIWTHRSPY/SIOCGIWTHRSPY and "struct iw_thrspy" + * - Add IW_ENCODE_TEMP and iw_range->encoding_login_index ++ * ++ * V16 to V17 ++ * ---------- ++ * - Add flags to frequency -> auto/fixed ++ * - Document (struct iw_quality *)->updated, add new flags (INVALID) ++ * - Wireless Event capability in struct iw_range ++ * - Add support for relative TxPower (yick !) + */ + + /**************************** CONSTANTS ****************************/ +@@ -251,7 +258,7 @@ + + /* -------------------- DEV PRIVATE IOCTL LIST -------------------- */ + +-/* These 16 ioctl are wireless device private. ++/* These 32 ioctl are wireless device private, for 16 commands. + * Each driver is free to use them for whatever purpose it chooses, + * however the driver *must* export the description of those ioctls + * with SIOCGIWPRIV and *must* use arguments as defined below. +@@ -266,8 +273,8 @@ + * We now have 32 commands, so a bit more space ;-). + * Also, all 'odd' commands are only usable by root and don't return the + * content of ifr/iwr to user (but you are not obliged to use the set/get +- * convention, just use every other two command). +- * And I repeat : you are not obliged to use them with iwspy, but you ++ * convention, just use every other two command). More details in iwpriv.c. ++ * And I repeat : you are not forced to use them with iwpriv, but you + * must be compliant with it. + */ + +@@ -352,6 +359,18 @@ + #define IW_MODE_SECOND 5 /* Secondary master/repeater (backup) */ + #define IW_MODE_MONITOR 6 /* Passive monitor (listen only) */ + ++/* Statistics flags (bitmask in updated) */ ++#define IW_QUAL_QUAL_UPDATED 0x1 /* Value was updated since last read */ ++#define IW_QUAL_LEVEL_UPDATED 0x2 ++#define IW_QUAL_NOISE_UPDATED 0x4 ++#define IW_QUAL_QUAL_INVALID 0x10 /* Driver doesn't provide value */ ++#define IW_QUAL_LEVEL_INVALID 0x20 ++#define IW_QUAL_NOISE_INVALID 0x40 ++ ++/* Frequency flags */ ++#define IW_FREQ_AUTO 0x00 /* Let the driver decides */ ++#define IW_FREQ_FIXED 0x01 /* Force a specific value */ ++ + /* Maximum number of size of encoding token available + * they are listed in the range structure */ + #define IW_MAX_ENCODING_SIZES 8 +@@ -390,6 +409,7 @@ + #define IW_TXPOW_TYPE 0x00FF /* Type of value */ + #define IW_TXPOW_DBM 0x0000 /* Value is in dBm */ + #define IW_TXPOW_MWATT 0x0001 /* Value is in mW */ ++#define IW_TXPOW_RELATIVE 0x0002 /* Value is in arbitrary units */ + #define IW_TXPOW_RANGE 0x1000 /* Range of value between min/max */ + + /* Retry limits and lifetime flags available */ +@@ -418,6 +438,25 @@ + /* Max number of char in custom event - use multiple of them if needed */ + #define IW_CUSTOM_MAX 256 /* In bytes */ + ++/* Event capability macros - in (struct iw_range *)->event_capa ++ * Because we have more than 32 possible events, we use an array of ++ * 32 bit bitmasks. Note : 32 bits = 0x20 = 2^5. */ ++#define IW_EVENT_CAPA_BASE(cmd) ((cmd >= SIOCIWFIRSTPRIV) ? \ ++ (cmd - SIOCIWFIRSTPRIV + 0x60) : \ ++ (cmd - SIOCSIWCOMMIT)) ++#define IW_EVENT_CAPA_INDEX(cmd) (IW_EVENT_CAPA_BASE(cmd) >> 5) ++#define IW_EVENT_CAPA_MASK(cmd) (1 << (IW_EVENT_CAPA_BASE(cmd) & 0x1F)) ++/* Event capability constants - event autogenerated by the kernel ++ * This list is valid for most 802.11 devices, customise as needed... */ ++#define IW_EVENT_CAPA_K_0 (IW_EVENT_CAPA_MASK(0x8B04) | \ ++ IW_EVENT_CAPA_MASK(0x8B06) | \ ++ IW_EVENT_CAPA_MASK(0x8B1A)) ++#define IW_EVENT_CAPA_K_1 (IW_EVENT_CAPA_MASK(0x8B2A)) ++/* "Easy" macro to set events in iw_range (less efficient) */ ++#define IW_EVENT_CAPA_SET(event_capa, cmd) (event_capa[IW_EVENT_CAPA_INDEX(cmd)] |= IW_EVENT_CAPA_MASK(cmd)) ++#define IW_EVENT_CAPA_SET_KERNEL(event_capa) {event_capa[0] |= IW_EVENT_CAPA_K_0; event_capa[1] |= IW_EVENT_CAPA_K_1; } ++ ++ + /****************************** TYPES ******************************/ + + /* --------------------------- SUBTYPES --------------------------- */ +@@ -456,7 +495,7 @@ struct iw_freq + __s32 m; /* Mantissa */ + __s16 e; /* Exponent */ + __u8 i; /* List index (when in range struct) */ +- __u8 pad; /* Unused - just for alignement */ ++ __u8 flags; /* Flags (fixed/auto) */ + }; + + /* +@@ -610,11 +649,12 @@ struct iw_range + /* Old Frequency (backward compat - moved lower ) */ + __u16 old_num_channels; + __u8 old_num_frequency; +- /* Filler to keep "version" at the same offset */ +- __s32 old_freq[6]; ++ ++ /* Wireless event capability bitmasks */ ++ __u32 event_capa[6]; + + /* signal level threshold range */ +- __s32 sensitivity; ++ __s32 sensitivity; + + /* Quality of link & SNR stuff */ + /* Quality range (link, level, noise) +diff -u -p linux/include/net/iw_handler.we16.h linux/include/net/iw_handler.h +--- linux/include/net/iw_handler.we16.h 2005-02-03 14:55:26.000000000 -0800 ++++ linux/include/net/iw_handler.h 2005-02-03 15:47:04.000000000 -0800 +@@ -1,10 +1,10 @@ + /* + * This file define the new driver API for Wireless Extensions + * +- * Version : 5 4.12.02 ++ * Version : 6 21.6.04 + * + * Authors : Jean Tourrilhes - HPL - +- * Copyright (c) 2001-2002 Jean Tourrilhes, All Rights Reserved. ++ * Copyright (c) 2001-2004 Jean Tourrilhes, All Rights Reserved. + */ + + #ifndef _IW_HANDLER_H +@@ -206,7 +206,7 @@ + * will be needed... + * I just plan to increment with each new version. + */ +-#define IW_HANDLER_VERSION 5 ++#define IW_HANDLER_VERSION 6 + + /* + * Changes : +@@ -224,11 +224,18 @@ + * V4 to V5 + * -------- + * - Add new spy support : struct iw_spy_data & prototypes ++ * ++ * V5 to V6 ++ * -------- ++ * - Change the way we get to spy_data method for added safety ++ * - Remove spy #ifdef, they are always on -> cleaner code ++ * - Add IW_DESCR_FLAG_NOMAX flag for very large requests ++ * - Start migrating get_wireless_stats to struct iw_handler_def + */ + + /**************************** CONSTANTS ****************************/ + +-/* Enable enhanced spy support. Disable to reduce footprint */ ++/* Enhanced spy support available */ + #define IW_WIRELESS_SPY + #define IW_WIRELESS_THRSPY + +@@ -258,6 +265,7 @@ + #define IW_DESCR_FLAG_EVENT 0x0002 /* Generate an event on SET */ + #define IW_DESCR_FLAG_RESTRICT 0x0004 /* GET : request is ROOT only */ + /* SET : Omit payload from generated iwevent */ ++#define IW_DESCR_FLAG_NOMAX 0x0008 /* GET : no limit on request size */ + /* Driver level flags */ + #define IW_DESCR_FLAG_WAIT 0x0100 /* Wait for driver event */ + +@@ -311,23 +319,25 @@ struct iw_handler_def + /* Array of handlers for standard ioctls + * We will call dev->wireless_handlers->standard[ioctl - SIOCSIWNAME] + */ +- iw_handler * standard; ++ const iw_handler * standard; + + /* Array of handlers for private ioctls + * Will call dev->wireless_handlers->private[ioctl - SIOCIWFIRSTPRIV] + */ +- iw_handler * private; ++ const iw_handler * private; + + /* Arguments of private handler. This one is just a list, so you + * can put it in any order you want and should not leave holes... + * We will automatically export that to user space... */ +- struct iw_priv_args * private_args; ++ const struct iw_priv_args * private_args; + +- /* Driver enhanced spy support */ +- long spy_offset; /* Spy data offset */ ++ /* This field will be *removed* in the next version of WE */ ++ long spy_offset; /* DO NOT USE */ + +- /* In the long term, get_wireless_stats will move from +- * 'struct net_device' to here, to minimise bloat. */ ++ /* New location of get_wireless_stats, to de-bloat struct net_device. ++ * The old pointer in struct net_device will be gradually phased ++ * out, and drivers are encouraged to use this one... */ ++ struct iw_statistics* (*get_wireless_stats)(struct net_device *dev); + }; + + /* ---------------------- IOCTL DESCRIPTION ---------------------- */ +@@ -374,18 +384,29 @@ struct iw_ioctl_description + */ + struct iw_spy_data + { +-#ifdef IW_WIRELESS_SPY + /* --- Standard spy support --- */ + int spy_number; + u_char spy_address[IW_MAX_SPY][ETH_ALEN]; + struct iw_quality spy_stat[IW_MAX_SPY]; +-#ifdef IW_WIRELESS_THRSPY + /* --- Enhanced spy support (event) */ + struct iw_quality spy_thr_low; /* Low threshold */ + struct iw_quality spy_thr_high; /* High threshold */ + u_char spy_thr_under[IW_MAX_SPY]; +-#endif /* IW_WIRELESS_THRSPY */ +-#endif /* IW_WIRELESS_SPY */ ++}; ++ ++/* --------------------- DEVICE WIRELESS DATA --------------------- */ ++/* ++ * This is all the wireless data specific to a device instance that ++ * is managed by the core of Wireless Extensions. ++ * We only keep pointer to those structures, so that a driver is free ++ * to share them between instances. ++ * This structure should be initialised before registering the device. ++ * Access to this data follow the same rules as any other struct net_device ++ * data (i.e. valid as long as struct net_device exist, same locking rules). ++ */ ++struct iw_public_data { ++ /* Driver enhanced spy support */ ++ struct iw_spy_data * spy_data; + }; + + /**************************** PROTOTYPES ****************************/ +diff -u -p linux/net/core/dev.we16.c linux/net/core/dev.c +--- linux/net/core/dev.we16.c 2005-02-03 14:55:56.000000000 -0800 ++++ linux/net/core/dev.c 2005-02-03 15:28:48.000000000 -0800 +@@ -2426,7 +2426,7 @@ int dev_ioctl(unsigned int cmd, void *ar + /* Follow me in net/core/wireless.c */ + ret = wireless_process_ioctl(&ifr, cmd); + rtnl_unlock(); +- if (!ret && IW_IS_GET(cmd) && ++ if (IW_IS_GET(cmd) && + copy_to_user(arg, &ifr, sizeof(struct ifreq))) + return -EFAULT; + return ret; +diff -u -p linux/net/core/wireless.we16.c linux/net/core/wireless.c +--- linux/net/core/wireless.we16.c 2005-02-03 14:56:09.000000000 -0800 ++++ linux/net/core/wireless.c 2005-02-03 16:33:22.000000000 -0800 +@@ -2,7 +2,7 @@ + * This file implement the Wireless Extensions APIs. + * + * Authors : Jean Tourrilhes - HPL - +- * Copyright (c) 1997-2003 Jean Tourrilhes, All Rights Reserved. ++ * Copyright (c) 1997-2004 Jean Tourrilhes, All Rights Reserved. + * + * (As all part of the Linux kernel, this file is GPL) + */ +@@ -48,6 +48,16 @@ + * o Add common spy support : iw_handler_set_spy(), wireless_spy_update() + * o Add enhanced spy support : iw_handler_set_thrspy() and event. + * o Add WIRELESS_EXT version display in /proc/net/wireless ++ * ++ * v6 - 18.06.04 - Jean II ++ * o Change get_spydata() method for added safety ++ * o Remove spy #ifdef, they are always on -> cleaner code ++ * o Allow any size GET request if user specifies length > max ++ * and if request has IW_DESCR_FLAG_NOMAX flag or is SIOCGIWPRIV ++ * o Start migrating get_wireless_stats to struct iw_handler_def ++ * o Add wmb() in iw_handler_set_spy() for non-coherent archs/cpus ++ * Based on patch from Pavel Roskin : ++ * o Fix kernel data leak to user space in private handler handling + */ + + /***************************** INCLUDES *****************************/ +@@ -64,11 +74,7 @@ + + /**************************** CONSTANTS ****************************/ + +-/* Enough lenience, let's make sure things are proper... */ +-#define WE_STRICT_WRITE /* Check write buffer size */ +-/* I'll probably drop both the define and kernel message in the next version */ +- +-/* Debuging stuff */ ++/* Debugging stuff */ + #undef WE_IOCTL_DEBUG /* Debug IOCTL API */ + #undef WE_EVENT_DEBUG /* Debug Event dispatcher */ + #undef WE_SPY_DEBUG /* Debug enhanced spy support */ +@@ -134,11 +140,11 @@ static const struct iw_ioctl_description + /* -- hole -- */ + { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, + /* SIOCGIWAPLIST */ +- { IW_HEADER_TYPE_POINT, 0, (sizeof(struct sockaddr) + sizeof(struct iw_quality)), 0, IW_MAX_AP, 0}, ++ { IW_HEADER_TYPE_POINT, 0, (sizeof(struct sockaddr) + sizeof(struct iw_quality)), 0, IW_MAX_AP, IW_DESCR_FLAG_NOMAX}, + /* SIOCSIWSCAN */ + { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, + /* SIOCGIWSCAN */ +- { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_SCAN_MAX_DATA, 0}, ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_SCAN_MAX_DATA, IW_DESCR_FLAG_NOMAX}, + /* SIOCSIWESSID */ + { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE + 1, IW_DESCR_FLAG_EVENT}, + /* SIOCGIWESSID */ +@@ -203,7 +209,7 @@ static const int standard_event_num = (s + sizeof(struct iw_ioctl_description)); + + /* Size (in bytes) of the various private data types */ +-static const char priv_type_size[] = { ++static const char iw_priv_type_size[] = { + 0, /* IW_PRIV_TYPE_NONE */ + 1, /* IW_PRIV_TYPE_BYTE */ + 1, /* IW_PRIV_TYPE_CHAR */ +@@ -270,12 +276,15 @@ static inline iw_handler get_handler(str + */ + static inline struct iw_statistics *get_wireless_stats(struct net_device *dev) + { ++ /* New location */ ++ if((dev->wireless_handlers != NULL) && ++ (dev->wireless_handlers->get_wireless_stats != NULL)) ++ return dev->wireless_handlers->get_wireless_stats(dev); ++ ++ /* Old location, will be phased out in next WE */ + return (dev->get_wireless_stats ? + dev->get_wireless_stats(dev) : + (struct iw_statistics *) NULL); +- /* In the future, get_wireless_stats may move from 'struct net_device' +- * to 'struct iw_handler_def', to de-bloat struct net_device. +- * Definitely worse a thought... */ + } + + /* ---------------------------------------------------------------- */ +@@ -310,14 +319,32 @@ static inline int call_commit_handler(st + + /* ---------------------------------------------------------------- */ + /* +- * Number of private arguments ++ * Calculate size of private arguments + */ + static inline int get_priv_size(__u16 args) + { + int num = args & IW_PRIV_SIZE_MASK; + int type = (args & IW_PRIV_TYPE_MASK) >> 12; + +- return num * priv_type_size[type]; ++ return num * iw_priv_type_size[type]; ++} ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Re-calculate the size of private arguments ++ */ ++static inline int adjust_priv_size(__u16 args, ++ union iwreq_data * wrqu) ++{ ++ int num = wrqu->data.length; ++ int max = args & IW_PRIV_SIZE_MASK; ++ int type = (args & IW_PRIV_TYPE_MASK) >> 12; ++ ++ /* Make sure the driver doesn't goof up */ ++ if (max < num) ++ num = max; ++ ++ return num * iw_priv_type_size[type]; + } + + +@@ -350,11 +377,14 @@ static inline int sprintf_wireless_stats + dev->name, + stats->status, + stats->qual.qual, +- stats->qual.updated & 1 ? '.' : ' ', ++ stats->qual.updated & IW_QUAL_QUAL_UPDATED ++ ? '.' : ' ', + ((__u8) stats->qual.level), +- stats->qual.updated & 2 ? '.' : ' ', ++ stats->qual.updated & IW_QUAL_LEVEL_UPDATED ++ ? '.' : ' ', + ((__u8) stats->qual.noise), +- stats->qual.updated & 4 ? '.' : ' ', ++ stats->qual.updated & IW_QUAL_NOISE_UPDATED ++ ? '.' : ' ', + stats->discard.nwid, + stats->discard.code, + stats->discard.fragment, +@@ -470,13 +500,15 @@ static inline int ioctl_export_private(s + /* Check NULL pointer */ + if(iwr->u.data.pointer == NULL) + return -EFAULT; +-#ifdef WE_STRICT_WRITE ++ + /* Check if there is enough buffer up there */ + if(iwr->u.data.length < dev->wireless_handlers->num_private_args) { +- printk(KERN_ERR "%s (WE) : Buffer for request SIOCGIWPRIV too small (%d<%d)\n", dev->name, iwr->u.data.length, dev->wireless_handlers->num_private_args); ++ /* User space can't know in advance how large the buffer ++ * needs to be. Give it a hint, so that we can support ++ * any size buffer we want somewhat efficiently... */ ++ iwr->u.data.length = dev->wireless_handlers->num_private_args; + return -E2BIG; + } +-#endif /* WE_STRICT_WRITE */ + + /* Set the number of available ioctls. */ + iwr->u.data.length = dev->wireless_handlers->num_private_args; +@@ -505,7 +537,6 @@ static inline int ioctl_standard_call(st + const struct iw_ioctl_description * descr; + struct iw_request_info info; + int ret = -EINVAL; +- int user_size = 0; + + /* Get the description of the IOCTL */ + if((cmd - SIOCIWFIRST) >= standard_ioctl_num) +@@ -536,8 +567,14 @@ static inline int ioctl_standard_call(st + #endif /* WE_SET_EVENT */ + } else { + char * extra; ++ int extra_size; ++ int user_length = 0; + int err; + ++ /* Calculate space needed by arguments. Always allocate ++ * for max space. Easier, and won't last long... */ ++ extra_size = descr->max_tokens * descr->token_size; ++ + /* Check what user space is giving us */ + if(IW_IS_SET(cmd)) { + /* Check NULL pointer */ +@@ -554,18 +591,33 @@ static inline int ioctl_standard_call(st + if(iwr->u.data.pointer == NULL) + return -EFAULT; + /* Save user space buffer size for checking */ +- user_size = iwr->u.data.length; ++ user_length = iwr->u.data.length; ++ ++ /* Don't check if user_length > max to allow forward ++ * compatibility. The test user_length < min is ++ * implied by the test at the end. */ ++ ++ /* Support for very large requests */ ++ if((descr->flags & IW_DESCR_FLAG_NOMAX) && ++ (user_length > descr->max_tokens)) { ++ /* Allow userspace to GET more than max so ++ * we can support any size GET requests. ++ * There is still a limit : -ENOMEM. */ ++ extra_size = user_length * descr->token_size; ++ /* Note : user_length is originally a __u16, ++ * and token_size is controlled by us, ++ * so extra_size won't get negative and ++ * won't overflow... */ ++ } + } + + #ifdef WE_IOCTL_DEBUG + printk(KERN_DEBUG "%s (WE) : Malloc %d bytes\n", +- dev->name, descr->max_tokens * descr->token_size); ++ dev->name, extra_size); + #endif /* WE_IOCTL_DEBUG */ + +- /* Always allocate for max space. Easier, and won't last +- * long... */ +- extra = kmalloc(descr->max_tokens * descr->token_size, +- GFP_KERNEL); ++ /* Create the kernel buffer */ ++ extra = kmalloc(extra_size, GFP_KERNEL); + if (extra == NULL) { + return -ENOMEM; + } +@@ -591,14 +643,11 @@ static inline int ioctl_standard_call(st + + /* If we have something to return to the user */ + if (!ret && IW_IS_GET(cmd)) { +-#ifdef WE_STRICT_WRITE + /* Check if there is enough buffer up there */ +- if(user_size < iwr->u.data.length) { +- printk(KERN_ERR "%s (WE) : Buffer for request %04X too small (%d<%d)\n", dev->name, cmd, user_size, iwr->u.data.length); ++ if(user_length < iwr->u.data.length) { + kfree(extra); + return -E2BIG; + } +-#endif /* WE_STRICT_WRITE */ + + err = copy_to_user(iwr->u.data.pointer, extra, + iwr->u.data.length * +@@ -661,7 +710,7 @@ static inline int ioctl_private_call(str + iw_handler handler) + { + struct iwreq * iwr = (struct iwreq *) ifr; +- struct iw_priv_args * descr = NULL; ++ const struct iw_priv_args * descr = NULL; + struct iw_request_info info; + int extra_size = 0; + int i; +@@ -701,7 +750,7 @@ static inline int ioctl_private_call(str + ((extra_size + offset) <= IFNAMSIZ)) + extra_size = 0; + } else { +- /* Size of set arguments */ ++ /* Size of get arguments */ + extra_size = get_priv_size(descr->get_args); + + /* Does it fits in iwr ? */ +@@ -771,6 +820,14 @@ static inline int ioctl_private_call(str + + /* If we have something to return to the user */ + if (!ret && IW_IS_GET(cmd)) { ++ ++ /* Adjust for the actual length if it's variable, ++ * avoid leaking kernel bits outside. */ ++ if (!(descr->get_args & IW_PRIV_SIZE_FIXED)) { ++ extra_size = adjust_priv_size(descr->get_args, ++ &(iwr->u)); ++ } ++ + err = copy_to_user(iwr->u.data.pointer, extra, + extra_size); + if (err) +@@ -1042,9 +1099,25 @@ void wireless_send_event(struct net_devi + * One of the main advantage of centralising spy support here is that + * it becomes much easier to improve and extend it without having to touch + * the drivers. One example is the addition of the Spy-Threshold events. +- * Note : IW_WIRELESS_SPY is defined in iw_handler.h + */ + ++/* ---------------------------------------------------------------- */ ++/* ++ * Return the pointer to the spy data in the driver. ++ * Because this is called on the Rx path via wireless_spy_update(), ++ * we want it to be efficient... ++ */ ++static inline struct iw_spy_data * get_spydata(struct net_device *dev) ++{ ++ /* This is the new way */ ++ if(dev->wireless_data) ++ return(dev->wireless_data->spy_data); ++ ++ /* This is the old way. Doesn't work for multi-headed drivers. ++ * It will be removed in the next version of WE. */ ++ return (dev->priv + dev->wireless_handlers->spy_offset); ++} ++ + /*------------------------------------------------------------------*/ + /* + * Standard Wireless Handler : set Spy List +@@ -1054,16 +1127,26 @@ int iw_handler_set_spy(struct net_device + union iwreq_data * wrqu, + char * extra) + { +-#ifdef IW_WIRELESS_SPY +- struct iw_spy_data * spydata = (dev->priv + +- dev->wireless_handlers->spy_offset); ++ struct iw_spy_data * spydata = get_spydata(dev); + struct sockaddr * address = (struct sockaddr *) extra; + ++ /* Make sure driver is not buggy or using the old API */ ++ if(!spydata) ++ return -EOPNOTSUPP; ++ + /* Disable spy collection while we copy the addresses. +- * As we don't disable interrupts, we need to do this to avoid races. +- * As we are the only writer, this is good enough. */ ++ * While we copy addresses, any call to wireless_spy_update() ++ * will NOP. This is OK, as anyway the addresses are changing. */ + spydata->spy_number = 0; + ++ /* We want to operate without locking, because wireless_spy_update() ++ * most likely will happen in the interrupt handler, and therefore ++ * have its own locking constraints and needs performance. ++ * The rtnl_lock() make sure we don't race with the other iw_handlers. ++ * This make sure wireless_spy_update() "see" that the spy list ++ * is temporarily disabled. */ ++ wmb(); ++ + /* Are there are addresses to copy? */ + if(wrqu->data.length > 0) { + int i; +@@ -1089,13 +1172,14 @@ int iw_handler_set_spy(struct net_device + spydata->spy_address[i][5]); + #endif /* WE_SPY_DEBUG */ + } ++ ++ /* Make sure above is updated before re-enabling */ ++ wmb(); ++ + /* Enable addresses */ + spydata->spy_number = wrqu->data.length; + + return 0; +-#else /* IW_WIRELESS_SPY */ +- return -EOPNOTSUPP; +-#endif /* IW_WIRELESS_SPY */ + } + + /*------------------------------------------------------------------*/ +@@ -1107,12 +1191,14 @@ int iw_handler_get_spy(struct net_device + union iwreq_data * wrqu, + char * extra) + { +-#ifdef IW_WIRELESS_SPY +- struct iw_spy_data * spydata = (dev->priv + +- dev->wireless_handlers->spy_offset); ++ struct iw_spy_data * spydata = get_spydata(dev); + struct sockaddr * address = (struct sockaddr *) extra; + int i; + ++ /* Make sure driver is not buggy or using the old API */ ++ if(!spydata) ++ return -EOPNOTSUPP; ++ + wrqu->data.length = spydata->spy_number; + + /* Copy addresses. */ +@@ -1129,9 +1215,6 @@ int iw_handler_get_spy(struct net_device + for(i = 0; i < spydata->spy_number; i++) + spydata->spy_stat[i].updated = 0; + return 0; +-#else /* IW_WIRELESS_SPY */ +- return -EOPNOTSUPP; +-#endif /* IW_WIRELESS_SPY */ + } + + /*------------------------------------------------------------------*/ +@@ -1143,11 +1226,13 @@ int iw_handler_set_thrspy(struct net_dev + union iwreq_data * wrqu, + char * extra) + { +-#ifdef IW_WIRELESS_THRSPY +- struct iw_spy_data * spydata = (dev->priv + +- dev->wireless_handlers->spy_offset); ++ struct iw_spy_data * spydata = get_spydata(dev); + struct iw_thrspy * threshold = (struct iw_thrspy *) extra; + ++ /* Make sure driver is not buggy or using the old API */ ++ if(!spydata) ++ return -EOPNOTSUPP; ++ + /* Just do it */ + memcpy(&(spydata->spy_thr_low), &(threshold->low), + 2 * sizeof(struct iw_quality)); +@@ -1160,9 +1245,6 @@ int iw_handler_set_thrspy(struct net_dev + #endif /* WE_SPY_DEBUG */ + + return 0; +-#else /* IW_WIRELESS_THRSPY */ +- return -EOPNOTSUPP; +-#endif /* IW_WIRELESS_THRSPY */ + } + + /*------------------------------------------------------------------*/ +@@ -1174,22 +1256,20 @@ int iw_handler_get_thrspy(struct net_dev + union iwreq_data * wrqu, + char * extra) + { +-#ifdef IW_WIRELESS_THRSPY +- struct iw_spy_data * spydata = (dev->priv + +- dev->wireless_handlers->spy_offset); ++ struct iw_spy_data * spydata = get_spydata(dev); + struct iw_thrspy * threshold = (struct iw_thrspy *) extra; + ++ /* Make sure driver is not buggy or using the old API */ ++ if(!spydata) ++ return -EOPNOTSUPP; ++ + /* Just do it */ + memcpy(&(threshold->low), &(spydata->spy_thr_low), + 2 * sizeof(struct iw_quality)); + + return 0; +-#else /* IW_WIRELESS_THRSPY */ +- return -EOPNOTSUPP; +-#endif /* IW_WIRELESS_THRSPY */ + } + +-#ifdef IW_WIRELESS_THRSPY + /*------------------------------------------------------------------*/ + /* + * Prepare and send a Spy Threshold event +@@ -1227,7 +1307,6 @@ static void iw_send_thrspy_event(struct + /* Send event to user space */ + wireless_send_event(dev, SIOCGIWTHRSPY, &wrqu, (char *) &threshold); + } +-#endif /* IW_WIRELESS_THRSPY */ + + /* ---------------------------------------------------------------- */ + /* +@@ -1240,12 +1319,14 @@ void wireless_spy_update(struct net_devi + unsigned char * address, + struct iw_quality * wstats) + { +-#ifdef IW_WIRELESS_SPY +- struct iw_spy_data * spydata = (dev->priv + +- dev->wireless_handlers->spy_offset); ++ struct iw_spy_data * spydata = get_spydata(dev); + int i; + int match = -1; + ++ /* Make sure driver is not buggy or using the old API */ ++ if(!spydata) ++ return; ++ + #ifdef WE_SPY_DEBUG + printk(KERN_DEBUG "wireless_spy_update() : offset %ld, spydata %p, address %02X:%02X:%02X:%02X:%02X:%02X\n", dev->wireless_handlers->spy_offset, spydata, address[0], address[1], address[2], address[3], address[4], address[5]); + #endif /* WE_SPY_DEBUG */ +@@ -1257,7 +1338,7 @@ void wireless_spy_update(struct net_devi + sizeof(struct iw_quality)); + match = i; + } +-#ifdef IW_WIRELESS_THRSPY ++ + /* Generate an event if we cross the spy threshold. + * To avoid event storms, we have a simple hysteresis : we generate + * event only when we go under the low threshold or above the +@@ -1277,6 +1358,4 @@ void wireless_spy_update(struct net_devi + } + } + } +-#endif /* IW_WIRELESS_THRSPY */ +-#endif /* IW_WIRELESS_SPY */ + } diff --git a/recipes/linux/files/iw_handlers.w13-5.diff b/recipes/linux/files/iw_handlers.w13-5.diff new file mode 100644 index 0000000000..a27a7654a9 --- /dev/null +++ b/recipes/linux/files/iw_handlers.w13-5.diff @@ -0,0 +1,1513 @@ +diff -u -p -r --new-file linux/include/linux-w12/netdevice.h linux/include/linux/netdevice.h +--- linux/include/linux-w12/netdevice.h Thu Nov 22 11:47:09 2001 ++++ linux/include/linux/netdevice.h Thu Jan 17 12:00:39 2002 +@@ -278,6 +278,10 @@ struct net_device + struct net_device_stats* (*get_stats)(struct net_device *dev); + struct iw_statistics* (*get_wireless_stats)(struct net_device *dev); + ++ /* List of functions to handle Wireless Extensions (instead of ioctl). ++ * See for details. Jean II */ ++ struct iw_handler_def * wireless_handlers; ++ + /* + * This marks the end of the "visible" part of the structure. All + * fields hereafter are internal to the system, and may change at +diff -u -p -r --new-file linux/include/linux-w12/wireless.h linux/include/linux/wireless.h +--- linux/include/linux-w12/wireless.h Thu Nov 22 11:47:12 2001 ++++ linux/include/linux/wireless.h Thu Jan 17 12:04:08 2002 +@@ -1,9 +1,10 @@ + /* + * This file define a set of standard wireless extensions + * +- * Version : 12 5.10.01 ++ * Version : 13 6.12.01 + * + * Authors : Jean Tourrilhes - HPL - ++ * Copyright (c) 1997-2001 Jean Tourrilhes, All Rights Reserved. + */ + + #ifndef _LINUX_WIRELESS_H +@@ -11,6 +12,8 @@ + + /************************** DOCUMENTATION **************************/ + /* ++ * Initial APIs (1996 -> onward) : ++ * ----------------------------- + * Basically, the wireless extensions are for now a set of standard ioctl + * call + /proc/net/wireless + * +@@ -27,16 +30,27 @@ + * We have the list of command plus a structure descibing the + * data exchanged... + * Note that to add these ioctl, I was obliged to modify : +- * net/core/dev.c (two place + add include) +- * net/ipv4/af_inet.c (one place + add include) ++ * # net/core/dev.c (two place + add include) ++ * # net/ipv4/af_inet.c (one place + add include) + * + * /proc/net/wireless is a copy of /proc/net/dev. + * We have a structure for data passed from the driver to /proc/net/wireless + * Too add this, I've modified : +- * net/core/dev.c (two other places) +- * include/linux/netdevice.h (one place) +- * include/linux/proc_fs.h (one place) ++ * # net/core/dev.c (two other places) ++ * # include/linux/netdevice.h (one place) ++ * # include/linux/proc_fs.h (one place) ++ * ++ * New driver API (2001 -> onward) : ++ * ------------------------------- ++ * This file is only concerned with the user space API and common definitions. ++ * The new driver API is defined and documented in : ++ * # include/net/iw_handler.h + * ++ * Note as well that /proc/net/wireless implementation has now moved in : ++ * # include/linux/wireless.c ++ * ++ * Other comments : ++ * -------------- + * Do not add here things that are redundant with other mechanisms + * (drivers init, ifconfig, /proc/net/dev, ...) and with are not + * wireless specific. +@@ -54,16 +68,14 @@ + #include /* for "struct sockaddr" et al */ + #include /* for IFNAMSIZ and co... */ + +-/**************************** CONSTANTS ****************************/ +- +-/* --------------------------- VERSION --------------------------- */ ++/***************************** VERSION *****************************/ + /* + * This constant is used to know the availability of the wireless + * extensions and to know which version of wireless extensions it is + * (there is some stuff that will be added in the future...) + * I just plan to increment with each new version. + */ +-#define WIRELESS_EXT 12 ++#define WIRELESS_EXT 13 + + /* + * Changes : +@@ -123,12 +135,20 @@ + * - Add DEV PRIVATE IOCTL to avoid collisions in SIOCDEVPRIVATE space + * - Add new statistics (frag, retry, beacon) + * - Add average quality (for user space calibration) ++ * ++ * V12 to V13 ++ * ---------- ++ * - Document creation of new driver API. ++ * - Extract union iwreq_data from struct iwreq (for new driver API). ++ * - Rename SIOCSIWNAME as SIOCSIWCOMMIT + */ + ++/**************************** CONSTANTS ****************************/ ++ + /* -------------------------- IOCTL LIST -------------------------- */ + + /* Basic operations */ +-#define SIOCSIWNAME 0x8B00 /* Unused */ ++#define SIOCSIWCOMMIT 0x8B00 /* Commit pending changes to driver */ + #define SIOCGIWNAME 0x8B01 /* get name == wireless protocol */ + #define SIOCSIWNWID 0x8B02 /* set network id (the cell) */ + #define SIOCGIWNWID 0x8B03 /* get network id */ +@@ -414,13 +434,49 @@ struct iw_statistics + + /* ------------------------ IOCTL REQUEST ------------------------ */ + /* ++ * This structure defines the payload of an ioctl, and is used ++ * below. ++ * ++ * Note that this structure should fit on the memory footprint ++ * of iwreq (which is the same as ifreq), which mean a max size of ++ * 16 octets = 128 bits. Warning, pointers might be 64 bits wide... ++ * You should check this when increasing the structures defined ++ * above in this file... ++ */ ++union iwreq_data ++{ ++ /* Config - generic */ ++ char name[IFNAMSIZ]; ++ /* Name : used to verify the presence of wireless extensions. ++ * Name of the protocol/provider... */ ++ ++ struct iw_point essid; /* Extended network name */ ++ struct iw_param nwid; /* network id (or domain - the cell) */ ++ struct iw_freq freq; /* frequency or channel : ++ * 0-1000 = channel ++ * > 1000 = frequency in Hz */ ++ ++ struct iw_param sens; /* signal level threshold */ ++ struct iw_param bitrate; /* default bit rate */ ++ struct iw_param txpower; /* default transmit power */ ++ struct iw_param rts; /* RTS threshold threshold */ ++ struct iw_param frag; /* Fragmentation threshold */ ++ __u32 mode; /* Operation mode */ ++ struct iw_param retry; /* Retry limits & lifetime */ ++ ++ struct iw_point encoding; /* Encoding stuff : tokens */ ++ struct iw_param power; /* PM duration/timeout */ ++ ++ struct sockaddr ap_addr; /* Access point address */ ++ ++ struct iw_point data; /* Other large parameters */ ++}; ++ ++/* + * The structure to exchange data for ioctl. + * This structure is the same as 'struct ifreq', but (re)defined for + * convenience... +- * +- * Note that it should fit on the same memory footprint ! +- * You should check this when increasing the above structures (16 octets) +- * 16 octets = 128 bits. Warning, pointers might be 64 bits wide... ++ * Do I need to remind you about structure size (32 octets) ? + */ + struct iwreq + { +@@ -429,35 +485,8 @@ struct iwreq + char ifrn_name[IFNAMSIZ]; /* if name, e.g. "eth0" */ + } ifr_ifrn; + +- /* Data part */ +- union +- { +- /* Config - generic */ +- char name[IFNAMSIZ]; +- /* Name : used to verify the presence of wireless extensions. +- * Name of the protocol/provider... */ +- +- struct iw_point essid; /* Extended network name */ +- struct iw_param nwid; /* network id (or domain - the cell) */ +- struct iw_freq freq; /* frequency or channel : +- * 0-1000 = channel +- * > 1000 = frequency in Hz */ +- +- struct iw_param sens; /* signal level threshold */ +- struct iw_param bitrate; /* default bit rate */ +- struct iw_param txpower; /* default transmit power */ +- struct iw_param rts; /* RTS threshold threshold */ +- struct iw_param frag; /* Fragmentation threshold */ +- __u32 mode; /* Operation mode */ +- struct iw_param retry; /* Retry limits & lifetime */ +- +- struct iw_point encoding; /* Encoding stuff : tokens */ +- struct iw_param power; /* PM duration/timeout */ +- +- struct sockaddr ap_addr; /* Access point address */ +- +- struct iw_point data; /* Other large parameters */ +- } u; ++ /* Data part (defined just above) */ ++ union iwreq_data u; + }; + + /* -------------------------- IOCTL DATA -------------------------- */ +diff -u -p -r --new-file linux/include/net-w12/iw_handler.h linux/include/net/iw_handler.h +--- linux/include/net-w12/iw_handler.h Wed Dec 31 16:00:00 1969 ++++ linux/include/net/iw_handler.h Thu Jan 17 12:16:46 2002 +@@ -0,0 +1,374 @@ ++/* ++ * This file define the new driver API for Wireless Extensions ++ * ++ * Version : 2 6.12.01 ++ * ++ * Authors : Jean Tourrilhes - HPL - ++ * Copyright (c) 2001 Jean Tourrilhes, All Rights Reserved. ++ */ ++ ++#ifndef _IW_HANDLER_H ++#define _IW_HANDLER_H ++ ++/************************** DOCUMENTATION **************************/ ++/* ++ * Initial driver API (1996 -> onward) : ++ * ----------------------------------- ++ * The initial API just sends the IOCTL request received from user space ++ * to the driver (via the driver ioctl handler). The driver has to ++ * handle all the rest... ++ * ++ * The initial API also defines a specific handler in struct net_device ++ * to handle wireless statistics. ++ * ++ * The initial APIs served us well and has proven a reasonably good design. ++ * However, there is a few shortcommings : ++ * o No events, everything is a request to the driver. ++ * o Large ioctl function in driver with gigantic switch statement ++ * (i.e. spaghetti code). ++ * o Driver has to mess up with copy_to/from_user, and in many cases ++ * does it unproperly. Common mistakes are : ++ * * buffer overflows (no checks or off by one checks) ++ * * call copy_to/from_user with irq disabled ++ * o The user space interface is tied to ioctl because of the use ++ * copy_to/from_user. ++ * ++ * New driver API (2001 -> onward) : ++ * ------------------------------- ++ * The new driver API is just a bunch of standard functions (handlers), ++ * each handling a specific Wireless Extension. The driver just export ++ * the list of handler it supports, and those will be called apropriately. ++ * ++ * I tried to keep the main advantage of the previous API (simplicity, ++ * efficiency and light weight), and also I provide a good dose of backward ++ * compatibility (most structures are the same, driver can use both API ++ * simultaneously, ...). ++ * Hopefully, I've also addressed the shortcomming of the initial API. ++ * ++ * The advantage of the new API are : ++ * o Handling of Extensions in driver broken in small contained functions ++ * o Tighter checks of ioctl before calling the driver ++ * o Flexible commit strategy (at least, the start of it) ++ * o Backward compatibility (can be mixed with old API) ++ * o Driver doesn't have to worry about memory and user-space issues ++ * The last point is important for the following reasons : ++ * o You are now able to call the new driver API from any API you ++ * want (including from within other parts of the kernel). ++ * o Common mistakes are avoided (buffer overflow, user space copy ++ * with irq disabled and so on). ++ * ++ * The Drawback of the new API are : ++ * o bloat (especially kernel) ++ * o need to migrate existing drivers to new API ++ * My initial testing shows that the new API adds around 3kB to the kernel ++ * and save between 0 and 5kB from a typical driver. ++ * Also, as all structures and data types are unchanged, the migration is ++ * quite straightforward (but tedious). ++ * ++ * --- ++ * ++ * The new driver API is defined below in this file. User space should ++ * not be aware of what's happening down there... ++ * ++ * A new kernel wrapper is in charge of validating the IOCTLs and calling ++ * the appropriate driver handler. This is implemented in : ++ * # net/core/wireless.c ++ * ++ * The driver export the list of handlers in : ++ * # include/linux/netdevice.h (one place) ++ * ++ * The new driver API is available for WIRELESS_EXT >= 13. ++ * Good luck with migration to the new API ;-) ++ */ ++ ++/* ---------------------- THE IMPLEMENTATION ---------------------- */ ++/* ++ * Some of the choice I've made are pretty controversials. Defining an ++ * API is very much weighting compromises. This goes into some of the ++ * details and the thinking behind the implementation. ++ * ++ * Implementation goals : ++ * -------------------- ++ * The implementation goals were as follow : ++ * o Obvious : you should not need a PhD to understand what's happening, ++ * the benefit is easier maintainance. ++ * o Flexible : it should accomodate a wide variety of driver ++ * implementations and be as flexible as the old API. ++ * o Lean : it should be efficient memory wise to minimise the impact ++ * on kernel footprint. ++ * o Transparent to user space : the large number of user space ++ * applications that use Wireless Extensions should not need ++ * any modifications. ++ * ++ * Array of functions versus Struct of functions ++ * --------------------------------------------- ++ * 1) Having an array of functions allow the kernel code to access the ++ * handler in a single lookup, which is much more efficient (think hash ++ * table here). ++ * 2) The only drawback is that driver writer may put their handler in ++ * the wrong slot. This is trivial to test (I set the frequency, the ++ * bitrate changes). Once the handler is in the proper slot, it will be ++ * there forever, because the array is only extended at the end. ++ * 3) Backward/forward compatibility : adding new handler just require ++ * extending the array, so you can put newer driver in older kernel ++ * without having to patch the kernel code (and vice versa). ++ * ++ * All handler are of the same generic type ++ * ---------------------------------------- ++ * That's a feature !!! ++ * 1) Having a generic handler allow to have generic code, which is more ++ * efficient. If each of the handler was individually typed I would need ++ * to add a big switch in the kernel (== more bloat). This solution is ++ * more scalable, adding new Wireless Extensions doesn't add new code. ++ * 2) You can use the same handler in different slots of the array. For ++ * hardware, it may be more efficient or logical to handle multiple ++ * Wireless Extensions with a single function, and the API allow you to ++ * do that. (An example would be a single record on the card to control ++ * both bitrate and frequency, the handler would read the old record, ++ * modify it according to info->cmd and rewrite it). ++ * ++ * Functions prototype uses union iwreq_data ++ * ----------------------------------------- ++ * Some would have prefered functions defined this way : ++ * static int mydriver_ioctl_setrate(struct net_device *dev, ++ * long rate, int auto) ++ * 1) The kernel code doesn't "validate" the content of iwreq_data, and ++ * can't do it (different hardware may have different notion of what a ++ * valid frequency is), so we don't pretend that we do it. ++ * 2) The above form is not extendable. If I want to add a flag (for ++ * example to distinguish setting max rate and basic rate), I would ++ * break the prototype. Using iwreq_data is more flexible. ++ * 3) Also, the above form is not generic (see above). ++ * 4) I don't expect driver developper using the wrong field of the ++ * union (Doh !), so static typechecking doesn't add much value. ++ * 5) Lastly, you can skip the union by doing : ++ * static int mydriver_ioctl_setrate(struct net_device *dev, ++ * struct iw_request_info *info, ++ * struct iw_param *rrq, ++ * char *extra) ++ * And then adding the handler in the array like this : ++ * (iw_handler) mydriver_ioctl_setrate, // SIOCSIWRATE ++ * ++ * Using functions and not a registry ++ * ---------------------------------- ++ * Another implementation option would have been for every instance to ++ * define a registry (a struct containing all the Wireless Extensions) ++ * and only have a function to commit the registry to the hardware. ++ * 1) This approach can be emulated by the current code, but not ++ * vice versa. ++ * 2) Some drivers don't keep any configuration in the driver, for them ++ * adding such a registry would be a significant bloat. ++ * 3) The code to translate from Wireless Extension to native format is ++ * needed anyway, so it would not reduce significantely the amount of code. ++ * 4) The current approach only selectively translate Wireless Extensions ++ * to native format and only selectively set, whereas the registry approach ++ * would require to translate all WE and set all parameters for any single ++ * change. ++ * 5) For many Wireless Extensions, the GET operation return the current ++ * dynamic value, not the value that was set. ++ * ++ * This header is ++ * --------------------------------- ++ * 1) This header is kernel space only and should not be exported to ++ * user space. Headers in "include/linux/" are exported, headers in ++ * "include/net/" are not. ++ * ++ * Mixed 32/64 bit issues ++ * ---------------------- ++ * The Wireless Extensions are designed to be 64 bit clean, by using only ++ * datatypes with explicit storage size. ++ * There are some issues related to kernel and user space using different ++ * memory model, and in particular 64bit kernel with 32bit user space. ++ * The problem is related to struct iw_point, that contains a pointer ++ * that *may* need to be translated. ++ * This is quite messy. The new API doesn't solve this problem (it can't), ++ * but is a step in the right direction : ++ * 1) Meta data about each ioctl is easily available, so we know what type ++ * of translation is needed. ++ * 2) The move of data between kernel and user space is only done in a single ++ * place in the kernel, so adding specific hooks in there is possible. ++ * 3) In the long term, it allows to move away from using ioctl as the ++ * user space API. ++ * ++ * So many comments and so few code ++ * -------------------------------- ++ * That's a feature. Comments won't bloat the resulting kernel binary. ++ */ ++ ++/***************************** INCLUDES *****************************/ ++ ++#include /* IOCTL user space API */ ++ ++/***************************** VERSION *****************************/ ++/* ++ * This constant is used to know which version of the driver API is ++ * available. Hopefully, this will be pretty stable and no changes ++ * will be needed... ++ * I just plan to increment with each new version. ++ */ ++#define IW_HANDLER_VERSION 2 ++ ++/**************************** CONSTANTS ****************************/ ++ ++/* Special error message for the driver to indicate that we ++ * should do a commit after return from the iw_handler */ ++#define EIWCOMMIT EINPROGRESS ++ ++/* Flags available in struct iw_request_info */ ++#define IW_REQUEST_FLAG_NONE 0x0000 /* No flag so far */ ++ ++/* Type of headers we know about (basically union iwreq_data) */ ++#define IW_HEADER_TYPE_NULL 0 /* Not available */ ++#define IW_HEADER_TYPE_CHAR 2 /* char [IFNAMSIZ] */ ++#define IW_HEADER_TYPE_UINT 4 /* __u32 */ ++#define IW_HEADER_TYPE_FREQ 5 /* struct iw_freq */ ++#define IW_HEADER_TYPE_POINT 6 /* struct iw_point */ ++#define IW_HEADER_TYPE_PARAM 7 /* struct iw_param */ ++#define IW_HEADER_TYPE_ADDR 8 /* struct sockaddr */ ++ ++/* Handling flags */ ++/* Most are not implemented. I just use them as a reminder of some ++ * cool features we might need one day ;-) */ ++#define IW_DESCR_FLAG_NONE 0x0000 /* Obvious */ ++/* Wrapper level flags */ ++#define IW_DESCR_FLAG_DUMP 0x0001 /* Not part of the dump command */ ++#define IW_DESCR_FLAG_EVENT 0x0002 /* Generate an event on SET */ ++#define IW_DESCR_FLAG_RESTRICT 0x0004 /* GET request is ROOT only */ ++/* Driver level flags */ ++#define IW_DESCR_FLAG_WAIT 0x0100 /* Wait for driver event */ ++ ++/****************************** TYPES ******************************/ ++ ++/* ----------------------- WIRELESS HANDLER ----------------------- */ ++/* ++ * A wireless handler is just a standard function, that looks like the ++ * ioctl handler. ++ * We also define there how a handler list look like... As the Wireless ++ * Extension space is quite dense, we use a simple array, which is faster ++ * (that's the perfect hash table ;-). ++ */ ++ ++/* ++ * Meta data about the request passed to the iw_handler. ++ * Most handlers can safely ignore what's in there. ++ * The 'cmd' field might come handy if you want to use the same handler ++ * for multiple command... ++ * This struct is also my long term insurance. I can add new fields here ++ * without breaking the prototype of iw_handler... ++ */ ++struct iw_request_info ++{ ++ __u16 cmd; /* Wireless Extension command */ ++ __u16 flags; /* More to come ;-) */ ++}; ++ ++/* ++ * This is how a function handling a Wireless Extension should look ++ * like (both get and set, standard and private). ++ */ ++typedef int (*iw_handler)(struct net_device *dev, struct iw_request_info *info, ++ union iwreq_data *wrqu, char *extra); ++ ++/* ++ * This define all the handler that the driver export. ++ * As you need only one per driver type, please use a static const ++ * shared by all driver instances... Same for the members... ++ * This will be linked from net_device in ++ */ ++struct iw_handler_def ++{ ++ /* Number of handlers defined (more precisely, index of the ++ * last defined handler + 1) */ ++ __u16 num_standard; ++ __u16 num_private; ++ /* Number of private arg description */ ++ __u16 num_private_args; ++ ++ /* Array of handlers for standard ioctls ++ * We will call dev->wireless_handlers->standard[ioctl - SIOCSIWNAME] ++ */ ++ iw_handler * standard; ++ ++ /* Array of handlers for private ioctls ++ * Will call dev->wireless_handlers->private[ioctl - SIOCIWFIRSTPRIV] ++ */ ++ iw_handler * private; ++ ++ /* Arguments of private handler. This one is just a list, so you ++ * can put it in any order you want and should not leave holes... ++ * We will automatically export that to user space... */ ++ struct iw_priv_args * private_args; ++ ++ /* In the long term, get_wireless_stats will move from ++ * 'struct net_device' to here, to minimise bloat. */ ++}; ++ ++/* ----------------------- WIRELESS EVENTS ----------------------- */ ++/* ++ * Currently we don't support events, so let's just plan for the ++ * future... ++ */ ++ ++/* ++ * A Wireless Event. ++ */ ++// How do we define short header ? We don't want a flag on length. ++// Probably a flag on event ? Highest bit to zero... ++struct iw_event ++{ ++ __u16 length; /* Lenght of this stuff */ ++ __u16 event; /* Wireless IOCTL */ ++ union iwreq_data header; /* IOCTL fixed payload */ ++ char extra[0]; /* Optional IOCTL data */ ++}; ++ ++/* ---------------------- IOCTL DESCRIPTION ---------------------- */ ++/* ++ * One of the main goal of the new interface is to deal entirely with ++ * user space/kernel space memory move. ++ * For that, we need to know : ++ * o if iwreq is a pointer or contain the full data ++ * o what is the size of the data to copy ++ * ++ * For private IOCTLs, we use the same rules as used by iwpriv and ++ * defined in struct iw_priv_args. ++ * ++ * For standard IOCTLs, things are quite different and we need to ++ * use the stuctures below. Actually, this struct is also more ++ * efficient, but that's another story... ++ */ ++ ++/* ++ * Describe how a standard IOCTL looks like. ++ */ ++struct iw_ioctl_description ++{ ++ __u8 header_type; /* NULL, iw_point or other */ ++ __u8 token_type; /* Future */ ++ __u16 token_size; /* Granularity of payload */ ++ __u16 min_tokens; /* Min acceptable token number */ ++ __u16 max_tokens; /* Max acceptable token number */ ++ __u32 flags; /* Special handling of the request */ ++}; ++ ++/* Need to think of short header translation table. Later. */ ++ ++/**************************** PROTOTYPES ****************************/ ++/* ++ * Functions part of the Wireless Extensions (defined in net/core/wireless.c). ++ * Those may be called only within the kernel. ++ */ ++ ++/* First : function strictly used inside the kernel */ ++ ++/* Handle /proc/net/wireless, called in net/code/dev.c */ ++extern int dev_get_wireless_info(char * buffer, char **start, off_t offset, ++ int length); ++ ++/* Handle IOCTLs, called in net/code/dev.c */ ++extern int wireless_process_ioctl(struct ifreq *ifr, unsigned int cmd); ++ ++/* Second : functions that may be called by driver modules */ ++/* None yet */ ++ ++#endif /* _LINUX_WIRELESS_H */ +diff -u -p -r --new-file linux/net/core-w12/Makefile linux/net/core/Makefile +--- linux/net/core-w12/Makefile Tue Oct 30 15:08:12 2001 ++++ linux/net/core/Makefile Thu Jan 17 11:06:07 2002 +@@ -26,5 +26,8 @@ obj-$(CONFIG_NET) += dev.o dev_mcast.o d + obj-$(CONFIG_NETFILTER) += netfilter.o + obj-$(CONFIG_NET_DIVERT) += dv.o + obj-$(CONFIG_NET_PROFILE) += profile.o ++obj-$(CONFIG_NET_RADIO) += wireless.o ++# Ugly. I wish all wireless drivers were moved in drivers/net/wireless ++obj-$(CONFIG_NET_PCMCIA_RADIO) += wireless.o + + include $(TOPDIR)/Rules.make +diff -u -p -r --new-file linux/net/core-w12/dev.c linux/net/core/dev.c +--- linux/net/core-w12/dev.c Wed Nov 7 14:39:36 2001 ++++ linux/net/core/dev.c Thu Jan 17 11:06:07 2002 +@@ -102,6 +102,7 @@ + #include + #if defined(CONFIG_NET_RADIO) || defined(CONFIG_NET_PCMCIA_RADIO) + #include /* Note : will define WIRELESS_EXT */ ++#include + #endif /* CONFIG_NET_RADIO || CONFIG_NET_PCMCIA_RADIO */ + #ifdef CONFIG_PLIP + extern int plip_init(void); +@@ -1796,122 +1797,6 @@ static int dev_proc_stats(char *buffer, + #endif /* CONFIG_PROC_FS */ + + +-#ifdef WIRELESS_EXT +-#ifdef CONFIG_PROC_FS +- +-/* +- * Print one entry of /proc/net/wireless +- * This is a clone of /proc/net/dev (just above) +- */ +-static int sprintf_wireless_stats(char *buffer, struct net_device *dev) +-{ +- /* Get stats from the driver */ +- struct iw_statistics *stats = (dev->get_wireless_stats ? +- dev->get_wireless_stats(dev) : +- (struct iw_statistics *) NULL); +- int size; +- +- if (stats != (struct iw_statistics *) NULL) { +- size = sprintf(buffer, +- "%6s: %04x %3d%c %3d%c %3d%c %6d %6d %6d %6d %6d %6d\n", +- dev->name, +- stats->status, +- stats->qual.qual, +- stats->qual.updated & 1 ? '.' : ' ', +- stats->qual.level, +- stats->qual.updated & 2 ? '.' : ' ', +- stats->qual.noise, +- stats->qual.updated & 4 ? '.' : ' ', +- stats->discard.nwid, +- stats->discard.code, +- stats->discard.fragment, +- stats->discard.retries, +- stats->discard.misc, +- stats->miss.beacon); +- stats->qual.updated = 0; +- } +- else +- size = 0; +- +- return size; +-} +- +-/* +- * Print info for /proc/net/wireless (print all entries) +- * This is a clone of /proc/net/dev (just above) +- */ +-static int dev_get_wireless_info(char * buffer, char **start, off_t offset, +- int length) +-{ +- int len = 0; +- off_t begin = 0; +- off_t pos = 0; +- int size; +- +- struct net_device * dev; +- +- size = sprintf(buffer, +- "Inter-| sta-| Quality | Discarded packets | Missed\n" +- " face | tus | link level noise | nwid crypt frag retry misc | beacon\n" +- ); +- +- pos += size; +- len += size; +- +- read_lock(&dev_base_lock); +- for (dev = dev_base; dev != NULL; dev = dev->next) { +- size = sprintf_wireless_stats(buffer + len, dev); +- len += size; +- pos = begin + len; +- +- if (pos < offset) { +- len = 0; +- begin = pos; +- } +- if (pos > offset + length) +- break; +- } +- read_unlock(&dev_base_lock); +- +- *start = buffer + (offset - begin); /* Start of wanted data */ +- len -= (offset - begin); /* Start slop */ +- if (len > length) +- len = length; /* Ending slop */ +- if (len < 0) +- len = 0; +- +- return len; +-} +-#endif /* CONFIG_PROC_FS */ +- +-/* +- * Allow programatic access to /proc/net/wireless even if /proc +- * doesn't exist... Also more efficient... +- */ +-static inline int dev_iwstats(struct net_device *dev, struct ifreq *ifr) +-{ +- /* Get stats from the driver */ +- struct iw_statistics *stats = (dev->get_wireless_stats ? +- dev->get_wireless_stats(dev) : +- (struct iw_statistics *) NULL); +- +- if (stats != (struct iw_statistics *) NULL) { +- struct iwreq * wrq = (struct iwreq *)ifr; +- +- /* Copy statistics to the user buffer */ +- if(copy_to_user(wrq->u.data.pointer, stats, +- sizeof(struct iw_statistics))) +- return -EFAULT; +- +- /* Check if we need to clear the update flag */ +- if(wrq->u.data.flags != 0) +- stats->qual.updated = 0; +- return(0); +- } else +- return -EOPNOTSUPP; +-} +-#endif /* WIRELESS_EXT */ +- + /** + * netdev_set_master - set up master/slave pair + * @slave: slave device +@@ -2209,11 +2094,6 @@ static int dev_ifsioc(struct ifreq *ifr, + notifier_call_chain(&netdev_chain, NETDEV_CHANGENAME, dev); + return 0; + +-#ifdef WIRELESS_EXT +- case SIOCGIWSTATS: +- return dev_iwstats(dev, ifr); +-#endif /* WIRELESS_EXT */ +- + /* + * Unknown or private ioctl + */ +@@ -2239,17 +2119,6 @@ static int dev_ifsioc(struct ifreq *ifr, + return -EOPNOTSUPP; + } + +-#ifdef WIRELESS_EXT +- if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) { +- if (dev->do_ioctl) { +- if (!netif_device_present(dev)) +- return -ENODEV; +- return dev->do_ioctl(dev, ifr, cmd); +- } +- return -EOPNOTSUPP; +- } +-#endif /* WIRELESS_EXT */ +- + } + return -EINVAL; + } +@@ -2431,7 +2300,8 @@ int dev_ioctl(unsigned int cmd, void *ar + } + dev_load(ifr.ifr_name); + rtnl_lock(); +- ret = dev_ifsioc(&ifr, cmd); ++ /* Follow me in net/core/wireless.c */ ++ ret = wireless_process_ioctl(&ifr, cmd); + rtnl_unlock(); + if (!ret && IW_IS_GET(cmd) && + copy_to_user(arg, &ifr, sizeof(struct ifreq))) +@@ -2856,6 +2726,7 @@ int __init net_dev_init(void) + proc_net_create("dev", 0, dev_get_info); + create_proc_read_entry("net/softnet_stat", 0, 0, dev_proc_stats, NULL); + #ifdef WIRELESS_EXT ++ /* Available in net/core/wireless.c */ + proc_net_create("wireless", 0, dev_get_wireless_info); + #endif /* WIRELESS_EXT */ + #endif /* CONFIG_PROC_FS */ +diff -u -p -r --new-file linux/net/core-w12/wireless.c linux/net/core/wireless.c +--- linux/net/core-w12/wireless.c Wed Dec 31 16:00:00 1969 ++++ linux/net/core/wireless.c Mon Jan 21 11:13:23 2002 +@@ -0,0 +1,733 @@ ++/* ++ * This file implement the Wireless Extensions APIs. ++ * ++ * Authors : Jean Tourrilhes - HPL - ++ * Copyright (c) 1997-2001 Jean Tourrilhes, All Rights Reserved. ++ * ++ * (As all part of the Linux kernel, this file is GPL) ++ */ ++ ++/************************** DOCUMENTATION **************************/ ++/* ++ * API definition : ++ * -------------- ++ * See for details of the APIs and the rest. ++ * ++ * History : ++ * ------- ++ * ++ * v1 - 5.12.01 - Jean II ++ * o Created this file. ++ * ++ * v2 - 13.12.01 - Jean II ++ * o Move /proc/net/wireless stuff from net/core/dev.c to here ++ * o Make Wireless Extension IOCTLs go through here ++ * o Added iw_handler handling ;-) ++ * o Added standard ioctl description ++ * o Initial dumb commit strategy based on orinoco.c ++ */ ++ ++/***************************** INCLUDES *****************************/ ++ ++#include /* copy_to_user() */ ++#include /* Not needed ??? */ ++#include /* off_t */ ++#include /* struct ifreq, dev_get_by_name() */ ++ ++#include /* Pretty obvious */ ++#include /* New driver API */ ++ ++/**************************** CONSTANTS ****************************/ ++ ++/* This will be turned on later on... */ ++#undef WE_STRICT_WRITE /* Check write buffer size */ ++ ++/* Debuging stuff */ ++#undef WE_IOCTL_DEBUG /* Debug IOCTL API */ ++ ++/************************* GLOBAL VARIABLES *************************/ ++/* ++ * You should not use global variables, because or re-entrancy. ++ * On our case, it's only const, so it's OK... ++ */ ++static const struct iw_ioctl_description standard_ioctl[] = { ++ /* SIOCSIWCOMMIT (internal) */ ++ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* SIOCGIWNAME */ ++ { IW_HEADER_TYPE_CHAR, 0, 0, 0, 0, IW_DESCR_FLAG_DUMP}, ++ /* SIOCSIWNWID */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, IW_DESCR_FLAG_EVENT}, ++ /* SIOCGIWNWID */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, IW_DESCR_FLAG_DUMP}, ++ /* SIOCSIWFREQ */ ++ { IW_HEADER_TYPE_FREQ, 0, 0, 0, 0, IW_DESCR_FLAG_EVENT}, ++ /* SIOCGIWFREQ */ ++ { IW_HEADER_TYPE_FREQ, 0, 0, 0, 0, IW_DESCR_FLAG_DUMP}, ++ /* SIOCSIWMODE */ ++ { IW_HEADER_TYPE_UINT, 0, 0, 0, 0, IW_DESCR_FLAG_EVENT}, ++ /* SIOCGIWMODE */ ++ { IW_HEADER_TYPE_UINT, 0, 0, 0, 0, IW_DESCR_FLAG_DUMP}, ++ /* SIOCSIWSENS */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCGIWSENS */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCSIWRANGE */ ++ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* SIOCGIWRANGE */ ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, sizeof(struct iw_range), IW_DESCR_FLAG_DUMP}, ++ /* SIOCSIWPRIV */ ++ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* SIOCGIWPRIV (handled directly by us) */ ++ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* SIOCSIWSTATS */ ++ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* SIOCGIWSTATS (handled directly by us) */ ++ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, IW_DESCR_FLAG_DUMP}, ++ /* SIOCSIWSPY */ ++ { IW_HEADER_TYPE_POINT, 0, sizeof(struct sockaddr), 0, IW_MAX_SPY, 0}, ++ /* SIOCGIWSPY */ ++ { IW_HEADER_TYPE_POINT, 0, (sizeof(struct sockaddr) + sizeof(struct iw_quality)), 0, IW_MAX_SPY, 0}, ++ /* -- hole -- */ ++ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* -- hole -- */ ++ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* SIOCSIWAP */ ++ { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, 0}, ++ /* SIOCGIWAP */ ++ { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, IW_DESCR_FLAG_DUMP}, ++ /* -- hole -- */ ++ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* SIOCGIWAPLIST */ ++ { IW_HEADER_TYPE_POINT, 0, (sizeof(struct sockaddr) + sizeof(struct iw_quality)), 0, IW_MAX_AP, 0}, ++ /* -- hole -- */ ++ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* -- hole -- */ ++ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* SIOCSIWESSID */ ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE, IW_DESCR_FLAG_EVENT}, ++ /* SIOCGIWESSID */ ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE, IW_DESCR_FLAG_DUMP}, ++ /* SIOCSIWNICKN */ ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE, 0}, ++ /* SIOCGIWNICKN */ ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE, 0}, ++ /* -- hole -- */ ++ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* -- hole -- */ ++ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* SIOCSIWRATE */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCGIWRATE */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCSIWRTS */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCGIWRTS */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCSIWFRAG */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCGIWFRAG */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCSIWTXPOW */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCGIWTXPOW */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCSIWRETRY */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCGIWRETRY */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCSIWENCODE */ ++ { IW_HEADER_TYPE_POINT, 4, 1, 0, IW_ENCODING_TOKEN_MAX, IW_DESCR_FLAG_EVENT | IW_DESCR_FLAG_RESTRICT}, ++ /* SIOCGIWENCODE */ ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ENCODING_TOKEN_MAX, IW_DESCR_FLAG_DUMP | IW_DESCR_FLAG_RESTRICT}, ++ /* SIOCSIWPOWER */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCGIWPOWER */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++}; ++ ++/* Size (in bytes) of the various private data types */ ++char priv_type_size[] = { 0, 1, 1, 0, 4, 4, 0, 0 }; ++ ++/************************ COMMON SUBROUTINES ************************/ ++/* ++ * Stuff that may be used in various place or doesn't fit in one ++ * of the section below. ++ */ ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Return the driver handler associated with a specific Wireless Extension. ++ * Called from various place, so make sure it remains efficient. ++ */ ++static inline iw_handler get_handler(struct net_device *dev, ++ unsigned int cmd) ++{ ++ unsigned int index; /* MUST be unsigned */ ++ ++ /* Check if we have some wireless handlers defined */ ++ if(dev->wireless_handlers == NULL) ++ return NULL; ++ ++ /* Try as a standard command */ ++ index = cmd - SIOCIWFIRST; ++ if(index < dev->wireless_handlers->num_standard) ++ return dev->wireless_handlers->standard[index]; ++ ++ /* Try as a private command */ ++ index = cmd - SIOCIWFIRSTPRIV; ++ if(index < dev->wireless_handlers->num_private) ++ return dev->wireless_handlers->private[index]; ++ ++ /* Not found */ ++ return NULL; ++} ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Get statistics out of the driver ++ */ ++static inline struct iw_statistics *get_wireless_stats(struct net_device *dev) ++{ ++ return (dev->get_wireless_stats ? ++ dev->get_wireless_stats(dev) : ++ (struct iw_statistics *) NULL); ++ /* In the future, get_wireless_stats may move from 'struct net_device' ++ * to 'struct iw_handler_def', to de-bloat struct net_device. ++ * Definitely worse a thought... */ ++} ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Call the commit handler in the driver ++ * (if exist and if conditions are right) ++ * ++ * Note : our current commit strategy is currently pretty dumb, ++ * but we will be able to improve on that... ++ * The goal is to try to agreagate as many changes as possible ++ * before doing the commit. Drivers that will define a commit handler ++ * are usually those that need a reset after changing parameters, so ++ * we want to minimise the number of reset. ++ * A cool idea is to use a timer : at each "set" command, we re-set the ++ * timer, when the timer eventually fires, we call the driver. ++ * Hopefully, more on that later. ++ * ++ * Also, I'm waiting to see how many people will complain about the ++ * netif_running(dev) test. I'm open on that one... ++ * Hopefully, the driver will remember to do a commit in "open()" ;-) ++ */ ++static inline int call_commit_handler(struct net_device * dev) ++{ ++ if((netif_running(dev)) && ++ (dev->wireless_handlers->standard[0] != NULL)) { ++ /* Call the commit handler on the driver */ ++ return dev->wireless_handlers->standard[0](dev, NULL, ++ NULL, NULL); ++ } else ++ return 0; /* Command completed successfully */ ++} ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Number of private arguments ++ */ ++static inline int get_priv_size(__u16 args) ++{ ++ int num = args & IW_PRIV_SIZE_MASK; ++ int type = (args & IW_PRIV_TYPE_MASK) >> 12; ++ ++ return num * priv_type_size[type]; ++} ++ ++ ++/******************** /proc/net/wireless SUPPORT ********************/ ++/* ++ * The /proc/net/wireless file is a human readable user-space interface ++ * exporting various wireless specific statistics from the wireless devices. ++ * This is the most popular part of the Wireless Extensions ;-) ++ * ++ * This interface is a pure clone of /proc/net/dev (in net/core/dev.c). ++ * The content of the file is basically the content of "struct iw_statistics". ++ */ ++ ++#ifdef CONFIG_PROC_FS ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Print one entry (line) of /proc/net/wireless ++ */ ++static inline int sprintf_wireless_stats(char *buffer, struct net_device *dev) ++{ ++ /* Get stats from the driver */ ++ struct iw_statistics *stats; ++ int size; ++ ++ stats = get_wireless_stats(dev); ++ if (stats != (struct iw_statistics *) NULL) { ++ size = sprintf(buffer, ++ "%6s: %04x %3d%c %3d%c %3d%c %6d %6d %6d %6d %6d %6d\n", ++ dev->name, ++ stats->status, ++ stats->qual.qual, ++ stats->qual.updated & 1 ? '.' : ' ', ++ stats->qual.level, ++ stats->qual.updated & 2 ? '.' : ' ', ++ stats->qual.noise, ++ stats->qual.updated & 4 ? '.' : ' ', ++ stats->discard.nwid, ++ stats->discard.code, ++ stats->discard.fragment, ++ stats->discard.retries, ++ stats->discard.misc, ++ stats->miss.beacon); ++ stats->qual.updated = 0; ++ } ++ else ++ size = 0; ++ ++ return size; ++} ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Print info for /proc/net/wireless (print all entries) ++ */ ++int dev_get_wireless_info(char * buffer, char **start, off_t offset, ++ int length) ++{ ++ int len = 0; ++ off_t begin = 0; ++ off_t pos = 0; ++ int size; ++ ++ struct net_device * dev; ++ ++ size = sprintf(buffer, ++ "Inter-| sta-| Quality | Discarded packets | Missed\n" ++ " face | tus | link level noise | nwid crypt frag retry misc | beacon\n" ++ ); ++ ++ pos += size; ++ len += size; ++ ++ read_lock(&dev_base_lock); ++ for (dev = dev_base; dev != NULL; dev = dev->next) { ++ size = sprintf_wireless_stats(buffer + len, dev); ++ len += size; ++ pos = begin + len; ++ ++ if (pos < offset) { ++ len = 0; ++ begin = pos; ++ } ++ if (pos > offset + length) ++ break; ++ } ++ read_unlock(&dev_base_lock); ++ ++ *start = buffer + (offset - begin); /* Start of wanted data */ ++ len -= (offset - begin); /* Start slop */ ++ if (len > length) ++ len = length; /* Ending slop */ ++ if (len < 0) ++ len = 0; ++ ++ return len; ++} ++#endif /* CONFIG_PROC_FS */ ++ ++/************************** IOCTL SUPPORT **************************/ ++/* ++ * The original user space API to configure all those Wireless Extensions ++ * is through IOCTLs. ++ * In there, we check if we need to call the new driver API (iw_handler) ++ * or just call the driver ioctl handler. ++ */ ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Allow programatic access to /proc/net/wireless even if /proc ++ * doesn't exist... Also more efficient... ++ */ ++static inline int dev_iwstats(struct net_device *dev, struct ifreq *ifr) ++{ ++ /* Get stats from the driver */ ++ struct iw_statistics *stats; ++ ++ stats = get_wireless_stats(dev); ++ if (stats != (struct iw_statistics *) NULL) { ++ struct iwreq * wrq = (struct iwreq *)ifr; ++ ++ /* Copy statistics to the user buffer */ ++ if(copy_to_user(wrq->u.data.pointer, stats, ++ sizeof(struct iw_statistics))) ++ return -EFAULT; ++ ++ /* Check if we need to clear the update flag */ ++ if(wrq->u.data.flags != 0) ++ stats->qual.updated = 0; ++ return 0; ++ } else ++ return -EOPNOTSUPP; ++} ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Export the driver private handler definition ++ * They will be picked up by tools like iwpriv... ++ */ ++static inline int ioctl_export_private(struct net_device * dev, ++ struct ifreq * ifr) ++{ ++ struct iwreq * iwr = (struct iwreq *) ifr; ++ ++ /* Check if the driver has something to export */ ++ if((dev->wireless_handlers->num_private_args == 0) || ++ (dev->wireless_handlers->private_args == NULL)) ++ return -EOPNOTSUPP; ++ ++ /* Check NULL pointer */ ++ if(iwr->u.data.pointer == NULL) ++ return -EFAULT; ++#ifdef WE_STRICT_WRITE ++ /* Check if there is enough buffer up there */ ++ if(iwr->u.data.length < (SIOCIWLASTPRIV - SIOCIWFIRSTPRIV + 1)) ++ return -E2BIG; ++#endif /* WE_STRICT_WRITE */ ++ ++ /* Set the number of available ioctls. */ ++ iwr->u.data.length = dev->wireless_handlers->num_private_args; ++ ++ /* Copy structure to the user buffer. */ ++ if (copy_to_user(iwr->u.data.pointer, ++ dev->wireless_handlers->private_args, ++ sizeof(struct iw_priv_args) * iwr->u.data.length)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Wrapper to call a standard Wireless Extension handler. ++ * We do various checks and also take care of moving data between ++ * user space and kernel space. ++ */ ++static inline int ioctl_standard_call(struct net_device * dev, ++ struct ifreq * ifr, ++ unsigned int cmd, ++ iw_handler handler) ++{ ++ struct iwreq * iwr = (struct iwreq *) ifr; ++ const struct iw_ioctl_description * descr; ++ struct iw_request_info info; ++ int ret = -EINVAL; ++ ++ /* Get the description of the IOCTL */ ++ descr = &(standard_ioctl[cmd - SIOCIWFIRST]); ++ ++#ifdef WE_IOCTL_DEBUG ++ printk(KERN_DEBUG "%s : Found standard handler for 0x%04X\n", ++ ifr->ifr_name, cmd); ++ printk(KERN_DEBUG "Header type : %d, token type : %d, token_size : %d, max_token : %d\n", descr->header_type, descr->token_type, descr->token_size, descr->max_tokens); ++#endif /* WE_IOCTL_DEBUG */ ++ ++ /* Prepare the call */ ++ info.cmd = cmd; ++ info.flags = 0; ++ ++ /* Check if we have a pointer to user space data or not */ ++ if(descr->header_type != IW_HEADER_TYPE_POINT) { ++ /* No extra arguments. Trivial to handle */ ++ ret = handler(dev, &info, &(iwr->u), NULL); ++ } else { ++ char * extra; ++ int err; ++ ++ /* Check what user space is giving us */ ++ if(IW_IS_SET(cmd)) { ++ /* Check NULL pointer */ ++ if((iwr->u.data.pointer == NULL) && ++ (iwr->u.data.length != 0)) ++ return -EFAULT; ++ /* Check if number of token fits within bounds */ ++ if(iwr->u.data.length > descr->max_tokens) ++ return -E2BIG; ++ if(iwr->u.data.length < descr->min_tokens) ++ return -EINVAL; ++ } else { ++ /* Check NULL pointer */ ++ if(iwr->u.data.pointer == NULL) ++ return -EFAULT; ++#ifdef WE_STRICT_WRITE ++ /* Check if there is enough buffer up there */ ++ if(iwr->u.data.length < descr->max_tokens) ++ return -E2BIG; ++#endif /* WE_STRICT_WRITE */ ++ } ++ ++#ifdef WE_IOCTL_DEBUG ++ printk(KERN_DEBUG "Malloc %d bytes\n", ++ descr->max_tokens * descr->token_size); ++#endif /* WE_IOCTL_DEBUG */ ++ ++ /* Always allocate for max space. Easier, and won't last ++ * long... */ ++ extra = kmalloc(descr->max_tokens * descr->token_size, ++ GFP_KERNEL); ++ if (extra == NULL) { ++ return -ENOMEM; ++ } ++ ++ /* If it is a SET, get all the extra data in here */ ++ if(IW_IS_SET(cmd) && (iwr->u.data.length != 0)) { ++ err = copy_from_user(extra, iwr->u.data.pointer, ++ iwr->u.data.length * ++ descr->token_size); ++ if (err) { ++ kfree(extra); ++ return -EFAULT; ++ } ++#ifdef WE_IOCTL_DEBUG ++ printk(KERN_DEBUG "Got %d bytes\n", ++ iwr->u.data.length * descr->token_size); ++#endif /* WE_IOCTL_DEBUG */ ++ } ++ ++ /* Call the handler */ ++ ret = handler(dev, &info, &(iwr->u), extra); ++ ++ /* If we have something to return to the user */ ++ if (!ret && IW_IS_GET(cmd)) { ++ err = copy_to_user(iwr->u.data.pointer, extra, ++ iwr->u.data.length * ++ descr->token_size); ++ if (err) ++ ret = -EFAULT; ++#ifdef WE_IOCTL_DEBUG ++ printk(KERN_DEBUG "Wrote %d bytes\n", ++ iwr->u.data.length * descr->token_size); ++#endif /* WE_IOCTL_DEBUG */ ++ } ++ ++ /* Cleanup - I told you it wasn't that long ;-) */ ++ kfree(extra); ++ } ++ ++ /* Call commit handler if needed and defined */ ++ if(ret == -EIWCOMMIT) ++ ret = call_commit_handler(dev); ++ ++ /* Here, we will generate the appropriate event if needed */ ++ ++ return ret; ++} ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Wrapper to call a private Wireless Extension handler. ++ * We do various checks and also take care of moving data between ++ * user space and kernel space. ++ * It's not as nice and slimline as the standard wrapper. The cause ++ * is struct iw_priv_args, which was not really designed for the ++ * job we are going here. ++ * ++ * IMPORTANT : This function prevent to set and get data on the same ++ * IOCTL and enforce the SET/GET convention. Not doing it would be ++ * far too hairy... ++ * If you need to set and get data at the same time, please don't use ++ * a iw_handler but process it in your ioctl handler (i.e. use the ++ * old driver API). ++ */ ++static inline int ioctl_private_call(struct net_device * dev, ++ struct ifreq * ifr, ++ unsigned int cmd, ++ iw_handler handler) ++{ ++ struct iwreq * iwr = (struct iwreq *) ifr; ++ struct iw_priv_args * descr = NULL; ++ struct iw_request_info info; ++ int extra_size = 0; ++ int i; ++ int ret = -EINVAL; ++ ++ /* Get the description of the IOCTL */ ++ for(i = 0; i < dev->wireless_handlers->num_private_args; i++) ++ if(cmd == dev->wireless_handlers->private_args[i].cmd) { ++ descr = &(dev->wireless_handlers->private_args[i]); ++ break; ++ } ++ ++#ifdef WE_IOCTL_DEBUG ++ printk(KERN_DEBUG "%s : Found private handler for 0x%04X\n", ++ ifr->ifr_name, cmd); ++ if(descr) { ++ printk(KERN_DEBUG "Name %s, set %X, get %X\n", ++ descr->name, descr->set_args, descr->get_args); ++ } ++#endif /* WE_IOCTL_DEBUG */ ++ ++ /* Compute the size of the set/get arguments */ ++ if(descr != NULL) { ++ if(IW_IS_SET(cmd)) { ++ /* Size of set arguments */ ++ extra_size = get_priv_size(descr->set_args); ++ ++ /* Does it fits in iwr ? */ ++ if((descr->set_args & IW_PRIV_SIZE_FIXED) && ++ (extra_size < IFNAMSIZ)) ++ extra_size = 0; ++ } else { ++ /* Size of set arguments */ ++ extra_size = get_priv_size(descr->get_args); ++ ++ /* Does it fits in iwr ? */ ++ if((descr->get_args & IW_PRIV_SIZE_FIXED) && ++ (extra_size < IFNAMSIZ)) ++ extra_size = 0; ++ } ++ } ++ ++ /* Prepare the call */ ++ info.cmd = cmd; ++ info.flags = 0; ++ ++ /* Check if we have a pointer to user space data or not. */ ++ if(extra_size == 0) { ++ /* No extra arguments. Trivial to handle */ ++ ret = handler(dev, &info, &(iwr->u), (char *) &(iwr->u)); ++ } else { ++ char * extra; ++ int err; ++ ++ /* Check what user space is giving us */ ++ if(IW_IS_SET(cmd)) { ++ /* Check NULL pointer */ ++ if((iwr->u.data.pointer == NULL) && ++ (iwr->u.data.length != 0)) ++ return -EFAULT; ++ ++ /* Does it fits within bounds ? */ ++ if(iwr->u.data.length > (descr->set_args & ++ IW_PRIV_SIZE_MASK)) ++ return -E2BIG; ++ } else { ++ /* Check NULL pointer */ ++ if(iwr->u.data.pointer == NULL) ++ return -EFAULT; ++ } ++ ++#ifdef WE_IOCTL_DEBUG ++ printk(KERN_DEBUG "Malloc %d bytes\n", extra_size); ++#endif /* WE_IOCTL_DEBUG */ ++ ++ /* Always allocate for max space. Easier, and won't last ++ * long... */ ++ extra = kmalloc(extra_size, GFP_KERNEL); ++ if (extra == NULL) { ++ return -ENOMEM; ++ } ++ ++ /* If it is a SET, get all the extra data in here */ ++ if(IW_IS_SET(cmd) && (iwr->u.data.length != 0)) { ++ err = copy_from_user(extra, iwr->u.data.pointer, ++ extra_size); ++ if (err) { ++ kfree(extra); ++ return -EFAULT; ++ } ++#ifdef WE_IOCTL_DEBUG ++ printk(KERN_DEBUG "Got %d elem\n", iwr->u.data.length); ++#endif /* WE_IOCTL_DEBUG */ ++ } ++ ++ /* Call the handler */ ++ ret = handler(dev, &info, &(iwr->u), extra); ++ ++ /* If we have something to return to the user */ ++ if (!ret && IW_IS_GET(cmd)) { ++ err = copy_to_user(iwr->u.data.pointer, extra, ++ extra_size); ++ if (err) ++ ret = -EFAULT; ++#ifdef WE_IOCTL_DEBUG ++ printk(KERN_DEBUG "Wrote %d elem\n", ++ iwr->u.data.length); ++#endif /* WE_IOCTL_DEBUG */ ++ } ++ ++ /* Cleanup - I told you it wasn't that long ;-) */ ++ kfree(extra); ++ } ++ ++ ++ /* Call commit handler if needed and defined */ ++ if(ret == -EIWCOMMIT) ++ ret = call_commit_handler(dev); ++ ++ return ret; ++} ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Main IOCTl dispatcher. Called from the main networking code ++ * (dev_ioctl() in net/core/dev.c). ++ * Check the type of IOCTL and call the appropriate wrapper... ++ */ ++int wireless_process_ioctl(struct ifreq *ifr, unsigned int cmd) ++{ ++ struct net_device *dev; ++ iw_handler handler; ++ ++ /* Permissions are already checked in dev_ioctl() before calling us. ++ * The copy_to/from_user() of ifr is also dealt with in there */ ++ ++ /* Make sure the device exist */ ++ if ((dev = __dev_get_by_name(ifr->ifr_name)) == NULL) ++ return -ENODEV; ++ ++ /* A bunch of special cases, then the generic case... ++ * Note that 'cmd' is already filtered in dev_ioctl() with ++ * (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) */ ++ switch(cmd) ++ { ++ case SIOCGIWSTATS: ++ /* Get Wireless Stats */ ++ return dev_iwstats(dev, ifr); ++ ++ case SIOCGIWPRIV: ++ /* Check if we have some wireless handlers defined */ ++ if(dev->wireless_handlers != NULL) { ++ /* We export to user space the definition of ++ * the private handler ourselves */ ++ return ioctl_export_private(dev, ifr); ++ } ++ // ## Fall-through for old API ## ++ default: ++ /* Generic IOCTL */ ++ /* Basic check */ ++ if (!netif_device_present(dev)) ++ return -ENODEV; ++ /* New driver API : try to find the handler */ ++ handler = get_handler(dev, cmd); ++ if(handler != NULL) { ++ /* Standard and private are not the same */ ++ if(cmd < SIOCIWFIRSTPRIV) ++ return ioctl_standard_call(dev, ++ ifr, ++ cmd, ++ handler); ++ else ++ return ioctl_private_call(dev, ++ ifr, ++ cmd, ++ handler); ++ } ++ /* Old driver API : call driver ioctl handler */ ++ if (dev->do_ioctl) { ++ return dev->do_ioctl(dev, ifr, cmd); ++ } ++ return -EOPNOTSUPP; ++ } ++ /* Not reached */ ++ return -EINVAL; ++} diff --git a/recipes/linux/files/iw_handlers.w14-5.diff b/recipes/linux/files/iw_handlers.w14-5.diff new file mode 100644 index 0000000000..539b160068 --- /dev/null +++ b/recipes/linux/files/iw_handlers.w14-5.diff @@ -0,0 +1,838 @@ +diff -u -p -r --new-file linux/include/linux-w13/rtnetlink.h linux/include/linux/rtnetlink.h +--- linux/include/linux-w13/rtnetlink.h Thu Jun 6 14:44:08 2002 ++++ linux/include/linux/rtnetlink.h Thu Jun 6 15:47:44 2002 +@@ -440,12 +440,14 @@ enum + #define IFLA_COST IFLA_COST + IFLA_PRIORITY, + #define IFLA_PRIORITY IFLA_PRIORITY +- IFLA_MASTER ++ IFLA_MASTER, + #define IFLA_MASTER IFLA_MASTER ++ IFLA_WIRELESS, /* Wireless Extension event - see wireless.h */ ++#define IFLA_WIRELESS IFLA_WIRELESS + }; + + +-#define IFLA_MAX IFLA_MASTER ++#define IFLA_MAX IFLA_WIRELESS + + #define IFLA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg)))) + #define IFLA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifinfomsg)) +diff -u -p -r --new-file linux/include/linux-w13/wireless.h linux/include/linux/wireless.h +--- linux/include/linux-w13/wireless.h Thu Jun 6 15:00:28 2002 ++++ linux/include/linux/wireless.h Thu Jun 6 15:47:44 2002 +@@ -1,10 +1,10 @@ + /* + * This file define a set of standard wireless extensions + * +- * Version : 13 6.12.01 ++ * Version : 14 25.1.02 + * + * Authors : Jean Tourrilhes - HPL - +- * Copyright (c) 1997-2001 Jean Tourrilhes, All Rights Reserved. ++ * Copyright (c) 1997-2002 Jean Tourrilhes, All Rights Reserved. + */ + + #ifndef _LINUX_WIRELESS_H +@@ -40,7 +40,7 @@ + * # include/linux/netdevice.h (one place) + * # include/linux/proc_fs.h (one place) + * +- * New driver API (2001 -> onward) : ++ * New driver API (2002 -> onward) : + * ------------------------------- + * This file is only concerned with the user space API and common definitions. + * The new driver API is defined and documented in : +@@ -49,6 +49,11 @@ + * Note as well that /proc/net/wireless implementation has now moved in : + * # include/linux/wireless.c + * ++ * Wireless Events (2002 -> onward) : ++ * -------------------------------- ++ * Events are defined at the end of this file, and implemented in : ++ * # include/linux/wireless.c ++ * + * Other comments : + * -------------- + * Do not add here things that are redundant with other mechanisms +@@ -75,7 +80,7 @@ + * (there is some stuff that will be added in the future...) + * I just plan to increment with each new version. + */ +-#define WIRELESS_EXT 13 ++#define WIRELESS_EXT 14 + + /* + * Changes : +@@ -141,6 +146,13 @@ + * - Document creation of new driver API. + * - Extract union iwreq_data from struct iwreq (for new driver API). + * - Rename SIOCSIWNAME as SIOCSIWCOMMIT ++ * ++ * V13 to V14 ++ * ---------- ++ * - Wireless Events support : define struct iw_event ++ * - Define additional specific event numbers ++ * - Add "addr" and "param" fields in union iwreq_data ++ * - AP scanning stuff (SIOCSIWSCAN and friends) + */ + + /**************************** CONSTANTS ****************************/ +@@ -175,6 +187,8 @@ + #define SIOCSIWAP 0x8B14 /* set access point MAC addresses */ + #define SIOCGIWAP 0x8B15 /* get access point MAC addresses */ + #define SIOCGIWAPLIST 0x8B17 /* get list of access point in range */ ++#define SIOCSIWSCAN 0x8B18 /* trigger scanning */ ++#define SIOCGIWSCAN 0x8B19 /* get scanning results */ + + /* 802.11 specific support */ + #define SIOCSIWESSID 0x8B1A /* set ESSID (network name) */ +@@ -238,6 +252,15 @@ + #define IW_IS_SET(cmd) (!((cmd) & 0x1)) + #define IW_IS_GET(cmd) ((cmd) & 0x1) + ++/* ----------------------- WIRELESS EVENTS ----------------------- */ ++/* Those are *NOT* ioctls, do not issue request on them !!! */ ++/* Most events use the same identifier as ioctl requests */ ++ ++#define IWEVTXDROP 0x8C00 /* Packet dropped to excessive retry */ ++#define IWEVQUAL 0x8C01 /* Quality part of statistics */ ++ ++#define IWEVFIRST 0x8C00 ++ + /* ------------------------- PRIVATE INFO ------------------------- */ + /* + * The following is used with SIOCGIWPRIV. It allow a driver to define +@@ -340,6 +363,19 @@ + #define IW_RETRY_MAX 0x0002 /* Value is a maximum */ + #define IW_RETRY_RELATIVE 0x0004 /* Value is not in seconds/ms/us */ + ++/* Scanning request flags */ ++#define IW_SCAN_DEFAULT 0x0000 /* Default scan of the driver */ ++#define IW_SCAN_ALL_ESSID 0x0001 /* Scan all ESSIDs */ ++#define IW_SCAN_THIS_ESSID 0x0002 /* Scan only this ESSID */ ++#define IW_SCAN_ALL_FREQ 0x0004 /* Scan all Frequencies */ ++#define IW_SCAN_THIS_FREQ 0x0008 /* Scan only this Frequency */ ++#define IW_SCAN_ALL_MODE 0x0010 /* Scan all Modes */ ++#define IW_SCAN_THIS_MODE 0x0020 /* Scan only this Mode */ ++#define IW_SCAN_ALL_RATE 0x0040 /* Scan all Bit-Rates */ ++#define IW_SCAN_THIS_RATE 0x0080 /* Scan only this Bit-Rate */ ++/* Maximum size of returned data */ ++#define IW_SCAN_MAX_DATA 4096 /* In bytes */ ++ + /****************************** TYPES ******************************/ + + /* --------------------------- SUBTYPES --------------------------- */ +@@ -466,9 +502,12 @@ union iwreq_data + + struct iw_point encoding; /* Encoding stuff : tokens */ + struct iw_param power; /* PM duration/timeout */ ++ struct iw_quality qual; /* Quality part of statistics */ + + struct sockaddr ap_addr; /* Access point address */ ++ struct sockaddr addr; /* Destination address (hw) */ + ++ struct iw_param param; /* Other small parameters */ + struct iw_point data; /* Other large parameters */ + }; + +@@ -595,5 +634,36 @@ struct iw_priv_args + __u16 get_args; /* Type and number of args */ + char name[IFNAMSIZ]; /* Name of the extension */ + }; ++ ++/* ----------------------- WIRELESS EVENTS ----------------------- */ ++/* ++ * Wireless events are carried through the rtnetlink socket to user ++ * space. They are encapsulated in the IFLA_WIRELESS field of ++ * a RTM_NEWLINK message. ++ */ ++ ++/* ++ * A Wireless Event. Contains basically the same data as the ioctl... ++ */ ++struct iw_event ++{ ++ __u16 len; /* Real lenght of this stuff */ ++ __u16 cmd; /* Wireless IOCTL */ ++ union iwreq_data u; /* IOCTL fixed payload */ ++}; ++ ++/* Size of the Event prefix (including padding and alignement junk) */ ++#define IW_EV_LCP_LEN (sizeof(struct iw_event) - sizeof(union iwreq_data)) ++/* Size of the various events */ ++#define IW_EV_CHAR_LEN (IW_EV_LCP_LEN + IFNAMSIZ) ++#define IW_EV_UINT_LEN (IW_EV_LCP_LEN + sizeof(__u32)) ++#define IW_EV_FREQ_LEN (IW_EV_LCP_LEN + sizeof(struct iw_freq)) ++#define IW_EV_POINT_LEN (IW_EV_LCP_LEN + sizeof(struct iw_point)) ++#define IW_EV_PARAM_LEN (IW_EV_LCP_LEN + sizeof(struct iw_param)) ++#define IW_EV_ADDR_LEN (IW_EV_LCP_LEN + sizeof(struct sockaddr)) ++#define IW_EV_QUAL_LEN (IW_EV_LCP_LEN + sizeof(struct iw_quality)) ++ ++/* Note : in the case of iw_point, the extra data will come at the ++ * end of the event */ + + #endif /* _LINUX_WIRELESS_H */ +diff -u -p -r --new-file linux/include/net-w13/iw_handler.h linux/include/net/iw_handler.h +--- linux/include/net-w13/iw_handler.h Thu Jun 6 15:06:16 2002 ++++ linux/include/net/iw_handler.h Thu Jun 6 15:48:06 2002 +@@ -1,10 +1,10 @@ + /* + * This file define the new driver API for Wireless Extensions + * +- * Version : 2 6.12.01 ++ * Version : 3 17.1.02 + * + * Authors : Jean Tourrilhes - HPL - +- * Copyright (c) 2001 Jean Tourrilhes, All Rights Reserved. ++ * Copyright (c) 2001-2002 Jean Tourrilhes, All Rights Reserved. + */ + + #ifndef _IW_HANDLER_H +@@ -33,7 +33,7 @@ + * o The user space interface is tied to ioctl because of the use + * copy_to/from_user. + * +- * New driver API (2001 -> onward) : ++ * New driver API (2002 -> onward) : + * ------------------------------- + * The new driver API is just a bunch of standard functions (handlers), + * each handling a specific Wireless Extension. The driver just export +@@ -206,7 +206,18 @@ + * will be needed... + * I just plan to increment with each new version. + */ +-#define IW_HANDLER_VERSION 2 ++#define IW_HANDLER_VERSION 3 ++ ++/* ++ * Changes : ++ * ++ * V2 to V3 ++ * -------- ++ * - Move event definition in ++ * - Add Wireless Event support : ++ * o wireless_send_event() prototype ++ * o iwe_stream_add_event/point() inline functions ++ */ + + /**************************** CONSTANTS ****************************/ + +@@ -225,6 +236,7 @@ + #define IW_HEADER_TYPE_POINT 6 /* struct iw_point */ + #define IW_HEADER_TYPE_PARAM 7 /* struct iw_param */ + #define IW_HEADER_TYPE_ADDR 8 /* struct sockaddr */ ++#define IW_HEADER_TYPE_QUAL 9 /* struct iw_quality */ + + /* Handling flags */ + /* Most are not implemented. I just use them as a reminder of some +@@ -233,7 +245,8 @@ + /* Wrapper level flags */ + #define IW_DESCR_FLAG_DUMP 0x0001 /* Not part of the dump command */ + #define IW_DESCR_FLAG_EVENT 0x0002 /* Generate an event on SET */ +-#define IW_DESCR_FLAG_RESTRICT 0x0004 /* GET request is ROOT only */ ++#define IW_DESCR_FLAG_RESTRICT 0x0004 /* GET : request is ROOT only */ ++ /* SET : Omit payload from generated iwevent */ + /* Driver level flags */ + #define IW_DESCR_FLAG_WAIT 0x0100 /* Wait for driver event */ + +@@ -303,25 +316,6 @@ struct iw_handler_def + * 'struct net_device' to here, to minimise bloat. */ + }; + +-/* ----------------------- WIRELESS EVENTS ----------------------- */ +-/* +- * Currently we don't support events, so let's just plan for the +- * future... +- */ +- +-/* +- * A Wireless Event. +- */ +-// How do we define short header ? We don't want a flag on length. +-// Probably a flag on event ? Highest bit to zero... +-struct iw_event +-{ +- __u16 length; /* Lenght of this stuff */ +- __u16 event; /* Wireless IOCTL */ +- union iwreq_data header; /* IOCTL fixed payload */ +- char extra[0]; /* Optional IOCTL data */ +-}; +- + /* ---------------------- IOCTL DESCRIPTION ---------------------- */ + /* + * One of the main goal of the new interface is to deal entirely with +@@ -369,6 +363,88 @@ extern int dev_get_wireless_info(char * + extern int wireless_process_ioctl(struct ifreq *ifr, unsigned int cmd); + + /* Second : functions that may be called by driver modules */ +-/* None yet */ + +-#endif /* _LINUX_WIRELESS_H */ ++/* Send a single event to user space */ ++extern void wireless_send_event(struct net_device * dev, ++ unsigned int cmd, ++ union iwreq_data * wrqu, ++ char * extra); ++ ++/* We may need a function to send a stream of events to user space. ++ * More on that later... */ ++ ++/************************* INLINE FUNTIONS *************************/ ++/* ++ * Function that are so simple that it's more efficient inlining them ++ */ ++ ++/*------------------------------------------------------------------*/ ++/* ++ * Wrapper to add an Wireless Event to a stream of events. ++ */ ++static inline char * ++iwe_stream_add_event(char * stream, /* Stream of events */ ++ char * ends, /* End of stream */ ++ struct iw_event *iwe, /* Payload */ ++ int event_len) /* Real size of payload */ ++{ ++ /* Check if it's possible */ ++ if((stream + event_len) < ends) { ++ iwe->len = event_len; ++ memcpy(stream, (char *) iwe, event_len); ++ stream += event_len; ++ } ++ return stream; ++} ++ ++/*------------------------------------------------------------------*/ ++/* ++ * Wrapper to add an short Wireless Event containing a pointer to a ++ * stream of events. ++ */ ++static inline char * ++iwe_stream_add_point(char * stream, /* Stream of events */ ++ char * ends, /* End of stream */ ++ struct iw_event *iwe, /* Payload */ ++ char * extra) ++{ ++ int event_len = IW_EV_POINT_LEN + iwe->u.data.length; ++ /* Check if it's possible */ ++ if((stream + event_len) < ends) { ++ iwe->len = event_len; ++ memcpy(stream, (char *) iwe, IW_EV_POINT_LEN); ++ memcpy(stream + IW_EV_POINT_LEN, extra, iwe->u.data.length); ++ stream += event_len; ++ } ++ return stream; ++} ++ ++/*------------------------------------------------------------------*/ ++/* ++ * Wrapper to add a value to a Wireless Event in a stream of events. ++ * Be careful, this one is tricky to use properly : ++ * At the first run, you need to have (value = event + IW_EV_LCP_LEN). ++ */ ++static inline char * ++iwe_stream_add_value(char * event, /* Event in the stream */ ++ char * value, /* Value in event */ ++ char * ends, /* End of stream */ ++ struct iw_event *iwe, /* Payload */ ++ int event_len) /* Real size of payload */ ++{ ++ /* Don't duplicate LCP */ ++ event_len -= IW_EV_LCP_LEN; ++ ++ /* Check if it's possible */ ++ if((value + event_len) < ends) { ++ /* Add new value */ ++ memcpy(value, (char *) iwe + IW_EV_LCP_LEN, event_len); ++ value += event_len; ++ /* Patch LCP */ ++ iwe->len = value - event; ++ memcpy(event, (char *) iwe, IW_EV_LCP_LEN); ++ } ++ return value; ++} ++ ++#endif /* _IW_HANDLER_H */ +diff -u -p -r --new-file linux/net/netsyms-w13.c linux/net/netsyms.c +--- linux/net/netsyms-w13.c Thu Jun 6 15:46:34 2002 ++++ linux/net/netsyms.c Thu Jun 6 15:47:44 2002 +@@ -588,4 +588,10 @@ EXPORT_SYMBOL(register_gifconf); + EXPORT_SYMBOL(net_call_rx_atomic); + EXPORT_SYMBOL(softnet_data); + ++#if defined(CONFIG_NET_RADIO) || defined(CONFIG_NET_PCMCIA_RADIO) ++/* Don't include the whole header mess for a single function */ ++extern void wireless_send_event(struct net_device *dev, unsigned int cmd, union iwreq_data *wrqu, char *extra); ++EXPORT_SYMBOL(wireless_send_event); ++#endif /* CONFIG_NET_RADIO || CONFIG_NET_PCMCIA_RADIO */ ++ + #endif /* CONFIG_NET */ +diff -u -p -r --new-file linux/net/core/wireless-w13.c linux/net/core/wireless.c +--- linux/net/core/wireless-w13.c Thu Jun 6 15:46:45 2002 ++++ linux/net/core/wireless.c Thu Jun 6 15:48:06 2002 +@@ -2,7 +2,7 @@ + * This file implement the Wireless Extensions APIs. + * + * Authors : Jean Tourrilhes - HPL - +- * Copyright (c) 1997-2001 Jean Tourrilhes, All Rights Reserved. ++ * Copyright (c) 1997-2002 Jean Tourrilhes, All Rights Reserved. + * + * (As all part of the Linux kernel, this file is GPL) + */ +@@ -25,6 +25,16 @@ + * o Added iw_handler handling ;-) + * o Added standard ioctl description + * o Initial dumb commit strategy based on orinoco.c ++ * ++ * v3 - 19.12.01 - Jean II ++ * o Make sure we don't go out of standard_ioctl[] in ioctl_standard_call ++ * o Add event dispatcher function ++ * o Add event description ++ * o Propagate events as rtnetlink IFLA_WIRELESS option ++ * o Generate event on selected SET requests ++ * ++ * v4 - 18.04.01 - Jean II ++ * o Fix stupid off by one in iw_ioctl_description : IW_ESSID_MAX_SIZE + 1 + */ + + /***************************** INCLUDES *****************************/ +@@ -33,6 +43,7 @@ + #include /* Not needed ??? */ + #include /* off_t */ + #include /* struct ifreq, dev_get_by_name() */ ++#include /* rtnetlink stuff */ + + #include /* Pretty obvious */ + #include /* New driver API */ +@@ -44,14 +55,23 @@ + + /* Debuging stuff */ + #undef WE_IOCTL_DEBUG /* Debug IOCTL API */ ++#undef WE_EVENT_DEBUG /* Debug Event dispatcher */ ++ ++/* Options */ ++#define WE_EVENT_NETLINK /* Propagate events using rtnetlink */ ++#define WE_SET_EVENT /* Generate an event on some set commands */ + + /************************* GLOBAL VARIABLES *************************/ + /* + * You should not use global variables, because or re-entrancy. + * On our case, it's only const, so it's OK... + */ ++/* ++ * Meta-data about all the standard Wireless Extension request we ++ * know about. ++ */ + static const struct iw_ioctl_description standard_ioctl[] = { +- /* SIOCSIWCOMMIT (internal) */ ++ /* SIOCSIWCOMMIT */ + { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, + /* SIOCGIWNAME */ + { IW_HEADER_TYPE_CHAR, 0, 0, 0, 0, IW_DESCR_FLAG_DUMP}, +@@ -99,18 +119,18 @@ static const struct iw_ioctl_description + { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, + /* SIOCGIWAPLIST */ + { IW_HEADER_TYPE_POINT, 0, (sizeof(struct sockaddr) + sizeof(struct iw_quality)), 0, IW_MAX_AP, 0}, +- /* -- hole -- */ +- { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, +- /* -- hole -- */ +- { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, ++ /* SIOCSIWSCAN */ ++ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, ++ /* SIOCGIWSCAN */ ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_SCAN_MAX_DATA, 0}, + /* SIOCSIWESSID */ +- { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE, IW_DESCR_FLAG_EVENT}, ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE + 1, IW_DESCR_FLAG_EVENT}, + /* SIOCGIWESSID */ +- { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE, IW_DESCR_FLAG_DUMP}, ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE + 1, IW_DESCR_FLAG_DUMP}, + /* SIOCSIWNICKN */ +- { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE, 0}, ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE + 1, 0}, + /* SIOCGIWNICKN */ +- { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE, 0}, ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE + 1, 0}, + /* -- hole -- */ + { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0}, + /* -- hole -- */ +@@ -136,7 +156,7 @@ static const struct iw_ioctl_description + /* SIOCGIWRETRY */ + { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, + /* SIOCSIWENCODE */ +- { IW_HEADER_TYPE_POINT, 4, 1, 0, IW_ENCODING_TOKEN_MAX, IW_DESCR_FLAG_EVENT | IW_DESCR_FLAG_RESTRICT}, ++ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ENCODING_TOKEN_MAX, IW_DESCR_FLAG_EVENT | IW_DESCR_FLAG_RESTRICT}, + /* SIOCGIWENCODE */ + { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ENCODING_TOKEN_MAX, IW_DESCR_FLAG_DUMP | IW_DESCR_FLAG_RESTRICT}, + /* SIOCSIWPOWER */ +@@ -144,9 +164,38 @@ static const struct iw_ioctl_description + /* SIOCGIWPOWER */ + { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0}, + }; ++static const int standard_ioctl_num = (sizeof(standard_ioctl) / ++ sizeof(struct iw_ioctl_description)); ++ ++/* ++ * Meta-data about all the additional standard Wireless Extension events ++ * we know about. ++ */ ++static const struct iw_ioctl_description standard_event[] = { ++ /* IWEVTXDROP */ ++ { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, 0}, ++ /* IWEVQUAL */ ++ { IW_HEADER_TYPE_QUAL, 0, 0, 0, 0, 0}, ++}; ++static const int standard_event_num = (sizeof(standard_event) / ++ sizeof(struct iw_ioctl_description)); + + /* Size (in bytes) of the various private data types */ +-char priv_type_size[] = { 0, 1, 1, 0, 4, 4, 0, 0 }; ++static const char priv_type_size[] = { 0, 1, 1, 0, 4, 4, 0, 0 }; ++ ++/* Size (in bytes) of various events */ ++static const int event_type_size[] = { ++ IW_EV_LCP_LEN, ++ 0, ++ IW_EV_CHAR_LEN, ++ 0, ++ IW_EV_UINT_LEN, ++ IW_EV_FREQ_LEN, ++ IW_EV_POINT_LEN, /* Without variable payload */ ++ IW_EV_PARAM_LEN, ++ IW_EV_ADDR_LEN, ++ IW_EV_QUAL_LEN, ++}; + + /************************ COMMON SUBROUTINES ************************/ + /* +@@ -162,7 +211,8 @@ char priv_type_size[] = { 0, 1, 1, 0, 4, + static inline iw_handler get_handler(struct net_device *dev, + unsigned int cmd) + { +- unsigned int index; /* MUST be unsigned */ ++ /* Don't "optimise" the following variable, it will crash */ ++ unsigned int index; /* *MUST* be unsigned */ + + /* Check if we have some wireless handlers defined */ + if(dev->wireless_handlers == NULL) +@@ -269,9 +319,9 @@ static inline int sprintf_wireless_stats + stats->status, + stats->qual.qual, + stats->qual.updated & 1 ? '.' : ' ', +- stats->qual.level, ++ ((__u8) stats->qual.level), + stats->qual.updated & 2 ? '.' : ' ', +- stats->qual.noise, ++ ((__u8) stats->qual.noise), + stats->qual.updated & 4 ? '.' : ' ', + stats->discard.nwid, + stats->discard.code, +@@ -423,12 +473,14 @@ static inline int ioctl_standard_call(st + int ret = -EINVAL; + + /* Get the description of the IOCTL */ ++ if((cmd - SIOCIWFIRST) >= standard_ioctl_num) ++ return -EOPNOTSUPP; + descr = &(standard_ioctl[cmd - SIOCIWFIRST]); + + #ifdef WE_IOCTL_DEBUG +- printk(KERN_DEBUG "%s : Found standard handler for 0x%04X\n", ++ printk(KERN_DEBUG "%s (WE) : Found standard handler for 0x%04X\n", + ifr->ifr_name, cmd); +- printk(KERN_DEBUG "Header type : %d, token type : %d, token_size : %d, max_token : %d\n", descr->header_type, descr->token_type, descr->token_size, descr->max_tokens); ++ printk(KERN_DEBUG "%s (WE) : Header type : %d, Token type : %d, size : %d, token : %d\n", dev->name, descr->header_type, descr->token_type, descr->token_size, descr->max_tokens); + #endif /* WE_IOCTL_DEBUG */ + + /* Prepare the call */ +@@ -437,8 +489,16 @@ static inline int ioctl_standard_call(st + + /* Check if we have a pointer to user space data or not */ + if(descr->header_type != IW_HEADER_TYPE_POINT) { ++ + /* No extra arguments. Trivial to handle */ + ret = handler(dev, &info, &(iwr->u), NULL); ++ ++#ifdef WE_SET_EVENT ++ /* Generate an event to notify listeners of the change */ ++ if((descr->flags & IW_DESCR_FLAG_EVENT) && ++ ((ret == 0) || (ret == -EIWCOMMIT))) ++ wireless_send_event(dev, cmd, &(iwr->u), NULL); ++#endif /* WE_SET_EVENT */ + } else { + char * extra; + int err; +@@ -466,8 +526,8 @@ static inline int ioctl_standard_call(st + } + + #ifdef WE_IOCTL_DEBUG +- printk(KERN_DEBUG "Malloc %d bytes\n", +- descr->max_tokens * descr->token_size); ++ printk(KERN_DEBUG "%s (WE) : Malloc %d bytes\n", ++ dev->name, descr->max_tokens * descr->token_size); + #endif /* WE_IOCTL_DEBUG */ + + /* Always allocate for max space. Easier, and won't last +@@ -488,7 +548,8 @@ static inline int ioctl_standard_call(st + return -EFAULT; + } + #ifdef WE_IOCTL_DEBUG +- printk(KERN_DEBUG "Got %d bytes\n", ++ printk(KERN_DEBUG "%s (WE) : Got %d bytes\n", ++ dev->name, + iwr->u.data.length * descr->token_size); + #endif /* WE_IOCTL_DEBUG */ + } +@@ -504,11 +565,26 @@ static inline int ioctl_standard_call(st + if (err) + ret = -EFAULT; + #ifdef WE_IOCTL_DEBUG +- printk(KERN_DEBUG "Wrote %d bytes\n", ++ printk(KERN_DEBUG "%s (WE) : Wrote %d bytes\n", ++ dev->name, + iwr->u.data.length * descr->token_size); + #endif /* WE_IOCTL_DEBUG */ + } + ++#ifdef WE_SET_EVENT ++ /* Generate an event to notify listeners of the change */ ++ if((descr->flags & IW_DESCR_FLAG_EVENT) && ++ ((ret == 0) || (ret == -EIWCOMMIT))) { ++ if(descr->flags & IW_DESCR_FLAG_RESTRICT) ++ /* If the event is restricted, don't ++ * export the payload */ ++ wireless_send_event(dev, cmd, &(iwr->u), NULL); ++ else ++ wireless_send_event(dev, cmd, &(iwr->u), ++ extra); ++ } ++#endif /* WE_SET_EVENT */ ++ + /* Cleanup - I told you it wasn't that long ;-) */ + kfree(extra); + } +@@ -558,11 +634,12 @@ static inline int ioctl_private_call(str + } + + #ifdef WE_IOCTL_DEBUG +- printk(KERN_DEBUG "%s : Found private handler for 0x%04X\n", ++ printk(KERN_DEBUG "%s (WE) : Found private handler for 0x%04X\n", + ifr->ifr_name, cmd); + if(descr) { +- printk(KERN_DEBUG "Name %s, set %X, get %X\n", +- descr->name, descr->set_args, descr->get_args); ++ printk(KERN_DEBUG "%s (WE) : Name %s, set %X, get %X\n", ++ dev->name, descr->name, ++ descr->set_args, descr->get_args); + } + #endif /* WE_IOCTL_DEBUG */ + +@@ -617,7 +694,8 @@ static inline int ioctl_private_call(str + } + + #ifdef WE_IOCTL_DEBUG +- printk(KERN_DEBUG "Malloc %d bytes\n", extra_size); ++ printk(KERN_DEBUG "%s (WE) : Malloc %d bytes\n", ++ dev->name, extra_size); + #endif /* WE_IOCTL_DEBUG */ + + /* Always allocate for max space. Easier, and won't last +@@ -636,7 +714,8 @@ static inline int ioctl_private_call(str + return -EFAULT; + } + #ifdef WE_IOCTL_DEBUG +- printk(KERN_DEBUG "Got %d elem\n", iwr->u.data.length); ++ printk(KERN_DEBUG "%s (WE) : Got %d elem\n", ++ dev->name, iwr->u.data.length); + #endif /* WE_IOCTL_DEBUG */ + } + +@@ -650,8 +729,8 @@ static inline int ioctl_private_call(str + if (err) + ret = -EFAULT; + #ifdef WE_IOCTL_DEBUG +- printk(KERN_DEBUG "Wrote %d elem\n", +- iwr->u.data.length); ++ printk(KERN_DEBUG "%s (WE) : Wrote %d elem\n", ++ dev->name, iwr->u.data.length); + #endif /* WE_IOCTL_DEBUG */ + } + +@@ -730,4 +809,178 @@ int wireless_process_ioctl(struct ifreq + } + /* Not reached */ + return -EINVAL; ++} ++ ++/************************* EVENT PROCESSING *************************/ ++/* ++ * Process events generated by the wireless layer or the driver. ++ * Most often, the event will be propagated through rtnetlink ++ */ ++ ++#ifdef WE_EVENT_NETLINK ++/* "rtnl" is defined in net/core/rtnetlink.c, but we need it here. ++ * It is declared in */ ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Fill a rtnetlink message with our event data. ++ * Note that we propage only the specified event and don't dump the ++ * current wireless config. Dumping the wireless config is far too ++ * expensive (for each parameter, the driver need to query the hardware). ++ */ ++static inline int rtnetlink_fill_iwinfo(struct sk_buff * skb, ++ struct net_device * dev, ++ int type, ++ char * event, ++ int event_len) ++{ ++ struct ifinfomsg *r; ++ struct nlmsghdr *nlh; ++ unsigned char *b = skb->tail; ++ ++ nlh = NLMSG_PUT(skb, 0, 0, type, sizeof(*r)); ++ r = NLMSG_DATA(nlh); ++ r->ifi_family = AF_UNSPEC; ++ r->ifi_type = dev->type; ++ r->ifi_index = dev->ifindex; ++ r->ifi_flags = dev->flags; ++ r->ifi_change = 0; /* Wireless changes don't affect those flags */ ++ ++ /* Add the wireless events in the netlink packet */ ++ RTA_PUT(skb, IFLA_WIRELESS, ++ event_len, event); ++ ++ nlh->nlmsg_len = skb->tail - b; ++ return skb->len; ++ ++nlmsg_failure: ++rtattr_failure: ++ skb_trim(skb, b - skb->data); ++ return -1; ++} ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Create and broadcast and send it on the standard rtnetlink socket ++ * This is a pure clone rtmsg_ifinfo() in net/core/rtnetlink.c ++ * Andrzej Krzysztofowicz mandated that I used a IFLA_XXX field ++ * within a RTM_NEWLINK event. ++ */ ++static inline void rtmsg_iwinfo(struct net_device * dev, ++ char * event, ++ int event_len) ++{ ++ struct sk_buff *skb; ++ int size = NLMSG_GOODSIZE; ++ ++ skb = alloc_skb(size, GFP_ATOMIC); ++ if (!skb) ++ return; ++ ++ if (rtnetlink_fill_iwinfo(skb, dev, RTM_NEWLINK, ++ event, event_len) < 0) { ++ kfree_skb(skb); ++ return; ++ } ++ NETLINK_CB(skb).dst_groups = RTMGRP_LINK; ++ netlink_broadcast(rtnl, skb, 0, RTMGRP_LINK, GFP_ATOMIC); ++} ++#endif /* WE_EVENT_NETLINK */ ++ ++/* ---------------------------------------------------------------- */ ++/* ++ * Main event dispatcher. Called from other parts and drivers. ++ * Send the event on the apropriate channels. ++ * May be called from interrupt context. ++ */ ++void wireless_send_event(struct net_device * dev, ++ unsigned int cmd, ++ union iwreq_data * wrqu, ++ char * extra) ++{ ++ const struct iw_ioctl_description * descr = NULL; ++ int extra_len = 0; ++ struct iw_event *event; /* Mallocated whole event */ ++ int event_len; /* Its size */ ++ int hdr_len; /* Size of the event header */ ++ /* Don't "optimise" the following variable, it will crash */ ++ unsigned cmd_index; /* *MUST* be unsigned */ ++ ++ /* Get the description of the IOCTL */ ++ if(cmd <= SIOCIWLAST) { ++ cmd_index = cmd - SIOCIWFIRST; ++ if(cmd_index < standard_ioctl_num) ++ descr = &(standard_ioctl[cmd_index]); ++ } else { ++ cmd_index = cmd - IWEVFIRST; ++ if(cmd_index < standard_event_num) ++ descr = &(standard_event[cmd_index]); ++ } ++ /* Don't accept unknown events */ ++ if(descr == NULL) { ++ /* Note : we don't return an error to the driver, because ++ * the driver would not know what to do about it. It can't ++ * return an error to the user, because the event is not ++ * initiated by a user request. ++ * The best the driver could do is to log an error message. ++ * We will do it ourselves instead... ++ */ ++ printk(KERN_ERR "%s (WE) : Invalid Wireless Event (0x%04X)\n", ++ dev->name, cmd); ++ return; ++ } ++#ifdef WE_EVENT_DEBUG ++ printk(KERN_DEBUG "%s (WE) : Got event 0x%04X\n", ++ dev->name, cmd); ++ printk(KERN_DEBUG "%s (WE) : Header type : %d, Token type : %d, size : %d, token : %d\n", dev->name, descr->header_type, descr->token_type, descr->token_size, descr->max_tokens); ++#endif /* WE_EVENT_DEBUG */ ++ ++ /* Check extra parameters and set extra_len */ ++ if(descr->header_type == IW_HEADER_TYPE_POINT) { ++ /* Check if number of token fits within bounds */ ++ if(wrqu->data.length > descr->max_tokens) { ++ printk(KERN_ERR "%s (WE) : Wireless Event too big (%d)\n", dev->name, wrqu->data.length); ++ return; ++ } ++ if(wrqu->data.length < descr->min_tokens) { ++ printk(KERN_ERR "%s (WE) : Wireless Event too small (%d)\n", dev->name, wrqu->data.length); ++ return; ++ } ++ /* Calculate extra_len - extra is NULL for restricted events */ ++ if(extra != NULL) ++ extra_len = wrqu->data.length * descr->token_size; ++#ifdef WE_EVENT_DEBUG ++ printk(KERN_DEBUG "%s (WE) : Event 0x%04X, tokens %d, extra_len %d\n", dev->name, cmd, wrqu->data.length, extra_len); ++#endif /* WE_EVENT_DEBUG */ ++ } ++ ++ /* Total length of the event */ ++ hdr_len = event_type_size[descr->header_type]; ++ event_len = hdr_len + extra_len; ++ ++#ifdef WE_EVENT_DEBUG ++ printk(KERN_DEBUG "%s (WE) : Event 0x%04X, hdr_len %d, event_len %d\n", dev->name, cmd, hdr_len, event_len); ++#endif /* WE_EVENT_DEBUG */ ++ ++ /* Create temporary buffer to hold the event */ ++ event = kmalloc(event_len, GFP_ATOMIC); ++ if(event == NULL) ++ return; ++ ++ /* Fill event */ ++ event->len = event_len; ++ event->cmd = cmd; ++ memcpy(&event->u, wrqu, hdr_len - IW_EV_LCP_LEN); ++ if(extra != NULL) ++ memcpy(((char *) event) + hdr_len, extra, extra_len); ++ ++#ifdef WE_EVENT_NETLINK ++ /* rtnetlink event channel */ ++ rtmsg_iwinfo(dev, (char *) event, event_len); ++#endif /* WE_EVENT_NETLINK */ ++ ++ /* Cleanup */ ++ kfree(event); ++ ++ return; /* Always success, I guess ;-) */ + } diff --git a/recipes/linux/files/linux-2.4-cpufreq.patch b/recipes/linux/files/linux-2.4-cpufreq.patch new file mode 100644 index 0000000000..c3526bb30d --- /dev/null +++ b/recipes/linux/files/linux-2.4-cpufreq.patch @@ -0,0 +1,20 @@ +Index: include/linux/cpufreq.h +=================================================================== +RCS file: /cvs/linux/kernel/include/linux/cpufreq.h,v +retrieving revision 1.4 +diff -u -r1.4 cpufreq.h +--- linux/include/linux/cpufreq.h 23 Aug 2002 22:18:47 -0000 1.4 ++++ linux/include/linux/cpufreq.h 29 Apr 2004 08:44:18 -0000 +@@ -16,9 +16,9 @@ + #include + + #ifndef CONFIG_SMP +-#define cpufreq_current(cpu) ((void)(cpu), __cpufreq_cur) +-#define cpufreq_max(cpu) ((void)(cpu), __cpufreq_max) +-#define cpufreq_min(cpu) ((void)(cpu), __cpufreq_min) ++#define cpufreq_current(cpu) (__cpufreq_cur) ++#define cpufreq_max(cpu) (__cpufreq_max) ++#define cpufreq_min(cpu) (__cpufreq_min) + #else + /* + * Should be something like: diff --git a/recipes/linux/files/linux-2.4-mmc-debugging.patch b/recipes/linux/files/linux-2.4-mmc-debugging.patch new file mode 100644 index 0000000000..6cde9e8ed2 --- /dev/null +++ b/recipes/linux/files/linux-2.4-mmc-debugging.patch @@ -0,0 +1,15 @@ +--- kernel/include/linux/mmc/mmc_protocol.h 2005-04-09 17:30:57.930462521 +0200 ++++ /tmp/mmc_protocol.h 2005-04-09 17:30:33.649097537 +0200 +@@ -273,10 +273,10 @@ + #define START_MMC_DEBUG(n) do { if (n <= g_mmc_debug) + #define END_MMC_DEBUG } while (0) + #else + #define MMC_DEBUG(n, args...) +-#define START_MMC_DEBUG(n) +-#define END_MMC_DEBUG ++#define START_MMC_DEBUG(n) do { if (0) ++#define END_MMC_DEBUG } while (0) + #endif /* CONFIG_MMC_DEBUG */ + + #endif /* MMC_MMC_PROTOCOL_H */ + diff --git a/recipes/linux/files/linux-2.4-no-short-loads.patch b/recipes/linux/files/linux-2.4-no-short-loads.patch new file mode 100644 index 0000000000..f2d6c74224 --- /dev/null +++ b/recipes/linux/files/linux-2.4-no-short-loads.patch @@ -0,0 +1,18 @@ +Index: arch/arm/Makefile +=================================================================== +RCS file: /cvs/linux/kernel/arch/arm/Makefile,v +retrieving revision 1.47 +diff -u -r1.47 Makefile +--- linux/arch/arm/Makefile 9 Jul 2003 14:10:56 -0000 1.47 ++++ linux/arch/arm/Makefile 28 Apr 2004 21:11:04 -0000 +@@ -60,8 +60,8 @@ + tune-$(CONFIG_CPU_XSCALE) :=-mtune=xscale + #tune-$(CONFIG_CPU_XSCALE) :=-mtune=strongarm + +-CFLAGS_BOOT :=$(apcs-y) $(arch-y) $(tune-y) -mshort-load-bytes -msoft-float -Uarm +-CFLAGS +=$(apcs-y) $(arch-y) $(tune-y) -mshort-load-bytes -msoft-float -Uarm ++CFLAGS_BOOT :=$(apcs-y) $(arch-y) $(tune-y) -msoft-float -Uarm ++CFLAGS +=$(apcs-y) $(arch-y) $(tune-y) -msoft-float -Uarm + AFLAGS +=$(apcs-y) $(arch-y) -msoft-float + + ifeq ($(CONFIG_CPU_26),y) diff --git a/recipes/linux/files/linux-2.4-usb-gadget.patch b/recipes/linux/files/linux-2.4-usb-gadget.patch new file mode 100644 index 0000000000..0864ee98f5 --- /dev/null +++ b/recipes/linux/files/linux-2.4-usb-gadget.patch @@ -0,0 +1,29506 @@ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/Documentation/Configure.help kernel/Documentation/Configure.help +--- /tmp/kernel/Documentation/Configure.help 2005-04-22 17:52:12.265476882 +0200 ++++ kernel/Documentation/Configure.help 2005-04-22 17:57:15.940717930 +0200 +@@ -23701,6 +23701,163 @@ + brave people. System crashes and other bad things are likely to occur if + you use this driver. If in doubt, select N. + ++CONFIG_USB_GADGET ++ USB is a master/slave protocol, organized with one master ++ host (such as a PC) controlling up to 127 peripheral devices. ++ The USB hardware is asymmetric, which makes it easier to set up: ++ you can't connect two "to-the-host" connectors to each other. ++ ++ Linux can run in the host, or in the peripheral. In both cases ++ you need a low level bus controller driver, and some software ++ talking to it. Peripheral controllers are often discrete silicon, ++ or are integrated with the CPU in a microcontroller. The more ++ familiar host side controllers have names like like "EHCI", "OHCI", ++ or "UHCI", and are usually integrated into southbridges on PC ++ motherboards. ++ ++ Enable this configuration option if you want to run Linux inside ++ a USB peripheral device. Configure one hardware driver for your ++ peripheral/device side bus controller, and a "gadget driver" for ++ your peripheral protocol. (If you use modular gadget drivers, ++ you may configure more than one.) ++ ++ If in doubt, say "N" and don't enable these drivers; most people ++ don't have this kind of hardware (except maybe inside Linux PDAs). ++ ++CONFIG_USB_GADGET_NET2280 ++ NetChip 2280 is a PCI based USB peripheral controller which ++ supports both full and high speed USB 2.0 data transfers. ++ ++ It has six configurable endpoints, as well as endpoint zero ++ (for control transfers) and several endpoints with dedicated ++ functions. ++ ++ Say "y" to link the driver statically, or "m" to build a ++ dynamically linked module called "net2280" and force all ++ gadget drivers to also be dynamically linked. ++ ++CONFIG_USB_GADGET_GOKU ++ The Toshiba TC86C001 is a PCI device which includes controllers ++ for full speed USB devices, IDE, I2C, SIO, plus a USB host (OHCI). ++ ++ The device controller has three configurable (bulk or interrupt) ++ endpoints, plus endpoint zero (for control transfers). ++ ++ Say "y" to link the driver statically, or "m" to build a ++ dynamically linked module called "goku_udc" and force all ++ gadget drivers to also be dynamically linked. ++ ++CONFIG_USB_GADGET_PXA2XX ++ Intel's PXA 2xx series XScale ARM-5TE processors include ++ an integrated full speed USB 1.1 device controller. ++ ++ It has fifteen fixed-function endpoints, as well as endpoint ++ zero (for control transfers). ++ ++ Say "y" to link the driver statically, or "m" to build a ++ dynamically linked module called "pxa2xx_udc" and force all ++ gadget drivers to also be dynamically linked. ++ ++CONFIG_USB_GADGET_SUPERH ++ Some Renesas SuperH processors (SH7705, SH7727...) include an ++ integrated high speed USB 1.1 device controller. ++ ++ It has three fixed-function endpoints, as well as endpoint zero (for ++ control transfers). ++ ++ Say "y" to link the driver statically, or "m" to build a ++ dynamically linked module called "superh_udc" and force all ++ gadget drivers to also be dynamically linked. ++ ++CONFIG_USB_ZERO ++ Gadget Zero is a two-configuration device. It either sinks and ++ sources bulk data; or it loops back a configurable number of ++ transfers. It also implements control requests, for "chapter 9" ++ conformance. The driver needs only two bulk-capable endpoints, so ++ it can work on top of most device-side usb controllers. It's ++ useful for testing, and is also a working example showing how ++ USB "gadget drivers" can be written. ++ ++ Make this be the first driver you try using on top of any new ++ USB peripheral controller driver. Then you can use host-side ++ test software, like the "usbtest" driver, to put your hardware ++ and its driver through a basic set of functional tests. ++ ++ Gadget Zero also works with the host-side "usb-skeleton" driver, ++ and with many kinds of host-side test software. You may need ++ to tweak product and vendor IDs before host software knows about ++ this device, and arrange to select an appropriate configuration. ++ ++ Say "y" to link the driver statically, or "m" to build a ++ dynamically linked module called "g_zero". ++ ++CONFIG_USB_ETH ++ This driver implements Ethernet style communication, in either ++ of two ways: ++ ++ - The "Communication Device Class" (CDC) Ethernet Control Model. ++ That protocol is often avoided with pure Ethernet adapters, in ++ favor of simpler vendor-specific hardware, but is widely ++ supported by firmware for smart network devices. ++ ++ - On hardware can't implement that protocol, a simpler approach ++ is used, placing fewer demands on USB. ++ ++ Within the USB device, this gadget driver exposes a network device ++ "usbX", where X depends on what other networking devices you have. ++ Treat it like a two-node Ethernet link: host, and gadget. ++ ++ The Linux-USB host-side "usbnet" driver interoperates with this ++ driver, so that deep I/O queues can be supported. On 2.4 kernels, ++ use "CDCEther" instead, if you're using the CDC option. That CDC ++ mode should also interoperate with standard CDC Ethernet class ++ drivers on other host operating systems. ++ ++ Say "y" to link the driver statically, or "m" to build a ++ dynamically linked module called "g_ether". ++ ++CONFIG_USB_ETH_RNDIS ++ Microsoft Windows XP bundles the "Remote NDIS" (RNDIS) protocol, ++ and Microsoft provides redistributable binary RNDIS drivers for ++ older versions of Windows. ++ ++ If you say "y" here, the Ethernet gadget driver will try to provide ++ a second device configuration, supporting RNDIS to talk to such ++ Microsoft USB hosts. ++ ++CONFIG_USB_FILE_STORAGE ++ The File-backed Storage Gadget acts as a USB Mass Storage ++ disk drive. As its storage repository it can use a regular ++ file or a block device (in much the same way as the "loop" ++ device driver), specified as a module parameter. ++ ++CONFIG_USB_FILE_STORAGE_TEST ++ Say "y" to generate the larger testing version of the ++ File-backed Storage Gadget, useful for probing the ++ behavior of USB Mass Storage hosts. Not needed for ++ normal operation. ++ ++CONFIG_USB_ETH_RNDIS ++ Microsoft Windows XP bundles the "Remote NDIS" (RNDIS) protocol, ++ and Microsoft provides redistributable binary RNDIS drivers for ++ older versions of Windows. ++ ++ If you say "y" here, the Ethernet gadget driver will try to provide ++ a second device configuration, supporting RNDIS to talk to such ++ Microsoft USB hosts. ++ ++CONFIG_USB_FILE_STORAGE ++ The File-backed Storage Gadget acts as a USB Mass Storage ++ disk drive. As its storage repository it can use a regular ++ file or a block device (in much the same way as the "loop" ++ device driver), specified as a module parameter. ++ ++CONFIG_USB_FILE_STORAGE_TEST ++ Say "y" to generate the larger testing version of the ++ File-backed Storage Gadget, useful for probing the ++ behavior of USB Mass Storage hosts. Not needed for ++ normal operation. ++ + Winbond W83977AF IrDA Device Driver + CONFIG_WINBOND_FIR + Say Y here if you want to build IrDA support for the Winbond +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/Makefile kernel/Makefile +--- /tmp/kernel/Makefile 2005-04-22 17:52:12.362461090 +0200 ++++ kernel/Makefile 2005-04-22 17:53:19.374549284 +0200 +@@ -196,6 +196,7 @@ + DRIVERS-$(CONFIG_HAMRADIO) += drivers/net/hamradio/hamradio.o + DRIVERS-$(CONFIG_TC) += drivers/tc/tc.a + DRIVERS-$(CONFIG_USB) += drivers/usb/usbdrv.o ++DRIVERS-$(CONFIG_USB_GADGET) += drivers/usb/gadget/built-in.o + DRIVERS-$(CONFIG_LAB) += drivers/bootldr/labmod.o + DRIVERS-$(CONFIG_INPUT) += drivers/input/inputdrv.o + DRIVERS-$(CONFIG_I2O) += drivers/message/i2o/i2o.o +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/Makefile kernel/drivers/Makefile +--- /tmp/kernel/drivers/Makefile 2005-04-22 17:52:12.728401503 +0200 ++++ kernel/drivers/Makefile 2005-04-22 17:53:19.523525026 +0200 +@@ -27,6 +27,7 @@ + subdir-$(CONFIG_MAC) += macintosh + subdir-$(CONFIG_PPC) += macintosh + subdir-$(CONFIG_USB) += usb ++subdir-$(CONFIG_USB_GADGET) += usb/gadget + subdir-$(CONFIG_INPUT) += input + subdir-$(CONFIG_PHONE) += telephony + subdir-$(CONFIG_SGI) += sgi +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/Config.in kernel/drivers/usb/Config.in +--- /tmp/kernel/drivers/usb/Config.in 2005-04-22 17:52:20.663109467 +0200 ++++ kernel/drivers/usb/Config.in 2005-04-22 17:53:19.376548959 +0200 +@@ -120,4 +120,7 @@ + dep_tristate ' USB Auerswald ISDN support (EXPERIMENTAL)' CONFIG_USB_AUERSWALD $CONFIG_USB $CONFIG_EXPERIMENTAL + dep_tristate ' Tieman Voyager USB Braille display support (EXPERIMENTAL)' CONFIG_USB_BRLVGER $CONFIG_USB $CONFIG_EXPERIMENTAL + fi ++ ++source drivers/usb/gadget/Config.in ++ + endmenu +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/Config.in kernel/drivers/usb/gadget/Config.in +--- /tmp/kernel/drivers/usb/gadget/Config.in 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/Config.in 2005-04-22 17:53:19.403544563 +0200 +@@ -0,0 +1,128 @@ ++# ++# USB device-side configuration ++# for 2.4 kbuild, drivers/usb/gadget/Config.in ++# ++# Long term, this likely doesn't all belong in one directory ++# Plan to split it up eventually. ++# ++mainmenu_option next_comment ++comment 'Support for USB gadgets' ++ ++tristate 'Support for USB Gadgets' CONFIG_USB_GADGET ++if [ "$CONFIG_USB_GADGET" = "y" -o "$CONFIG_USB_GADGET" = "m" ]; then ++ ++ # ++ # really want _exactly one_ device controller driver at a time, ++ # since they control compile options for gadget drivers. ++ # ++ choice 'USB Peripheral Controller Driver' "\ ++ Intel-PXA2xx/IXP4xx CONFIG_USB_GADGET_PXA2XX \ ++ National-N9603/N9604 CONFIG_USB_GADGET_N9604 \ ++ NetChip-2280 CONFIG_USB_GADGET_NET2280 \ ++ Renesas-SH7705/7727 CONFIG_USB_GADGET_SUPERH \ ++ Toshiba-TC86C001(Goku-S) CONFIG_USB_GADGET_GOKU \ ++ " NetChip-2280 ++ ++ define_tristate CONFIG_USB_GADGET_CONTROLLER n ++ ++ if [ "$CONFIG_ARCH_PXA" = "y" -o "$CONFIG_ARCH_IXP425" = "y" ] ; then ++ if [ "$CONFIG_USB_GADGET_PXA2XX" = "y" ] ; then ++ define_tristate CONFIG_USB_PXA2XX $CONFIG_USB_GADGET ++ define_tristate CONFIG_USB_GADGET_CONTROLLER $CONFIG_USB_PXA2XX ++ fi ++ fi ++ if [ "$CONFIG_PCI" = "y" -a "$CONFIG_USB_GADGET_NET2280" = "y" ] ; then ++ define_tristate CONFIG_USB_NET2280 $CONFIG_USB_GADGET ++ define_tristate CONFIG_USB_GADGET_CONTROLLER $CONFIG_USB_NET2280 ++ fi ++ if [ "$CONFIG_SUPERH" = "y" -a "$CONFIG_USB_GADGET_SUPERH" = "y" ] ; then ++ define_tristate CONFIG_USB_SUPERH $CONFIG_USB_GADGET ++ define_tristate CONFIG_USB_GADGET_CONTROLLER $CONFIG_USB_SUPERH ++ fi ++ if [ "$CONFIG_PCI" = "y" -a "$CONFIG_USB_GADGET_GOKU" = "y" ] ; then ++ define_tristate CONFIG_USB_GOKU $CONFIG_USB_GADGET ++ define_tristate CONFIG_USB_GADGET_CONTROLLER $CONFIG_USB_GOKU ++ fi ++ if [ "$CONFIG_USB_GADGET_N9604" = "y" ] ; then ++ define_tristate CONFIG_USB_N9604 $CONFIG_USB_GADGET ++ define_tristate CONFIG_USB_GADGET_CONTROLLER $CONFIG_USB_N9604 ++ fi ++ ++ # or any other controller that supports high speed transfers ... ++ define_bool CONFIG_USB_GADGET_DUALSPEED $CONFIG_USB_GADGET_NET2280 ++ ++ if [ "$CONFIG_USB_GADGET_CONTROLLER" = "y" -o "$CONFIG_USB_GADGET_CONTROLLER" = "m" ] ; then ++ ++ # ++ # no reason not to enable more than one gadget driver module, but ++ # for static linking that would make no sense since the usb model ++ # has exactly one of these upstream connections and only one ++ # lowest-level driver can control it. ++ # ++ # gadget drivers are compiled to work on specific hardware, since ++ # ++ # (a) gadget driver need hardware-specific configuration, like what ++ # endpoint names and numbers to use, maxpacket sizes, etc ++ # ++ # (b) specific hardware features like iso endpoints may be required ++ # ++ comment 'USB Gadget Drivers' ++ ++ # FIXME when drivers all use #ifdef CONFIG_USB_GADGET_* tests, ++ # just remove all this driver-specific define_bool logic ++ ++ dep_tristate ' Gadget Zero (DEVELOPMENT)' CONFIG_USB_ZERO $CONFIG_USB_GADGET_CONTROLLER ++ dep_tristate ' Ethernet Gadget (EXPERIMENTAL)' CONFIG_USB_ETH $CONFIG_USB_GADGET_CONTROLLER $CONFIG_NET ++ if [ "$CONFIG_USB_ETH" = "y" -o "$CONFIG_USB_ETH" = "m" ] ; then ++ bool ' RNDIS support (EXPERIMENTAL)' CONFIG_USB_ETH_RNDIS ++ fi ++ dep_tristate ' Gadget Filesystem API (EXPERIMENTAL)' CONFIG_USB_GADGETFS $CONFIG_USB_GADGET_CONTROLLER ++ dep_tristate ' File-backed Storage Gadget (DEVELOPMENT)' CONFIG_USB_FILE_STORAGE $CONFIG_USB_GADGET_CONTROLLER ++ dep_mbool ' File-backed Storage Gadget test mode' CONFIG_USB_FILE_STORAGE_TEST $CONFIG_USB_FILE_STORAGE ++ dep_tristate ' Serial Gadget (EXPERIMENTAL)' CONFIG_USB_G_SERIAL $CONFIG_USB_GADGET_CONTROLLER ++ ++ ++ # enforce the "only one statically linked gadget driver" rule ++ ++ if [ "$CONFIG_USB_ZERO" = "y" ]; then ++ # zero = y ++ define_tristate CONFIG_USB_ETH n ++ define_tristate CONFIG_USB_GADGETFS n ++ define_tristate CONFIG_USB_FILE_STORAGE n ++ define_tristate CONFIG_USB_G_SERIAL n ++ fi ++ ++ if [ "$CONFIG_USB_ETH" = "y" ]; then ++ define_tristate CONFIG_USB_ZERO n ++ # eth = y ++ define_tristate CONFIG_USB_GADGETFS n ++ define_tristate CONFIG_USB_FILE_STORAGE n ++ define_tristate CONFIG_USB_G_SERIAL n ++ fi ++ ++ if [ "$CONFIG_USB_GADGETFS" = "y" ]; then ++ define_tristate CONFIG_USB_ZERO n ++ define_tristate CONFIG_USB_ETH n ++ # gadgetfs = y ++ define_tristate CONFIG_USB_FILE_STORAGE n ++ define_tristate CONFIG_USB_G_SERIAL n ++ fi ++ ++ if [ "$CONFIG_USB_FILE_STORAGE" = "y" ]; then ++ define_tristate CONFIG_USB_ZERO n ++ define_tristate CONFIG_USB_ETH n ++ define_tristate CONFIG_USB_GADGETFS n ++ # file_storage = y ++ define_tristate CONFIG_USB_G_SERIAL n ++ fi ++ ++ if [ "$CONFIG_USB_G_SERIAL" = "y" ]; then ++ define_tristate CONFIG_USB_ZERO n ++ define_tristate CONFIG_USB_ETH n ++ define_tristate CONFIG_USB_GADGETFS n ++ define_tristate CONFIG_USB_FILE_STORAGE n ++ # g_serial = y ++ fi ++ fi ++fi ++endmenu +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/Makefile kernel/drivers/usb/gadget/Makefile +--- /tmp/kernel/drivers/usb/gadget/Makefile 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/Makefile 2005-04-22 17:53:19.405544237 +0200 +@@ -0,0 +1,58 @@ ++# ++# Makefile for USB peripheral controller and gadget drivers ++# for kbuild 2.4 ++# ++ ++# for static linking ++O_TARGET := built-in.o ++ ++list-multi := g_zero.o g_ether.o gadgetfs.o g_file_storage.o g_serial.o ++ ++obj-$(CONFIG_USB_NET2280) += net2280.o ++obj-$(CONFIG_USB_PXA2XX) += pxa2xx_udc.o ++obj-$(CONFIG_USB_GOKU) += goku_udc.o ++obj-$(CONFIG_USB_SUPERH) += superh_udc.o ++obj-$(CONFIG_USB_N9604) += n9604.o ++ ++# only one of these may be statically linked ... ++controller-$(CONFIG_USB_NET2280) += net2280.o ++controller-$(CONFIG_USB_PXA2XX) += pxa2xx_udc.o ++controller-$(CONFIG_USB_GOKU) += goku_udc.o ++controller-$(CONFIG_USB_SUPERH) += superh_udc.o ++controller-$(CONFIG_USB_N9604) += n9604.o ++ ++# ... and only one of these, too; kbuild/kconfig don't help though. ++g_zero-objs := zero.o usbstring.o config.o epautoconf.o ++obj-$(CONFIG_USB_ZERO) += g_zero.o ++ ++g_ether-objs := ether.o usbstring.o config.o epautoconf.o ++obj-$(CONFIG_USB_ETH) += g_ether.o ++ ++ifeq ($(CONFIG_USB_ETH_RNDIS),y) ++ g_ether-objs += rndis.o ++endif ++ ++gadgetfs-objs := inode.o usbstring.o ++obj-$(CONFIG_USB_GADGETFS) += gadgetfs.o ++ ++g_file_storage-objs := file_storage.o usbstring.o config.o \ ++ epautoconf.o ++obj-$(CONFIG_USB_FILE_STORAGE) += g_file_storage.o ++ ++g_serial-objs := gserial.o usbstring.o epautoconf.o ++obj-$(CONFIG_USB_G_SERIAL) += g_serial.o ++ ++export-objs := $(controller-y) $(controller-m) ++ ++include $(TOPDIR)/Rules.make ++ ++g_zero.o: $(g_zero-objs) ++ $(LD) -r -o $@ $(g_zero-objs) ++g_ether.o: $(g_ether-objs) ++ $(LD) -r -o $@ $(g_ether-objs) ++gadgetfs.o: $(gadgetfs-objs) ++ $(LD) -r -o $@ $(gadgetfs-objs) ++g_file_storage.o: $(g_file_storage-objs) ++ $(LD) -r -o $@ $(g_file_storage-objs) ++g_serial.o: $(g_serial-objs) ++ $(LD) -r -o $@ $(g_serial-objs) +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/config.c kernel/drivers/usb/gadget/config.c +--- /tmp/kernel/drivers/usb/gadget/config.c 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/config.c 2005-04-22 17:53:19.408543749 +0200 +@@ -0,0 +1,116 @@ ++/* ++ * usb/gadget/config.c -- simplify building config descriptors ++ * ++ * Copyright (C) 2003 David Brownell ++ * ++ * 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 ++#include ++#include ++#include ++#include ++ ++#include ++ ++ ++/** ++ * usb_descriptor_fillbuf - fill buffer with descriptors ++ * @buf: Buffer to be filled ++ * @buflen: Size of buf ++ * @src: Array of descriptor pointers, terminated by null pointer. ++ * ++ * Copies descriptors into the buffer, returning the length or a ++ * negative error code if they can't all be copied. Useful when ++ * assembling descriptors for an associated set of interfaces used ++ * as part of configuring a composite device; or in other cases where ++ * sets of descriptors need to be marshaled. ++ */ ++int ++usb_descriptor_fillbuf(void *buf, unsigned buflen, ++ const struct usb_descriptor_header **src) ++{ ++ u8 *dest = buf; ++ ++ if (!src) ++ return -EINVAL; ++ ++ /* fill buffer from src[] until null descriptor ptr */ ++ for (; 0 != *src; src++) { ++ unsigned len = (*src)->bLength; ++ ++ if (len > buflen) ++ return -EINVAL; ++ memcpy(dest, *src, len); ++ buflen -= len; ++ dest += len; ++ } ++ return dest - (u8 *)buf; ++} ++ ++ ++/** ++ * usb_gadget_config_buf - builts a complete configuration descriptor ++ * @config: Header for the descriptor, including characteristics such ++ * as power requirements and number of interfaces. ++ * @desc: Null-terminated vector of pointers to the descriptors (interface, ++ * endpoint, etc) defining all functions in this device configuration. ++ * @buf: Buffer for the resulting configuration descriptor. ++ * @length: Length of buffer. If this is not big enough to hold the ++ * entire configuration descriptor, an error code will be returned. ++ * ++ * This copies descriptors into the response buffer, building a descriptor ++ * for that configuration. It returns the buffer length or a negative ++ * status code. The config.wTotalLength field is set to match the length ++ * of the result, but other descriptor fields (including power usage and ++ * interface count) must be set by the caller. ++ * ++ * Gadget drivers could use this when constructing a config descriptor ++ * in response to USB_REQ_GET_DESCRIPTOR. They will need to patch the ++ * resulting bDescriptorType value if USB_DT_OTHER_SPEED_CONFIG is needed. ++ */ ++int usb_gadget_config_buf( ++ const struct usb_config_descriptor *config, ++ void *buf, ++ unsigned length, ++ const struct usb_descriptor_header **desc ++) ++{ ++ struct usb_config_descriptor *cp = buf; ++ int len; ++ ++ /* config descriptor first */ ++ if (length < USB_DT_CONFIG_SIZE || !desc) ++ return -EINVAL; ++ *cp = *config; ++ ++ /* then interface/endpoint/class/vendor/... */ ++ len = usb_descriptor_fillbuf(USB_DT_CONFIG_SIZE + (u8*)buf, ++ length - USB_DT_CONFIG_SIZE, desc); ++ if (len < 0) ++ return len; ++ len += USB_DT_CONFIG_SIZE; ++ if (len > 0xffff) ++ return -EINVAL; ++ ++ /* patch up the config descriptor */ ++ cp->bLength = USB_DT_CONFIG_SIZE; ++ cp->bDescriptorType = USB_DT_CONFIG; ++ cp->wTotalLength = cpu_to_le16(len); ++ cp->bmAttributes |= USB_CONFIG_ATT_ONE; ++ return len; ++} ++ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/epautoconf.c kernel/drivers/usb/gadget/epautoconf.c +--- /tmp/kernel/drivers/usb/gadget/epautoconf.c 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/epautoconf.c 2005-04-22 17:53:19.410543423 +0200 +@@ -0,0 +1,311 @@ ++/* ++ * epautoconf.c -- endpoint autoconfiguration for usb gadget drivers ++ * ++ * Copyright (C) 2004 David Brownell ++ * ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "gadget_chips.h" ++ ++ ++/* we must assign addresses for configurable endpoints (like net2280) */ ++static __initdata unsigned epnum; ++ ++// #define MANY_ENDPOINTS ++#ifdef MANY_ENDPOINTS ++/* more than 15 configurable endpoints */ ++static __initdata unsigned in_epnum; ++#endif ++ ++ ++/* ++ * This should work with endpoints from controller drivers sharing the ++ * same endpoint naming convention. By example: ++ * ++ * - ep1, ep2, ... address is fixed, not direction or type ++ * - ep1in, ep2out, ... address and direction are fixed, not type ++ * - ep1-bulk, ep2-bulk, ... address and type are fixed, not direction ++ * - ep1in-bulk, ep2out-iso, ... all three are fixed ++ * - ep-* ... no functionality restrictions ++ * ++ * Type suffixes are "-bulk", "-iso", or "-int". Numbers are decimal. ++ * Less common restrictions are implied by gadget_is_*(). ++ * ++ * NOTE: each endpoint is unidirectional, as specified by its USB ++ * descriptor; and isn't specific to a configuration or altsetting. ++ */ ++static int __init ++ep_matches ( ++ struct usb_gadget *gadget, ++ struct usb_ep *ep, ++ struct usb_endpoint_descriptor *desc ++) ++{ ++ u8 type; ++ const char *tmp; ++ u16 max; ++ ++ /* endpoint already claimed? */ ++ if (0 != ep->driver_data) ++ return 0; ++ ++ /* only support ep0 for portable CONTROL traffic */ ++ type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; ++ if (USB_ENDPOINT_XFER_CONTROL == type) ++ return 0; ++ ++ /* some other naming convention */ ++ if ('e' != ep->name[0]) ++ return 0; ++ ++ /* type-restriction: "-iso", "-bulk", or "-int". ++ * direction-restriction: "in", "out". ++ */ ++ if ('-' != ep->name[2]) { ++ tmp = strrchr (ep->name, '-'); ++ if (tmp) { ++ switch (type) { ++ case USB_ENDPOINT_XFER_INT: ++ /* bulk endpoints handle interrupt transfers, ++ * except the toggle-quirky iso-synch kind ++ */ ++ if ('s' == tmp[2]) // == "-iso" ++ return 0; ++ /* for now, avoid PXA "interrupt-in"; ++ * it's documented as never using DATA1. ++ */ ++ if (gadget_is_pxa (gadget) ++ && 'i' == tmp [1]) ++ return 0; ++ break; ++ case USB_ENDPOINT_XFER_BULK: ++ if ('b' != tmp[1]) // != "-bulk" ++ return 0; ++ break; ++ case USB_ENDPOINT_XFER_ISOC: ++ if ('s' != tmp[2]) // != "-iso" ++ return 0; ++ } ++ } else { ++ tmp = ep->name + strlen (ep->name); ++ } ++ ++ /* direction-restriction: "..in-..", "out-.." */ ++ tmp--; ++ if (!isdigit (*tmp)) { ++ if (desc->bEndpointAddress & USB_DIR_IN) { ++ if ('n' != *tmp) ++ return 0; ++ } else { ++ if ('t' != *tmp) ++ return 0; ++ } ++ } ++ } ++ ++ /* endpoint maxpacket size is an input parameter, except for bulk ++ * where it's an output parameter representing the full speed limit. ++ * the usb spec fixes high speed bulk maxpacket at 512 bytes. ++ */ ++ max = 0x7ff & le16_to_cpup (&desc->wMaxPacketSize); ++ switch (type) { ++ case USB_ENDPOINT_XFER_INT: ++ /* INT: limit 64 bytes full speed, 1024 high speed */ ++ if (!gadget->is_dualspeed && max > 64) ++ return 0; ++ /* FALLTHROUGH */ ++ ++ case USB_ENDPOINT_XFER_ISOC: ++ /* ISO: limit 1023 bytes full speed, 1024 high speed */ ++ if (ep->maxpacket < max) ++ return 0; ++ if (!gadget->is_dualspeed && max > 1023) ++ return 0; ++ ++ /* BOTH: "high bandwidth" works only at high speed */ ++ if ((desc->wMaxPacketSize & __constant_cpu_to_le16(3<<11))) { ++ if (!gadget->is_dualspeed) ++ return 0; ++ /* configure your hardware with enough buffering!! */ ++ } ++ break; ++ } ++ ++ /* MATCH!! */ ++ ++ /* report address */ ++ if (isdigit (ep->name [2])) { ++ u8 num = simple_strtol (&ep->name [2], NULL, 10); ++ desc->bEndpointAddress |= num; ++#ifdef MANY_ENDPOINTS ++ } else if (desc->bEndpointAddress & USB_DIR_IN) { ++ if (++in_epnum > 15) ++ return 0; ++ desc->bEndpointAddress = USB_DIR_IN | in_epnum; ++#endif ++ } else { ++ if (++epnum > 15) ++ return 0; ++ desc->bEndpointAddress |= epnum; ++ } ++ ++ /* report (variable) full speed bulk maxpacket */ ++ if (USB_ENDPOINT_XFER_BULK == type) { ++ int size = ep->maxpacket; ++ ++ /* min() doesn't work on bitfields with gcc-3.5 */ ++ if (size > 64) ++ size = 64; ++ desc->wMaxPacketSize = cpu_to_le16(size); ++ } ++ return 1; ++} ++ ++static struct usb_ep * __init ++find_ep (struct usb_gadget *gadget, const char *name) ++{ ++ struct usb_ep *ep; ++ ++ list_for_each_entry (ep, &gadget->ep_list, ep_list) { ++ if (0 == strcmp (ep->name, name)) ++ return ep; ++ } ++ return NULL; ++} ++ ++/** ++ * usb_ep_autoconfig - choose an endpoint matching the descriptor ++ * @gadget: The device to which the endpoint must belong. ++ * @desc: Endpoint descriptor, with endpoint direction and transfer mode ++ * initialized. For periodic transfers, the maximum packet ++ * size must also be initialized. This is modified on success. ++ * ++ * By choosing an endpoint to use with the specified descriptor, this ++ * routine simplifies writing gadget drivers that work with multiple ++ * USB device controllers. The endpoint would be passed later to ++ * usb_ep_enable(), along with some descriptor. ++ * ++ * That second descriptor won't always be the same as the first one. ++ * For example, isochronous endpoints can be autoconfigured for high ++ * bandwidth, and then used in several lower bandwidth altsettings. ++ * Also, high and full speed descriptors will be different. ++ * ++ * Be sure to examine and test the results of autoconfiguration on your ++ * hardware. This code may not make the best choices about how to use the ++ * USB controller, and it can't know all the restrictions that may apply. ++ * Some combinations of driver and hardware won't be able to autoconfigure. ++ * ++ * On success, this returns an un-claimed usb_ep, and modifies the endpoint ++ * descriptor bEndpointAddress. For bulk endpoints, the wMaxPacket value ++ * is initialized as if the endpoint were used at full speed. To prevent ++ * the endpoint from being returned by a later autoconfig call, claim it ++ * by assigning ep->driver_data to some non-null value. ++ * ++ * On failure, this returns a null endpoint descriptor. ++ */ ++struct usb_ep * __init usb_ep_autoconfig ( ++ struct usb_gadget *gadget, ++ struct usb_endpoint_descriptor *desc ++) ++{ ++ struct usb_ep *ep; ++ u8 type; ++ ++ type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; ++ ++ /* First, apply chip-specific "best usage" knowledge. ++ * This might make a good usb_gadget_ops hook ... ++ */ ++ if (gadget_is_net2280 (gadget) && type == USB_ENDPOINT_XFER_INT) { ++ /* ep-e, ep-f are PIO with only 64 byte fifos */ ++ ep = find_ep (gadget, "ep-e"); ++ if (ep && ep_matches (gadget, ep, desc)) ++ return ep; ++ ep = find_ep (gadget, "ep-f"); ++ if (ep && ep_matches (gadget, ep, desc)) ++ return ep; ++ ++ } else if (gadget_is_goku (gadget)) { ++ if (USB_ENDPOINT_XFER_INT == type) { ++ /* single buffering is enough */ ++ ep = find_ep (gadget, "ep3-bulk"); ++ if (ep && ep_matches (gadget, ep, desc)) ++ return ep; ++ } else if (USB_ENDPOINT_XFER_BULK == type ++ && (USB_DIR_IN & desc->bEndpointAddress)) { ++ /* DMA may be available */ ++ ep = find_ep (gadget, "ep2-bulk"); ++ if (ep && ep_matches (gadget, ep, desc)) ++ return ep; ++ } ++ ++ } else if (gadget_is_sh (gadget) && USB_ENDPOINT_XFER_INT == type) { ++ /* single buffering is enough; maybe 8 byte fifo is too */ ++ ep = find_ep (gadget, "ep3in-bulk"); ++ if (ep && ep_matches (gadget, ep, desc)) ++ return ep; ++ ++ } else if (gadget_is_mq11xx (gadget) && USB_ENDPOINT_XFER_INT == type) { ++ ep = find_ep (gadget, "ep1-bulk"); ++ if (ep && ep_matches (gadget, ep, desc)) ++ return ep; ++ } ++ ++ /* Second, look at endpoints until an unclaimed one looks usable */ ++ list_for_each_entry (ep, &gadget->ep_list, ep_list) { ++ if (ep_matches (gadget, ep, desc)) ++ return ep; ++ } ++ ++ /* Fail */ ++ return NULL; ++} ++ ++/** ++ * usb_ep_autoconfig_reset - reset endpoint autoconfig state ++ * @gadget: device for which autoconfig state will be reset ++ * ++ * Use this for devices where one configuration may need to assign ++ * endpoint resources very differently from the next one. It clears ++ * state such as ep->driver_data and the record of assigned endpoints ++ * used by usb_ep_autoconfig(). ++ */ ++void __init usb_ep_autoconfig_reset (struct usb_gadget *gadget) ++{ ++ struct usb_ep *ep; ++ ++ list_for_each_entry (ep, &gadget->ep_list, ep_list) { ++ ep->driver_data = NULL; ++ } ++#ifdef MANY_ENDPOINTS ++ in_epnum = 0; ++#endif ++ epnum = 0; ++} ++ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/ether.c kernel/drivers/usb/gadget/ether.c +--- /tmp/kernel/drivers/usb/gadget/ether.c 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/ether.c 2005-04-22 18:01:31.044861540 +0200 +@@ -0,0 +1,2734 @@ ++/* ++ * ether.c -- Ethernet gadget driver, with CDC and non-CDC options ++ * ++ * Copyright (C) 2003-2005 David Brownell ++ * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger ++ * ++ * 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 ++ */ ++ ++ ++// #define DEBUG 1 ++// #define VERBOSE ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include "gadget_chips.h" ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* ++ * Ethernet gadget driver -- with CDC and non-CDC options ++ * Builds on hardware support for a full duplex link. ++ * ++ * CDC Ethernet is the standard USB solution for sending Ethernet frames ++ * using USB. Real hardware tends to use the same framing protocol but look ++ * different for control features. This driver strongly prefers to use ++ * this USB-IF standard as its open-systems interoperability solution; ++ * most host side USB stacks (except from Microsoft) support it. ++ * ++ * There's some hardware that can't talk CDC. We make that hardware ++ * implement a "minimalist" vendor-agnostic CDC core: same framing, but ++ * link-level setup only requires activating the configuration. ++ * Linux supports it, but other host operating systems may not. ++ * (This is a subset of CDC Ethernet.) ++ * ++ * A third option is also in use. Rather than CDC Ethernet, or something ++ * simpler, Microsoft pushes their own approach: RNDIS. The published ++ * RNDIS specs are ambiguous and appear to be incomplete, and are also ++ * needlessly complex. ++ */ ++ ++#define DRIVER_DESC "Ethernet Gadget" ++#define DRIVER_VERSION "Equinox 2004" ++ ++static const char shortname [] = "ether"; ++static const char driver_desc [] = DRIVER_DESC; ++ ++#define RX_EXTRA 20 /* guard against rx overflows */ ++ ++#ifdef CONFIG_USB_ETH_RNDIS ++#include "rndis.h" ++#else ++#define rndis_init() 0 ++#define rndis_exit() do{}while(0) ++#endif ++ ++/* 2.6-compat */ ++#ifndef container_of ++#define container_of list_entry ++#endif ++ ++/** PO: really needed? */ ++#include ++#define work_struct tq_struct ++#define INIT_WORK INIT_TQUEUE ++#define schedule_work schedule_task ++#define flush_scheduled_work flush_scheduled_tasks ++ ++static void random_ether_addr (u8 *addr) ++{ ++ get_random_bytes (addr, ETH_ALEN); ++ addr [0] &= 0xfe; // clear multicast bit ++ addr [0] |= 0x02; // set local assignment bit (IEEE802) ++} ++ ++/* CDC and RNDIS support the same host-chosen outgoing packet filters. */ ++#define DEFAULT_FILTER (USB_CDC_PACKET_TYPE_BROADCAST \ ++ |USB_CDC_PACKET_TYPE_DIRECTED) ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++struct eth_dev { ++ spinlock_t lock; ++ struct usb_gadget *gadget; ++ struct usb_request *req; /* for control responses */ ++ struct usb_request *stat_req; /* for cdc & rndis status */ ++ ++ u8 config; ++ struct usb_ep *in_ep, *out_ep, *status_ep; ++ const struct usb_endpoint_descriptor ++ *in, *out, *status; ++ struct list_head tx_reqs, rx_reqs; ++ ++ struct net_device *net; ++ struct net_device_stats stats; ++ atomic_t tx_qlen; ++ ++ struct work_struct work; ++ unsigned zlp:1; ++ unsigned cdc:1; ++ unsigned rndis:1; ++ unsigned suspended:1; ++ u16 cdc_filter; ++ unsigned long todo; ++#define WORK_RX_MEMORY 0 ++ int rndis_config; ++ u8 host_mac [ETH_ALEN]; ++}; ++ ++/* This version autoconfigures as much as possible at run-time. ++ * ++ * It also ASSUMES a self-powered device, without remote wakeup, ++ * although remote wakeup support would make sense. ++ */ ++static const char *EP_IN_NAME; ++static const char *EP_OUT_NAME; ++static const char *EP_STATUS_NAME; ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! ++ * Instead: allocate your own, using normal USB-IF procedures. ++ */ ++ ++/* Thanks to NetChip Technologies for donating this product ID. ++ * It's for devices with only CDC Ethernet configurations. ++ */ ++#define CDC_VENDOR_NUM 0x0525 /* NetChip */ ++#define CDC_PRODUCT_NUM 0xa4a1 /* Linux-USB Ethernet Gadget */ ++ ++/* For hardware that can't talk CDC, we use the same vendor ID that ++ * ARM Linux has used for ethernet-over-usb, both with sa1100 and ++ * with pxa250. We're protocol-compatible, if the host-side drivers ++ * use the endpoint descriptors. bcdDevice (version) is nonzero, so ++ * drivers that need to hard-wire endpoint numbers have a hook. ++ * ++ * The protocol is a minimal subset of CDC Ether, which works on any bulk ++ * hardware that's not deeply broken ... even on hardware that can't talk ++ * RNDIS (like SA-1100, with no interrupt endpoint, or anything that ++ * doesn't handle control-OUT). ++ */ ++#define SIMPLE_VENDOR_NUM 0x049f ++#define SIMPLE_PRODUCT_NUM 0x505a ++ ++/* For hardware that can talk RNDIS and either of the above protocols, ++ * use this ID ... the windows INF files will know it. Unless it's ++ * used with CDC Ethernet, Linux 2.4 hosts will need updates to choose ++ * the non-RNDIS configuration. ++ */ ++#define RNDIS_VENDOR_NUM 0x0525 /* NetChip */ ++#define RNDIS_PRODUCT_NUM 0xa4a2 /* Ethernet/RNDIS Gadget */ ++ ++ ++/* Some systems will want different product identifers published in the ++ * device descriptor, either numbers or strings or both. These string ++ * parameters are in UTF-8 (superset of ASCII's 7 bit characters). ++ */ ++ ++static ushort __initdata idVendor; ++MODULE_PARM(idVendor, "h"); ++MODULE_PARM_DESC(idVendor, "USB Vendor ID"); ++ ++static ushort __initdata idProduct; ++MODULE_PARM(idProduct, "h"); ++MODULE_PARM_DESC(idProduct, "USB Product ID"); ++ ++static ushort __initdata bcdDevice; ++MODULE_PARM(bcdDevice, "h"); ++MODULE_PARM_DESC(bcdDevice, "USB Device version (BCD)"); ++ ++static char *__initdata iManufacturer; ++MODULE_PARM(iManufacturer, "s"); ++MODULE_PARM_DESC(iManufacturer, "USB Manufacturer string"); ++ ++static char *__initdata iProduct; ++MODULE_PARM(iProduct, "s"); ++MODULE_PARM_DESC(iProduct, "USB Product string"); ++ ++/* initial value, changed by "ifconfig usb0 hw ether xx:xx:xx:xx:xx:xx" */ ++static char *__initdata dev_addr; ++MODULE_PARM(dev_addr, "s"); ++MODULE_PARM_DESC(dev_addr, "Device Ethernet Address"); ++ ++/* this address is invisible to ifconfig */ ++static char *__initdata host_addr; ++MODULE_PARM(host_addr, "s"); ++MODULE_PARM_DESC(host_addr, "Host Ethernet Address"); ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* Include CDC support if we could run on CDC-capable hardware. */ ++ ++#ifdef CONFIG_USB_GADGET_NET2280 ++#define DEV_CONFIG_CDC ++#endif ++ ++#ifdef CONFIG_USB_GADGET_DUMMY_HCD ++#define DEV_CONFIG_CDC ++#endif ++ ++#ifdef CONFIG_USB_GADGET_GOKU ++#define DEV_CONFIG_CDC ++#endif ++ ++#ifdef CONFIG_USB_GADGET_LH7A40X ++#define DEV_CONFIG_CDC ++#endif ++ ++#ifdef CONFIG_USB_GADGET_MQ11XX ++#define DEV_CONFIG_CDC ++#endif ++ ++#ifdef CONFIG_USB_GADGET_OMAP ++#define DEV_CONFIG_CDC ++#endif ++ ++#ifdef CONFIG_USB_GADGET_N9604 ++#define DEV_CONFIG_CDC ++#endif ++ ++#ifdef CONFIG_USB_GADGET_PXA27X ++#define DEV_CONFIG_CDC ++#endif ++ ++#ifdef CONFIG_USB_GADGET_AT91 ++#define DEV_CONFIG_CDC ++#endif ++ ++ ++/* For CDC-incapable hardware, choose the simple cdc subset. ++ * Anything that talks bulk (without notable bugs) can do this. ++ */ ++#ifdef CONFIG_USB_GADGET_PXA2XX ++#define DEV_CONFIG_SUBSET ++#endif ++ ++#ifdef CONFIG_USB_GADGET_SH ++#define DEV_CONFIG_SUBSET ++#endif ++ ++#ifdef CONFIG_USB_GADGET_SA1100 ++/* use non-CDC for backwards compatibility */ ++#define DEV_CONFIG_SUBSET ++#endif ++ ++#ifdef CONFIG_USB_GADGET_S3C2410 ++#define DEV_CONFIG_CDC ++#endif ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* "main" config is either CDC, or its simple subset */ ++static inline int is_cdc(struct eth_dev *dev) ++{ ++#if !defined(DEV_CONFIG_SUBSET) ++ return 1; /* only cdc possible */ ++#elif !defined (DEV_CONFIG_CDC) ++ return 0; /* only subset possible */ ++#else ++ return dev->cdc; /* depends on what hardware we found */ ++#endif ++} ++ ++/* "secondary" RNDIS config may sometimes be activated */ ++static inline int rndis_active(struct eth_dev *dev) ++{ ++#ifdef CONFIG_USB_ETH_RNDIS ++ return dev->rndis; ++#else ++ return 0; ++#endif ++} ++ ++#define subset_active(dev) (!is_cdc(dev) && !rndis_active(dev)) ++#define cdc_active(dev) ( is_cdc(dev) && !rndis_active(dev)) ++ ++ ++ ++#define DEFAULT_QLEN 2 /* double buffering by default */ ++ ++/* peak bulk transfer bits-per-second */ ++#define HS_BPS (13 * 512 * 8 * 1000 * 8) ++#define FS_BPS (19 * 64 * 1 * 1000 * 8) ++ ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ ++static unsigned qmult = 5; ++MODULE_PARM(qmult, "i"); ++ ++ ++/* for dual-speed hardware, use deeper queues at highspeed */ ++#define qlen(gadget) \ ++ (DEFAULT_QLEN*((gadget->speed == USB_SPEED_HIGH) ? qmult : 1)) ++ ++/* also defer IRQs on highspeed TX */ ++#define TX_DELAY qmult ++ ++#define BITRATE(g) (((g)->speed == USB_SPEED_HIGH) ? HS_BPS : FS_BPS) ++ ++#else /* full speed (low speed doesn't do bulk) */ ++#define qlen(gadget) DEFAULT_QLEN ++ ++#define BITRATE(g) FS_BPS ++#endif ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++#define xprintk(d,level,fmt,args...) \ ++ printk(level "%s: " fmt , (d)->net->name , ## args) ++ ++#ifdef DEBUG ++#undef DEBUG ++#define DEBUG(dev,fmt,args...) \ ++ xprintk(dev , KERN_DEBUG , fmt , ## args) ++#else ++#define DEBUG(dev,fmt,args...) \ ++ do { } while (0) ++#endif /* DEBUG */ ++ ++#ifdef VERBOSE ++#define VDEBUG DEBUG ++#else ++#define VDEBUG(dev,fmt,args...) \ ++ do { } while (0) ++#endif /* DEBUG */ ++ ++#define ERROR(dev,fmt,args...) \ ++ xprintk(dev , KERN_ERR , fmt , ## args) ++#define WARN(dev,fmt,args...) \ ++ xprintk(dev , KERN_WARNING , fmt , ## args) ++#define INFO(dev,fmt,args...) \ ++ xprintk(dev , KERN_INFO , fmt , ## args) ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* USB DRIVER HOOKUP (to the hardware driver, below us), mostly ++ * ep0 implementation: descriptors, config management, setup(). ++ * also optional class-specific notification interrupt transfer. ++ */ ++ ++/* ++ * DESCRIPTORS ... most are static, but strings and (full) configuration ++ * descriptors are built on demand. For now we do either full CDC, or ++ * our simple subset, with RNDIS as an optional second configuration. ++ * ++ * RNDIS includes some CDC ACM descriptors ... like CDC Ethernet. But ++ * the class descriptors match a modem (they're ignored; it's really just ++ * Ethernet functionality), they don't need the NOP altsetting, and the ++ * status transfer endpoint isn't optional. ++ */ ++ ++#define STRING_MANUFACTURER 1 ++#define STRING_PRODUCT 2 ++#define STRING_ETHADDR 3 ++#define STRING_DATA 4 ++#define STRING_CONTROL 5 ++#define STRING_RNDIS_CONTROL 6 ++#define STRING_CDC 7 ++#define STRING_SUBSET 8 ++#define STRING_RNDIS 9 ++ ++#define USB_BUFSIZ 256 /* holds our biggest descriptor */ ++ ++/* ++ * This device advertises one configuration, eth_config, unless RNDIS ++ * is enabled (rndis_config) on hardware supporting at least two configs. ++ * ++ * NOTE: Controllers like superh_udc should probably be able to use ++ * an RNDIS-only configuration. ++ * ++ * FIXME define some higher-powered configurations to make it easier ++ * to recharge batteries ... ++ */ ++ ++#define DEV_CONFIG_VALUE 1 /* cdc or subset */ ++#define DEV_RNDIS_CONFIG_VALUE 2 /* rndis; optional */ ++ ++static struct usb_device_descriptor ++device_desc = { ++ .bLength = sizeof device_desc, ++ .bDescriptorType = USB_DT_DEVICE, ++ ++ .bcdUSB = __constant_cpu_to_le16 (0x0200), ++ ++ .bDeviceClass = USB_CLASS_COMM, ++ .bDeviceSubClass = 0, ++ .bDeviceProtocol = 0, ++ ++ .idVendor = __constant_cpu_to_le16 (CDC_VENDOR_NUM), ++ .idProduct = __constant_cpu_to_le16 (CDC_PRODUCT_NUM), ++ .iManufacturer = STRING_MANUFACTURER, ++ .iProduct = STRING_PRODUCT, ++ .bNumConfigurations = 1, ++}; ++ ++static struct usb_otg_descriptor ++otg_descriptor = { ++ .bLength = sizeof otg_descriptor, ++ .bDescriptorType = USB_DT_OTG, ++ ++ .bmAttributes = USB_OTG_SRP, ++}; ++ ++static struct usb_config_descriptor ++eth_config = { ++ .bLength = sizeof eth_config, ++ .bDescriptorType = USB_DT_CONFIG, ++ ++ /* compute wTotalLength on the fly */ ++ .bNumInterfaces = 2, ++ .bConfigurationValue = DEV_CONFIG_VALUE, ++ .iConfiguration = STRING_CDC, ++ .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, ++ .bMaxPower = 50, ++}; ++ ++#ifdef CONFIG_USB_ETH_RNDIS ++static struct usb_config_descriptor ++rndis_config = { ++ .bLength = sizeof rndis_config, ++ .bDescriptorType = USB_DT_CONFIG, ++ ++ /* compute wTotalLength on the fly */ ++ .bNumInterfaces = 2, ++ .bConfigurationValue = DEV_RNDIS_CONFIG_VALUE, ++ .iConfiguration = STRING_RNDIS, ++ .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, ++ .bMaxPower = 50, ++}; ++#endif ++ ++/* ++ * Compared to the simple CDC subset, the full CDC Ethernet model adds ++ * three class descriptors, two interface descriptors, optional status ++ * endpoint. Both have a "data" interface and two bulk endpoints. ++ * There are also differences in how control requests are handled. ++ * ++ * RNDIS shares a lot with CDC-Ethernet, since it's a variant of ++ * the CDC-ACM (modem) spec. ++ */ ++ ++#ifdef DEV_CONFIG_CDC ++static struct usb_interface_descriptor ++control_intf = { ++ .bLength = sizeof control_intf, ++ .bDescriptorType = USB_DT_INTERFACE, ++ ++ .bInterfaceNumber = 0, ++ /* status endpoint is optional; this may be patched later */ ++ .bNumEndpoints = 1, ++ .bInterfaceClass = USB_CLASS_COMM, ++ .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, ++ .bInterfaceProtocol = USB_CDC_PROTO_NONE, ++ .iInterface = STRING_CONTROL, ++}; ++#endif ++ ++#ifdef CONFIG_USB_ETH_RNDIS ++static const struct usb_interface_descriptor ++rndis_control_intf = { ++ .bLength = sizeof rndis_control_intf, ++ .bDescriptorType = USB_DT_INTERFACE, ++ ++ .bInterfaceNumber = 0, ++ .bNumEndpoints = 1, ++ .bInterfaceClass = USB_CLASS_COMM, ++ .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, ++ .bInterfaceProtocol = USB_CDC_ACM_PROTO_VENDOR, ++ .iInterface = STRING_RNDIS_CONTROL, ++}; ++#endif ++ ++#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) ++ ++static const struct usb_cdc_header_desc header_desc = { ++ .bLength = sizeof header_desc, ++ .bDescriptorType = USB_DT_CS_INTERFACE, ++ .bDescriptorSubType = USB_CDC_HEADER_TYPE, ++ ++ .bcdCDC = __constant_cpu_to_le16 (0x0110), ++}; ++ ++static const struct usb_cdc_union_desc union_desc = { ++ .bLength = sizeof union_desc, ++ .bDescriptorType = USB_DT_CS_INTERFACE, ++ .bDescriptorSubType = USB_CDC_UNION_TYPE, ++ ++ .bMasterInterface0 = 0, /* index of control interface */ ++ .bSlaveInterface0 = 1, /* index of DATA interface */ ++}; ++ ++#endif /* CDC || RNDIS */ ++ ++#ifdef CONFIG_USB_ETH_RNDIS ++ ++static const struct usb_cdc_call_mgmt_descriptor call_mgmt_descriptor = { ++ .bLength = sizeof call_mgmt_descriptor, ++ .bDescriptorType = USB_DT_CS_INTERFACE, ++ .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, ++ ++ .bmCapabilities = 0x00, ++ .bDataInterface = 0x01, ++}; ++ ++static struct usb_cdc_acm_descriptor acm_descriptor = { ++ .bLength = sizeof acm_descriptor, ++ .bDescriptorType = USB_DT_CS_INTERFACE, ++ .bDescriptorSubType = USB_CDC_ACM_TYPE, ++ ++ .bmCapabilities = 0x00, ++}; ++ ++#endif ++ ++#ifdef DEV_CONFIG_CDC ++ ++static const struct usb_cdc_ether_desc ether_desc = { ++ .bLength = sizeof ether_desc, ++ .bDescriptorType = USB_DT_CS_INTERFACE, ++ .bDescriptorSubType = USB_CDC_ETHERNET_TYPE, ++ ++ /* this descriptor actually adds value, surprise! */ ++ .iMACAddress = STRING_ETHADDR, ++ .bmEthernetStatistics = __constant_cpu_to_le32 (0), /* no statistics */ ++ .wMaxSegmentSize = __constant_cpu_to_le16 (ETH_FRAME_LEN), ++ .wNumberMCFilters = __constant_cpu_to_le16 (0), ++ .bNumberPowerFilters = 0, ++}; ++ ++#endif ++ ++#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) ++ ++/* include the status endpoint if we can, even where it's optional. ++ * use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one ++ * packet, to simplify cancelation; and a big transfer interval, to ++ * waste less bandwidth. ++ * ++ * some drivers (like Linux 2.4 cdc-ether!) "need" it to exist even ++ * if they ignore the connect/disconnect notifications that real aether ++ * can provide. more advanced cdc configurations might want to support ++ * encapsulated commands (vendor-specific, using control-OUT). ++ * ++ * RNDIS requires the status endpoint, since it uses that encapsulation ++ * mechanism for its funky RPC scheme. ++ */ ++ ++#define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */ ++#define STATUS_BYTECOUNT 16 /* 8 byte header + data */ ++ ++static struct usb_endpoint_descriptor ++fs_status_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ .bEndpointAddress = USB_DIR_IN, ++ .bmAttributes = USB_ENDPOINT_XFER_INT, ++ .wMaxPacketSize = __constant_cpu_to_le16 (STATUS_BYTECOUNT), ++ .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC, ++}; ++#endif ++ ++#ifdef DEV_CONFIG_CDC ++ ++/* the default data interface has no endpoints ... */ ++ ++static const struct usb_interface_descriptor ++data_nop_intf = { ++ .bLength = sizeof data_nop_intf, ++ .bDescriptorType = USB_DT_INTERFACE, ++ ++ .bInterfaceNumber = 1, ++ .bAlternateSetting = 0, ++ .bNumEndpoints = 0, ++ .bInterfaceClass = USB_CLASS_CDC_DATA, ++ .bInterfaceSubClass = 0, ++ .bInterfaceProtocol = 0, ++}; ++ ++/* ... but the "real" data interface has two bulk endpoints */ ++ ++static const struct usb_interface_descriptor ++data_intf = { ++ .bLength = sizeof data_intf, ++ .bDescriptorType = USB_DT_INTERFACE, ++ ++ .bInterfaceNumber = 1, ++ .bAlternateSetting = 1, ++ .bNumEndpoints = 2, ++ .bInterfaceClass = USB_CLASS_CDC_DATA, ++ .bInterfaceSubClass = 0, ++ .bInterfaceProtocol = 0, ++ .iInterface = STRING_DATA, ++}; ++ ++#endif ++ ++#ifdef CONFIG_USB_ETH_RNDIS ++ ++/* RNDIS doesn't activate by changing to the "real" altsetting */ ++ ++static const struct usb_interface_descriptor ++rndis_data_intf = { ++ .bLength = sizeof rndis_data_intf, ++ .bDescriptorType = USB_DT_INTERFACE, ++ ++ .bInterfaceNumber = 1, ++ .bAlternateSetting = 0, ++ .bNumEndpoints = 2, ++ .bInterfaceClass = USB_CLASS_CDC_DATA, ++ .bInterfaceSubClass = 0, ++ .bInterfaceProtocol = 0, ++ .iInterface = STRING_DATA, ++}; ++ ++#endif ++ ++#ifdef DEV_CONFIG_SUBSET ++ ++/* ++ * "Simple" CDC-subset option is a simple vendor-neutral model that most ++ * full speed controllers can handle: one interface, two bulk endpoints. ++ */ ++ ++static const struct usb_interface_descriptor ++subset_data_intf = { ++ .bLength = sizeof subset_data_intf, ++ .bDescriptorType = USB_DT_INTERFACE, ++ ++ .bInterfaceNumber = 0, ++ .bAlternateSetting = 0, ++ .bNumEndpoints = 2, ++ .bInterfaceClass = USB_CLASS_VENDOR_SPEC, ++ .bInterfaceSubClass = 0, ++ .bInterfaceProtocol = 0, ++ .iInterface = STRING_DATA, ++}; ++ ++#endif /* SUBSET */ ++ ++ ++static struct usb_endpoint_descriptor ++fs_source_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ .bEndpointAddress = USB_DIR_IN, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++}; ++ ++static struct usb_endpoint_descriptor ++fs_sink_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ .bEndpointAddress = USB_DIR_OUT, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++}; ++ ++static const struct usb_descriptor_header *fs_eth_function [11] = { ++ (struct usb_descriptor_header *) &otg_descriptor, ++#ifdef DEV_CONFIG_CDC ++ /* "cdc" mode descriptors */ ++ (struct usb_descriptor_header *) &control_intf, ++ (struct usb_descriptor_header *) &header_desc, ++ (struct usb_descriptor_header *) &union_desc, ++ (struct usb_descriptor_header *) ðer_desc, ++ /* NOTE: status endpoint may need to be removed */ ++ (struct usb_descriptor_header *) &fs_status_desc, ++ /* data interface, with altsetting */ ++ (struct usb_descriptor_header *) &data_nop_intf, ++ (struct usb_descriptor_header *) &data_intf, ++ (struct usb_descriptor_header *) &fs_source_desc, ++ (struct usb_descriptor_header *) &fs_sink_desc, ++ NULL, ++#endif /* DEV_CONFIG_CDC */ ++}; ++ ++static inline void __init fs_subset_descriptors(void) ++{ ++#ifdef DEV_CONFIG_SUBSET ++ fs_eth_function[1] = (struct usb_descriptor_header *) &subset_data_intf; ++ fs_eth_function[2] = (struct usb_descriptor_header *) &fs_source_desc; ++ fs_eth_function[3] = (struct usb_descriptor_header *) &fs_sink_desc; ++ fs_eth_function[4] = NULL; ++#else ++ fs_eth_function[1] = NULL; ++#endif ++} ++ ++#ifdef CONFIG_USB_ETH_RNDIS ++static const struct usb_descriptor_header *fs_rndis_function [] = { ++ (struct usb_descriptor_header *) &otg_descriptor, ++ /* control interface matches ACM, not Ethernet */ ++ (struct usb_descriptor_header *) &rndis_control_intf, ++ (struct usb_descriptor_header *) &header_desc, ++ (struct usb_descriptor_header *) &call_mgmt_descriptor, ++ (struct usb_descriptor_header *) &acm_descriptor, ++ (struct usb_descriptor_header *) &union_desc, ++ (struct usb_descriptor_header *) &fs_status_desc, ++ /* data interface has no altsetting */ ++ (struct usb_descriptor_header *) &rndis_data_intf, ++ (struct usb_descriptor_header *) &fs_source_desc, ++ (struct usb_descriptor_header *) &fs_sink_desc, ++ NULL, ++}; ++#endif ++ ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ ++/* ++ * usb 2.0 devices need to expose both high speed and full speed ++ * descriptors, unless they only run at full speed. ++ */ ++ ++#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) ++static struct usb_endpoint_descriptor ++hs_status_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ .bmAttributes = USB_ENDPOINT_XFER_INT, ++ .wMaxPacketSize = __constant_cpu_to_le16 (STATUS_BYTECOUNT), ++ .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, ++}; ++#endif /* DEV_CONFIG_CDC */ ++ ++static struct usb_endpoint_descriptor ++hs_source_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ .wMaxPacketSize = __constant_cpu_to_le16 (512), ++}; ++ ++static struct usb_endpoint_descriptor ++hs_sink_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ .wMaxPacketSize = __constant_cpu_to_le16 (512), ++}; ++ ++static struct usb_qualifier_descriptor ++dev_qualifier = { ++ .bLength = sizeof dev_qualifier, ++ .bDescriptorType = USB_DT_DEVICE_QUALIFIER, ++ ++ .bcdUSB = __constant_cpu_to_le16 (0x0200), ++ .bDeviceClass = USB_CLASS_COMM, ++ ++ .bNumConfigurations = 1, ++}; ++ ++static const struct usb_descriptor_header *hs_eth_function [11] = { ++ (struct usb_descriptor_header *) &otg_descriptor, ++#ifdef DEV_CONFIG_CDC ++ /* "cdc" mode descriptors */ ++ (struct usb_descriptor_header *) &control_intf, ++ (struct usb_descriptor_header *) &header_desc, ++ (struct usb_descriptor_header *) &union_desc, ++ (struct usb_descriptor_header *) ðer_desc, ++ /* NOTE: status endpoint may need to be removed */ ++ (struct usb_descriptor_header *) &hs_status_desc, ++ /* data interface, with altsetting */ ++ (struct usb_descriptor_header *) &data_nop_intf, ++ (struct usb_descriptor_header *) &data_intf, ++ (struct usb_descriptor_header *) &hs_source_desc, ++ (struct usb_descriptor_header *) &hs_sink_desc, ++ NULL, ++#endif /* DEV_CONFIG_CDC */ ++}; ++ ++static inline void __init hs_subset_descriptors(void) ++{ ++#ifdef DEV_CONFIG_SUBSET ++ hs_eth_function[1] = (struct usb_descriptor_header *) &subset_data_intf; ++ hs_eth_function[2] = (struct usb_descriptor_header *) &fs_source_desc; ++ hs_eth_function[3] = (struct usb_descriptor_header *) &fs_sink_desc; ++ hs_eth_function[4] = NULL; ++#else ++ hs_eth_function[1] = NULL; ++#endif ++} ++ ++#ifdef CONFIG_USB_ETH_RNDIS ++static const struct usb_descriptor_header *hs_rndis_function [] = { ++ (struct usb_descriptor_header *) &otg_descriptor, ++ /* control interface matches ACM, not Ethernet */ ++ (struct usb_descriptor_header *) &rndis_control_intf, ++ (struct usb_descriptor_header *) &header_desc, ++ (struct usb_descriptor_header *) &call_mgmt_descriptor, ++ (struct usb_descriptor_header *) &acm_descriptor, ++ (struct usb_descriptor_header *) &union_desc, ++ (struct usb_descriptor_header *) &hs_status_desc, ++ /* data interface has no altsetting */ ++ (struct usb_descriptor_header *) &rndis_data_intf, ++ (struct usb_descriptor_header *) &hs_source_desc, ++ (struct usb_descriptor_header *) &hs_sink_desc, ++ NULL, ++}; ++#endif ++ ++ ++/* maxpacket and other transfer characteristics vary by speed. */ ++#define ep_desc(g,hs,fs) (((g)->speed==USB_SPEED_HIGH)?(hs):(fs)) ++ ++#else ++ ++/* if there's no high speed support, maxpacket doesn't change. */ ++#define ep_desc(g,hs,fs) fs ++ ++static inline void __init hs_subset_descriptors(void) ++{ ++} ++ ++#endif /* !CONFIG_USB_GADGET_DUALSPEED */ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* descriptors that are built on-demand */ ++ ++static char manufacturer [50]; ++static char product_desc [40] = DRIVER_DESC; ++ ++#ifdef DEV_CONFIG_CDC ++/* address that the host will use ... usually assigned at random */ ++static char ethaddr [2 * ETH_ALEN + 1]; ++#endif ++ ++/* static strings, in UTF-8 */ ++static struct usb_string strings [] = { ++ { STRING_MANUFACTURER, manufacturer, }, ++ { STRING_PRODUCT, product_desc, }, ++ { STRING_DATA, "Ethernet Data", }, ++#ifdef DEV_CONFIG_CDC ++ { STRING_CDC, "CDC Ethernet", }, ++ { STRING_ETHADDR, ethaddr, }, ++ { STRING_CONTROL, "CDC Communications Control", }, ++#endif ++#ifdef DEV_CONFIG_SUBSET ++ { STRING_SUBSET, "CDC Ethernet Subset", }, ++#endif ++#ifdef CONFIG_USB_ETH_RNDIS ++ { STRING_RNDIS, "RNDIS", }, ++ { STRING_RNDIS_CONTROL, "RNDIS Communications Control", }, ++#endif ++ { } /* end of list */ ++}; ++ ++static struct usb_gadget_strings stringtab = { ++ .language = 0x0409, /* en-us */ ++ .strings = strings, ++}; ++ ++/* ++ * one config, two interfaces: control, data. ++ * complications: class descriptors, and an altsetting. ++ */ ++static int ++config_buf (enum usb_device_speed speed, ++ u8 *buf, u8 type, ++ unsigned index, int is_otg) ++{ ++ int len; ++ const struct usb_config_descriptor *config; ++ const struct usb_descriptor_header **function; ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ int hs = (speed == USB_SPEED_HIGH); ++ ++ if (type == USB_DT_OTHER_SPEED_CONFIG) ++ hs = !hs; ++#define which_fn(t) (hs ? hs_ ## t ## _function : fs_ ## t ## _function) ++#else ++#define which_fn(t) (fs_ ## t ## _function) ++#endif ++ ++ if (index >= device_desc.bNumConfigurations) ++ return -EINVAL; ++ ++#ifdef CONFIG_USB_ETH_RNDIS ++ /* list the RNDIS config first, to make Microsoft's drivers ++ * happy. DOCSIS 1.0 needs this too. ++ */ ++ if (device_desc.bNumConfigurations == 2 && index == 0) { ++ config = &rndis_config; ++ function = which_fn (rndis); ++ } else ++#endif ++ { ++ config = ð_config; ++ function = which_fn (eth); ++ } ++ ++ /* for now, don't advertise srp-only devices */ ++ if (!is_otg) ++ function++; ++ ++ len = usb_gadget_config_buf (config, buf, USB_BUFSIZ, function); ++ if (len < 0) ++ return len; ++ ((struct usb_config_descriptor *) buf)->bDescriptorType = type; ++ return len; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void eth_start (struct eth_dev *dev, int gfp_flags); ++static int alloc_requests (struct eth_dev *dev, unsigned n, int gfp_flags); ++ ++#ifdef DEV_CONFIG_CDC ++static inline int ether_alt_ep_setup (struct eth_dev *dev, struct usb_ep *ep) ++{ ++ const struct usb_endpoint_descriptor *d; ++ ++ /* With CDC, the host isn't allowed to use these two data ++ * endpoints in the default altsetting for the interface. ++ * so we don't activate them yet. Reset from SET_INTERFACE. ++ * ++ * Strictly speaking RNDIS should work the same: activation is ++ * a side effect of setting a packet filter. Deactivation is ++ * from REMOTE_NDIS_HALT_MSG, reset from REMOTE_NDIS_RESET_MSG. ++ */ ++ ++ /* one endpoint writes data back IN to the host */ ++ if (strcmp (ep->name, EP_IN_NAME) == 0) { ++ d = ep_desc (dev->gadget, &hs_source_desc, &fs_source_desc); ++ ep->driver_data = dev; ++ dev->in = d; ++ ++ /* one endpoint just reads OUT packets */ ++ } else if (strcmp (ep->name, EP_OUT_NAME) == 0) { ++ d = ep_desc (dev->gadget, &hs_sink_desc, &fs_sink_desc); ++ ep->driver_data = dev; ++ dev->out = d; ++ ++ /* optional status/notification endpoint */ ++ } else if (EP_STATUS_NAME && ++ strcmp (ep->name, EP_STATUS_NAME) == 0) { ++ int result; ++ ++ d = ep_desc (dev->gadget, &hs_status_desc, &fs_status_desc); ++ result = usb_ep_enable (ep, d); ++ if (result < 0) ++ return result; ++ ++ ep->driver_data = dev; ++ dev->status = d; ++ } ++ return 0; ++} ++#endif ++ ++#if defined(DEV_CONFIG_SUBSET) || defined(CONFIG_USB_ETH_RNDIS) ++static inline int ether_ep_setup (struct eth_dev *dev, struct usb_ep *ep) ++{ ++ int result; ++ const struct usb_endpoint_descriptor *d; ++ ++ /* CDC subset is simpler: if the device is there, ++ * it's live with rx and tx endpoints. ++ * ++ * Do this as a shortcut for RNDIS too. ++ */ ++ ++ /* one endpoint writes data back IN to the host */ ++ if (strcmp (ep->name, EP_IN_NAME) == 0) { ++ d = ep_desc (dev->gadget, &hs_source_desc, &fs_source_desc); ++ result = usb_ep_enable (ep, d); ++ if (result < 0) ++ return result; ++ ++ ep->driver_data = dev; ++ dev->in = d; ++ ++ /* one endpoint just reads OUT packets */ ++ } else if (strcmp (ep->name, EP_OUT_NAME) == 0) { ++ d = ep_desc (dev->gadget, &hs_sink_desc, &fs_sink_desc); ++ result = usb_ep_enable (ep, d); ++ if (result < 0) ++ return result; ++ ++ ep->driver_data = dev; ++ dev->out = d; ++ } ++ ++ return 0; ++} ++#endif ++ ++static int ++set_ether_config (struct eth_dev *dev, int gfp_flags) ++{ ++ int result = 0; ++ struct usb_ep *ep; ++ struct usb_gadget *gadget = dev->gadget; ++ ++ gadget_for_each_ep (ep, gadget) { ++#ifdef DEV_CONFIG_CDC ++ if (!dev->rndis && dev->cdc) { ++ result = ether_alt_ep_setup (dev, ep); ++ if (result == 0) ++ continue; ++ } ++#endif ++ ++#ifdef CONFIG_USB_ETH_RNDIS ++ if (dev->rndis && strcmp (ep->name, EP_STATUS_NAME) == 0) { ++ const struct usb_endpoint_descriptor *d; ++ d = ep_desc (gadget, &hs_status_desc, &fs_status_desc); ++ result = usb_ep_enable (ep, d); ++ if (result == 0) { ++ ep->driver_data = dev; ++ dev->status = d; ++ continue; ++ } ++ } else ++#endif ++ ++ { ++#if defined(DEV_CONFIG_SUBSET) || defined(CONFIG_USB_ETH_RNDIS) ++ result = ether_ep_setup (dev, ep); ++ if (result == 0) ++ continue; ++#endif ++ } ++ ++ /* stop on error */ ++ ERROR (dev, "can't enable %s, result %d\n", ep->name, result); ++ break; ++ } ++ if (!result && (!dev->in_ep || !dev->out_ep)) ++ result = -ENODEV; ++ ++ if (result == 0) ++ result = alloc_requests (dev, qlen (gadget), gfp_flags); ++ ++ /* on error, disable any endpoints */ ++ if (result < 0) { ++#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) ++ if (dev->status) ++ (void) usb_ep_disable (dev->status_ep); ++#endif ++ dev->status = NULL; ++#if defined(DEV_CONFIG_SUBSET) || defined(CONFIG_USB_ETH_RNDIS) ++ if (dev->rndis || !dev->cdc) { ++ if (dev->in) ++ (void) usb_ep_disable (dev->in_ep); ++ if (dev->out) ++ (void) usb_ep_disable (dev->out_ep); ++ } ++#endif ++ dev->in = NULL; ++ dev->out = NULL; ++ } else ++ ++ /* activate non-CDC configs right away ++ * this isn't strictly according to the RNDIS spec ++ */ ++#if defined(DEV_CONFIG_SUBSET) || defined(CONFIG_USB_ETH_RNDIS) ++ if (dev->rndis || !dev->cdc) { ++ netif_carrier_on (dev->net); ++ if (netif_running (dev->net)) { ++ spin_unlock (&dev->lock); ++ eth_start (dev, GFP_ATOMIC); ++ spin_lock (&dev->lock); ++ } ++ } ++#endif ++ ++ if (result == 0) ++ DEBUG (dev, "qlen %d\n", qlen (gadget)); ++ ++ /* caller is responsible for cleanup on error */ ++ return result; ++} ++ ++static void eth_reset_config (struct eth_dev *dev) ++{ ++ struct usb_request *req; ++ ++ if (dev->config == 0) ++ return; ++ ++ DEBUG (dev, "%s\n", __FUNCTION__); ++ ++ netif_stop_queue (dev->net); ++ netif_carrier_off (dev->net); ++ ++ /* disable endpoints, forcing (synchronous) completion of ++ * pending i/o. then free the requests. ++ */ ++ if (dev->in) { ++ usb_ep_disable (dev->in_ep); ++ while (likely (!list_empty (&dev->tx_reqs))) { ++ req = container_of (dev->tx_reqs.next, ++ struct usb_request, list); ++ list_del (&req->list); ++ usb_ep_free_request (dev->in_ep, req); ++ } ++ } ++ if (dev->out) { ++ usb_ep_disable (dev->out_ep); ++ while (likely (!list_empty (&dev->rx_reqs))) { ++ req = container_of (dev->rx_reqs.next, ++ struct usb_request, list); ++ list_del (&req->list); ++ usb_ep_free_request (dev->out_ep, req); ++ } ++ } ++ ++ if (dev->status) { ++ usb_ep_disable (dev->status_ep); ++ } ++ dev->config = 0; ++} ++ ++/* change our operational config. must agree with the code ++ * that returns config descriptors, and altsetting code. ++ */ ++static int ++eth_set_config (struct eth_dev *dev, unsigned number, int gfp_flags) ++{ ++ int result = 0; ++ struct usb_gadget *gadget = dev->gadget; ++ ++ if (number == dev->config) ++ return 0; ++ ++ if (gadget_is_sa1100 (gadget) ++ && dev->config ++ && atomic_read (&dev->tx_qlen) != 0) { ++ /* tx fifo is full, but we can't clear it...*/ ++ INFO (dev, "can't change configurations\n"); ++ return -ESPIPE; ++ } ++ eth_reset_config (dev); ++ ++ /* default: pass all packets, no multicast filtering */ ++ dev->cdc_filter = 0x000f; ++ ++ switch (number) { ++ case DEV_CONFIG_VALUE: ++ dev->rndis = 0; ++ result = set_ether_config (dev, gfp_flags); ++ break; ++#ifdef CONFIG_USB_ETH_RNDIS ++ case DEV_RNDIS_CONFIG_VALUE: ++ dev->rndis = 1; ++ result = set_ether_config (dev, gfp_flags); ++ break; ++#endif ++ default: ++ result = -EINVAL; ++ /* FALL THROUGH */ ++ case 0: ++ break; ++ } ++ ++ if (result) { ++ if (number) ++ eth_reset_config (dev); ++ usb_gadget_vbus_draw(dev->gadget, ++ dev->gadget->is_otg ? 8 : 100); ++ } else { ++ char *speed; ++ unsigned power; ++ ++ power = 2 * eth_config.bMaxPower; ++ usb_gadget_vbus_draw(dev->gadget, power); ++ ++ switch (gadget->speed) { ++ case USB_SPEED_FULL: speed = "full"; break; ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ case USB_SPEED_HIGH: speed = "high"; break; ++#endif ++ default: speed = "?"; break; ++ } ++ ++ dev->config = number; ++ INFO (dev, "%s speed config #%d: %d mA, %s, using %s\n", ++ speed, number, power, driver_desc, ++ dev->rndis ++ ? "RNDIS" ++ : (dev->cdc ++ ? "CDC Ethernet" ++ : "CDC Ethernet Subset")); ++ } ++ return result; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++#ifdef DEV_CONFIG_CDC ++ ++static void eth_status_complete (struct usb_ep *ep, struct usb_request *req) ++{ ++ struct usb_cdc_notification *event = req->buf; ++ int value = req->status; ++ struct eth_dev *dev = ep->driver_data; ++ ++ /* issue the second notification if host reads the first */ ++ if (event->bNotificationType == USB_CDC_NOTIFY_NETWORK_CONNECTION ++ && value == 0) { ++ __le32 *data = req->buf + sizeof *event; ++ ++ event->bmRequestType = 0xA1; ++ event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE; ++ event->wValue = __constant_cpu_to_le16 (0); ++ event->wIndex = __constant_cpu_to_le16 (1); ++ event->wLength = __constant_cpu_to_le16 (8); ++ ++ /* SPEED_CHANGE data is up/down speeds in bits/sec */ ++ data [0] = data [1] = cpu_to_le32 (BITRATE (dev->gadget)); ++ ++ req->length = STATUS_BYTECOUNT; ++ value = usb_ep_queue (ep, req, GFP_ATOMIC); ++ DEBUG (dev, "send SPEED_CHANGE --> %d\n", value); ++ if (value == 0) ++ return; ++ } else if (value != -ECONNRESET) ++ DEBUG (dev, "event %02x --> %d\n", ++ event->bNotificationType, value); ++ event->bmRequestType = 0xff; ++} ++ ++static void issue_start_status (struct eth_dev *dev) ++{ ++ struct usb_request *req = dev->stat_req; ++ struct usb_cdc_notification *event; ++ int value; ++ ++ DEBUG (dev, "%s, flush old status first\n", __FUNCTION__); ++ ++ /* flush old status ++ * ++ * FIXME ugly idiom, maybe we'd be better with just ++ * a "cancel the whole queue" primitive since any ++ * unlink-one primitive has way too many error modes. ++ * here, we "know" toggle is already clear... ++ */ ++ usb_ep_disable (dev->status_ep); ++ usb_ep_enable (dev->status_ep, dev->status); ++ ++ /* 3.8.1 says to issue first NETWORK_CONNECTION, then ++ * a SPEED_CHANGE. could be useful in some configs. ++ */ ++ event = req->buf; ++ event->bmRequestType = 0xA1; ++ event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION; ++ event->wValue = __constant_cpu_to_le16 (1); /* connected */ ++ event->wIndex = __constant_cpu_to_le16 (1); ++ event->wLength = 0; ++ ++ req->length = sizeof *event; ++ req->complete = eth_status_complete; ++ value = usb_ep_queue (dev->status_ep, req, GFP_ATOMIC); ++ if (value < 0) ++ DEBUG (dev, "status buf queue --> %d\n", value); ++} ++ ++#endif ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void eth_setup_complete (struct usb_ep *ep, struct usb_request *req) ++{ ++ if (req->status || req->actual != req->length) ++ DEBUG ((struct eth_dev *) ep->driver_data, ++ "setup complete --> %d, %d/%d\n", ++ req->status, req->actual, req->length); ++} ++ ++#ifdef CONFIG_USB_ETH_RNDIS ++ ++static void rndis_response_complete (struct usb_ep *ep, struct usb_request *req) ++{ ++ if (req->status || req->actual != req->length) ++ DEBUG ((struct eth_dev *) ep->driver_data, ++ "rndis response complete --> %d, %d/%d\n", ++ req->status, req->actual, req->length); ++ ++ /* done sending after USB_CDC_GET_ENCAPSULATED_RESPONSE */ ++} ++ ++static void rndis_command_complete (struct usb_ep *ep, struct usb_request *req) ++{ ++ struct eth_dev *dev = ep->driver_data; ++ int status; ++ ++ /* received RNDIS command from USB_CDC_SEND_ENCAPSULATED_COMMAND */ ++ spin_lock(&dev->lock); ++ status = rndis_msg_parser (dev->rndis_config, (u8 *) req->buf); ++ if (status < 0) ++ ERROR(dev, "%s: rndis parse error %d\n", __FUNCTION__, status); ++ spin_unlock(&dev->lock); ++} ++ ++#endif /* RNDIS */ ++ ++/* ++ * The setup() callback implements all the ep0 functionality that's not ++ * handled lower down. CDC has a number of less-common features: ++ * ++ * - two interfaces: control, and ethernet data ++ * - Ethernet data interface has two altsettings: default, and active ++ * - class-specific descriptors for the control interface ++ * - class-specific control requests ++ */ ++static int ++eth_setup (struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) ++{ ++ struct eth_dev *dev = get_gadget_data (gadget); ++ struct usb_request *req = dev->req; ++ int value = -EOPNOTSUPP; ++ u16 wIndex = ctrl->wIndex; ++ u16 wValue = ctrl->wValue; ++ u16 wLength = ctrl->wLength; ++ ++ /* descriptors just go into the pre-allocated ep0 buffer, ++ * while config change events may enable network traffic. ++ */ ++ req->complete = eth_setup_complete; ++ switch (ctrl->bRequest) { ++ ++ case USB_REQ_GET_DESCRIPTOR: ++ if (ctrl->bRequestType != USB_DIR_IN) ++ break; ++ switch (wValue >> 8) { ++ ++ case USB_DT_DEVICE: ++ value = min (wLength, (u16) sizeof device_desc); ++ memcpy (req->buf, &device_desc, value); ++ break; ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ case USB_DT_DEVICE_QUALIFIER: ++ if (!gadget->is_dualspeed) ++ break; ++ value = min (wLength, (u16) sizeof dev_qualifier); ++ memcpy (req->buf, &dev_qualifier, value); ++ break; ++ ++ case USB_DT_OTHER_SPEED_CONFIG: ++ if (!gadget->is_dualspeed) ++ break; ++ // FALLTHROUGH ++#endif /* CONFIG_USB_GADGET_DUALSPEED */ ++ case USB_DT_CONFIG: ++ value = config_buf (gadget->speed, req->buf, ++ wValue >> 8, ++ wValue & 0xff, ++ gadget->is_otg); ++ if (value >= 0) ++ value = min (wLength, (u16) value); ++ break; ++ ++ case USB_DT_STRING: ++ value = usb_gadget_get_string (&stringtab, ++ wValue & 0xff, req->buf); ++ if (value >= 0) ++ value = min (wLength, (u16) value); ++ break; ++ } ++ break; ++ ++ case USB_REQ_SET_CONFIGURATION: ++ if (ctrl->bRequestType != 0) ++ break; ++ if (gadget->a_hnp_support) ++ DEBUG (dev, "HNP available\n"); ++ else if (gadget->a_alt_hnp_support) ++ DEBUG (dev, "HNP needs a different root port\n"); ++ spin_lock (&dev->lock); ++ value = eth_set_config (dev, wValue, GFP_ATOMIC); ++ spin_unlock (&dev->lock); ++ break; ++ case USB_REQ_GET_CONFIGURATION: ++ if (ctrl->bRequestType != USB_DIR_IN) ++ break; ++ *(u8 *)req->buf = dev->config; ++ value = min (wLength, (u16) 1); ++ break; ++ ++ case USB_REQ_SET_INTERFACE: ++ if (ctrl->bRequestType != USB_RECIP_INTERFACE ++ || !dev->config ++ || wIndex > 1) ++ break; ++ if (!dev->cdc && wIndex != 0) ++ break; ++ spin_lock (&dev->lock); ++ ++ /* PXA hardware partially handles SET_INTERFACE; ++ * we need to kluge around that interference. ++ */ ++ if (gadget_is_pxa (gadget)) { ++ value = eth_set_config (dev, DEV_CONFIG_VALUE, ++ GFP_ATOMIC); ++ goto done_set_intf; ++ } ++ ++#ifdef DEV_CONFIG_CDC ++ switch (wIndex) { ++ case 0: /* control/master intf */ ++ if (wValue != 0) ++ break; ++ if (dev->status) { ++ usb_ep_disable (dev->status_ep); ++ usb_ep_enable (dev->status_ep, dev->status); ++ } ++ value = 0; ++ break; ++ case 1: /* data intf */ ++ if (wValue > 1) ++ break; ++ usb_ep_disable (dev->in_ep); ++ usb_ep_disable (dev->out_ep); ++ ++ /* CDC requires the data transfers not be done from ++ * the default interface setting ... also, setting ++ * the non-default interface clears filters etc. ++ */ ++ if (wValue == 1) { ++ usb_ep_enable (dev->in_ep, dev->in); ++ usb_ep_enable (dev->out_ep, dev->out); ++ dev->cdc_filter = DEFAULT_FILTER; ++ netif_carrier_on (dev->net); ++ if (dev->status) ++ issue_start_status (dev); ++ if (netif_running (dev->net)) { ++ spin_unlock (&dev->lock); ++ eth_start (dev, GFP_ATOMIC); ++ spin_lock (&dev->lock); ++ } ++ } else { ++ netif_stop_queue (dev->net); ++ netif_carrier_off (dev->net); ++ } ++ value = 0; ++ break; ++ } ++#else ++ /* FIXME this is wrong, as is the assumption that ++ * all non-PXA hardware talks real CDC ... ++ */ ++ WARN(dev, "set_interface ignored!\n"); ++#endif /* DEV_CONFIG_CDC */ ++ ++done_set_intf: ++ spin_unlock (&dev->lock); ++ break; ++ case USB_REQ_GET_INTERFACE: ++ if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE) ++ || !dev->config ++ || wIndex > 1) ++ break; ++ if (!(dev->cdc || dev->rndis) && wIndex != 0) ++ break; ++ ++ /* for CDC, iff carrier is on, data interface is active. */ ++ if (dev->rndis || wIndex != 1) ++ *(u8 *)req->buf = 0; ++ else ++ *(u8 *)req->buf = netif_carrier_ok (dev->net) ? 1 : 0; ++ value = min (wLength, (u16) 1); ++ break; ++ ++#ifdef DEV_CONFIG_CDC ++ case USB_CDC_SET_ETHERNET_PACKET_FILTER: ++ /* see 6.2.30: no data, wIndex = interface, ++ * wValue = packet filter bitmap ++ */ ++ if (ctrl->bRequestType != (USB_TYPE_CLASS|USB_RECIP_INTERFACE) ++ || !dev->cdc ++ || dev->rndis ++ || wLength != 0 ++ || wIndex > 1) ++ break; ++ DEBUG (dev, "packet filter %02x\n", wValue); ++ dev->cdc_filter = wValue; ++ value = 0; ++ break; ++ ++ /* and potentially: ++ * case USB_CDC_SET_ETHERNET_MULTICAST_FILTERS: ++ * case USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER: ++ * case USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER: ++ * case USB_CDC_GET_ETHERNET_STATISTIC: ++ */ ++ ++#endif /* DEV_CONFIG_CDC */ ++ ++#ifdef CONFIG_USB_ETH_RNDIS ++ /* RNDIS uses the CDC command encapsulation mechanism to implement ++ * an RPC scheme, with much getting/setting of attributes by OID. ++ */ ++ case USB_CDC_SEND_ENCAPSULATED_COMMAND: ++ if (ctrl->bRequestType != (USB_TYPE_CLASS|USB_RECIP_INTERFACE) ++ || !dev->rndis ++ || wLength > USB_BUFSIZ ++ || wValue ++ || rndis_control_intf.bInterfaceNumber ++ != wIndex) ++ break; ++ /* read the request, then process it */ ++ value = wLength; ++ req->complete = rndis_command_complete; ++ /* later, rndis_control_ack () sends a notification */ ++ break; ++ ++ case USB_CDC_GET_ENCAPSULATED_RESPONSE: ++ if ((USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE) ++ == ctrl->bRequestType ++ && dev->rndis ++ // && wLength >= 0x0400 ++ && !wValue ++ && rndis_control_intf.bInterfaceNumber ++ == wIndex) { ++ u8 *buf; ++ ++ /* return the result */ ++ buf = rndis_get_next_response (dev->rndis_config, ++ &value); ++ if (buf) { ++ memcpy (req->buf, buf, value); ++ req->complete = rndis_response_complete; ++ rndis_free_response(dev->rndis_config, buf); ++ } ++ /* else stalls ... spec says to avoid that */ ++ } ++ break; ++#endif /* RNDIS */ ++ ++ default: ++ VDEBUG (dev, ++ "unknown control req%02x.%02x v%04x i%04x l%d\n", ++ ctrl->bRequestType, ctrl->bRequest, ++ wValue, wIndex, wLength); ++ } ++ ++ /* respond with data transfer before status phase? */ ++ if (value >= 0) { ++ req->length = value; ++ req->zero = value < wLength ++ && (value % gadget->ep0->maxpacket) == 0; ++ value = usb_ep_queue (gadget->ep0, req, GFP_ATOMIC); ++ if (value < 0) { ++ DEBUG (dev, "ep_queue --> %d\n", value); ++ req->status = 0; ++ eth_setup_complete (gadget->ep0, req); ++ } ++ } ++ ++ /* host either stalls (value < 0) or reports success */ ++ return value; ++} ++ ++static void ++eth_disconnect (struct usb_gadget *gadget) ++{ ++ struct eth_dev *dev = get_gadget_data (gadget); ++ unsigned long flags; ++ ++ spin_lock_irqsave (&dev->lock, flags); ++ netif_stop_queue (dev->net); ++ netif_carrier_off (dev->net); ++ eth_reset_config (dev); ++ spin_unlock_irqrestore (&dev->lock, flags); ++ ++ /* FIXME RNDIS should enter RNDIS_UNINITIALIZED */ ++ ++ /* next we may get setup() calls to enumerate new connections; ++ * or an unbind() during shutdown (including removing module). ++ */ ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* NETWORK DRIVER HOOKUP (to the layer above this driver) */ ++ ++/* glue code: in more recent 2.4 kernels these functions are contained in netdev.h */ ++ ++#ifndef HAVE_NETDEV_PRIV ++static inline void *netdev_priv(struct net_device *net) ++{ ++ return net->priv; ++} ++#endif ++ ++#ifndef HAVE_FREE_NETDEV ++static inline void free_netdev(struct net_device *dev) ++{ ++ kfree(dev); ++} ++#endif ++ ++static int eth_change_mtu (struct net_device *net, int new_mtu) ++{ ++ struct eth_dev *dev = netdev_priv(net); ++ ++ // FIXME if rndis, don't change while link's live ++ ++ if (new_mtu <= ETH_HLEN || new_mtu > ETH_FRAME_LEN) ++ return -ERANGE; ++ /* no zero-length packet read wanted after mtu-sized packets */ ++ if (((new_mtu + sizeof (struct ethhdr)) % dev->in_ep->maxpacket) == 0) ++ return -EDOM; ++ net->mtu = new_mtu; ++ return 0; ++} ++ ++static struct net_device_stats *eth_get_stats (struct net_device *net) ++{ ++ return &((struct eth_dev *)netdev_priv(net))->stats; ++} ++ ++static int eth_ethtool_ioctl (struct net_device *net, void *useraddr) ++{ ++ struct eth_dev *dev = (struct eth_dev *) net->priv; ++ u32 cmd; ++ ++ if (get_user (cmd, (u32 *)useraddr)) ++ return -EFAULT; ++ switch (cmd) { ++ ++ case ETHTOOL_GDRVINFO: { /* get driver info */ ++ struct ethtool_drvinfo info; ++ ++ memset (&info, 0, sizeof info); ++ info.cmd = ETHTOOL_GDRVINFO; ++ strncpy (info.driver, shortname, sizeof info.driver); ++ strncpy (info.version, DRIVER_VERSION, sizeof info.version); ++ strncpy (info.fw_version, dev->gadget->name, sizeof info.fw_version); ++ strncpy (info.bus_info, dev->gadget->dev.bus_id, ++ sizeof info.bus_info); ++ if (copy_to_user (useraddr, &info, sizeof (info))) ++ return -EFAULT; ++ return 0; ++ } ++ ++ case ETHTOOL_GLINK: { /* get link status */ ++ struct ethtool_value edata = { ETHTOOL_GLINK }; ++ ++ edata.data = (dev->gadget->speed != USB_SPEED_UNKNOWN); ++ if (copy_to_user (useraddr, &edata, sizeof (edata))) ++ return -EFAULT; ++ return 0; ++ } ++ ++ } ++ /* Note that the ethtool user space code requires EOPNOTSUPP */ ++ return -EOPNOTSUPP; ++} ++ ++static int eth_ioctl (struct net_device *net, struct ifreq *rq, int cmd) ++{ ++ switch (cmd) { ++ case SIOCETHTOOL: ++ return eth_ethtool_ioctl (net, (void *)rq->ifr_data); ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static void defer_kevent (struct eth_dev *dev, int flag) ++{ ++ if (test_and_set_bit (flag, &dev->todo)) ++ return; ++ if (!schedule_work (&dev->work)) ++ ERROR (dev, "kevent %d may have been dropped\n", flag); ++ else ++ DEBUG (dev, "kevent %d scheduled\n", flag); ++} ++ ++static void rx_complete (struct usb_ep *ep, struct usb_request *req); ++ ++#ifndef NET_IP_ALIGN ++/* this can be a cpu-specific value */ ++#define NET_IP_ALIGN 2 ++#endif ++ ++static int ++rx_submit (struct eth_dev *dev, struct usb_request *req, int gfp_flags) ++{ ++ struct sk_buff *skb; ++ int retval = -ENOMEM; ++ size_t size; ++ ++ /* Padding up to RX_EXTRA handles minor disagreements with host. ++ * Normally we use the USB "terminate on short read" convention; ++ * so allow up to (N*maxpacket), since that memory is normally ++ * already allocated. Some hardware doesn't deal well with short ++ * reads (e.g. DMA must be N*maxpacket), so for now don't trim a ++ * byte off the end (to force hardware errors on overflow). ++ * ++ * RNDIS uses internal framing, and explicitly allows senders to ++ * pad to end-of-packet. That's potentially nice for speed, ++ * but means receivers can't recover synch on their own. ++ */ ++ size = (sizeof (struct ethhdr) + dev->net->mtu + RX_EXTRA); ++ size += dev->out_ep->maxpacket - 1; ++#ifdef CONFIG_USB_ETH_RNDIS ++ if (dev->rndis) ++ size += sizeof (struct rndis_packet_msg_type); ++#endif ++ size -= size % dev->out_ep->maxpacket; ++ ++ if ((skb = alloc_skb (size + NET_IP_ALIGN, gfp_flags)) == 0) { ++ DEBUG (dev, "no rx skb\n"); ++ goto enomem; ++ } ++ ++ /* Some platforms perform better when IP packets are aligned, ++ * but on at least one, checksumming fails otherwise. Note: ++ * this doesn't account for variable-sized RNDIS headers. ++ */ ++ skb_reserve(skb, NET_IP_ALIGN); ++ ++ req->buf = skb->data; ++ req->length = size; ++ req->complete = rx_complete; ++ req->context = skb; ++ ++ retval = usb_ep_queue (dev->out_ep, req, gfp_flags); ++ if (retval == -ENOMEM) ++enomem: ++ defer_kevent (dev, WORK_RX_MEMORY); ++ if (retval) { ++ DEBUG (dev, "rx submit --> %d\n", retval); ++ dev_kfree_skb_any (skb); ++ spin_lock (&dev->lock); ++ list_add (&req->list, &dev->rx_reqs); ++ spin_unlock (&dev->lock); ++ } ++ return retval; ++} ++ ++static void rx_complete (struct usb_ep *ep, struct usb_request *req) ++{ ++ struct sk_buff *skb = req->context; ++ struct eth_dev *dev = ep->driver_data; ++ int status = req->status; ++ ++ switch (status) { ++ ++ /* normal completion */ ++ case 0: ++ skb_put (skb, req->actual); ++#ifdef CONFIG_USB_ETH_RNDIS ++ /* we know MaxPacketsPerTransfer == 1 here */ ++ if (dev->rndis) ++ rndis_rm_hdr (req->buf, &(skb->len)); ++#endif ++ if (ETH_HLEN > skb->len || skb->len > ETH_FRAME_LEN) { ++ dev->stats.rx_errors++; ++ dev->stats.rx_length_errors++; ++ DEBUG (dev, "rx length %d\n", skb->len); ++ break; ++ } ++ ++ skb->dev = dev->net; ++ skb->protocol = eth_type_trans (skb, dev->net); ++ dev->stats.rx_packets++; ++ dev->stats.rx_bytes += skb->len; ++ ++ /* no buffer copies needed, unless hardware can't ++ * use skb buffers. ++ */ ++ status = netif_rx (skb); ++ skb = NULL; ++ break; ++ ++ /* software-driven interface shutdown */ ++ case -ECONNRESET: // unlink ++ case -ESHUTDOWN: // disconnect etc ++ VDEBUG (dev, "rx shutdown, code %d\n", status); ++ goto quiesce; ++ ++ /* for hardware automagic (such as pxa) */ ++ case -ECONNABORTED: // endpoint reset ++ DEBUG (dev, "rx %s reset\n", ep->name); ++ defer_kevent (dev, WORK_RX_MEMORY); ++quiesce: ++ dev_kfree_skb_any (skb); ++ goto clean; ++ ++ /* data overrun */ ++ case -EOVERFLOW: ++ dev->stats.rx_over_errors++; ++ // FALLTHROUGH ++ ++ default: ++ dev->stats.rx_errors++; ++ DEBUG (dev, "rx status %d\n", status); ++ break; ++ } ++ ++ if (skb) ++ dev_kfree_skb_any (skb); ++ if (!netif_running (dev->net)) { ++clean: ++ /* nobody reading rx_reqs, so no dev->lock */ ++ list_add (&req->list, &dev->rx_reqs); ++ req = NULL; ++ } ++ if (req) ++ rx_submit (dev, req, GFP_ATOMIC); ++} ++ ++static int prealloc (struct list_head *list, struct usb_ep *ep, ++ unsigned n, int gfp_flags) ++{ ++ unsigned i; ++ struct usb_request *req; ++ ++ if (!n) ++ return -ENOMEM; ++ ++ /* queue/recycle up to N requests */ ++ i = n; ++ list_for_each_entry (req, list, list) { ++ if (i-- == 0) ++ goto extra; ++ } ++ while (i--) { ++ req = usb_ep_alloc_request (ep, gfp_flags); ++ if (!req) ++ return list_empty (list) ? -ENOMEM : 0; ++ list_add (&req->list, list); ++ } ++ return 0; ++ ++extra: ++ /* free extras */ ++ for (;;) { ++ struct list_head *next; ++ ++ next = req->list.next; ++ list_del (&req->list); ++ usb_ep_free_request (ep, req); ++ ++ if (next == list) ++ break; ++ ++ req = container_of (next, struct usb_request, list); ++ } ++ return 0; ++} ++ ++static int alloc_requests (struct eth_dev *dev, unsigned n, int gfp_flags) ++{ ++ int status; ++ ++ status = prealloc (&dev->tx_reqs, dev->in_ep, n, gfp_flags); ++ if (status < 0) ++ goto fail; ++ status = prealloc (&dev->rx_reqs, dev->out_ep, n, gfp_flags); ++ if (status < 0) ++ goto fail; ++ return 0; ++fail: ++ DEBUG (dev, "can't alloc requests\n"); ++ return status; ++} ++ ++static void rx_fill (struct eth_dev *dev, int gfp_flags) ++{ ++ struct usb_request *req; ++ unsigned long flags; ++ ++ clear_bit (WORK_RX_MEMORY, &dev->todo); ++ ++ /* fill unused rxq slots with some skb */ ++ spin_lock_irqsave (&dev->lock, flags); ++ while (!list_empty (&dev->rx_reqs)) { ++ req = container_of (dev->rx_reqs.next, ++ struct usb_request, list); ++ list_del_init (&req->list); ++ spin_unlock_irqrestore (&dev->lock, flags); ++ ++ if (rx_submit (dev, req, gfp_flags) < 0) { ++ defer_kevent (dev, WORK_RX_MEMORY); ++ return; ++ } ++ ++ spin_lock_irqsave (&dev->lock, flags); ++ } ++ spin_unlock_irqrestore (&dev->lock, flags); ++} ++ ++static void eth_work (void *_dev) ++{ ++ struct eth_dev *dev = _dev; ++ ++ if (test_bit (WORK_RX_MEMORY, &dev->todo)) { ++ if (netif_running (dev->net)) ++ rx_fill (dev, GFP_KERNEL); ++ else ++ clear_bit (WORK_RX_MEMORY, &dev->todo); ++ } ++ ++ if (dev->todo) ++ DEBUG (dev, "work done, flags = 0x%lx\n", dev->todo); ++} ++ ++static void tx_complete (struct usb_ep *ep, struct usb_request *req) ++{ ++ struct sk_buff *skb = req->context; ++ struct eth_dev *dev = ep->driver_data; ++ ++ switch (req->status) { ++ default: ++ dev->stats.tx_errors++; ++ VDEBUG (dev, "tx err %d\n", req->status); ++ /* FALLTHROUGH */ ++ case -ECONNRESET: // unlink ++ case -ESHUTDOWN: // disconnect etc ++ break; ++ case 0: ++ dev->stats.tx_bytes += skb->len; ++ } ++ dev->stats.tx_packets++; ++ ++ spin_lock (&dev->lock); ++ list_add (&req->list, &dev->tx_reqs); ++ spin_unlock (&dev->lock); ++ dev_kfree_skb_any (skb); ++ ++ atomic_dec (&dev->tx_qlen); ++ if (netif_carrier_ok (dev->net)) ++ netif_wake_queue (dev->net); ++} ++ ++static inline int eth_is_promisc (struct eth_dev *dev) ++{ ++ /* no filters for the CDC subset; always promisc */ ++ if (subset_active (dev)) ++ return 1; ++ return dev->cdc_filter & USB_CDC_PACKET_TYPE_PROMISCUOUS; ++} ++ ++static int eth_start_xmit (struct sk_buff *skb, struct net_device *net) ++{ ++ struct eth_dev *dev = netdev_priv(net); ++ int length = skb->len; ++ int retval; ++ struct usb_request *req = NULL; ++ unsigned long flags; ++ ++ /* apply outgoing CDC or RNDIS filters */ ++ if (!eth_is_promisc (dev)) { ++ u8 *dest = skb->data; ++ ++ if (dest [0] & 0x01) { ++ u16 type; ++ ++ /* ignores USB_CDC_PACKET_TYPE_MULTICAST and host ++ * SET_ETHERNET_MULTICAST_FILTERS requests ++ */ ++ if (memcmp (dest, net->broadcast, ETH_ALEN) == 0) ++ type = USB_CDC_PACKET_TYPE_BROADCAST; ++ else ++ type = USB_CDC_PACKET_TYPE_ALL_MULTICAST; ++ if (!(dev->cdc_filter & type)) { ++ dev_kfree_skb_any (skb); ++ return 0; ++ } ++ } ++ /* ignores USB_CDC_PACKET_TYPE_DIRECTED */ ++ } ++ ++ spin_lock_irqsave (&dev->lock, flags); ++ req = container_of (dev->tx_reqs.next, struct usb_request, list); ++ list_del (&req->list); ++ if (list_empty (&dev->tx_reqs)) ++ netif_stop_queue (net); ++ spin_unlock_irqrestore (&dev->lock, flags); ++ ++ /* no buffer copies needed, unless the network stack did it ++ * or the hardware can't use skb buffers. ++ * or there's not enough space for any RNDIS headers we need ++ */ ++#ifdef CONFIG_USB_ETH_RNDIS ++ if (dev->rndis) { ++ struct sk_buff *skb_rndis; ++ ++ skb_rndis = skb_realloc_headroom (skb, ++ sizeof (struct rndis_packet_msg_type)); ++ if (!skb_rndis) ++ goto drop; ++ ++ dev_kfree_skb_any (skb); ++ skb = skb_rndis; ++ rndis_add_hdr (skb); ++ length = skb->len; ++ } ++#endif ++ req->buf = skb->data; ++ req->context = skb; ++ req->complete = tx_complete; ++ ++ /* use zlp framing on tx for strict CDC-Ether conformance, ++ * though any robust network rx path ignores extra padding. ++ * and some hardware doesn't like to write zlps. ++ */ ++ req->zero = 1; ++ if (!dev->zlp && (length % dev->in_ep->maxpacket) == 0) ++ length++; ++ ++ req->length = length; ++ ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ /* throttle highspeed IRQ rate back slightly */ ++ req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH) ++ ? ((atomic_read (&dev->tx_qlen) % TX_DELAY) != 0) ++ : 0; ++#endif ++ ++ retval = usb_ep_queue (dev->in_ep, req, GFP_ATOMIC); ++ switch (retval) { ++ default: ++ DEBUG (dev, "tx queue err %d\n", retval); ++ break; ++ case 0: ++ net->trans_start = jiffies; ++ atomic_inc (&dev->tx_qlen); ++ } ++ ++ if (retval) { ++#ifdef CONFIG_USB_ETH_RNDIS ++drop: ++#endif ++ dev->stats.tx_dropped++; ++ dev_kfree_skb_any (skb); ++ spin_lock_irqsave (&dev->lock, flags); ++ if (list_empty (&dev->tx_reqs)) ++ netif_start_queue (net); ++ list_add (&req->list, &dev->tx_reqs); ++ spin_unlock_irqrestore (&dev->lock, flags); ++ } ++ return 0; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++#ifdef CONFIG_USB_ETH_RNDIS ++ ++static void rndis_send_media_state (struct eth_dev *dev, int connect) ++{ ++ if (!dev) ++ return; ++ ++ if (connect) { ++ if (rndis_signal_connect (dev->rndis_config)) ++ return; ++ } else { ++ if (rndis_signal_disconnect (dev->rndis_config)) ++ return; ++ } ++} ++ ++static void ++rndis_control_ack_complete (struct usb_ep *ep, struct usb_request *req) ++{ ++ if (req->status || req->actual != req->length) ++ DEBUG ((struct eth_dev *) ep->driver_data, ++ "rndis control ack complete --> %d, %d/%d\n", ++ req->status, req->actual, req->length); ++ ++ usb_ep_free_buffer(ep, req->buf, req->dma, 8); ++ usb_ep_free_request(ep, req); ++} ++ ++static int rndis_control_ack (struct net_device *net) ++{ ++ struct eth_dev *dev = netdev_priv(net); ++ u32 length; ++ struct usb_request *resp; ++ ++ /* in case RNDIS calls this after disconnect */ ++ if (!dev->status_ep) { ++ DEBUG (dev, "status ENODEV\n"); ++ return -ENODEV; ++ } ++ ++ /* Allocate memory for notification ie. ACK */ ++ resp = usb_ep_alloc_request (dev->status_ep, GFP_ATOMIC); ++ if (!resp) { ++ DEBUG (dev, "status ENOMEM\n"); ++ return -ENOMEM; ++ } ++ ++ resp->buf = usb_ep_alloc_buffer (dev->status_ep, 8, ++ &resp->dma, GFP_ATOMIC); ++ if (!resp->buf) { ++ DEBUG (dev, "status buf ENOMEM\n"); ++ usb_ep_free_request (dev->status_ep, resp); ++ return -ENOMEM; ++ } ++ ++ /* Send RNDIS RESPONSE_AVAILABLE notification; ++ * USB_CDC_NOTIFY_RESPONSE_AVAILABLE should work too ++ */ ++ resp->length = 8; ++ resp->complete = rndis_control_ack_complete; ++ ++ *((__le32 *) resp->buf) = __constant_cpu_to_le32 (1); ++ *((__le32 *) resp->buf + 1) = __constant_cpu_to_le32 (0); ++ ++ length = usb_ep_queue (dev->status_ep, resp, GFP_ATOMIC); ++ if (length < 0) { ++ resp->status = 0; ++ rndis_control_ack_complete (dev->status_ep, resp); ++ } ++ ++ return 0; ++} ++ ++#endif /* RNDIS */ ++ ++static void eth_start (struct eth_dev *dev, int gfp_flags) ++{ ++ DEBUG (dev, "%s\n", __FUNCTION__); ++ ++ /* fill the rx queue */ ++ rx_fill (dev, gfp_flags); ++ ++ /* and open the tx floodgates */ ++ atomic_set (&dev->tx_qlen, 0); ++ netif_wake_queue (dev->net); ++#ifdef CONFIG_USB_ETH_RNDIS ++ if (dev->rndis) { ++ rndis_set_param_medium (dev->rndis_config, ++ NDIS_MEDIUM_802_3, ++ BITRATE(dev->gadget)); ++ rndis_send_media_state (dev, 1); ++ } ++#endif ++} ++ ++static int eth_open (struct net_device *net) ++{ ++ struct eth_dev *dev = netdev_priv(net); ++ ++ DEBUG (dev, "%s\n", __FUNCTION__); ++ if (netif_carrier_ok (dev->net)) ++ eth_start (dev, GFP_KERNEL); ++ return 0; ++} ++ ++static int eth_stop (struct net_device *net) ++{ ++ struct eth_dev *dev = netdev_priv(net); ++ ++ VDEBUG (dev, "%s\n", __FUNCTION__); ++ netif_stop_queue (net); ++ ++ DEBUG (dev, "stop stats: rx/tx %ld/%ld, errs %ld/%ld\n", ++ dev->stats.rx_packets, dev->stats.tx_packets, ++ dev->stats.rx_errors, dev->stats.tx_errors ++ ); ++ ++ /* ensure there are no more active requests */ ++ if (dev->config) { ++ usb_ep_disable (dev->in_ep); ++ usb_ep_disable (dev->out_ep); ++ if (netif_carrier_ok (dev->net)) { ++ DEBUG (dev, "host still using in/out endpoints\n"); ++ // FIXME idiom may leave toggle wrong here ++ usb_ep_enable (dev->in_ep, dev->in); ++ usb_ep_enable (dev->out_ep, dev->out); ++ } ++ if (dev->status_ep) { ++ usb_ep_disable (dev->status_ep); ++ usb_ep_enable (dev->status_ep, dev->status); ++ } ++ } ++ ++#ifdef CONFIG_USB_ETH_RNDIS ++ if (dev->rndis) { ++ rndis_set_param_medium (dev->rndis_config, ++ NDIS_MEDIUM_802_3, 0); ++ rndis_send_media_state (dev, 0); ++ } ++#endif ++ ++ return 0; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static struct usb_request *eth_req_alloc (struct usb_ep *ep, unsigned size) ++{ ++ struct usb_request *req; ++ ++ req = usb_ep_alloc_request (ep, GFP_KERNEL); ++ if (!req) ++ return NULL; ++ ++ req->buf = kmalloc (size, GFP_KERNEL); ++ if (!req->buf) { ++ usb_ep_free_request (ep, req); ++ req = NULL; ++ } ++ return req; ++} ++ ++static void ++eth_req_free (struct usb_ep *ep, struct usb_request *req) ++{ ++ kfree (req->buf); ++ usb_ep_free_request (ep, req); ++} ++ ++ ++static void ++eth_unbind (struct usb_gadget *gadget) ++{ ++ struct eth_dev *dev = get_gadget_data (gadget); ++ ++ DEBUG (dev, "unbind\n"); ++#ifdef CONFIG_USB_ETH_RNDIS ++ rndis_deregister (dev->rndis_config); ++ rndis_exit (); ++#endif ++ ++ /* we've already been disconnected ... no i/o is active */ ++ if (dev->req) { ++ eth_req_free (gadget->ep0, dev->req); ++ dev->req = NULL; ++ } ++ if (dev->stat_req) { ++ eth_req_free (dev->status_ep, dev->stat_req); ++ dev->stat_req = NULL; ++ } ++ ++ unregister_netdev (dev->net); ++ free_netdev(dev->net); ++ ++ /* assuming we used keventd, it must quiesce too */ ++ flush_scheduled_work (); ++ set_gadget_data (gadget, NULL); ++} ++ ++static u8 __init nibble (unsigned char c) ++{ ++ if (likely (isdigit (c))) ++ return c - '0'; ++ c = toupper (c); ++ if (likely (isxdigit (c))) ++ return 10 + c - 'A'; ++ return 0; ++} ++ ++static void __init get_ether_addr (const char *str, u8 *dev_addr) ++{ ++ if (str) { ++ unsigned i; ++ ++ for (i = 0; i < 6; i++) { ++ unsigned char num; ++ ++ if((*str == '.') || (*str == ':')) ++ str++; ++ num = nibble(*str++) << 4; ++ num |= (nibble(*str++)); ++ dev_addr [i] = num; ++ } ++ if (is_valid_ether_addr (dev_addr)) ++ return; ++ } ++ random_ether_addr(dev_addr); ++} ++ ++static int __init ++eth_bind (struct usb_gadget *gadget) ++{ ++ struct eth_dev *dev; ++ struct net_device *net; ++ u8 cdc = 1, zlp = 1, rndis = 1; ++ struct usb_ep *in_ep, *out_ep, *status_ep = NULL; ++ int status = -ENOMEM; ++ ++ /* these flags are only ever cleared; compiler take note */ ++#ifndef DEV_CONFIG_CDC ++ cdc = 0; ++#endif ++#ifndef CONFIG_USB_ETH_RNDIS ++ rndis = 0; ++#endif ++ ++ /* Because most host side USB stacks handle CDC Ethernet, that ++ * standard protocol is _strongly_ preferred for interop purposes. ++ * (By everyone except Microsoft.) ++ */ ++ if (gadget_is_net2280 (gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0201); ++ } else if (gadget_is_dummy (gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0202); ++ } else if (gadget_is_pxa (gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0203); ++ /* pxa doesn't support altsettings */ ++ cdc = 0; ++ } else if (gadget_is_sh(gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0204); ++ /* sh doesn't support multiple interfaces or configs */ ++ cdc = 0; ++ rndis = 0; ++ } else if (gadget_is_sa1100 (gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0205); ++ /* hardware can't write zlps */ ++ zlp = 0; ++ /* sa1100 CAN do CDC, without status endpoint ... we use ++ * non-CDC to be compatible with ARM Linux-2.4 "usb-eth". ++ */ ++ cdc = 0; ++ } else if (gadget_is_goku (gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0206); ++ } else if (gadget_is_mq11xx (gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0207); ++ } else if (gadget_is_omap (gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0208); ++ } else if (gadget_is_lh7a40x(gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0209); ++ } else if (gadget_is_n9604(gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0210); ++ } else if (gadget_is_pxa27x(gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0211); ++ } else if (gadget_is_s3c2410(gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0212); ++ } else if (gadget_is_at91(gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0213); ++ } else { ++ /* can't assume CDC works. don't want to default to ++ * anything less functional on CDC-capable hardware, ++ * so we fail in this case. ++ */ ++ printk (KERN_ERR "%s: " ++ "controller '%s' not recognized\n", ++ shortname, gadget->name); ++ return -ENODEV; ++ } ++ snprintf (manufacturer, sizeof manufacturer, ++ UTS_SYSNAME " " UTS_RELEASE "/%s", ++ gadget->name); ++ ++ /* If there's an RNDIS configuration, that's what Windows wants to ++ * be using ... so use these product IDs here and in the "linux.inf" ++ * needed to install MSFT drivers. Current Linux kernels will use ++ * the second configuration if it's CDC Ethernet, and need some help ++ * to choose the right configuration otherwise. ++ */ ++ if (rndis) { ++ device_desc.idVendor = ++ __constant_cpu_to_le16(RNDIS_VENDOR_NUM); ++ device_desc.idProduct = ++ __constant_cpu_to_le16(RNDIS_PRODUCT_NUM); ++ snprintf (product_desc, sizeof product_desc, ++ "RNDIS/%s", driver_desc); ++ ++ /* CDC subset ... recognized by Linux since 2.4.10, but Windows ++ * drivers aren't widely available. ++ */ ++ } else if (!cdc) { ++ device_desc.bDeviceClass = USB_CLASS_VENDOR_SPEC; ++ device_desc.idVendor = ++ __constant_cpu_to_le16(SIMPLE_VENDOR_NUM); ++ device_desc.idProduct = ++ __constant_cpu_to_le16(SIMPLE_PRODUCT_NUM); ++ } ++ ++ /* support optional vendor/distro customization */ ++ if (idVendor) { ++ if (!idProduct) { ++ printk (KERN_ERR "%s: idVendor needs idProduct!\n", ++ shortname); ++ return -ENODEV; ++ } ++ device_desc.idVendor = cpu_to_le16(idVendor); ++ device_desc.idProduct = cpu_to_le16(idProduct); ++ if (bcdDevice) ++ device_desc.bcdDevice = cpu_to_le16(bcdDevice); ++ } ++ if (iManufacturer) ++ strncpy (manufacturer, iManufacturer, sizeof manufacturer); ++ if (iProduct) ++ strncpy (product_desc, iProduct, sizeof product_desc); ++ ++ /* all we really need is bulk IN/OUT */ ++ usb_ep_autoconfig_reset (gadget); ++ in_ep = usb_ep_autoconfig (gadget, &fs_source_desc); ++ if (!in_ep) { ++autoconf_fail: ++ printk (KERN_ERR "%s: can't autoconfigure on %s\n", ++ shortname, gadget->name); ++ return -ENODEV; ++ } ++ EP_IN_NAME = in_ep->name; ++ in_ep->driver_data = in_ep; /* claim */ ++ ++ out_ep = usb_ep_autoconfig (gadget, &fs_sink_desc); ++ if (!out_ep) ++ goto autoconf_fail; ++ EP_OUT_NAME = out_ep->name; ++ out_ep->driver_data = out_ep; /* claim */ ++ ++#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) ++ /* CDC Ethernet control interface doesn't require a status endpoint. ++ * Since some hosts expect one, try to allocate one anyway. ++ */ ++ if (cdc || rndis) { ++ status_ep = usb_ep_autoconfig (gadget, &fs_status_desc); ++ if (status_ep) { ++ EP_STATUS_NAME = status_ep->name; ++ status_ep->driver_data = status_ep; /* claim */ ++ } else if (rndis) { ++ printk (KERN_ERR "%s: " ++ "can't run RNDIS on %s\n", ++ shortname, gadget->name); ++ return -ENODEV; ++#ifdef DEV_CONFIG_CDC ++ /* pxa25x only does CDC subset; often used with RNDIS */ ++ } else if (cdc) { ++ control_intf.bNumEndpoints = 0; ++ /* FIXME remove endpoint from descriptor list */ ++#endif ++ } ++ } ++#endif ++ ++ /* one config: cdc, else minimal subset */ ++ if (!cdc) { ++ eth_config.bNumInterfaces = 1; ++ eth_config.iConfiguration = STRING_SUBSET; ++ fs_subset_descriptors(); ++ hs_subset_descriptors(); ++ } ++ ++ /* For now RNDIS is always a second config */ ++ if (rndis) ++ device_desc.bNumConfigurations = 2; ++ ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ if (rndis) ++ dev_qualifier.bNumConfigurations = 2; ++ else if (!cdc) ++ dev_qualifier.bDeviceClass = USB_CLASS_VENDOR_SPEC; ++ ++ /* assumes ep0 uses the same value for both speeds ... */ ++ dev_qualifier.bMaxPacketSize0 = device_desc.bMaxPacketSize0; ++ ++ /* and that all endpoints are dual-speed */ ++ hs_source_desc.bEndpointAddress = fs_source_desc.bEndpointAddress; ++ hs_sink_desc.bEndpointAddress = fs_sink_desc.bEndpointAddress; ++#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) ++ if (EP_STATUS_NAME) ++ hs_status_desc.bEndpointAddress = ++ fs_status_desc.bEndpointAddress; ++#endif ++#endif /* DUALSPEED */ ++ ++ device_desc.bMaxPacketSize0 = gadget->ep0->maxpacket; ++ usb_gadget_set_selfpowered (gadget); ++ ++ if (gadget->is_otg) { ++ otg_descriptor.bmAttributes |= USB_OTG_HNP, ++ eth_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP; ++ eth_config.bMaxPower = 4; ++#ifdef CONFIG_USB_ETH_RNDIS ++ rndis_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP; ++ rndis_config.bMaxPower = 4; ++#endif ++ } ++ ++ net = alloc_etherdev (sizeof *dev); ++ if (!net) ++ return status; ++ dev = netdev_priv(net); ++ spin_lock_init (&dev->lock); ++ INIT_WORK (&dev->work, eth_work, dev); ++ INIT_LIST_HEAD (&dev->tx_reqs); ++ INIT_LIST_HEAD (&dev->rx_reqs); ++ ++ /* network device setup */ ++ dev->net = net; ++ SET_MODULE_OWNER (net); ++ strcpy (net->name, "usb%d"); ++ dev->cdc = cdc; ++ dev->zlp = zlp; ++ ++ dev->in_ep = in_ep; ++ dev->out_ep = out_ep; ++ dev->status_ep = status_ep; ++ ++ /* Module params for these addresses should come from ID proms. ++ * The host side address is used with CDC and RNDIS, and commonly ++ * ends up in a persistent config database. ++ */ ++ get_ether_addr(dev_addr, net->dev_addr); ++ if (cdc || rndis) { ++ get_ether_addr(host_addr, dev->host_mac); ++#ifdef DEV_CONFIG_CDC ++ snprintf (ethaddr, sizeof ethaddr, "%02X%02X%02X%02X%02X%02X", ++ dev->host_mac [0], dev->host_mac [1], ++ dev->host_mac [2], dev->host_mac [3], ++ dev->host_mac [4], dev->host_mac [5]); ++#endif ++ } ++ ++ if (rndis) { ++ status = rndis_init(); ++ if (status < 0) { ++ printk (KERN_ERR "%s: can't init RNDIS, %d\n", ++ shortname, status); ++ goto fail; ++ } ++ } ++ ++ net->change_mtu = eth_change_mtu; ++ net->get_stats = eth_get_stats; ++ net->hard_start_xmit = eth_start_xmit; ++ net->open = eth_open; ++ net->stop = eth_stop; ++ // watchdog_timeo, tx_timeout ... ++ // set_multicast_list ++ net->do_ioctl = eth_ioctl; ++ ++ /* preallocate control message data and buffer */ ++ dev->req = eth_req_alloc (gadget->ep0, USB_BUFSIZ); ++ if (!dev->req) ++ goto fail; ++ dev->req->complete = eth_setup_complete; ++ ++ /* PO: this code my be reached with STATUS_BYTECOUNT undefined ++ Don't allocate stat_req then?! ++ */ ++#ifdef STATUS_BYTECOUNT ++ /* ... and maybe likewise for status transfer */ ++ if (dev->status_ep) { ++ dev->stat_req = eth_req_alloc (dev->status_ep, ++ STATUS_BYTECOUNT); ++ if (!dev->stat_req) { ++ eth_req_free (gadget->ep0, dev->req); ++ goto fail; ++ } ++ } ++#endif ++ ++ /* finish hookup to lower layer ... */ ++ dev->gadget = gadget; ++ set_gadget_data (gadget, dev); ++ gadget->ep0->driver_data = dev; ++ ++ /* two kinds of host-initiated state changes: ++ * - iff DATA transfer is active, carrier is "on" ++ * - tx queueing enabled if open *and* carrier is "on" ++ */ ++ netif_stop_queue (dev->net); ++ netif_carrier_off (dev->net); ++ ++ // SET_NETDEV_DEV (dev->net, &gadget->dev); ++ status = register_netdev (dev->net); ++ if (status < 0) ++ goto fail1; ++ ++ INFO (dev, "%s, version: " DRIVER_VERSION "\n", driver_desc); ++ INFO (dev, "using %s, OUT %s IN %s%s%s\n", gadget->name, ++ EP_OUT_NAME, EP_IN_NAME, ++ EP_STATUS_NAME ? " STATUS " : "", ++ EP_STATUS_NAME ? EP_STATUS_NAME : "" ++ ); ++ INFO (dev, "MAC %02x:%02x:%02x:%02x:%02x:%02x\n", ++ net->dev_addr [0], net->dev_addr [1], ++ net->dev_addr [2], net->dev_addr [3], ++ net->dev_addr [4], net->dev_addr [5]); ++ ++ if (cdc || rndis) ++ INFO (dev, "HOST MAC %02x:%02x:%02x:%02x:%02x:%02x\n", ++ dev->host_mac [0], dev->host_mac [1], ++ dev->host_mac [2], dev->host_mac [3], ++ dev->host_mac [4], dev->host_mac [5]); ++ ++#ifdef CONFIG_USB_ETH_RNDIS ++ if (rndis) { ++ u32 vendorID = 0; ++ ++ /* FIXME RNDIS vendor id == "vendor NIC code" == ? */ ++ ++ dev->rndis_config = rndis_register (rndis_control_ack); ++ if (dev->rndis_config < 0) { ++fail0: ++ unregister_netdev (dev->net); ++ status = -ENODEV; ++ goto fail; ++ } ++ ++ /* these set up a lot of the OIDs that RNDIS needs */ ++ rndis_set_host_mac (dev->rndis_config, dev->host_mac); ++ if (rndis_set_param_dev (dev->rndis_config, dev->net, ++ &dev->stats)) ++ goto fail0; ++ if (rndis_set_param_vendor (dev->rndis_config, vendorID, ++ manufacturer)) ++ goto fail0; ++ if (rndis_set_param_medium (dev->rndis_config, ++ NDIS_MEDIUM_802_3, ++ 0)) ++ goto fail0; ++ INFO (dev, "RNDIS ready\n"); ++ } ++#endif ++ ++ return status; ++ ++fail1: ++ DEBUG (dev, "register_netdev failed, %d\n", status); ++fail: ++ eth_unbind (gadget); ++ return status; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void ++eth_suspend (struct usb_gadget *gadget) ++{ ++ struct eth_dev *dev = get_gadget_data (gadget); ++ ++ DEBUG (dev, "suspend\n"); ++ dev->suspended = 1; ++} ++ ++static void ++eth_resume (struct usb_gadget *gadget) ++{ ++ struct eth_dev *dev = get_gadget_data (gadget); ++ ++ DEBUG (dev, "resume\n"); ++ dev->suspended = 0; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static struct usb_gadget_driver eth_driver = { ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ .speed = USB_SPEED_HIGH, ++#else ++ .speed = USB_SPEED_FULL, ++#endif ++ .function = (char *) driver_desc, ++ .bind = eth_bind, ++ .unbind = eth_unbind, ++ ++ .setup = eth_setup, ++ .disconnect = eth_disconnect, ++ ++ /* PO: is this available? */ ++ .suspend = eth_suspend, ++ .resume = eth_resume, ++ ++ .driver = { ++ .name = (char *) shortname, ++ // .shutdown = ... ++ // .suspend = ... ++ // .resume = ... ++ }, ++}; ++ ++MODULE_DESCRIPTION (DRIVER_DESC); ++MODULE_AUTHOR ("David Brownell, Benedikt Spanger"); ++MODULE_LICENSE ("GPL"); ++ ++ ++static int __init init (void) ++{ ++ return usb_gadget_register_driver (ð_driver); ++} ++module_init (init); ++ ++static void __exit cleanup (void) ++{ ++ usb_gadget_unregister_driver (ð_driver); ++} ++module_exit (cleanup); ++ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/file_storage.c kernel/drivers/usb/gadget/file_storage.c +--- /tmp/kernel/drivers/usb/gadget/file_storage.c 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/file_storage.c 2005-04-22 17:53:19.431540004 +0200 +@@ -0,0 +1,3939 @@ ++/* ++ * file_storage.c -- File-backed USB Storage Gadget, for USB development ++ * ++ * Copyright (C) 2003, 2004 Alan Stern ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions, and the following disclaimer, ++ * without modification. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. The names of the above-listed copyright holders may not be used ++ * to endorse or promote products derived from this software without ++ * specific prior written permission. ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS ++ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ++ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR ++ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ++ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, ++ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR ++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF ++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++ ++/* ++ * The File-backed Storage Gadget acts as a USB Mass Storage device, ++ * appearing to the host as a disk drive. In addition to providing an ++ * example of a genuinely useful gadget driver for a USB device, it also ++ * illustrates a technique of double-buffering for increased throughput. ++ * Last but not least, it gives an easy way to probe the behavior of the ++ * Mass Storage drivers in a USB host. ++ * ++ * Backing storage is provided by a regular file or a block device, specified ++ * by the "file" module parameter. Access can be limited to read-only by ++ * setting the optional "ro" module parameter. ++ * ++ * The gadget supports the Control-Bulk (CB), Control-Bulk-Interrupt (CBI), ++ * and Bulk-Only (also known as Bulk-Bulk-Bulk or BBB) transports, selected ++ * by the optional "transport" module parameter. It also supports the ++ * following protocols: RBC (0x01), ATAPI or SFF-8020i (0x02), QIC-157 (0c03), ++ * UFI (0x04), SFF-8070i (0x05), and transparent SCSI (0x06), selected by ++ * the optional "protocol" module parameter. For testing purposes the ++ * gadget will indicate that it has removable media if the optional ++ * "removable" module parameter is set. In addition, the default Vendor ID, ++ * Product ID, and release number can be overridden. ++ * ++ * There is support for multiple logical units (LUNs), each of which has ++ * its own backing file. The number of LUNs can be set using the optional ++ * "luns" module parameter (anywhere from 1 to 8), and the corresponding ++ * files are specified using comma-separated lists for "file" and "ro". ++ * The default number of LUNs is taken from the number of "file" elements; ++ * it is 1 if "file" is not given. If "removable" is not set then a backing ++ * file must be specified for each LUN. If it is set, then an unspecified ++ * or empty backing filename means the LUN's medium is not loaded. ++ * ++ * Requirements are modest; only a bulk-in and a bulk-out endpoint are ++ * needed (an interrupt-out endpoint is also needed for CBI). The memory ++ * requirement amounts to two 16K buffers, size configurable by a parameter. ++ * Support is included for both full-speed and high-speed operation. ++ * ++ * Module options: ++ * ++ * file=filename[,filename...] ++ * Required if "removable" is not set, names of ++ * the files or block devices used for ++ * backing storage ++ * ro=b[,b...] Default false, booleans for read-only access ++ * luns=N Default N = number of filenames, number of ++ * LUNs to support ++ * transport=XXX Default BBB, transport name (CB, CBI, or BBB) ++ * protocol=YYY Default SCSI, protocol name (RBC, 8020 or ++ * ATAPI, QIC, UFI, 8070, or SCSI; ++ * also 1 - 6) ++ * removable Default false, boolean for removable media ++ * vendor=0xVVVV Default 0x0525 (NetChip), USB Vendor ID ++ * product=0xPPPP Default 0xa4a5 (FSG), USB Product ID ++ * release=0xRRRR Override the USB release number (bcdDevice) ++ * buflen=N Default N=16384, buffer size used (will be ++ * rounded down to a multiple of ++ * PAGE_CACHE_SIZE) ++ * stall Default determined according to the type of ++ * USB device controller (usually true), ++ * boolean to permit the driver to halt ++ * bulk endpoints ++ * ++ * If CONFIG_USB_FILE_STORAGE_TEST is not set, only the "file" and "ro" ++ * options are available; default values are used for everything else. ++ * ++ * This gadget driver is heavily based on "Gadget Zero" by David Brownell. ++ */ ++ ++ ++/* ++ * Driver Design ++ * ++ * The FSG driver is fairly straightforward. There is a main kernel ++ * thread that handles most of the work. Interrupt routines field ++ * callbacks from the controller driver: bulk- and interrupt-request ++ * completion notifications, endpoint-0 events, and disconnect events. ++ * Completion events are passed to the main thread by wakeup calls. Many ++ * ep0 requests are handled at interrupt time, but SetInterface, ++ * SetConfiguration, and device reset requests are forwarded to the ++ * thread in the form of "exceptions" using SIGUSR1 signals (since they ++ * should interrupt any ongoing file I/O operations). ++ * ++ * The thread's main routine implements the standard command/data/status ++ * parts of a SCSI interaction. It and its subroutines are full of tests ++ * for pending signals/exceptions -- all this polling is necessary since ++ * the kernel has no setjmp/longjmp equivalents. (Maybe this is an ++ * indication that the driver really wants to be running in userspace.) ++ * An important point is that so long as the thread is alive it keeps an ++ * open reference to the backing file. This will prevent unmounting ++ * the backing file's underlying filesystem and could cause problems ++ * during system shutdown, for example. To prevent such problems, the ++ * thread catches INT, TERM, and KILL signals and converts them into ++ * an EXIT exception. ++ * ++ * In normal operation the main thread is started during the gadget's ++ * fsg_bind() callback and stopped during fsg_unbind(). But it can also ++ * exit when it receives a signal, and there's no point leaving the ++ * gadget running when the thread is dead. So just before the thread ++ * exits, it deregisters the gadget driver. This makes things a little ++ * tricky: The driver is deregistered at two places, and the exiting ++ * thread can indirectly call fsg_unbind() which in turn can tell the ++ * thread to exit. The first problem is resolved through the use of the ++ * REGISTERED atomic bitflag; the driver will only be deregistered once. ++ * The second problem is resolved by having fsg_unbind() check ++ * fsg->state; it won't try to stop the thread if the state is already ++ * FSG_STATE_TERMINATED. ++ * ++ * To provide maximum throughput, the driver uses a circular pipeline of ++ * buffer heads (struct fsg_buffhd). In principle the pipeline can be ++ * arbitrarily long; in practice the benefits don't justify having more ++ * than 2 stages (i.e., double buffering). But it helps to think of the ++ * pipeline as being a long one. Each buffer head contains a bulk-in and ++ * a bulk-out request pointer (since the buffer can be used for both ++ * output and input -- directions always are given from the host's ++ * point of view) as well as a pointer to the buffer and various state ++ * variables. ++ * ++ * Use of the pipeline follows a simple protocol. There is a variable ++ * (fsg->next_buffhd_to_fill) that points to the next buffer head to use. ++ * At any time that buffer head may still be in use from an earlier ++ * request, so each buffer head has a state variable indicating whether ++ * it is EMPTY, FULL, or BUSY. Typical use involves waiting for the ++ * buffer head to be EMPTY, filling the buffer either by file I/O or by ++ * USB I/O (during which the buffer head is BUSY), and marking the buffer ++ * head FULL when the I/O is complete. Then the buffer will be emptied ++ * (again possibly by USB I/O, during which it is marked BUSY) and ++ * finally marked EMPTY again (possibly by a completion routine). ++ * ++ * A module parameter tells the driver to avoid stalling the bulk ++ * endpoints wherever the transport specification allows. This is ++ * necessary for some UDCs like the SuperH, which cannot reliably clear a ++ * halt on a bulk endpoint. However, under certain circumstances the ++ * Bulk-only specification requires a stall. In such cases the driver ++ * will halt the endpoint and set a flag indicating that it should clear ++ * the halt in software during the next device reset. Hopefully this ++ * will permit everything to work correctly. ++ * ++ * One subtle point concerns sending status-stage responses for ep0 ++ * requests. Some of these requests, such as device reset, can involve ++ * interrupting an ongoing file I/O operation, which might take an ++ * arbitrarily long time. During that delay the host might give up on ++ * the original ep0 request and issue a new one. When that happens the ++ * driver should not notify the host about completion of the original ++ * request, as the host will no longer be waiting for it. So the driver ++ * assigns to each ep0 request a unique tag, and it keeps track of the ++ * tag value of the request associated with a long-running exception ++ * (device-reset, interface-change, or configuration-change). When the ++ * exception handler is finished, the status-stage response is submitted ++ * only if the current ep0 request tag is equal to the exception request ++ * tag. Thus only the most recently received ep0 request will get a ++ * status-stage response. ++ * ++ * Warning: This driver source file is too long. It ought to be split up ++ * into a header file plus about 3 separate .c files, to handle the details ++ * of the Gadget, USB Mass Storage, and SCSI protocols. ++ */ ++ ++ ++#undef DEBUG ++#undef VERBOSE ++#undef DUMP_MSGS ++ ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include "gadget_chips.h" ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++#define DRIVER_DESC "File-backed Storage Gadget" ++#define DRIVER_NAME "g_file_storage" ++#define DRIVER_VERSION "05 June 2004" ++ ++static const char longname[] = DRIVER_DESC; ++static const char shortname[] = DRIVER_NAME; ++ ++MODULE_DESCRIPTION(DRIVER_DESC); ++MODULE_AUTHOR("Alan Stern"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ ++/* Thanks to NetChip Technologies for donating this product ID. ++ * ++ * DO NOT REUSE THESE IDs with any other driver!! Ever!! ++ * Instead: allocate your own, using normal USB-IF procedures. */ ++#define DRIVER_VENDOR_ID 0x0525 // NetChip ++#define DRIVER_PRODUCT_ID 0xa4a5 // Linux-USB File-backed Storage Gadget ++ ++ ++/* ++ * This driver assumes self-powered hardware and has no way for users to ++ * trigger remote wakeup. It uses autoconfiguration to select endpoints ++ * and endpoint addresses. ++ */ ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++#define fakedev_printk(level, dev, format, args...) \ ++ printk(level "%s %s: " format , DRIVER_NAME , (dev)->name , ## args) ++ ++#define xprintk(f,level,fmt,args...) \ ++ fakedev_printk(level , (f)->gadget , fmt , ## args) ++#define yprintk(l,level,fmt,args...) \ ++ fakedev_printk(level , &(l)->dev , fmt , ## args) ++ ++#ifdef DEBUG ++#define DBG(fsg,fmt,args...) \ ++ xprintk(fsg , KERN_DEBUG , fmt , ## args) ++#define LDBG(lun,fmt,args...) \ ++ yprintk(lun , KERN_DEBUG , fmt , ## args) ++#define MDBG(fmt,args...) \ ++ printk(KERN_DEBUG DRIVER_NAME ": " fmt , ## args) ++#else ++#define DBG(fsg,fmt,args...) \ ++ do { } while (0) ++#define LDBG(lun,fmt,args...) \ ++ do { } while (0) ++#define MDBG(fmt,args...) \ ++ do { } while (0) ++#undef VERBOSE ++#undef DUMP_MSGS ++#endif /* DEBUG */ ++ ++#ifdef VERBOSE ++#define VDBG DBG ++#define VLDBG LDBG ++#else ++#define VDBG(fsg,fmt,args...) \ ++ do { } while (0) ++#define VLDBG(lun,fmt,args...) \ ++ do { } while (0) ++#endif /* VERBOSE */ ++ ++#define ERROR(fsg,fmt,args...) \ ++ xprintk(fsg , KERN_ERR , fmt , ## args) ++#define LERROR(lun,fmt,args...) \ ++ yprintk(lun , KERN_ERR , fmt , ## args) ++ ++#define WARN(fsg,fmt,args...) \ ++ xprintk(fsg , KERN_WARNING , fmt , ## args) ++#define LWARN(lun,fmt,args...) \ ++ yprintk(lun , KERN_WARNING , fmt , ## args) ++ ++#define INFO(fsg,fmt,args...) \ ++ xprintk(fsg , KERN_INFO , fmt , ## args) ++#define LINFO(lun,fmt,args...) \ ++ yprintk(lun , KERN_INFO , fmt , ## args) ++ ++#define MINFO(fmt,args...) \ ++ printk(KERN_INFO DRIVER_NAME ": " fmt , ## args) ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* Encapsulate the module parameter settings */ ++ ++#define MAX_LUNS 8 ++ ++static char *file[MAX_LUNS] = {NULL, }; ++static int ro[MAX_LUNS] = {0, }; ++static unsigned int luns = 0; ++ // Default values ++static char *transport = "BBB"; ++static char *protocol = "SCSI"; ++static int removable = 0; ++static unsigned short vendor = DRIVER_VENDOR_ID; ++static unsigned short product = DRIVER_PRODUCT_ID; ++static unsigned short release = 0xffff; // Use controller chip type ++static unsigned int buflen = 16384; ++static int stall = 1; ++ ++static struct { ++ unsigned int nluns; ++ ++ char *transport_parm; ++ char *protocol_parm; ++ int removable; ++ unsigned short vendor; ++ unsigned short product; ++ unsigned short release; ++ unsigned int buflen; ++ int can_stall; ++ ++ int transport_type; ++ char *transport_name; ++ int protocol_type; ++ char *protocol_name; ++ ++} mod_data; ++ ++ ++MODULE_PARM(file, "1-8s"); ++MODULE_PARM_DESC(file, "names of backing files or devices"); ++ ++MODULE_PARM(ro, "1-8b"); ++MODULE_PARM_DESC(ro, "true to force read-only"); ++ ++ ++/* In the non-TEST version, only the file and ro module parameters ++ * are available. */ ++#ifdef CONFIG_USB_FILE_STORAGE_TEST ++ ++MODULE_PARM(luns, "i"); ++MODULE_PARM_DESC(luns, "number of LUNs"); ++ ++MODULE_PARM(transport, "s"); ++MODULE_PARM_DESC(transport, "type of transport (BBB, CBI, or CB)"); ++ ++MODULE_PARM(protocol, "s"); ++MODULE_PARM_DESC(protocol, "type of protocol (RBC, 8020, QIC, UFI, " ++ "8070, or SCSI)"); ++ ++MODULE_PARM(removable, "b"); ++MODULE_PARM_DESC(removable, "true to simulate removable media"); ++ ++MODULE_PARM(vendor, "h"); ++MODULE_PARM_DESC(vendor, "USB Vendor ID"); ++ ++MODULE_PARM(product, "h"); ++MODULE_PARM_DESC(product, "USB Product ID"); ++ ++MODULE_PARM(release, "h"); ++MODULE_PARM_DESC(release, "USB release number"); ++ ++MODULE_PARM(buflen, "i"); ++MODULE_PARM_DESC(buflen, "I/O buffer size"); ++ ++MODULE_PARM(stall, "i"); ++MODULE_PARM_DESC(stall, "false to prevent bulk stalls"); ++ ++#endif /* CONFIG_USB_FILE_STORAGE_TEST */ ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* USB protocol value = the transport method */ ++#define USB_PR_CBI 0x00 // Control/Bulk/Interrupt ++#define USB_PR_CB 0x01 // Control/Bulk w/o interrupt ++#define USB_PR_BULK 0x50 // Bulk-only ++ ++/* USB subclass value = the protocol encapsulation */ ++#define USB_SC_RBC 0x01 // Reduced Block Commands (flash) ++#define USB_SC_8020 0x02 // SFF-8020i, MMC-2, ATAPI (CD-ROM) ++#define USB_SC_QIC 0x03 // QIC-157 (tape) ++#define USB_SC_UFI 0x04 // UFI (floppy) ++#define USB_SC_8070 0x05 // SFF-8070i (removable) ++#define USB_SC_SCSI 0x06 // Transparent SCSI ++ ++/* Bulk-only data structures */ ++ ++/* Command Block Wrapper */ ++struct bulk_cb_wrap { ++ u32 Signature; // Contains 'USBC' ++ u32 Tag; // Unique per command id ++ u32 DataTransferLength; // Size of the data ++ u8 Flags; // Direction in bit 7 ++ u8 Lun; // LUN (normally 0) ++ u8 Length; // Of the CDB, <= MAX_COMMAND_SIZE ++ u8 CDB[16]; // Command Data Block ++}; ++ ++#define USB_BULK_CB_WRAP_LEN 31 ++#define USB_BULK_CB_SIG 0x43425355 // Spells out USBC ++#define USB_BULK_IN_FLAG 0x80 ++ ++/* Command Status Wrapper */ ++struct bulk_cs_wrap { ++ u32 Signature; // Should = 'USBS' ++ u32 Tag; // Same as original command ++ u32 Residue; // Amount not transferred ++ u8 Status; // See below ++}; ++ ++#define USB_BULK_CS_WRAP_LEN 13 ++#define USB_BULK_CS_SIG 0x53425355 // Spells out 'USBS' ++#define USB_STATUS_PASS 0 ++#define USB_STATUS_FAIL 1 ++#define USB_STATUS_PHASE_ERROR 2 ++ ++/* Bulk-only class specific requests */ ++#define USB_BULK_RESET_REQUEST 0xff ++#define USB_BULK_GET_MAX_LUN_REQUEST 0xfe ++ ++ ++/* CBI Interrupt data structure */ ++struct interrupt_data { ++ u8 bType; ++ u8 bValue; ++}; ++ ++#define CBI_INTERRUPT_DATA_LEN 2 ++ ++/* CBI Accept Device-Specific Command request */ ++#define USB_CBI_ADSC_REQUEST 0x00 ++ ++ ++#define MAX_COMMAND_SIZE 16 // Length of a SCSI Command Data Block ++ ++/* SCSI commands that we recognize */ ++#define SC_FORMAT_UNIT 0x04 ++#define SC_INQUIRY 0x12 ++#define SC_MODE_SELECT_6 0x15 ++#define SC_MODE_SELECT_10 0x55 ++#define SC_MODE_SENSE_6 0x1a ++#define SC_MODE_SENSE_10 0x5a ++#define SC_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1e ++#define SC_READ_6 0x08 ++#define SC_READ_10 0x28 ++#define SC_READ_12 0xa8 ++#define SC_READ_CAPACITY 0x25 ++#define SC_READ_FORMAT_CAPACITIES 0x23 ++#define SC_RELEASE 0x17 ++#define SC_REQUEST_SENSE 0x03 ++#define SC_RESERVE 0x16 ++#define SC_SEND_DIAGNOSTIC 0x1d ++#define SC_START_STOP_UNIT 0x1b ++#define SC_SYNCHRONIZE_CACHE 0x35 ++#define SC_TEST_UNIT_READY 0x00 ++#define SC_VERIFY 0x2f ++#define SC_WRITE_6 0x0a ++#define SC_WRITE_10 0x2a ++#define SC_WRITE_12 0xaa ++ ++/* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */ ++#define SS_NO_SENSE 0 ++#define SS_COMMUNICATION_FAILURE 0x040800 ++#define SS_INVALID_COMMAND 0x052000 ++#define SS_INVALID_FIELD_IN_CDB 0x052400 ++#define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x052100 ++#define SS_LOGICAL_UNIT_NOT_SUPPORTED 0x052500 ++#define SS_MEDIUM_NOT_PRESENT 0x023a00 ++#define SS_MEDIUM_REMOVAL_PREVENTED 0x055302 ++#define SS_NOT_READY_TO_READY_TRANSITION 0x062800 ++#define SS_RESET_OCCURRED 0x062900 ++#define SS_SAVING_PARAMETERS_NOT_SUPPORTED 0x053900 ++#define SS_UNRECOVERED_READ_ERROR 0x031100 ++#define SS_WRITE_ERROR 0x030c02 ++#define SS_WRITE_PROTECTED 0x072700 ++ ++#define SK(x) ((u8) ((x) >> 16)) // Sense Key byte, etc. ++#define ASC(x) ((u8) ((x) >> 8)) ++#define ASCQ(x) ((u8) (x)) ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* ++ * These definitions will permit the compiler to avoid generating code for ++ * parts of the driver that aren't used in the non-TEST version. Even gcc ++ * can recognize when a test of a constant expression yields a dead code ++ * path. ++ * ++ * Also, in the non-TEST version, open_backing_file() is only used during ++ * initialization and the sysfs attribute store_xxx routines aren't used ++ * at all. We will define NORMALLY_INIT to mark them as __init so they ++ * don't occupy kernel code space unnecessarily. ++ */ ++ ++#ifdef CONFIG_USB_FILE_STORAGE_TEST ++ ++#define transport_is_bbb() (mod_data.transport_type == USB_PR_BULK) ++#define transport_is_cbi() (mod_data.transport_type == USB_PR_CBI) ++#define protocol_is_scsi() (mod_data.protocol_type == USB_SC_SCSI) ++#define backing_file_is_open(curlun) ((curlun)->filp != NULL) ++#define NORMALLY_INIT ++ ++#else ++ ++#define transport_is_bbb() 1 ++#define transport_is_cbi() 0 ++#define protocol_is_scsi() 1 ++#define backing_file_is_open(curlun) 1 ++#define NORMALLY_INIT __init ++ ++#endif /* CONFIG_USB_FILE_STORAGE_TEST */ ++ ++ ++struct lun { ++ struct file *filp; ++ loff_t file_length; ++ loff_t num_sectors; ++ ++ unsigned int ro : 1; ++ unsigned int prevent_medium_removal : 1; ++ unsigned int registered : 1; ++ ++ u32 sense_data; ++ u32 sense_data_info; ++ u32 unit_attention_data; ++ ++#define BUS_ID_SIZE 20 ++ struct __lun_device { ++ char name[BUS_ID_SIZE]; ++ void *driver_data; ++ } dev; ++}; ++ ++ ++/* Big enough to hold our biggest descriptor */ ++#define EP0_BUFSIZE 256 ++#define DELAYED_STATUS (EP0_BUFSIZE + 999) // An impossibly large value ++ ++/* Number of buffers we will use. 2 is enough for double-buffering */ ++#define NUM_BUFFERS 2 ++ ++enum fsg_buffer_state { ++ BUF_STATE_EMPTY = 0, ++ BUF_STATE_FULL, ++ BUF_STATE_BUSY ++}; ++ ++struct fsg_buffhd { ++ void *buf; ++ dma_addr_t dma; ++ volatile enum fsg_buffer_state state; ++ struct fsg_buffhd *next; ++ ++ /* The NetChip 2280 is faster, and handles some protocol faults ++ * better, if we don't submit any short bulk-out read requests. ++ * So we will record the intended request length here. */ ++ unsigned int bulk_out_intended_length; ++ ++ struct usb_request *inreq; ++ volatile int inreq_busy; ++ struct usb_request *outreq; ++ volatile int outreq_busy; ++}; ++ ++enum fsg_state { ++ FSG_STATE_COMMAND_PHASE = -10, // This one isn't used anywhere ++ FSG_STATE_DATA_PHASE, ++ FSG_STATE_STATUS_PHASE, ++ ++ FSG_STATE_IDLE = 0, ++ FSG_STATE_ABORT_BULK_OUT, ++ FSG_STATE_RESET, ++ FSG_STATE_INTERFACE_CHANGE, ++ FSG_STATE_CONFIG_CHANGE, ++ FSG_STATE_DISCONNECT, ++ FSG_STATE_EXIT, ++ FSG_STATE_TERMINATED ++}; ++ ++enum data_direction { ++ DATA_DIR_UNKNOWN = 0, ++ DATA_DIR_FROM_HOST, ++ DATA_DIR_TO_HOST, ++ DATA_DIR_NONE ++}; ++ ++struct fsg_dev { ++ /* lock protects: state, all the req_busy's, and cbbuf_cmnd */ ++ spinlock_t lock; ++ struct usb_gadget *gadget; ++ ++ /* filesem protects: backing files in use */ ++ struct rw_semaphore filesem; ++ ++ struct usb_ep *ep0; // Handy copy of gadget->ep0 ++ struct usb_request *ep0req; // For control responses ++ volatile unsigned int ep0_req_tag; ++ const char *ep0req_name; ++ ++ struct usb_request *intreq; // For interrupt responses ++ volatile int intreq_busy; ++ struct fsg_buffhd *intr_buffhd; ++ ++ unsigned int bulk_out_maxpacket; ++ enum fsg_state state; // For exception handling ++ unsigned int exception_req_tag; ++ ++ u8 config, new_config; ++ ++ unsigned int running : 1; ++ unsigned int bulk_in_enabled : 1; ++ unsigned int bulk_out_enabled : 1; ++ unsigned int intr_in_enabled : 1; ++ unsigned int phase_error : 1; ++ unsigned int short_packet_received : 1; ++ unsigned int bad_lun_okay : 1; ++ ++ unsigned long atomic_bitflags; ++#define REGISTERED 0 ++#define CLEAR_BULK_HALTS 1 ++ ++ struct usb_ep *bulk_in; ++ struct usb_ep *bulk_out; ++ struct usb_ep *intr_in; ++ ++ struct fsg_buffhd *next_buffhd_to_fill; ++ struct fsg_buffhd *next_buffhd_to_drain; ++ struct fsg_buffhd buffhds[NUM_BUFFERS]; ++ ++ wait_queue_head_t thread_wqh; ++ int thread_wakeup_needed; ++ struct completion thread_notifier; ++ int thread_pid; ++ struct task_struct *thread_task; ++ sigset_t thread_signal_mask; ++ ++ int cmnd_size; ++ u8 cmnd[MAX_COMMAND_SIZE]; ++ enum data_direction data_dir; ++ u32 data_size; ++ u32 data_size_from_cmnd; ++ u32 tag; ++ unsigned int lun; ++ u32 residue; ++ u32 usb_amount_left; ++ ++ /* The CB protocol offers no way for a host to know when a command ++ * has completed. As a result the next command may arrive early, ++ * and we will still have to handle it. For that reason we need ++ * a buffer to store new commands when using CB (or CBI, which ++ * does not oblige a host to wait for command completion either). */ ++ int cbbuf_cmnd_size; ++ u8 cbbuf_cmnd[MAX_COMMAND_SIZE]; ++ ++ unsigned int nluns; ++ struct lun *luns; ++ struct lun *curlun; ++}; ++ ++typedef void (*fsg_routine_t)(struct fsg_dev *); ++ ++static int inline exception_in_progress(struct fsg_dev *fsg) ++{ ++ return (fsg->state > FSG_STATE_IDLE); ++} ++ ++/* Make bulk-out requests be divisible by the maxpacket size */ ++static void inline set_bulk_out_req_length(struct fsg_dev *fsg, ++ struct fsg_buffhd *bh, unsigned int length) ++{ ++ unsigned int rem; ++ ++ bh->bulk_out_intended_length = length; ++ rem = length % fsg->bulk_out_maxpacket; ++ if (rem > 0) ++ length += fsg->bulk_out_maxpacket - rem; ++ bh->outreq->length = length; ++} ++ ++static struct fsg_dev *the_fsg; ++static struct usb_gadget_driver fsg_driver; ++ ++static void close_backing_file(struct lun *curlun); ++static void close_all_backing_files(struct fsg_dev *fsg); ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++#ifdef DUMP_MSGS ++ ++static void dump_msg(struct fsg_dev *fsg, const char *label, ++ const u8 *buf, unsigned int length) ++{ ++ unsigned int start, num, i; ++ char line[52], *p; ++ ++ if (length >= 512) ++ return; ++ DBG(fsg, "%s, length %u:\n", label, length); ++ ++ start = 0; ++ while (length > 0) { ++ num = min(length, 16u); ++ p = line; ++ for (i = 0; i < num; ++i) { ++ if (i == 8) ++ *p++ = ' '; ++ sprintf(p, " %02x", buf[i]); ++ p += 3; ++ } ++ *p = 0; ++ printk(KERN_DEBUG "%6x: %s\n", start, line); ++ buf += num; ++ start += num; ++ length -= num; ++ } ++} ++ ++static void inline dump_cdb(struct fsg_dev *fsg) ++{} ++ ++#else ++ ++static void inline dump_msg(struct fsg_dev *fsg, const char *label, ++ const u8 *buf, unsigned int length) ++{} ++ ++static void inline dump_cdb(struct fsg_dev *fsg) ++{ ++ int i; ++ char cmdbuf[3*MAX_COMMAND_SIZE + 1]; ++ ++ for (i = 0; i < fsg->cmnd_size; ++i) ++ sprintf(cmdbuf + i*3, " %02x", fsg->cmnd[i]); ++ VDBG(fsg, "SCSI CDB: %s\n", cmdbuf); ++} ++ ++#endif /* DUMP_MSGS */ ++ ++ ++static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep) ++{ ++ const char *name; ++ ++ if (ep == fsg->bulk_in) ++ name = "bulk-in"; ++ else if (ep == fsg->bulk_out) ++ name = "bulk-out"; ++ else ++ name = ep->name; ++ DBG(fsg, "%s set halt\n", name); ++ return usb_ep_set_halt(ep); ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* Routines for unaligned data access */ ++ ++static u16 inline get_be16(u8 *buf) ++{ ++ return ((u16) buf[0] << 8) | ((u16) buf[1]); ++} ++ ++static u32 inline get_be32(u8 *buf) ++{ ++ return ((u32) buf[0] << 24) | ((u32) buf[1] << 16) | ++ ((u32) buf[2] << 8) | ((u32) buf[3]); ++} ++ ++static void inline put_be16(u8 *buf, u16 val) ++{ ++ buf[0] = val >> 8; ++ buf[1] = val; ++} ++ ++static void inline put_be32(u8 *buf, u32 val) ++{ ++ buf[0] = val >> 24; ++ buf[1] = val >> 16; ++ buf[2] = val >> 8; ++ buf[3] = val; ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* ++ * DESCRIPTORS ... most are static, but strings and (full) configuration ++ * descriptors are built on demand. Also the (static) config and interface ++ * descriptors are adjusted during fsg_bind(). ++ */ ++#define STRING_MANUFACTURER 1 ++#define STRING_PRODUCT 2 ++#define STRING_SERIAL 3 ++ ++/* There is only one configuration. */ ++#define CONFIG_VALUE 1 ++ ++static struct usb_device_descriptor ++device_desc = { ++ .bLength = sizeof device_desc, ++ .bDescriptorType = USB_DT_DEVICE, ++ ++ .bcdUSB = __constant_cpu_to_le16(0x0200), ++ .bDeviceClass = USB_CLASS_PER_INTERFACE, ++ ++ /* The next three values can be overridden by module parameters */ ++ .idVendor = __constant_cpu_to_le16(DRIVER_VENDOR_ID), ++ .idProduct = __constant_cpu_to_le16(DRIVER_PRODUCT_ID), ++ .bcdDevice = __constant_cpu_to_le16(0xffff), ++ ++ .iManufacturer = STRING_MANUFACTURER, ++ .iProduct = STRING_PRODUCT, ++ .iSerialNumber = STRING_SERIAL, ++ .bNumConfigurations = 1, ++}; ++ ++static struct usb_config_descriptor ++config_desc = { ++ .bLength = sizeof config_desc, ++ .bDescriptorType = USB_DT_CONFIG, ++ ++ /* wTotalLength computed by usb_gadget_config_buf() */ ++ .bNumInterfaces = 1, ++ .bConfigurationValue = CONFIG_VALUE, ++ .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, ++ .bMaxPower = 1, // self-powered ++}; ++ ++/* There is only one interface. */ ++ ++static struct usb_interface_descriptor ++intf_desc = { ++ .bLength = sizeof intf_desc, ++ .bDescriptorType = USB_DT_INTERFACE, ++ ++ .bNumEndpoints = 2, // Adjusted during fsg_bind() ++ .bInterfaceClass = USB_CLASS_MASS_STORAGE, ++ .bInterfaceSubClass = USB_SC_SCSI, // Adjusted during fsg_bind() ++ .bInterfaceProtocol = USB_PR_BULK, // Adjusted during fsg_bind() ++}; ++ ++/* Three full-speed endpoint descriptors: bulk-in, bulk-out, ++ * and interrupt-in. */ ++ ++static struct usb_endpoint_descriptor ++fs_bulk_in_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ .bEndpointAddress = USB_DIR_IN, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ /* wMaxPacketSize set by autoconfiguration */ ++}; ++ ++static struct usb_endpoint_descriptor ++fs_bulk_out_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ .bEndpointAddress = USB_DIR_OUT, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ /* wMaxPacketSize set by autoconfiguration */ ++}; ++ ++static struct usb_endpoint_descriptor ++fs_intr_in_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ .bEndpointAddress = USB_DIR_IN, ++ .bmAttributes = USB_ENDPOINT_XFER_INT, ++ .wMaxPacketSize = __constant_cpu_to_le16(2), ++ .bInterval = 32, // frames -> 32 ms ++}; ++ ++static const struct usb_descriptor_header *fs_function[] = { ++ (struct usb_descriptor_header *) &intf_desc, ++ (struct usb_descriptor_header *) &fs_bulk_in_desc, ++ (struct usb_descriptor_header *) &fs_bulk_out_desc, ++ (struct usb_descriptor_header *) &fs_intr_in_desc, ++ NULL, ++}; ++ ++ ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ ++/* ++ * USB 2.0 devices need to expose both high speed and full speed ++ * descriptors, unless they only run at full speed. ++ * ++ * That means alternate endpoint descriptors (bigger packets) ++ * and a "device qualifier" ... plus more construction options ++ * for the config descriptor. ++ */ ++static struct usb_qualifier_descriptor ++dev_qualifier = { ++ .bLength = sizeof dev_qualifier, ++ .bDescriptorType = USB_DT_DEVICE_QUALIFIER, ++ ++ .bcdUSB = __constant_cpu_to_le16(0x0200), ++ .bDeviceClass = USB_CLASS_PER_INTERFACE, ++ ++ .bNumConfigurations = 1, ++}; ++ ++static struct usb_endpoint_descriptor ++hs_bulk_in_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ /* bEndpointAddress copied from fs_bulk_in_desc during fsg_bind() */ ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ .wMaxPacketSize = __constant_cpu_to_le16(512), ++}; ++ ++static struct usb_endpoint_descriptor ++hs_bulk_out_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ /* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */ ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ .wMaxPacketSize = __constant_cpu_to_le16(512), ++ .bInterval = 1, // NAK every 1 uframe ++}; ++ ++static struct usb_endpoint_descriptor ++hs_intr_in_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ /* bEndpointAddress copied from fs_intr_in_desc during fsg_bind() */ ++ .bmAttributes = USB_ENDPOINT_XFER_INT, ++ .wMaxPacketSize = __constant_cpu_to_le16(2), ++ .bInterval = 9, // 2**(9-1) = 256 uframes -> 32 ms ++}; ++ ++static const struct usb_descriptor_header *hs_function[] = { ++ (struct usb_descriptor_header *) &intf_desc, ++ (struct usb_descriptor_header *) &hs_bulk_in_desc, ++ (struct usb_descriptor_header *) &hs_bulk_out_desc, ++ (struct usb_descriptor_header *) &hs_intr_in_desc, ++ NULL, ++}; ++ ++/* Maxpacket and other transfer characteristics vary by speed. */ ++#define ep_desc(g,fs,hs) (((g)->speed==USB_SPEED_HIGH) ? (hs) : (fs)) ++ ++#else ++ ++/* If there's no high speed support, always use the full-speed descriptor. */ ++#define ep_desc(g,fs,hs) fs ++ ++#endif /* !CONFIG_USB_GADGET_DUALSPEED */ ++ ++ ++/* The CBI specification limits the serial string to 12 uppercase hexadecimal ++ * characters. */ ++static char manufacturer[40]; ++static char serial[13]; ++ ++/* Static strings, in UTF-8 (for simplicity we use only ASCII characters) */ ++static struct usb_string strings[] = { ++ {STRING_MANUFACTURER, manufacturer}, ++ {STRING_PRODUCT, longname}, ++ {STRING_SERIAL, serial}, ++ {} ++}; ++ ++static struct usb_gadget_strings stringtab = { ++ .language = 0x0409, // en-us ++ .strings = strings, ++}; ++ ++ ++/* ++ * Config descriptors must agree with the code that sets configurations ++ * and with code managing interfaces and their altsettings. They must ++ * also handle different speeds and other-speed requests. ++ */ ++static int populate_config_buf(enum usb_device_speed speed, ++ u8 *buf, u8 type, unsigned index) ++{ ++ int len; ++ const struct usb_descriptor_header **function; ++ ++ if (index > 0) ++ return -EINVAL; ++ ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ if (type == USB_DT_OTHER_SPEED_CONFIG) ++ speed = (USB_SPEED_FULL + USB_SPEED_HIGH) - speed; ++ if (speed == USB_SPEED_HIGH) ++ function = hs_function; ++ else ++#endif ++ function = fs_function; ++ ++ len = usb_gadget_config_buf(&config_desc, buf, EP0_BUFSIZE, function); ++ if (len < 0) ++ return len; ++ ((struct usb_config_descriptor *) buf)->bDescriptorType = type; ++ return len; ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* These routines may be called in process context or in_irq */ ++ ++static void wakeup_thread(struct fsg_dev *fsg) ++{ ++ /* Tell the main thread that something has happened */ ++ fsg->thread_wakeup_needed = 1; ++ wake_up_all(&fsg->thread_wqh); ++} ++ ++ ++static void raise_exception(struct fsg_dev *fsg, enum fsg_state new_state) ++{ ++ unsigned long flags; ++ struct task_struct *thread_task; ++ ++ /* Do nothing if a higher-priority exception is already in progress. ++ * If a lower-or-equal priority exception is in progress, preempt it ++ * and notify the main thread by sending it a signal. */ ++ spin_lock_irqsave(&fsg->lock, flags); ++ if (fsg->state <= new_state) { ++ fsg->exception_req_tag = fsg->ep0_req_tag; ++ fsg->state = new_state; ++ thread_task = fsg->thread_task; ++ if (thread_task) ++ send_sig_info(SIGUSR1, (void *) 1L, thread_task); ++ } ++ spin_unlock_irqrestore(&fsg->lock, flags); ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* The disconnect callback and ep0 routines. These always run in_irq, ++ * except that ep0_queue() is called in the main thread to acknowledge ++ * completion of various requests: set config, set interface, and ++ * Bulk-only device reset. */ ++ ++static void fsg_disconnect(struct usb_gadget *gadget) ++{ ++ struct fsg_dev *fsg = get_gadget_data(gadget); ++ ++ DBG(fsg, "disconnect or port reset\n"); ++ raise_exception(fsg, FSG_STATE_DISCONNECT); ++} ++ ++ ++static int ep0_queue(struct fsg_dev *fsg) ++{ ++ int rc; ++ ++ rc = usb_ep_queue(fsg->ep0, fsg->ep0req, GFP_ATOMIC); ++ if (rc != 0 && rc != -ESHUTDOWN) { ++ ++ /* We can't do much more than wait for a reset */ ++ WARN(fsg, "error in submission: %s --> %d\n", ++ fsg->ep0->name, rc); ++ } ++ return rc; ++} ++ ++static void ep0_complete(struct usb_ep *ep, struct usb_request *req) ++{ ++ struct fsg_dev *fsg = (struct fsg_dev *) ep->driver_data; ++ ++ if (req->actual > 0) ++ dump_msg(fsg, fsg->ep0req_name, req->buf, req->actual); ++ if (req->status || req->actual != req->length) ++ DBG(fsg, "%s --> %d, %u/%u\n", __FUNCTION__, ++ req->status, req->actual, req->length); ++ if (req->status == -ECONNRESET) // Request was cancelled ++ usb_ep_fifo_flush(ep); ++ ++ if (req->status == 0 && req->context) ++ ((fsg_routine_t) (req->context))(fsg); ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* Bulk and interrupt endpoint completion handlers. ++ * These always run in_irq. */ ++ ++static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req) ++{ ++ struct fsg_dev *fsg = (struct fsg_dev *) ep->driver_data; ++ struct fsg_buffhd *bh = (struct fsg_buffhd *) req->context; ++ ++ if (req->status || req->actual != req->length) ++ DBG(fsg, "%s --> %d, %u/%u\n", __FUNCTION__, ++ req->status, req->actual, req->length); ++ if (req->status == -ECONNRESET) // Request was cancelled ++ usb_ep_fifo_flush(ep); ++ ++ /* Hold the lock while we update the request and buffer states */ ++ spin_lock(&fsg->lock); ++ bh->inreq_busy = 0; ++ bh->state = BUF_STATE_EMPTY; ++ spin_unlock(&fsg->lock); ++ wakeup_thread(fsg); ++} ++ ++static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) ++{ ++ struct fsg_dev *fsg = (struct fsg_dev *) ep->driver_data; ++ struct fsg_buffhd *bh = (struct fsg_buffhd *) req->context; ++ ++ dump_msg(fsg, "bulk-out", req->buf, req->actual); ++ if (req->status || req->actual != bh->bulk_out_intended_length) ++ DBG(fsg, "%s --> %d, %u/%u\n", __FUNCTION__, ++ req->status, req->actual, ++ bh->bulk_out_intended_length); ++ if (req->status == -ECONNRESET) // Request was cancelled ++ usb_ep_fifo_flush(ep); ++ ++ /* Hold the lock while we update the request and buffer states */ ++ spin_lock(&fsg->lock); ++ bh->outreq_busy = 0; ++ bh->state = BUF_STATE_FULL; ++ spin_unlock(&fsg->lock); ++ wakeup_thread(fsg); ++} ++ ++static void intr_in_complete(struct usb_ep *ep, struct usb_request *req) ++{ ++#ifdef CONFIG_USB_FILE_STORAGE_TEST ++ struct fsg_dev *fsg = (struct fsg_dev *) ep->driver_data; ++ struct fsg_buffhd *bh = (struct fsg_buffhd *) req->context; ++ ++ if (req->status || req->actual != req->length) ++ DBG(fsg, "%s --> %d, %u/%u\n", __FUNCTION__, ++ req->status, req->actual, req->length); ++ if (req->status == -ECONNRESET) // Request was cancelled ++ usb_ep_fifo_flush(ep); ++ ++ /* Hold the lock while we update the request and buffer states */ ++ spin_lock(&fsg->lock); ++ fsg->intreq_busy = 0; ++ bh->state = BUF_STATE_EMPTY; ++ spin_unlock(&fsg->lock); ++ wakeup_thread(fsg); ++#endif /* CONFIG_USB_FILE_STORAGE_TEST */ ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* Ep0 class-specific handlers. These always run in_irq. */ ++ ++static void received_cbi_adsc(struct fsg_dev *fsg, struct fsg_buffhd *bh) ++{ ++#ifdef CONFIG_USB_FILE_STORAGE_TEST ++ struct usb_request *req = fsg->ep0req; ++ static u8 cbi_reset_cmnd[6] = { ++ SC_SEND_DIAGNOSTIC, 4, 0xff, 0xff, 0xff, 0xff}; ++ ++ /* Error in command transfer? */ ++ if (req->status || req->length != req->actual || ++ req->actual < 6 || req->actual > MAX_COMMAND_SIZE) { ++ ++ /* Not all controllers allow a protocol stall after ++ * receiving control-out data, but we'll try anyway. */ ++ fsg_set_halt(fsg, fsg->ep0); ++ return; // Wait for reset ++ } ++ ++ /* Is it the special reset command? */ ++ if (req->actual >= sizeof cbi_reset_cmnd && ++ memcmp(req->buf, cbi_reset_cmnd, ++ sizeof cbi_reset_cmnd) == 0) { ++ ++ /* Raise an exception to stop the current operation ++ * and reinitialize our state. */ ++ DBG(fsg, "cbi reset request\n"); ++ raise_exception(fsg, FSG_STATE_RESET); ++ return; ++ } ++ ++ VDBG(fsg, "CB[I] accept device-specific command\n"); ++ spin_lock(&fsg->lock); ++ ++ /* Save the command for later */ ++ if (fsg->cbbuf_cmnd_size) ++ WARN(fsg, "CB[I] overwriting previous command\n"); ++ fsg->cbbuf_cmnd_size = req->actual; ++ memcpy(fsg->cbbuf_cmnd, req->buf, fsg->cbbuf_cmnd_size); ++ ++ spin_unlock(&fsg->lock); ++ wakeup_thread(fsg); ++#endif /* CONFIG_USB_FILE_STORAGE_TEST */ ++} ++ ++ ++static int class_setup_req(struct fsg_dev *fsg, ++ const struct usb_ctrlrequest *ctrl) ++{ ++ struct usb_request *req = fsg->ep0req; ++ int value = -EOPNOTSUPP; ++ ++ if (!fsg->config) ++ return value; ++ ++ /* Handle Bulk-only class-specific requests */ ++ if (transport_is_bbb()) { ++ switch (ctrl->bRequest) { ++ ++ case USB_BULK_RESET_REQUEST: ++ if (ctrl->bRequestType != (USB_DIR_OUT | ++ USB_TYPE_CLASS | USB_RECIP_INTERFACE)) ++ break; ++ if (ctrl->wIndex != 0) { ++ value = -EDOM; ++ break; ++ } ++ ++ /* Raise an exception to stop the current operation ++ * and reinitialize our state. */ ++ DBG(fsg, "bulk reset request\n"); ++ raise_exception(fsg, FSG_STATE_RESET); ++ value = DELAYED_STATUS; ++ break; ++ ++ case USB_BULK_GET_MAX_LUN_REQUEST: ++ if (ctrl->bRequestType != (USB_DIR_IN | ++ USB_TYPE_CLASS | USB_RECIP_INTERFACE)) ++ break; ++ if (ctrl->wIndex != 0) { ++ value = -EDOM; ++ break; ++ } ++ VDBG(fsg, "get max LUN\n"); ++ *(u8 *) req->buf = fsg->nluns - 1; ++ value = min(ctrl->wLength, (u16) 1); ++ break; ++ } ++ } ++ ++ /* Handle CBI class-specific requests */ ++ else { ++ switch (ctrl->bRequest) { ++ ++ case USB_CBI_ADSC_REQUEST: ++ if (ctrl->bRequestType != (USB_DIR_OUT | ++ USB_TYPE_CLASS | USB_RECIP_INTERFACE)) ++ break; ++ if (ctrl->wIndex != 0) { ++ value = -EDOM; ++ break; ++ } ++ if (ctrl->wLength > MAX_COMMAND_SIZE) { ++ value = -EOVERFLOW; ++ break; ++ } ++ value = ctrl->wLength; ++ fsg->ep0req->context = received_cbi_adsc; ++ break; ++ } ++ } ++ ++ if (value == -EOPNOTSUPP) ++ VDBG(fsg, ++ "unknown class-specific control req " ++ "%02x.%02x v%04x i%04x l%u\n", ++ ctrl->bRequestType, ctrl->bRequest, ++ ctrl->wValue, ctrl->wIndex, ctrl->wLength); ++ return value; ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* Ep0 standard request handlers. These always run in_irq. */ ++ ++static int standard_setup_req(struct fsg_dev *fsg, ++ const struct usb_ctrlrequest *ctrl) ++{ ++ struct usb_request *req = fsg->ep0req; ++ int value = -EOPNOTSUPP; ++ ++ /* Usually this just stores reply data in the pre-allocated ep0 buffer, ++ * but config change events will also reconfigure hardware. */ ++ switch (ctrl->bRequest) { ++ ++ case USB_REQ_GET_DESCRIPTOR: ++ if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD | ++ USB_RECIP_DEVICE)) ++ break; ++ switch (ctrl->wValue >> 8) { ++ ++ case USB_DT_DEVICE: ++ VDBG(fsg, "get device descriptor\n"); ++ value = min(ctrl->wLength, (u16) sizeof device_desc); ++ memcpy(req->buf, &device_desc, value); ++ break; ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ case USB_DT_DEVICE_QUALIFIER: ++ VDBG(fsg, "get device qualifier\n"); ++ if (!fsg->gadget->is_dualspeed) ++ break; ++ value = min(ctrl->wLength, (u16) sizeof dev_qualifier); ++ memcpy(req->buf, &dev_qualifier, value); ++ break; ++ ++ case USB_DT_OTHER_SPEED_CONFIG: ++ VDBG(fsg, "get other-speed config descriptor\n"); ++ if (!fsg->gadget->is_dualspeed) ++ break; ++ goto get_config; ++#endif ++ case USB_DT_CONFIG: ++ VDBG(fsg, "get configuration descriptor\n"); ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ get_config: ++#endif ++ value = populate_config_buf(fsg->gadget->speed, ++ req->buf, ++ ctrl->wValue >> 8, ++ ctrl->wValue & 0xff); ++ if (value >= 0) ++ value = min(ctrl->wLength, (u16) value); ++ break; ++ ++ case USB_DT_STRING: ++ VDBG(fsg, "get string descriptor\n"); ++ ++ /* wIndex == language code */ ++ value = usb_gadget_get_string(&stringtab, ++ ctrl->wValue & 0xff, req->buf); ++ if (value >= 0) ++ value = min(ctrl->wLength, (u16) value); ++ break; ++ } ++ break; ++ ++ /* One config, two speeds */ ++ case USB_REQ_SET_CONFIGURATION: ++ if (ctrl->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD | ++ USB_RECIP_DEVICE)) ++ break; ++ VDBG(fsg, "set configuration\n"); ++ if (ctrl->wValue == CONFIG_VALUE || ctrl->wValue == 0) { ++ fsg->new_config = ctrl->wValue; ++ ++ /* Raise an exception to wipe out previous transaction ++ * state (queued bufs, etc) and set the new config. */ ++ raise_exception(fsg, FSG_STATE_CONFIG_CHANGE); ++ value = DELAYED_STATUS; ++ } ++ break; ++ case USB_REQ_GET_CONFIGURATION: ++ if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD | ++ USB_RECIP_DEVICE)) ++ break; ++ VDBG(fsg, "get configuration\n"); ++ *(u8 *) req->buf = fsg->config; ++ value = min(ctrl->wLength, (u16) 1); ++ break; ++ ++ case USB_REQ_SET_INTERFACE: ++ if (ctrl->bRequestType != (USB_DIR_OUT| USB_TYPE_STANDARD | ++ USB_RECIP_INTERFACE)) ++ break; ++ if (fsg->config && ctrl->wIndex == 0) { ++ ++ /* Raise an exception to wipe out previous transaction ++ * state (queued bufs, etc) and install the new ++ * interface altsetting. */ ++ raise_exception(fsg, FSG_STATE_INTERFACE_CHANGE); ++ value = DELAYED_STATUS; ++ } ++ break; ++ case USB_REQ_GET_INTERFACE: ++ if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD | ++ USB_RECIP_INTERFACE)) ++ break; ++ if (!fsg->config) ++ break; ++ if (ctrl->wIndex != 0) { ++ value = -EDOM; ++ break; ++ } ++ VDBG(fsg, "get interface\n"); ++ *(u8 *) req->buf = 0; ++ value = min(ctrl->wLength, (u16) 1); ++ break; ++ ++ default: ++ VDBG(fsg, ++ "unknown control req %02x.%02x v%04x i%04x l%u\n", ++ ctrl->bRequestType, ctrl->bRequest, ++ ctrl->wValue, ctrl->wIndex, ctrl->wLength); ++ } ++ ++ return value; ++} ++ ++ ++static int fsg_setup(struct usb_gadget *gadget, ++ const struct usb_ctrlrequest *ctrl) ++{ ++ struct fsg_dev *fsg = get_gadget_data(gadget); ++ int rc; ++ ++ ++fsg->ep0_req_tag; // Record arrival of a new request ++ fsg->ep0req->context = NULL; ++ fsg->ep0req->length = 0; ++ dump_msg(fsg, "ep0-setup", (u8 *) ctrl, sizeof(*ctrl)); ++ ++ if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) ++ rc = class_setup_req(fsg, ctrl); ++ else ++ rc = standard_setup_req(fsg, ctrl); ++ ++ /* Respond with data/status or defer until later? */ ++ if (rc >= 0 && rc != DELAYED_STATUS) { ++ fsg->ep0req->length = rc; ++ fsg->ep0req_name = (ctrl->bRequestType & USB_DIR_IN ? ++ "ep0-in" : "ep0-out"); ++ rc = ep0_queue(fsg); ++ } ++ ++ /* Device either stalls (rc < 0) or reports success */ ++ return rc; ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* All the following routines run in process context */ ++ ++ ++/* Use this for bulk or interrupt transfers, not ep0 */ ++static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep, ++ struct usb_request *req, volatile int *pbusy, ++ volatile enum fsg_buffer_state *state) ++{ ++ int rc; ++ ++ if (ep == fsg->bulk_in) ++ dump_msg(fsg, "bulk-in", req->buf, req->length); ++ else if (ep == fsg->intr_in) ++ dump_msg(fsg, "intr-in", req->buf, req->length); ++ *pbusy = 1; ++ *state = BUF_STATE_BUSY; ++ rc = usb_ep_queue(ep, req, GFP_KERNEL); ++ if (rc != 0) { ++ *pbusy = 0; ++ *state = BUF_STATE_EMPTY; ++ ++ /* We can't do much more than wait for a reset */ ++ ++ /* Note: currently the net2280 driver fails zero-length ++ * submissions if DMA is enabled. */ ++ if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP && ++ req->length == 0)) ++ WARN(fsg, "error in submission: %s --> %d\n", ++ ep->name, rc); ++ } ++} ++ ++ ++static int sleep_thread(struct fsg_dev *fsg) ++{ ++ int rc; ++ ++ /* Wait until a signal arrives or we are woken up */ ++ rc = wait_event_interruptible(fsg->thread_wqh, ++ fsg->thread_wakeup_needed); ++ fsg->thread_wakeup_needed = 0; ++ return (rc ? -EINTR : 0); ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int do_read(struct fsg_dev *fsg) ++{ ++ struct lun *curlun = fsg->curlun; ++ u32 lba; ++ struct fsg_buffhd *bh; ++ int rc; ++ u32 amount_left; ++ loff_t file_offset, file_offset_tmp; ++ unsigned int amount; ++ unsigned int partial_page; ++ ssize_t nread; ++ ++ /* Get the starting Logical Block Address and check that it's ++ * not too big */ ++ if (fsg->cmnd[0] == SC_READ_6) ++ lba = (fsg->cmnd[1] << 16) | get_be16(&fsg->cmnd[2]); ++ else { ++ lba = get_be32(&fsg->cmnd[2]); ++ ++ /* We allow DPO (Disable Page Out = don't save data in the ++ * cache) and FUA (Force Unit Access = don't read from the ++ * cache), but we don't implement them. */ ++ if ((fsg->cmnd[1] & ~0x18) != 0) { ++ curlun->sense_data = SS_INVALID_FIELD_IN_CDB; ++ return -EINVAL; ++ } ++ } ++ if (lba >= curlun->num_sectors) { ++ curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; ++ return -EINVAL; ++ } ++ file_offset = ((loff_t) lba) << 9; ++ ++ /* Carry out the file reads */ ++ amount_left = fsg->data_size_from_cmnd; ++ if (unlikely(amount_left == 0)) ++ return -EIO; // No default reply ++ ++ for (;;) { ++ ++ /* Figure out how much we need to read: ++ * Try to read the remaining amount. ++ * But don't read more than the buffer size. ++ * And don't try to read past the end of the file. ++ * Finally, if we're not at a page boundary, don't read past ++ * the next page. ++ * If this means reading 0 then we were asked to read past ++ * the end of file. */ ++ amount = min((unsigned int) amount_left, mod_data.buflen); ++ amount = min((loff_t) amount, ++ curlun->file_length - file_offset); ++ partial_page = file_offset & (PAGE_CACHE_SIZE - 1); ++ if (partial_page > 0) ++ amount = min(amount, (unsigned int) PAGE_CACHE_SIZE - ++ partial_page); ++ ++ /* Wait for the next buffer to become available */ ++ bh = fsg->next_buffhd_to_fill; ++ while (bh->state != BUF_STATE_EMPTY) { ++ if ((rc = sleep_thread(fsg)) != 0) ++ return rc; ++ } ++ ++ /* If we were asked to read past the end of file, ++ * end with an empty buffer. */ ++ if (amount == 0) { ++ curlun->sense_data = ++ SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; ++ curlun->sense_data_info = file_offset >> 9; ++ bh->inreq->length = 0; ++ bh->state = BUF_STATE_FULL; ++ break; ++ } ++ ++ /* Perform the read */ ++ file_offset_tmp = file_offset; ++ nread = curlun->filp->f_op->read(curlun->filp, ++ (char *) bh->buf, ++ amount, &file_offset_tmp); ++ VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, ++ (unsigned long long) file_offset, ++ (int) nread); ++ if (signal_pending(current)) ++ return -EINTR; ++ ++ if (nread < 0) { ++ LDBG(curlun, "error in file read: %d\n", ++ (int) nread); ++ nread = 0; ++ } else if (nread < amount) { ++ LDBG(curlun, "partial file read: %d/%u\n", ++ (int) nread, amount); ++ nread -= (nread & 511); // Round down to a block ++ } ++ file_offset += nread; ++ amount_left -= nread; ++ fsg->residue -= nread; ++ bh->inreq->length = nread; ++ bh->state = BUF_STATE_FULL; ++ ++ /* If an error occurred, report it and its position */ ++ if (nread < amount) { ++ curlun->sense_data = SS_UNRECOVERED_READ_ERROR; ++ curlun->sense_data_info = file_offset >> 9; ++ break; ++ } ++ ++ if (amount_left == 0) ++ break; // No more left to read ++ ++ /* Send this buffer and go read some more */ ++ bh->inreq->zero = 0; ++ start_transfer(fsg, fsg->bulk_in, bh->inreq, ++ &bh->inreq_busy, &bh->state); ++ fsg->next_buffhd_to_fill = bh->next; ++ } ++ ++ return -EIO; // No default reply ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int do_write(struct fsg_dev *fsg) ++{ ++ struct lun *curlun = fsg->curlun; ++ u32 lba; ++ struct fsg_buffhd *bh; ++ int get_some_more; ++ u32 amount_left_to_req, amount_left_to_write; ++ loff_t usb_offset, file_offset, file_offset_tmp; ++ unsigned int amount; ++ unsigned int partial_page; ++ ssize_t nwritten; ++ int rc; ++ ++ if (curlun->ro) { ++ curlun->sense_data = SS_WRITE_PROTECTED; ++ return -EINVAL; ++ } ++ curlun->filp->f_flags &= ~O_SYNC; // Default is not to wait ++ ++ /* Get the starting Logical Block Address and check that it's ++ * not too big */ ++ if (fsg->cmnd[0] == SC_WRITE_6) ++ lba = (fsg->cmnd[1] << 16) | get_be16(&fsg->cmnd[2]); ++ else { ++ lba = get_be32(&fsg->cmnd[2]); ++ ++ /* We allow DPO (Disable Page Out = don't save data in the ++ * cache) and FUA (Force Unit Access = write directly to the ++ * medium). We don't implement DPO; we implement FUA by ++ * performing synchronous output. */ ++ if ((fsg->cmnd[1] & ~0x18) != 0) { ++ curlun->sense_data = SS_INVALID_FIELD_IN_CDB; ++ return -EINVAL; ++ } ++ if (fsg->cmnd[1] & 0x08) // FUA ++ curlun->filp->f_flags |= O_SYNC; ++ } ++ if (lba >= curlun->num_sectors) { ++ curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; ++ return -EINVAL; ++ } ++ ++ /* Carry out the file writes */ ++ get_some_more = 1; ++ file_offset = usb_offset = ((loff_t) lba) << 9; ++ amount_left_to_req = amount_left_to_write = fsg->data_size_from_cmnd; ++ ++ while (amount_left_to_write > 0) { ++ ++ /* Queue a request for more data from the host */ ++ bh = fsg->next_buffhd_to_fill; ++ if (bh->state == BUF_STATE_EMPTY && get_some_more) { ++ ++ /* Figure out how much we want to get: ++ * Try to get the remaining amount. ++ * But don't get more than the buffer size. ++ * And don't try to go past the end of the file. ++ * If we're not at a page boundary, ++ * don't go past the next page. ++ * If this means getting 0, then we were asked ++ * to write past the end of file. ++ * Finally, round down to a block boundary. */ ++ amount = min(amount_left_to_req, mod_data.buflen); ++ amount = min((loff_t) amount, curlun->file_length - ++ usb_offset); ++ partial_page = usb_offset & (PAGE_CACHE_SIZE - 1); ++ if (partial_page > 0) ++ amount = min(amount, ++ (unsigned int) PAGE_CACHE_SIZE - partial_page); ++ ++ if (amount == 0) { ++ get_some_more = 0; ++ curlun->sense_data = ++ SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; ++ curlun->sense_data_info = usb_offset >> 9; ++ continue; ++ } ++ amount -= (amount & 511); ++ if (amount == 0) { ++ ++ /* Why were we were asked to transfer a ++ * partial block? */ ++ get_some_more = 0; ++ continue; ++ } ++ ++ /* Get the next buffer */ ++ usb_offset += amount; ++ fsg->usb_amount_left -= amount; ++ amount_left_to_req -= amount; ++ if (amount_left_to_req == 0) ++ get_some_more = 0; ++ ++ /* amount is always divisible by 512, hence by ++ * the bulk-out maxpacket size */ ++ bh->outreq->length = bh->bulk_out_intended_length = ++ amount; ++ start_transfer(fsg, fsg->bulk_out, bh->outreq, ++ &bh->outreq_busy, &bh->state); ++ fsg->next_buffhd_to_fill = bh->next; ++ continue; ++ } ++ ++ /* Write the received data to the backing file */ ++ bh = fsg->next_buffhd_to_drain; ++ if (bh->state == BUF_STATE_EMPTY && !get_some_more) ++ break; // We stopped early ++ if (bh->state == BUF_STATE_FULL) { ++ fsg->next_buffhd_to_drain = bh->next; ++ bh->state = BUF_STATE_EMPTY; ++ ++ /* Did something go wrong with the transfer? */ ++ if (bh->outreq->status != 0) { ++ curlun->sense_data = SS_COMMUNICATION_FAILURE; ++ curlun->sense_data_info = file_offset >> 9; ++ break; ++ } ++ ++ amount = bh->outreq->actual; ++ if (curlun->file_length - file_offset < amount) { ++ LERROR(curlun, ++ "write %u @ %llu beyond end %llu\n", ++ amount, (unsigned long long) file_offset, ++ (unsigned long long) curlun->file_length); ++ amount = curlun->file_length - file_offset; ++ } ++ ++ /* Perform the write */ ++ file_offset_tmp = file_offset; ++ nwritten = curlun->filp->f_op->write(curlun->filp, ++ (char *) bh->buf, ++ amount, &file_offset_tmp); ++ VLDBG(curlun, "file write %u @ %llu -> %d\n", amount, ++ (unsigned long long) file_offset, ++ (int) nwritten); ++ if (signal_pending(current)) ++ return -EINTR; // Interrupted! ++ ++ if (nwritten < 0) { ++ LDBG(curlun, "error in file write: %d\n", ++ (int) nwritten); ++ nwritten = 0; ++ } else if (nwritten < amount) { ++ LDBG(curlun, "partial file write: %d/%u\n", ++ (int) nwritten, amount); ++ nwritten -= (nwritten & 511); ++ // Round down to a block ++ } ++ file_offset += nwritten; ++ amount_left_to_write -= nwritten; ++ fsg->residue -= nwritten; ++ ++ /* If an error occurred, report it and its position */ ++ if (nwritten < amount) { ++ curlun->sense_data = SS_WRITE_ERROR; ++ curlun->sense_data_info = file_offset >> 9; ++ break; ++ } ++ ++ /* Did the host decide to stop early? */ ++ if (bh->outreq->actual != bh->outreq->length) { ++ fsg->short_packet_received = 1; ++ break; ++ } ++ continue; ++ } ++ ++ /* Wait for something to happen */ ++ if ((rc = sleep_thread(fsg)) != 0) ++ return rc; ++ } ++ ++ return -EIO; // No default reply ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* Sync the file data, don't bother with the metadata. ++ * This code was copied from fs/buffer.c:sys_fdatasync(). */ ++static int fsync_sub(struct lun *curlun) ++{ ++ struct file *filp = curlun->filp; ++ struct inode *inode; ++ int rc, err; ++ ++ if (curlun->ro || !filp) ++ return 0; ++ if (!filp->f_op->fsync) ++ return -EINVAL; ++ ++ inode = filp->f_dentry->d_inode; ++ down(&inode->i_sem); ++ rc = filemap_fdatasync(inode->i_mapping); ++ err = filp->f_op->fsync(filp, filp->f_dentry, 1); ++ if (!rc) ++ rc = err; ++ err = filemap_fdatawait(inode->i_mapping); ++ if (!rc) ++ rc = err; ++ up(&inode->i_sem); ++ VLDBG(curlun, "fdatasync -> %d\n", rc); ++ return rc; ++} ++ ++static void fsync_all(struct fsg_dev *fsg) ++{ ++ int i; ++ ++ for (i = 0; i < fsg->nluns; ++i) ++ fsync_sub(&fsg->luns[i]); ++} ++ ++static int do_synchronize_cache(struct fsg_dev *fsg) ++{ ++ struct lun *curlun = fsg->curlun; ++ int rc; ++ ++ /* We ignore the requested LBA and write out all file's ++ * dirty data buffers. */ ++ rc = fsync_sub(curlun); ++ if (rc) ++ curlun->sense_data = SS_WRITE_ERROR; ++ return 0; ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void invalidate_sub(struct lun *curlun) ++{ ++ struct file *filp = curlun->filp; ++ struct inode *inode = filp->f_dentry->d_inode; ++ ++ invalidate_inode_pages(inode); ++} ++ ++static int do_verify(struct fsg_dev *fsg) ++{ ++ struct lun *curlun = fsg->curlun; ++ u32 lba; ++ u32 verification_length; ++ struct fsg_buffhd *bh = fsg->next_buffhd_to_fill; ++ loff_t file_offset, file_offset_tmp; ++ u32 amount_left; ++ unsigned int amount; ++ ssize_t nread; ++ ++ /* Get the starting Logical Block Address and check that it's ++ * not too big */ ++ lba = get_be32(&fsg->cmnd[2]); ++ if (lba >= curlun->num_sectors) { ++ curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; ++ return -EINVAL; ++ } ++ ++ /* We allow DPO (Disable Page Out = don't save data in the ++ * cache) but we don't implement it. */ ++ if ((fsg->cmnd[1] & ~0x10) != 0) { ++ curlun->sense_data = SS_INVALID_FIELD_IN_CDB; ++ return -EINVAL; ++ } ++ ++ verification_length = get_be16(&fsg->cmnd[7]); ++ if (unlikely(verification_length == 0)) ++ return -EIO; // No default reply ++ ++ /* Prepare to carry out the file verify */ ++ amount_left = verification_length << 9; ++ file_offset = ((loff_t) lba) << 9; ++ ++ /* Write out all the dirty buffers before invalidating them */ ++ fsync_sub(curlun); ++ if (signal_pending(current)) ++ return -EINTR; ++ ++ invalidate_sub(curlun); ++ if (signal_pending(current)) ++ return -EINTR; ++ ++ /* Just try to read the requested blocks */ ++ while (amount_left > 0) { ++ ++ /* Figure out how much we need to read: ++ * Try to read the remaining amount, but not more than ++ * the buffer size. ++ * And don't try to read past the end of the file. ++ * If this means reading 0 then we were asked to read ++ * past the end of file. */ ++ amount = min((unsigned int) amount_left, mod_data.buflen); ++ amount = min((loff_t) amount, ++ curlun->file_length - file_offset); ++ if (amount == 0) { ++ curlun->sense_data = ++ SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; ++ curlun->sense_data_info = file_offset >> 9; ++ break; ++ } ++ ++ /* Perform the read */ ++ file_offset_tmp = file_offset; ++ nread = curlun->filp->f_op->read(curlun->filp, ++ (char *) bh->buf, ++ amount, &file_offset_tmp); ++ VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, ++ (unsigned long long) file_offset, ++ (int) nread); ++ if (signal_pending(current)) ++ return -EINTR; ++ ++ if (nread < 0) { ++ LDBG(curlun, "error in file verify: %d\n", ++ (int) nread); ++ nread = 0; ++ } else if (nread < amount) { ++ LDBG(curlun, "partial file verify: %d/%u\n", ++ (int) nread, amount); ++ nread -= (nread & 511); // Round down to a sector ++ } ++ if (nread == 0) { ++ curlun->sense_data = SS_UNRECOVERED_READ_ERROR; ++ curlun->sense_data_info = file_offset >> 9; ++ break; ++ } ++ file_offset += nread; ++ amount_left -= nread; ++ } ++ return 0; ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int do_inquiry(struct fsg_dev *fsg, struct fsg_buffhd *bh) ++{ ++ u8 *buf = (u8 *) bh->buf; ++ ++ static char vendor_id[] = "Linux "; ++ static char product_id[] = "File-Stor Gadget"; ++ ++ if (!fsg->curlun) { // Unsupported LUNs are okay ++ fsg->bad_lun_okay = 1; ++ memset(buf, 0, 36); ++ buf[0] = 0x7f; // Unsupported, no device-type ++ return 36; ++ } ++ ++ memset(buf, 0, 8); // Non-removable, direct-access device ++ if (mod_data.removable) ++ buf[1] = 0x80; ++ buf[2] = 2; // ANSI SCSI level 2 ++ buf[3] = 2; // SCSI-2 INQUIRY data format ++ buf[4] = 31; // Additional length ++ // No special options ++ sprintf(buf + 8, "%-8s%-16s%04x", vendor_id, product_id, ++ mod_data.release); ++ return 36; ++} ++ ++ ++static int do_request_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh) ++{ ++ struct lun *curlun = fsg->curlun; ++ u8 *buf = (u8 *) bh->buf; ++ u32 sd, sdinfo; ++ ++ /* ++ * From the SCSI-2 spec., section 7.9 (Unit attention condition): ++ * ++ * If a REQUEST SENSE command is received from an initiator ++ * with a pending unit attention condition (before the target ++ * generates the contingent allegiance condition), then the ++ * target shall either: ++ * a) report any pending sense data and preserve the unit ++ * attention condition on the logical unit, or, ++ * b) report the unit attention condition, may discard any ++ * pending sense data, and clear the unit attention ++ * condition on the logical unit for that initiator. ++ * ++ * FSG normally uses option a); enable this code to use option b). ++ */ ++#if 0 ++ if (curlun && curlun->unit_attention_data != SS_NO_SENSE) { ++ curlun->sense_data = curlun->unit_attention_data; ++ curlun->unit_attention_data = SS_NO_SENSE; ++ } ++#endif ++ ++ if (!curlun) { // Unsupported LUNs are okay ++ fsg->bad_lun_okay = 1; ++ sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; ++ sdinfo = 0; ++ } else { ++ sd = curlun->sense_data; ++ sdinfo = curlun->sense_data_info; ++ curlun->sense_data = SS_NO_SENSE; ++ curlun->sense_data_info = 0; ++ } ++ ++ memset(buf, 0, 18); ++ buf[0] = 0x80 | 0x70; // Valid, current error ++ buf[2] = SK(sd); ++ put_be32(&buf[3], sdinfo); // Sense information ++ buf[7] = 18 - 8; // Additional sense length ++ buf[12] = ASC(sd); ++ buf[13] = ASCQ(sd); ++ return 18; ++} ++ ++ ++static int do_read_capacity(struct fsg_dev *fsg, struct fsg_buffhd *bh) ++{ ++ struct lun *curlun = fsg->curlun; ++ u32 lba = get_be32(&fsg->cmnd[2]); ++ int pmi = fsg->cmnd[8]; ++ u8 *buf = (u8 *) bh->buf; ++ ++ /* Check the PMI and LBA fields */ ++ if (pmi > 1 || (pmi == 0 && lba != 0)) { ++ curlun->sense_data = SS_INVALID_FIELD_IN_CDB; ++ return -EINVAL; ++ } ++ ++ put_be32(&buf[0], curlun->num_sectors - 1); // Max logical block ++ put_be32(&buf[4], 512); // Block length ++ return 8; ++} ++ ++ ++static int do_mode_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh) ++{ ++ struct lun *curlun = fsg->curlun; ++ int mscmnd = fsg->cmnd[0]; ++ u8 *buf = (u8 *) bh->buf; ++ u8 *buf0 = buf; ++ int pc, page_code; ++ int changeable_values, all_pages; ++ int valid_page = 0; ++ int len, limit; ++ ++ if ((fsg->cmnd[1] & ~0x08) != 0) { // Mask away DBD ++ curlun->sense_data = SS_INVALID_FIELD_IN_CDB; ++ return -EINVAL; ++ } ++ pc = fsg->cmnd[2] >> 6; ++ page_code = fsg->cmnd[2] & 0x3f; ++ if (pc == 3) { ++ curlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED; ++ return -EINVAL; ++ } ++ changeable_values = (pc == 1); ++ all_pages = (page_code == 0x3f); ++ ++ /* Write the mode parameter header. Fixed values are: default ++ * medium type, no cache control (DPOFUA), and no block descriptors. ++ * The only variable value is the WriteProtect bit. We will fill in ++ * the mode data length later. */ ++ memset(buf, 0, 8); ++ if (mscmnd == SC_MODE_SENSE_6) { ++ buf[2] = (curlun->ro ? 0x80 : 0x00); // WP, DPOFUA ++ buf += 4; ++ limit = 255; ++ } else { // SC_MODE_SENSE_10 ++ buf[3] = (curlun->ro ? 0x80 : 0x00); // WP, DPOFUA ++ buf += 8; ++ limit = 65535; // Should really be mod_data.buflen ++ } ++ ++ /* No block descriptors */ ++ ++ /* The mode pages, in numerical order. The only page we support ++ * is the Caching page. */ ++ if (page_code == 0x08 || all_pages) { ++ valid_page = 1; ++ buf[0] = 0x08; // Page code ++ buf[1] = 10; // Page length ++ memset(buf+2, 0, 10); // None of the fields are changeable ++ ++ if (!changeable_values) { ++ buf[2] = 0x04; // Write cache enable, ++ // Read cache not disabled ++ // No cache retention priorities ++ put_be16(&buf[4], 0xffff); // Don't disable prefetch ++ // Minimum prefetch = 0 ++ put_be16(&buf[8], 0xffff); // Maximum prefetch ++ put_be16(&buf[10], 0xffff); // Maximum prefetch ceiling ++ } ++ buf += 12; ++ } ++ ++ /* Check that a valid page was requested and the mode data length ++ * isn't too long. */ ++ len = buf - buf0; ++ if (!valid_page || len > limit) { ++ curlun->sense_data = SS_INVALID_FIELD_IN_CDB; ++ return -EINVAL; ++ } ++ ++ /* Store the mode data length */ ++ if (mscmnd == SC_MODE_SENSE_6) ++ buf0[0] = len - 1; ++ else ++ put_be16(buf0, len - 2); ++ return len; ++} ++ ++ ++static int do_start_stop(struct fsg_dev *fsg) ++{ ++ struct lun *curlun = fsg->curlun; ++ int loej, start; ++ ++ if (!mod_data.removable) { ++ curlun->sense_data = SS_INVALID_COMMAND; ++ return -EINVAL; ++ } ++ ++ // int immed = fsg->cmnd[1] & 0x01; ++ loej = fsg->cmnd[4] & 0x02; ++ start = fsg->cmnd[4] & 0x01; ++ ++#ifdef CONFIG_USB_FILE_STORAGE_TEST ++ if ((fsg->cmnd[1] & ~0x01) != 0 || // Mask away Immed ++ (fsg->cmnd[4] & ~0x03) != 0) { // Mask LoEj, Start ++ curlun->sense_data = SS_INVALID_FIELD_IN_CDB; ++ return -EINVAL; ++ } ++ ++ if (!start) { ++ ++ /* Are we allowed to unload the media? */ ++ if (curlun->prevent_medium_removal) { ++ LDBG(curlun, "unload attempt prevented\n"); ++ curlun->sense_data = SS_MEDIUM_REMOVAL_PREVENTED; ++ return -EINVAL; ++ } ++ if (loej) { // Simulate an unload/eject ++ up_read(&fsg->filesem); ++ down_write(&fsg->filesem); ++ close_backing_file(curlun); ++ up_write(&fsg->filesem); ++ down_read(&fsg->filesem); ++ } ++ } else { ++ ++ /* Our emulation doesn't support mounting; the medium is ++ * available for use as soon as it is loaded. */ ++ if (!backing_file_is_open(curlun)) { ++ curlun->sense_data = SS_MEDIUM_NOT_PRESENT; ++ return -EINVAL; ++ } ++ } ++#endif ++ return 0; ++} ++ ++ ++static int do_prevent_allow(struct fsg_dev *fsg) ++{ ++ struct lun *curlun = fsg->curlun; ++ int prevent; ++ ++ if (!mod_data.removable) { ++ curlun->sense_data = SS_INVALID_COMMAND; ++ return -EINVAL; ++ } ++ ++ prevent = fsg->cmnd[4] & 0x01; ++ if ((fsg->cmnd[4] & ~0x01) != 0) { // Mask away Prevent ++ curlun->sense_data = SS_INVALID_FIELD_IN_CDB; ++ return -EINVAL; ++ } ++ ++ if (curlun->prevent_medium_removal && !prevent) ++ fsync_sub(curlun); ++ curlun->prevent_medium_removal = prevent; ++ return 0; ++} ++ ++ ++static int do_read_format_capacities(struct fsg_dev *fsg, ++ struct fsg_buffhd *bh) ++{ ++ struct lun *curlun = fsg->curlun; ++ u8 *buf = (u8 *) bh->buf; ++ ++ buf[0] = buf[1] = buf[2] = 0; ++ buf[3] = 8; // Only the Current/Maximum Capacity Descriptor ++ buf += 4; ++ ++ put_be32(&buf[0], curlun->num_sectors); // Number of blocks ++ put_be32(&buf[4], 512); // Block length ++ buf[4] = 0x02; // Current capacity ++ return 12; ++} ++ ++ ++static int do_mode_select(struct fsg_dev *fsg, struct fsg_buffhd *bh) ++{ ++ struct lun *curlun = fsg->curlun; ++ ++ /* We don't support MODE SELECT */ ++ curlun->sense_data = SS_INVALID_COMMAND; ++ return -EINVAL; ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int halt_bulk_in_endpoint(struct fsg_dev *fsg) ++{ ++ int rc; ++ ++ rc = fsg_set_halt(fsg, fsg->bulk_in); ++ if (rc == -EAGAIN) ++ VDBG(fsg, "delayed bulk-in endpoint halt\n"); ++ while (rc != 0) { ++ if (rc != -EAGAIN) { ++ WARN(fsg, "usb_ep_set_halt -> %d\n", rc); ++ rc = 0; ++ break; ++ } ++ ++ /* Wait for a short time and then try again */ ++ set_current_state(TASK_INTERRUPTIBLE); ++ if (schedule_timeout(HZ / 10) != 0) ++ return -EINTR; ++ rc = usb_ep_set_halt(fsg->bulk_in); ++ } ++ return rc; ++} ++ ++static int pad_with_zeros(struct fsg_dev *fsg) ++{ ++ struct fsg_buffhd *bh = fsg->next_buffhd_to_fill; ++ u32 nkeep = bh->inreq->length; ++ u32 nsend; ++ int rc; ++ ++ bh->state = BUF_STATE_EMPTY; // For the first iteration ++ fsg->usb_amount_left = nkeep + fsg->residue; ++ while (fsg->usb_amount_left > 0) { ++ ++ /* Wait for the next buffer to be free */ ++ while (bh->state != BUF_STATE_EMPTY) { ++ if ((rc = sleep_thread(fsg)) != 0) ++ return rc; ++ } ++ ++ nsend = min(fsg->usb_amount_left, (u32) mod_data.buflen); ++ memset(bh->buf + nkeep, 0, nsend - nkeep); ++ bh->inreq->length = nsend; ++ bh->inreq->zero = 0; ++ start_transfer(fsg, fsg->bulk_in, bh->inreq, ++ &bh->inreq_busy, &bh->state); ++ bh = fsg->next_buffhd_to_fill = bh->next; ++ fsg->usb_amount_left -= nsend; ++ nkeep = 0; ++ } ++ return 0; ++} ++ ++static int throw_away_data(struct fsg_dev *fsg) ++{ ++ struct fsg_buffhd *bh; ++ u32 amount; ++ int rc; ++ ++ while ((bh = fsg->next_buffhd_to_drain)->state != BUF_STATE_EMPTY || ++ fsg->usb_amount_left > 0) { ++ ++ /* Throw away the data in a filled buffer */ ++ if (bh->state == BUF_STATE_FULL) { ++ bh->state = BUF_STATE_EMPTY; ++ fsg->next_buffhd_to_drain = bh->next; ++ ++ /* A short packet or an error ends everything */ ++ if (bh->outreq->actual != bh->outreq->length || ++ bh->outreq->status != 0) { ++ raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT); ++ return -EINTR; ++ } ++ continue; ++ } ++ ++ /* Try to submit another request if we need one */ ++ bh = fsg->next_buffhd_to_fill; ++ if (bh->state == BUF_STATE_EMPTY && fsg->usb_amount_left > 0) { ++ amount = min(fsg->usb_amount_left, ++ (u32) mod_data.buflen); ++ ++ /* amount is always divisible by 512, hence by ++ * the bulk-out maxpacket size */ ++ bh->outreq->length = bh->bulk_out_intended_length = ++ amount; ++ start_transfer(fsg, fsg->bulk_out, bh->outreq, ++ &bh->outreq_busy, &bh->state); ++ fsg->next_buffhd_to_fill = bh->next; ++ fsg->usb_amount_left -= amount; ++ continue; ++ } ++ ++ /* Otherwise wait for something to happen */ ++ if ((rc = sleep_thread(fsg)) != 0) ++ return rc; ++ } ++ return 0; ++} ++ ++ ++static int finish_reply(struct fsg_dev *fsg) ++{ ++ struct fsg_buffhd *bh = fsg->next_buffhd_to_fill; ++ int rc = 0; ++ ++ switch (fsg->data_dir) { ++ case DATA_DIR_NONE: ++ break; // Nothing to send ++ ++ /* If we don't know whether the host wants to read or write, ++ * this must be CB or CBI with an unknown command. We mustn't ++ * try to send or receive any data. So stall both bulk pipes ++ * if we can and wait for a reset. */ ++ case DATA_DIR_UNKNOWN: ++ if (mod_data.can_stall) { ++ fsg_set_halt(fsg, fsg->bulk_out); ++ rc = halt_bulk_in_endpoint(fsg); ++ } ++ break; ++ ++ /* All but the last buffer of data must have already been sent */ ++ case DATA_DIR_TO_HOST: ++ if (fsg->data_size == 0) ++ ; // Nothing to send ++ ++ /* If there's no residue, simply send the last buffer */ ++ else if (fsg->residue == 0) { ++ bh->inreq->zero = 0; ++ start_transfer(fsg, fsg->bulk_in, bh->inreq, ++ &bh->inreq_busy, &bh->state); ++ fsg->next_buffhd_to_fill = bh->next; ++ } ++ ++ /* There is a residue. For CB and CBI, simply mark the end ++ * of the data with a short packet. However, if we are ++ * allowed to stall, there was no data at all (residue == ++ * data_size), and the command failed (invalid LUN or ++ * sense data is set), then halt the bulk-in endpoint ++ * instead. */ ++ else if (!transport_is_bbb()) { ++ if (mod_data.can_stall && ++ fsg->residue == fsg->data_size && ++ (!fsg->curlun || fsg->curlun->sense_data != SS_NO_SENSE)) { ++ bh->state = BUF_STATE_EMPTY; ++ rc = halt_bulk_in_endpoint(fsg); ++ } else { ++ bh->inreq->zero = 1; ++ start_transfer(fsg, fsg->bulk_in, bh->inreq, ++ &bh->inreq_busy, &bh->state); ++ fsg->next_buffhd_to_fill = bh->next; ++ } ++ } ++ ++ /* For Bulk-only, if we're allowed to stall then send the ++ * short packet and halt the bulk-in endpoint. If we can't ++ * stall, pad out the remaining data with 0's. */ ++ else { ++ if (mod_data.can_stall) { ++ bh->inreq->zero = 1; ++ start_transfer(fsg, fsg->bulk_in, bh->inreq, ++ &bh->inreq_busy, &bh->state); ++ fsg->next_buffhd_to_fill = bh->next; ++ rc = halt_bulk_in_endpoint(fsg); ++ } else ++ rc = pad_with_zeros(fsg); ++ } ++ break; ++ ++ /* We have processed all we want from the data the host has sent. ++ * There may still be outstanding bulk-out requests. */ ++ case DATA_DIR_FROM_HOST: ++ if (fsg->residue == 0) ++ ; // Nothing to receive ++ ++ /* Did the host stop sending unexpectedly early? */ ++ else if (fsg->short_packet_received) { ++ raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT); ++ rc = -EINTR; ++ } ++ ++ /* We haven't processed all the incoming data. If we are ++ * allowed to stall, halt the bulk-out endpoint and cancel ++ * any outstanding requests. */ ++ else if (mod_data.can_stall) { ++ fsg_set_halt(fsg, fsg->bulk_out); ++ raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT); ++ rc = -EINTR; ++ } ++ ++ /* We can't stall. Read in the excess data and throw it ++ * all away. */ ++ else ++ rc = throw_away_data(fsg); ++ break; ++ } ++ return rc; ++} ++ ++ ++static int send_status(struct fsg_dev *fsg) ++{ ++ struct lun *curlun = fsg->curlun; ++ struct fsg_buffhd *bh; ++ int rc; ++ u8 status = USB_STATUS_PASS; ++ u32 sd, sdinfo = 0; ++ ++ /* Wait for the next buffer to become available */ ++ bh = fsg->next_buffhd_to_fill; ++ while (bh->state != BUF_STATE_EMPTY) { ++ if ((rc = sleep_thread(fsg)) != 0) ++ return rc; ++ } ++ ++ if (curlun) { ++ sd = curlun->sense_data; ++ sdinfo = curlun->sense_data_info; ++ } else if (fsg->bad_lun_okay) ++ sd = SS_NO_SENSE; ++ else ++ sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; ++ ++ if (fsg->phase_error) { ++ DBG(fsg, "sending phase-error status\n"); ++ status = USB_STATUS_PHASE_ERROR; ++ sd = SS_INVALID_COMMAND; ++ } else if (sd != SS_NO_SENSE) { ++ DBG(fsg, "sending command-failure status\n"); ++ status = USB_STATUS_FAIL; ++ VDBG(fsg, " sense data: SK x%02x, ASC x%02x, ASCQ x%02x;" ++ " info x%x\n", ++ SK(sd), ASC(sd), ASCQ(sd), sdinfo); ++ } ++ ++ if (transport_is_bbb()) { ++ struct bulk_cs_wrap *csw = (struct bulk_cs_wrap *) bh->buf; ++ ++ /* Store and send the Bulk-only CSW */ ++ csw->Signature = __constant_cpu_to_le32(USB_BULK_CS_SIG); ++ csw->Tag = fsg->tag; ++ csw->Residue = cpu_to_le32(fsg->residue); ++ csw->Status = status; ++ ++ bh->inreq->length = USB_BULK_CS_WRAP_LEN; ++ bh->inreq->zero = 0; ++ start_transfer(fsg, fsg->bulk_in, bh->inreq, ++ &bh->inreq_busy, &bh->state); ++ ++ } else if (mod_data.transport_type == USB_PR_CB) { ++ ++ /* Control-Bulk transport has no status stage! */ ++ return 0; ++ ++ } else { // USB_PR_CBI ++ struct interrupt_data *buf = (struct interrupt_data *) ++ bh->buf; ++ ++ /* Store and send the Interrupt data. UFI sends the ASC ++ * and ASCQ bytes. Everything else sends a Type (which ++ * is always 0) and the status Value. */ ++ if (mod_data.protocol_type == USB_SC_UFI) { ++ buf->bType = ASC(sd); ++ buf->bValue = ASCQ(sd); ++ } else { ++ buf->bType = 0; ++ buf->bValue = status; ++ } ++ fsg->intreq->length = CBI_INTERRUPT_DATA_LEN; ++ ++ fsg->intr_buffhd = bh; // Point to the right buffhd ++ fsg->intreq->buf = bh->inreq->buf; ++ fsg->intreq->dma = bh->inreq->dma; ++ fsg->intreq->context = bh; ++ start_transfer(fsg, fsg->intr_in, fsg->intreq, ++ &fsg->intreq_busy, &bh->state); ++ } ++ ++ fsg->next_buffhd_to_fill = bh->next; ++ return 0; ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* Check whether the command is properly formed and whether its data size ++ * and direction agree with the values we already have. */ ++static int check_command(struct fsg_dev *fsg, int cmnd_size, ++ enum data_direction data_dir, unsigned int mask, ++ int needs_medium, const char *name) ++{ ++ int i; ++ int lun = fsg->cmnd[1] >> 5; ++ static const char dirletter[4] = {'u', 'o', 'i', 'n'}; ++ char hdlen[20]; ++ struct lun *curlun; ++ ++ /* Adjust the expected cmnd_size for protocol encapsulation padding. ++ * Transparent SCSI doesn't pad. */ ++ if (protocol_is_scsi()) ++ ; ++ ++ /* There's some disagreement as to whether RBC pads commands or not. ++ * We'll play it safe and accept either form. */ ++ else if (mod_data.protocol_type == USB_SC_RBC) { ++ if (fsg->cmnd_size == 12) ++ cmnd_size = 12; ++ ++ /* All the other protocols pad to 12 bytes */ ++ } else ++ cmnd_size = 12; ++ ++ hdlen[0] = 0; ++ if (fsg->data_dir != DATA_DIR_UNKNOWN) ++ sprintf(hdlen, ", H%c=%u", dirletter[(int) fsg->data_dir], ++ fsg->data_size); ++ VDBG(fsg, "SCSI command: %s; Dc=%d, D%c=%u; Hc=%d%s\n", ++ name, cmnd_size, dirletter[(int) data_dir], ++ fsg->data_size_from_cmnd, fsg->cmnd_size, hdlen); ++ ++ /* We can't reply at all until we know the correct data direction ++ * and size. */ ++ if (fsg->data_size_from_cmnd == 0) ++ data_dir = DATA_DIR_NONE; ++ if (fsg->data_dir == DATA_DIR_UNKNOWN) { // CB or CBI ++ fsg->data_dir = data_dir; ++ fsg->data_size = fsg->data_size_from_cmnd; ++ ++ } else { // Bulk-only ++ if (fsg->data_size < fsg->data_size_from_cmnd) { ++ ++ /* Host data size < Device data size is a phase error. ++ * Carry out the command, but only transfer as much ++ * as we are allowed. */ ++ fsg->data_size_from_cmnd = fsg->data_size; ++ fsg->phase_error = 1; ++ } ++ } ++ fsg->residue = fsg->usb_amount_left = fsg->data_size; ++ ++ /* Conflicting data directions is a phase error */ ++ if (fsg->data_dir != data_dir && fsg->data_size_from_cmnd > 0) ++ goto phase_error; ++ ++ /* Verify the length of the command itself */ ++ if (cmnd_size != fsg->cmnd_size) { ++ ++ /* Special case workaround: MS-Windows issues REQUEST SENSE ++ * with cbw->Length == 12 (it should be 6). */ ++ if (fsg->cmnd[0] == SC_REQUEST_SENSE && fsg->cmnd_size == 12) ++ cmnd_size = fsg->cmnd_size; ++ else ++ goto phase_error; ++ } ++ ++ /* Check that the LUN values are oonsistent */ ++ if (transport_is_bbb()) { ++ if (fsg->lun != lun) ++ DBG(fsg, "using LUN %d from CBW, " ++ "not LUN %d from CDB\n", ++ fsg->lun, lun); ++ } else ++ fsg->lun = lun; // Use LUN from the command ++ ++ /* Check the LUN */ ++ if (fsg->lun >= 0 && fsg->lun < fsg->nluns) { ++ fsg->curlun = curlun = &fsg->luns[fsg->lun]; ++ if (fsg->cmnd[0] != SC_REQUEST_SENSE) { ++ curlun->sense_data = SS_NO_SENSE; ++ curlun->sense_data_info = 0; ++ } ++ } else { ++ fsg->curlun = curlun = NULL; ++ fsg->bad_lun_okay = 0; ++ ++ /* INQUIRY and REQUEST SENSE commands are explicitly allowed ++ * to use unsupported LUNs; all others may not. */ ++ if (fsg->cmnd[0] != SC_INQUIRY && ++ fsg->cmnd[0] != SC_REQUEST_SENSE) { ++ DBG(fsg, "unsupported LUN %d\n", fsg->lun); ++ return -EINVAL; ++ } ++ } ++ ++ /* If a unit attention condition exists, only INQUIRY and ++ * REQUEST SENSE commands are allowed; anything else must fail. */ ++ if (curlun && curlun->unit_attention_data != SS_NO_SENSE && ++ fsg->cmnd[0] != SC_INQUIRY && ++ fsg->cmnd[0] != SC_REQUEST_SENSE) { ++ curlun->sense_data = curlun->unit_attention_data; ++ curlun->unit_attention_data = SS_NO_SENSE; ++ return -EINVAL; ++ } ++ ++ /* Check that only command bytes listed in the mask are non-zero */ ++ fsg->cmnd[1] &= 0x1f; // Mask away the LUN ++ for (i = 1; i < cmnd_size; ++i) { ++ if (fsg->cmnd[i] && !(mask & (1 << i))) { ++ if (curlun) ++ curlun->sense_data = SS_INVALID_FIELD_IN_CDB; ++ return -EINVAL; ++ } ++ } ++ ++ /* If the medium isn't mounted and the command needs to access ++ * it, return an error. */ ++ if (curlun && !backing_file_is_open(curlun) && needs_medium) { ++ curlun->sense_data = SS_MEDIUM_NOT_PRESENT; ++ return -EINVAL; ++ } ++ ++ return 0; ++ ++phase_error: ++ fsg->phase_error = 1; ++ return -EINVAL; ++} ++ ++ ++static int do_scsi_command(struct fsg_dev *fsg) ++{ ++ struct fsg_buffhd *bh; ++ int rc; ++ int reply = -EINVAL; ++ int i; ++ static char unknown[16]; ++ ++ dump_cdb(fsg); ++ ++ /* Wait for the next buffer to become available for data or status */ ++ bh = fsg->next_buffhd_to_drain = fsg->next_buffhd_to_fill; ++ while (bh->state != BUF_STATE_EMPTY) { ++ if ((rc = sleep_thread(fsg)) != 0) ++ return rc; ++ } ++ fsg->phase_error = 0; ++ fsg->short_packet_received = 0; ++ ++ down_read(&fsg->filesem); // We're using the backing file ++ switch (fsg->cmnd[0]) { ++ ++ case SC_INQUIRY: ++ fsg->data_size_from_cmnd = fsg->cmnd[4]; ++ if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST, ++ (1<<4), 0, ++ "INQUIRY")) == 0) ++ reply = do_inquiry(fsg, bh); ++ break; ++ ++ case SC_MODE_SELECT_6: ++ fsg->data_size_from_cmnd = fsg->cmnd[4]; ++ if ((reply = check_command(fsg, 6, DATA_DIR_FROM_HOST, ++ (1<<1) | (1<<4), 0, ++ "MODE SELECT(6)")) == 0) ++ reply = do_mode_select(fsg, bh); ++ break; ++ ++ case SC_MODE_SELECT_10: ++ fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]); ++ if ((reply = check_command(fsg, 10, DATA_DIR_FROM_HOST, ++ (1<<1) | (3<<7), 0, ++ "MODE SELECT(10)")) == 0) ++ reply = do_mode_select(fsg, bh); ++ break; ++ ++ case SC_MODE_SENSE_6: ++ fsg->data_size_from_cmnd = fsg->cmnd[4]; ++ if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST, ++ (1<<1) | (1<<2) | (1<<4), 0, ++ "MODE SENSE(6)")) == 0) ++ reply = do_mode_sense(fsg, bh); ++ break; ++ ++ case SC_MODE_SENSE_10: ++ fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]); ++ if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, ++ (1<<1) | (1<<2) | (3<<7), 0, ++ "MODE SENSE(10)")) == 0) ++ reply = do_mode_sense(fsg, bh); ++ break; ++ ++ case SC_PREVENT_ALLOW_MEDIUM_REMOVAL: ++ fsg->data_size_from_cmnd = 0; ++ if ((reply = check_command(fsg, 6, DATA_DIR_NONE, ++ (1<<4), 0, ++ "PREVENT-ALLOW MEDIUM REMOVAL")) == 0) ++ reply = do_prevent_allow(fsg); ++ break; ++ ++ case SC_READ_6: ++ i = fsg->cmnd[4]; ++ fsg->data_size_from_cmnd = (i == 0 ? 256 : i) << 9; ++ if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST, ++ (7<<1) | (1<<4), 1, ++ "READ(6)")) == 0) ++ reply = do_read(fsg); ++ break; ++ ++ case SC_READ_10: ++ fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]) << 9; ++ if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, ++ (1<<1) | (0xf<<2) | (3<<7), 1, ++ "READ(10)")) == 0) ++ reply = do_read(fsg); ++ break; ++ ++ case SC_READ_12: ++ fsg->data_size_from_cmnd = get_be32(&fsg->cmnd[6]) << 9; ++ if ((reply = check_command(fsg, 12, DATA_DIR_TO_HOST, ++ (1<<1) | (0xf<<2) | (0xf<<6), 1, ++ "READ(12)")) == 0) ++ reply = do_read(fsg); ++ break; ++ ++ case SC_READ_CAPACITY: ++ fsg->data_size_from_cmnd = 8; ++ if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, ++ (0xf<<2) | (1<<8), 1, ++ "READ CAPACITY")) == 0) ++ reply = do_read_capacity(fsg, bh); ++ break; ++ ++ case SC_READ_FORMAT_CAPACITIES: ++ fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]); ++ if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, ++ (3<<7), 1, ++ "READ FORMAT CAPACITIES")) == 0) ++ reply = do_read_format_capacities(fsg, bh); ++ break; ++ ++ case SC_REQUEST_SENSE: ++ fsg->data_size_from_cmnd = fsg->cmnd[4]; ++ if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST, ++ (1<<4), 0, ++ "REQUEST SENSE")) == 0) ++ reply = do_request_sense(fsg, bh); ++ break; ++ ++ case SC_START_STOP_UNIT: ++ fsg->data_size_from_cmnd = 0; ++ if ((reply = check_command(fsg, 6, DATA_DIR_NONE, ++ (1<<1) | (1<<4), 0, ++ "START-STOP UNIT")) == 0) ++ reply = do_start_stop(fsg); ++ break; ++ ++ case SC_SYNCHRONIZE_CACHE: ++ fsg->data_size_from_cmnd = 0; ++ if ((reply = check_command(fsg, 10, DATA_DIR_NONE, ++ (0xf<<2) | (3<<7), 1, ++ "SYNCHRONIZE CACHE")) == 0) ++ reply = do_synchronize_cache(fsg); ++ break; ++ ++ case SC_TEST_UNIT_READY: ++ fsg->data_size_from_cmnd = 0; ++ reply = check_command(fsg, 6, DATA_DIR_NONE, ++ 0, 1, ++ "TEST UNIT READY"); ++ break; ++ ++ /* Although optional, this command is used by MS-Windows. We ++ * support a minimal version: BytChk must be 0. */ ++ case SC_VERIFY: ++ fsg->data_size_from_cmnd = 0; ++ if ((reply = check_command(fsg, 10, DATA_DIR_NONE, ++ (1<<1) | (0xf<<2) | (3<<7), 1, ++ "VERIFY")) == 0) ++ reply = do_verify(fsg); ++ break; ++ ++ case SC_WRITE_6: ++ i = fsg->cmnd[4]; ++ fsg->data_size_from_cmnd = (i == 0 ? 256 : i) << 9; ++ if ((reply = check_command(fsg, 6, DATA_DIR_FROM_HOST, ++ (7<<1) | (1<<4), 1, ++ "WRITE(6)")) == 0) ++ reply = do_write(fsg); ++ break; ++ ++ case SC_WRITE_10: ++ fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]) << 9; ++ if ((reply = check_command(fsg, 10, DATA_DIR_FROM_HOST, ++ (1<<1) | (0xf<<2) | (3<<7), 1, ++ "WRITE(10)")) == 0) ++ reply = do_write(fsg); ++ break; ++ ++ case SC_WRITE_12: ++ fsg->data_size_from_cmnd = get_be32(&fsg->cmnd[6]) << 9; ++ if ((reply = check_command(fsg, 12, DATA_DIR_FROM_HOST, ++ (1<<1) | (0xf<<2) | (0xf<<6), 1, ++ "WRITE(12)")) == 0) ++ reply = do_write(fsg); ++ break; ++ ++ /* Some mandatory commands that we recognize but don't implement. ++ * They don't mean much in this setting. It's left as an exercise ++ * for anyone interested to implement RESERVE and RELEASE in terms ++ * of Posix locks. */ ++ case SC_FORMAT_UNIT: ++ case SC_RELEASE: ++ case SC_RESERVE: ++ case SC_SEND_DIAGNOSTIC: ++ // Fall through ++ ++ default: ++ fsg->data_size_from_cmnd = 0; ++ sprintf(unknown, "Unknown x%02x", fsg->cmnd[0]); ++ if ((reply = check_command(fsg, fsg->cmnd_size, ++ DATA_DIR_UNKNOWN, 0xff, 0, unknown)) == 0) { ++ fsg->curlun->sense_data = SS_INVALID_COMMAND; ++ reply = -EINVAL; ++ } ++ break; ++ } ++ up_read(&fsg->filesem); ++ ++ if (reply == -EINTR || signal_pending(current)) ++ return -EINTR; ++ ++ /* Set up the single reply buffer for finish_reply() */ ++ if (reply == -EINVAL) ++ reply = 0; // Error reply length ++ if (reply >= 0 && fsg->data_dir == DATA_DIR_TO_HOST) { ++ reply = min((u32) reply, fsg->data_size_from_cmnd); ++ bh->inreq->length = reply; ++ bh->state = BUF_STATE_FULL; ++ fsg->residue -= reply; ++ } // Otherwise it's already set ++ ++ return 0; ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int received_cbw(struct fsg_dev *fsg, struct fsg_buffhd *bh) ++{ ++ struct usb_request *req = bh->outreq; ++ struct bulk_cb_wrap *cbw = (struct bulk_cb_wrap *) req->buf; ++ ++ /* Was this a real packet? */ ++ if (req->status) ++ return -EINVAL; ++ ++ /* Is the CBW valid? */ ++ if (req->actual != USB_BULK_CB_WRAP_LEN || ++ cbw->Signature != __constant_cpu_to_le32( ++ USB_BULK_CB_SIG)) { ++ DBG(fsg, "invalid CBW: len %u sig 0x%x\n", ++ req->actual, ++ le32_to_cpu(cbw->Signature)); ++ ++ /* The Bulk-only spec says we MUST stall the bulk pipes! ++ * If we want to avoid stalls, set a flag so that we will ++ * clear the endpoint halts at the next reset. */ ++ if (!mod_data.can_stall) ++ set_bit(CLEAR_BULK_HALTS, &fsg->atomic_bitflags); ++ fsg_set_halt(fsg, fsg->bulk_out); ++ halt_bulk_in_endpoint(fsg); ++ return -EINVAL; ++ } ++ ++ /* Is the CBW meaningful? */ ++ if (cbw->Lun >= MAX_LUNS || cbw->Flags & ~USB_BULK_IN_FLAG || ++ cbw->Length < 6 || cbw->Length > MAX_COMMAND_SIZE) { ++ DBG(fsg, "non-meaningful CBW: lun = %u, flags = 0x%x, " ++ "cmdlen %u\n", ++ cbw->Lun, cbw->Flags, cbw->Length); ++ ++ /* We can do anything we want here, so let's stall the ++ * bulk pipes if we are allowed to. */ ++ if (mod_data.can_stall) { ++ fsg_set_halt(fsg, fsg->bulk_out); ++ halt_bulk_in_endpoint(fsg); ++ } ++ return -EINVAL; ++ } ++ ++ /* Save the command for later */ ++ fsg->cmnd_size = cbw->Length; ++ memcpy(fsg->cmnd, cbw->CDB, fsg->cmnd_size); ++ if (cbw->Flags & USB_BULK_IN_FLAG) ++ fsg->data_dir = DATA_DIR_TO_HOST; ++ else ++ fsg->data_dir = DATA_DIR_FROM_HOST; ++ fsg->data_size = le32_to_cpu(cbw->DataTransferLength); ++ if (fsg->data_size == 0) ++ fsg->data_dir = DATA_DIR_NONE; ++ fsg->lun = cbw->Lun; ++ fsg->tag = cbw->Tag; ++ return 0; ++} ++ ++ ++static int get_next_command(struct fsg_dev *fsg) ++{ ++ struct fsg_buffhd *bh; ++ int rc = 0; ++ ++ if (transport_is_bbb()) { ++ ++ /* Wait for the next buffer to become available */ ++ bh = fsg->next_buffhd_to_fill; ++ while (bh->state != BUF_STATE_EMPTY) { ++ if ((rc = sleep_thread(fsg)) != 0) ++ return rc; ++ } ++ ++ /* Queue a request to read a Bulk-only CBW */ ++ set_bulk_out_req_length(fsg, bh, USB_BULK_CB_WRAP_LEN); ++ start_transfer(fsg, fsg->bulk_out, bh->outreq, ++ &bh->outreq_busy, &bh->state); ++ ++ /* We will drain the buffer in software, which means we ++ * can reuse it for the next filling. No need to advance ++ * next_buffhd_to_fill. */ ++ ++ /* Wait for the CBW to arrive */ ++ while (bh->state != BUF_STATE_FULL) { ++ if ((rc = sleep_thread(fsg)) != 0) ++ return rc; ++ } ++ rc = received_cbw(fsg, bh); ++ bh->state = BUF_STATE_EMPTY; ++ ++ } else { // USB_PR_CB or USB_PR_CBI ++ ++ /* Wait for the next command to arrive */ ++ while (fsg->cbbuf_cmnd_size == 0) { ++ if ((rc = sleep_thread(fsg)) != 0) ++ return rc; ++ } ++ ++ /* Is the previous status interrupt request still busy? ++ * The host is allowed to skip reading the status, ++ * so we must cancel it. */ ++ if (fsg->intreq_busy) ++ usb_ep_dequeue(fsg->intr_in, fsg->intreq); ++ ++ /* Copy the command and mark the buffer empty */ ++ fsg->data_dir = DATA_DIR_UNKNOWN; ++ spin_lock_irq(&fsg->lock); ++ fsg->cmnd_size = fsg->cbbuf_cmnd_size; ++ memcpy(fsg->cmnd, fsg->cbbuf_cmnd, fsg->cmnd_size); ++ fsg->cbbuf_cmnd_size = 0; ++ spin_unlock_irq(&fsg->lock); ++ } ++ return rc; ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int enable_endpoint(struct fsg_dev *fsg, struct usb_ep *ep, ++ const struct usb_endpoint_descriptor *d) ++{ ++ int rc; ++ ++ ep->driver_data = fsg; ++ rc = usb_ep_enable(ep, d); ++ if (rc) ++ ERROR(fsg, "can't enable %s, result %d\n", ep->name, rc); ++ return rc; ++} ++ ++static int alloc_request(struct fsg_dev *fsg, struct usb_ep *ep, ++ struct usb_request **preq) ++{ ++ *preq = usb_ep_alloc_request(ep, GFP_ATOMIC); ++ if (*preq) ++ return 0; ++ ERROR(fsg, "can't allocate request for %s\n", ep->name); ++ return -ENOMEM; ++} ++ ++/* ++ * Reset interface setting and re-init endpoint state (toggle etc). ++ * Call with altsetting < 0 to disable the interface. The only other ++ * available altsetting is 0, which enables the interface. ++ */ ++static int do_set_interface(struct fsg_dev *fsg, int altsetting) ++{ ++ int rc = 0; ++ int i; ++ const struct usb_endpoint_descriptor *d; ++ ++ if (fsg->running) ++ DBG(fsg, "reset interface\n"); ++ ++reset: ++ /* Deallocate the requests */ ++ for (i = 0; i < NUM_BUFFERS; ++i) { ++ struct fsg_buffhd *bh = &fsg->buffhds[i]; ++ ++ if (bh->inreq) { ++ usb_ep_free_request(fsg->bulk_in, bh->inreq); ++ bh->inreq = NULL; ++ } ++ if (bh->outreq) { ++ usb_ep_free_request(fsg->bulk_out, bh->outreq); ++ bh->outreq = NULL; ++ } ++ } ++ if (fsg->intreq) { ++ usb_ep_free_request(fsg->intr_in, fsg->intreq); ++ fsg->intreq = NULL; ++ } ++ ++ /* Disable the endpoints */ ++ if (fsg->bulk_in_enabled) { ++ usb_ep_disable(fsg->bulk_in); ++ fsg->bulk_in_enabled = 0; ++ } ++ if (fsg->bulk_out_enabled) { ++ usb_ep_disable(fsg->bulk_out); ++ fsg->bulk_out_enabled = 0; ++ } ++ if (fsg->intr_in_enabled) { ++ usb_ep_disable(fsg->intr_in); ++ fsg->intr_in_enabled = 0; ++ } ++ ++ fsg->running = 0; ++ if (altsetting < 0 || rc != 0) ++ return rc; ++ ++ DBG(fsg, "set interface %d\n", altsetting); ++ ++ /* Enable the endpoints */ ++ d = ep_desc(fsg->gadget, &fs_bulk_in_desc, &hs_bulk_in_desc); ++ if ((rc = enable_endpoint(fsg, fsg->bulk_in, d)) != 0) ++ goto reset; ++ fsg->bulk_in_enabled = 1; ++ ++ d = ep_desc(fsg->gadget, &fs_bulk_out_desc, &hs_bulk_out_desc); ++ if ((rc = enable_endpoint(fsg, fsg->bulk_out, d)) != 0) ++ goto reset; ++ fsg->bulk_out_enabled = 1; ++ fsg->bulk_out_maxpacket = d->wMaxPacketSize; ++ ++ if (transport_is_cbi()) { ++ d = ep_desc(fsg->gadget, &fs_intr_in_desc, &hs_intr_in_desc); ++ if ((rc = enable_endpoint(fsg, fsg->intr_in, d)) != 0) ++ goto reset; ++ fsg->intr_in_enabled = 1; ++ } ++ ++ /* Allocate the requests */ ++ for (i = 0; i < NUM_BUFFERS; ++i) { ++ struct fsg_buffhd *bh = &fsg->buffhds[i]; ++ ++ if ((rc = alloc_request(fsg, fsg->bulk_in, &bh->inreq)) != 0) ++ goto reset; ++ if ((rc = alloc_request(fsg, fsg->bulk_out, &bh->outreq)) != 0) ++ goto reset; ++ bh->inreq->buf = bh->outreq->buf = bh->buf; ++ bh->inreq->dma = bh->outreq->dma = bh->dma; ++ bh->inreq->context = bh->outreq->context = bh; ++ bh->inreq->complete = bulk_in_complete; ++ bh->outreq->complete = bulk_out_complete; ++ } ++ if (transport_is_cbi()) { ++ if ((rc = alloc_request(fsg, fsg->intr_in, &fsg->intreq)) != 0) ++ goto reset; ++ fsg->intreq->complete = intr_in_complete; ++ } ++ ++ fsg->running = 1; ++ for (i = 0; i < fsg->nluns; ++i) ++ fsg->luns[i].unit_attention_data = SS_RESET_OCCURRED; ++ return rc; ++} ++ ++ ++/* ++ * Change our operational configuration. This code must agree with the code ++ * that returns config descriptors, and with interface altsetting code. ++ * ++ * It's also responsible for power management interactions. Some ++ * configurations might not work with our current power sources. ++ * For now we just assume the gadget is always self-powered. ++ */ ++static int do_set_config(struct fsg_dev *fsg, u8 new_config) ++{ ++ int rc = 0; ++ ++ /* Disable the single interface */ ++ if (fsg->config != 0) { ++ DBG(fsg, "reset config\n"); ++ fsg->config = 0; ++ rc = do_set_interface(fsg, -1); ++ } ++ ++ /* Enable the interface */ ++ if (new_config != 0) { ++ fsg->config = new_config; ++ if ((rc = do_set_interface(fsg, 0)) != 0) ++ fsg->config = 0; // Reset on errors ++ else { ++ char *speed; ++ ++ switch (fsg->gadget->speed) { ++ case USB_SPEED_LOW: speed = "low"; break; ++ case USB_SPEED_FULL: speed = "full"; break; ++ case USB_SPEED_HIGH: speed = "high"; break; ++ default: speed = "?"; break; ++ } ++ INFO(fsg, "%s speed config #%d\n", speed, fsg->config); ++ } ++ } ++ return rc; ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void handle_exception(struct fsg_dev *fsg) ++{ ++ siginfo_t info; ++ int sig; ++ int i; ++ int num_active; ++ struct fsg_buffhd *bh; ++ enum fsg_state old_state; ++ u8 new_config; ++ struct lun *curlun; ++ unsigned int exception_req_tag; ++ int rc; ++ ++ /* Clear the existing signals. Anything but SIGUSR1 is converted ++ * into a high-priority EXIT exception. */ ++ for (;;) { ++ spin_lock_irq(¤t->sigmask_lock); ++ sig = dequeue_signal(&fsg->thread_signal_mask, &info); ++ spin_unlock_irq(¤t->sigmask_lock); ++ if (!sig) ++ break; ++ if (sig != SIGUSR1) { ++ if (fsg->state < FSG_STATE_EXIT) ++ DBG(fsg, "Main thread exiting on signal\n"); ++ raise_exception(fsg, FSG_STATE_EXIT); ++ } ++ } ++ ++ /* Cancel all the pending transfers */ ++ if (fsg->intreq_busy) ++ usb_ep_dequeue(fsg->intr_in, fsg->intreq); ++ for (i = 0; i < NUM_BUFFERS; ++i) { ++ bh = &fsg->buffhds[i]; ++ if (bh->inreq_busy) ++ usb_ep_dequeue(fsg->bulk_in, bh->inreq); ++ if (bh->outreq_busy) ++ usb_ep_dequeue(fsg->bulk_out, bh->outreq); ++ } ++ ++ /* Wait until everything is idle */ ++ for (;;) { ++ num_active = fsg->intreq_busy; ++ for (i = 0; i < NUM_BUFFERS; ++i) { ++ bh = &fsg->buffhds[i]; ++ num_active += bh->inreq_busy + bh->outreq_busy; ++ } ++ if (num_active == 0) ++ break; ++ if (sleep_thread(fsg)) ++ return; ++ } ++ ++ /* Clear out the controller's fifos */ ++ if (fsg->bulk_in_enabled) ++ usb_ep_fifo_flush(fsg->bulk_in); ++ if (fsg->bulk_out_enabled) ++ usb_ep_fifo_flush(fsg->bulk_out); ++ if (fsg->intr_in_enabled) ++ usb_ep_fifo_flush(fsg->intr_in); ++ ++ /* Reset the I/O buffer states and pointers, the SCSI ++ * state, and the exception. Then invoke the handler. */ ++ spin_lock_irq(&fsg->lock); ++ ++ for (i = 0; i < NUM_BUFFERS; ++i) { ++ bh = &fsg->buffhds[i]; ++ bh->state = BUF_STATE_EMPTY; ++ } ++ fsg->next_buffhd_to_fill = fsg->next_buffhd_to_drain = ++ &fsg->buffhds[0]; ++ ++ exception_req_tag = fsg->exception_req_tag; ++ new_config = fsg->new_config; ++ old_state = fsg->state; ++ ++ if (old_state == FSG_STATE_ABORT_BULK_OUT) ++ fsg->state = FSG_STATE_STATUS_PHASE; ++ else { ++ for (i = 0; i < fsg->nluns; ++i) { ++ curlun = &fsg->luns[i]; ++ curlun->prevent_medium_removal = 0; ++ curlun->sense_data = curlun->unit_attention_data = ++ SS_NO_SENSE; ++ curlun->sense_data_info = 0; ++ } ++ fsg->state = FSG_STATE_IDLE; ++ } ++ spin_unlock_irq(&fsg->lock); ++ ++ /* Carry out any extra actions required for the exception */ ++ switch (old_state) { ++ default: ++ break; ++ ++ case FSG_STATE_ABORT_BULK_OUT: ++ send_status(fsg); ++ spin_lock_irq(&fsg->lock); ++ if (fsg->state == FSG_STATE_STATUS_PHASE) ++ fsg->state = FSG_STATE_IDLE; ++ spin_unlock_irq(&fsg->lock); ++ break; ++ ++ case FSG_STATE_RESET: ++ /* In case we were forced against our will to halt a ++ * bulk endpoint, clear the halt now. (The SuperH UDC ++ * requires this.) */ ++ if (test_and_clear_bit(CLEAR_BULK_HALTS, ++ &fsg->atomic_bitflags)) { ++ usb_ep_clear_halt(fsg->bulk_in); ++ usb_ep_clear_halt(fsg->bulk_out); ++ } ++ ++ if (transport_is_bbb()) { ++ if (fsg->ep0_req_tag == exception_req_tag) ++ ep0_queue(fsg); // Complete the status stage ++ ++ } else if (transport_is_cbi()) ++ send_status(fsg); // Status by interrupt pipe ++ ++ /* Technically this should go here, but it would only be ++ * a waste of time. Ditto for the INTERFACE_CHANGE and ++ * CONFIG_CHANGE cases. */ ++ // for (i = 0; i < fsg->nluns; ++i) ++ // fsg->luns[i].unit_attention_data = SS_RESET_OCCURRED; ++ break; ++ ++ case FSG_STATE_INTERFACE_CHANGE: ++ rc = do_set_interface(fsg, 0); ++ if (fsg->ep0_req_tag != exception_req_tag) ++ break; ++ if (rc != 0) // STALL on errors ++ fsg_set_halt(fsg, fsg->ep0); ++ else // Complete the status stage ++ ep0_queue(fsg); ++ break; ++ ++ case FSG_STATE_CONFIG_CHANGE: ++ rc = do_set_config(fsg, new_config); ++ if (fsg->ep0_req_tag != exception_req_tag) ++ break; ++ if (rc != 0) // STALL on errors ++ fsg_set_halt(fsg, fsg->ep0); ++ else // Complete the status stage ++ ep0_queue(fsg); ++ break; ++ ++ case FSG_STATE_DISCONNECT: ++ fsync_all(fsg); ++ do_set_config(fsg, 0); // Unconfigured state ++ break; ++ ++ case FSG_STATE_EXIT: ++ case FSG_STATE_TERMINATED: ++ do_set_config(fsg, 0); // Free resources ++ spin_lock_irq(&fsg->lock); ++ fsg->state = FSG_STATE_TERMINATED; // Stop the thread ++ spin_unlock_irq(&fsg->lock); ++ break; ++ } ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int fsg_main_thread(void *fsg_) ++{ ++ struct fsg_dev *fsg = (struct fsg_dev *) fsg_; ++ ++ fsg->thread_task = current; ++ ++ /* Release all our userspace resources */ ++ daemonize(); ++ reparent_to_init(); ++ strncpy(current->comm, "file-storage-gadget", ++ sizeof(current->comm) - 1); ++ ++ /* Allow the thread to be killed by a signal, but set the signal mask ++ * to block everything but INT, TERM, KILL, and USR1. */ ++ siginitsetinv(&fsg->thread_signal_mask, sigmask(SIGINT) | ++ sigmask(SIGTERM) | sigmask(SIGKILL) | ++ sigmask(SIGUSR1)); ++ spin_lock_irq(¤t->sigmask_lock); ++ flush_signals(current); ++ current->blocked = fsg->thread_signal_mask; ++ recalc_sigpending(current); ++ spin_unlock_irq(¤t->sigmask_lock); ++ ++ /* Arrange for userspace references to be interpreted as kernel ++ * pointers. That way we can pass a kernel pointer to a routine ++ * that expects a __user pointer and it will work okay. */ ++ set_fs(get_ds()); ++ ++ /* Wait for the gadget registration to finish up */ ++ wait_for_completion(&fsg->thread_notifier); ++ ++ /* The main loop */ ++ while (fsg->state != FSG_STATE_TERMINATED) { ++ if (exception_in_progress(fsg) || signal_pending(current)) { ++ handle_exception(fsg); ++ continue; ++ } ++ ++ if (!fsg->running) { ++ sleep_thread(fsg); ++ continue; ++ } ++ ++ if (get_next_command(fsg)) ++ continue; ++ ++ spin_lock_irq(&fsg->lock); ++ if (!exception_in_progress(fsg)) ++ fsg->state = FSG_STATE_DATA_PHASE; ++ spin_unlock_irq(&fsg->lock); ++ ++ if (do_scsi_command(fsg) || finish_reply(fsg)) ++ continue; ++ ++ spin_lock_irq(&fsg->lock); ++ if (!exception_in_progress(fsg)) ++ fsg->state = FSG_STATE_STATUS_PHASE; ++ spin_unlock_irq(&fsg->lock); ++ ++ if (send_status(fsg)) ++ continue; ++ ++ spin_lock_irq(&fsg->lock); ++ if (!exception_in_progress(fsg)) ++ fsg->state = FSG_STATE_IDLE; ++ spin_unlock_irq(&fsg->lock); ++ } ++ ++ fsg->thread_task = NULL; ++ flush_signals(current); ++ ++ /* In case we are exiting because of a signal, unregister the ++ * gadget driver and close the backing file. */ ++ if (test_and_clear_bit(REGISTERED, &fsg->atomic_bitflags)) { ++ usb_gadget_unregister_driver(&fsg_driver); ++ close_all_backing_files(fsg); ++ } ++ ++ /* Let the unbind and cleanup routines know the thread has exited */ ++ complete_and_exit(&fsg->thread_notifier, 0); ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* If the next two routines are called while the gadget is registered, ++ * the caller must own fsg->filesem for writing. */ ++ ++static int NORMALLY_INIT open_backing_file(struct lun *curlun, ++ const char *filename) ++{ ++ int ro; ++ struct file *filp = NULL; ++ int rc = -EINVAL; ++ struct inode *inode = NULL; ++ loff_t size; ++ loff_t num_sectors; ++ ++ /* R/W if we can, R/O if we must */ ++ ro = curlun->ro; ++ if (!ro) { ++ filp = filp_open(filename, O_RDWR | O_LARGEFILE, 0); ++ if (-EROFS == PTR_ERR(filp)) ++ ro = 1; ++ } ++ if (ro) ++ filp = filp_open(filename, O_RDONLY | O_LARGEFILE, 0); ++ if (IS_ERR(filp)) { ++ LINFO(curlun, "unable to open backing file: %s\n", filename); ++ return PTR_ERR(filp); ++ } ++ ++ if (!(filp->f_mode & FMODE_WRITE)) ++ ro = 1; ++ ++ if (filp->f_dentry) ++ inode = filp->f_dentry->d_inode; ++ if (inode && S_ISBLK(inode->i_mode)) { ++ kdev_t dev = inode->i_rdev; ++ ++ if (blk_size[MAJOR(dev)]) ++ size = (loff_t) blk_size[MAJOR(dev)][MINOR(dev)] << ++ BLOCK_SIZE_BITS; ++ else { ++ LINFO(curlun, "unable to find file size: %s\n", ++ filename); ++ goto out; ++ } ++ } else if (inode && S_ISREG(inode->i_mode)) ++ size = inode->i_size; ++ else { ++ LINFO(curlun, "invalid file type: %s\n", filename); ++ goto out; ++ } ++ ++ /* If we can't read the file, it's no good. ++ * If we can't write the file, use it read-only. */ ++ if (!filp->f_op || !filp->f_op->read) { ++ LINFO(curlun, "file not readable: %s\n", filename); ++ goto out; ++ } ++ if (IS_RDONLY(inode) || !filp->f_op->write) ++ ro = 1; ++ ++ num_sectors = size >> 9; // File size in 512-byte sectors ++ if (num_sectors == 0) { ++ LINFO(curlun, "file too small: %s\n", filename); ++ rc = -ETOOSMALL; ++ goto out; ++ } ++ ++ get_file(filp); ++ curlun->ro = ro; ++ curlun->filp = filp; ++ curlun->file_length = size; ++ curlun->num_sectors = num_sectors; ++ LDBG(curlun, "open backing file: %s\n", filename); ++ rc = 0; ++ ++out: ++ filp_close(filp, current->files); ++ return rc; ++} ++ ++ ++static void close_backing_file(struct lun *curlun) ++{ ++ if (curlun->filp) { ++ LDBG(curlun, "close backing file\n"); ++ fput(curlun->filp); ++ curlun->filp = NULL; ++ } ++} ++ ++static void close_all_backing_files(struct fsg_dev *fsg) ++{ ++ int i; ++ ++ for (i = 0; i < fsg->nluns; ++i) ++ close_backing_file(&fsg->luns[i]); ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void fsg_unbind(struct usb_gadget *gadget) ++{ ++ struct fsg_dev *fsg = get_gadget_data(gadget); ++ int i; ++ struct usb_request *req = fsg->ep0req; ++ ++ DBG(fsg, "unbind\n"); ++ clear_bit(REGISTERED, &fsg->atomic_bitflags); ++ ++ /* If the thread isn't already dead, tell it to exit now */ ++ if (fsg->state != FSG_STATE_TERMINATED) { ++ raise_exception(fsg, FSG_STATE_EXIT); ++ wait_for_completion(&fsg->thread_notifier); ++ ++ /* The cleanup routine waits for this completion also */ ++ complete(&fsg->thread_notifier); ++ } ++ ++ /* Free the data buffers */ ++ for (i = 0; i < NUM_BUFFERS; ++i) { ++ struct fsg_buffhd *bh = &fsg->buffhds[i]; ++ ++ if (bh->buf) ++ usb_ep_free_buffer(fsg->bulk_in, bh->buf, bh->dma, ++ mod_data.buflen); ++ } ++ ++ /* Free the request and buffer for endpoint 0 */ ++ if (req) { ++ if (req->buf) ++ usb_ep_free_buffer(fsg->ep0, req->buf, ++ req->dma, EP0_BUFSIZE); ++ usb_ep_free_request(fsg->ep0, req); ++ } ++ ++ set_gadget_data(gadget, 0); ++} ++ ++ ++static int __init check_parameters(struct fsg_dev *fsg) ++{ ++ int prot; ++ ++ /* Store the default values */ ++ mod_data.transport_type = USB_PR_BULK; ++ mod_data.transport_name = "Bulk-only"; ++ mod_data.protocol_type = USB_SC_SCSI; ++ mod_data.protocol_name = "Transparent SCSI"; ++ ++ if (gadget_is_sh(fsg->gadget)) ++ mod_data.can_stall = 0; ++ ++ if (mod_data.release == 0xffff) { // Parameter wasn't set ++ if (gadget_is_net2280(fsg->gadget)) ++ mod_data.release = __constant_cpu_to_le16(0x0221); ++ else if (gadget_is_dummy(fsg->gadget)) ++ mod_data.release = __constant_cpu_to_le16(0x0222); ++ else if (gadget_is_pxa(fsg->gadget)) ++ mod_data.release = __constant_cpu_to_le16(0x0223); ++ else if (gadget_is_sh(fsg->gadget)) ++ mod_data.release = __constant_cpu_to_le16(0x0224); ++ ++ /* The sa1100 controller is not supported */ ++ ++ else if (gadget_is_goku(fsg->gadget)) ++ mod_data.release = __constant_cpu_to_le16(0x0226); ++ else if (gadget_is_mq11xx(fsg->gadget)) ++ mod_data.release = __constant_cpu_to_le16(0x0227); ++ else if (gadget_is_omap(fsg->gadget)) ++ mod_data.release = __constant_cpu_to_le16(0x0228); ++ else { ++ WARN(fsg, "controller '%s' not recognized\n", ++ fsg->gadget->name); ++ mod_data.release = __constant_cpu_to_le16(0x0299); ++ } ++ } ++ ++ prot = simple_strtol(mod_data.protocol_parm, NULL, 0); ++ ++#ifdef CONFIG_USB_FILE_STORAGE_TEST ++ if (strnicmp(mod_data.transport_parm, "BBB", 10) == 0) { ++ ; // Use default setting ++ } else if (strnicmp(mod_data.transport_parm, "CB", 10) == 0) { ++ mod_data.transport_type = USB_PR_CB; ++ mod_data.transport_name = "Control-Bulk"; ++ } else if (strnicmp(mod_data.transport_parm, "CBI", 10) == 0) { ++ mod_data.transport_type = USB_PR_CBI; ++ mod_data.transport_name = "Control-Bulk-Interrupt"; ++ } else { ++ ERROR(fsg, "invalid transport: %s\n", mod_data.transport_parm); ++ return -EINVAL; ++ } ++ ++ if (strnicmp(mod_data.protocol_parm, "SCSI", 10) == 0 || ++ prot == USB_SC_SCSI) { ++ ; // Use default setting ++ } else if (strnicmp(mod_data.protocol_parm, "RBC", 10) == 0 || ++ prot == USB_SC_RBC) { ++ mod_data.protocol_type = USB_SC_RBC; ++ mod_data.protocol_name = "RBC"; ++ } else if (strnicmp(mod_data.protocol_parm, "8020", 4) == 0 || ++ strnicmp(mod_data.protocol_parm, "ATAPI", 10) == 0 || ++ prot == USB_SC_8020) { ++ mod_data.protocol_type = USB_SC_8020; ++ mod_data.protocol_name = "8020i (ATAPI)"; ++ } else if (strnicmp(mod_data.protocol_parm, "QIC", 3) == 0 || ++ prot == USB_SC_QIC) { ++ mod_data.protocol_type = USB_SC_QIC; ++ mod_data.protocol_name = "QIC-157"; ++ } else if (strnicmp(mod_data.protocol_parm, "UFI", 10) == 0 || ++ prot == USB_SC_UFI) { ++ mod_data.protocol_type = USB_SC_UFI; ++ mod_data.protocol_name = "UFI"; ++ } else if (strnicmp(mod_data.protocol_parm, "8070", 4) == 0 || ++ prot == USB_SC_8070) { ++ mod_data.protocol_type = USB_SC_8070; ++ mod_data.protocol_name = "8070i"; ++ } else { ++ ERROR(fsg, "invalid protocol: %s\n", mod_data.protocol_parm); ++ return -EINVAL; ++ } ++ ++ mod_data.buflen &= PAGE_CACHE_MASK; ++ if (mod_data.buflen <= 0) { ++ ERROR(fsg, "invalid buflen\n"); ++ return -ETOOSMALL; ++ } ++#endif /* CONFIG_USB_FILE_STORAGE_TEST */ ++ ++ return 0; ++} ++ ++ ++static int __init fsg_bind(struct usb_gadget *gadget) ++{ ++ struct fsg_dev *fsg = the_fsg; ++ int rc; ++ int i; ++ struct lun *curlun; ++ struct usb_ep *ep; ++ struct usb_request *req; ++ char *pathbuf, *p; ++ ++ fsg->gadget = gadget; ++ set_gadget_data(gadget, fsg); ++ fsg->ep0 = gadget->ep0; ++ fsg->ep0->driver_data = fsg; ++ ++ if ((rc = check_parameters(fsg)) != 0) ++ goto out; ++ ++ /* Find out how many LUNs there should be */ ++ i = mod_data.nluns; ++ if (i == 0) { ++ for (i = MAX_LUNS; i > 1; --i) { ++ if (file[i - 1]) ++ break; ++ } ++ } ++ if (i > MAX_LUNS) { ++ ERROR(fsg, "invalid number of LUNs: %d\n", i); ++ rc = -EINVAL; ++ goto out; ++ } ++ ++ /* Create the LUNs and open their backing files. We can't register ++ * the LUN devices until the gadget itself is registered, which ++ * doesn't happen until after fsg_bind() returns. */ ++ fsg->luns = kmalloc(i * sizeof(struct lun), GFP_KERNEL); ++ if (!fsg->luns) { ++ rc = -ENOMEM; ++ goto out; ++ } ++ memset(fsg->luns, 0, i * sizeof(struct lun)); ++ fsg->nluns = i; ++ ++ for (i = 0; i < fsg->nluns; ++i) { ++ curlun = &fsg->luns[i]; ++ curlun->ro = ro[i]; ++ curlun->dev.driver_data = fsg; ++ snprintf(curlun->dev.name, BUS_ID_SIZE, ++ "%s-lun%d", gadget->name, i); ++ ++ if (file[i] && *file[i]) { ++ if ((rc = open_backing_file(curlun, file[i])) != 0) ++ goto out; ++ } else if (!mod_data.removable) { ++ ERROR(fsg, "no file given for LUN%d\n", i); ++ rc = -EINVAL; ++ goto out; ++ } ++ } ++ ++ /* Find all the endpoints we will use */ ++ usb_ep_autoconfig_reset(gadget); ++ ep = usb_ep_autoconfig(gadget, &fs_bulk_in_desc); ++ if (!ep) ++ goto autoconf_fail; ++ ep->driver_data = fsg; // claim the endpoint ++ fsg->bulk_in = ep; ++ ++ ep = usb_ep_autoconfig(gadget, &fs_bulk_out_desc); ++ if (!ep) ++ goto autoconf_fail; ++ ep->driver_data = fsg; // claim the endpoint ++ fsg->bulk_out = ep; ++ ++ if (transport_is_cbi()) { ++ ep = usb_ep_autoconfig(gadget, &fs_intr_in_desc); ++ if (!ep) ++ goto autoconf_fail; ++ ep->driver_data = fsg; // claim the endpoint ++ fsg->intr_in = ep; ++ } ++ ++ /* Fix up the descriptors */ ++ device_desc.bMaxPacketSize0 = fsg->ep0->maxpacket; ++ device_desc.idVendor = cpu_to_le16(mod_data.vendor); ++ device_desc.idProduct = cpu_to_le16(mod_data.product); ++ device_desc.bcdDevice = cpu_to_le16(mod_data.release); ++ ++ i = (transport_is_cbi() ? 3 : 2); // Number of endpoints ++ intf_desc.bNumEndpoints = i; ++ intf_desc.bInterfaceSubClass = mod_data.protocol_type; ++ intf_desc.bInterfaceProtocol = mod_data.transport_type; ++ fs_function[i+1] = NULL; ++ ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ hs_function[i+1] = NULL; ++ ++ /* Assume ep0 uses the same maxpacket value for both speeds */ ++ dev_qualifier.bMaxPacketSize0 = fsg->ep0->maxpacket; ++ ++ /* Assume that all endpoint addresses are the same for both speeds */ ++ hs_bulk_in_desc.bEndpointAddress = fs_bulk_in_desc.bEndpointAddress; ++ hs_bulk_out_desc.bEndpointAddress = fs_bulk_out_desc.bEndpointAddress; ++ hs_intr_in_desc.bEndpointAddress = fs_intr_in_desc.bEndpointAddress; ++#endif ++ ++ rc = -ENOMEM; ++ ++ /* Allocate the request and buffer for endpoint 0 */ ++ fsg->ep0req = req = usb_ep_alloc_request(fsg->ep0, GFP_KERNEL); ++ if (!req) ++ goto out; ++ req->buf = usb_ep_alloc_buffer(fsg->ep0, EP0_BUFSIZE, ++ &req->dma, GFP_KERNEL); ++ if (!req->buf) ++ goto out; ++ req->complete = ep0_complete; ++ ++ /* Allocate the data buffers */ ++ for (i = 0; i < NUM_BUFFERS; ++i) { ++ struct fsg_buffhd *bh = &fsg->buffhds[i]; ++ ++ bh->buf = usb_ep_alloc_buffer(fsg->bulk_in, mod_data.buflen, ++ &bh->dma, GFP_KERNEL); ++ if (!bh->buf) ++ goto out; ++ bh->next = bh + 1; ++ } ++ fsg->buffhds[NUM_BUFFERS - 1].next = &fsg->buffhds[0]; ++ ++ /* This should reflect the actual gadget power source */ ++ usb_gadget_set_selfpowered(gadget); ++ ++ snprintf(manufacturer, sizeof manufacturer, ++ UTS_SYSNAME " " UTS_RELEASE " with %s", ++ gadget->name); ++ ++ /* On a real device, serial[] would be loaded from permanent ++ * storage. We just encode it from the driver version string. */ ++ for (i = 0; i < sizeof(serial) - 2; i += 2) { ++ unsigned char c = DRIVER_VERSION[i / 2]; ++ ++ if (!c) ++ break; ++ sprintf(&serial[i], "%02X", c); ++ } ++ ++ if ((rc = kernel_thread(fsg_main_thread, fsg, (CLONE_VM | CLONE_FS | ++ CLONE_FILES))) < 0) ++ goto out; ++ fsg->thread_pid = rc; ++ ++ INFO(fsg, DRIVER_DESC ", version: " DRIVER_VERSION "\n"); ++ INFO(fsg, "Number of LUNs=%d\n", fsg->nluns); ++ ++ pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); ++ for (i = 0; i < fsg->nluns; ++i) { ++ curlun = &fsg->luns[i]; ++ if (backing_file_is_open(curlun)) { ++ p = NULL; ++ if (pathbuf) { ++ p = d_path(curlun->filp->f_dentry, ++ curlun->filp->f_vfsmnt, ++ pathbuf, PATH_MAX); ++ if (IS_ERR(p)) ++ p = NULL; ++ } ++ LINFO(curlun, "ro=%d, file: %s\n", ++ curlun->ro, (p ? p : "(error)")); ++ } ++ } ++ kfree(pathbuf); ++ ++ DBG(fsg, "transport=%s (x%02x)\n", ++ mod_data.transport_name, mod_data.transport_type); ++ DBG(fsg, "protocol=%s (x%02x)\n", ++ mod_data.protocol_name, mod_data.protocol_type); ++ DBG(fsg, "VendorID=x%04x, ProductID=x%04x, Release=x%04x\n", ++ mod_data.vendor, mod_data.product, mod_data.release); ++ DBG(fsg, "removable=%d, stall=%d, buflen=%u\n", ++ mod_data.removable, mod_data.can_stall, ++ mod_data.buflen); ++ DBG(fsg, "I/O thread pid: %d\n", fsg->thread_pid); ++ return 0; ++ ++autoconf_fail: ++ ERROR(fsg, "unable to autoconfigure all endpoints\n"); ++ rc = -ENOTSUPP; ++ ++out: ++ fsg->state = FSG_STATE_TERMINATED; // The thread is dead ++ fsg_unbind(gadget); ++ close_all_backing_files(fsg); ++ return rc; ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static struct usb_gadget_driver fsg_driver = { ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ .speed = USB_SPEED_HIGH, ++#else ++ .speed = USB_SPEED_FULL, ++#endif ++ .function = (char *) longname, ++ .bind = fsg_bind, ++ .unbind = fsg_unbind, ++ .disconnect = fsg_disconnect, ++ .setup = fsg_setup, ++ ++ .driver = { ++ .name = (char *) shortname, ++ // .release = ... ++ // .suspend = ... ++ // .resume = ... ++ }, ++}; ++ ++ ++static int __init fsg_alloc(void) ++{ ++ struct fsg_dev *fsg; ++ ++ fsg = kmalloc(sizeof *fsg, GFP_KERNEL); ++ if (!fsg) ++ return -ENOMEM; ++ memset(fsg, 0, sizeof *fsg); ++ spin_lock_init(&fsg->lock); ++ init_rwsem(&fsg->filesem); ++ init_waitqueue_head(&fsg->thread_wqh); ++ init_completion(&fsg->thread_notifier); ++ ++ the_fsg = fsg; ++ return 0; ++} ++ ++ ++static void fsg_free(struct fsg_dev *fsg) ++{ ++ kfree(fsg->luns); ++ kfree(fsg); ++} ++ ++ ++static int __init fsg_init(void) ++{ ++ int rc; ++ struct fsg_dev *fsg; ++ ++ /* Put the module parameters where they belong -- arghh! */ ++ mod_data.nluns = luns; ++ mod_data.transport_parm = transport; ++ mod_data.protocol_parm = protocol; ++ mod_data.removable = removable; ++ mod_data.vendor = vendor; ++ mod_data.product = product; ++ mod_data.release = release; ++ mod_data.buflen = buflen; ++ mod_data.can_stall = stall; ++ ++ if ((rc = fsg_alloc()) != 0) ++ return rc; ++ fsg = the_fsg; ++ if ((rc = usb_gadget_register_driver(&fsg_driver)) != 0) { ++ fsg_free(fsg); ++ return rc; ++ } ++ set_bit(REGISTERED, &fsg->atomic_bitflags); ++ ++ /* Tell the thread to start working */ ++ complete(&fsg->thread_notifier); ++ return 0; ++} ++module_init(fsg_init); ++ ++ ++static void __exit fsg_cleanup(void) ++{ ++ struct fsg_dev *fsg = the_fsg; ++ ++ /* Unregister the driver iff the thread hasn't already done so */ ++ if (test_and_clear_bit(REGISTERED, &fsg->atomic_bitflags)) ++ usb_gadget_unregister_driver(&fsg_driver); ++ ++ /* Wait for the thread to finish up */ ++ wait_for_completion(&fsg->thread_notifier); ++ ++ close_all_backing_files(fsg); ++ fsg_free(fsg); ++} ++module_exit(fsg_cleanup); +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/gadget_chips.h kernel/drivers/usb/gadget/gadget_chips.h +--- /tmp/kernel/drivers/usb/gadget/gadget_chips.h 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/gadget_chips.h 2005-04-22 17:53:19.434539516 +0200 +@@ -0,0 +1,92 @@ ++/* ++ * USB device controllers have lots of quirks. Use these macros in ++ * gadget drivers or other code that needs to deal with them, and which ++ * autoconfigures instead of using early binding to the hardware. ++ * ++ * This could eventually work like the ARM mach_is_*() stuff, driven by ++ * some config file that gets updated as new hardware is supported. ++ * ++ * NOTE: some of these controller drivers may not be available yet. ++ */ ++#ifdef CONFIG_USB_GADGET_NET2280 ++#define gadget_is_net2280(g) !strcmp("net2280", (g)->name) ++#else ++#define gadget_is_net2280(g) 0 ++#endif ++ ++#ifdef CONFIG_USB_GADGET_DUMMY_HCD ++#define gadget_is_dummy(g) !strcmp("dummy_udc", (g)->name) ++#else ++#define gadget_is_dummy(g) 0 ++#endif ++ ++#ifdef CONFIG_USB_GADGET_PXA2XX ++#define gadget_is_pxa(g) !strcmp("pxa2xx_udc", (g)->name) ++#else ++#define gadget_is_pxa(g) 0 ++#endif ++ ++#ifdef CONFIG_USB_GADGET_GOKU ++#define gadget_is_goku(g) !strcmp("goku_udc", (g)->name) ++#else ++#define gadget_is_goku(g) 0 ++#endif ++ ++#ifdef CONFIG_USB_GADGET_SUPERH ++#define gadget_is_sh(g) !strcmp("sh_udc", (g)->name) ++#else ++#define gadget_is_sh(g) 0 ++#endif ++ ++#ifdef CONFIG_USB_GADGET_SA1100 ++#define gadget_is_sa1100(g) !strcmp("sa1100_udc", (g)->name) ++#else ++#define gadget_is_sa1100(g) 0 ++#endif ++ ++#ifdef CONFIG_USB_GADGET_LH7A40X ++#define gadget_is_lh7a40x(g) !strcmp("lh7a40x_udc", (g)->name) ++#else ++#define gadget_is_lh7a40x(g) 0 ++#endif ++ ++#ifdef CONFIG_USB_GADGET_MQ11XX ++#define gadget_is_mq11xx(g) !strcmp("mq11xx_udc", (g)->name) ++#else ++#define gadget_is_mq11xx(g) 0 ++#endif ++ ++#ifdef CONFIG_USB_GADGET_OMAP ++#define gadget_is_omap(g) !strcmp("omap_udc", (g)->name) ++#else ++#define gadget_is_omap(g) 0 ++#endif ++ ++#ifdef CONFIG_USB_GADGET_N9604 ++#define gadget_is_n9604(g) !strcmp("n9604_udc", (g)->name) ++#else ++#define gadget_is_n9604(g) 0 ++#endif ++ ++#ifdef CONFIG_USB_GADGET_PXA27X ++#define gadget_is_pxa27x(g) !strcmp("pxa27x_udc", (g)->name) ++#else ++#define gadget_is_pxa27x(g) 0 ++#endif ++ ++#ifdef CONFIG_USB_GADGET_S3C2410 ++#define gadget_is_s3c2410(g) !strcmp("s3c2410_udc", (g)->name) ++#else ++#define gadget_is_s3c2410(g) 0 ++#endif ++ ++#ifdef CONFIG_USB_GADGET_AT91 ++#define gadget_is_at91(g) !strcmp("at91_udc", (g)->name) ++#else ++#define gadget_is_at91(g) 0 ++#endif ++ ++// CONFIG_USB_GADGET_SX2 ++// CONFIG_USB_GADGET_AU1X00 ++// ... ++ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/goku_udc.c kernel/drivers/usb/gadget/goku_udc.c +--- /tmp/kernel/drivers/usb/gadget/goku_udc.c 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/goku_udc.c 2005-04-22 17:53:19.440538539 +0200 +@@ -0,0 +1,1975 @@ ++/* ++ * Toshiba TC86C001 ("Goku-S") USB Device Controller driver ++ * ++ * Copyright (C) 2000-2002 Lineo ++ * by Stuart Lynne, Tom Rushworth, and Bruce Balden ++ * Copyright (C) 2002 Toshiba Corporation ++ * Copyright (C) 2003 MontaVista Software (source@mvista.com) ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++/* ++ * This device has ep0 and three semi-configurable bulk/interrupt endpoints. ++ * ++ * - Endpoint numbering is fixed: ep{1,2,3}-bulk ++ * - Gadget drivers can choose ep maxpacket (8/16/32/64) ++ * - Gadget drivers can choose direction (IN, OUT) ++ * - DMA works with ep1 (OUT transfers) and ep2 (IN transfers). ++ */ ++ ++#undef DEBUG ++// #define VERBOSE /* extra debug messages (success too) */ ++// #define USB_TRACE /* packet-level success messages */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++ ++#include "goku_udc.h" ++ ++#define DRIVER_DESC "TC86C001 USB Device Controller" ++#define DRIVER_VERSION "30-Oct 2003" ++ ++#define DMA_ADDR_INVALID (~(dma_addr_t)0) ++ ++static const char driver_name [] = "goku_udc"; ++static const char driver_desc [] = DRIVER_DESC; ++ ++MODULE_AUTHOR("source@mvista.com"); ++MODULE_DESCRIPTION(DRIVER_DESC); ++MODULE_LICENSE("GPL"); ++ ++ ++/* ++ * IN dma behaves ok under testing, though the IN-dma abort paths don't ++ * seem to behave quite as expected. Used by default. ++ * ++ * OUT dma documents design problems handling the common "short packet" ++ * transfer termination policy; it couldn't enabled by default, even ++ * if the OUT-dma abort problems had a resolution. ++ */ ++static unsigned use_dma = 1; ++ ++#if 0 ++//#include ++/* "modprobe goku_udc use_dma=1" etc ++ * 0 to disable dma ++ * 1 to use IN dma only (normal operation) ++ * 2 to use IN and OUT dma ++ */ ++module_param(use_dma, uint, S_IRUGO); ++#endif ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void nuke(struct goku_ep *, int status); ++ ++static inline void ++command(struct goku_udc_regs *regs, int command, unsigned epnum) ++{ ++ writel(COMMAND_EP(epnum) | command, ®s->Command); ++ udelay(300); ++} ++ ++static int ++goku_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) ++{ ++ struct goku_udc *dev; ++ struct goku_ep *ep; ++ u32 mode; ++ u16 max; ++ unsigned long flags; ++ ++ ep = container_of(_ep, struct goku_ep, ep); ++ if (!_ep || !desc || ep->desc ++ || desc->bDescriptorType != USB_DT_ENDPOINT) ++ return -EINVAL; ++ dev = ep->dev; ++ if (ep == &dev->ep[0]) ++ return -EINVAL; ++ if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) ++ return -ESHUTDOWN; ++ if (ep->num != (desc->bEndpointAddress & 0x0f)) ++ return -EINVAL; ++ ++ switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { ++ case USB_ENDPOINT_XFER_BULK: ++ case USB_ENDPOINT_XFER_INT: ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ if ((readl(ep->reg_status) & EPxSTATUS_EP_MASK) ++ != EPxSTATUS_EP_INVALID) ++ return -EBUSY; ++ ++ /* enabling the no-toggle interrupt mode would need an api hook */ ++ mode = 0; ++ max = le16_to_cpu(get_unaligned(&desc->wMaxPacketSize)); ++ switch (max) { ++ case 64: mode++; ++ case 32: mode++; ++ case 16: mode++; ++ case 8: mode <<= 3; ++ break; ++ default: ++ return -EINVAL; ++ } ++ mode |= 2 << 1; /* bulk, or intr-with-toggle */ ++ ++ /* ep1/ep2 dma direction is chosen early; it works in the other ++ * direction, with pio. be cautious with out-dma. ++ */ ++ ep->is_in = (USB_DIR_IN & desc->bEndpointAddress) != 0; ++ if (ep->is_in) { ++ mode |= 1; ++ ep->dma = (use_dma != 0) && (ep->num == UDC_MSTRD_ENDPOINT); ++ } else { ++ ep->dma = (use_dma == 2) && (ep->num == UDC_MSTWR_ENDPOINT); ++ if (ep->dma) ++ DBG(dev, "%s out-dma hides short packets\n", ++ ep->ep.name); ++ } ++ ++ spin_lock_irqsave(&ep->dev->lock, flags); ++ ++ /* ep1 and ep2 can do double buffering and/or dma */ ++ if (ep->num < 3) { ++ struct goku_udc_regs *regs = ep->dev->regs; ++ u32 tmp; ++ ++ /* double buffer except (for now) with pio in */ ++ tmp = ((ep->dma || !ep->is_in) ++ ? 0x10 /* double buffered */ ++ : 0x11 /* single buffer */ ++ ) << ep->num; ++ tmp |= readl(®s->EPxSingle); ++ writel(tmp, ®s->EPxSingle); ++ ++ tmp = (ep->dma ? 0x10/*dma*/ : 0x11/*pio*/) << ep->num; ++ tmp |= readl(®s->EPxBCS); ++ writel(tmp, ®s->EPxBCS); ++ } ++ writel(mode, ep->reg_mode); ++ command(ep->dev->regs, COMMAND_RESET, ep->num); ++ ep->ep.maxpacket = max; ++ ep->stopped = 0; ++ ep->desc = desc; ++ spin_unlock_irqrestore(&ep->dev->lock, flags); ++ ++ DBG(dev, "enable %s %s %s maxpacket %u\n", ep->ep.name, ++ ep->is_in ? "IN" : "OUT", ++ ep->dma ? "dma" : "pio", ++ max); ++ ++ return 0; ++} ++ ++static void ep_reset(struct goku_udc_regs *regs, struct goku_ep *ep) ++{ ++ struct goku_udc *dev = ep->dev; ++ ++ if (regs) { ++ command(regs, COMMAND_INVALID, ep->num); ++ if (ep->num) { ++ if (ep->num == UDC_MSTWR_ENDPOINT) ++ dev->int_enable &= ~(INT_MSTWREND ++ |INT_MSTWRTMOUT); ++ else if (ep->num == UDC_MSTRD_ENDPOINT) ++ dev->int_enable &= ~INT_MSTRDEND; ++ dev->int_enable &= ~INT_EPxDATASET (ep->num); ++ } else ++ dev->int_enable &= ~INT_EP0; ++ writel(dev->int_enable, ®s->int_enable); ++ readl(®s->int_enable); ++ if (ep->num < 3) { ++ struct goku_udc_regs *regs = ep->dev->regs; ++ u32 tmp; ++ ++ tmp = readl(®s->EPxSingle); ++ tmp &= ~(0x11 << ep->num); ++ writel(tmp, ®s->EPxSingle); ++ ++ tmp = readl(®s->EPxBCS); ++ tmp &= ~(0x11 << ep->num); ++ writel(tmp, ®s->EPxBCS); ++ } ++ /* reset dma in case we're still using it */ ++ if (ep->dma) { ++ u32 master; ++ ++ master = readl(®s->dma_master) & MST_RW_BITS; ++ if (ep->num == UDC_MSTWR_ENDPOINT) { ++ master &= ~MST_W_BITS; ++ master |= MST_WR_RESET; ++ } else { ++ master &= ~MST_R_BITS; ++ master |= MST_RD_RESET; ++ } ++ writel(master, ®s->dma_master); ++ } ++ } ++ ++ ep->ep.maxpacket = MAX_FIFO_SIZE; ++ ep->desc = 0; ++ ep->stopped = 1; ++ ep->irqs = 0; ++ ep->dma = 0; ++} ++ ++static int goku_ep_disable(struct usb_ep *_ep) ++{ ++ struct goku_ep *ep; ++ struct goku_udc *dev; ++ unsigned long flags; ++ ++ ep = container_of(_ep, struct goku_ep, ep); ++ if (!_ep || !ep->desc) ++ return -ENODEV; ++ dev = ep->dev; ++ if (dev->ep0state == EP0_SUSPEND) ++ return -EBUSY; ++ ++ VDBG(dev, "disable %s\n", _ep->name); ++ ++ spin_lock_irqsave(&dev->lock, flags); ++ nuke(ep, -ESHUTDOWN); ++ ep_reset(dev->regs, ep); ++ spin_unlock_irqrestore(&dev->lock, flags); ++ ++ return 0; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static struct usb_request * ++goku_alloc_request(struct usb_ep *_ep, int gfp_flags) ++{ ++ struct goku_request *req; ++ ++ if (!_ep) ++ return 0; ++ req = kmalloc(sizeof *req, gfp_flags); ++ if (!req) ++ return 0; ++ ++ memset(req, 0, sizeof *req); ++ req->req.dma = DMA_ADDR_INVALID; ++ INIT_LIST_HEAD(&req->queue); ++ return &req->req; ++} ++ ++static void ++goku_free_request(struct usb_ep *_ep, struct usb_request *_req) ++{ ++ struct goku_request *req; ++ ++ if (!_ep || !_req) ++ return; ++ ++ req = container_of(_req, struct goku_request, req); ++ WARN_ON(!list_empty(&req->queue)); ++ kfree(req); ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++#undef USE_KMALLOC ++ ++/* many common platforms have dma-coherent caches, which means that it's ++ * safe to use kmalloc() memory for all i/o buffers without using any ++ * cache flushing calls. (unless you're trying to share cache lines ++ * between dma and non-dma activities, which is a slow idea in any case.) ++ * ++ * other platforms need more care, with 2.6 having a moderately general ++ * solution except for the common "buffer is smaller than a page" case. ++ */ ++#if defined(CONFIG_X86) ++#define USE_KMALLOC ++ ++#elif defined(CONFIG_MIPS) && !defined(CONFIG_NONCOHERENT_IO) ++#define USE_KMALLOC ++ ++#elif defined(CONFIG_PPC) && !defined(CONFIG_NOT_COHERENT_CACHE) ++#define USE_KMALLOC ++ ++#endif ++ ++/* allocating buffers this way eliminates dma mapping overhead, which ++ * on some platforms will mean eliminating a per-io buffer copy. with ++ * some kinds of system caches, further tweaks may still be needed. ++ */ ++static void * ++goku_alloc_buffer(struct usb_ep *_ep, unsigned bytes, ++ dma_addr_t *dma, int gfp_flags) ++{ ++ void *retval; ++ struct goku_ep *ep; ++ ++ ep = container_of(_ep, struct goku_ep, ep); ++ if (!_ep) ++ return 0; ++ *dma = DMA_ADDR_INVALID; ++ ++#if defined(USE_KMALLOC) ++ retval = kmalloc(bytes, gfp_flags); ++ if (retval) ++ *dma = virt_to_phys(retval); ++#else ++ if (ep->dma) { ++ /* one problem with this call is that it wastes memory on ++ * typical 1/N page allocations: it allocates 1-N pages. ++ * another is that it always uses GFP_ATOMIC. ++ */ ++#warning Using pci_alloc_consistent even with buffers smaller than a page. ++ retval = pci_alloc_consistent(ep->dev->pdev, bytes, dma); ++ } else ++ retval = kmalloc(bytes, gfp_flags); ++#endif ++ return retval; ++} ++ ++static void ++goku_free_buffer(struct usb_ep *_ep, void *buf, dma_addr_t dma, unsigned bytes) ++{ ++ /* free memory into the right allocator */ ++#ifndef USE_KMALLOC ++ if (dma != DMA_ADDR_INVALID) { ++ struct goku_ep *ep; ++ ++ ep = container_of(_ep, struct goku_ep, ep); ++ if (!_ep) ++ return; ++ /* one problem with this call is that some platforms ++ * don't allow it to be used in_irq(). ++ */ ++ pci_free_consistent(ep->dev->pdev, bytes, buf, dma); ++ } else ++#endif ++ kfree (buf); ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void ++done(struct goku_ep *ep, struct goku_request *req, int status) ++{ ++ struct goku_udc *dev; ++ unsigned stopped = ep->stopped; ++ ++ list_del_init(&req->queue); ++ ++ if (likely(req->req.status == -EINPROGRESS)) ++ req->req.status = status; ++ else ++ status = req->req.status; ++ ++ dev = ep->dev; ++ if (req->mapped) { ++ pci_unmap_single(dev->pdev, req->req.dma, req->req.length, ++ ep->is_in ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); ++ req->req.dma = DMA_ADDR_INVALID; ++ req->mapped = 0; ++ } ++ ++#ifndef USB_TRACE ++ if (status && status != -ESHUTDOWN) ++#endif ++ VDBG(dev, "complete %s req %p stat %d len %u/%u\n", ++ ep->ep.name, &req->req, status, ++ req->req.actual, req->req.length); ++ ++ /* don't modify queue heads during completion callback */ ++ ep->stopped = 1; ++ spin_unlock(&dev->lock); ++ req->req.complete(&ep->ep, &req->req); ++ spin_lock(&dev->lock); ++ ep->stopped = stopped; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static inline int ++write_packet(u32 *fifo, u8 *buf, struct goku_request *req, unsigned max) ++{ ++ unsigned length, count; ++ ++ length = min(req->req.length - req->req.actual, max); ++ req->req.actual += length; ++ ++ count = length; ++ while (likely(count--)) ++ writel(*buf++, fifo); ++ return length; ++} ++ ++// return: 0 = still running, 1 = completed, negative = errno ++static int write_fifo(struct goku_ep *ep, struct goku_request *req) ++{ ++ struct goku_udc *dev = ep->dev; ++ u32 tmp; ++ u8 *buf; ++ unsigned count; ++ int is_last; ++ ++ tmp = readl(&dev->regs->DataSet); ++ buf = req->req.buf + req->req.actual; ++ prefetch(buf); ++ ++ dev = ep->dev; ++ if (unlikely(ep->num == 0 && dev->ep0state != EP0_IN)) ++ return -EL2HLT; ++ ++ /* NOTE: just single-buffered PIO-IN for now. */ ++ if (unlikely((tmp & DATASET_A(ep->num)) != 0)) ++ return 0; ++ ++ /* clear our "packet available" irq */ ++ if (ep->num != 0) ++ writel(~INT_EPxDATASET(ep->num), &dev->regs->int_status); ++ ++ count = write_packet(ep->reg_fifo, buf, req, ep->ep.maxpacket); ++ ++ /* last packet often short (sometimes a zlp, especially on ep0) */ ++ if (unlikely(count != ep->ep.maxpacket)) { ++ writel(~(1<num), &dev->regs->EOP); ++ if (ep->num == 0) { ++ dev->ep[0].stopped = 1; ++ dev->ep0state = EP0_STATUS; ++ } ++ is_last = 1; ++ } else { ++ if (likely(req->req.length != req->req.actual) ++ || req->req.zero) ++ is_last = 0; ++ else ++ is_last = 1; ++ } ++#if 0 /* printk seemed to trash is_last...*/ ++//#ifdef USB_TRACE ++ VDBG(dev, "wrote %s %u bytes%s IN %u left %p\n", ++ ep->ep.name, count, is_last ? "/last" : "", ++ req->req.length - req->req.actual, req); ++#endif ++ ++ /* requests complete when all IN data is in the FIFO, ++ * or sometimes later, if a zlp was needed. ++ */ ++ if (is_last) { ++ done(ep, req, 0); ++ return 1; ++ } ++ ++ return 0; ++} ++ ++static int read_fifo(struct goku_ep *ep, struct goku_request *req) ++{ ++ struct goku_udc_regs *regs; ++ u32 size, set; ++ u8 *buf; ++ unsigned bufferspace, is_short, dbuff; ++ ++ regs = ep->dev->regs; ++top: ++ buf = req->req.buf + req->req.actual; ++ prefetchw(buf); ++ ++ if (unlikely(ep->num == 0 && ep->dev->ep0state != EP0_OUT)) ++ return -EL2HLT; ++ ++ dbuff = (ep->num == 1 || ep->num == 2); ++ do { ++ /* ack dataset irq matching the status we'll handle */ ++ if (ep->num != 0) ++ writel(~INT_EPxDATASET(ep->num), ®s->int_status); ++ ++ set = readl(®s->DataSet) & DATASET_AB(ep->num); ++ size = readl(®s->EPxSizeLA[ep->num]); ++ bufferspace = req->req.length - req->req.actual; ++ ++ /* usually do nothing without an OUT packet */ ++ if (likely(ep->num != 0 || bufferspace != 0)) { ++ if (unlikely(set == 0)) ++ break; ++ /* use ep1/ep2 double-buffering for OUT */ ++ if (!(size & PACKET_ACTIVE)) ++ size = readl(®s->EPxSizeLB[ep->num]); ++ if (!(size & PACKET_ACTIVE)) // "can't happen" ++ break; ++ size &= DATASIZE; /* EPxSizeH == 0 */ ++ ++ /* ep0out no-out-data case for set_config, etc */ ++ } else ++ size = 0; ++ ++ /* read all bytes from this packet */ ++ req->req.actual += size; ++ is_short = (size < ep->ep.maxpacket); ++#ifdef USB_TRACE ++ VDBG(ep->dev, "read %s %u bytes%s OUT req %p %u/%u\n", ++ ep->ep.name, size, is_short ? "/S" : "", ++ req, req->req.actual, req->req.length); ++#endif ++ while (likely(size-- != 0)) { ++ u8 byte = (u8) readl(ep->reg_fifo); ++ ++ if (unlikely(bufferspace == 0)) { ++ /* this happens when the driver's buffer ++ * is smaller than what the host sent. ++ * discard the extra data in this packet. ++ */ ++ if (req->req.status != -EOVERFLOW) ++ DBG(ep->dev, "%s overflow %u\n", ++ ep->ep.name, size); ++ req->req.status = -EOVERFLOW; ++ } else { ++ *buf++ = byte; ++ bufferspace--; ++ } ++ } ++ ++ /* completion */ ++ if (unlikely(is_short || req->req.actual == req->req.length)) { ++ if (unlikely(ep->num == 0)) { ++ /* non-control endpoints now usable? */ ++ if (ep->dev->req_config) ++ writel(ep->dev->configured ++ ? USBSTATE_CONFIGURED ++ : 0, ++ ®s->UsbState); ++ /* ep0out status stage */ ++ writel(~(1<<0), ®s->EOP); ++ ep->stopped = 1; ++ ep->dev->ep0state = EP0_STATUS; ++ } ++ done(ep, req, 0); ++ ++ /* empty the second buffer asap */ ++ if (dbuff && !list_empty(&ep->queue)) { ++ req = list_entry(ep->queue.next, ++ struct goku_request, queue); ++ goto top; ++ } ++ return 1; ++ } ++ } while (dbuff); ++ return 0; ++} ++ ++static inline void ++pio_irq_enable(struct goku_udc *dev, struct goku_udc_regs *regs, int epnum) ++{ ++ dev->int_enable |= INT_EPxDATASET (epnum); ++ writel(dev->int_enable, ®s->int_enable); ++ /* write may still be posted */ ++} ++ ++static inline void ++pio_irq_disable(struct goku_udc *dev, struct goku_udc_regs *regs, int epnum) ++{ ++ dev->int_enable &= ~INT_EPxDATASET (epnum); ++ writel(dev->int_enable, ®s->int_enable); ++ /* write may still be posted */ ++} ++ ++static inline void ++pio_advance(struct goku_ep *ep) ++{ ++ struct goku_request *req; ++ ++ if (unlikely(list_empty (&ep->queue))) ++ return; ++ req = list_entry(ep->queue.next, struct goku_request, queue); ++ (ep->is_in ? write_fifo : read_fifo)(ep, req); ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++// return: 0 = q running, 1 = q stopped, negative = errno ++static int start_dma(struct goku_ep *ep, struct goku_request *req) ++{ ++ struct goku_udc_regs *regs = ep->dev->regs; ++ u32 master; ++ u32 start = req->req.dma; ++ u32 end = start + req->req.length - 1; ++ ++ master = readl(®s->dma_master) & MST_RW_BITS; ++ ++ /* re-init the bits affecting IN dma; careful with zlps */ ++ if (likely(ep->is_in)) { ++ if (unlikely(master & MST_RD_ENA)) { ++ DBG (ep->dev, "start, IN active dma %03x!!\n", ++ master); ++// return -EL2HLT; ++ } ++ writel(end, ®s->in_dma_end); ++ writel(start, ®s->in_dma_start); ++ ++ master &= ~MST_R_BITS; ++ if (unlikely(req->req.length == 0)) ++ master = MST_RD_ENA | MST_RD_EOPB; ++ else if ((req->req.length % ep->ep.maxpacket) != 0 ++ || req->req.zero) ++ master = MST_RD_ENA | MST_EOPB_ENA; ++ else ++ master = MST_RD_ENA | MST_EOPB_DIS; ++ ++ ep->dev->int_enable |= INT_MSTRDEND; ++ ++ /* Goku DMA-OUT merges short packets, which plays poorly with ++ * protocols where short packets mark the transfer boundaries. ++ * The chip supports a nonstandard policy with INT_MSTWRTMOUT, ++ * ending transfers after 3 SOFs; we don't turn it on. ++ */ ++ } else { ++ if (unlikely(master & MST_WR_ENA)) { ++ DBG (ep->dev, "start, OUT active dma %03x!!\n", ++ master); ++// return -EL2HLT; ++ } ++ writel(end, ®s->out_dma_end); ++ writel(start, ®s->out_dma_start); ++ ++ master &= ~MST_W_BITS; ++ master |= MST_WR_ENA | MST_TIMEOUT_DIS; ++ ++ ep->dev->int_enable |= INT_MSTWREND|INT_MSTWRTMOUT; ++ } ++ ++ writel(master, ®s->dma_master); ++ writel(ep->dev->int_enable, ®s->int_enable); ++ return 0; ++} ++ ++static void dma_advance(struct goku_udc *dev, struct goku_ep *ep) ++{ ++ struct goku_request *req; ++ struct goku_udc_regs *regs = ep->dev->regs; ++ u32 master; ++ ++ master = readl(®s->dma_master); ++ ++ if (unlikely(list_empty(&ep->queue))) { ++stop: ++ if (ep->is_in) ++ dev->int_enable &= ~INT_MSTRDEND; ++ else ++ dev->int_enable &= ~(INT_MSTWREND|INT_MSTWRTMOUT); ++ writel(dev->int_enable, ®s->int_enable); ++ return; ++ } ++ req = list_entry(ep->queue.next, struct goku_request, queue); ++ ++ /* normal hw dma completion (not abort) */ ++ if (likely(ep->is_in)) { ++ if (unlikely(master & MST_RD_ENA)) ++ return; ++ req->req.actual = readl(®s->in_dma_current); ++ } else { ++ if (unlikely(master & MST_WR_ENA)) ++ return; ++ ++ /* hardware merges short packets, and also hides packet ++ * overruns. a partial packet MAY be in the fifo here. ++ */ ++ req->req.actual = readl(®s->out_dma_current); ++ } ++ req->req.actual -= req->req.dma; ++ req->req.actual++; ++ ++#ifdef USB_TRACE ++ VDBG(dev, "done %s %s dma, %u/%u bytes, req %p\n", ++ ep->ep.name, ep->is_in ? "IN" : "OUT", ++ req->req.actual, req->req.length, req); ++#endif ++ done(ep, req, 0); ++ if (list_empty(&ep->queue)) ++ goto stop; ++ req = list_entry(ep->queue.next, struct goku_request, queue); ++ (void) start_dma(ep, req); ++} ++ ++static void abort_dma(struct goku_ep *ep, int status) ++{ ++ struct goku_udc_regs *regs = ep->dev->regs; ++ struct goku_request *req; ++ u32 curr, master; ++ ++ /* NAK future host requests, hoping the implicit delay lets the ++ * dma engine finish reading (or writing) its latest packet and ++ * empty the dma buffer (up to 16 bytes). ++ * ++ * This avoids needing to clean up a partial packet in the fifo; ++ * we can't do that for IN without side effects to HALT and TOGGLE. ++ */ ++ command(regs, COMMAND_FIFO_DISABLE, ep->num); ++ req = list_entry(ep->queue.next, struct goku_request, queue); ++ master = readl(®s->dma_master) & MST_RW_BITS; ++ ++ /* FIXME using these resets isn't usably documented. this may ++ * not work unless it's followed by disabling the endpoint. ++ * ++ * FIXME the OUT reset path doesn't even behave consistently. ++ */ ++ if (ep->is_in) { ++ if (unlikely((readl(®s->dma_master) & MST_RD_ENA) == 0)) ++ goto finished; ++ curr = readl(®s->in_dma_current); ++ ++ writel(curr, ®s->in_dma_end); ++ writel(curr, ®s->in_dma_start); ++ ++ master &= ~MST_R_BITS; ++ master |= MST_RD_RESET; ++ writel(master, ®s->dma_master); ++ ++ if (readl(®s->dma_master) & MST_RD_ENA) ++ DBG(ep->dev, "IN dma active after reset!\n"); ++ ++ } else { ++ if (unlikely((readl(®s->dma_master) & MST_WR_ENA) == 0)) ++ goto finished; ++ curr = readl(®s->out_dma_current); ++ ++ writel(curr, ®s->out_dma_end); ++ writel(curr, ®s->out_dma_start); ++ ++ master &= ~MST_W_BITS; ++ master |= MST_WR_RESET; ++ writel(master, ®s->dma_master); ++ ++ if (readl(®s->dma_master) & MST_WR_ENA) ++ DBG(ep->dev, "OUT dma active after reset!\n"); ++ } ++ req->req.actual = (curr - req->req.dma) + 1; ++ req->req.status = status; ++ ++ VDBG(ep->dev, "%s %s %s %d/%d\n", __FUNCTION__, ep->ep.name, ++ ep->is_in ? "IN" : "OUT", ++ req->req.actual, req->req.length); ++ ++ command(regs, COMMAND_FIFO_ENABLE, ep->num); ++ ++ return; ++ ++finished: ++ /* dma already completed; no abort needed */ ++ command(regs, COMMAND_FIFO_ENABLE, ep->num); ++ req->req.actual = req->req.length; ++ req->req.status = 0; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int ++goku_queue(struct usb_ep *_ep, struct usb_request *_req, int gfp_flags) ++{ ++ struct goku_request *req; ++ struct goku_ep *ep; ++ struct goku_udc *dev; ++ unsigned long flags; ++ int status; ++ ++ /* always require a cpu-view buffer so pio works */ ++ req = container_of(_req, struct goku_request, req); ++ if (unlikely(!_req || !_req->complete ++ || !_req->buf || !list_empty(&req->queue))) ++ return -EINVAL; ++ ep = container_of(_ep, struct goku_ep, ep); ++ if (unlikely(!_ep || (!ep->desc && ep->num != 0))) ++ return -EINVAL; ++ dev = ep->dev; ++ if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) ++ return -ESHUTDOWN; ++ ++ /* can't touch registers when suspended */ ++ if (dev->ep0state == EP0_SUSPEND) ++ return -EBUSY; ++ ++ /* set up dma mapping in case the caller didn't */ ++ if (ep->dma && _req->dma == DMA_ADDR_INVALID) { ++ _req->dma = pci_map_single(dev->pdev, _req->buf, _req->length, ++ ep->is_in ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); ++ req->mapped = 1; ++ } ++ ++#ifdef USB_TRACE ++ VDBG(dev, "%s queue req %p, len %u buf %p\n", ++ _ep->name, _req, _req->length, _req->buf); ++#endif ++ ++ spin_lock_irqsave(&dev->lock, flags); ++ ++ _req->status = -EINPROGRESS; ++ _req->actual = 0; ++ ++ /* for ep0 IN without premature status, zlp is required and ++ * writing EOP starts the status stage (OUT). ++ */ ++ if (unlikely(ep->num == 0 && ep->is_in)) ++ _req->zero = 1; ++ ++ /* kickstart this i/o queue? */ ++ status = 0; ++ if (list_empty(&ep->queue) && likely(!ep->stopped)) { ++ /* dma: done after dma completion IRQ (or error) ++ * pio: done after last fifo operation ++ */ ++ if (ep->dma) ++ status = start_dma(ep, req); ++ else ++ status = (ep->is_in ? write_fifo : read_fifo)(ep, req); ++ ++ if (unlikely(status != 0)) { ++ if (status > 0) ++ status = 0; ++ req = 0; ++ } ++ ++ } /* else pio or dma irq handler advances the queue. */ ++ ++ if (likely(req != 0)) ++ list_add_tail(&req->queue, &ep->queue); ++ ++ if (likely(!list_empty(&ep->queue)) ++ && likely(ep->num != 0) ++ && !ep->dma ++ && !(dev->int_enable & INT_EPxDATASET (ep->num))) ++ pio_irq_enable(dev, dev->regs, ep->num); ++ ++ spin_unlock_irqrestore(&dev->lock, flags); ++ ++ /* pci writes may still be posted */ ++ return status; ++} ++ ++/* dequeue ALL requests */ ++static void nuke(struct goku_ep *ep, int status) ++{ ++ struct goku_request *req; ++ ++ ep->stopped = 1; ++ if (list_empty(&ep->queue)) ++ return; ++ if (ep->dma) ++ abort_dma(ep, status); ++ while (!list_empty(&ep->queue)) { ++ req = list_entry(ep->queue.next, struct goku_request, queue); ++ done(ep, req, status); ++ } ++} ++ ++/* dequeue JUST ONE request */ ++static int goku_dequeue(struct usb_ep *_ep, struct usb_request *_req) ++{ ++ struct goku_request *req; ++ struct goku_ep *ep; ++ struct goku_udc *dev; ++ unsigned long flags; ++ ++ ep = container_of(_ep, struct goku_ep, ep); ++ if (!_ep || !_req || (!ep->desc && ep->num != 0)) ++ return -EINVAL; ++ dev = ep->dev; ++ if (!dev->driver) ++ return -ESHUTDOWN; ++ ++ /* we can't touch (dma) registers when suspended */ ++ if (dev->ep0state == EP0_SUSPEND) ++ return -EBUSY; ++ ++ VDBG(dev, "%s %s %s %s %p\n", __FUNCTION__, _ep->name, ++ ep->is_in ? "IN" : "OUT", ++ ep->dma ? "dma" : "pio", ++ _req); ++ ++ spin_lock_irqsave(&dev->lock, flags); ++ ++ /* make sure it's actually queued on this endpoint */ ++ list_for_each_entry (req, &ep->queue, queue) { ++ if (&req->req == _req) ++ break; ++ } ++ if (&req->req != _req) { ++ spin_unlock_irqrestore (&dev->lock, flags); ++ return -EINVAL; ++ } ++ ++ if (ep->dma && ep->queue.next == &req->queue && !ep->stopped) { ++ abort_dma(ep, -ECONNRESET); ++ done(ep, req, -ECONNRESET); ++ dma_advance(dev, ep); ++ } else if (!list_empty(&req->queue)) ++ done(ep, req, -ECONNRESET); ++ else ++ req = 0; ++ spin_unlock_irqrestore(&dev->lock, flags); ++ ++ return req ? 0 : -EOPNOTSUPP; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void goku_clear_halt(struct goku_ep *ep) ++{ ++ // assert (ep->num !=0) ++ VDBG(ep->dev, "%s clear halt\n", ep->ep.name); ++ command(ep->dev->regs, COMMAND_SETDATA0, ep->num); ++ command(ep->dev->regs, COMMAND_STALL_CLEAR, ep->num); ++ if (ep->stopped) { ++ ep->stopped = 0; ++ if (ep->dma) { ++ struct goku_request *req; ++ ++ if (list_empty(&ep->queue)) ++ return; ++ req = list_entry(ep->queue.next, struct goku_request, ++ queue); ++ (void) start_dma(ep, req); ++ } else ++ pio_advance(ep); ++ } ++} ++ ++static int goku_set_halt(struct usb_ep *_ep, int value) ++{ ++ struct goku_ep *ep; ++ unsigned long flags; ++ int retval = 0; ++ ++ if (!_ep) ++ return -ENODEV; ++ ep = container_of (_ep, struct goku_ep, ep); ++ ++ if (ep->num == 0) { ++ if (value) { ++ ep->dev->ep0state = EP0_STALL; ++ ep->dev->ep[0].stopped = 1; ++ } else ++ return -EINVAL; ++ ++ /* don't change EPxSTATUS_EP_INVALID to READY */ ++ } else if (!ep->desc) { ++ DBG(ep->dev, "%s %s inactive?\n", __FUNCTION__, ep->ep.name); ++ return -EINVAL; ++ } ++ ++ spin_lock_irqsave(&ep->dev->lock, flags); ++ if (!list_empty(&ep->queue)) ++ retval = -EAGAIN; ++ else if (ep->is_in && value ++ /* data in (either) packet buffer? */ ++ && (ep->dev->regs->DataSet & DATASET_AB(ep->num))) ++ retval = -EAGAIN; ++ else if (!value) ++ goku_clear_halt(ep); ++ else { ++ ep->stopped = 1; ++ VDBG(ep->dev, "%s set halt\n", ep->ep.name); ++ command(ep->dev->regs, COMMAND_STALL, ep->num); ++ readl(ep->reg_status); ++ } ++ spin_unlock_irqrestore(&ep->dev->lock, flags); ++ return retval; ++} ++ ++static int goku_fifo_status(struct usb_ep *_ep) ++{ ++ struct goku_ep *ep; ++ struct goku_udc_regs *regs; ++ u32 size; ++ ++ if (!_ep) ++ return -ENODEV; ++ ep = container_of(_ep, struct goku_ep, ep); ++ ++ /* size is only reported sanely for OUT */ ++ if (ep->is_in) ++ return -EOPNOTSUPP; ++ ++ /* ignores 16-byte dma buffer; SizeH == 0 */ ++ regs = ep->dev->regs; ++ size = readl(®s->EPxSizeLA[ep->num]) & DATASIZE; ++ size += readl(®s->EPxSizeLB[ep->num]) & DATASIZE; ++ VDBG(ep->dev, "%s %s %u\n", __FUNCTION__, ep->ep.name, size); ++ return size; ++} ++ ++static void goku_fifo_flush(struct usb_ep *_ep) ++{ ++ struct goku_ep *ep; ++ struct goku_udc_regs *regs; ++ u32 size; ++ ++ if (!_ep) ++ return; ++ ep = container_of(_ep, struct goku_ep, ep); ++ VDBG(ep->dev, "%s %s\n", __FUNCTION__, ep->ep.name); ++ ++ /* don't change EPxSTATUS_EP_INVALID to READY */ ++ if (!ep->desc && ep->num != 0) { ++ DBG(ep->dev, "%s %s inactive?\n", __FUNCTION__, ep->ep.name); ++ return; ++ } ++ ++ regs = ep->dev->regs; ++ size = readl(®s->EPxSizeLA[ep->num]); ++ size &= DATASIZE; ++ ++ /* Non-desirable behavior: FIFO_CLEAR also clears the ++ * endpoint halt feature. For OUT, we _could_ just read ++ * the bytes out (PIO, if !ep->dma); for in, no choice. ++ */ ++ if (size) ++ command(regs, COMMAND_FIFO_CLEAR, ep->num); ++} ++ ++static struct usb_ep_ops goku_ep_ops = { ++ .enable = goku_ep_enable, ++ .disable = goku_ep_disable, ++ ++ .alloc_request = goku_alloc_request, ++ .free_request = goku_free_request, ++ ++ .alloc_buffer = goku_alloc_buffer, ++ .free_buffer = goku_free_buffer, ++ ++ .queue = goku_queue, ++ .dequeue = goku_dequeue, ++ ++ .set_halt = goku_set_halt, ++ .fifo_status = goku_fifo_status, ++ .fifo_flush = goku_fifo_flush, ++}; ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int goku_get_frame(struct usb_gadget *_gadget) ++{ ++ return -EOPNOTSUPP; ++} ++ ++static const struct usb_gadget_ops goku_ops = { ++ .get_frame = goku_get_frame, ++ // no remote wakeup ++ // not selfpowered ++}; ++ ++/*-------------------------------------------------------------------------*/ ++ ++static inline char *dmastr(void) ++{ ++ if (use_dma == 0) ++ return "(dma disabled)"; ++ else if (use_dma == 2) ++ return "(dma IN and OUT)"; ++ else ++ return "(dma IN)"; ++} ++ ++/* if we're trying to save space, don't bother with this proc file */ ++ ++#if defined(CONFIG_PROC_FS) && !defined(CONFIG_EMBEDDED) ++# define UDC_PROC_FILE ++#endif ++ ++#ifdef UDC_PROC_FILE ++ ++static const char proc_node_name [] = "driver/udc"; ++ ++#define FOURBITS "%s%s%s%s" ++#define EIGHTBITS FOURBITS FOURBITS ++ ++static void ++dump_intmask(const char *label, u32 mask, char **next, unsigned *size) ++{ ++ int t; ++ ++ /* int_status is the same format ... */ ++ t = snprintf(*next, *size, ++ "%s %05X =" FOURBITS EIGHTBITS EIGHTBITS "\n", ++ label, mask, ++ (mask & INT_PWRDETECT) ? " power" : "", ++ (mask & INT_SYSERROR) ? " sys" : "", ++ (mask & INT_MSTRDEND) ? " in-dma" : "", ++ (mask & INT_MSTWRTMOUT) ? " wrtmo" : "", ++ ++ (mask & INT_MSTWREND) ? " out-dma" : "", ++ (mask & INT_MSTWRSET) ? " wrset" : "", ++ (mask & INT_ERR) ? " err" : "", ++ (mask & INT_SOF) ? " sof" : "", ++ ++ (mask & INT_EP3NAK) ? " ep3nak" : "", ++ (mask & INT_EP2NAK) ? " ep2nak" : "", ++ (mask & INT_EP1NAK) ? " ep1nak" : "", ++ (mask & INT_EP3DATASET) ? " ep3" : "", ++ ++ (mask & INT_EP2DATASET) ? " ep2" : "", ++ (mask & INT_EP1DATASET) ? " ep1" : "", ++ (mask & INT_STATUSNAK) ? " ep0snak" : "", ++ (mask & INT_STATUS) ? " ep0status" : "", ++ ++ (mask & INT_SETUP) ? " setup" : "", ++ (mask & INT_ENDPOINT0) ? " ep0" : "", ++ (mask & INT_USBRESET) ? " reset" : "", ++ (mask & INT_SUSPEND) ? " suspend" : ""); ++ *size -= t; ++ *next += t; ++} ++ ++ ++static int ++udc_proc_read(char *buffer, char **start, off_t off, int count, ++ int *eof, void *_dev) ++{ ++ char *buf = buffer; ++ struct goku_udc *dev = _dev; ++ struct goku_udc_regs *regs = dev->regs; ++ char *next = buf; ++ unsigned size = count; ++ unsigned long flags; ++ int i, t, is_usb_connected; ++ u32 tmp; ++ ++ if (off != 0) ++ return 0; ++ ++ local_irq_save(flags); ++ ++ /* basic device status */ ++ tmp = readl(®s->power_detect); ++ is_usb_connected = tmp & PW_DETECT; ++ t = snprintf(next, size, ++ "%s - %s\n" ++ "%s version: %s %s\n" ++ "Gadget driver: %s\n" ++ "Host %s, %s\n" ++ "\n", ++ pci_name(dev->pdev), driver_desc, ++ driver_name, DRIVER_VERSION, dmastr(), ++ dev->driver ? dev->driver->driver.name : "(none)", ++ is_usb_connected ++ ? ((tmp & PW_PULLUP) ? "full speed" : "powered") ++ : "disconnected", ++ ({char *tmp; ++ switch(dev->ep0state){ ++ case EP0_DISCONNECT: tmp = "ep0_disconnect"; break; ++ case EP0_IDLE: tmp = "ep0_idle"; break; ++ case EP0_IN: tmp = "ep0_in"; break; ++ case EP0_OUT: tmp = "ep0_out"; break; ++ case EP0_STATUS: tmp = "ep0_status"; break; ++ case EP0_STALL: tmp = "ep0_stall"; break; ++ case EP0_SUSPEND: tmp = "ep0_suspend"; break; ++ default: tmp = "ep0_?"; break; ++ } tmp; }) ++ ); ++ size -= t; ++ next += t; ++ ++ dump_intmask("int_status", readl(®s->int_status), &next, &size); ++ dump_intmask("int_enable", readl(®s->int_enable), &next, &size); ++ ++ if (!is_usb_connected || !dev->driver || (tmp & PW_PULLUP) == 0) ++ goto done; ++ ++ /* registers for (active) device and ep0 */ ++ t = snprintf(next, size, "\nirqs %lu\ndataset %02x " ++ "single.bcs %02x.%02x state %x addr %u\n", ++ dev->irqs, readl(®s->DataSet), ++ readl(®s->EPxSingle), readl(®s->EPxBCS), ++ readl(®s->UsbState), ++ readl(®s->address)); ++ size -= t; ++ next += t; ++ ++ tmp = readl(®s->dma_master); ++ t = snprintf(next, size, ++ "dma %03X =" EIGHTBITS "%s %s\n", tmp, ++ (tmp & MST_EOPB_DIS) ? " eopb-" : "", ++ (tmp & MST_EOPB_ENA) ? " eopb+" : "", ++ (tmp & MST_TIMEOUT_DIS) ? " tmo-" : "", ++ (tmp & MST_TIMEOUT_ENA) ? " tmo+" : "", ++ ++ (tmp & MST_RD_EOPB) ? " eopb" : "", ++ (tmp & MST_RD_RESET) ? " in_reset" : "", ++ (tmp & MST_WR_RESET) ? " out_reset" : "", ++ (tmp & MST_RD_ENA) ? " IN" : "", ++ ++ (tmp & MST_WR_ENA) ? " OUT" : "", ++ (tmp & MST_CONNECTION) ++ ? "ep1in/ep2out" ++ : "ep1out/ep2in"); ++ size -= t; ++ next += t; ++ ++ /* dump endpoint queues */ ++ for (i = 0; i < 4; i++) { ++ struct goku_ep *ep = &dev->ep [i]; ++ struct goku_request *req; ++ int t; ++ ++ if (i && !ep->desc) ++ continue; ++ ++ tmp = readl(ep->reg_status); ++ t = snprintf(next, size, ++ "%s %s max %u %s, irqs %lu, " ++ "status %02x (%s) " FOURBITS "\n", ++ ep->ep.name, ++ ep->is_in ? "in" : "out", ++ ep->ep.maxpacket, ++ ep->dma ? "dma" : "pio", ++ ep->irqs, ++ tmp, ({ char *s; ++ switch (tmp & EPxSTATUS_EP_MASK) { ++ case EPxSTATUS_EP_READY: ++ s = "ready"; break; ++ case EPxSTATUS_EP_DATAIN: ++ s = "packet"; break; ++ case EPxSTATUS_EP_FULL: ++ s = "full"; break; ++ case EPxSTATUS_EP_TX_ERR: // host will retry ++ s = "tx_err"; break; ++ case EPxSTATUS_EP_RX_ERR: ++ s = "rx_err"; break; ++ case EPxSTATUS_EP_BUSY: /* ep0 only */ ++ s = "busy"; break; ++ case EPxSTATUS_EP_STALL: ++ s = "stall"; break; ++ case EPxSTATUS_EP_INVALID: // these "can't happen" ++ s = "invalid"; break; ++ default: ++ s = "?"; break; ++ }; s; }), ++ (tmp & EPxSTATUS_TOGGLE) ? "data1" : "data0", ++ (tmp & EPxSTATUS_SUSPEND) ? " suspend" : "", ++ (tmp & EPxSTATUS_FIFO_DISABLE) ? " disable" : "", ++ (tmp & EPxSTATUS_STAGE_ERROR) ? " ep0stat" : "" ++ ); ++ if (t <= 0 || t > size) ++ goto done; ++ size -= t; ++ next += t; ++ ++ if (list_empty(&ep->queue)) { ++ t = snprintf(next, size, "\t(nothing queued)\n"); ++ if (t <= 0 || t > size) ++ goto done; ++ size -= t; ++ next += t; ++ continue; ++ } ++ list_for_each_entry(req, &ep->queue, queue) { ++ if (ep->dma && req->queue.prev == &ep->queue) { ++ if (i == UDC_MSTRD_ENDPOINT) ++ tmp = readl(®s->in_dma_current); ++ else ++ tmp = readl(®s->out_dma_current); ++ tmp -= req->req.dma; ++ tmp++; ++ } else ++ tmp = req->req.actual; ++ ++ t = snprintf(next, size, ++ "\treq %p len %u/%u buf %p\n", ++ &req->req, tmp, req->req.length, ++ req->req.buf); ++ if (t <= 0 || t > size) ++ goto done; ++ size -= t; ++ next += t; ++ } ++ } ++ ++done: ++ local_irq_restore(flags); ++ *eof = 1; ++ return count - size; ++} ++ ++#endif /* UDC_PROC_FILE */ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void udc_reinit (struct goku_udc *dev) ++{ ++ static char *names [] = { "ep0", "ep1-bulk", "ep2-bulk", "ep3-bulk" }; ++ ++ unsigned i; ++ ++ INIT_LIST_HEAD (&dev->gadget.ep_list); ++ dev->gadget.ep0 = &dev->ep [0].ep; ++ dev->gadget.speed = USB_SPEED_UNKNOWN; ++ dev->ep0state = EP0_DISCONNECT; ++ dev->irqs = 0; ++ ++ for (i = 0; i < 4; i++) { ++ struct goku_ep *ep = &dev->ep[i]; ++ ++ ep->num = i; ++ ep->ep.name = names[i]; ++ ep->reg_fifo = &dev->regs->ep_fifo [i]; ++ ep->reg_status = &dev->regs->ep_status [i]; ++ ep->reg_mode = &dev->regs->ep_mode[i]; ++ ++ ep->ep.ops = &goku_ep_ops; ++ list_add_tail (&ep->ep.ep_list, &dev->gadget.ep_list); ++ ep->dev = dev; ++ INIT_LIST_HEAD (&ep->queue); ++ ++ ep_reset(0, ep); ++ } ++ ++ dev->ep[0].reg_mode = 0; ++ dev->ep[0].ep.maxpacket = MAX_EP0_SIZE; ++ list_del_init (&dev->ep[0].ep.ep_list); ++} ++ ++static void udc_reset(struct goku_udc *dev) ++{ ++ struct goku_udc_regs *regs = dev->regs; ++ ++ writel(0, ®s->power_detect); ++ writel(0, ®s->int_enable); ++ readl(®s->int_enable); ++ dev->int_enable = 0; ++ ++ /* deassert reset, leave USB D+ at hi-Z (no pullup) ++ * don't let INT_PWRDETECT sequence begin ++ */ ++ udelay(250); ++ writel(PW_RESETB, ®s->power_detect); ++ readl(®s->int_enable); ++} ++ ++static void ep0_start(struct goku_udc *dev) ++{ ++ struct goku_udc_regs *regs = dev->regs; ++ unsigned i; ++ ++ VDBG(dev, "%s\n", __FUNCTION__); ++ ++ udc_reset(dev); ++ udc_reinit (dev); ++ //writel(MST_EOPB_ENA | MST_TIMEOUT_ENA, ®s->dma_master); ++ ++ /* hw handles set_address, set_feature, get_status; maybe more */ ++ writel( G_REQMODE_SET_INTF | G_REQMODE_GET_INTF ++ | G_REQMODE_SET_CONF | G_REQMODE_GET_CONF ++ | G_REQMODE_GET_DESC ++ | G_REQMODE_CLEAR_FEAT ++ , ®s->reqmode); ++ ++ for (i = 0; i < 4; i++) ++ dev->ep[i].irqs = 0; ++ ++ /* can't modify descriptors after writing UsbReady */ ++ for (i = 0; i < DESC_LEN; i++) ++ writel(0, ®s->descriptors[i]); ++ writel(0, ®s->UsbReady); ++ ++ /* expect ep0 requests when the host drops reset */ ++ writel(PW_RESETB | PW_PULLUP, ®s->power_detect); ++ dev->int_enable = INT_DEVWIDE | INT_EP0; ++ writel(dev->int_enable, &dev->regs->int_enable); ++ readl(®s->int_enable); ++ dev->gadget.speed = USB_SPEED_FULL; ++ dev->ep0state = EP0_IDLE; ++} ++ ++static void udc_enable(struct goku_udc *dev) ++{ ++ /* start enumeration now, or after power detect irq */ ++ if (readl(&dev->regs->power_detect) & PW_DETECT) ++ ep0_start(dev); ++ else { ++ DBG(dev, "%s\n", __FUNCTION__); ++ dev->int_enable = INT_PWRDETECT; ++ writel(dev->int_enable, &dev->regs->int_enable); ++ } ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* keeping it simple: ++ * - one bus driver, initted first; ++ * - one function driver, initted second ++ */ ++ ++static struct goku_udc *the_controller; ++ ++/* when a driver is successfully registered, it will receive ++ * control requests including set_configuration(), which enables ++ * non-control requests. then usb traffic follows until a ++ * disconnect is reported. then a host may connect again, or ++ * the driver might get unbound. ++ */ ++int usb_gadget_register_driver(struct usb_gadget_driver *driver) ++{ ++ struct goku_udc *dev = the_controller; ++ int retval; ++ ++ if (!driver ++ || driver->speed != USB_SPEED_FULL ++ || !driver->bind ++ || !driver->unbind ++ || !driver->disconnect ++ || !driver->setup) ++ return -EINVAL; ++ if (!dev) ++ return -ENODEV; ++ if (dev->driver) ++ return -EBUSY; ++ ++ /* hook up the driver */ ++ dev->driver = driver; ++ retval = driver->bind(&dev->gadget); ++ if (retval) { ++ DBG(dev, "bind to driver %s --> error %d\n", ++ driver->driver.name, retval); ++ dev->driver = 0; ++ return retval; ++ } ++ ++ /* then enable host detection and ep0; and we're ready ++ * for set_configuration as well as eventual disconnect. ++ */ ++ udc_enable(dev); ++ ++ DBG(dev, "registered gadget driver '%s'\n", driver->driver.name); ++ return 0; ++} ++EXPORT_SYMBOL(usb_gadget_register_driver); ++ ++static void ++stop_activity(struct goku_udc *dev, struct usb_gadget_driver *driver) ++{ ++ unsigned i; ++ ++ DBG (dev, "%s\n", __FUNCTION__); ++ ++ if (dev->gadget.speed == USB_SPEED_UNKNOWN) ++ driver = 0; ++ ++ /* disconnect gadget driver after quiesceing hw and the driver */ ++ udc_reset (dev); ++ for (i = 0; i < 4; i++) ++ nuke(&dev->ep [i], -ESHUTDOWN); ++ if (driver) { ++ spin_unlock(&dev->lock); ++ driver->disconnect(&dev->gadget); ++ spin_lock(&dev->lock); ++ } ++ ++ if (dev->driver) ++ udc_enable(dev); ++} ++ ++int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) ++{ ++ struct goku_udc *dev = the_controller; ++ unsigned long flags; ++ ++ if (!dev) ++ return -ENODEV; ++ if (!driver || driver != dev->driver) ++ return -EINVAL; ++ ++ spin_lock_irqsave(&dev->lock, flags); ++ dev->driver = 0; ++ stop_activity(dev, driver); ++ spin_unlock_irqrestore(&dev->lock, flags); ++ ++ driver->unbind(&dev->gadget); ++ ++ DBG(dev, "unregistered driver '%s'\n", driver->driver.name); ++ return 0; ++} ++EXPORT_SYMBOL(usb_gadget_unregister_driver); ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void ep0_setup(struct goku_udc *dev) ++{ ++ struct goku_udc_regs *regs = dev->regs; ++ struct usb_ctrlrequest ctrl; ++ int tmp; ++ ++ /* read SETUP packet and enter DATA stage */ ++ ctrl.bRequestType = readl(®s->bRequestType); ++ ctrl.bRequest = readl(®s->bRequest); ++ ctrl.wValue = (readl(®s->wValueH) << 8) | readl(®s->wValueL); ++ ctrl.wIndex = (readl(®s->wIndexH) << 8) | readl(®s->wIndexL); ++ ctrl.wLength = (readl(®s->wLengthH) << 8) | readl(®s->wLengthL); ++ writel(0, ®s->SetupRecv); ++ ++ nuke(&dev->ep[0], 0); ++ dev->ep[0].stopped = 0; ++ if (likely(ctrl.bRequestType & USB_DIR_IN)) { ++ dev->ep[0].is_in = 1; ++ dev->ep0state = EP0_IN; ++ /* detect early status stages */ ++ writel(ICONTROL_STATUSNAK, &dev->regs->IntControl); ++ } else { ++ dev->ep[0].is_in = 0; ++ dev->ep0state = EP0_OUT; ++ ++ /* NOTE: CLEAR_FEATURE is done in software so that we can ++ * synchronize transfer restarts after bulk IN stalls. data ++ * won't even enter the fifo until the halt is cleared. ++ */ ++ switch (ctrl.bRequest) { ++ case USB_REQ_CLEAR_FEATURE: ++ switch (ctrl.bRequestType) { ++ case USB_RECIP_ENDPOINT: ++ tmp = ctrl.wIndex & 0x0f; ++ /* active endpoint */ ++ if (tmp > 3 || (!dev->ep[tmp].desc && tmp != 0)) ++ goto stall; ++ if (ctrl.wIndex & USB_DIR_IN) { ++ if (!dev->ep[tmp].is_in) ++ goto stall; ++ } else { ++ if (dev->ep[tmp].is_in) ++ goto stall; ++ } ++ /* endpoint halt */ ++ if (ctrl.wValue != 0) ++ goto stall; ++ if (tmp) ++ goku_clear_halt(&dev->ep[tmp]); ++succeed: ++ /* start ep0out status stage */ ++ writel(~(1<<0), ®s->EOP); ++ dev->ep[0].stopped = 1; ++ dev->ep0state = EP0_STATUS; ++ return; ++ case USB_RECIP_DEVICE: ++ /* device remote wakeup: always clear */ ++ if (ctrl.wValue != 1) ++ goto stall; ++ VDBG(dev, "clear dev remote wakeup\n"); ++ goto succeed; ++ case USB_RECIP_INTERFACE: ++ goto stall; ++ default: /* pass to gadget driver */ ++ break; ++ } ++ break; ++ default: ++ break; ++ } ++ } ++ ++#ifdef USB_TRACE ++ VDBG(dev, "SETUP %02x.%02x v%04x i%04x l%04x\n", ++ ctrl.bRequestType, ctrl.bRequest, ++ ctrl.wValue, ctrl.wIndex, ctrl.wLength); ++#endif ++ ++ /* hw wants to know when we're configured (or not) */ ++ dev->req_config = (ctrl.bRequest == USB_REQ_SET_CONFIGURATION ++ && ctrl.bRequestType == USB_RECIP_DEVICE); ++ if (unlikely(dev->req_config)) ++ dev->configured = (ctrl.wValue != 0); ++ ++ /* delegate everything to the gadget driver. ++ * it may respond after this irq handler returns. ++ */ ++ spin_unlock (&dev->lock); ++ tmp = dev->driver->setup(&dev->gadget, &ctrl); ++ spin_lock (&dev->lock); ++ if (unlikely(tmp < 0)) { ++stall: ++#ifdef USB_TRACE ++ VDBG(dev, "req %02x.%02x protocol STALL; err %d\n", ++ ctrl.bRequestType, ctrl.bRequest, tmp); ++#endif ++ command(regs, COMMAND_STALL, 0); ++ dev->ep[0].stopped = 1; ++ dev->ep0state = EP0_STALL; ++ } ++ ++ /* expect at least one data or status stage irq */ ++} ++ ++#define ACK(irqbit) { \ ++ stat &= ~irqbit; \ ++ writel(~irqbit, ®s->int_status); \ ++ handled = 1; \ ++ } ++ ++static irqreturn_t goku_irq(int irq, void *_dev, struct pt_regs *r) ++{ ++ struct goku_udc *dev = _dev; ++ struct goku_udc_regs *regs = dev->regs; ++ struct goku_ep *ep; ++ u32 stat, handled = 0; ++ unsigned i, rescans = 5; ++ ++ spin_lock(&dev->lock); ++ ++rescan: ++ stat = readl(®s->int_status) & dev->int_enable; ++ if (!stat) ++ goto done; ++ dev->irqs++; ++ ++ /* device-wide irqs */ ++ if (unlikely(stat & INT_DEVWIDE)) { ++ if (stat & INT_SYSERROR) { ++ ERROR(dev, "system error\n"); ++ stop_activity(dev, dev->driver); ++ stat = 0; ++ handled = 1; ++ // FIXME have a neater way to prevent re-enumeration ++ dev->driver = 0; ++ goto done; ++ } ++ if (stat & INT_PWRDETECT) { ++ writel(~stat, ®s->int_status); ++ if (readl(&dev->regs->power_detect) & PW_DETECT) { ++ VDBG(dev, "connect\n"); ++ ep0_start(dev); ++ } else { ++ DBG(dev, "disconnect\n"); ++ if (dev->gadget.speed == USB_SPEED_FULL) ++ stop_activity(dev, dev->driver); ++ dev->ep0state = EP0_DISCONNECT; ++ dev->int_enable = INT_DEVWIDE; ++ writel(dev->int_enable, &dev->regs->int_enable); ++ } ++ stat = 0; ++ handled = 1; ++ goto done; ++ } ++ if (stat & INT_SUSPEND) { ++ ACK(INT_SUSPEND); ++ if (readl(®s->ep_status[0]) & EPxSTATUS_SUSPEND) { ++ switch (dev->ep0state) { ++ case EP0_DISCONNECT: ++ case EP0_SUSPEND: ++ goto pm_next; ++ default: ++ break; ++ } ++ DBG(dev, "USB suspend\n"); ++ dev->ep0state = EP0_SUSPEND; ++ if (dev->gadget.speed != USB_SPEED_UNKNOWN ++ && dev->driver ++ && dev->driver->suspend) { ++ spin_unlock(&dev->lock); ++ dev->driver->suspend(&dev->gadget); ++ spin_lock(&dev->lock); ++ } ++ } else { ++ if (dev->ep0state != EP0_SUSPEND) { ++ DBG(dev, "bogus USB resume %d\n", ++ dev->ep0state); ++ goto pm_next; ++ } ++ DBG(dev, "USB resume\n"); ++ dev->ep0state = EP0_IDLE; ++ if (dev->gadget.speed != USB_SPEED_UNKNOWN ++ && dev->driver ++ && dev->driver->resume) { ++ spin_unlock(&dev->lock); ++ dev->driver->resume(&dev->gadget); ++ spin_lock(&dev->lock); ++ } ++ } ++ } ++pm_next: ++ if (stat & INT_USBRESET) { /* hub reset done */ ++ ACK(INT_USBRESET); ++ INFO(dev, "USB reset done, gadget %s\n", ++ dev->driver->driver.name); ++ } ++ // and INT_ERR on some endpoint's crc/bitstuff/... problem ++ } ++ ++ /* progress ep0 setup, data, or status stages. ++ * no transition {EP0_STATUS, EP0_STALL} --> EP0_IDLE; saves irqs ++ */ ++ if (stat & INT_SETUP) { ++ ACK(INT_SETUP); ++ dev->ep[0].irqs++; ++ ep0_setup(dev); ++ } ++ if (stat & INT_STATUSNAK) { ++ ACK(INT_STATUSNAK|INT_ENDPOINT0); ++ if (dev->ep0state == EP0_IN) { ++ ep = &dev->ep[0]; ++ ep->irqs++; ++ nuke(ep, 0); ++ writel(~(1<<0), ®s->EOP); ++ dev->ep0state = EP0_STATUS; ++ } ++ } ++ if (stat & INT_ENDPOINT0) { ++ ACK(INT_ENDPOINT0); ++ ep = &dev->ep[0]; ++ ep->irqs++; ++ pio_advance(ep); ++ } ++ ++ /* dma completion */ ++ if (stat & INT_MSTRDEND) { /* IN */ ++ ACK(INT_MSTRDEND); ++ ep = &dev->ep[UDC_MSTRD_ENDPOINT]; ++ ep->irqs++; ++ dma_advance(dev, ep); ++ } ++ if (stat & INT_MSTWREND) { /* OUT */ ++ ACK(INT_MSTWREND); ++ ep = &dev->ep[UDC_MSTWR_ENDPOINT]; ++ ep->irqs++; ++ dma_advance(dev, ep); ++ } ++ if (stat & INT_MSTWRTMOUT) { /* OUT */ ++ ACK(INT_MSTWRTMOUT); ++ ep = &dev->ep[UDC_MSTWR_ENDPOINT]; ++ ep->irqs++; ++ ERROR(dev, "%s write timeout ?\n", ep->ep.name); ++ // reset dma? then dma_advance() ++ } ++ ++ /* pio */ ++ for (i = 1; i < 4; i++) { ++ u32 tmp = INT_EPxDATASET(i); ++ ++ if (!(stat & tmp)) ++ continue; ++ ep = &dev->ep[i]; ++ pio_advance(ep); ++ if (list_empty (&ep->queue)) ++ pio_irq_disable(dev, regs, i); ++ stat &= ~tmp; ++ handled = 1; ++ ep->irqs++; ++ } ++ ++ if (rescans--) ++ goto rescan; ++ ++done: ++ (void)readl(®s->int_enable); ++ spin_unlock(&dev->lock); ++ if (stat) ++ DBG(dev, "unhandled irq status: %05x (%05x, %05x)\n", stat, ++ readl(®s->int_status), dev->int_enable); ++ return IRQ_RETVAL(handled); ++} ++ ++#undef ACK ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* tear down the binding between this driver and the pci device */ ++ ++static void goku_remove(struct pci_dev *pdev) ++{ ++ struct goku_udc *dev = pci_get_drvdata(pdev); ++ ++ DBG(dev, "%s\n", __FUNCTION__); ++ /* start with the driver above us */ ++ if (dev->driver) { ++ /* should have been done already by driver model core */ ++ WARN(dev, "pci remove, driver '%s' is still registered\n", ++ dev->driver->driver.name); ++ usb_gadget_unregister_driver(dev->driver); ++ } ++ ++#ifdef UDC_PROC_FILE ++ remove_proc_entry(proc_node_name, NULL); ++#endif ++ if (dev->regs) ++ udc_reset(dev); ++ if (dev->got_irq) ++ free_irq(pdev->irq, dev); ++ if (dev->regs) ++ iounmap(dev->regs); ++ if (dev->got_region) ++ release_mem_region(pci_resource_start (pdev, 0), ++ pci_resource_len (pdev, 0)); ++ if (dev->enabled) ++ pci_disable_device(pdev); ++ ++ pci_set_drvdata(pdev, 0); ++ dev->regs = 0; ++ the_controller = 0; ++ ++ INFO(dev, "unbind\n"); ++} ++ ++/* wrap this driver around the specified pci device, but ++ * don't respond over USB until a gadget driver binds to us. ++ */ ++ ++static int goku_probe(struct pci_dev *pdev, const struct pci_device_id *id) ++{ ++ struct goku_udc *dev = 0; ++ unsigned long resource, len; ++ void *base = 0; ++ int retval; ++ char buf [8], *bufp; ++ ++ /* if you want to support more than one controller in a system, ++ * usb_gadget_driver_{register,unregister}() must change. ++ */ ++ if (the_controller) { ++ WARN(dev, "ignoring %s\n", pci_name(pdev)); ++ return -EBUSY; ++ } ++ if (!pdev->irq) { ++ printk(KERN_ERR "Check PCI %s IRQ setup!\n", pci_name(pdev)); ++ retval = -ENODEV; ++ goto done; ++ } ++ ++ /* alloc, and start init */ ++ dev = kmalloc (sizeof *dev, SLAB_KERNEL); ++ if (dev == NULL){ ++ pr_debug("enomem %s\n", pci_name(pdev)); ++ retval = -ENOMEM; ++ goto done; ++ } ++ ++ memset(dev, 0, sizeof *dev); ++ spin_lock_init(&dev->lock); ++ dev->pdev = pdev; ++ dev->gadget.ops = &goku_ops; ++ ++ /* the "gadget" abstracts/virtualizes the controller */ ++ dev->gadget.dev.bus_id = "gadget"; ++ dev->gadget.name = driver_name; ++ ++ /* now all the pci goodies ... */ ++ retval = pci_enable_device(pdev); ++ if (retval < 0) { ++ DBG(dev, "can't enable, %d\n", retval); ++ goto done; ++ } ++ dev->enabled = 1; ++ ++ resource = pci_resource_start(pdev, 0); ++ len = pci_resource_len(pdev, 0); ++ if (!request_mem_region(resource, len, driver_name)) { ++ DBG(dev, "controller already in use\n"); ++ retval = -EBUSY; ++ goto done; ++ } ++ dev->got_region = 1; ++ ++ base = ioremap_nocache(resource, len); ++ if (base == NULL) { ++ DBG(dev, "can't map memory\n"); ++ retval = -EFAULT; ++ goto done; ++ } ++ dev->regs = (struct goku_udc_regs *) base; ++ ++ pci_set_drvdata(pdev, dev); ++ INFO(dev, "%s\n", driver_desc); ++ INFO(dev, "version: " DRIVER_VERSION " %s\n", dmastr()); ++#ifndef __sparc__ ++ snprintf(buf, sizeof buf, "%d", pdev->irq); ++ bufp = buf; ++#else ++ bufp = __irq_itoa(pdev->irq); ++#endif ++ INFO(dev, "irq %s, pci mem %p\n", bufp, base); ++ ++ /* init to known state, then setup irqs */ ++ udc_reset(dev); ++ udc_reinit (dev); ++ if (request_irq(pdev->irq, goku_irq, SA_SHIRQ/*|SA_SAMPLE_RANDOM*/, ++ driver_name, dev) != 0) { ++ DBG(dev, "request interrupt %s failed\n", bufp); ++ retval = -EBUSY; ++ goto done; ++ } ++ dev->got_irq = 1; ++ if (use_dma) ++ pci_set_master(pdev); ++ ++ ++#ifdef UDC_PROC_FILE ++ create_proc_read_entry(proc_node_name, 0, NULL, udc_proc_read, dev); ++#endif ++ ++ /* done */ ++ the_controller = dev; ++ ++ return 0; ++ ++done: ++ if (dev) ++ goku_remove (pdev); ++ return retval; ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static struct pci_device_id pci_ids [] = { { ++ .class = ((PCI_CLASS_SERIAL_USB << 8) | 0xfe), ++ .class_mask = ~0, ++ .vendor = 0x102f, /* Toshiba */ ++ .device = 0x0107, /* this UDC */ ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ ++}, { /* end: all zeroes */ } ++}; ++MODULE_DEVICE_TABLE (pci, pci_ids); ++ ++static struct pci_driver goku_pci_driver = { ++ .name = (char *) driver_name, ++ .id_table = pci_ids, ++ ++ .probe = goku_probe, ++ .remove = goku_remove, ++ ++ /* FIXME add power management support */ ++}; ++ ++static int __init init (void) ++{ ++ return pci_module_init (&goku_pci_driver); ++} ++module_init (init); ++ ++static void __exit cleanup (void) ++{ ++ pci_unregister_driver (&goku_pci_driver); ++} ++module_exit (cleanup); +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/goku_udc.h kernel/drivers/usb/gadget/goku_udc.h +--- /tmp/kernel/drivers/usb/gadget/goku_udc.h 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/goku_udc.h 2005-04-22 17:53:19.443538050 +0200 +@@ -0,0 +1,321 @@ ++/* ++ * Toshiba TC86C001 ("Goku-S") USB Device Controller driver ++ * ++ * Copyright (C) 2000-2002 Lineo ++ * by Stuart Lynne, Tom Rushworth, and Bruce Balden ++ * Copyright (C) 2002 Toshiba Corporation ++ * Copyright (C) 2003 MontaVista Software (source@mvista.com) ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++/* ++ * PCI BAR 0 points to these registers. ++ */ ++struct goku_udc_regs { ++ /* irq management */ ++ u32 int_status; /* 0x000 */ ++ u32 int_enable; ++#define INT_SUSPEND 0x00001 /* or resume */ ++#define INT_USBRESET 0x00002 ++#define INT_ENDPOINT0 0x00004 ++#define INT_SETUP 0x00008 ++#define INT_STATUS 0x00010 ++#define INT_STATUSNAK 0x00020 ++#define INT_EPxDATASET(n) (0x00020 << (n)) /* 0 < n < 4 */ ++# define INT_EP1DATASET 0x00040 ++# define INT_EP2DATASET 0x00080 ++# define INT_EP3DATASET 0x00100 ++#define INT_EPnNAK(n) (0x00100 < (n)) /* 0 < n < 4 */ ++# define INT_EP1NAK 0x00200 ++# define INT_EP2NAK 0x00400 ++# define INT_EP3NAK 0x00800 ++#define INT_SOF 0x01000 ++#define INT_ERR 0x02000 ++#define INT_MSTWRSET 0x04000 ++#define INT_MSTWREND 0x08000 ++#define INT_MSTWRTMOUT 0x10000 ++#define INT_MSTRDEND 0x20000 ++#define INT_SYSERROR 0x40000 ++#define INT_PWRDETECT 0x80000 ++ ++#define INT_DEVWIDE (INT_PWRDETECT|INT_SYSERROR/*|INT_ERR*/|INT_USBRESET|INT_SUSPEND) ++#define INT_EP0 (INT_SETUP|INT_ENDPOINT0/*|INT_STATUS*/|INT_STATUSNAK) ++ ++ u32 dma_master; ++#define MST_EOPB_DIS 0x0800 ++#define MST_EOPB_ENA 0x0400 ++#define MST_TIMEOUT_DIS 0x0200 ++#define MST_TIMEOUT_ENA 0x0100 ++#define MST_RD_EOPB 0x0080 /* write-only */ ++#define MST_RD_RESET 0x0040 ++#define MST_WR_RESET 0x0020 ++#define MST_RD_ENA 0x0004 /* 1:start, 0:ignore */ ++#define MST_WR_ENA 0x0002 /* 1:start, 0:ignore */ ++#define MST_CONNECTION 0x0001 /* 0 for ep1out/ep2in */ ++ ++#define MST_R_BITS (MST_EOPB_DIS|MST_EOPB_ENA \ ++ |MST_RD_ENA|MST_RD_RESET) ++#define MST_W_BITS (MST_TIMEOUT_DIS|MST_TIMEOUT_ENA \ ++ |MST_WR_ENA|MST_WR_RESET) ++#define MST_RW_BITS (MST_R_BITS|MST_W_BITS \ ++ |MST_CONNECTION) ++ ++/* these values assume (dma_master & MST_CONNECTION) == 0 */ ++#define UDC_MSTWR_ENDPOINT 1 ++#define UDC_MSTRD_ENDPOINT 2 ++ ++ /* dma master write */ ++ u32 out_dma_start; ++ u32 out_dma_end; ++ u32 out_dma_current; ++ ++ /* dma master read */ ++ u32 in_dma_start; ++ u32 in_dma_end; ++ u32 in_dma_current; ++ ++ u32 power_detect; ++#define PW_DETECT 0x04 ++#define PW_RESETB 0x02 ++#define PW_PULLUP 0x01 ++ ++ u8 _reserved0 [0x1d8]; ++ ++ /* endpoint registers */ ++ u32 ep_fifo [4]; /* 0x200 */ ++ u8 _reserved1 [0x10]; ++ u32 ep_mode [4]; /* only 1-3 valid */ ++ u8 _reserved2 [0x10]; ++ ++ u32 ep_status [4]; ++#define EPxSTATUS_TOGGLE 0x40 ++#define EPxSTATUS_SUSPEND 0x20 ++#define EPxSTATUS_EP_MASK (0x07<<2) ++# define EPxSTATUS_EP_READY (0<<2) ++# define EPxSTATUS_EP_DATAIN (1<<2) ++# define EPxSTATUS_EP_FULL (2<<2) ++# define EPxSTATUS_EP_TX_ERR (3<<2) ++# define EPxSTATUS_EP_RX_ERR (4<<2) ++# define EPxSTATUS_EP_BUSY (5<<2) ++# define EPxSTATUS_EP_STALL (6<<2) ++# define EPxSTATUS_EP_INVALID (7<<2) ++#define EPxSTATUS_FIFO_DISABLE 0x02 ++#define EPxSTATUS_STAGE_ERROR 0x01 ++ ++ u8 _reserved3 [0x10]; ++ u32 EPxSizeLA[4]; ++#define PACKET_ACTIVE (1<<7) ++#define DATASIZE 0x7f ++ u8 _reserved3a [0x10]; ++ u32 EPxSizeLB[4]; /* only 1,2 valid */ ++ u8 _reserved3b [0x10]; ++ u32 EPxSizeHA[4]; /* only 1-3 valid */ ++ u8 _reserved3c [0x10]; ++ u32 EPxSizeHB[4]; /* only 1,2 valid */ ++ u8 _reserved4[0x30]; ++ ++ /* SETUP packet contents */ ++ u32 bRequestType; /* 0x300 */ ++ u32 bRequest; ++ u32 wValueL; ++ u32 wValueH; ++ u32 wIndexL; ++ u32 wIndexH; ++ u32 wLengthL; ++ u32 wLengthH; ++ ++ /* command interaction/handshaking */ ++ u32 SetupRecv; /* 0x320 */ ++ u32 CurrConfig; ++ u32 StdRequest; ++ u32 Request; ++ u32 DataSet; ++#define DATASET_A(epnum) (1<<(2*(epnum))) ++#define DATASET_B(epnum) (2<<(2*(epnum))) ++#define DATASET_AB(epnum) (3<<(2*(epnum))) ++ u8 _reserved5[4]; ++ ++ u32 UsbState; ++#define USBSTATE_CONFIGURED 0x04 ++#define USBSTATE_ADDRESSED 0x02 ++#define USBSTATE_DEFAULT 0x01 ++ ++ u32 EOP; ++ ++ u32 Command; /* 0x340 */ ++#define COMMAND_SETDATA0 2 ++#define COMMAND_RESET 3 ++#define COMMAND_STALL 4 ++#define COMMAND_INVALID 5 ++#define COMMAND_FIFO_DISABLE 7 ++#define COMMAND_FIFO_ENABLE 8 ++#define COMMAND_INIT_DESCRIPTOR 9 ++#define COMMAND_FIFO_CLEAR 10 /* also stall */ ++#define COMMAND_STALL_CLEAR 11 ++#define COMMAND_EP(n) ((n) << 4) ++ ++ u32 EPxSingle; ++ u8 _reserved6[4]; ++ u32 EPxBCS; ++ u8 _reserved7[8]; ++ u32 IntControl; ++#define ICONTROL_STATUSNAK 1 ++ u8 _reserved8[4]; ++ ++ u32 reqmode; // 0x360 standard request mode, low 8 bits ++#define G_REQMODE_SET_INTF (1<<7) ++#define G_REQMODE_GET_INTF (1<<6) ++#define G_REQMODE_SET_CONF (1<<5) ++#define G_REQMODE_GET_CONF (1<<4) ++#define G_REQMODE_GET_DESC (1<<3) ++#define G_REQMODE_SET_FEAT (1<<2) ++#define G_REQMODE_CLEAR_FEAT (1<<1) ++#define G_REQMODE_GET_STATUS (1<<0) ++ ++ u32 ReqMode; ++ u8 _reserved9[0x18]; ++ u32 PortStatus; /* 0x380 */ ++ u8 _reserved10[8]; ++ u32 address; ++ u32 buff_test; ++ u8 _reserved11[4]; ++ u32 UsbReady; ++ u8 _reserved12[4]; ++ u32 SetDescStall; /* 0x3a0 */ ++ u8 _reserved13[0x45c]; ++ ++ /* hardware could handle limited GET_DESCRIPTOR duties */ ++#define DESC_LEN 0x80 ++ u32 descriptors[DESC_LEN]; /* 0x800 */ ++ u8 _reserved14[0x600]; ++ ++} __attribute__ ((packed)); ++ ++#define MAX_FIFO_SIZE 64 ++#define MAX_EP0_SIZE 8 /* ep0 fifo is bigger, though */ ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* DRIVER DATA STRUCTURES and UTILITIES */ ++ ++struct goku_ep { ++ struct usb_ep ep; ++ struct goku_udc *dev; ++ unsigned long irqs; ++ ++ unsigned num:8, ++ dma:1, ++ is_in:1, ++ stopped:1; ++ ++ /* analogous to a host-side qh */ ++ struct list_head queue; ++ const struct usb_endpoint_descriptor *desc; ++ ++ u32 *reg_fifo; ++ u32 *reg_mode; ++ u32 *reg_status; ++}; ++ ++struct goku_request { ++ struct usb_request req; ++ struct list_head queue; ++ ++ unsigned mapped:1; ++}; ++ ++enum ep0state { ++ EP0_DISCONNECT, /* no host */ ++ EP0_IDLE, /* between STATUS ack and SETUP report */ ++ EP0_IN, EP0_OUT, /* data stage */ ++ EP0_STATUS, /* status stage */ ++ EP0_STALL, /* data or status stages */ ++ EP0_SUSPEND, /* usb suspend */ ++}; ++ ++struct goku_udc { ++ /* each pci device provides one gadget, several endpoints */ ++ struct usb_gadget gadget; ++ spinlock_t lock; ++ struct goku_ep ep[4]; ++ struct usb_gadget_driver *driver; ++ ++ enum ep0state ep0state; ++ unsigned got_irq:1, ++ got_region:1, ++ req_config:1, ++ configured:1, ++ enabled:1; ++ ++ /* pci state used to access those endpoints */ ++ struct pci_dev *pdev; ++ struct goku_udc_regs *regs; ++ u32 int_enable; ++ ++ /* statistics... */ ++ unsigned long irqs; ++}; ++ ++/*-------------------------------------------------------------------------*/ ++ ++#define xprintk(dev,level,fmt,args...) \ ++ printk(level "%s %s: " fmt , driver_name , \ ++ pci_name(dev->pdev) , ## args) ++ ++#ifdef DEBUG ++#define DBG(dev,fmt,args...) \ ++ xprintk(dev , KERN_DEBUG , fmt , ## args) ++#else ++#define DBG(dev,fmt,args...) \ ++ do { } while (0) ++#endif /* DEBUG */ ++ ++#ifdef VERBOSE ++#define VDBG DBG ++#else ++#define VDBG(dev,fmt,args...) \ ++ do { } while (0) ++#endif /* VERBOSE */ ++ ++#define ERROR(dev,fmt,args...) \ ++ xprintk(dev , KERN_ERR , fmt , ## args) ++#define WARN(dev,fmt,args...) \ ++ xprintk(dev , KERN_WARNING , fmt , ## args) ++#define INFO(dev,fmt,args...) \ ++ xprintk(dev , KERN_INFO , fmt , ## args) ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* 2.5 stuff that's sometimes missing in 2.4 */ ++ ++#ifndef container_of ++#define container_of list_entry ++#endif ++ ++#ifndef likely ++#define likely(x) (x) ++#define unlikely(x) (x) ++#endif ++ ++#ifndef BUG_ON ++#define BUG_ON(condition) do { if (unlikely((condition)!=0)) BUG(); } while(0) ++#endif ++ ++#ifndef WARN_ON ++#define WARN_ON(x) do { } while (0) ++#endif ++ ++#ifndef IRQ_NONE ++typedef void irqreturn_t; ++#define IRQ_NONE ++#define IRQ_HANDLED ++#define IRQ_RETVAL(x) ++#endif ++ ++#ifndef pci_name ++#define pci_name(pdev) ((pdev)->slot_name) ++#endif +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/gserial.c kernel/drivers/usb/gadget/gserial.c +--- /tmp/kernel/drivers/usb/gadget/gserial.c 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/gserial.c 2005-04-22 17:53:19.450536911 +0200 +@@ -0,0 +1,2301 @@ ++/* ++ * g_serial.c -- USB gadget serial driver ++ * ++ * $Id: gserial.c,v 1.17 2003/10/01 06:31:57 borchers Exp $ ++ * ++ * Copyright 2003 (c) Al Borchers (alborchers@steinerpoint.com) ++ * ++ * This code is based in part on the Gadget Zero driver, which ++ * is Copyright (C) 2003 by David Brownell, all rights reserved. ++ * ++ * This code also borrows from usbserial.c, which is ++ * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com) ++ * Copyright (c) 2000 Peter Berger (pberger@brimson.com) ++ * Copyright (c) 2000 Al Borchers (alborchers@steinerpoint.com) ++ * ++ * This software is distributed under the terms of the GNU General ++ * Public License ("GPL") as published by the Free Software Foundation, ++ * either version 2 of that License or (at your option) any later version. ++ * ++ */ ++ ++#ifndef __KERNEL__ ++#define __KERNEL__ ++#endif ++ ++#ifndef MODULE ++#define MODULE ++#endif ++ ++ ++/* Includes */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include "gadget_chips.h" ++ ++ ++/* Wait Cond */ ++ ++#define __wait_cond_interruptible(wq, condition, lock, flags, ret) \ ++do { \ ++ wait_queue_t __wait; \ ++ init_waitqueue_entry(&__wait, current); \ ++ \ ++ add_wait_queue(&wq, &__wait); \ ++ for (;;) { \ ++ set_current_state(TASK_INTERRUPTIBLE); \ ++ if (condition) \ ++ break; \ ++ if (!signal_pending(current)) { \ ++ spin_unlock_irqrestore(lock, flags); \ ++ schedule(); \ ++ spin_lock_irqsave(lock, flags); \ ++ continue; \ ++ } \ ++ ret = -ERESTARTSYS; \ ++ break; \ ++ } \ ++ current->state = TASK_RUNNING; \ ++ remove_wait_queue(&wq, &__wait); \ ++} while (0) ++ ++#define wait_cond_interruptible(wq, condition, lock, flags) \ ++({ \ ++ int __ret = 0; \ ++ if (!(condition)) \ ++ __wait_cond_interruptible(wq, condition, lock, flags, \ ++ __ret); \ ++ __ret; \ ++}) ++ ++#define __wait_cond_interruptible_timeout(wq, condition, lock, flags, \ ++ timeout, ret) \ ++do { \ ++ signed long __timeout = timeout; \ ++ wait_queue_t __wait; \ ++ init_waitqueue_entry(&__wait, current); \ ++ \ ++ add_wait_queue(&wq, &__wait); \ ++ for (;;) { \ ++ set_current_state(TASK_INTERRUPTIBLE); \ ++ if (__timeout == 0) \ ++ break; \ ++ if (condition) \ ++ break; \ ++ if (!signal_pending(current)) { \ ++ spin_unlock_irqrestore(lock, flags); \ ++ __timeout = schedule_timeout(__timeout); \ ++ spin_lock_irqsave(lock, flags); \ ++ continue; \ ++ } \ ++ ret = -ERESTARTSYS; \ ++ break; \ ++ } \ ++ current->state = TASK_RUNNING; \ ++ remove_wait_queue(&wq, &__wait); \ ++} while (0) ++ ++#define wait_cond_interruptible_timeout(wq, condition, lock, flags, \ ++ timeout) \ ++({ \ ++ int __ret = 0; \ ++ if (!(condition)) \ ++ __wait_cond_interruptible_timeout(wq, condition, lock, \ ++ flags, timeout, __ret); \ ++ __ret; \ ++}) ++ ++ ++/* Defines */ ++ ++#define GS_VERSION_STR "v1.0" ++#define GS_VERSION_NUM 0x0100 ++ ++#define GS_LONG_NAME "Gadget Serial" ++#define GS_SHORT_NAME "g_serial" ++ ++#define GS_MAJOR 127 ++#define GS_MINOR_START 0 ++ ++#define GS_NUM_PORTS 16 ++ ++#define GS_NUM_CONFIGS 1 ++#define GS_NO_CONFIG_ID 0 ++#define GS_BULK_CONFIG_ID 2 ++ ++#define GS_NUM_INTERFACES 1 ++#define GS_INTERFACE_ID 0 ++#define GS_ALT_INTERFACE_ID 0 ++ ++#define GS_NUM_ENDPOINTS 2 ++ ++#define GS_MAX_DESC_LEN 256 ++ ++#define GS_DEFAULT_READ_Q_SIZE 32 ++#define GS_DEFAULT_WRITE_Q_SIZE 32 ++ ++#define GS_DEFAULT_WRITE_BUF_SIZE 8192 ++#define GS_TMP_BUF_SIZE 8192 ++ ++#define GS_CLOSE_TIMEOUT 15 ++ ++/* debug macro */ ++#if G_SERIAL_DEBUG ++ ++static int debug = G_SERIAL_DEBUG; ++ ++#define gs_debug(format, arg...) \ ++ do { if(debug) printk( KERN_DEBUG format, ## arg ); } while(0) ++#define gs_debug_level(level, format, arg...) \ ++ do { if(debug>=level) printk( KERN_DEBUG format, ## arg ); } while(0) ++ ++#else ++ ++#define gs_debug(format, arg...) \ ++ do { } while(0) ++#define gs_debug_level(level, format, arg...) \ ++ do { } while(0) ++ ++#endif /* G_SERIAL_DEBUG */ ++ ++ ++/* Thanks to NetChip Technologies for donating this product ID. ++ * ++ * DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! ++ * Instead: allocate your own, using normal USB-IF procedures. ++ */ ++#define GS_VENDOR_ID 0x0525 /* NetChip */ ++#define GS_PRODUCT_ID 0xa4a6 /* Linux-USB Serial Gadget */ ++ ++ ++/* Structures */ ++ ++struct gs_dev; ++ ++/* circular buffer */ ++struct gs_buf { ++ unsigned int buf_size; ++ char *buf_buf; ++ char *buf_get; ++ char *buf_put; ++}; ++ ++/* list of requests */ ++struct gs_req_entry { ++ struct list_head re_entry; ++ struct usb_request *re_req; ++}; ++ ++/* the port structure holds info for each port, one for each minor number */ ++struct gs_port { ++ struct gs_dev *port_dev; /* pointer to device struct */ ++ struct tty_struct *port_tty; /* pointer to tty struct */ ++ spinlock_t port_lock; ++ int port_num; ++ int port_open_count; ++ int port_in_use; /* open/close in progress */ ++ wait_queue_head_t port_write_wait;/* waiting to write */ ++ struct gs_buf *port_write_buf; ++}; ++ ++/* the device structure holds info for the USB device */ ++struct gs_dev { ++ struct usb_gadget *dev_gadget; /* gadget device pointer */ ++ spinlock_t dev_lock; /* lock for set/reset config */ ++ int dev_config; /* configuration number */ ++ struct usb_ep *dev_in_ep; /* address of in endpoint */ ++ struct usb_ep *dev_out_ep; /* address of out endpoint */ ++ struct usb_request *dev_ctrl_req; /* control request */ ++ struct list_head dev_req_list; /* list of write requests */ ++ int dev_sched_port; /* round robin port scheduled */ ++ struct gs_port *dev_port[GS_NUM_PORTS]; /* the ports */ ++}; ++ ++ ++/* Functions */ ++ ++/* module */ ++static int __init gs_module_init( void ); ++static void __exit gs_module_exit( void ); ++ ++/* tty driver */ ++static int gs_open( struct tty_struct *tty, struct file *file ); ++static void gs_close( struct tty_struct *tty, struct file *file ); ++static int gs_write( struct tty_struct *tty, int from_user, ++ const unsigned char *buf, int count ); ++static void gs_put_char( struct tty_struct *tty, unsigned char ch ); ++static void gs_flush_chars( struct tty_struct *tty ); ++static int gs_write_room( struct tty_struct *tty ); ++static int gs_chars_in_buffer( struct tty_struct *tty ); ++static void gs_throttle( struct tty_struct * tty ); ++static void gs_unthrottle( struct tty_struct * tty ); ++static void gs_break( struct tty_struct *tty, int break_state ); ++static int gs_ioctl( struct tty_struct *tty, struct file *file, ++ unsigned int cmd, unsigned long arg ); ++static void gs_set_termios( struct tty_struct *tty, struct termios *old ); ++static int gs_read_proc( char *page, char **start, off_t off, int count, ++ int *eof, void *data ); ++ ++static int gs_send( struct gs_dev *dev ); ++static int gs_send_packet( struct gs_dev *dev, char *packet, ++ unsigned int size ); ++static int gs_recv_packet( struct gs_dev *dev, char *packet, ++ unsigned int size ); ++static void gs_read_complete( struct usb_ep *ep, struct usb_request *req ); ++static void gs_write_complete( struct usb_ep *ep, struct usb_request *req ); ++ ++/* gadget driver */ ++static int gs_bind( struct usb_gadget *gadget ); ++static void gs_unbind( struct usb_gadget *gadget ); ++static int gs_setup( struct usb_gadget *gadget, ++ const struct usb_ctrlrequest *ctrl ); ++static void gs_setup_complete( struct usb_ep *ep, struct usb_request *req ); ++static void gs_disconnect( struct usb_gadget *gadget ); ++static int gs_set_config( struct gs_dev *dev, unsigned config ); ++static void gs_reset_config( struct gs_dev *dev ); ++static int gs_build_config_desc( u8 *buf, enum usb_device_speed speed, ++ u8 type, unsigned int index ); ++ ++static struct usb_request *gs_alloc_req( struct usb_ep *ep, unsigned int len, ++ int kmalloc_flags ); ++static void gs_free_req( struct usb_ep *ep, struct usb_request *req ); ++ ++static struct gs_req_entry *gs_alloc_req_entry( struct usb_ep *ep, unsigned len, ++ int kmalloc_flags ); ++static void gs_free_req_entry( struct usb_ep *ep, struct gs_req_entry *req ); ++ ++static int gs_alloc_ports( struct gs_dev *dev, int kmalloc_flags ); ++static void gs_free_ports( struct gs_dev *dev ); ++ ++/* circular buffer */ ++static struct gs_buf *gs_buf_alloc( unsigned int size, int kmalloc_flags ); ++static void gs_buf_free( struct gs_buf *gb ); ++static void gs_buf_clear( struct gs_buf *gb ); ++static unsigned int gs_buf_data_avail( struct gs_buf *gb ); ++static unsigned int gs_buf_space_avail( struct gs_buf *gb ); ++static unsigned int gs_buf_put( struct gs_buf *gb, const char *buf, ++ unsigned int count ); ++static unsigned int gs_buf_get( struct gs_buf *gb, char *buf, ++ unsigned int count ); ++ ++/* external functions */ ++extern int net2280_set_fifo_mode(struct usb_gadget *gadget, int mode); ++ ++ ++/* Globals */ ++ ++static struct gs_dev *gs_device; ++ ++static const char *EP_IN_NAME; ++static const char *EP_OUT_NAME; ++ ++static struct semaphore gs_open_close_sem[GS_NUM_PORTS]; ++ ++static unsigned int read_q_size = GS_DEFAULT_READ_Q_SIZE; ++static unsigned int write_q_size = GS_DEFAULT_WRITE_Q_SIZE; ++ ++static unsigned int write_buf_size = GS_DEFAULT_WRITE_BUF_SIZE; ++ ++static unsigned char gs_tmp_buf[GS_TMP_BUF_SIZE]; ++static struct semaphore gs_tmp_buf_sem; ++ ++/* tty variables */ ++static int gs_refcount; ++static struct tty_struct *gs_tty[GS_NUM_PORTS]; ++static struct termios *gs_termios[GS_NUM_PORTS]; ++static struct termios *gs_termios_locked[GS_NUM_PORTS]; ++ ++/* tty driver struct */ ++static struct tty_driver gs_tty_driver = { ++ .magic = TTY_DRIVER_MAGIC, ++ .driver_name = GS_SHORT_NAME, ++ .name = "ttygs", ++ .major = GS_MAJOR, ++ .minor_start = GS_MINOR_START, ++ .num = GS_NUM_PORTS, ++ .type = TTY_DRIVER_TYPE_SERIAL, ++ .subtype = SERIAL_TYPE_NORMAL, ++ .flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS, ++ .refcount = &gs_refcount, ++ .table = gs_tty, ++ .termios = gs_termios, ++ .termios_locked = gs_termios_locked, ++ ++ .open = gs_open, ++ .close = gs_close, ++ .write = gs_write, ++ .put_char = gs_put_char, ++ .flush_chars = gs_flush_chars, ++ .write_room = gs_write_room, ++ .ioctl = gs_ioctl, ++ .set_termios = gs_set_termios, ++ .throttle = gs_throttle, ++ .unthrottle = gs_unthrottle, ++ .break_ctl = gs_break, ++ .chars_in_buffer = gs_chars_in_buffer, ++ .read_proc = gs_read_proc, ++}; ++ ++/* gadget driver struct */ ++static struct usb_gadget_driver gs_gadget_driver = { ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ .speed = USB_SPEED_HIGH, ++#else ++ .speed = USB_SPEED_FULL, ++#endif ++ .function = GS_LONG_NAME, ++ .bind = gs_bind, ++ .unbind = gs_unbind, ++ .setup = gs_setup, ++ .disconnect = gs_disconnect, ++ .driver = { ++ .name = GS_SHORT_NAME, ++ /* .shutdown = ... */ ++ /* .suspend = ... */ ++ /* .resume = ... */ ++ }, ++}; ++ ++ ++/* USB descriptors */ ++ ++#define GS_MANUFACTURER_STR_ID 1 ++#define GS_PRODUCT_STR_ID 2 ++#define GS_SERIAL_STR_ID 3 ++#define GS_CONFIG_STR_ID 4 ++ ++/* static strings, in iso 8859/1 */ ++static char manufacturer[40]; ++static struct usb_string gs_strings[] = { ++ { GS_MANUFACTURER_STR_ID, manufacturer }, ++ { GS_PRODUCT_STR_ID, GS_LONG_NAME }, ++ { GS_SERIAL_STR_ID, "0" }, ++ { GS_CONFIG_STR_ID, "Bulk" }, ++ { } /* end of list */ ++}; ++ ++static struct usb_gadget_strings gs_string_table = { ++ .language = 0x0409, /* en-us */ ++ .strings = gs_strings, ++}; ++ ++static struct usb_device_descriptor gs_device_desc = { ++ .bLength = USB_DT_DEVICE_SIZE, ++ .bDescriptorType = USB_DT_DEVICE, ++ .bcdUSB = __constant_cpu_to_le16(0x0200), ++ .bDeviceClass = USB_CLASS_VENDOR_SPEC, ++ .idVendor = __constant_cpu_to_le16(GS_VENDOR_ID), ++ .idProduct = __constant_cpu_to_le16(GS_PRODUCT_ID), ++ .iManufacturer = GS_MANUFACTURER_STR_ID, ++ .iProduct = GS_PRODUCT_STR_ID, ++ .iSerialNumber = GS_SERIAL_STR_ID, ++ .bNumConfigurations = GS_NUM_CONFIGS, ++}; ++ ++static const struct usb_config_descriptor gs_config_desc = { ++ .bLength = USB_DT_CONFIG_SIZE, ++ .bDescriptorType = USB_DT_CONFIG, ++ /* .wTotalLength set by gs_build_config_desc */ ++ .bNumInterfaces = GS_NUM_INTERFACES, ++ .bConfigurationValue = GS_BULK_CONFIG_ID, ++ .iConfiguration = GS_CONFIG_STR_ID, ++ .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, ++ .bMaxPower = 1, ++}; ++ ++static const struct usb_interface_descriptor gs_interface_desc = { ++ .bLength = USB_DT_INTERFACE_SIZE, ++ .bDescriptorType = USB_DT_INTERFACE, ++ .bNumEndpoints = GS_NUM_ENDPOINTS, ++ .bInterfaceClass = USB_CLASS_VENDOR_SPEC, ++ .iInterface = GS_CONFIG_STR_ID, ++}; ++ ++static struct usb_endpoint_descriptor gs_fullspeed_in_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ .bEndpointAddress = USB_DIR_IN, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++}; ++ ++static struct usb_endpoint_descriptor gs_fullspeed_out_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ .bEndpointAddress = USB_DIR_OUT, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++}; ++ ++static struct usb_endpoint_descriptor gs_highspeed_in_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ .wMaxPacketSize = __constant_cpu_to_le16(512), ++}; ++ ++static struct usb_endpoint_descriptor gs_highspeed_out_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ .wMaxPacketSize = __constant_cpu_to_le16(512), ++}; ++ ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++static struct usb_qualifier_descriptor gs_qualifier_desc = { ++ .bLength = sizeof(struct usb_qualifier_descriptor), ++ .bDescriptorType = USB_DT_DEVICE_QUALIFIER, ++ .bcdUSB = __constant_cpu_to_le16 (0x0200), ++ .bDeviceClass = USB_CLASS_VENDOR_SPEC, ++ /* assumes ep0 uses the same value for both speeds ... */ ++ .bNumConfigurations = GS_NUM_CONFIGS, ++}; ++#endif ++ ++ ++/* Module */ ++ ++MODULE_DESCRIPTION( GS_LONG_NAME ); ++MODULE_AUTHOR( "Al Borchers" ); ++MODULE_LICENSE( "GPL" ); ++ ++#if G_SERIAL_DEBUG ++MODULE_PARM( debug, "i" ); ++MODULE_PARM_DESC( debug, "Enable debugging, 0=off, 1=on" ); ++#endif ++ ++MODULE_PARM( read_q_size, "i" ); ++MODULE_PARM_DESC( read_q_size, "Read request queue size, default=32" ); ++ ++MODULE_PARM( write_q_size, "i" ); ++MODULE_PARM_DESC( write_q_size, "Write request queue size, default=32" ); ++ ++MODULE_PARM( write_buf_size, "i" ); ++MODULE_PARM_DESC( write_buf_size, "Write buffer size, default=8192" ); ++ ++module_init( gs_module_init ); ++module_exit( gs_module_exit ); ++ ++/* ++* gs_module_init ++* ++* Register as a USB gadget driver and a tty driver. ++*/ ++ ++static int __init gs_module_init( void ) ++{ ++ ++ int i,ret; ++ ++ ++ if( (ret=usb_gadget_register_driver( &gs_gadget_driver )) ) { ++ printk( KERN_ERR ++ "gs_module_init: cannot register gadget driver, ret=%d\n", ++ ret ); ++ return( ret ); ++ } ++ ++ /* initial stty settings */ ++ gs_tty_driver.init_termios = tty_std_termios; ++ gs_tty_driver.init_termios.c_cflag ++ = B9600 | CS8 | CREAD | HUPCL | CLOCAL; ++ ++ for( i=0; idevice ) - GS_MINOR_START; ++ ++ gs_debug( "gs_open: (%d,%p,%p)\n", port_num, tty, file ); ++ ++ tty->driver_data = NULL; ++ ++ if( port_num < 0 || port_num >= GS_NUM_PORTS ) { ++ printk( KERN_ERR "gs_open: (%d,%p,%p) invalid port number\n", ++ port_num, tty, file ); ++ return( -ENODEV ); ++ } ++ ++ dev = gs_device; ++ ++ if( dev == NULL ) { ++ printk( KERN_ERR "gs_open: (%d,%p,%p) NULL device pointer\n", ++ port_num, tty, file ); ++ return( -ENODEV ); ++ } ++ ++ sem = &gs_open_close_sem[port_num]; ++ if( down_interruptible( sem ) ) { ++ printk( KERN_ERR ++ "gs_open: (%d,%p,%p) interrupted waiting for semaphore\n", ++ port_num, tty, file ); ++ return( -ERESTARTSYS ); ++ } ++ ++ spin_lock_irqsave(&dev->dev_lock, flags ); ++ ++ if( dev->dev_config == GS_NO_CONFIG_ID ) { ++ printk( KERN_ERR ++ "gs_open: (%d,%p,%p) device is not connected\n", ++ port_num, tty, file ); ++ spin_unlock_irqrestore(&dev->dev_lock, flags ); ++ up( sem ); ++ return( -ENODEV ); ++ } ++ ++ port = dev->dev_port[port_num]; ++ ++ if( port == NULL ) { ++ printk( KERN_ERR "gs_open: (%d,%p,%p) NULL port pointer\n", ++ port_num, tty, file ); ++ spin_unlock_irqrestore(&dev->dev_lock, flags ); ++ up( sem ); ++ return( -ENODEV ); ++ } ++ ++ spin_lock(&port->port_lock ); ++ spin_unlock(&dev->dev_lock ); ++ ++ if( port->port_dev == NULL ) { ++ printk( KERN_ERR "gs_open: (%d,%p,%p) port disconnected (1)\n", ++ port_num, tty, file ); ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ up( sem ); ++ return( -EIO ); ++ } ++ ++ if( port->port_open_count > 0 ) { ++ ++port->port_open_count; ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ gs_debug( "gs_open: (%d,%p,%p) already open\n", ++ port_num, tty, file ); ++ up( sem ); ++ return( 0 ); ++ } ++ ++ /* mark port as in use, we can drop port lock and sleep if necessary */ ++ port->port_in_use = 1; ++ ++ /* allocate write buffer on first open */ ++ if( port->port_write_buf == NULL ) { ++ ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ buf = gs_buf_alloc( write_buf_size, GFP_KERNEL ); ++ spin_lock_irqsave(&port->port_lock, flags ); ++ ++ /* might have been disconnected while asleep, check */ ++ if( port->port_dev == NULL ) { ++ printk( KERN_ERR ++ "gs_open: (%d,%p,%p) port disconnected (2)\n", ++ port_num, tty, file ); ++ port->port_in_use = 0; ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ up( sem ); ++ return( -EIO ); ++ } ++ ++ if( (port->port_write_buf=buf) == NULL ) { ++ printk( KERN_ERR "gs_open: (%d,%p,%p) cannot allocate port write buffer\n", ++ port_num, tty, file ); ++ port->port_in_use = 0; ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ up( sem ); ++ return( -ENOMEM ); ++ } ++ ++ } ++ ++ /* wait for carrier detect (not implemented) */ ++ ++ /* might have been disconnected while asleep, check */ ++ if( port->port_dev == NULL ) { ++ printk( KERN_ERR "gs_open: (%d,%p,%p) port disconnected (3)\n", ++ port_num, tty, file ); ++ port->port_in_use = 0; ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ up( sem ); ++ return( -EIO ); ++ } ++ ++ tty->driver_data = port; ++ port->port_tty = tty; ++ port->port_open_count = 1; ++ port->port_in_use = 0; ++ ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ up( sem ); ++ ++ gs_debug( "gs_open: (%d,%p,%p) completed\n", port_num, tty, file ); ++ ++ return( 0 ); ++ ++} ++ ++ ++/* ++ * gs_close ++ */ ++ ++static void gs_close( struct tty_struct *tty, struct file *file ) ++{ ++ ++ unsigned long flags; ++ struct gs_port *port = tty->driver_data; ++ struct semaphore *sem; ++ ++ ++ if( port == NULL ) { ++ printk( KERN_ERR "gs_close: NULL port pointer\n" ); ++ return; ++ } ++ ++ gs_debug( "gs_close: (%d,%p,%p)\n", port->port_num, tty, file ); ++ ++ sem = &gs_open_close_sem[port->port_num]; ++ down( sem ); ++ ++ spin_lock_irqsave(&port->port_lock, flags ); ++ ++ if( port->port_open_count == 0 ) { ++ printk( KERN_ERR ++ "gs_close: (%d,%p,%p) port is already closed\n", ++ port->port_num, tty, file ); ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ up( sem ); ++ return; ++ } ++ ++ if( port->port_open_count > 0 ) { ++ --port->port_open_count; ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ up( sem ); ++ return; ++ } ++ ++ /* free disconnected port on final close */ ++ if( port->port_dev == NULL ) { ++ kfree( port ); ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ up( sem ); ++ return; ++ } ++ ++ /* mark port as closed but in use, we can drop port lock */ ++ /* and sleep if necessary */ ++ port->port_in_use = 1; ++ port->port_open_count = 0; ++ ++ /* wait for write buffer to drain, or */ ++ /* at most GS_CLOSE_TIMEOUT seconds */ ++ if( gs_buf_data_avail( port->port_write_buf ) > 0 ) { ++ wait_cond_interruptible_timeout( port->port_write_wait, ++ port->port_dev == NULL ++ || gs_buf_data_avail(port->port_write_buf) == 0, ++ &port->port_lock, flags, GS_CLOSE_TIMEOUT * HZ ); ++ } ++ ++ /* free disconnected port on final close */ ++ /* (might have happened during the above sleep) */ ++ if( port->port_dev == NULL ) { ++ kfree( port ); ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ up( sem ); ++ return; ++ } ++ ++ gs_buf_clear( port->port_write_buf ); ++ ++ tty->driver_data = NULL; ++ port->port_tty = NULL; ++ port->port_in_use = 0; ++ ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ up( sem ); ++ ++ gs_debug( "gs_close: (%d,%p,%p) completed\n", ++ port->port_num, tty, file ); ++ ++} ++ ++ ++/* ++ * gs_write ++ */ ++ ++static int gs_write( struct tty_struct *tty, int from_user, ++ const unsigned char *buf, int count ) ++{ ++ ++ unsigned long flags; ++ struct gs_port *port = tty->driver_data; ++ ++ ++ if( port == NULL ) { ++ printk( KERN_ERR "gs_write: NULL port pointer\n" ); ++ return( -EIO ); ++ } ++ ++ gs_debug( "gs_write: (%d,%p) writing %d bytes\n", port->port_num, tty, ++ count ); ++ ++ if( count == 0 ) ++ return( 0 ); ++ ++ /* copy from user into tmp buffer, get tmp_buf semaphore */ ++ if( from_user ) { ++ if( count > GS_TMP_BUF_SIZE ) ++ count = GS_TMP_BUF_SIZE; ++ down( &gs_tmp_buf_sem ); ++ if( copy_from_user( gs_tmp_buf, buf, count ) != 0 ) { ++ up( &gs_tmp_buf_sem ); ++ printk( KERN_ERR ++ "gs_write: (%d,%p) cannot copy from user space\n", ++ port->port_num, tty ); ++ return( -EFAULT ); ++ } ++ buf = gs_tmp_buf; ++ } ++ ++ spin_lock_irqsave(&port->port_lock, flags ); ++ ++ if( port->port_dev == NULL ) { ++ printk( KERN_ERR "gs_write: (%d,%p) port is not connected\n", ++ port->port_num, tty ); ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ if( from_user ) ++ up( &gs_tmp_buf_sem ); ++ return( -EIO ); ++ } ++ ++ if( port->port_open_count == 0 ) { ++ printk( KERN_ERR "gs_write: (%d,%p) port is closed\n", ++ port->port_num, tty ); ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ if( from_user ) ++ up( &gs_tmp_buf_sem ); ++ return( -EBADF ); ++ } ++ ++ count = gs_buf_put( port->port_write_buf, buf, count ); ++ ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ ++ if( from_user ) ++ up( &gs_tmp_buf_sem ); ++ ++ gs_send( gs_device ); ++ ++ gs_debug( "gs_write: (%d,%p) wrote %d bytes\n", port->port_num, tty, ++ count ); ++ ++ return( count ); ++ ++} ++ ++ ++/* ++ * gs_put_char ++ */ ++ ++static void gs_put_char( struct tty_struct *tty, unsigned char ch ) ++{ ++ ++ unsigned long flags; ++ struct gs_port *port = tty->driver_data; ++ ++ ++ if( port == NULL ) { ++ printk( KERN_ERR "gs_put_char: NULL port pointer\n" ); ++ return; ++ } ++ ++ gs_debug( "gs_put_char: (%d,%p) char=0x%x, called from %p, %p, %p\n", port->port_num, tty, ch, __builtin_return_address(0), __builtin_return_address(1), __builtin_return_address(2) ); ++ ++ spin_lock_irqsave(&port->port_lock, flags ); ++ ++ if( port->port_dev == NULL ) { ++ printk( KERN_ERR "gs_put_char: (%d,%p) port is not connected\n", ++ port->port_num, tty ); ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ return; ++ } ++ ++ if( port->port_open_count == 0 ) { ++ printk( KERN_ERR "gs_put_char: (%d,%p) port is closed\n", ++ port->port_num, tty ); ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ return; ++ } ++ ++ gs_buf_put( port->port_write_buf, &ch, 1 ); ++ ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ ++} ++ ++ ++/* ++ * gs_flush_chars ++ */ ++ ++static void gs_flush_chars( struct tty_struct *tty ) ++{ ++ ++ unsigned long flags; ++ struct gs_port *port = tty->driver_data; ++ ++ ++ if( port == NULL ) { ++ printk( KERN_ERR "gs_flush_chars: NULL port pointer\n" ); ++ return; ++ } ++ ++ gs_debug( "gs_flush_chars: (%d,%p)\n", port->port_num, tty ); ++ ++ spin_lock_irqsave(&port->port_lock, flags ); ++ ++ if( port->port_dev == NULL ) { ++ printk( KERN_ERR ++ "gs_flush_chars: (%d,%p) port is not connected\n", ++ port->port_num, tty ); ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ return; ++ } ++ ++ if( port->port_open_count == 0 ) { ++ printk( KERN_ERR "gs_flush_chars: (%d,%p) port is closed\n", ++ port->port_num, tty ); ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ return; ++ } ++ ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ ++ gs_send( gs_device ); ++ ++} ++ ++ ++/* ++ * gs_write_room ++ */ ++ ++static int gs_write_room( struct tty_struct *tty ) ++{ ++ ++ int room = 0; ++ unsigned long flags; ++ struct gs_port *port = tty->driver_data; ++ ++ ++ if( port == NULL ) ++ return( 0 ); ++ ++ spin_lock_irqsave(&port->port_lock, flags ); ++ ++ if( port->port_dev != NULL && port->port_open_count > 0 ++ && port->port_write_buf != NULL ) ++ room = gs_buf_space_avail( port->port_write_buf ); ++ ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ ++ gs_debug( "gs_write_room: (%d,%p) room=%d\n", ++ port->port_num, tty, room ); ++ ++ return( room ); ++ ++} ++ ++ ++/* ++ * gs_chars_in_buffer ++ */ ++ ++static int gs_chars_in_buffer( struct tty_struct *tty ) ++{ ++ ++ int chars = 0; ++ unsigned long flags; ++ struct gs_port *port = tty->driver_data; ++ ++ ++ if( port == NULL ) ++ return( 0 ); ++ ++ spin_lock_irqsave(&port->port_lock, flags ); ++ ++ if( port->port_dev != NULL && port->port_open_count > 0 ++ && port->port_write_buf != NULL ) ++ chars = gs_buf_data_avail( port->port_write_buf ); ++ ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ ++ gs_debug( "gs_chars_in_buffer: (%d,%p) chars=%d\n", ++ port->port_num, tty, chars ); ++ ++ return( chars ); ++ ++} ++ ++ ++/* ++ * gs_throttle ++ */ ++ ++static void gs_throttle( struct tty_struct *tty ) ++{ ++ ++} ++ ++ ++/* ++ * gs_unthrottle ++ */ ++ ++static void gs_unthrottle( struct tty_struct *tty ) ++{ ++ ++} ++ ++ ++/* ++ * gs_break ++ */ ++ ++static void gs_break( struct tty_struct *tty, int break_state ) ++{ ++ ++} ++ ++ ++/* ++ * gs_ioctl ++ */ ++ ++static int gs_ioctl( struct tty_struct *tty, struct file *file, ++ unsigned int cmd, unsigned long arg ) ++{ ++ ++ struct gs_port *port = tty->driver_data; ++ ++ ++ if( port == NULL ) { ++ printk( KERN_ERR "gs_ioctl: NULL port pointer\n" ); ++ return( -EIO ); ++ } ++ ++ gs_debug( "gs_ioctl: (%d,%p,%p) cmd=0x%4.4x, arg=%lu\n", ++ port->port_num, tty, file, cmd, arg ); ++ ++ /* handle ioctls */ ++ ++ /* could not handle ioctl */ ++ return( -ENOIOCTLCMD ); ++ ++} ++ ++ ++/* ++ * gs_set_termios ++ */ ++ ++static void gs_set_termios( struct tty_struct *tty, struct termios *old ) ++{ ++ ++} ++ ++ ++/* ++ * gs_read_proc ++ */ ++ ++static int gs_read_proc( char *page, char **start, off_t off, int count, ++ int *eof, void *data ) ++{ ++ ++ return( 0 ); ++ ++} ++ ++ ++/* ++* gs_send ++* ++* This function finds available write requests, calls ++* gs_send_packet to fill these packets with data, and ++* continues until either there are no more write requests ++* available or no more data to send. This function is ++* run whenever data arrives or write requests are available. ++*/ ++ ++static int gs_send( struct gs_dev *dev ) ++{ ++ ++ int ret,len; ++ unsigned long flags; ++ struct usb_ep *ep; ++ struct usb_request *req; ++ struct gs_req_entry *req_entry; ++ ++ ++ if( dev == NULL ) { ++ printk( KERN_ERR "gs_send: NULL device pointer\n" ); ++ return( -ENODEV ); ++ } ++ ++ spin_lock_irqsave(&dev->dev_lock, flags ); ++ ++ ep = dev->dev_in_ep; ++ ++ while( !list_empty( &dev->dev_req_list ) ) { ++ ++ req_entry = list_entry( dev->dev_req_list.next, ++ struct gs_req_entry, re_entry ); ++ ++ req = req_entry->re_req; ++ ++ len = gs_send_packet( dev, req->buf, ep->maxpacket ); ++ ++ if( len > 0 ) { ++gs_debug_level( 3, "gs_send: len=%d, 0x%2.2x 0x%2.2x 0x%2.2x ...\n", len, *((unsigned char *)req->buf), *((unsigned char *)req->buf+1), *((unsigned char *)req->buf+2) ); ++ list_del( &req_entry->re_entry ); ++ req->length = len; ++ if( (ret=usb_ep_queue( ep, req, GFP_ATOMIC )) ) { ++ printk( KERN_ERR ++ "gs_send: cannot queue read request, ret=%d\n", ++ ret ); ++ break; ++ } ++ } else { ++ break; ++ } ++ ++ } ++ ++ spin_unlock_irqrestore(&dev->dev_lock, flags ); ++ ++ return( 0 ); ++ ++} ++ ++ ++/* ++ * gs_send_packet ++ * ++ * If there is data to send, a packet is built in the given ++ * buffer and the size is returned. If there is no data to ++ * send, 0 is returned. If there is any error a negative ++ * error number is returned. ++ * ++ * Called during USB completion routine, on interrupt time. ++ * ++ * We assume that disconnect will not happen until all completion ++ * routines have completed, so we can assume that the dev_port ++ * array does not change during the lifetime of this function. ++ */ ++ ++static int gs_send_packet( struct gs_dev *dev, char *packet, unsigned int size ) ++{ ++ ++ unsigned int len; ++ struct gs_port *port; ++ ++ ++ /* TEMPORARY -- only port 0 is supported right now */ ++ port = dev->dev_port[0]; ++ ++ if( port == NULL ) { ++ printk( KERN_ERR ++ "gs_send_packet: port=%d, NULL port pointer\n", ++ 0 ); ++ return( -EIO ); ++ } ++ ++ spin_lock(&port->port_lock ); ++ ++ len = gs_buf_data_avail( port->port_write_buf ); ++ if( len < size ) ++ size = len; ++ ++ if( size == 0 ) { ++ spin_unlock(&port->port_lock ); ++ return( 0 ); ++ } ++ ++ size = gs_buf_get( port->port_write_buf, packet, size ); ++ ++ wake_up_interruptible( &port->port_tty->write_wait ); ++ ++ spin_unlock(&port->port_lock ); ++ ++ return( size ); ++ ++} ++ ++ ++/* ++ * gs_recv_packet ++ * ++ * Called for each USB packet received. Reads the packet ++ * header and stuffs the data in the appropriate tty buffer. ++ * Returns 0 if successful, or a negative error number. ++ * ++ * Called during USB completion routine, on interrupt time. ++ * ++ * We assume that disconnect will not happen until all completion ++ * routines have completed, so we can assume that the dev_port ++ * array does not change during the lifetime of this function. ++ */ ++ ++static int gs_recv_packet( struct gs_dev *dev, char *packet, unsigned int size ) ++{ ++ ++ unsigned int len; ++ struct gs_port *port; ++ ++ ++ /* TEMPORARY -- only port 0 is supported right now */ ++ port = dev->dev_port[0]; ++ ++ if( port == NULL ) { ++ printk( KERN_ERR "gs_recv_packet: port=%d, NULL port pointer\n", ++ port->port_num ); ++ return( -EIO ); ++ } ++ ++ spin_lock(&port->port_lock ); ++ ++ if( port->port_tty == NULL ) { ++ printk( KERN_ERR "gs_recv_packet: port=%d, NULL tty pointer\n", ++ port->port_num ); ++ spin_unlock(&port->port_lock ); ++ return( -EIO ); ++ } ++ ++ if( port->port_tty->magic != TTY_MAGIC ) { ++ printk( KERN_ERR "gs_recv_packet: port=%d, bad tty magic\n", ++ port->port_num ); ++ spin_unlock(&port->port_lock ); ++ return( -EIO ); ++ } ++ ++ len = (unsigned int)(TTY_FLIPBUF_SIZE - port->port_tty->flip.count); ++ if( len < size ) ++ size = len; ++ ++ if( size > 0 ) { ++ memcpy( port->port_tty->flip.char_buf_ptr, packet, size ); ++ port->port_tty->flip.char_buf_ptr += size; ++ port->port_tty->flip.count += size; ++ tty_flip_buffer_push( port->port_tty ); ++ wake_up_interruptible( &port->port_tty->read_wait ); ++ } ++ ++ spin_unlock(&port->port_lock ); ++ ++ return( 0 ); ++ ++} ++ ++ ++/* ++* gs_read_complete ++*/ ++ ++static void gs_read_complete( struct usb_ep *ep, struct usb_request *req ) ++{ ++ ++ int ret; ++ struct gs_dev *dev = ep->driver_data; ++ ++ ++ if( dev == NULL ) { ++ printk( KERN_ERR "gs_read_complete: NULL device pointer\n" ); ++ return; ++ } ++ ++ switch( req->status ) { ++ ++ case 0: ++ /* normal completion */ ++ gs_recv_packet( dev, req->buf, req->actual ); ++requeue: ++ req->length = ep->maxpacket; ++ if( (ret=usb_ep_queue( ep, req, GFP_ATOMIC )) ) { ++ printk( KERN_ERR ++ "gs_read_complete: cannot queue read request, ret=%d\n", ++ ret ); ++ } ++ break; ++ ++ case -ESHUTDOWN: ++ /* disconnect */ ++ gs_debug( "gs_read_complete: shutdown\n" ); ++ gs_free_req( ep, req ); ++ break; ++ ++ default: ++ /* unexpected */ ++ printk( KERN_ERR ++ "gs_read_complete: unexpected status error, status=%d\n", ++ req->status ); ++ goto requeue; ++ break; ++ ++ } ++ ++} ++ ++ ++/* ++* gs_write_complete ++*/ ++ ++static void gs_write_complete( struct usb_ep *ep, struct usb_request *req ) ++{ ++ ++ struct gs_dev *dev = ep->driver_data; ++ struct gs_req_entry *gs_req = req->context; ++ ++ ++ if( dev == NULL ) { ++ printk( KERN_ERR "gs_write_complete: NULL device pointer\n" ); ++ return; ++ } ++ ++ switch( req->status ) { ++ ++ case 0: ++ /* normal completion */ ++requeue: ++ if( gs_req == NULL ) { ++ printk( KERN_ERR ++ "gs_write_complete: NULL request pointer\n" ); ++ return; ++ } ++ ++ spin_lock(&dev->dev_lock ); ++ list_add( &gs_req->re_entry, &dev->dev_req_list ); ++ spin_unlock(&dev->dev_lock ); ++ ++ gs_send( dev ); ++ ++ break; ++ ++ case -ESHUTDOWN: ++ /* disconnect */ ++ gs_debug( "gs_write_complete: shutdown\n" ); ++ gs_free_req( ep, req ); ++ break; ++ ++ default: ++ printk( KERN_ERR ++ "gs_write_complete: unexpected status error, status=%d\n", ++ req->status ); ++ goto requeue; ++ break; ++ ++ } ++ ++} ++ ++ ++/* Gadget Driver */ ++ ++/* ++ * gs_bind ++ * ++ * Called on module load. Allocates and initializes the device ++ * structure and a control request. ++ */ ++static int gs_bind(struct usb_gadget *gadget) ++{ ++ int ret; ++ struct usb_ep *ep; ++ struct gs_dev *dev; ++ ++ usb_ep_autoconfig_reset(gadget); ++ ++ ep = usb_ep_autoconfig(gadget, &gs_fullspeed_in_desc); ++ if (!ep) ++ goto autoconf_fail; ++ EP_IN_NAME = ep->name; ++ ep->driver_data = ep; /* claim the endpoint */ ++ ++ ep = usb_ep_autoconfig(gadget, &gs_fullspeed_out_desc); ++ if (!ep) ++ goto autoconf_fail; ++ EP_OUT_NAME = ep->name; ++ ep->driver_data = ep; /* claim the endpoint */ ++ ++ /* device specific bcdDevice value in device descriptor */ ++ if (gadget_is_net2280(gadget)) { ++ gs_device_desc.bcdDevice = ++ __constant_cpu_to_le16(GS_VERSION_NUM|0x0001); ++ } else if (gadget_is_pxa(gadget)) { ++ gs_device_desc.bcdDevice = ++ __constant_cpu_to_le16(GS_VERSION_NUM|0x0002); ++ } else if (gadget_is_sh(gadget)) { ++ gs_device_desc.bcdDevice = ++ __constant_cpu_to_le16(GS_VERSION_NUM|0x0003); ++ } else if (gadget_is_sa1100(gadget)) { ++ gs_device_desc.bcdDevice = ++ __constant_cpu_to_le16(GS_VERSION_NUM|0x0004); ++ } else if (gadget_is_goku(gadget)) { ++ gs_device_desc.bcdDevice = ++ __constant_cpu_to_le16(GS_VERSION_NUM|0x0005); ++ } else if (gadget_is_mq11xx(gadget)) { ++ gs_device_desc.bcdDevice = ++ __constant_cpu_to_le16(GS_VERSION_NUM|0x0006); ++ } else if (gadget_is_omap(gadget)) { ++ gs_device_desc.bcdDevice = ++ __constant_cpu_to_le16(GS_VERSION_NUM|0x0007); ++ } else { ++ printk(KERN_WARNING "gs_bind: controller '%s' not recognized\n", ++ gadget->name); ++ /* unrecognized, but safe unless bulk is REALLY quirky */ ++ gs_device_desc.bcdDevice = ++ __constant_cpu_to_le16(GS_VERSION_NUM|0x0099); ++ } ++ ++ gs_device_desc.bMaxPacketSize0 = gadget->ep0->maxpacket; ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ /* assume ep0 uses the same packet size for both speeds */ ++ gs_qualifier_desc.bMaxPacketSize0 = gs_device_desc.bMaxPacketSize0; ++ /* assume endpoints are dual-speed */ ++ gs_highspeed_in_desc.bEndpointAddress = ++ gs_fullspeed_in_desc.bEndpointAddress; ++ gs_highspeed_out_desc.bEndpointAddress = ++ gs_fullspeed_out_desc.bEndpointAddress; ++#endif /* CONFIG_USB_GADGET_DUALSPEED */ ++ ++ usb_gadget_set_selfpowered(gadget); ++ ++ gs_device = dev = kmalloc(sizeof(struct gs_dev), GFP_KERNEL); ++ if (dev == NULL) ++ return -ENOMEM; ++ ++ snprintf (manufacturer, sizeof(manufacturer), ++ UTS_SYSNAME " " UTS_RELEASE " with %s", gadget->name); ++ ++ memset(dev, 0, sizeof(struct gs_dev)); ++ dev->dev_gadget = gadget; ++ spin_lock_init(&dev->dev_lock); ++ INIT_LIST_HEAD(&dev->dev_req_list); ++ set_gadget_data(gadget, dev); ++ ++ if ((ret = gs_alloc_ports(dev, GFP_KERNEL)) != 0) { ++ printk(KERN_ERR "gs_bind: cannot allocate ports\n"); ++ gs_unbind(gadget); ++ return ret; ++ } ++ ++ /* preallocate control response and buffer */ ++ dev->dev_ctrl_req = gs_alloc_req(gadget->ep0, GS_MAX_DESC_LEN, ++ GFP_KERNEL); ++ if (dev->dev_ctrl_req == NULL) { ++ gs_unbind(gadget); ++ return -ENOMEM; ++ } ++ dev->dev_ctrl_req->complete = gs_setup_complete; ++ ++ gadget->ep0->driver_data = dev; ++ ++ printk(KERN_INFO "gs_bind: %s %s bound\n", ++ GS_LONG_NAME, GS_VERSION_STR); ++ ++ return 0; ++ ++autoconf_fail: ++ printk(KERN_ERR "gs_bind: cannot autoconfigure on %s\n", gadget->name); ++ return -ENODEV; ++} ++ ++ ++/* ++ * gs_unbind ++ * ++ * Called on module unload. Frees the control request and device ++ * structure. ++ */ ++ ++static void gs_unbind( struct usb_gadget *gadget ) ++{ ++ ++ struct gs_dev *dev = get_gadget_data( gadget ); ++ ++ ++ gs_device = NULL; ++ ++ /* read/write requests already freed, only control request remains */ ++ if( dev != NULL ) { ++ if( dev->dev_ctrl_req != NULL ) ++ gs_free_req( gadget->ep0, dev->dev_ctrl_req ); ++ gs_free_ports( dev ); ++ kfree( dev ); ++ set_gadget_data( gadget, NULL ); ++ } ++ ++ printk( KERN_INFO "gs_unbind: %s %s unbound\n", GS_LONG_NAME, ++ GS_VERSION_STR ); ++ ++} ++ ++ ++/* ++ * gs_setup ++ * ++ * Implements all the control endpoint functionality that's not ++ * handled in hardware or the hardware driver. ++ * ++ * Returns the size of the data sent to the host, or a negative ++ * error number. ++ */ ++ ++static int gs_setup( struct usb_gadget *gadget, ++ const struct usb_ctrlrequest *ctrl ) ++{ ++ ++ int ret = -EOPNOTSUPP; ++ unsigned int sv_config; ++ struct gs_dev *dev = get_gadget_data( gadget ); ++ struct usb_request *req = dev->dev_ctrl_req; ++ ++ ++ switch (ctrl->bRequest) { ++ ++ case USB_REQ_GET_DESCRIPTOR: ++ ++ if( ctrl->bRequestType != USB_DIR_IN ) ++ break; ++ ++ switch (ctrl->wValue >> 8) { ++ ++ case USB_DT_DEVICE: ++ ret = min( ctrl->wLength, ++ (u16)sizeof(struct usb_device_descriptor) ); ++ memcpy( req->buf, &gs_device_desc, ret ); ++ break; ++ ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ case USB_DT_DEVICE_QUALIFIER: ++ if (!gadget->is_dualspeed) ++ break; ++ ret = min( ctrl->wLength, ++ (u16)sizeof(struct usb_qualifier_descriptor) ); ++ memcpy( req->buf, &gs_qualifier_desc, ret ); ++ break; ++ ++ case USB_DT_OTHER_SPEED_CONFIG: ++#endif /* CONFIG_USB_GADGET_DUALSPEED */ ++ case USB_DT_CONFIG: ++ ret = gs_build_config_desc( req->buf, gadget->speed, ++ ctrl->wValue >> 8, ctrl->wValue & 0xff ); ++ if( ret >= 0 ) ++ ret = min( ctrl->wLength, (u16)ret ); ++ break; ++ ++ case USB_DT_STRING: ++ /* wIndex == language code. */ ++ ret = usb_gadget_get_string( &gs_string_table, ++ ctrl->wValue & 0xff, req->buf ); ++ if( ret >= 0 ) ++ ret = min( ctrl->wLength, (u16)ret ); ++ break; ++ } ++ break; ++ ++ case USB_REQ_SET_CONFIGURATION: ++ if( ctrl->bRequestType != 0 ) ++ break; ++ spin_lock( &dev->dev_lock ); ++ ret = gs_set_config( dev, ctrl->wValue ); ++ spin_unlock( &dev->dev_lock ); ++ break; ++ ++ case USB_REQ_GET_CONFIGURATION: ++ if( ctrl->bRequestType != USB_DIR_IN ) ++ break; ++ *(u8 *)req->buf = dev->dev_config; ++ ret = min( ctrl->wLength, (u16)1 ); ++ break; ++ ++ case USB_REQ_SET_INTERFACE: ++ if( ctrl->bRequestType != USB_RECIP_INTERFACE ) ++ break; ++ spin_lock( &dev->dev_lock ); ++ if( dev->dev_config == GS_BULK_CONFIG_ID ++ && ctrl->wIndex == GS_INTERFACE_ID ++ && ctrl->wValue == GS_ALT_INTERFACE_ID ) { ++ sv_config = dev->dev_config; ++ /* since there is only one interface, setting the */ ++ /* interface is equivalent to setting the config */ ++ gs_reset_config( dev ); ++ gs_set_config( dev, sv_config ); ++ ret = 0; ++ } ++ spin_unlock( &dev->dev_lock ); ++ break; ++ ++ case USB_REQ_GET_INTERFACE: ++ if( ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE) ) ++ break; ++ if( dev->dev_config == GS_NO_CONFIG_ID ) ++ break; ++ if( ctrl->wIndex != GS_INTERFACE_ID ) { ++ ret = -EDOM; ++ break; ++ } ++ *(u8 *)req->buf = GS_ALT_INTERFACE_ID; ++ ret = min( ctrl->wLength, (u16)1 ); ++ break; ++ ++ default: ++ printk( KERN_ERR "gs_setup: unknown request, type=%02x, request=%02x, value=%04x, index=%04x, length=%d\n", ++ ctrl->bRequestType, ctrl->bRequest, ctrl->wValue, ++ ctrl->wIndex, ctrl->wLength ); ++ break; ++ ++ } ++ ++ /* respond with data transfer before status phase? */ ++ if( ret >= 0 ) { ++ req->length = ret; ++ ret = usb_ep_queue( gadget->ep0, req, GFP_ATOMIC ); ++ if( ret < 0 ) { ++ printk( KERN_ERR ++ "gs_setup: cannot queue response, ret=%d\n", ++ ret ); ++ req->status = 0; ++ gs_setup_complete( gadget->ep0, req ); ++ } ++ } ++ ++ /* device either stalls (ret < 0) or reports success */ ++ return( ret ); ++ ++} ++ ++ ++/* ++ * gs_setup_complete ++ */ ++ ++static void gs_setup_complete( struct usb_ep *ep, struct usb_request *req ) ++{ ++ if( req->status || req->actual != req->length ) { ++ printk( KERN_ERR "gs_setup_complete: status error, status=%d, actual=%d, length=%d\n", ++ req->status, req->actual, req->length ); ++ } ++} ++ ++ ++/* ++ * gs_disconnect ++ * ++ * Called when the device is disconnected. Frees the closed ++ * ports and disconnects open ports. Open ports will be freed ++ * on close. Then reallocates the ports for the next connection. ++ */ ++ ++static void gs_disconnect( struct usb_gadget *gadget ) ++{ ++ ++ unsigned long flags; ++ struct gs_dev *dev = get_gadget_data( gadget ); ++ ++ ++ spin_lock_irqsave( &dev->dev_lock, flags ); ++ ++ gs_reset_config( dev ); ++ ++ /* free closed ports and disconnect open ports */ ++ /* (open ports will be freed when closed) */ ++ gs_free_ports( dev ); ++ ++ /* re-allocate ports for the next connection */ ++ if( gs_alloc_ports( dev, GFP_ATOMIC ) != 0 ) ++ printk( KERN_ERR "gs_disconnect: cannot re-allocate ports\n" ); ++ ++ spin_unlock_irqrestore( &dev->dev_lock, flags ); ++ ++ printk( KERN_INFO "gs_disconnect: %s disconnected\n", GS_LONG_NAME ); ++ ++} ++ ++ ++/* ++ * gs_set_config ++ * ++ * Configures the device by enabling device specific ++ * optimizations, setting up the endpoints, allocating ++ * read and write requests and queuing read requests. ++ * ++ * The device lock must be held when calling this function. ++ */ ++ ++static int gs_set_config( struct gs_dev *dev, unsigned config ) ++{ ++ ++ int i; ++ int ret = 0; ++ struct usb_gadget *gadget = dev->dev_gadget; ++ struct usb_ep *ep; ++ struct usb_request *req; ++ struct gs_req_entry *req_entry; ++ ++ ++ if( dev == NULL ) { ++ printk( KERN_ERR "gs_set_config: NULL device pointer\n" ); ++ return( 0 ); ++ } ++ ++ if( config == dev->dev_config ) ++ return( 0 ); ++ ++ gs_reset_config( dev ); ++ ++ if( config == GS_NO_CONFIG_ID ) ++ return( 0 ); ++ ++ if( config != GS_BULK_CONFIG_ID ) ++ return( -EINVAL ); ++ ++ /* device specific optimizations */ ++ if (gadget_is_net2280(gadget)) ++ net2280_set_fifo_mode(gadget, 1); ++ ++ gadget_for_each_ep( ep, gadget ) { ++ ++ if( strcmp( ep->name, EP_IN_NAME ) == 0 ) { ++ ret = usb_ep_enable( ep, ++ gadget->speed == USB_SPEED_HIGH ? ++ &gs_highspeed_in_desc : &gs_fullspeed_in_desc ); ++ if( ret == 0 ) { ++ ep->driver_data = dev; ++ dev->dev_in_ep = ep; ++ } else { ++ printk( KERN_ERR "gs_set_config: cannot enable in endpoint %s, ret=%d\n", ++ ep->name, ret ); ++ gs_reset_config( dev ); ++ return( ret ); ++ } ++ } ++ ++ else if( strcmp( ep->name, EP_OUT_NAME ) == 0 ) { ++ ret = usb_ep_enable( ep, ++ gadget->speed == USB_SPEED_HIGH ? ++ &gs_highspeed_out_desc : ++ &gs_fullspeed_out_desc ); ++ if( ret == 0 ) { ++ ep->driver_data = dev; ++ dev->dev_out_ep = ep; ++ } else { ++ printk( KERN_ERR "gs_set_config: cannot enable out endpoint %s, ret=%d\n", ++ ep->name, ret ); ++ gs_reset_config( dev ); ++ return( ret ); ++ } ++ } ++ ++ } ++ ++ if( dev->dev_in_ep == NULL || dev->dev_out_ep == NULL ) { ++ gs_reset_config( dev ); ++ printk( KERN_ERR "gs_set_config: cannot find endpoints\n" ); ++ return( -ENODEV ); ++ } ++ ++ /* allocate and queue read requests */ ++ ep = dev->dev_out_ep; ++ for( i=0; imaxpacket, GFP_ATOMIC )) ) { ++ req->complete = gs_read_complete; ++ if( (ret=usb_ep_queue( ep, req, GFP_ATOMIC )) ) { ++ printk( KERN_ERR "gs_set_config: cannot queue read request, ret=%d\n", ++ ret ); ++ } ++ } else { ++ gs_reset_config( dev ); ++ printk( KERN_ERR ++ "gs_set_config: cannot allocate read requests\n" ); ++ return( -ENOMEM ); ++ } ++ } ++ ++ /* allocate write requests, and put on free list */ ++ ep = dev->dev_in_ep; ++ for( i=0; imaxpacket, ++ GFP_ATOMIC )) ) { ++ req_entry->re_req->complete = gs_write_complete; ++ list_add( &req_entry->re_entry, &dev->dev_req_list ); ++ } else { ++ gs_reset_config( dev ); ++ printk( KERN_ERR ++ "gs_set_config: cannot allocate write requests\n" ); ++ return( -ENOMEM ); ++ } ++ } ++ ++ dev->dev_config = config; ++ ++ printk( KERN_INFO "gs_set_config: %s configured for %s speed\n", ++ GS_LONG_NAME, ++ gadget->speed == USB_SPEED_HIGH ? "high" : "full" ); ++ ++ return( 0 ); ++ ++} ++ ++ ++/* ++ * gs_reset_config ++ * ++ * Mark the device as not configured, disable all endpoints, ++ * which forces completion of pending I/O and frees queued ++ * requests, and free the remaining write requests on the ++ * free list. ++ * ++ * The device lock must be held when calling this function. ++ */ ++ ++static void gs_reset_config( struct gs_dev *dev ) ++{ ++ ++ struct gs_req_entry *req_entry; ++ ++ ++ if( dev == NULL ) { ++ printk( KERN_ERR "gs_reset_config: NULL device pointer\n" ); ++ return; ++ } ++ ++ if( dev->dev_config == GS_NO_CONFIG_ID ) ++ return; ++ ++ dev->dev_config = GS_NO_CONFIG_ID; ++ ++ /* free write requests on the free list */ ++ while( !list_empty( &dev->dev_req_list ) ) { ++ req_entry = list_entry( dev->dev_req_list.next, ++ struct gs_req_entry, re_entry ); ++ list_del( &req_entry->re_entry ); ++ gs_free_req_entry( dev->dev_in_ep, req_entry ); ++ } ++ ++ /* disable endpoints, forcing completion of pending i/o; */ ++ /* completion handlers free their requests in this case */ ++ if( dev->dev_in_ep ) { ++ usb_ep_disable( dev->dev_in_ep ); ++ dev->dev_in_ep = NULL; ++ } ++ if( dev->dev_out_ep ) { ++ usb_ep_disable( dev->dev_out_ep ); ++ dev->dev_out_ep = NULL; ++ } ++ ++} ++ ++ ++/* ++ * gs_build_config_desc ++ * ++ * Builds a config descriptor in the given buffer and returns the ++ * length, or a negative error number. ++ */ ++ ++static int gs_build_config_desc( u8 *buf, enum usb_device_speed speed, ++ u8 type, unsigned int index ) ++{ ++ ++ int high_speed; ++ int len = USB_DT_CONFIG_SIZE + USB_DT_INTERFACE_SIZE ++ + GS_NUM_ENDPOINTS * USB_DT_ENDPOINT_SIZE; ++ ++ ++ /* only one config */ ++ if( index != 0 ) ++ return( -EINVAL ); ++ ++ memcpy( buf, &gs_config_desc, USB_DT_CONFIG_SIZE ); ++ ((struct usb_config_descriptor *)buf)->bDescriptorType = type; ++ ((struct usb_config_descriptor *)buf)->wTotalLength = ++ __constant_cpu_to_le16( len ); ++ buf += USB_DT_CONFIG_SIZE; ++ ++ memcpy( buf, &gs_interface_desc, USB_DT_INTERFACE_SIZE ); ++ buf += USB_DT_INTERFACE_SIZE; ++ ++ /* other speed switches high and full speed */ ++ high_speed = (speed == USB_SPEED_HIGH); ++ if( type == USB_DT_OTHER_SPEED_CONFIG ) ++ high_speed = !high_speed; ++ ++ memcpy( buf, ++ high_speed ? &gs_highspeed_in_desc : &gs_fullspeed_in_desc, ++ USB_DT_ENDPOINT_SIZE ); ++ buf += USB_DT_ENDPOINT_SIZE; ++ memcpy( buf, ++ high_speed ? &gs_highspeed_out_desc : &gs_fullspeed_out_desc, ++ USB_DT_ENDPOINT_SIZE ); ++ ++ return( len ); ++ ++} ++ ++ ++/* ++ * gs_alloc_req ++ * ++ * Allocate a usb_request and its buffer. Returns a pointer to the ++ * usb_request or NULL if there is an error. ++ */ ++ ++static struct usb_request *gs_alloc_req( struct usb_ep *ep, unsigned int len, ++ int kmalloc_flags ) ++{ ++ ++ struct usb_request *req; ++ ++ ++ if( ep == NULL ) ++ return( NULL ); ++ ++ req = usb_ep_alloc_request( ep, kmalloc_flags ); ++ ++ if( req != NULL ) { ++ req->length = len; ++ req->buf = usb_ep_alloc_buffer( ep, len, &req->dma, ++ kmalloc_flags ); ++ if( req->buf == NULL ) { ++ usb_ep_free_request( ep, req ); ++ return( NULL ); ++ } ++ } ++ ++ return( req ); ++ ++} ++ ++ ++/* ++ * gs_free_req ++ * ++ * Free a usb_request and its buffer. ++ */ ++ ++static void gs_free_req( struct usb_ep *ep, struct usb_request *req ) ++{ ++ if( ep != NULL && req != NULL ) { ++ if( req->buf != NULL ) ++ usb_ep_free_buffer( ep, req->buf, req->dma, ++ req->length ); ++ usb_ep_free_request( ep, req ); ++ } ++} ++ ++ ++/* ++ * gs_alloc_req_entry ++ * ++ * Allocates a request and its buffer, using the given ++ * endpoint, buffer len, and kmalloc flags. ++ */ ++ ++static struct gs_req_entry *gs_alloc_req_entry( struct usb_ep *ep, ++ unsigned len, int kmalloc_flags ) ++{ ++ ++ struct gs_req_entry *req; ++ ++ ++ req = kmalloc( sizeof(struct gs_req_entry), kmalloc_flags ); ++ if( req == NULL ) ++ return( NULL ); ++ ++ req->re_req = gs_alloc_req( ep, len, kmalloc_flags ); ++ if( req->re_req == NULL ) { ++ kfree( req ); ++ return( NULL ); ++ } ++ ++ req->re_req->context = req; ++ ++ return( req ); ++ ++} ++ ++ ++/* ++ * gs_free_req_entry ++ * ++ * Frees a request and its buffer. ++ */ ++ ++static void gs_free_req_entry( struct usb_ep *ep, struct gs_req_entry *req ) ++{ ++ if( ep != NULL && req != NULL ) { ++ if( req->re_req != NULL ) ++ gs_free_req( ep, req->re_req ); ++ kfree( req ); ++ } ++} ++ ++ ++/* ++ * gs_alloc_ports ++ * ++ * Allocate all ports and set the gs_dev struct to point to them. ++ * Return 0 if successful, or a negative error number. ++ * ++ * The device lock is normally held when calling this function. ++ */ ++ ++static int gs_alloc_ports( struct gs_dev *dev, int kmalloc_flags ) ++{ ++ ++ int i; ++ struct gs_port *port; ++ ++ ++ if( dev == NULL ) ++ return( -EIO ); ++ ++ for( i=0; iport_dev = dev; ++ port->port_num = i; ++ spin_lock_init( &port->port_lock ); ++ init_waitqueue_head( &port->port_write_wait ); ++ ++ dev->dev_port[i] = port; ++ ++ } ++ ++ return( 0 ); ++ ++} ++ ++ ++/* ++ * gs_free_ports ++ * ++ * Free all closed ports. Open ports are disconnected by ++ * freeing their write buffers, setting their device pointers ++ * and the pointers to them in the device to NULL. These ++ * ports will be freed when closed. ++ * ++ * The device lock is normally held when calling this function. ++ */ ++ ++static void gs_free_ports( struct gs_dev *dev ) ++{ ++ ++ int i; ++ unsigned long flags; ++ struct gs_port *port; ++ ++ ++ if( dev == NULL ) ++ return; ++ ++ for( i=0; idev_port[i]) != NULL ) { ++ ++ dev->dev_port[i] = NULL; ++ ++ spin_lock_irqsave(&port->port_lock, flags ); ++ ++ if( port->port_write_buf != NULL ) { ++ gs_buf_free( port->port_write_buf ); ++ port->port_write_buf = NULL; ++ } ++ ++ if( port->port_open_count > 0 || port->port_in_use ) { ++ port->port_dev = NULL; ++ wake_up_interruptible( &port->port_write_wait ); ++ wake_up_interruptible( &port->port_tty->read_wait ); ++ wake_up_interruptible( &port->port_tty->write_wait ); ++ } else { ++ kfree( port ); ++ } ++ ++ spin_unlock_irqrestore(&port->port_lock, flags ); ++ ++ } ++ ++ } ++ ++} ++ ++ ++/* Circular Buffer */ ++ ++/* ++ * gs_buf_alloc ++ * ++ * Allocate a circular buffer and all associated memory. ++ */ ++ ++static struct gs_buf *gs_buf_alloc( unsigned int size, int kmalloc_flags ) ++{ ++ ++ struct gs_buf *gb; ++ ++ ++ if( size == 0 ) ++ return( NULL ); ++ ++ gb = (struct gs_buf *)kmalloc( sizeof(struct gs_buf), kmalloc_flags ); ++ if( gb == NULL ) ++ return( NULL ); ++ ++ gb->buf_buf = kmalloc( size, kmalloc_flags ); ++ if( gb->buf_buf == NULL ) { ++ kfree( gb ); ++ return( NULL ); ++ } ++ ++ gb->buf_size = size; ++ gb->buf_get = gb->buf_put = gb->buf_buf; ++ ++ return( gb ); ++ ++} ++ ++ ++/* ++ * gs_buf_free ++ * ++ * Free the buffer and all associated memory. ++ */ ++ ++void gs_buf_free( struct gs_buf *gb ) ++{ ++ if( gb != NULL ) { ++ if( gb->buf_buf != NULL ) ++ kfree( gb->buf_buf ); ++ kfree( gb ); ++ } ++} ++ ++ ++/* ++ * gs_buf_clear ++ * ++ * Clear out all data in the circular buffer. ++ */ ++ ++void gs_buf_clear( struct gs_buf *gb ) ++{ ++ if( gb != NULL ) ++ gb->buf_get = gb->buf_put; ++ /* equivalent to a get of all data available */ ++} ++ ++ ++/* ++ * gs_buf_data_avail ++ * ++ * Return the number of bytes of data available in the circular ++ * buffer. ++ */ ++ ++unsigned int gs_buf_data_avail( struct gs_buf *gb ) ++{ ++ if( gb != NULL ) ++ return( (gb->buf_size + gb->buf_put - gb->buf_get) ++ % gb->buf_size ); ++ else ++ return( 0 ); ++} ++ ++ ++/* ++ * gs_buf_space_avail ++ * ++ * Return the number of bytes of space available in the circular ++ * buffer. ++ */ ++ ++unsigned int gs_buf_space_avail( struct gs_buf *gb ) ++{ ++ if( gb != NULL ) ++ return( (gb->buf_size + gb->buf_get - gb->buf_put - 1) ++ % gb->buf_size ); ++ else ++ return( 0 ); ++} ++ ++ ++/* ++ * gs_buf_put ++ * ++ * Copy data data from a user buffer and put it into the circular buffer. ++ * Restrict to the amount of space available. ++ * ++ * Return the number of bytes copied. ++ */ ++ ++unsigned int gs_buf_put( struct gs_buf *gb, const char *buf, ++ unsigned int count ) ++{ ++ ++ unsigned int len; ++ ++ ++ if( gb == NULL ) ++ return( 0 ); ++ ++ len = gs_buf_space_avail( gb ); ++ if( count > len ) ++ count = len; ++ ++ if( count == 0 ) ++ return( 0 ); ++ ++ len = gb->buf_buf + gb->buf_size - gb->buf_put; ++ if( count > len ) { ++ memcpy( gb->buf_put, buf, len ); ++ memcpy( gb->buf_buf, buf+len, count - len ); ++ gb->buf_put = gb->buf_buf + count - len; ++ } else { ++ memcpy( gb->buf_put, buf, count ); ++ if( count < len ) ++ gb->buf_put += count; ++ else /* count == len */ ++ gb->buf_put = gb->buf_buf; ++ } ++ ++ return( count ); ++ ++} ++ ++ ++/* ++ * gs_buf_get ++ * ++ * Get data from the circular buffer and copy to the given buffer. ++ * Restrict to the amount of data available. ++ * ++ * Return the number of bytes copied. ++ */ ++ ++unsigned int gs_buf_get( struct gs_buf *gb, char *buf, unsigned int count ) ++{ ++ ++ unsigned int len; ++ ++ ++ if( gb == NULL ) ++ return( 0 ); ++ ++ len = gs_buf_data_avail( gb ); ++ if( count > len ) ++ count = len; ++ ++ if( count == 0 ) ++ return( 0 ); ++ ++ len = gb->buf_buf + gb->buf_size - gb->buf_get; ++ if( count > len ) { ++ memcpy( buf, gb->buf_get, len ); ++ memcpy( buf+len, gb->buf_buf, count - len ); ++ gb->buf_get = gb->buf_buf + count - len; ++ } else { ++ memcpy( buf, gb->buf_get, count ); ++ if( count < len ) ++ gb->buf_get += count; ++ else /* count == len */ ++ gb->buf_get = gb->buf_buf; ++ } ++ ++ return( count ); ++ ++} +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/inode.c kernel/drivers/usb/gadget/inode.c +--- /tmp/kernel/drivers/usb/gadget/inode.c 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/inode.c 2005-04-22 17:53:19.456535934 +0200 +@@ -0,0 +1,1807 @@ ++/* ++ * inode.c -- user mode filesystem api for usb gadget controllers ++ * ++ * Copyright (C) 2003 David Brownell ++ * Copyright (C) 2003 Agilent Technologies ++ * ++ * 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 ++ */ ++ ++ ++#define DEBUG 1 /* data to help fault diagnosis */ ++// #define VERBOSE /* extra debug messages (success too) */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifndef BUG_ON ++#define BUG_ON(condition) do { if (unlikely((condition)!=0)) BUG(); } while(0) ++#endif ++ ++#include ++#include ++ ++ ++/* ++ * The gadgetfs API maps each endpoint to a file descriptor so that you ++ * can use standard synchronous read/write calls for I/O. There's some ++ * O_NONBLOCK and O_ASYNC/FASYNC style i/o support. Example usermode ++ * drivers show how this works in practice. ++ * ++ * Key parts that must be USB-specific are protocols defining how the ++ * read/write operations relate to the hardware state machines. There ++ * are two types of files. One type is for the device, implementing ep0. ++ * The other type is for each IN or OUT endpoint. In both cases, the ++ * user mode driver must configure the hardware before using it. ++ * ++ * - First, dev_config() is called when /dev/gadget/$CHIP is configured ++ * (by writing configuration and device descriptors). Afterwards it ++ * may serve as a source of device events, used to handle all control ++ * requests other than basic enumeration. ++ * ++ * - Then either immediately, or after a SET_CONFIGURATION control request, ++ * ep_config() is called when each /dev/gadget/ep* file is configured ++ * (by writing endpoint descriptors). Afterwards these files are used ++ * to write() IN data or to read() OUT data. To halt the endpoint, a ++ * "wrong direction" request is issued (like reading an IN endpoint). ++ * ++ * Unlike "usbfs" the only ioctl()s are for things that are rare, and maybe ++ * not possible on all hardware. For example, precise fault handling with ++ * respect to data left in endpoint fifos after aborted operations; or ++ * selective clearing of endpoint halts, to implement SET_INTERFACE. ++ */ ++ ++#define DRIVER_DESC "USB Gadget filesystem" ++#define DRIVER_VERSION "20 Aug 2003" ++ ++static const char driver_desc [] = DRIVER_DESC; ++static const char shortname [] = "gadgetfs"; ++ ++MODULE_DESCRIPTION (DRIVER_DESC); ++MODULE_AUTHOR ("David Brownell"); ++MODULE_LICENSE ("GPL"); ++ ++ ++/*----------------------------------------------------------------------*/ ++ ++#define GADGETFS_MAGIC 0xaee71ee7 ++#define DMA_ADDR_INVALID (~(dma_addr_t)0) ++ ++/* /dev/gadget/$CHIP represents ep0 and the whole device */ ++enum ep0_state { ++ /* DISBLED is the initial state. ++ */ ++ STATE_DEV_DISABLED = 0, ++ ++ /* Only one open() of /dev/gadget/$CHIP; only one file tracks ++ * ep0/device i/o modes and binding to the controller. Driver ++ * must always write descriptors to initialize the device, then ++ * the device becomes UNCONNECTED until enumeration. ++ */ ++ STATE_OPENED, ++ ++ /* From then on, ep0 fd is in either of two basic modes: ++ * - (UN)CONNECTED: read usb_gadgetfs_event(s) from it ++ * - SETUP: read/write will transfer control data and succeed; ++ * or if "wrong direction", performs protocol stall ++ */ ++ STATE_UNCONNECTED, ++ STATE_CONNECTED, ++ STATE_SETUP, ++ ++ /* UNBOUND means the driver closed ep0, so the device won't be ++ * accessible again (DEV_DISABLED) until all fds are closed. ++ */ ++ STATE_DEV_UNBOUND, ++}; ++ ++/* enough for the whole queue: most events invalidate others */ ++#define N_EVENT 5 ++ ++struct dev_data { ++ spinlock_t lock; ++ atomic_t count; ++ enum ep0_state state; ++ struct usb_gadgetfs_event event [N_EVENT]; ++ unsigned ev_next; ++ struct fasync_struct *fasync; ++ u8 current_config; ++ ++ /* drivers reading ep0 MUST handle control requests (SETUP) ++ * reported that way; else the host will time out. ++ */ ++ unsigned usermode_setup : 1, ++ setup_in : 1, ++ setup_can_stall : 1, ++ setup_out_ready : 1, ++ setup_out_error : 1, ++ setup_abort : 1; ++ ++ /* the rest is basically write-once */ ++ struct usb_config_descriptor *config, *hs_config; ++ struct usb_device_descriptor *dev; ++ struct usb_request *req; ++ struct usb_gadget *gadget; ++ struct list_head epfiles; ++ void *buf; ++ wait_queue_head_t wait; ++ struct super_block *sb; ++ struct dentry *dentry; ++ ++ /* except this scratch i/o buffer for ep0 */ ++ u8 rbuf [256]; ++}; ++ ++static inline void get_dev (struct dev_data *data) ++{ ++ atomic_inc (&data->count); ++} ++ ++static void put_dev (struct dev_data *data) ++{ ++ if (likely (!atomic_dec_and_test (&data->count))) ++ return; ++ /* needs no more cleanup */ ++ BUG_ON (waitqueue_active (&data->wait)); ++ kfree (data); ++} ++ ++static struct dev_data *dev_new (void) ++{ ++ struct dev_data *dev; ++ ++ dev = kmalloc (sizeof *dev, GFP_KERNEL); ++ if (!dev) ++ return 0; ++ memset (dev, 0, sizeof *dev); ++ dev->state = STATE_DEV_DISABLED; ++ atomic_set (&dev->count, 1); ++ spin_lock_init (&dev->lock); ++ INIT_LIST_HEAD (&dev->epfiles); ++ init_waitqueue_head (&dev->wait); ++ return dev; ++} ++ ++/*----------------------------------------------------------------------*/ ++ ++/* other /dev/gadget/$ENDPOINT files represent endpoints */ ++enum ep_state { ++ STATE_EP_DISABLED = 0, ++ STATE_EP_READY, ++ STATE_EP_DEFER_ENABLE, ++ STATE_EP_ENABLED, ++ STATE_EP_UNBOUND, ++}; ++ ++struct ep_data { ++ struct semaphore lock; ++ enum ep_state state; ++ atomic_t count; ++ struct dev_data *dev; ++ /* must hold dev->lock before accessing ep or req */ ++ struct usb_ep *ep; ++ struct usb_request *req; ++ ssize_t status; ++ char name [16]; ++ struct usb_endpoint_descriptor desc, hs_desc; ++ struct list_head epfiles; ++ wait_queue_head_t wait; ++ struct dentry *dentry; ++ struct inode *inode; ++}; ++ ++static inline void get_ep (struct ep_data *data) ++{ ++ atomic_inc (&data->count); ++} ++ ++static void put_ep (struct ep_data *data) ++{ ++ if (likely (!atomic_dec_and_test (&data->count))) ++ return; ++ put_dev (data->dev); ++ /* needs no more cleanup */ ++ BUG_ON (!list_empty (&data->epfiles)); ++ BUG_ON (waitqueue_active (&data->wait)); ++ BUG_ON (down_trylock (&data->lock) != 0); ++ kfree (data); ++} ++ ++/*----------------------------------------------------------------------*/ ++ ++/* most "how to use the hardware" policy choices are in userspace: ++ * mapping endpoint roles the driver needs to the capabilities that ++ * the usb controller exposes. ++ */ ++ ++ // FIXME the 2.6 version just probes the controller ++ // driver to find out the chip name; we should too. ++ ++#ifdef CONFIG_USB_GADGET_NET2280 ++#define CHIP "net2280" ++#define HIGHSPEED ++#endif ++ ++#ifdef CONFIG_USB_GADGET_PXA2XX ++#define CHIP "pxa2xx_udc" ++/* earlier hardware doesn't have UDCCFR, races set_{config,interface} */ ++#warning works best with pxa255 or newer ++#endif ++ ++#ifdef CONFIG_USB_GADGET_GOKU ++#define CHIP "goku_udc" ++#endif ++ ++#ifdef CONFIG_USB_GADGET_SA1100 ++#define CHIP "sa1100" ++#endif ++ ++#ifdef CONFIG_USB_GADGET_SUPERH ++#define CHIP "superh_udc" ++#endif ++ ++#ifdef CONFIG_USB_GADGET_N9604 ++#define CHIP "n9604_udc" ++#endif ++ ++ ++/*----------------------------------------------------------------------*/ ++ ++/* NOTE: don't use dev_printk calls before binding to the gadget ++ * at the end of ep0 configuration, or after unbind. ++ */ ++ ++/* too wordy: dev_printk(level , &(d)->gadget->dev , fmt , ## args) */ ++#define xprintk(d,level,fmt,args...) \ ++ printk(level "%s: " fmt , shortname , ## args) ++ ++#ifdef DEBUG ++#define DBG(dev,fmt,args...) \ ++ xprintk(dev , KERN_DEBUG , fmt , ## args) ++#else ++#define DBG(dev,fmt,args...) \ ++ do { } while (0) ++#endif /* DEBUG */ ++ ++#ifdef VERBOSE ++#define VDEBUG DBG ++#else ++#define VDEBUG(dev,fmt,args...) \ ++ do { } while (0) ++#endif /* DEBUG */ ++ ++#define ERROR(dev,fmt,args...) \ ++ xprintk(dev , KERN_ERR , fmt , ## args) ++#define WARN(dev,fmt,args...) \ ++ xprintk(dev , KERN_WARNING , fmt , ## args) ++#define INFO(dev,fmt,args...) \ ++ xprintk(dev , KERN_INFO , fmt , ## args) ++ ++ ++/*----------------------------------------------------------------------*/ ++ ++/* SYNCHRONOUS ENDPOINT OPERATIONS (bulk/intr/iso) ++ * ++ * After opening, configure non-control endpoints. Then use normal ++ * stream read() and write() requests; and maybe ioctl() to get more ++ * precise FIFO status when recovering from cancelation. ++ */ ++ ++static void epio_complete (struct usb_ep *ep, struct usb_request *req) ++{ ++ struct ep_data *epdata = ep->driver_data; ++ ++ if (!req->context) ++ return; ++ if (req->status) ++ epdata->status = req->status; ++ else ++ epdata->status = req->actual; ++ complete ((struct completion *)req->context); ++} ++ ++/* tasklock endpoint, returning when it's connected. ++ * still need dev->lock to use epdata->ep. ++ */ ++static int ++get_ready_ep (unsigned f_flags, struct ep_data *epdata) ++{ ++ int val; ++ ++ if (f_flags & O_NONBLOCK) { ++ if (down_trylock (&epdata->lock) != 0) ++ goto nonblock; ++ if (epdata->state != STATE_EP_ENABLED) { ++ up (&epdata->lock); ++nonblock: ++ val = -EAGAIN; ++ } else ++ val = 0; ++ return val; ++ } ++ ++ if ((val = down_interruptible (&epdata->lock)) < 0) ++ return val; ++newstate: ++ switch (epdata->state) { ++ case STATE_EP_ENABLED: ++ break; ++ case STATE_EP_DEFER_ENABLE: ++ DBG (epdata->dev, "%s wait for host\n", epdata->name); ++ if ((val = wait_event_interruptible (epdata->wait, ++ epdata->state != STATE_EP_DEFER_ENABLE ++ || epdata->dev->state == STATE_DEV_UNBOUND ++ )) < 0) ++ goto fail; ++ goto newstate; ++ // case STATE_EP_DISABLED: /* "can't happen" */ ++ // case STATE_EP_READY: /* "can't happen" */ ++ default: /* error! */ ++ pr_debug ("%s: ep %p not available, state %d\n", ++ shortname, epdata, epdata->state); ++ // FALLTHROUGH ++ case STATE_EP_UNBOUND: /* clean disconnect */ ++ val = -ENODEV; ++fail: ++ up (&epdata->lock); ++ } ++ return val; ++} ++ ++static ssize_t ++ep_io (struct ep_data *epdata, void *buf, unsigned len) ++{ ++ DECLARE_COMPLETION (done); ++ int value; ++ ++ spin_lock_irq (&epdata->dev->lock); ++ if (likely (epdata->ep != NULL)) { ++ struct usb_request *req = epdata->req; ++ ++ req->context = &done; ++ req->complete = epio_complete; ++ req->buf = buf; ++ req->length = len; ++ value = usb_ep_queue (epdata->ep, req, GFP_ATOMIC); ++ } else ++ value = -ENODEV; ++ spin_unlock_irq (&epdata->dev->lock); ++ ++ if (likely (value == 0)) { ++ value = wait_event_interruptible (done.wait, done.done); ++ if (value != 0) { ++ spin_lock_irq (&epdata->dev->lock); ++ if (likely (epdata->ep != NULL)) { ++ DBG (epdata->dev, "%s i/o interrupted\n", ++ epdata->name); ++ usb_ep_dequeue (epdata->ep, epdata->req); ++ spin_unlock_irq (&epdata->dev->lock); ++ ++ wait_event (done.wait, done.done); ++ if (epdata->status == -ECONNRESET) ++ epdata->status = -EINTR; ++ } else { ++ spin_unlock_irq (&epdata->dev->lock); ++ ++ DBG (epdata->dev, "endpoint gone\n"); ++ epdata->status = -ENODEV; ++ } ++ } ++ return epdata->status; ++ } ++ return value; ++} ++ ++ ++/* handle a synchronous OUT bulk/intr/iso transfer */ ++static ssize_t ++ep_read (struct file *fd, char *buf, size_t len, loff_t *ptr) ++{ ++ struct ep_data *data = fd->private_data; ++ void *kbuf; ++ ssize_t value; ++ ++ if ((value = get_ready_ep (fd->f_flags, data)) < 0) ++ return value; ++ ++ /* halt any endpoint by doing a "wrong direction" i/o call */ ++ if (data->desc.bEndpointAddress & USB_DIR_IN) { ++ if ((data->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ++ == USB_ENDPOINT_XFER_ISOC) ++ return -EINVAL; ++ DBG (data->dev, "%s halt\n", data->name); ++ spin_lock_irq (&data->dev->lock); ++ if (likely (data->ep != NULL)) ++ usb_ep_set_halt (data->ep); ++ spin_unlock_irq (&data->dev->lock); ++ up (&data->lock); ++ return -EBADMSG; ++ } ++ ++ /* FIXME readahead for O_NONBLOCK and poll(); careful with ZLPs */ ++ ++ value = -ENOMEM; ++ kbuf = kmalloc (len, SLAB_KERNEL); ++ if (unlikely (!kbuf)) ++ goto free1; ++ ++ value = ep_io (data, kbuf, len); ++ VDEBUG (data->dev, "%s read %d OUT, status %d\n", ++ data->name, len, value); ++ if (value >= 0 && copy_to_user (buf, kbuf, value)) ++ value = -EFAULT; ++ ++free1: ++ up (&data->lock); ++ kfree (kbuf); ++ return value; ++} ++ ++/* handle a synchronous IN bulk/intr/iso transfer */ ++static ssize_t ++ep_write (struct file *fd, const char *buf, size_t len, loff_t *ptr) ++{ ++ struct ep_data *data = fd->private_data; ++ void *kbuf; ++ ssize_t value; ++ ++ if ((value = get_ready_ep (fd->f_flags, data)) < 0) ++ return value; ++ ++ /* halt any endpoint by doing a "wrong direction" i/o call */ ++ if (!(data->desc.bEndpointAddress & USB_DIR_IN)) { ++ if ((data->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ++ == USB_ENDPOINT_XFER_ISOC) ++ return -EINVAL; ++ DBG (data->dev, "%s halt\n", data->name); ++ spin_lock_irq (&data->dev->lock); ++ if (likely (data->ep != NULL)) ++ usb_ep_set_halt (data->ep); ++ spin_unlock_irq (&data->dev->lock); ++ up (&data->lock); ++ return -EBADMSG; ++ } ++ ++ /* FIXME writebehind for O_NONBLOCK and poll(), qlen = 1 */ ++ ++ value = -ENOMEM; ++ kbuf = kmalloc (len, SLAB_KERNEL); ++ if (!kbuf) ++ goto free1; ++ if (copy_from_user (kbuf, buf, len)) { ++ value = -EFAULT; ++ goto free1; ++ } ++ ++ value = ep_io (data, kbuf, len); ++ VDEBUG (data->dev, "%s write %d IN, status %d\n", ++ data->name, len, value); ++free1: ++ up (&data->lock); ++ kfree (kbuf); ++ return value; ++} ++ ++static int ++ep_release (struct inode *inode, struct file *fd) ++{ ++ struct ep_data *data = fd->private_data; ++ ++ /* clean up if this can be reopened */ ++ if (data->state != STATE_EP_UNBOUND) { ++ data->state = STATE_EP_DISABLED; ++ data->desc.bDescriptorType = 0; ++ data->hs_desc.bDescriptorType = 0; ++ } ++ put_ep (data); ++ return 0; ++} ++ ++static int ep_ioctl (struct inode *inode, struct file *fd, ++ unsigned code, unsigned long value) ++{ ++ struct ep_data *data = fd->private_data; ++ int status; ++ ++ if ((status = get_ready_ep (fd->f_flags, data)) < 0) ++ return status; ++ ++ spin_lock_irq (&data->dev->lock); ++ if (likely (data->ep != NULL)) { ++ switch (code) { ++ case GADGETFS_FIFO_STATUS: ++ status = usb_ep_fifo_status (data->ep); ++ break; ++ case GADGETFS_FIFO_FLUSH: ++ usb_ep_fifo_flush (data->ep); ++ break; ++ case GADGETFS_CLEAR_HALT: ++ status = usb_ep_clear_halt (data->ep); ++ break; ++ default: ++ status = -ENOTTY; ++ } ++ } else ++ status = -ENODEV; ++ spin_unlock_irq (&data->dev->lock); ++ up (&data->lock); ++ return status; ++} ++ ++/* used after endpoint configuration */ ++static struct file_operations ep_io_operations = { ++ .owner = THIS_MODULE, ++ .read = ep_read, ++ .write = ep_write, ++ .ioctl = ep_ioctl, ++ .release = ep_release, ++ ++ // .aio_read = ep_aio_read, ++ // .aio_write = ep_aio_write, ++}; ++ ++/* ENDPOINT INITIALIZATION ++ * ++ * fd = open ("/dev/gadget/$ENDPOINT", O_RDWR) ++ * status = write (fd, descriptors, sizeof descriptors) ++ * ++ * That write establishes the endpoint configuration, configuring ++ * the controller to process bulk, interrupt, or isochronous transfers ++ * at the right maxpacket size, and so on. ++ * ++ * The descriptors are message type 1, identified by a host order u32 ++ * at the beginning of what's written. Descriptor order is: full/low ++ * speed descriptor, then optional high speed descriptor. ++ */ ++static ssize_t ++ep_config (struct file *fd, const char *buf, size_t len, loff_t *ptr) ++{ ++ struct ep_data *data = fd->private_data; ++ struct usb_ep *ep; ++ u32 tag; ++ int value; ++ ++ if ((value = down_interruptible (&data->lock)) < 0) ++ return value; ++ ++ if (data->state != STATE_EP_READY) { ++ value = -EL2HLT; ++ goto fail; ++ } ++ ++ value = len; ++ if (len < USB_DT_ENDPOINT_SIZE + 4) ++ goto fail0; ++ ++ /* we might need to change message format someday */ ++ if (copy_from_user (&tag, buf, 4)) { ++ goto fail1; ++ } ++ if (tag != 1) { ++ DBG(data->dev, "config %s, bad tag %d\n", data->name, tag); ++ goto fail0; ++ } ++ buf += 4; ++ len -= 4; ++ ++ /* NOTE: audio endpoint extensions not accepted here; ++ * just don't include the extra bytes. ++ */ ++ ++ /* full/low speed descriptor, then high speed */ ++ if (copy_from_user (&data->desc, buf, USB_DT_ENDPOINT_SIZE)) { ++ goto fail1; ++ } ++ if (data->desc.bLength != USB_DT_ENDPOINT_SIZE ++ || data->desc.bDescriptorType != USB_DT_ENDPOINT) ++ goto fail0; ++ if (len != USB_DT_ENDPOINT_SIZE) { ++ if (len != 2 * USB_DT_ENDPOINT_SIZE) ++ goto fail0; ++ if (copy_from_user (&data->hs_desc, buf + USB_DT_ENDPOINT_SIZE, ++ USB_DT_ENDPOINT_SIZE)) { ++ goto fail1; ++ } ++ if (data->hs_desc.bLength != USB_DT_ENDPOINT_SIZE ++ || data->hs_desc.bDescriptorType ++ != USB_DT_ENDPOINT) { ++ DBG(data->dev, "config %s, bad hs length or type\n", ++ data->name); ++ goto fail0; ++ } ++ } ++ value = len; ++ ++ spin_lock_irq (&data->dev->lock); ++ if (data->dev->state == STATE_DEV_UNBOUND) { ++ value = -ENOENT; ++ goto gone; ++ } else if ((ep = data->ep) == NULL) { ++ value = -ENODEV; ++ goto gone; ++ } ++ switch (data->dev->gadget->speed) { ++ case USB_SPEED_LOW: ++ case USB_SPEED_FULL: ++ value = usb_ep_enable (ep, &data->desc); ++ if (value == 0) ++ data->state = STATE_EP_ENABLED; ++ break; ++#ifdef HIGHSPEED ++ case USB_SPEED_HIGH: ++ /* fails if caller didn't provide that descriptor... */ ++ value = usb_ep_enable (ep, &data->hs_desc); ++ if (value == 0) ++ data->state = STATE_EP_ENABLED; ++ break; ++#endif ++ default: ++ DBG (data->dev, "unconnected, %s init deferred\n", ++ data->name); ++ data->state = STATE_EP_DEFER_ENABLE; ++ } ++ if (value == 0) ++ fd->f_op = &ep_io_operations; ++gone: ++ spin_unlock_irq (&data->dev->lock); ++ if (value < 0) { ++fail: ++ data->desc.bDescriptorType = 0; ++ data->hs_desc.bDescriptorType = 0; ++ } ++ up (&data->lock); ++ return value; ++fail0: ++ value = -EINVAL; ++ goto fail; ++fail1: ++ value = -EFAULT; ++ goto fail; ++} ++ ++static int ++ep_open (struct inode *inode, struct file *fd) ++{ ++ struct ep_data *data = inode->u.generic_ip; ++ int value = -EBUSY; ++ ++ if (down_interruptible (&data->lock) != 0) ++ return -EINTR; ++ spin_lock_irq (&data->dev->lock); ++ if (data->dev->state == STATE_DEV_UNBOUND) ++ value = -ENOENT; ++ else if (data->state == STATE_EP_DISABLED) { ++ value = 0; ++ data->state = STATE_EP_READY; ++ get_ep (data); ++ fd->private_data = data; ++ VDEBUG (data->dev, "%s ready\n", data->name); ++ } else ++ DBG (data->dev, "%s state %d\n", ++ data->name, data->state); ++ spin_unlock_irq (&data->dev->lock); ++ up (&data->lock); ++ return value; ++} ++ ++/* used before endpoint configuration */ ++static struct file_operations ep_config_operations = { ++ .owner = THIS_MODULE, ++ .open = ep_open, ++ .write = ep_config, ++ .release = ep_release, ++}; ++ ++/*----------------------------------------------------------------------*/ ++ ++/* EP0 IMPLEMENTATION can be partly in userspace. ++ * ++ * Drivers that use this facility receive various events, including ++ * control requests the kernel doesn't handle. Drivers that don't ++ * use this facility may be too simple-minded for real applications. ++ */ ++ ++static inline void ep0_readable (struct dev_data *dev) ++{ ++ wake_up (&dev->wait); ++ kill_fasync (&dev->fasync, SIGIO, POLL_IN); ++} ++ ++static void clean_req (struct usb_ep *ep, struct usb_request *req) ++{ ++ struct dev_data *dev = ep->driver_data; ++ ++ if (req->buf != dev->rbuf) { ++ usb_ep_free_buffer (ep, req->buf, req->dma, req->length); ++ req->buf = dev->rbuf; ++ req->dma = DMA_ADDR_INVALID; ++ } ++ req->complete = epio_complete; ++ dev->setup_out_ready = 0; ++} ++ ++static void ep0_complete (struct usb_ep *ep, struct usb_request *req) ++{ ++ struct dev_data *dev = ep->driver_data; ++ int free = 1; ++ ++ /* for control OUT, data must still get to userspace */ ++ if (!dev->setup_in) { ++ dev->setup_out_error = (req->status != 0); ++ if (!dev->setup_out_error) ++ free = 0; ++ dev->setup_out_ready = 1; ++ ep0_readable (dev); ++ } else if (dev->state == STATE_SETUP) ++ dev->state = STATE_CONNECTED; ++ ++ /* clean up as appropriate */ ++ if (free && req->buf != &dev->rbuf) ++ clean_req (ep, req); ++ req->complete = epio_complete; ++} ++ ++static int setup_req (struct usb_ep *ep, struct usb_request *req, u16 len) ++{ ++ struct dev_data *dev = ep->driver_data; ++ ++ if (dev->setup_out_ready) { ++ DBG (dev, "ep0 request busy!\n"); ++ return -EBUSY; ++ } ++ if (len > sizeof (dev->rbuf)) ++ req->buf = usb_ep_alloc_buffer (ep, len, &req->dma, GFP_ATOMIC); ++ if (req->buf == 0) { ++ req->buf = dev->rbuf; ++ return -ENOMEM; ++ } ++ req->complete = ep0_complete; ++ req->length = len; ++ return 0; ++} ++ ++static ssize_t ++ep0_read (struct file *fd, char *buf, size_t len, loff_t *ptr) ++{ ++ struct dev_data *dev = fd->private_data; ++ ssize_t retval; ++ enum ep0_state state; ++ ++ spin_lock_irq (&dev->lock); ++ ++ /* report fd mode change before acting on it */ ++ if (dev->setup_abort) { ++ dev->setup_abort = 0; ++ retval = -EIDRM; ++ goto done; ++ } ++ ++ /* control DATA stage */ ++ if ((state = dev->state) == STATE_SETUP) { ++ ++ if (dev->setup_in) { /* stall IN */ ++ VDEBUG(dev, "ep0in stall\n"); ++ (void) usb_ep_set_halt (dev->gadget->ep0); ++ retval = -EL2HLT; ++ dev->state = STATE_CONNECTED; ++ ++ } else if (len == 0) { /* ack SET_CONFIGURATION etc */ ++ struct usb_ep *ep = dev->gadget->ep0; ++ struct usb_request *req = dev->req; ++ ++ if ((retval = setup_req (ep, req, 0)) == 0) ++ retval = usb_ep_queue (ep, req, GFP_ATOMIC); ++ dev->state = STATE_CONNECTED; ++ ++ } else { /* collect OUT data */ ++ if ((fd->f_flags & O_NONBLOCK) != 0 ++ && !dev->setup_out_ready) { ++ retval = -EAGAIN; ++ goto done; ++ } ++ spin_unlock_irq (&dev->lock); ++ retval = wait_event_interruptible (dev->wait, ++ dev->setup_out_ready != 0); ++ ++ /* FIXME state could change from under us */ ++ spin_lock_irq (&dev->lock); ++ if (retval) ++ goto done; ++ if (dev->setup_out_error) ++ retval = -EIO; ++ else { ++ len = min (len, dev->req->actual); ++// FIXME don't call this with the spinlock held ... ++ if (copy_to_user (buf, &dev->req->buf, len)) ++ retval = -EFAULT; ++ clean_req (dev->gadget->ep0, dev->req); ++ /* NOTE userspace can't yet choose to stall */ ++ } ++ } ++ goto done; ++ } ++ ++ /* else normal: return event data */ ++ if (len < sizeof dev->event [0]) { ++ retval = -EINVAL; ++ goto done; ++ } ++ len -= len % sizeof (struct usb_gadgetfs_event); ++ dev->usermode_setup = 1; ++ ++scan: ++ /* return queued events right away */ ++ if (dev->ev_next != 0) { ++ unsigned i, n; ++ int tmp = dev->ev_next; ++ ++ len = min (len, tmp * sizeof (struct usb_gadgetfs_event)); ++ n = len / sizeof (struct usb_gadgetfs_event); ++ ++ /* ep0 can't deliver events when STATE_SETUP */ ++ for (i = 0; i < n; i++) { ++ if (dev->event [i].type == GADGETFS_SETUP) { ++ len = n = i + 1; ++ len *= sizeof (struct usb_gadgetfs_event); ++ n = 0; ++ break; ++ } ++ } ++ spin_unlock_irq (&dev->lock); ++ if (copy_to_user (buf, &dev->event, len)) ++ retval = -EFAULT; ++ else ++ retval = len; ++ if (len > 0) { ++ len /= sizeof (struct usb_gadgetfs_event); ++ ++ /* NOTE this doesn't guard against broken drivers; ++ * concurrent ep0 readers may lose events. ++ */ ++ spin_lock_irq (&dev->lock); ++ dev->ev_next -= len; ++ if (dev->ev_next != 0) ++ memmove (&dev->event, &dev->event [len], ++ sizeof (struct usb_gadgetfs_event) ++ * (tmp - len)); ++ if (n == 0) ++ dev->state = STATE_SETUP; ++ spin_unlock_irq (&dev->lock); ++ } ++ return retval; ++ } ++ if (fd->f_flags & O_NONBLOCK) { ++ retval = -EAGAIN; ++ goto done; ++ } ++ ++ switch (state) { ++ default: ++ DBG (dev, "fail %s, state %d\n", __FUNCTION__, state); ++ retval = -ESRCH; ++ break; ++ case STATE_UNCONNECTED: ++ case STATE_CONNECTED: ++ spin_unlock_irq (&dev->lock); ++ DBG (dev, "%s wait\n", __FUNCTION__); ++ ++ /* wait for events */ ++ retval = wait_event_interruptible (dev->wait, ++ dev->ev_next != 0); ++ if (retval < 0) ++ return retval; ++ spin_lock_irq (&dev->lock); ++ goto scan; ++ } ++ ++done: ++ spin_unlock_irq (&dev->lock); ++ return retval; ++} ++ ++static struct usb_gadgetfs_event * ++next_event (struct dev_data *dev, enum usb_gadgetfs_event_type type) ++{ ++ struct usb_gadgetfs_event *event; ++ unsigned i; ++ ++ switch (type) { ++ /* these events purge the queue */ ++ case GADGETFS_DISCONNECT: ++ if (dev->state == STATE_SETUP) ++ dev->setup_abort = 1; ++ // FALL THROUGH ++ case GADGETFS_CONNECT: ++ dev->ev_next = 0; ++ break; ++ case GADGETFS_SETUP: /* previous request timed out */ ++ case GADGETFS_SUSPEND: /* same effect */ ++ /* these events can't be repeated */ ++ for (i = 0; i != dev->ev_next; i++) { ++ if (dev->event [i].type != type) ++ continue; ++ DBG (dev, "discard old event %d\n", type); ++ dev->ev_next--; ++ if (i == dev->ev_next) ++ break; ++ /* indices start at zero, for simplicity */ ++ memmove (&dev->event [i], &dev->event [i + 1], ++ sizeof (struct usb_gadgetfs_event) ++ * (dev->ev_next - i)); ++ } ++ break; ++ default: ++ BUG (); ++ } ++ event = &dev->event [dev->ev_next++]; ++ BUG_ON (dev->ev_next > N_EVENT); ++ VDEBUG (dev, "ev %d, next %d\n", type, dev->ev_next); ++ memset (event, 0, sizeof *event); ++ event->type = type; ++ return event; ++} ++ ++static ssize_t ++ep0_write (struct file *fd, const char *buf, size_t len, loff_t *ptr) ++{ ++ struct dev_data *dev = fd->private_data; ++ ssize_t retval = -ESRCH; ++ ++ spin_lock_irq (&dev->lock); ++ ++ /* report fd mode change before acting on it */ ++ if (dev->setup_abort) { ++ dev->setup_abort = 0; ++ retval = -EIDRM; ++ ++ /* data and/or status stage for control request */ ++ } else if (dev->state == STATE_SETUP) { ++ ++ /* IN DATA+STATUS caller makes len <= wLength */ ++ if (dev->setup_in) { ++ retval = setup_req (dev->gadget->ep0, dev->req, len); ++ if (retval == 0) { ++ spin_unlock_irq (&dev->lock); ++ if (copy_from_user (dev->req->buf, buf, len)) ++ retval = -EFAULT; ++ else ++ retval = usb_ep_queue ( ++ dev->gadget->ep0, dev->req, ++ GFP_KERNEL); ++ if (retval < 0) { ++ spin_lock_irq (&dev->lock); ++ clean_req (dev->gadget->ep0, dev->req); ++ spin_unlock_irq (&dev->lock); ++ } else ++ retval = len; ++ ++ return retval; ++ } ++ ++ /* can stall some OUT transfers */ ++ } else if (dev->setup_can_stall) { ++ VDEBUG(dev, "ep0out stall\n"); ++ (void) usb_ep_set_halt (dev->gadget->ep0); ++ retval = -EL2HLT; ++ dev->state = STATE_CONNECTED; ++ } else { ++ DBG(dev, "bogus ep0out stall!\n"); ++ } ++ } else ++ DBG (dev, "fail %s, state %d\n", __FUNCTION__, dev->state); ++ ++ spin_unlock_irq (&dev->lock); ++ return retval; ++} ++ ++static int ++ep0_fasync (int f, struct file *fd, int on) ++{ ++ struct dev_data *dev = fd->private_data; ++ // caller must F_SETOWN before signal delivery happens ++ VDEBUG (dev, "%s %s\n", __FUNCTION__, on ? "on" : "off"); ++ return fasync_helper (f, fd, on, &dev->fasync); ++} ++ ++static struct usb_gadget_driver gadgetfs_driver; ++ ++static int ++dev_release (struct inode *inode, struct file *fd) ++{ ++ struct dev_data *dev = fd->private_data; ++ ++ /* closing ep0 === shutdown all */ ++ ++ usb_gadget_unregister_driver (&gadgetfs_driver); ++ ++ /* at this point "good" hardware has disconnected the ++ * device from USB; the host won't see it any more. ++ * alternatively, all host requests will time out. ++ */ ++ ++ fasync_helper (-1, fd, 0, &dev->fasync); ++ kfree (dev->buf); ++ dev->buf = 0; ++ put_dev (dev); ++ ++ /* other endpoints were all decoupled from this device */ ++ dev->state = STATE_DEV_DISABLED; ++ return 0; ++} ++ ++static int dev_ioctl (struct inode *inode, struct file *fd, ++ unsigned code, unsigned long value) ++{ ++ struct dev_data *dev = fd->private_data; ++ struct usb_gadget *gadget = dev->gadget; ++ ++ if (gadget->ops->ioctl) ++ return gadget->ops->ioctl (gadget, code, value); ++ return -ENOTTY; ++} ++ ++/* used after device configuration */ ++static struct file_operations ep0_io_operations = { ++ .owner = THIS_MODULE, ++ .read = ep0_read, ++ .write = ep0_write, ++ .fasync = ep0_fasync, ++ // .poll = ep0_poll, ++ .ioctl = dev_ioctl, ++ .release = dev_release, ++}; ++ ++/*----------------------------------------------------------------------*/ ++ ++/* The in-kernel gadget driver handles most ep0 issues, in particular ++ * enumerating the single configuration (as provided from user space). ++ * ++ * Unrecognized ep0 requests may be handled in user space. ++ */ ++ ++#ifdef HIGHSPEED ++static void make_qualifier (struct dev_data *dev) ++{ ++ struct usb_qualifier_descriptor qual; ++ struct usb_device_descriptor *desc; ++ ++ qual.bLength = sizeof qual; ++ qual.bDescriptorType = USB_DT_DEVICE_QUALIFIER; ++ qual.bcdUSB = __constant_cpu_to_le16 (0x0200); ++ ++ desc = dev->dev; ++ qual.bDeviceClass = desc->bDeviceClass; ++ qual.bDeviceSubClass = desc->bDeviceSubClass; ++ qual.bDeviceProtocol = desc->bDeviceProtocol; ++ ++ /* assumes ep0 uses the same value for both speeds ... */ ++ qual.bMaxPacketSize0 = desc->bMaxPacketSize0; ++ ++ qual.bNumConfigurations = 1; ++ qual.bRESERVED = 0; ++ ++ memcpy (dev->rbuf, &qual, sizeof qual); ++} ++#endif ++ ++static int ++config_buf (struct dev_data *dev, u8 type, unsigned index) ++{ ++ int len; ++#ifdef HIGHSPEED ++ int hs; ++#endif ++ ++ /* only one configuration */ ++ if (index > 0) ++ return -EINVAL; ++ ++#ifdef HIGHSPEED ++ hs = (dev->gadget->speed == USB_SPEED_HIGH); ++ if (type == USB_DT_OTHER_SPEED_CONFIG) ++ hs = !hs; ++ if (hs) { ++ dev->req->buf = dev->hs_config; ++ len = le16_to_cpup (&dev->hs_config->wTotalLength); ++ } else ++#endif ++ { ++ dev->req->buf = dev->config; ++ len = le16_to_cpup (&dev->config->wTotalLength); ++ } ++ ((u8 *)dev->req->buf) [1] = type; ++ return len; ++} ++ ++static int ++gadgetfs_setup (struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) ++{ ++ struct dev_data *dev = get_gadget_data (gadget); ++ struct usb_request *req = dev->req; ++ int value = -EOPNOTSUPP; ++ struct usb_gadgetfs_event *event; ++ ++ spin_lock (&dev->lock); ++ dev->setup_abort = 0; ++ if (dev->state == STATE_UNCONNECTED) { ++ struct usb_ep *ep; ++ struct ep_data *data; ++ ++ dev->state = STATE_CONNECTED; ++ dev->dev->bMaxPacketSize0 = gadget->ep0->maxpacket; ++ ++#ifdef HIGHSPEED ++ if (gadget->speed == USB_SPEED_HIGH && dev->hs_config == 0) { ++ ERROR (dev, "no high speed config??\n"); ++ return -EINVAL; ++ } ++#endif /* HIGHSPEED */ ++ ++ INFO (dev, "connected\n"); ++ event = next_event (dev, GADGETFS_CONNECT); ++ event->u.speed = gadget->speed; ++ ep0_readable (dev); ++ ++ list_for_each_entry (ep, &gadget->ep_list, ep_list) { ++ data = ep->driver_data; ++ /* ... down_trylock (&data->lock) ... */ ++ if (data->state != STATE_EP_DEFER_ENABLE) ++ continue; ++#ifdef HIGHSPEED ++ if (gadget->speed == USB_SPEED_HIGH) ++ value = usb_ep_enable (ep, &data->hs_desc); ++ else ++#endif /* HIGHSPEED */ ++ value = usb_ep_enable (ep, &data->desc); ++ if (value) { ++ ERROR (dev, "deferred %s enable --> %d\n", ++ data->name, value); ++ continue; ++ } ++ data->state = STATE_EP_ENABLED; ++ wake_up (&data->wait); ++ DBG (dev, "woke up %s waiters\n", data->name); ++ } ++ ++ /* host may have given up waiting for response. we can miss control ++ * requests handled lower down (device/endpoint status and features); ++ * then ep0_{read,write} will report the wrong status. controller ++ * driver will have aborted pending i/o. ++ */ ++ } else if (dev->state == STATE_SETUP) ++ dev->setup_abort = 1; ++ ++ req->buf = dev->rbuf; ++ req->dma = DMA_ADDR_INVALID; ++ req->context = 0; ++ value = -EOPNOTSUPP; ++ switch (ctrl->bRequest) { ++ ++ case USB_REQ_GET_DESCRIPTOR: ++ if (ctrl->bRequestType != USB_DIR_IN) ++ goto unrecognized; ++ switch (ctrl->wValue >> 8) { ++ ++ case USB_DT_DEVICE: ++ value = min (ctrl->wLength, (u16) sizeof *dev->dev); ++ req->buf = dev->dev; ++ break; ++#ifdef HIGHSPEED ++ case USB_DT_DEVICE_QUALIFIER: ++ if (!dev->hs_config) ++ break; ++ value = min (ctrl->wLength, (u16) ++ sizeof (struct usb_qualifier_descriptor)); ++ make_qualifier (dev); ++ break; ++ case USB_DT_OTHER_SPEED_CONFIG: ++ // FALLTHROUGH ++#endif ++ case USB_DT_CONFIG: ++ value = config_buf (dev, ++ ctrl->wValue >> 8, ++ ctrl->wValue & 0xff); ++ if (value >= 0) ++ value = min (ctrl->wLength, (u16) value); ++ break; ++ case USB_DT_STRING: ++ goto unrecognized; ++ ++ default: // all others are errors ++ break; ++ } ++ break; ++ ++ /* currently one config, two speeds */ ++ case USB_REQ_SET_CONFIGURATION: ++ if (ctrl->bRequestType != 0) ++ break; ++ if (0 == (u8) ctrl->wValue) { ++ value = 0; ++ dev->current_config = 0; ++ // user mode expected to disable endpoints ++ } else { ++ u8 config; ++#ifdef HIGHSPEED ++ if (gadget->speed == USB_SPEED_HIGH) ++ config = dev->hs_config->bConfigurationValue; ++ else ++#endif ++ config = dev->config->bConfigurationValue; ++ ++ if (config == (u8) ctrl->wValue) { ++ value = 0; ++ dev->current_config = config; ++ } ++ } ++ ++ /* report SET_CONFIGURATION like any other control request, ++ * except that usermode may not stall this. the next ++ * request mustn't be allowed start until this finishes: ++ * endpoints and threads set up, etc. ++ * ++ * NOTE: older PXA hardware (before PXA 255: without UDCCFR) ++ * has bad/racey automagic that prevents synchronizing here. ++ * even kernel mode drivers often miss them. ++ */ ++ if (value == 0) { ++ INFO (dev, "configuration #%d\n", dev->current_config); ++ if (dev->usermode_setup) { ++ dev->setup_can_stall = 0; ++ goto delegate; ++ } ++ } ++ break; ++ ++#ifndef CONFIG_USB_GADGETFS_PXA2XX ++ /* PXA automagically handles this request too */ ++ case USB_REQ_GET_CONFIGURATION: ++ if (ctrl->bRequestType != 0x80) ++ break; ++ *(u8 *)req->buf = dev->current_config; ++ value = min (ctrl->wLength, (u16) 1); ++ break; ++#endif ++ ++ default: ++unrecognized: ++ VDEBUG (dev, "%s req%02x.%02x v%04x i%04x l%d\n", ++ dev->usermode_setup ? "delegate" : "fail", ++ ctrl->bRequestType, ctrl->bRequest, ++ ctrl->wValue, ctrl->wIndex, ctrl->wLength); ++ ++ /* if there's an ep0 reader, don't stall */ ++ if (dev->usermode_setup) { ++ dev->setup_can_stall = 1; ++delegate: ++ dev->setup_in = (ctrl->bRequestType & USB_DIR_IN) ++ ? 1 : 0; ++ dev->setup_out_ready = 0; ++ dev->setup_out_error = 0; ++ value = 0; ++ ++ /* read DATA stage for OUT right away */ ++ if (unlikely (!dev->setup_in && ctrl->wLength)) { ++ value = setup_req (gadget->ep0, dev->req, ++ ctrl->wLength); ++ if (value < 0) ++ break; ++ value = usb_ep_queue (gadget->ep0, dev->req, ++ GFP_ATOMIC); ++ if (value < 0) { ++ clean_req (gadget->ep0, dev->req); ++ break; ++ } ++ ++ /* we can't currently stall these */ ++ dev->setup_can_stall = 0; ++ } ++ ++ /* state changes when reader collects event */ ++ event = next_event (dev, GADGETFS_SETUP); ++ event->u.setup = *ctrl; ++ ep0_readable (dev); ++ spin_unlock (&dev->lock); ++ return 0; ++ } ++ } ++ ++ /* proceed with data transfer and status phases? */ ++ if (value >= 0 && dev->state != STATE_SETUP) { ++ req->length = value; ++ value = usb_ep_queue (gadget->ep0, req, GFP_ATOMIC); ++ if (value < 0) { ++ DBG (dev, "ep_queue --> %d\n", value); ++ req->status = 0; ++ } ++ } ++ ++ /* device stalls when value < 0 */ ++ spin_unlock (&dev->lock); ++ return value; ++} ++ ++static void destroy_ep_files (struct dev_data *dev) ++{ ++ struct list_head *entry, *tmp; ++ ++ DBG (dev, "%s %d\n", __FUNCTION__, dev->state); ++ ++ /* dev->state must prevent interference */ ++restart: ++ spin_lock_irq (&dev->lock); ++ list_for_each_safe (entry, tmp, &dev->epfiles) { ++ struct ep_data *ep; ++ ++ /* break link to FS */ ++ ep = list_entry (entry, struct ep_data, epfiles); ++ list_del_init (&ep->epfiles); ++ ++ /* break link to controller */ ++ if (ep->state == STATE_EP_ENABLED) ++ (void) usb_ep_disable (ep->ep); ++ ep->state = STATE_EP_UNBOUND; ++ usb_ep_free_request (ep->ep, ep->req); ++ ep->ep = 0; ++ wake_up (&ep->wait); ++ put_ep (ep); ++ ++ spin_unlock_irq (&dev->lock); ++ ++ /* fds may still be open */ ++ goto restart; ++ } ++ spin_unlock_irq (&dev->lock); ++} ++ ++ ++static int activate_ep_files (struct dev_data *dev) ++{ ++ struct usb_ep *ep; ++ ++ gadget_for_each_ep (ep, dev->gadget) { ++ struct ep_data *data; ++ ++ data = kmalloc (sizeof *data, GFP_KERNEL); ++ if (!data) ++ goto enomem; ++ memset (data, 0, sizeof data); ++ data->state = STATE_EP_DISABLED; ++ init_MUTEX (&data->lock); ++ init_waitqueue_head (&data->wait); ++ ++ strncpy (data->name, ep->name, sizeof (data->name) - 1); ++ atomic_set (&data->count, 1); ++ data->dev = dev; ++ get_dev (dev); ++ ++ data->ep = ep; ++ ep->driver_data = data; ++ ++ data->req = usb_ep_alloc_request (ep, GFP_KERNEL); ++ if (!data->req) ++ goto enomem; ++ ++ list_add_tail (&data->epfiles, &dev->epfiles); ++ } ++ return 0; ++ ++enomem: ++ DBG (dev, "%s enomem\n", __FUNCTION__); ++ destroy_ep_files (dev); ++ return -ENOMEM; ++} ++ ++static void ++gadgetfs_unbind (struct usb_gadget *gadget) ++{ ++ struct dev_data *dev = get_gadget_data (gadget); ++ ++ DBG (dev, "%s\n", __FUNCTION__); ++ ++ spin_lock_irq (&dev->lock); ++ dev->state = STATE_DEV_UNBOUND; ++ spin_unlock_irq (&dev->lock); ++ ++ destroy_ep_files (dev); ++ gadget->ep0->driver_data = 0; ++ set_gadget_data (gadget, 0); ++ ++ /* we've already been disconnected ... no i/o is active */ ++ if (dev->req) ++ usb_ep_free_request (gadget->ep0, dev->req); ++ DBG (dev, "%s done\n", __FUNCTION__); ++ put_dev (dev); ++} ++ ++static struct dev_data *the_device; ++ ++static int ++gadgetfs_bind (struct usb_gadget *gadget) ++{ ++ struct dev_data *dev = the_device; ++ ++ if (!dev) ++ return -ESRCH; ++ if (0 != strcmp (CHIP, gadget->name)) { ++ printk (KERN_ERR "%s expected " CHIP " controller not %s\n", ++ shortname, gadget->name); ++ return -ENODEV; ++ } ++ ++ set_gadget_data (gadget, dev); ++ dev->gadget = gadget; ++ gadget->ep0->driver_data = dev; ++ dev->dev->bMaxPacketSize0 = gadget->ep0->maxpacket; ++ ++ /* preallocate control response and buffer */ ++ dev->req = usb_ep_alloc_request (gadget->ep0, GFP_KERNEL); ++ if (!dev->req) ++ goto enomem; ++ dev->req->context = 0; ++ dev->req->complete = epio_complete; ++ ++ if (activate_ep_files (dev) < 0) ++ goto enomem; ++ ++ INFO (dev, "bound to %s driver\n", gadget->name); ++ dev->state = STATE_UNCONNECTED; ++ get_dev (dev); ++ return 0; ++ ++enomem: ++ gadgetfs_unbind (gadget); ++ return -ENOMEM; ++} ++ ++static void ++gadgetfs_disconnect (struct usb_gadget *gadget) ++{ ++ struct dev_data *dev = get_gadget_data (gadget); ++ ++ if (dev->state == STATE_UNCONNECTED) { ++ DBG (dev, "already unconnected\n"); ++ return; ++ } ++ dev->state = STATE_UNCONNECTED; ++ ++ INFO (dev, "disconnected\n"); ++ spin_lock (&dev->lock); ++ next_event (dev, GADGETFS_DISCONNECT); ++ ep0_readable (dev); ++ spin_unlock (&dev->lock); ++} ++ ++static void ++gadgetfs_suspend (struct usb_gadget *gadget) ++{ ++ struct dev_data *dev = get_gadget_data (gadget); ++ ++ INFO (dev, "suspended from state %d\n", dev->state); ++ spin_lock (&dev->lock); ++ switch (dev->state) { ++ case STATE_SETUP: // VERY odd... host died?? ++ case STATE_CONNECTED: ++ case STATE_UNCONNECTED: ++ next_event (dev, GADGETFS_SUSPEND); ++ ep0_readable (dev); ++ /* FALLTHROUGH */ ++ default: ++ break; ++ } ++ spin_unlock (&dev->lock); ++} ++ ++static struct usb_gadget_driver gadgetfs_driver = { ++#ifdef HIGHSPEED ++ .speed = USB_SPEED_HIGH, ++#else ++ .speed = USB_SPEED_FULL, ++#endif ++ .function = (char *) driver_desc, ++ .bind = gadgetfs_bind, ++ .unbind = gadgetfs_unbind, ++ .setup = gadgetfs_setup, ++ .disconnect = gadgetfs_disconnect, ++ .suspend = gadgetfs_suspend, ++ ++ .driver = { ++ .name = (char *) shortname, ++ // .shutdown = ... ++ // .suspend = ... ++ // .resume = ... ++ }, ++}; ++ ++/*----------------------------------------------------------------------*/ ++ ++/* DEVICE INITIALIZATION ++ * ++ * fd = open ("/dev/gadget/$CHIP", O_RDWR) ++ * status = write (fd, descriptors, sizeof descriptors) ++ * ++ * That write establishes the device configuration, so the kernel can ++ * bind to the controller ... guaranteeing it can handle enumeration ++ * at all necessary speeds. Descriptor order is: ++ * ++ * . message tag (u32, host order) ... for now, must be zero; it ++ * would change to support features like multi-config devices ++ * . full/low speed config ... all wTotalLength bytes (with interface, ++ * class, altsetting, endpoint, and other descriptors) ++ * . high speed config ... all descriptors, for high speed operation; ++ * this one's optional except for high-speed hardware ++ * . device descriptor ++ * ++ * Endpoints are not yet enabled. Drivers may want to immediately ++ * initialize them, using the /dev/gadget/ep* files that are available ++ * as soon as the kernel sees the configuration, or they can wait ++ * until device configuration and interface altsetting changes create ++ * the need to configure (or unconfigure) them. ++ * ++ * After initialization, the device stays active for as long as that ++ * $CHIP file is open. Events may then be read from that descriptor, ++ * such configuration notifications. More complex drivers will handle ++ * some control requests in user space. ++ */ ++ ++static int is_valid_config (struct usb_config_descriptor *config) ++{ ++ return config->bDescriptorType == USB_DT_CONFIG ++ && config->bLength == USB_DT_CONFIG_SIZE ++ && config->bConfigurationValue != 0 ++ && (config->bmAttributes & USB_CONFIG_ATT_ONE) != 0 ++ && (config->bmAttributes & USB_CONFIG_ATT_WAKEUP) == 0; ++ /* FIXME check lengths: walk to end */ ++} ++ ++static ssize_t ++dev_config (struct file *fd, const char *buf, size_t len, loff_t *ptr) ++{ ++ struct dev_data *dev = fd->private_data; ++ ssize_t value = len, length = len; ++ unsigned total; ++ u32 tag; ++ char *kbuf; ++ ++ if (dev->state != STATE_OPENED) ++ return -EEXIST; ++ ++ if (len < (USB_DT_CONFIG_SIZE + USB_DT_DEVICE_SIZE + 4)) ++ return -EINVAL; ++ ++ /* we might need to change message format someday */ ++ if (copy_from_user (&tag, buf, 4)) ++ return -EFAULT; ++ if (tag != 0) ++ return -EINVAL; ++ buf += 4; ++ length -= 4; ++ ++ kbuf = kmalloc (length, SLAB_KERNEL); ++ if (!kbuf) ++ return -ENOMEM; ++ if (copy_from_user (kbuf, buf, length)) { ++ kfree (kbuf); ++ return -EFAULT; ++ } ++ ++ spin_lock_irq (&dev->lock); ++ value = -EINVAL; ++ if (dev->buf) ++ goto fail; ++ dev->buf = kbuf; ++ ++ /* full or low speed config */ ++ dev->config = (void *) kbuf; ++ total = le16_to_cpup (&dev->config->wTotalLength); ++ if (!is_valid_config (dev->config) || total >= length) ++ goto fail; ++ kbuf += total; ++ length -= total; ++ ++ /* optional high speed config */ ++ if (kbuf [1] == USB_DT_CONFIG) { ++ dev->hs_config = (void *) kbuf; ++ total = le16_to_cpup (&dev->hs_config->wTotalLength); ++ if (!is_valid_config (dev->hs_config) || total >= length) ++ goto fail; ++ kbuf += total; ++ length -= total; ++ } ++ ++ /* could support multiple configs, using another encoding! */ ++ ++ /* device descriptor (tweaked for paranoia) */ ++ if (length != USB_DT_DEVICE_SIZE) ++ goto fail; ++ dev->dev = (void *)kbuf; ++ if (dev->dev->bLength != USB_DT_DEVICE_SIZE ++ || dev->dev->bDescriptorType != USB_DT_DEVICE ++ || dev->dev->bNumConfigurations != 1) ++ goto fail; ++ dev->dev->bNumConfigurations = 1; ++ dev->dev->bcdUSB = __constant_cpu_to_le16 (0x0200); ++ ++ /* triggers gadgetfs_bind(); then we can enumerate. */ ++ spin_unlock_irq (&dev->lock); ++ value = usb_gadget_register_driver (&gadgetfs_driver); ++ if (value != 0) { ++ kfree (dev->buf); ++ dev->buf = 0; ++ } else { ++ /* at this point "good" hardware has for the first time ++ * let the USB the host see us. alternatively, if users ++ * unplug/replug that will clear all the error state. ++ * ++ * note: everything running before here was guaranteed ++ * to choke driver model style diagnostics. from here ++ * on, they can work ... except in cleanup paths that ++ * kick in after the ep0 descriptor is closed. ++ */ ++ fd->f_op = &ep0_io_operations; ++ value = len; ++ } ++ return value; ++ ++fail: ++ spin_unlock_irq (&dev->lock); ++ pr_debug ("%s: %s fail %d, %p\n", shortname, __FUNCTION__, value, dev); ++ kfree (dev->buf); ++ dev->buf = 0; ++ return value; ++} ++ ++static int ++dev_open (struct inode *inode, struct file *fd) ++{ ++ struct dev_data *dev = inode->u.generic_ip; ++ int value = -EBUSY; ++ ++ if (dev->state == STATE_DEV_DISABLED) { ++ dev->ev_next = 0; ++ dev->state = STATE_OPENED; ++ fd->private_data = dev; ++ get_dev (dev); ++ value = 0; ++ } ++ return value; ++} ++ ++static struct file_operations dev_init_operations = { ++ .owner = THIS_MODULE, ++ .open = dev_open, ++ .write = dev_config, ++ .fasync = ep0_fasync, ++ .ioctl = dev_ioctl, ++ .release = dev_release, ++}; ++ ++/*----------------------------------------------------------------------*/ ++ ++/* ++ * implementation for 2.4 uses character special files ++ * ep0/device file MKDEV (c_major, 0) ++ * first data ep MKDEV (c_major, 1) ++ * second data ep MKDEV (c_major, 2) ++ * ... ++ * ++ * FIXME can do it as a real filesystem on 2.4 too, without libfs ++ */ ++static int c_major = 240; /* 240 is local/experimental */ ++MODULE_PARM (c_major, "i"); ++MODULE_PARM_DESC (c_major, "major number for char special files"); ++ ++static int gadget_open (struct inode *ino, struct file *fp) ++{ ++ int num = minor (ino->i_rdev); ++ struct dev_data *dev; ++ struct file_operations *ops; ++ ++ /* ep0 file, "/dev/gadget/$CHIP" */ ++ if (num == 0) { ++ int status; ++ ++ if (the_device != 0) ++ return -EBUSY; ++ the_device = dev_new (); ++ if (the_device == 0) ++ return -ENOMEM; ++ ++ dev = the_device; ++ ino->u.generic_ip = dev; ++ ops = &dev_init_operations; ++ fp->f_op = ops; ++ ++ status = ops->open (ino, fp); ++ if (status < 0) { ++ put_dev (dev); ++ the_device = 0; ++ } ++ return status; ++ ++ /* ep files, "/dev/gadget/$ENDPOINT" */ ++ } else { ++ struct list_head *entry; ++ struct ep_data *data; ++ ++ /* unavailable till device is initted */ ++ dev = the_device; ++ if (dev == 0) ++ return -ENODEV; ++ ++ /* order in controller's name listing matters! */ ++ list_for_each (entry, &dev->epfiles) { ++ if (--num == 0) ++ goto found; ++ } ++ return -ENODEV; ++found: ++ data = list_entry (entry, struct ep_data, epfiles); ++ ino->u.generic_ip = data; ++ ops = &ep_config_operations; ++ fp->f_op = ops; ++ ++ return ops->open (ino, fp); ++ } ++} ++ ++static struct file_operations gadget_fops = { ++ .owner = THIS_MODULE, ++ .open = gadget_open, ++}; ++ ++/*----------------------------------------------------------------------*/ ++ ++static int __init init (void) ++{ ++ int status; ++ ++ status = register_chrdev (c_major, shortname, &gadget_fops); ++ if (status < 0) { ++ printk (KERN_WARNING "%s: can't get major %d\n", ++ shortname, c_major); ++ return status; ++ } ++ ++ /* dynamic assignment */ ++ if (c_major == 0) ++ c_major = status; ++ status = 0; ++ ++ pr_info ("%s: using char major %d\n", shortname, c_major); ++ ++ if (status == 0) ++ pr_info ("%s: %s, version " DRIVER_VERSION "\n", ++ shortname, driver_desc); ++ return status; ++} ++module_init (init); ++ ++static void __exit cleanup (void) ++{ ++ pr_debug ("unregister %s\n", shortname); ++ unregister_chrdev (c_major, shortname); ++} ++module_exit (cleanup); ++ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/n9604.c kernel/drivers/usb/gadget/n9604.c +--- /tmp/kernel/drivers/usb/gadget/n9604.c 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/n9604.c 2005-04-22 17:53:19.461535120 +0200 +@@ -0,0 +1,1088 @@ ++/* ++ * National 9603/4 USB Device Controller driver ++ * Copyright (C) 2004 Technical Solutions Inc. (support@techsol.ca) ++ * ported from : The Goku-S driver ++ * Copyright (C) 2003 MontaVista Software (source@mvista.com) ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++/* ++ * This device has ep0 and six semi-configurable bulk/interrupt endpoints. ++ * ++ * - Endpoint numbering is fixed: ++ * Endpoint 0: ep0 ++ * Endpoint 1: ep1in (tx) ++ * Endpoint 2: ep2out (rx) ++ * Endpoint 3: ep3in (tx) ++ * Endpoint 4: ep4out (rx) ++ * Endpoint 5: ep5in (tx) ++ * Endpoint 6: ep6out (rx) ++ */ ++ ++/* ++ * The ep->stage information refers to the state of a setup transaction ++ * ++ * state 0: no setup packet has been received ++ * state 1: setup packet has been received ++ * state 2: data has been sent/received ++ * state 3: ZLP has been received/sent ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "n9604.h" ++#include "n9604regs.h" ++ ++inline void Flush_and_enable(u8 control_reg) { ++ write_9604(RXC_FLUSH, control_reg); ++ while (read_9604(control_reg) & RXC_FLUSH); ++ write_9604(RXC_RX_EN, control_reg); ++} ++inline void Flush(u8 control_reg) { ++ write_9604(RXC_FLUSH, control_reg); ++ while (read_9604(control_reg) & RXC_FLUSH); ++} ++ ++#define DRIVER_DESC "N9604 USB Device Controller" ++#define DRIVER_VERSION "29-Oct 2004" ++ ++static const char driver_name [] = "n9604_udc"; ++static const char driver_desc [] = DRIVER_DESC; ++ ++MODULE_AUTHOR("support@techsol.ca"); ++MODULE_DESCRIPTION(DRIVER_DESC); ++MODULE_LICENSE("GPL"); ++ ++static void nuke(struct n9604_ep *ep, int status); ++inline void send_zero_length(int endpoint, struct n9604_udc *dev); ++ ++u8 * USBN9604_Offset; //device virtual address ++ ++/* FIXME all the IRQ stuff is board-specific ++ */ ++ ++#define h7201_readl readl ++#define h7201_writel writel ++ ++#define ETHER_IRQ_IP_OFFSET 0 ++#define ETHER_IRQ_BIT_POS 0 ++#define ETHER_IRQ_IM_OFFSET 0 ++ ++#define IRQ_GPIOC -1 ++ ++#define USBD_ENABLE_IRQ {h7201_writel( h7201_readl(ETHER_IRQ_IP_OFFSET) | (1 << ETHER_IRQ_BIT_POS), ETHER_IRQ_IP_OFFSET); h7201_writel( h7201_readl(ETHER_IRQ_IM_OFFSET) | (1 << ETHER_IRQ_BIT_POS), ETHER_IRQ_IM_OFFSET);} ++#define USBD_DISABLE_IRQ h7201_writel( h7201_readl(ETHER_IRQ_IM_OFFSET) & ~(1 << ETHER_IRQ_BIT_POS), ETHER_IRQ_IM_OFFSET); ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++//enable an end point, of description desc ++static int n9604_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) { ++ struct n9604_udc *dev; ++ struct n9604_ep *ep; ++ u16 max; ++ ++ ep = container_of(_ep, struct n9604_ep, ep); ++ ++ if (!_ep || !desc || ep->desc || desc->bDescriptorType != USB_DT_ENDPOINT) ++ return -EINVAL; ++ ++ dev = ep->dev; ++ if (!ep->num) ++ return -EINVAL; ++ if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) ++ return -ESHUTDOWN; ++ if (ep->num && !(desc->bEndpointAddress & 0x0f)) ++ return -EINVAL; ++ ++ switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { ++ case USB_ENDPOINT_XFER_BULK: ++ case USB_ENDPOINT_XFER_INT: ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ write_9604((ep->numActual & EPC_EP_MASK) | EPC_EP_EN | (EPC_ISO * ((desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_ISOC)), ep->control); ++ if (ep->is_in) ++ Flush(ep->command); ++ else ++ Flush_and_enable(ep->command); ++ ++ max = le16_to_cpu(get_unaligned(&desc->wMaxPacketSize)); ++ ep->ep.maxpacket = min_t(u16, max, MAX_FIFO_SIZE); ++ ep->desc = desc; ++ ++ return 0; ++} ++ ++static int n9604_ep_disable(struct usb_ep *_ep)//ep > 0 ++{ ++ struct n9604_ep *ep; ++ struct n9604_udc *dev; ++ unsigned long flags; ++ ++ ep = container_of(_ep, struct n9604_ep, ep); ++ ++ if (!_ep || !ep->desc) ++ return -ENODEV; ++ dev = ep->dev; ++ ++ spin_lock_irqsave(&dev->lock, flags); ++ nuke(ep, -ESHUTDOWN); ++ write_9604(0, ep->command); ++ ep->desc = NULL; ++ spin_unlock_irqrestore(&dev->lock, flags); ++ ++ return 0; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static struct usb_request * ++n9604_alloc_request(struct usb_ep *_ep, int gfp_flags) ++{ ++ struct n9604_request *req; ++ ++ if (!_ep) ++ return 0; ++ req = kmalloc(sizeof *req, gfp_flags); ++ if (!req) ++ return 0; ++ ++ memset(req, 0, sizeof *req); ++ INIT_LIST_HEAD(&req->queue); ++ return &req->req; ++} ++ ++static void ++n9604_free_request(struct usb_ep *_ep, struct usb_request *_req) ++{ ++ struct n9604_request *req; ++ ++ if (!_ep || !_req) ++ return; ++ ++ req = container_of(_req, struct n9604_request, req); ++ WARN_ON(!list_empty(&req->queue)); ++ kfree(req); ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void done(struct n9604_ep *ep, struct n9604_request *req, int status); ++ ++static inline int ++write_packet(struct n9604_ep *ep, u8 *buf, struct n9604_request *req) ++{ ++ unsigned written_length, desired_length, available_length, maximum_length, flags, loop_length; ++ ++ u8 fifo = ep->fifo; ++ u8 command = ep->command; ++ u8 status = ep->status; ++ if (!ep->num) { ++ fifo = TXD0; ++ command = TXC0; ++ status = TXS0; ++ } ++ ++ if (read_9604(command) & TXC_TX_EN) ++ return -EBUSY; ++ ep->packets++; ++ ++ desired_length = req->req.length - req->req.actual; ++ available_length = read_9604(status) & TXS_TCOUNT_MASK;//might be greater ++ written_length = 0; ++ if (ep->num) ++ maximum_length = MAX_FIFO_SIZE; ++ else ++ maximum_length = MAX_EP0_SIZE; ++ ++ while ((loop_length = min(min(available_length, desired_length), maximum_length))) { ++ int i = loop_length; ++ while (i) { write_9604(*buf++,fifo); i--; } ++ written_length += loop_length; ++ desired_length -= loop_length; ++ maximum_length -= loop_length; ++ if (desired_length && maximum_length)//check if really need to read the chip again ++ available_length = (read_9604(status) & TXS_TCOUNT_MASK); ++ } ++ ++ req->req.actual += written_length; ++ ++ flags = TXC_TX_EN; ++ if (ep->num) ++ flags |= TXC_LAST; ++ if (ep->toggle) ++ flags |= TXC_TOGGLE; ++ write_9604(flags, command); ++ ep->toggle = !(ep->toggle); ++ if (!written_length) req->req.zero = 0;//just wrote zero bytes, there is no more need for req.zero ++ return written_length; ++} ++ ++// return: 0 = still running, 1 = completed, negative = errno ++static int write_fifo(struct n9604_ep *ep, struct n9604_request *req) ++{ ++ struct n9604_udc *dev = ep->dev; ++ u8 *buf; ++ unsigned count; ++ int is_last; ++ ++ buf = req->req.buf + req->req.actual; ++ prefetch(buf); ++ ++ dev = ep->dev; ++ ++ count = write_packet(ep, buf, req); ++ if (count < 0) ++ return count; ++ ++ /* last packet often short (sometimes a zlp, especially on ep0) */ ++ if ((req->req.length != req->req.actual) || req->req.zero) ++ is_last = 0; ++ else ++ is_last = 1; ++ ++ /* requests complete when all IN data is in the FIFO, ++ * or sometimes later, if a zlp was needed. ++ */ ++ if (is_last) { ++ done(ep, req, 0); ++ return 1; ++ } ++ return 0; ++} ++ ++static inline void pio_irq_enable(struct n9604_ep *ep); ++ ++static int read_fifo(struct n9604_ep *ep, struct n9604_request *req) ++{ ++ u32 size; ++ u8 *buf; ++ int bufferspace_available, fifospace_left, num_bytes_read; ++ int fifo, status; ++ ep->packets++; ++ if (!ep->num) { ++ fifo = RXD0; ++ status = RXS0; ++ } else { ++ fifo = ep->fifo; ++ status = ep->status; ++ } ++ num_bytes_read = 0; ++ buf = req->req.buf + req->req.actual; ++ bufferspace_available = req->req.length - req->req.actual; ++ size = read_9604(status) & (RXS_RCOUNTMASK | RXS_RX_ERR);//number of bytes ready to be read (15 if greater than 15) ++ if (ep->num && (size & RXS_RX_ERR)) { ++ ERROR(ep->dev, "DATA ERROR!!!! on ep%d\nFlushing Fifo", ep->num); ++ Flush_and_enable(ep->command); ++ goto leave; ++ } ++ size = size & ~RXS_RX_ERR;//clear the bit ++ if (ep->num) fifospace_left = MAX_FIFO_SIZE; ++ else fifospace_left = MAX_EP0_SIZE; ++loop: ++ /* read all bytes from this packet */ ++ while (size-- != 0) { ++ u8 byte = read_9604(fifo); ++ if (unlikely(bufferspace_available == 0)) { ++ /* this happens when the driver's buffer ++ * is smaller than what the host sent. ++ * discard the extra data in this packet. ++ */ ++ done(ep, req, -EOVERFLOW); ++ return 1; ++ } else { ++ *buf++ = byte; ++ bufferspace_available--; ++ fifospace_left--; ++ num_bytes_read++; ++ } ++ } ++ if ((size = (read_9604(status) & RXS_RCOUNTMASK))) { ++ goto loop;//since there is more data ++ } ++ /* completion */ ++ req->req.actual = req->req.actual + num_bytes_read; ++ if (fifospace_left || req->req.actual == req->req.length) { ++ done(ep, req, 0); ++ return 1; ++ } ++leave: ++ pio_irq_enable(ep);//turn the interrupt back on ++ return 0; ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static inline void ++pio_irq_enable(struct n9604_ep *ep) ++{ ++ if (ep->is_in) ++ write_9604(read_9604(TXMSK) | 1 << ep->fifoNum | 0x10 << ep->fifoNum, TXMSK); ++ else { ++ u8 command = ep->command; ++ if (!ep->num) command = RXC0; ++ write_9604(read_9604(RXMSK) | 1 << ep->fifoNum | 0x10 << ep->fifoNum, RXMSK); ++ write_9604(RXC_RX_EN | RXC_RFWL0 | RXC_RFWL1, command); ++ } ++} ++ ++static inline void ++pio_irq_disable(struct n9604_ep *ep)//epnum != 0 ++{ ++ if (ep->is_in) ++ write_9604(read_9604(TXMSK) & ~(1 << ep->fifoNum) & ~(0x10 << ep->fifoNum), TXMSK); ++ else ++ write_9604(read_9604(RXMSK) & ~(1 << ep->fifoNum) & ~(0x10 << ep->fifoNum), RXMSK); ++} ++ ++static int request_voodoo = 0;//number of bytes the host requested ++ ++static inline void ++pio_advance(struct n9604_ep *ep) ++{ ++ struct n9604_request *req; ++ ++ if (list_empty (&ep->queue)) { ++ if (!ep->num) { ++ if (ep->is_in && (ep->stage == 2)) { ++ ep->is_in = 0;//switch modes ++ Flush_and_enable(RXC0);//needed to receive a ZLP after tx ++ ep->stage++;//and bump the stage number ++ } else if (ep->stage == 3) { ++ ep->stage = 0; ++ } ++ } ++ return; ++ } ++ req = list_entry(ep->queue.next, struct n9604_request, queue); ++ (ep->is_in ? write_fifo : read_fifo)(ep, req); ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void * n9604_alloc_buffer(struct usb_ep *_ep, unsigned bytes, dma_addr_t *dma, int gfp_flags) ++{ ++ return kmalloc(bytes, gfp_flags); ++} ++ ++static void n9604_free_buffer(struct usb_ep *_ep, void *buf, dma_addr_t dma, unsigned bytes) ++{ ++ kfree (buf); ++} ++ ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void ++done(struct n9604_ep *ep, struct n9604_request *req, int status) ++{ ++ struct n9604_udc *dev; ++ ++ list_del_init(&req->queue); ++ ep->queue_active--; ++ ++ if (req->req.status == -EINPROGRESS) ++ req->req.status = status; ++ else ++ status = req->req.status; ++ ++ dev = ep->dev; ++ ++ /* don't modify queue heads during completion callback */ ++ if (ep->num) ++ pio_irq_disable(ep); ++ else if (!ep->nuking) { ++ ep->stage++; ++ ep->toggle = 1;//other endpoints stay in their flipping mode between transactions ++ if (ep->stage == 2) {//we are in stage 2 now ++ if (!ep->is_in) { ++ ep->is_in = 1;//switch modes ++ request_voodoo = 1;//prevents n9604_queue from calling us again before doing anything ++ send_zero_length(0, dev); ++ } else {//we have to receive a ZLP ++ //this will happen when the tx is complete, the pio_advance fcn will activate it for us ++ } ++ } ++ } ++ ++ req->req.complete(&ep->ep, &req->req); ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int ++n9604_queue(struct usb_ep *_ep, struct usb_request *_req, int gfp_flags) ++{ ++ struct n9604_request *req; ++ struct n9604_ep *ep; ++ struct n9604_udc *dev; ++ unsigned long flags; ++ int status; ++ ++ req = container_of(_req, struct n9604_request, req); ++ if (unlikely(!_req || !_req->complete ++ || !_req->buf || !list_empty(&req->queue))) ++ return -EINVAL; ++ ep = container_of(_ep, struct n9604_ep, ep); ++ if (unlikely(!_ep || (!ep->desc && ep->num != 0))) ++ return -EINVAL; ++ dev = ep->dev; ++ if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { ++ return -ESHUTDOWN; ++ } ++ if (ep->nuking) ++ return -ESHUTDOWN; ++ ++ spin_lock_irqsave(&dev->lock, flags); ++ ++ ep->queue_reqs++; ++ ep->queue_active++; ++ ++ _req->status = -EINPROGRESS; ++ _req->actual = 0; ++ ++ /* for ep0 IN without premature status, zlp is required and ++ * writing EOP starts the status stage (OUT). ++ */ ++ if (ep->num == 0) { ++ if ((request_voodoo > _req->length) && !(_req->length % MAX_EP0_SIZE) && (_req->length != 0)) { ++ _req->zero = 1; ++ } ++ if (!request_voodoo && !ep->is_in) {//this is a zero length request ++ spin_unlock_irqrestore(&dev->lock, flags);//David ++ done(ep, req, 0);//this doesn't check if the list is empty (probably not an issue) ++ return 0; //shouldn't this be handled by the rx irq fcn, and passed to pio_advance ++ }//that may conflict with the voodoo stuff, maybe best to leave it ++ } ++ ++ /* kickstart this i/o queue? */ ++ status = 0; ++ if (list_empty(&ep->queue) && ep->is_in) { ++ status = write_fifo(ep, req); ++ if (status == -EBUSY) ++ ;//we should queue up the request then ++ else { ++ if (status != 0) { ++ if (status > 0) ++ status = 0; ++ req = 0; ++ } ++ } ++ } /* else pio or dma irq handler advances the queue. */ ++ ++ if (req != 0) { ++ list_add_tail(&req->queue, &ep->queue); ++ pio_irq_enable(ep); ++ } ++ ++ spin_unlock_irqrestore(&dev->lock, flags); ++ return status; ++} ++ ++/* dequeue ALL requests */ ++static void nuke(struct n9604_ep *ep, int status) ++{ ++ struct n9604_request *req; ++ ++ if (list_empty(&ep->queue)) ++ return; ++ ep->nuking = 1; ++ while (!list_empty(&ep->queue)) { ++ req = list_entry(ep->queue.next, struct n9604_request, queue); ++ done(ep, req, status); ++ } ++ ep->nuking = 0; ++} ++ ++/* dequeue JUST ONE request */ ++static int n9604_dequeue(struct usb_ep *_ep, struct usb_request *_req) ++{ ++ struct n9604_request *req; ++ struct n9604_ep *ep; ++ struct n9604_udc *dev; ++ unsigned long flags; ++ ++ ep = container_of(_ep, struct n9604_ep, ep); ++ if (!_ep || !_req || (!ep->desc && ep->num != 0)) ++ return -EINVAL; ++ dev = ep->dev; ++ ++ if (!dev->driver) ++ return -ESHUTDOWN; ++ ++ spin_lock_irqsave(&dev->lock, flags); ++ ++ /* make sure it's actually queued on this endpoint */ ++ list_for_each_entry (req, &ep->queue, queue) { ++ if (&req->req == _req) ++ break; ++ } ++ if (&req->req != _req) { ++ spin_unlock_irqrestore (&dev->lock, flags); ++ return -EINVAL; ++ } ++ ++ spin_unlock_irqrestore(&dev->lock, flags); ++ ++ return req ? 0 : -EOPNOTSUPP; ++} ++ ++static int n9604_clear_halt(struct usb_ep *_ep) { ++ struct n9604_ep *ep; ++ ep = container_of (_ep, struct n9604_ep, ep); ++ ++ write_9604(read_9604(ep->control) & ~EPC_STALL, ep->control); ++ pio_advance(ep); ++ return 0; ++} ++ ++static int n9604_set_halt(struct usb_ep *_ep, int value) { ++ struct n9604_ep *ep; ++ unsigned long flags; ++ int retval = 0; ++ ++ if (!_ep) { ++ retval = -ENODEV; goto exit; ++ } ++ ep = container_of (_ep, struct n9604_ep, ep); ++ ++ if (ep->num == 0) {//is this valid? ++ if (!value) { ++ retval = -EINVAL; goto exit; } ++ ++ /* don't change EPxSTATUS_EP_INVALID to READY */ ++ } else if (!ep->desc) { ++ retval = -EINVAL; goto exit; ++ } ++ ++ spin_lock_irqsave(&ep->dev->lock, flags); ++ if (!list_empty(&ep->queue)) ++ retval = -EAGAIN; ++ else if (!value) ++ n9604_clear_halt(_ep); ++ else { ++ write_9604(read_9604(ep->control) | EPC_STALL, ep->control); ++ } ++ spin_unlock_irqrestore(&ep->dev->lock, flags); ++exit: ++ return retval; ++} ++ ++static int n9604_fifo_status(struct usb_ep *_ep) {//not implemented ++ return -1; ++} ++ ++static void n9604_fifo_flush(struct usb_ep *_ep) {//not implemented ++ struct n9604_ep *ep; ++ ep = container_of (_ep, struct n9604_ep, ep); ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static struct usb_ep_ops n9604_ep_ops = { ++ .enable = n9604_ep_enable, ++ .disable = n9604_ep_disable, ++ ++ .alloc_request = n9604_alloc_request,//io request objects called struct usb_request ++ .free_request = n9604_free_request, ++ ++ .alloc_buffer = n9604_alloc_buffer, ++ .free_buffer = n9604_free_buffer, ++ ++ .queue = n9604_queue,//submit a struct usb_request object to an endpoint ++ .dequeue = n9604_dequeue, ++ ++ .set_halt = n9604_set_halt,//halts an endpoint ++ .fifo_status = n9604_fifo_status,//bytes in FIFO + data ready to go in FIFO ++ .fifo_flush = n9604_fifo_flush,//flush all the data, endpoint is probably been reconfigured ++}; ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int n9604_get_frame(struct usb_gadget *_gadget) ++{ ++ return -EOPNOTSUPP; ++} ++ ++static const struct usb_gadget_ops n9604_ops = { ++ .get_frame = n9604_get_frame, ++}; ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void udc_reinit (struct n9604_udc *dev) ++{ ++ static char *names [] = { "ep0", "ep1in", "ep2out", "ep3in", "ep4out", "ep5in", "ep6out" }; ++ unsigned i; ++ ++ INIT_LIST_HEAD (&dev->gadget.ep_list); ++ dev->gadget.ep0 = &dev->ep [0].ep; ++ dev->gadget.speed = USB_SPEED_UNKNOWN; ++ dev->irqs = 0; ++ dev->configured = 0; ++ ++ //for (i = 0; i < 7; i++) { ++ for (i = 0; i < ARRAY_SIZE(names); i++) { ++ struct n9604_ep *ep = &dev->ep[i]; ++ ep->num = i; ++ ep->numActual = i; ++ ep->ep.name = names[i]; ++ ep->irqs = 0; ++ if (i) { ++ ep->fifo = (i * 4) + RXD0; //each FIFO address is 4 bytes away. TXD0 is the first ++ ep->control = ep->fifo - 1; ++ ep->status = ep->fifo + 1; ++ ep->command = ep->fifo + 2; ++ Flush(ep->command);//flush any data in the fifo//we don't care about the previous state ++ read_9604(ep->status); ++ ep->ep.maxpacket = MAX_FIFO_SIZE; ++ } else {//were are endpoint 0 ++ ep->fifo = ep->control = ep->status = ep->command = 0xff;//this should force an error ++ //we need to do this since we don't know if ++ //this is tx or rx ++ read_9604(TXS0); ++ Flush(TXC0); ++ Flush(RXC0);//we could potentially (probably) overwriting a pending setup packet ++ if (ep->stage)//if we get a setup packet before we have a chance to finish the reset we have a problem ++ read_9604(RXS0);//fix this by sending stalls or something ++ ep->stage = 0; ++ ep->ep.maxpacket = MAX_EP0_SIZE; ++ } ++ ep->is_in = i % 2; ++ ep->fifoNum = (i + ep->is_in) / 2;//ignored for endpoint 0 ++ ep->ep.ops = &n9604_ep_ops; ++ list_add_tail (&ep->ep.ep_list, &dev->gadget.ep_list); ++ ep->dev = dev; ++ INIT_LIST_HEAD (&ep->queue); ++ ep->nuking=0; ++ ep->queue_reqs = 0; ++ ep->queue_active = 0; ++ ep->packets = 0; ++ ep->desc = 0; ++ ep->irqs = 0; ++ } ++ ++ list_del_init (&dev->ep[0].ep.ep_list); ++ ++ write_9604(~WKUP_PNDUSB & ~WKUP_PNDUC & read_9604(WKUP), WKUP);//clear the bits, we've done a reset ++ write_9604(FAR_AD_EN, FAR);//enable the chip to answer requests//address 0 ++ dev->address = 0; ++ write_9604(0, EPC0);//clear the control register ++ write_9604(NFSR_NodeOperational, NFSR);//we're going for gold ++} ++ ++static void udc_reset(struct n9604_udc *dev) ++{ ++ //USBD_DISABLE_IRQ; This disables all interrupts sharing that line ++ write_9604(MCNTRL_SRST,MCNTRL);//software reset -- this also prevents pullup ++ write_9604(0x00, MAMSK); //disable interrupts ++} ++ ++ ++ ++static void udc_enable(struct n9604_udc *dev) ++{ ++ udc_reset(dev); //this is to prevent a pullup resistor ++ udc_reinit (dev); ++ ++ dev->gadget.speed = USB_SPEED_FULL; ++ ++ // enable ep0 interrupts ++ dev->ep[0].is_in = 0; ++ ++ write_9604(MAMSK_WARN | MAMSK_ALT | MAMSK_TX_EV | MAMSK_RX_EV | MAMSK_INTR, MAMSK);//for now we turn it all on, except frames & ULD & NAK ++ write_9604(ALTMSK_RESET, ALTMSK);//just turn on reset ++ write_9604(0x11, TXMSK); ++ write_9604(0x11, RXMSK); ++ write_9604(0x0, NAKMSK); ++ write_9604(0x0, FWMSK); ++ write_9604(MCNTRL_NAT | MCNTRL_INTOC_ActHigh, MCNTRL);//this activates the pull-up and turns on interrupts ++ USBD_ENABLE_IRQ; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* keeping it simple: ++ * - one bus driver, initted first; ++ * - one function driver, initted second ++ */ ++ ++static struct n9604_udc *the_controller; ++ ++/* when a driver is successfully registered, it will receive ++ * control requests including set_configuration(), which enables ++ * non-control requests. then usb traffic follows until a ++ * disconnect is reported. then a host may connect again, or ++ * the driver might get unbound. ++ */ ++int usb_gadget_register_driver(struct usb_gadget_driver *driver) ++{ ++ struct n9604_udc *dev = the_controller; ++ int retval; ++ ++ if (!driver ++ || driver->speed != USB_SPEED_FULL ++ || !driver->bind ++ || !driver->unbind ++ || !driver->disconnect ++ || !driver->setup) ++ return -EINVAL; ++ if (!dev) ++ return -ENODEV; ++ if (dev->driver) ++ return -EBUSY; ++ ++ /* hook up the driver */ ++ dev->driver = driver; ++ retval = driver->bind(&dev->gadget); ++ if (retval) { ++ dev->driver = 0; ++ return retval; ++ } ++ ++ /* then enable host detection and ep0; and we're ready ++ * for set_configuration as well as eventual disconnect. ++ */ ++ udc_enable(dev); ++ ++ return 0; ++} ++EXPORT_SYMBOL(usb_gadget_register_driver); ++ ++int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) ++{ ++ struct n9604_udc *dev = the_controller; ++ unsigned long flags; ++ int i; ++ ++ if (!dev) ++ return -ENODEV; ++ if (!driver || driver != dev->driver) ++ return -EINVAL; ++ ++ spin_lock_irqsave(&dev->lock, flags); ++ dev->driver = 0; ++ ++ udc_reset(dev);//reset & diable irqs ++ for (i = 0; i < ARRAY_SIZE(dev->ep); i++) ++ nuke(&dev->ep [i], -ESHUTDOWN); ++ spin_unlock_irqrestore(&dev->lock, flags); ++ ++ if (dev->gadget.speed != USB_SPEED_UNKNOWN) ++ driver->disconnect(&dev->gadget); ++ driver->unbind(&dev->gadget); ++ ++ return 0; ++} ++EXPORT_SYMBOL(usb_gadget_unregister_driver); ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++inline u8 tx_ev_irq(struct n9604_udc *dev) { ++ u8 mask; ++ ++ mask = read_9604(TXEV) & read_9604(TXMSK); ++ ++ if (mask & TXEV_FIFO0) { ++ write_9604(0, EPC0);//make sure we are not stalled, & not using the default address ++ read_9604(TXS0);//should really check for error conditions ++ dev->ep[0].irqs++; ++ pio_advance(&dev->ep[0]); ++ } ++ if (mask & TXEV_FIFO1) { ++ read_9604(TXS1); ++ dev->ep[1].irqs++; ++ pio_advance(&dev->ep[1]); ++ } ++ if (mask & TXEV_FIFO2) { ++ read_9604(TXS2); ++ dev->ep[3].irqs++; ++ pio_advance(&dev->ep[3]); ++ } ++ if (mask & TXEV_FIFO3) { ++ read_9604(TXS3); ++ dev->ep[5].irqs++; ++ pio_advance(&dev->ep[5]); ++ } ++ return mask; ++} ++ ++static void my_req_complete(struct usb_ep *_ep, struct usb_request *req) {//this was for the setup packet, but I guess I could use it for anything ++ n9604_free_buffer(_ep, req->buf, req->dma, req->length); ++ n9604_free_request(_ep, req); ++} ++ ++inline void send_dummy_packet(int endpoint, struct n9604_udc *dev, int length) { ++ struct usb_request *my_req; ++ my_req = n9604_alloc_request(&dev->ep[endpoint].ep, GFP_ATOMIC); ++ my_req->length = length; ++ my_req->buf = n9604_alloc_buffer(&dev->ep[endpoint].ep, length, &my_req->dma, GFP_ATOMIC); ++ my_req->complete = my_req_complete; ++ n9604_queue(&dev->ep[endpoint].ep, my_req, GFP_ATOMIC); ++} ++ ++inline void send_zero_length(int endpoint, struct n9604_udc *dev) { ++ send_dummy_packet(endpoint, dev, 0); ++} ++ ++inline void rx_ev_irq(struct n9604_udc *dev) { ++ u8 mask; ++ struct n9604_ep *ep; ++ ++ mask = read_9604(RXEV) & read_9604(RXMSK); ++ ++ if (mask & RXEV_FIFO0) { ++ static int read_mode = 0; ++ u8 rxs_mask = read_9604(RXS0); ++ ep = &dev->ep[0]; ++ ep->irqs++; ++ if (rxs_mask & RXS_SETUP) { ++ struct usb_ctrlrequest ctrl; ++ ep->packets++; ++ write_9604(0x40, ALTMSK);//someone is talking to us. Make sure we can be reset if we lose this communication ++ ep->stage = 1; ++ rxs_mask = read_9604(RXS0);//2nd read (1st one is for zero length packet) ++ ctrl.bRequestType = read_9604(RXD0); ++ ctrl.bRequest = read_9604(RXD0); ++ ctrl.wValue = read_9604(RXD0) + (read_9604(RXD0) << 8); ++ ctrl.wIndex = read_9604(RXD0) + (read_9604(RXD0) << 8); ++ ctrl.wLength = read_9604(RXD0) + (read_9604(RXD0) << 8); ++ ep->toggle = 1; ++ request_voodoo = ctrl.wLength; ++ if (ctrl.bRequestType & 0x80) {//This is an IN transaction ++ ep->is_in = 1;//David: is this correct for both cases//check with n9604_queue ++ read_mode = 0; ++ if (ctrl.wLength) {//should be followed by ZLP out packet ++ } else {//host expects ZLP out packet ++ ep->stage = 2; ++ } ++ } else {//This is an out transaction ++ if (ctrl.wLength) { ++ ep->is_in = 0; ++ read_mode = 1; ++ } else {//host expects ZLP in packet ++ read_mode = 0; ++ ep->stage = 2; ++ ep->is_in = 1; ++ } ++ } ++ switch (ctrl.bRequest) { ++ case USB_REQ_SET_ADDRESS: ++ write_9604(EPC_DEF, EPC0);//we still want to respond to the default address ++ write_9604(((dev->address = (ctrl.wValue & FAR_AD_MASK))) | FAR_AD_EN, FAR); ++ send_zero_length(0, dev); ++ dev->configured = 1;//we can send longer packets now :) ++ read_9604(ALTEV); ++ write_9604(ALTMSK_RESET, ALTMSK);//we also listen to reset requests too ++ break; ++ case USB_REQ_CLEAR_FEATURE: ++ if (ctrl.wValue == 0 && ctrl.bRequestType == 2) {//endpoint halt ++ int i; ++ for (i = 0; i < ARRAY_SIZE(dev->ep); i++) ++ if ((ctrl.wIndex & 0xF) == dev->ep[i].numActual) ++ n9604_clear_halt(&dev->ep[i].ep); ++ send_zero_length(0, dev); ++ break; ++ } ++ case USB_REQ_SET_DESCRIPTOR: ++ case USB_REQ_SYNCH_FRAME: ++ case USB_REQ_GET_STATUS: ++ case USB_REQ_SET_FEATURE: ++ case USB_REQ_SET_CONFIGURATION: ++ case USB_REQ_GET_DESCRIPTOR: ++ case USB_REQ_GET_CONFIGURATION: ++ case USB_REQ_SET_INTERFACE: ++ case USB_REQ_GET_INTERFACE: ++ default: ++ if (dev->driver->setup(&dev->gadget, &ctrl) < 0)//there was an error ++ if (((ctrl.bRequestType & 0x80) && ctrl.wLength) || (!(ctrl.bRequestType & 0x80) && !ctrl.wLength)) ++ send_zero_length(0, dev); ++ }//crtl.bRequest ++ }//setup ++ else if (read_mode) ++ pio_advance(ep); ++ else { ++ ep->stage = 0; ++ ep->packets++; ++ } ++ }//fifo 0 ++ if (mask & RXEV_FIFO1) { ++ ep = &dev->ep[2]; ++ pio_advance(ep); ++ ep->irqs++; ++ } ++ if (mask & RXEV_FIFO2) { ++ ep = &dev->ep[4]; ++ pio_advance(ep); ++ ep->irqs++; ++ } ++ if (mask & RXEV_FIFO3) { ++ ep = &dev->ep[6]; ++ pio_advance(ep); ++ ep->irqs++; ++ } ++} ++ ++inline void alt_ev_irq(struct n9604_udc *dev) { ++ u8 mask; ++ ++ mask = read_9604(ALTEV) & read_9604(ALTMSK); ++ ++ if (mask & ALTEV_EOP); ++ if (mask & ALTEV_SD3); ++ if (mask & ALTEV_SD5); ++ if (mask & ALTEV_RESET) { ++ int i; ++ udelay(1200);//no idea why this is needed, but it makes things work ++ write_9604(0x0, FAR);//lets not respond to any packets until we are ready ++ write_9604(NFSR_NodeReset, NFSR); ++ dev->driver->disconnect(&dev->gadget); ++ for (i = 0; i < ARRAY_SIZE(dev->ep); i++) ++ nuke(&dev->ep [i], -ESHUTDOWN);//this should be handled above by disconnect ++ write_9604(0x00, ALTMSK);//make sure reset is turned off, or we will constantly be interrupted ++ write_9604(0x11, TXMSK); ++ write_9604(0x11, RXMSK); ++ udc_reinit(dev); ++ dev->gadget.speed = USB_SPEED_FULL; ++ dev->ep[0].is_in = 0; ++ } ++ if (mask & ALTEV_RESUME); //write_9604(NFSR_NodeOperational, NFSR); ++ if (mask & ALTEV_WKUP);//we don't really sleep ++ if (mask & ALTEV_DMA); ++} ++ ++static void n9604_irq(int irq, void *_dev, struct pt_regs *r) { ++ struct n9604_udc *dev = _dev; ++ u8 mask; ++ ++ mask = read_9604(MAEV) & read_9604(MAMSK); ++ if (!mask) ++ return; ++ ++ if (mask & MAEV_ALT) { ++ alt_ev_irq(dev); ++ mask = read_9604(MAEV) & read_9604(MAMSK);//force a re-read of the current pending interrupts ++ } ++ if (mask & MAEV_TX_EV) ++ tx_ev_irq(dev); ++ if (mask & MAEV_RX_EV) ++ rx_ev_irq(dev); ++ dev->irqs++; ++ return; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int __init init (void) ++{ ++ struct n9604_udc *dev; ++ int ret; ++ u8 * addr; ++ ++ if (the_controller) ++ return -EBUSY; ++ ++ addr = ioremap(USBN9604_PHYS, 0x2);//ioremap will bump this to 1 page size ++ if (!addr) { ++ ERROR(dev, KERN_ERR "Unable to remap address\n"); ++ return -EINVAL; ++ } ++ ++ USBN9604_Offset = addr; ++ ++ if ((read_9604(RID) & 0xF) != 0x2) { //0x2 is the identifier for 9603/4 ++ iounmap(addr); ++ return -ENODEV; ++ } ++ ++ /* alloc, and start init */ ++ dev = kmalloc(sizeof *dev, SLAB_KERNEL); ++ if (dev == NULL){ ++ WARN(dev, "No memory"); ++ iounmap(addr); ++ return -ENOMEM; ++ } ++ memset(dev, 0, sizeof *dev); ++ spin_lock_init(&dev->lock); ++ dev->gadget.ops = &n9604_ops; ++ dev->gadget.is_dualspeed = 0; ++ ++ /* the "gadget" abstracts/virtualizes the controller */ ++ dev->gadget.dev.bus_id = "gadget"; ++ dev->gadget.name = driver_name; ++ ++ /* initialize the hardware */ ++ ++ udc_reset(dev); ++ ++ write_9604(CCONF_CODIS | 11, CCONF); ++ ++ udc_reinit(dev);//this is necessary as it sets up the epx functions ++ ++ the_controller = dev; ++ ++ if ((ret=request_irq(IRQ_GPIOC, n9604_irq, SA_SHIRQ, driver_name,dev))) { ++ WARN(dev, "Can't get IRQ\n"); ++ iounmap(addr); ++ return ret; ++ } ++ ++ return 0; ++} ++module_init (init); ++ ++static void __exit cleanup (void) ++{ ++ struct n9604_udc *dev = the_controller; ++ ++ //first kill the interrupts ++ udc_reset(dev); ++ free_irq(IRQ_GPIOC, dev); ++ ++ /* start with the driver above us */ ++ if (dev->driver) { ++ /* should have been done already by driver model core */ ++ WARN(dev, "Warning: Driver '%s' is still registered\n", ++ dev->driver->driver.name); ++ usb_gadget_unregister_driver(dev->driver); ++ } ++ kfree(dev); ++ iounmap(USBN9604_Offset); ++ the_controller = 0; ++ ++} ++module_exit (cleanup); ++ ++MODULE_PARM_DESC (delayTime, "Delays after reads and writes to the USB chip"); ++MODULE_PARM (delayTime, "i"); ++ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/n9604.h kernel/drivers/usb/gadget/n9604.h +--- /tmp/kernel/drivers/usb/gadget/n9604.h 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/n9604.h 2005-04-22 17:53:19.463534794 +0200 +@@ -0,0 +1,112 @@ ++/* ++ * National 9604 USB device controller driver ++ * ++ * Copyright 2003 Technical Solutions Inc. ++ * ++ * ported from: ++ * ++ * Toshiba TC86C001 ("Goku-S") USB Device Controller driver ++ * ++ * Copyright (C) 2000-2002 Lineo ++ * by Stuart Lynne, Tom Rushworth, and Bruce Balden ++ * Copyright (C) 2002 Toshiba Corporation ++ * Copyright (C) 2003 MontaVista Software (source@mvista.com) ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++#define MAX_FIFO_SIZE 64 ++#define MAX_EP0_SIZE 8 ++ ++struct n9604_ep { ++ struct usb_ep ep; ++ struct n9604_udc *dev; ++ unsigned long irqs; ++ int acct_req_lengths[4]; ++ int acct_req_dir[4];//direction ++ unsigned long queue_reqs;//how many times has n9604_queue been called ++ unsigned long queue_active;//how many current requests ++ unsigned long packets;//counter of raw packets ++ unsigned num:4, ++ numActual:4, ++ fifoNum:2, ++ is_in:1, ++ stage:2,//for ep0, 0 = unused, 1 = got setup, 2 = done transfer/ready to send/receive ZLP ++ toggle:1, ++ nuking:1;//are we killing on off this endpoint//only used for ep0 to help with stages ++ /* analogous to a host-side qh */ ++ struct list_head queue; ++ const struct usb_endpoint_descriptor *desc; ++ ++ u8 control; ++ u8 fifo; ++ u8 status; ++ u8 command; ++}; ++ ++struct n9604_request { ++ struct usb_request req; ++ struct list_head queue; ++ int complete;//this is added for tx requests ++ //if set the entire request has been written to the fifo, just waiting for confirmation ++ //from the interrupt that it has been sent ++ ++ unsigned mapped:1; ++}; ++ ++struct n9604_udc { ++ struct usb_gadget gadget; ++ spinlock_t lock; ++ struct n9604_ep ep[7]; ++ struct usb_gadget_driver *driver; ++ int configured; ++ ++ u8 address; ++ ++ /* statistics... */ ++ unsigned long irqs; ++}; ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++#define xprintk(dev,level,fmt,args...) \ ++ printk(level "%s %s: " fmt , driver_name , \ ++ "S2410 gadget" , ## args) ++ ++#define ERROR(dev,fmt,args...) \ ++ xprintk(dev , KERN_ERR , fmt , ## args) ++#define WARN(dev,fmt,args...) \ ++ xprintk(dev , KERN_WARNING , fmt , ## args) ++#define INFO(dev,fmt,args...) \ ++ xprintk(dev , KERN_INFO , fmt , ## args) ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* 2.5 stuff that's sometimes missing in 2.4 */ ++ ++#ifndef container_of ++#define container_of list_entry ++#endif ++ ++#ifndef likely ++#define likely(x) (x) ++#define unlikely(x) (x) ++#endif ++ ++#ifndef BUG_ON ++#define BUG_ON(condition) do { if (unlikely((condition)!=0)) BUG(); } while(0) ++#endif ++ ++#ifndef WARN_ON ++#define WARN_ON(x) do { } while (0) ++#endif ++ ++#ifndef IRQ_NONE ++typedef void irqreturn_t; ++#define IRQ_NONE ++#define IRQ_HANDLED ++#define IRQ_RETVAL(x) ++#endif +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/n9604regs.h kernel/drivers/usb/gadget/n9604regs.h +--- /tmp/kernel/drivers/usb/gadget/n9604regs.h 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/n9604regs.h 2005-04-22 17:53:19.466534306 +0200 +@@ -0,0 +1,248 @@ ++/* National 9604 registers */ ++ ++#define USBN9604_PHYS 0x08000000 ++ ++extern u8 * USBN9604_Offset; ++ ++static u8 last_address = 255;//an invalid address ++ ++inline u8 read_9604(u8 addr) { ++ u8 tmp; ++ if (addr != last_address) { ++ outb(addr, USBN9604_Offset + 1); ++ last_address = addr; ++ } ++ tmp = inb(USBN9604_Offset); ++ return tmp; ++} ++ ++inline void write_9604(u8 value, u8 addr) { ++ if (addr != last_address) { ++ outb(addr, USBN9604_Offset + 1); ++ last_address = addr; ++ } ++ outb(value, USBN9604_Offset); ++} ++ ++ ++ ++#define MCNTRL 0x00 ++#define CCONF 0x01 ++ ++#define RID 0x03 ++#define FAR 0x04 ++#define NFSR 0x05 ++#define MAEV 0x06 ++#define MAMSK 0x07 ++#define ALTEV 0x08 ++#define ALTMSK 0x09 ++#define TXEV 0x0A ++#define TXMSK 0x0B ++#define RXEV 0x0C ++#define RXMSK 0x0D ++#define NAKEV 0x0E ++#define NAKMSK 0x0F ++#define FWEV 0x10 ++#define FWMSK 0x11 ++#define FNH 0x12 ++#define FNL 0x13 ++#define DMACNTRL 0x14 ++#define DMAEV 0x15 ++#define DMAMSK 0x16 ++#define MIR 0x17 ++#define DMACNT 0x18 ++#define DMAERR 0x19 ++ ++#define WKUP 0x1B ++ ++ ++ ++ ++#define EPC0 0x20 ++#define TXD0 0x21 ++#define TXS0 0x22 ++#define TXC0 0x23 ++ ++#define RXD0 0x25 ++#define RXS0 0x26 ++#define RXC0 0x27 ++#define EPC1 0x28 ++#define TXD1 0x29 ++#define TXS1 0x2A ++#define TXC1 0x2B ++#define EPC2 0x2C ++#define RXD1 0x2D ++#define RXS1 0x2E ++#define RXC1 0x2F ++#define EPC3 0x30 ++#define TXD2 0x31 ++#define TXS2 0x32 ++#define TXC2 0x33 ++#define EPC4 0x34 ++#define RXD2 0x35 ++#define RXS2 0x36 ++#define RXC2 0x37 ++#define EPC5 0x38 ++#define TXD3 0x39 ++#define TXS3 0x3A ++#define TXC3 0x3B ++#define EPC6 0x3C ++#define RXD3 0x3D ++#define RXS3 0x3E ++#define RXC3 0x3F ++ ++ ++/* MCNTRL values */ ++#define MCNTRL_SRST (1 << 0) ++#define MCNTRL_VGE (1 << 2) ++#define MCNTRL_NAT (1 << 3) ++#define MCNTRL_INTOC_MASK (3 << 6) ++#define MCNTRL_INTOC_DISABLE 0 ++#define MCNTRL_INTOC_ActLowOpen (1 << 6) ++#define MCNTRL_INTOC_ActHigh (2 << 6) ++#define MCNTRL_INTOC_ActLowPP (3 << 6) ++ ++/* CCONF values */ ++#define CCONF_CLKDIV_MASK 0x0F ++#define CCONF_CODIS (1 << 7) ++ ++/* FAR values */ ++#define FAR_AD_MASK 0x7F ++#define FAR_AD_EN 0x80 ++ ++/* NFSR values */ ++#define NFSR_NodeReset 0x0 ++#define NFSR_NodeResume 0x1 ++#define NFSR_NodeOperational 0x2 ++#define NFSR_NodeSuspend 0x3 ++ ++/* MAEV values */ ++#define MAEV_WARN (1 << 0) ++#define MAEV_ALT (1 << 1) ++#define MAEV_TX_EV (1 << 2) ++#define MAEV_FRAME (1 << 3) ++#define MAEV_NAK (1 << 4) ++#define MAEV_ULD (1 << 5) ++#define MAEV_RX_EV (1 << 6) ++#define MAEV_INTR (1 << 7) ++ ++/* MAMSK values */ ++#define MAMSK_WARN (1 << 0) ++#define MAMSK_ALT (1 << 1) ++#define MAMSK_TX_EV (1 << 2) ++#define MAMSK_FRAME (1 << 3) ++#define MAMSK_NAK (1 << 4) ++#define MAMSK_ULD (1 << 5) ++#define MAMSK_RX_EV (1 << 6) ++#define MAMSK_INTR (1 << 7) ++ ++/* ALTEV values */ ++ ++#define ALTEV_WKUP (1 << 1) ++#define ALTEV_DMA (1 << 2) ++#define ALTEV_EOP (1 << 3) ++#define ALTEV_SD3 (1 << 4) ++#define ALTEV_SD5 (1 << 5) ++#define ALTEV_RESET (1 << 6) ++#define ALTEV_RESUME (1 << 7) ++ ++/* ALTMSK values */ ++ ++#define ALTMSK_WKUP (1 << 1) ++#define ALTMSK_DMA (1 << 2) ++#define ALTMSK_EOP (1 << 3) ++#define ALTMSK_SD3 (1 << 4) ++#define ALTMSK_SD5 (1 << 5) ++#define ALTMSK_RESET (1 << 6) ++#define ALTMSK_RESUME (1 << 7) ++ ++/* NAKEV values */ ++ ++#define NAKEV_TXFIFO0 (1 << 0) ++#define NAKEV_TXFIFO1 (1 << 1) ++#define NAKEV_TXFIFO2 (1 << 2) ++#define NAKEV_TXFIFO3 (1 << 3) ++#define NAKEV_RXFIFO0 (1 << 4) ++#define NAKEV_RXFIFO1 (1 << 5) ++#define NAKEV_RXFIFO2 (1 << 6) ++#define NAKEV_RXFIFO3 (1 << 7) ++ ++ ++/* WKUP values */ ++#define WKUP_PNDUSB (1 << 0) ++#define WKUP_PNDUC (1 << 1) ++#define WKUP_ENUSB (1 << 2) ++#define WKUP_ENUC (1 << 3) ++#define WKUP_WKMODE (1 << 5) ++#define WKUP_HOS (1 << 6) ++#define WKUP_FHT (1 << 7) ++ ++/* EPC values */ ++ ++#define EPC_EP_MASK 0x0F //EP0 == 0 ++#define EPC_EP_EN (1 << 4)//not EP0 ++#define EPC_ISO (1 << 5)//not EP0 ++#define EPC_DEF (1 << 6)//EP0 only ++#define EPC_STALL (1 << 7) ++ ++/* TXS values */ ++ ++#define TXS_TCOUNT_MASK 0x1F ++#define TXS_TX_DONE (1 << 5) ++#define TXS_ACK_STAT (1 << 6) ++#define TXS_TX_URUN (1 << 7) ++ ++/* TXC values */ ++ ++#define TXC_TX_EN (1 << 0) ++#define TXC_LAST (1 << 1)//not for endpoint 0 ++#define TXC_TOGGLE (1 << 2)//sets DATA1 when set ++#define TXC_FLUSH (1 << 3) ++#define TXC_IGN_IN (1 << 4)//only endpoint 0 ++#define TXC_RFF (1 << 4)//not for endpoint 0 ++#define TXC_TFWL0 (1 << 5)//" ++#define TXC_TFWL1 (1 << 6)//" ++#define TXC_IGN_ISOMSK (1 << 7)//" ++ ++/* TXEV values */ ++ ++#define TXEV_FIFO0 (1 << 0) ++#define TXEV_FIFO1 (1 << 1) ++#define TXEV_FIFO2 (1 << 2) ++#define TXEV_FIFO3 (1 << 3) ++#define TXEV_UDRRN0 (1 << 4) ++#define TXEV_UDRRN1 (1 << 5) ++#define TXEV_UDRRN2 (1 << 6) ++#define TXEV_UDRRN3 (1 << 7) ++ ++ ++/* RXEV values */ ++ ++#define RXEV_FIFO0 (1 << 0) ++#define RXEV_FIFO1 (1 << 1) ++#define RXEV_FIFO2 (1 << 2) ++#define RXEV_FIFO3 (1 << 3) ++#define RXEV_OVRRN0 (1 << 4) ++#define RXEV_OVRRN1 (1 << 5) ++#define RXEV_OVRRN2 (1 << 6) ++#define RXEV_OVRRN3 (1 << 7) ++ ++/* RXC values */ ++ ++#define RXC_RX_EN (1 << 0) ++#define RXC_IGN_OUT (1 << 1) ++#define RXC_IGN_SETUP (1 << 2) ++#define RXC_FLUSH (1 << 3) ++#define RXC_RFWL0 (1 << 5) ++#define RXC_RFWL1 (1 << 6) ++ ++/* RXS values */ ++ ++#define RXS_RCOUNTMASK 0xF ++#define RXS_RX_LAST (1 << 4) ++#define RXS_TOGGLE (1 << 5) ++#define RXS_SETUP (1 << 6) ++#define RXS_RX_ERR (1 << 7) ++ ++ ++ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/ndis.h kernel/drivers/usb/gadget/ndis.h +--- /tmp/kernel/drivers/usb/gadget/ndis.h 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/ndis.h 2005-04-22 17:53:19.469533817 +0200 +@@ -0,0 +1,217 @@ ++/* ++ * ndis.h ++ * ++ * ntddndis.h modified by Benedikt Spranger ++ * ++ * Thanks to the cygwin development team, ++ * espacially to Casper S. Hornstrup ++ * ++ * THIS SOFTWARE IS NOT COPYRIGHTED ++ * ++ * This source code is offered for use in the public domain. You may ++ * use, modify or distribute it freely. ++ * ++ * This code is distributed in the hope that it will be useful but ++ * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY ++ * DISCLAIMED. This includes but is not limited to warranties of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ * ++ */ ++ ++#ifndef _LINUX_NDIS_H ++#define _LINUX_NDIS_H ++ ++ ++#define NDIS_STATUS_MULTICAST_FULL 0xC0010009 ++#define NDIS_STATUS_MULTICAST_EXISTS 0xC001000A ++#define NDIS_STATUS_MULTICAST_NOT_FOUND 0xC001000B ++ ++enum NDIS_DEVICE_POWER_STATE { ++ NdisDeviceStateUnspecified = 0, ++ NdisDeviceStateD0, ++ NdisDeviceStateD1, ++ NdisDeviceStateD2, ++ NdisDeviceStateD3, ++ NdisDeviceStateMaximum ++}; ++ ++struct NDIS_PM_WAKE_UP_CAPABILITIES { ++ enum NDIS_DEVICE_POWER_STATE MinMagicPacketWakeUp; ++ enum NDIS_DEVICE_POWER_STATE MinPatternWakeUp; ++ enum NDIS_DEVICE_POWER_STATE MinLinkChangeWakeUp; ++}; ++ ++/* NDIS_PNP_CAPABILITIES.Flags constants */ ++#define NDIS_DEVICE_WAKE_UP_ENABLE 0x00000001 ++#define NDIS_DEVICE_WAKE_ON_PATTERN_MATCH_ENABLE 0x00000002 ++#define NDIS_DEVICE_WAKE_ON_MAGIC_PACKET_ENABLE 0x00000004 ++ ++struct NDIS_PNP_CAPABILITIES { ++ u32 Flags; ++ struct NDIS_PM_WAKE_UP_CAPABILITIES WakeUpCapabilities; ++}; ++ ++struct NDIS_PM_PACKET_PATTERN { ++ u32 Priority; ++ u32 Reserved; ++ u32 MaskSize; ++ u32 PatternOffset; ++ u32 PatternSize; ++ u32 PatternFlags; ++}; ++ ++ ++/* Required Object IDs (OIDs) */ ++#define OID_GEN_SUPPORTED_LIST 0x00010101 ++#define OID_GEN_HARDWARE_STATUS 0x00010102 ++#define OID_GEN_MEDIA_SUPPORTED 0x00010103 ++#define OID_GEN_MEDIA_IN_USE 0x00010104 ++#define OID_GEN_MAXIMUM_LOOKAHEAD 0x00010105 ++#define OID_GEN_MAXIMUM_FRAME_SIZE 0x00010106 ++#define OID_GEN_LINK_SPEED 0x00010107 ++#define OID_GEN_TRANSMIT_BUFFER_SPACE 0x00010108 ++#define OID_GEN_RECEIVE_BUFFER_SPACE 0x00010109 ++#define OID_GEN_TRANSMIT_BLOCK_SIZE 0x0001010A ++#define OID_GEN_RECEIVE_BLOCK_SIZE 0x0001010B ++#define OID_GEN_VENDOR_ID 0x0001010C ++#define OID_GEN_VENDOR_DESCRIPTION 0x0001010D ++#define OID_GEN_CURRENT_PACKET_FILTER 0x0001010E ++#define OID_GEN_CURRENT_LOOKAHEAD 0x0001010F ++#define OID_GEN_DRIVER_VERSION 0x00010110 ++#define OID_GEN_MAXIMUM_TOTAL_SIZE 0x00010111 ++#define OID_GEN_PROTOCOL_OPTIONS 0x00010112 ++#define OID_GEN_MAC_OPTIONS 0x00010113 ++#define OID_GEN_MEDIA_CONNECT_STATUS 0x00010114 ++#define OID_GEN_MAXIMUM_SEND_PACKETS 0x00010115 ++#define OID_GEN_VENDOR_DRIVER_VERSION 0x00010116 ++#define OID_GEN_SUPPORTED_GUIDS 0x00010117 ++#define OID_GEN_NETWORK_LAYER_ADDRESSES 0x00010118 ++#define OID_GEN_TRANSPORT_HEADER_OFFSET 0x00010119 ++#define OID_GEN_MACHINE_NAME 0x0001021A ++#define OID_GEN_RNDIS_CONFIG_PARAMETER 0x0001021B ++#define OID_GEN_VLAN_ID 0x0001021C ++ ++/* Optional OIDs */ ++#define OID_GEN_MEDIA_CAPABILITIES 0x00010201 ++#define OID_GEN_PHYSICAL_MEDIUM 0x00010202 ++ ++/* Required statistics OIDs */ ++#define OID_GEN_XMIT_OK 0x00020101 ++#define OID_GEN_RCV_OK 0x00020102 ++#define OID_GEN_XMIT_ERROR 0x00020103 ++#define OID_GEN_RCV_ERROR 0x00020104 ++#define OID_GEN_RCV_NO_BUFFER 0x00020105 ++ ++/* Optional statistics OIDs */ ++#define OID_GEN_DIRECTED_BYTES_XMIT 0x00020201 ++#define OID_GEN_DIRECTED_FRAMES_XMIT 0x00020202 ++#define OID_GEN_MULTICAST_BYTES_XMIT 0x00020203 ++#define OID_GEN_MULTICAST_FRAMES_XMIT 0x00020204 ++#define OID_GEN_BROADCAST_BYTES_XMIT 0x00020205 ++#define OID_GEN_BROADCAST_FRAMES_XMIT 0x00020206 ++#define OID_GEN_DIRECTED_BYTES_RCV 0x00020207 ++#define OID_GEN_DIRECTED_FRAMES_RCV 0x00020208 ++#define OID_GEN_MULTICAST_BYTES_RCV 0x00020209 ++#define OID_GEN_MULTICAST_FRAMES_RCV 0x0002020A ++#define OID_GEN_BROADCAST_BYTES_RCV 0x0002020B ++#define OID_GEN_BROADCAST_FRAMES_RCV 0x0002020C ++#define OID_GEN_RCV_CRC_ERROR 0x0002020D ++#define OID_GEN_TRANSMIT_QUEUE_LENGTH 0x0002020E ++#define OID_GEN_GET_TIME_CAPS 0x0002020F ++#define OID_GEN_GET_NETCARD_TIME 0x00020210 ++#define OID_GEN_NETCARD_LOAD 0x00020211 ++#define OID_GEN_DEVICE_PROFILE 0x00020212 ++#define OID_GEN_INIT_TIME_MS 0x00020213 ++#define OID_GEN_RESET_COUNTS 0x00020214 ++#define OID_GEN_MEDIA_SENSE_COUNTS 0x00020215 ++#define OID_GEN_FRIENDLY_NAME 0x00020216 ++#define OID_GEN_MINIPORT_INFO 0x00020217 ++#define OID_GEN_RESET_VERIFY_PARAMETERS 0x00020218 ++ ++/* IEEE 802.3 (Ethernet) OIDs */ ++#define NDIS_802_3_MAC_OPTION_PRIORITY 0x00000001 ++ ++#define OID_802_3_PERMANENT_ADDRESS 0x01010101 ++#define OID_802_3_CURRENT_ADDRESS 0x01010102 ++#define OID_802_3_MULTICAST_LIST 0x01010103 ++#define OID_802_3_MAXIMUM_LIST_SIZE 0x01010104 ++#define OID_802_3_MAC_OPTIONS 0x01010105 ++#define OID_802_3_RCV_ERROR_ALIGNMENT 0x01020101 ++#define OID_802_3_XMIT_ONE_COLLISION 0x01020102 ++#define OID_802_3_XMIT_MORE_COLLISIONS 0x01020103 ++#define OID_802_3_XMIT_DEFERRED 0x01020201 ++#define OID_802_3_XMIT_MAX_COLLISIONS 0x01020202 ++#define OID_802_3_RCV_OVERRUN 0x01020203 ++#define OID_802_3_XMIT_UNDERRUN 0x01020204 ++#define OID_802_3_XMIT_HEARTBEAT_FAILURE 0x01020205 ++#define OID_802_3_XMIT_TIMES_CRS_LOST 0x01020206 ++#define OID_802_3_XMIT_LATE_COLLISIONS 0x01020207 ++ ++/* OID_GEN_MINIPORT_INFO constants */ ++#define NDIS_MINIPORT_BUS_MASTER 0x00000001 ++#define NDIS_MINIPORT_WDM_DRIVER 0x00000002 ++#define NDIS_MINIPORT_SG_LIST 0x00000004 ++#define NDIS_MINIPORT_SUPPORTS_MEDIA_QUERY 0x00000008 ++#define NDIS_MINIPORT_INDICATES_PACKETS 0x00000010 ++#define NDIS_MINIPORT_IGNORE_PACKET_QUEUE 0x00000020 ++#define NDIS_MINIPORT_IGNORE_REQUEST_QUEUE 0x00000040 ++#define NDIS_MINIPORT_IGNORE_TOKEN_RING_ERRORS 0x00000080 ++#define NDIS_MINIPORT_INTERMEDIATE_DRIVER 0x00000100 ++#define NDIS_MINIPORT_IS_NDIS_5 0x00000200 ++#define NDIS_MINIPORT_IS_CO 0x00000400 ++#define NDIS_MINIPORT_DESERIALIZE 0x00000800 ++#define NDIS_MINIPORT_REQUIRES_MEDIA_POLLING 0x00001000 ++#define NDIS_MINIPORT_SUPPORTS_MEDIA_SENSE 0x00002000 ++#define NDIS_MINIPORT_NETBOOT_CARD 0x00004000 ++#define NDIS_MINIPORT_PM_SUPPORTED 0x00008000 ++#define NDIS_MINIPORT_SUPPORTS_MAC_ADDRESS_OVERWRITE 0x00010000 ++#define NDIS_MINIPORT_USES_SAFE_BUFFER_APIS 0x00020000 ++#define NDIS_MINIPORT_HIDDEN 0x00040000 ++#define NDIS_MINIPORT_SWENUM 0x00080000 ++#define NDIS_MINIPORT_SURPRISE_REMOVE_OK 0x00100000 ++#define NDIS_MINIPORT_NO_HALT_ON_SUSPEND 0x00200000 ++#define NDIS_MINIPORT_HARDWARE_DEVICE 0x00400000 ++#define NDIS_MINIPORT_SUPPORTS_CANCEL_SEND_PACKETS 0x00800000 ++#define NDIS_MINIPORT_64BITS_DMA 0x01000000 ++ ++#define NDIS_MEDIUM_802_3 0x00000000 ++#define NDIS_MEDIUM_802_5 0x00000001 ++#define NDIS_MEDIUM_FDDI 0x00000002 ++#define NDIS_MEDIUM_WAN 0x00000003 ++#define NDIS_MEDIUM_LOCAL_TALK 0x00000004 ++#define NDIS_MEDIUM_DIX 0x00000005 ++#define NDIS_MEDIUM_ARCENT_RAW 0x00000006 ++#define NDIS_MEDIUM_ARCENT_878_2 0x00000007 ++#define NDIS_MEDIUM_ATM 0x00000008 ++#define NDIS_MEDIUM_WIRELESS_LAN 0x00000009 ++#define NDIS_MEDIUM_IRDA 0x0000000A ++#define NDIS_MEDIUM_BPC 0x0000000B ++#define NDIS_MEDIUM_CO_WAN 0x0000000C ++#define NDIS_MEDIUM_1394 0x0000000D ++ ++#define NDIS_PACKET_TYPE_DIRECTED 0x00000001 ++#define NDIS_PACKET_TYPE_MULTICAST 0x00000002 ++#define NDIS_PACKET_TYPE_ALL_MULTICAST 0x00000004 ++#define NDIS_PACKET_TYPE_BROADCAST 0x00000008 ++#define NDIS_PACKET_TYPE_SOURCE_ROUTING 0x00000010 ++#define NDIS_PACKET_TYPE_PROMISCUOUS 0x00000020 ++#define NDIS_PACKET_TYPE_SMT 0x00000040 ++#define NDIS_PACKET_TYPE_ALL_LOCAL 0x00000080 ++#define NDIS_PACKET_TYPE_GROUP 0x00000100 ++#define NDIS_PACKET_TYPE_ALL_FUNCTIONAL 0x00000200 ++#define NDIS_PACKET_TYPE_FUNCTIONAL 0x00000400 ++#define NDIS_PACKET_TYPE_MAC_FRAME 0x00000800 ++ ++#define NDIS_MEDIA_STATE_CONNECTED 0x00000000 ++#define NDIS_MEDIA_STATE_DISCONNECTED 0x00000001 ++ ++#define NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA 0x00000001 ++#define NDIS_MAC_OPTION_RECEIVE_SERIALIZED 0x00000002 ++#define NDIS_MAC_OPTION_TRANSFERS_NOT_PEND 0x00000004 ++#define NDIS_MAC_OPTION_NO_LOOPBACK 0x00000008 ++#define NDIS_MAC_OPTION_FULL_DUPLEX 0x00000010 ++#define NDIS_MAC_OPTION_EOTX_INDICATION 0x00000020 ++#define NDIS_MAC_OPTION_8021P_PRIORITY 0x00000040 ++#define NDIS_MAC_OPTION_RESERVED 0x80000000 ++ ++#endif /* _LINUX_NDIS_H */ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/net2280.c kernel/drivers/usb/gadget/net2280.c +--- /tmp/kernel/drivers/usb/gadget/net2280.c 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/net2280.c 2005-04-22 17:53:19.478532352 +0200 +@@ -0,0 +1,2918 @@ ++/* ++ * Driver for the NetChip 2280 USB device controller. ++ * Specs and errata are available from . ++ * ++ * NetChip Technology Inc. supported the development of this driver. ++ * ++ * ++ * CODE STATUS HIGHLIGHTS ++ * ++ * This driver should work well with most "gadget" drivers, including ++ * the File Storage, Serial, and Ethernet/RNDIS gadget drivers ++ * as well as Gadget Zero and Gadgetfs. ++ * ++ * DMA is enabled by default. Drivers using transfer queues might use ++ * DMA chaining to remove IRQ latencies between transfers. (Except when ++ * short OUT transfers happen.) Drivers can use the req->no_interrupt ++ * hint to completely eliminate some IRQs, if a later IRQ is guaranteed ++ * and DMA chaining is enabled. ++ * ++ * Note that almost all the errata workarounds here are only needed for ++ * rev1 chips. Rev1a silicon (0110) fixes almost all of them. ++ */ ++ ++/* ++ * Copyright (C) 2003 David Brownell ++ * Copyright (C) 2003 NetChip Technologies ++ * ++ * 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 ++ */ ++ ++#undef DEBUG /* messages on error and most fault paths */ ++#undef VERBOSE /* extra debug messages (success too) */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++ ++#define DRIVER_DESC "NetChip 2280 USB Peripheral Controller" ++#define DRIVER_VERSION "2004 Jan 14" ++ ++#define DMA_ADDR_INVALID (~(dma_addr_t)0) ++#define EP_DONTUSE 13 /* nonzero */ ++ ++#define USE_RDK_LEDS /* GPIO pins control three LEDs */ ++ ++ ++static const char driver_name [] = "net2280"; ++static const char driver_desc [] = DRIVER_DESC; ++ ++static const char ep0name [] = "ep0"; ++static const char *ep_name [] = { ++ ep0name, ++ "ep-a", "ep-b", "ep-c", "ep-d", ++ "ep-e", "ep-f", ++}; ++ ++/* use_dma -- general goodness, fewer interrupts, less cpu load (vs PIO) ++ * use_dma_chaining -- dma descriptor queueing gives even more irq reduction ++ * ++ * The net2280 DMA engines are not tightly integrated with their FIFOs; ++ * not all cases are (yet) handled well in this driver or the silicon. ++ * Some gadget drivers work better with the dma support here than others. ++ * These two parameters let you use PIO or more aggressive DMA. ++ */ ++static int use_dma = 1; ++static int use_dma_chaining = 0; ++ ++MODULE_PARM (use_dma, "i"); ++MODULE_PARM_DESC (use_dma, "true to use dma controllers"); ++ ++MODULE_PARM (use_dma_chaining, "i"); ++MODULE_PARM_DESC (use_dma_chaining, "true to use dma descriptor queues"); ++ ++ ++/* mode 0 == ep-{a,b,c,d} 1K fifo each ++ * mode 1 == ep-{a,b} 2K fifo each, ep-{c,d} unavailable ++ * mode 2 == ep-a 2K fifo, ep-{b,c} 1K each, ep-d unavailable ++ */ ++static ushort fifo_mode = 0; ++ ++MODULE_PARM (fifo_mode, "h"); ++MODULE_PARM_DESC (fifo_mode, "net2280 fifo mode"); ++ ++ ++#define DIR_STRING(bAddress) (((bAddress) & USB_DIR_IN) ? "in" : "out") ++ ++#if defined(USE_SYSFS_DEBUG_FILES) || defined (DEBUG) ++static char *type_string (u8 bmAttributes) ++{ ++ switch ((bmAttributes) & USB_ENDPOINT_XFERTYPE_MASK) { ++ case USB_ENDPOINT_XFER_BULK: return "bulk"; ++ case USB_ENDPOINT_XFER_ISOC: return "iso"; ++ case USB_ENDPOINT_XFER_INT: return "intr"; ++ }; ++ return "control"; ++} ++#endif ++ ++#include "net2280.h" ++ ++#define valid_bit __constant_cpu_to_le32 (1 << VALID_BIT) ++#define dma_done_ie __constant_cpu_to_le32 (1 << DMA_DONE_INTERRUPT_ENABLE) ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int ++net2280_enable (struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) ++{ ++ struct net2280 *dev; ++ struct net2280_ep *ep; ++ u32 max, tmp; ++ unsigned long flags; ++ ++ ep = container_of (_ep, struct net2280_ep, ep); ++ if (!_ep || !desc || ep->desc || _ep->name == ep0name ++ || desc->bDescriptorType != USB_DT_ENDPOINT) ++ return -EINVAL; ++ dev = ep->dev; ++ if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) ++ return -ESHUTDOWN; ++ ++ /* erratum 0119 workaround ties up an endpoint number */ ++ if ((desc->bEndpointAddress & 0x0f) == EP_DONTUSE) ++ return -EDOM; ++ ++ /* sanity check ep-e/ep-f since their fifos are small */ ++ max = le16_to_cpu (desc->wMaxPacketSize) & 0x1fff; ++ if (ep->num > 4 && max > 64) ++ return -ERANGE; ++ ++ spin_lock_irqsave (&dev->lock, flags); ++ _ep->maxpacket = max & 0x7ff; ++ ep->desc = desc; ++ ++ /* ep_reset() has already been called */ ++ ep->stopped = 0; ++ ep->out_overflow = 0; ++ ++ /* set speed-dependent max packet; may kick in high bandwidth */ ++ set_idx_reg (dev->regs, REG_EP_MAXPKT (dev, ep->num), max); ++ ++ /* FIFO lines can't go to different packets. PIO is ok, so ++ * use it instead of troublesome (non-bulk) multi-packet DMA. ++ */ ++ if (ep->dma && (max % 4) != 0 && use_dma_chaining) { ++ DEBUG (ep->dev, "%s, no dma for maxpacket %d\n", ++ ep->ep.name, ep->ep.maxpacket); ++ ep->dma = NULL; ++ } ++ ++ /* set type, direction, address; reset fifo counters */ ++ writel ((1 << FIFO_FLUSH), &ep->regs->ep_stat); ++ tmp = (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK); ++ if (tmp == USB_ENDPOINT_XFER_INT) { ++ /* erratum 0105 workaround prevents hs NYET */ ++ if (dev->chiprev == 0100 ++ && dev->gadget.speed == USB_SPEED_HIGH ++ && !(desc->bEndpointAddress & USB_DIR_IN)) ++ writel ((1 << CLEAR_NAK_OUT_PACKETS_MODE), ++ &ep->regs->ep_rsp); ++ } else if (tmp == USB_ENDPOINT_XFER_BULK) { ++ /* catch some particularly blatant driver bugs */ ++ if ((dev->gadget.speed == USB_SPEED_HIGH ++ && max != 512) ++ || (dev->gadget.speed == USB_SPEED_FULL ++ && max > 64)) { ++ spin_unlock_irqrestore (&dev->lock, flags); ++ return -ERANGE; ++ } ++ } ++ ep->is_iso = (tmp == USB_ENDPOINT_XFER_ISOC) ? 1 : 0; ++ tmp <<= ENDPOINT_TYPE; ++ tmp |= desc->bEndpointAddress; ++ tmp |= (4 << ENDPOINT_BYTE_COUNT); /* default full fifo lines */ ++ tmp |= 1 << ENDPOINT_ENABLE; ++ wmb (); ++ ++ /* for OUT transfers, block the rx fifo until a read is posted */ ++ ep->is_in = (tmp & USB_DIR_IN) != 0; ++ if (!ep->is_in) ++ writel ((1 << SET_NAK_OUT_PACKETS), &ep->regs->ep_rsp); ++ ++ writel (tmp, &ep->regs->ep_cfg); ++ ++ /* enable irqs */ ++ if (!ep->dma) { /* pio, per-packet */ ++ tmp = (1 << ep->num) | readl (&dev->regs->pciirqenb0); ++ writel (tmp, &dev->regs->pciirqenb0); ++ ++ tmp = (1 << DATA_PACKET_RECEIVED_INTERRUPT_ENABLE) ++ | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE) ++ | readl (&ep->regs->ep_irqenb); ++ writel (tmp, &ep->regs->ep_irqenb); ++ } else { /* dma, per-request */ ++ tmp = (1 << (8 + ep->num)); /* completion */ ++ tmp |= readl (&dev->regs->pciirqenb1); ++ writel (tmp, &dev->regs->pciirqenb1); ++ ++ /* for short OUT transfers, dma completions can't ++ * advance the queue; do it pio-style, by hand. ++ * NOTE erratum 0112 workaround #2 ++ */ ++ if ((desc->bEndpointAddress & USB_DIR_IN) == 0) { ++ tmp = (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT_ENABLE); ++ writel (tmp, &ep->regs->ep_irqenb); ++ ++ tmp = (1 << ep->num) | readl (&dev->regs->pciirqenb0); ++ writel (tmp, &dev->regs->pciirqenb0); ++ } ++ } ++ ++ tmp = desc->bEndpointAddress; ++ DEBUG (dev, "enabled %s (ep%d%s-%s) %s max %04x\n", ++ _ep->name, tmp & 0x0f, DIR_STRING (tmp), ++ type_string (desc->bmAttributes), ++ ep->dma ? "dma" : "pio", max); ++ ++ /* pci writes may still be posted */ ++ spin_unlock_irqrestore (&dev->lock, flags); ++ return 0; ++} ++ ++static int handshake (u32 *ptr, u32 mask, u32 done, int usec) ++{ ++ u32 result; ++ ++ do { ++ result = readl (ptr); ++ if (result == ~(u32)0) /* "device unplugged" */ ++ return -ENODEV; ++ result &= mask; ++ if (result == done) ++ return 0; ++ udelay (1); ++ usec--; ++ } while (usec > 0); ++ return -ETIMEDOUT; ++} ++ ++static struct usb_ep_ops net2280_ep_ops; ++ ++static void ep_reset (struct net2280_regs *regs, struct net2280_ep *ep) ++{ ++ u32 tmp; ++ ++ ep->desc = NULL; ++ INIT_LIST_HEAD (&ep->queue); ++ ++ ep->ep.maxpacket = ~0; ++ ep->ep.ops = &net2280_ep_ops; ++ ++ /* disable the dma, irqs, endpoint... */ ++ if (ep->dma) { ++ writel (0, &ep->dma->dmactl); ++ writel ( (1 << DMA_SCATTER_GATHER_DONE_INTERRUPT) ++ | (1 << DMA_TRANSACTION_DONE_INTERRUPT) ++ | (1 << DMA_ABORT) ++ , &ep->dma->dmastat); ++ ++ tmp = readl (®s->pciirqenb0); ++ tmp &= ~(1 << ep->num); ++ writel (tmp, ®s->pciirqenb0); ++ } else { ++ tmp = readl (®s->pciirqenb1); ++ tmp &= ~(1 << (8 + ep->num)); /* completion */ ++ writel (tmp, ®s->pciirqenb1); ++ } ++ writel (0, &ep->regs->ep_irqenb); ++ ++ /* init to our chosen defaults, notably so that we NAK OUT ++ * packets until the driver queues a read (+note erratum 0112) ++ */ ++ writel ( (1 << SET_NAK_OUT_PACKETS_MODE) ++ | (1 << SET_NAK_OUT_PACKETS) ++ | (1 << CLEAR_EP_HIDE_STATUS_PHASE) ++ | (1 << CLEAR_INTERRUPT_MODE) ++ | (1 << CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE) ++ | (1 << CLEAR_ENDPOINT_TOGGLE) ++ | (1 << CLEAR_ENDPOINT_HALT) ++ , &ep->regs->ep_rsp); ++ ++ /* scrub most status bits, and flush any fifo state */ ++ writel ( (1 << TIMEOUT) ++ | (1 << USB_STALL_SENT) ++ | (1 << USB_IN_NAK_SENT) ++ | (1 << USB_IN_ACK_RCVD) ++ | (1 << USB_OUT_PING_NAK_SENT) ++ | (1 << USB_OUT_ACK_SENT) ++ | (1 << FIFO_OVERFLOW) ++ | (1 << FIFO_UNDERFLOW) ++ | (1 << FIFO_FLUSH) ++ | (1 << SHORT_PACKET_OUT_DONE_INTERRUPT) ++ | (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT) ++ | (1 << DATA_PACKET_RECEIVED_INTERRUPT) ++ | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT) ++ | (1 << DATA_OUT_PING_TOKEN_INTERRUPT) ++ | (1 << DATA_IN_TOKEN_INTERRUPT) ++ , &ep->regs->ep_stat); ++ ++ /* fifo size is handled separately */ ++} ++ ++static void nuke (struct net2280_ep *); ++ ++static int net2280_disable (struct usb_ep *_ep) ++{ ++ struct net2280_ep *ep; ++ unsigned long flags; ++ ++ ep = container_of (_ep, struct net2280_ep, ep); ++ if (!_ep || !ep->desc || _ep->name == ep0name) ++ return -EINVAL; ++ ++ spin_lock_irqsave (&ep->dev->lock, flags); ++ nuke (ep); ++ ep_reset (ep->dev->regs, ep); ++ ++ VDEBUG (ep->dev, "disabled %s %s\n", ++ ep->dma ? "dma" : "pio", _ep->name); ++ ++ /* synch memory views with the device */ ++ (void) readl (&ep->regs->ep_cfg); ++ ++ if (use_dma && !ep->dma && ep->num >= 1 && ep->num <= 4) ++ ep->dma = &ep->dev->dma [ep->num - 1]; ++ ++ spin_unlock_irqrestore (&ep->dev->lock, flags); ++ return 0; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static struct usb_request * ++net2280_alloc_request (struct usb_ep *_ep, int gfp_flags) ++{ ++ struct net2280_ep *ep; ++ struct net2280_request *req; ++ ++ if (!_ep) ++ return NULL; ++ ep = container_of (_ep, struct net2280_ep, ep); ++ ++ req = kmalloc (sizeof *req, gfp_flags); ++ if (!req) ++ return NULL; ++ ++ memset (req, 0, sizeof *req); ++ req->req.dma = DMA_ADDR_INVALID; ++ INIT_LIST_HEAD (&req->queue); ++ ++ /* this dma descriptor may be swapped with the previous dummy */ ++ if (ep->dma) { ++ struct net2280_dma *td; ++ ++ td = pci_pool_alloc (ep->dev->requests, gfp_flags, ++ &req->td_dma); ++ if (!td) { ++ kfree (req); ++ return NULL; ++ } ++ td->dmacount = 0; /* not VALID */ ++ td->dmaaddr = __constant_cpu_to_le32 (DMA_ADDR_INVALID); ++ td->dmadesc = td->dmaaddr; ++ req->td = td; ++ } ++ return &req->req; ++} ++ ++static void ++net2280_free_request (struct usb_ep *_ep, struct usb_request *_req) ++{ ++ struct net2280_ep *ep; ++ struct net2280_request *req; ++ ++ ep = container_of (_ep, struct net2280_ep, ep); ++ if (!_ep || !_req) ++ return; ++ ++ req = container_of (_req, struct net2280_request, req); ++ WARN_ON (!list_empty (&req->queue)); ++ if (req->td) ++ pci_pool_free (ep->dev->requests, req->td, req->td_dma); ++ kfree (req); ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++#undef USE_KMALLOC ++ ++/* many common platforms have dma-coherent caches, which means that it's ++ * safe to use kmalloc() memory for all i/o buffers without using any ++ * cache flushing calls. (unless you're trying to share cache lines ++ * between dma and non-dma activities, which is a slow idea in any case.) ++ * ++ * other platforms need more care, with 2.5 having a moderately general ++ * solution (which falls down for allocations smaller than one page) ++ * that improves significantly on the 2.4 PCI allocators by removing ++ * the restriction that memory never be freed in_interrupt(). ++ */ ++#if defined(CONFIG_X86) ++#define USE_KMALLOC ++ ++#elif defined(CONFIG_PPC) && !defined(CONFIG_NOT_COHERENT_CACHE) ++#define USE_KMALLOC ++ ++#elif defined(CONFIG_MIPS) && !defined(CONFIG_NONCOHERENT_IO) ++#define USE_KMALLOC ++ ++/* FIXME there are other cases, including an x86-64 one ... */ ++#endif ++ ++/* allocating buffers this way eliminates dma mapping overhead, which ++ * on some platforms will mean eliminating a per-io buffer copy. with ++ * some kinds of system caches, further tweaks may still be needed. ++ */ ++static void * ++net2280_alloc_buffer ( ++ struct usb_ep *_ep, ++ unsigned bytes, ++ dma_addr_t *dma, ++ int gfp_flags ++) ++{ ++ void *retval; ++ struct net2280_ep *ep; ++ ++ ep = container_of (_ep, struct net2280_ep, ep); ++ if (!_ep) ++ return NULL; ++ *dma = DMA_ADDR_INVALID; ++ ++#if defined(USE_KMALLOC) ++ retval = kmalloc(bytes, gfp_flags); ++ if (retval) ++ *dma = virt_to_phys(retval); ++#else ++ if (ep->dma) { ++ /* one problem with this call is that it wastes memory on ++ * typical 1/N page allocations: it allocates 1..N pages. ++ * another is that it always uses GFP_ATOMIC. ++ */ ++#warning Using pci_alloc_consistent even with buffers smaller than a page. ++ retval = pci_alloc_consistent(ep->dev->pdev, bytes, dma); ++ } else ++ retval = kmalloc(bytes, gfp_flags); ++#endif ++ return retval; ++} ++ ++static void ++net2280_free_buffer ( ++ struct usb_ep *_ep, ++ void *buf, ++ dma_addr_t dma, ++ unsigned bytes ++) { ++ /* free memory into the right allocator */ ++#ifndef USE_KMALLOC ++ if (dma != DMA_ADDR_INVALID) { ++ struct net2280_ep *ep; ++ ++ ep = container_of(_ep, struct net2280_ep, ep); ++ if (!_ep) ++ return; ++ /* one problem with this call is that some platforms ++ * don't allow it to be used in_irq(). ++ */ ++ pci_free_consistent(ep->dev->pdev, bytes, buf, dma); ++ } else ++#endif ++ kfree (buf); ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* load a packet into the fifo we use for usb IN transfers. ++ * works for all endpoints. ++ * ++ * NOTE: pio with ep-a..ep-d could stuff multiple packets into the fifo ++ * at a time, but this code is simpler because it knows it only writes ++ * one packet. ep-a..ep-d should use dma instead. ++ */ ++static void ++write_fifo (struct net2280_ep *ep, struct usb_request *req) ++{ ++ struct net2280_ep_regs *regs = ep->regs; ++ u8 *buf; ++ u32 tmp; ++ unsigned count, total; ++ ++ /* INVARIANT: fifo is currently empty. (testable) */ ++ ++ if (req) { ++ buf = req->buf + req->actual; ++ prefetch (buf); ++ total = req->length - req->actual; ++ } else { ++ total = 0; ++ buf = NULL; ++ } ++ ++ /* write just one packet at a time */ ++ count = ep->ep.maxpacket; ++ if (count > total) /* min() cannot be used on a bitfield */ ++ count = total; ++ ++ VDEBUG (ep->dev, "write %s fifo (IN) %d bytes%s req %p\n", ++ ep->ep.name, count, ++ (count != ep->ep.maxpacket) ? " (short)" : "", ++ req); ++ while (count >= 4) { ++ /* NOTE be careful if you try to align these. fifo lines ++ * should normally be full (4 bytes) and successive partial ++ * lines are ok only in certain cases. ++ */ ++ tmp = get_unaligned ((u32 *)buf); ++ cpu_to_le32s (&tmp); ++ writel (tmp, ®s->ep_data); ++ buf += 4; ++ count -= 4; ++ } ++ ++ /* last fifo entry is "short" unless we wrote a full packet. ++ * also explicitly validate last word in (periodic) transfers ++ * when maxpacket is not a multiple of 4 bytes. ++ */ ++ if (count || total < ep->ep.maxpacket) { ++ tmp = count ? get_unaligned ((u32 *)buf) : count; ++ cpu_to_le32s (&tmp); ++ set_fifo_bytecount (ep, count & 0x03); ++ writel (tmp, ®s->ep_data); ++ } ++ ++ /* pci writes may still be posted */ ++} ++ ++/* work around erratum 0106: PCI and USB race over the OUT fifo. ++ * caller guarantees chiprev 0100, out endpoint is NAKing, and ++ * there's no real data in the fifo. ++ * ++ * NOTE: also used in cases where that erratum doesn't apply: ++ * where the host wrote "too much" data to us. ++ */ ++static void out_flush (struct net2280_ep *ep) ++{ ++ u32 *statp, tmp; ++ ++ ASSERT_OUT_NAKING (ep); ++ ++ statp = &ep->regs->ep_stat; ++ writel ( (1 << DATA_OUT_PING_TOKEN_INTERRUPT) ++ | (1 << DATA_PACKET_RECEIVED_INTERRUPT) ++ , statp); ++ writel ((1 << FIFO_FLUSH), statp); ++ mb (); ++ tmp = readl (statp); ++ if (tmp & (1 << DATA_OUT_PING_TOKEN_INTERRUPT) ++ /* high speed did bulk NYET; fifo isn't filling */ ++ && ep->dev->gadget.speed == USB_SPEED_FULL) { ++ unsigned usec; ++ ++ usec = 50; /* 64 byte bulk/interrupt */ ++ handshake (statp, (1 << USB_OUT_PING_NAK_SENT), ++ (1 << USB_OUT_PING_NAK_SENT), usec); ++ /* NAK done; now CLEAR_NAK_OUT_PACKETS is safe */ ++ } ++} ++ ++/* unload packet(s) from the fifo we use for usb OUT transfers. ++ * returns true iff the request completed, because of short packet ++ * or the request buffer having filled with full packets. ++ * ++ * for ep-a..ep-d this will read multiple packets out when they ++ * have been accepted. ++ */ ++static int ++read_fifo (struct net2280_ep *ep, struct net2280_request *req) ++{ ++ struct net2280_ep_regs *regs = ep->regs; ++ u8 *buf = req->req.buf + req->req.actual; ++ unsigned count, tmp, is_short; ++ unsigned cleanup = 0, prevent = 0; ++ ++ /* erratum 0106 ... packets coming in during fifo reads might ++ * be incompletely rejected. not all cases have workarounds. ++ */ ++ if (ep->dev->chiprev == 0x0100 ++ && ep->dev->gadget.speed == USB_SPEED_FULL) { ++ udelay (1); ++ tmp = readl (&ep->regs->ep_stat); ++ if ((tmp & (1 << NAK_OUT_PACKETS))) ++ cleanup = 1; ++ else if ((tmp & (1 << FIFO_FULL))) { ++ start_out_naking (ep); ++ prevent = 1; ++ } ++ /* else: hope we don't see the problem */ ++ } ++ ++ /* never overflow the rx buffer. the fifo reads packets until ++ * it sees a short one; we might not be ready for them all. ++ */ ++ prefetchw (buf); ++ count = readl (®s->ep_avail); ++ if (unlikely (count == 0)) { ++ udelay (1); ++ tmp = readl (&ep->regs->ep_stat); ++ count = readl (®s->ep_avail); ++ /* handled that data already? */ ++ if (count == 0 && (tmp & (1 << NAK_OUT_PACKETS)) == 0) ++ return 0; ++ } ++ ++ tmp = req->req.length - req->req.actual; ++ if (count > tmp) { ++ /* as with DMA, data overflow gets flushed */ ++ if ((tmp % ep->ep.maxpacket) != 0) { ++ ERROR (ep->dev, ++ "%s out fifo %d bytes, expected %d\n", ++ ep->ep.name, count, tmp); ++ req->req.status = -EOVERFLOW; ++ cleanup = 1; ++ /* NAK_OUT_PACKETS will be set, so flushing is safe; ++ * the next read will start with the next packet ++ */ ++ } /* else it's a ZLP, no worries */ ++ count = tmp; ++ } ++ req->req.actual += count; ++ ++ is_short = (count == 0) || ((count % ep->ep.maxpacket) != 0); ++ ++ VDEBUG (ep->dev, "read %s fifo (OUT) %d bytes%s%s%s req %p %d/%d\n", ++ ep->ep.name, count, is_short ? " (short)" : "", ++ cleanup ? " flush" : "", prevent ? " nak" : "", ++ req, req->req.actual, req->req.length); ++ ++ while (count >= 4) { ++ tmp = readl (®s->ep_data); ++ cpu_to_le32s (&tmp); ++ put_unaligned (tmp, (u32 *)buf); ++ buf += 4; ++ count -= 4; ++ } ++ if (count) { ++ tmp = readl (®s->ep_data); ++ /* LE conversion is implicit here: */ ++ do { ++ *buf++ = (u8) tmp; ++ tmp >>= 8; ++ } while (--count); ++ } ++ if (cleanup) ++ out_flush (ep); ++ if (prevent) { ++ writel ((1 << CLEAR_NAK_OUT_PACKETS), &ep->regs->ep_rsp); ++ (void) readl (&ep->regs->ep_rsp); ++ } ++ ++ return is_short || ((req->req.actual == req->req.length) ++ && !req->req.zero); ++} ++ ++/* fill out dma descriptor to match a given request */ ++static void ++fill_dma_desc (struct net2280_ep *ep, struct net2280_request *req, int valid) ++{ ++ struct net2280_dma *td = req->td; ++ u32 dmacount = req->req.length; ++ ++ /* don't let DMA continue after a short OUT packet, ++ * so overruns can't affect the next transfer. ++ * in case of overruns on max-size packets, we can't ++ * stop the fifo from filling but we can flush it. ++ */ ++ if (ep->is_in) ++ dmacount |= (1 << DMA_DIRECTION); ++ else if ((dmacount % ep->ep.maxpacket) != 0) ++ dmacount |= (1 << END_OF_CHAIN); ++ ++ req->valid = valid; ++ if (valid) ++ dmacount |= (1 << VALID_BIT); ++ if (likely(!req->req.no_interrupt || !use_dma_chaining)) ++ dmacount |= (1 << DMA_DONE_INTERRUPT_ENABLE); ++ ++ /* td->dmadesc = previously set by caller */ ++ td->dmaaddr = cpu_to_le32p (&req->req.dma); ++ ++ /* 2280 may be polling VALID_BIT through ep->dma->dmadesc */ ++ wmb (); ++ td->dmacount = cpu_to_le32p (&dmacount); ++} ++ ++static const u32 dmactl_default = ++ (1 << DMA_SCATTER_GATHER_DONE_INTERRUPT) ++ | (1 << DMA_CLEAR_COUNT_ENABLE) ++ /* erratum 0116 workaround part 1 (use POLLING) */ ++ | (POLL_100_USEC << DESCRIPTOR_POLLING_RATE) ++ | (1 << DMA_VALID_BIT_POLLING_ENABLE) ++ | (1 << DMA_VALID_BIT_ENABLE) ++ | (1 << DMA_SCATTER_GATHER_ENABLE) ++ /* erratum 0116 workaround part 2 (no AUTOSTART) */ ++ | (1 << DMA_ENABLE); ++ ++static inline void spin_stop_dma (struct net2280_dma_regs *dma) ++{ ++ handshake (&dma->dmactl, (1 << DMA_ENABLE), 0, 50); ++} ++ ++static inline void stop_dma (struct net2280_dma_regs *dma) ++{ ++ writel (readl (&dma->dmactl) & ~(1 << DMA_ENABLE), &dma->dmactl); ++ spin_stop_dma (dma); ++} ++ ++static void start_queue (struct net2280_ep *ep, u32 dmactl, u32 td_dma) ++{ ++ struct net2280_dma_regs *dma = ep->dma; ++ ++ writel ((1 << VALID_BIT) | (ep->is_in << DMA_DIRECTION), ++ &dma->dmacount); ++ writel (readl (&dma->dmastat), &dma->dmastat); ++ ++ writel (td_dma, &dma->dmadesc); ++ writel (dmactl, &dma->dmactl); ++ ++ /* erratum 0116 workaround part 3: pci arbiter away from net2280 */ ++ (void) readl (&ep->dev->pci->pcimstctl); ++ ++ writel ((1 << DMA_START), &dma->dmastat); ++ ++ if (!ep->is_in) ++ stop_out_naking (ep); ++} ++ ++static void start_dma (struct net2280_ep *ep, struct net2280_request *req) ++{ ++ u32 tmp; ++ struct net2280_dma_regs *dma = ep->dma; ++ ++ /* FIXME can't use DMA for ZLPs */ ++ ++ /* on this path we "know" there's no dma active (yet) */ ++ WARN_ON (readl (&dma->dmactl) & (1 << DMA_ENABLE)); ++ writel (0, &ep->dma->dmactl); ++ ++ /* previous OUT packet might have been short */ ++ if (!ep->is_in && ((tmp = readl (&ep->regs->ep_stat)) ++ & (1 << NAK_OUT_PACKETS)) != 0) { ++ writel ((1 << SHORT_PACKET_TRANSFERRED_INTERRUPT), ++ &ep->regs->ep_stat); ++ ++ tmp = readl (&ep->regs->ep_avail); ++ if (tmp) { ++ writel (readl (&dma->dmastat), &dma->dmastat); ++ ++ /* transfer all/some fifo data */ ++ writel (req->req.dma, &dma->dmaaddr); ++ tmp = min (tmp, req->req.length); ++ ++ /* dma irq, faking scatterlist status */ ++ req->td->dmacount = cpu_to_le32 (req->req.length - tmp); ++ writel ((1 << DMA_DONE_INTERRUPT_ENABLE) ++ | tmp, &dma->dmacount); ++ req->td->dmadesc = 0; ++ req->valid = 1; ++ ++ writel ((1 << DMA_ENABLE), &dma->dmactl); ++ writel ((1 << DMA_START), &dma->dmastat); ++ return; ++ } ++ } ++ ++ tmp = dmactl_default; ++ ++ /* force packet boundaries between dma requests, but prevent the ++ * controller from automagically writing a last "short" packet ++ * (zero length) unless the driver explicitly said to do that. ++ */ ++ if (ep->is_in) { ++ if (likely ((req->req.length % ep->ep.maxpacket) != 0 ++ || req->req.zero)) { ++ tmp |= (1 << DMA_FIFO_VALIDATE); ++ ep->in_fifo_validate = 1; ++ } else ++ ep->in_fifo_validate = 0; ++ } ++ ++ /* init req->td, pointing to the current dummy */ ++ req->td->dmadesc = cpu_to_le32 (ep->td_dma); ++ fill_dma_desc (ep, req, 1); ++ ++ if (!use_dma_chaining) ++ req->td->dmacount |= __constant_cpu_to_le32 (1 << END_OF_CHAIN); ++ ++ start_queue (ep, tmp, req->td_dma); ++} ++ ++static inline void ++queue_dma (struct net2280_ep *ep, struct net2280_request *req, int valid) ++{ ++ struct net2280_dma *end; ++ dma_addr_t tmp; ++ ++ /* swap new dummy for old, link; fill and maybe activate */ ++ end = ep->dummy; ++ ep->dummy = req->td; ++ req->td = end; ++ ++ tmp = ep->td_dma; ++ ep->td_dma = req->td_dma; ++ req->td_dma = tmp; ++ ++ end->dmadesc = cpu_to_le32 (ep->td_dma); ++ ++ fill_dma_desc (ep, req, valid); ++} ++ ++static void ++done (struct net2280_ep *ep, struct net2280_request *req, int status) ++{ ++ struct net2280 *dev; ++ unsigned stopped = ep->stopped; ++ ++ list_del_init (&req->queue); ++ ++ if (req->req.status == -EINPROGRESS) ++ req->req.status = status; ++ else ++ status = req->req.status; ++ ++ dev = ep->dev; ++ if (req->mapped) { ++ pci_unmap_single (dev->pdev, req->req.dma, req->req.length, ++ ep->is_in ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); ++ req->req.dma = DMA_ADDR_INVALID; ++ req->mapped = 0; ++ } ++ ++ if (status && status != -ESHUTDOWN) ++ VDEBUG (dev, "complete %s req %p stat %d len %u/%u\n", ++ ep->ep.name, &req->req, status, ++ req->req.actual, req->req.length); ++ ++ /* don't modify queue heads during completion callback */ ++ ep->stopped = 1; ++ spin_unlock (&dev->lock); ++ req->req.complete (&ep->ep, &req->req); ++ spin_lock (&dev->lock); ++ ep->stopped = stopped; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int ++net2280_queue (struct usb_ep *_ep, struct usb_request *_req, int gfp_flags) ++{ ++ struct net2280_request *req; ++ struct net2280_ep *ep; ++ struct net2280 *dev; ++ unsigned long flags; ++ ++ /* we always require a cpu-view buffer, so that we can ++ * always use pio (as fallback or whatever). ++ */ ++ req = container_of (_req, struct net2280_request, req); ++ if (!_req || !_req->complete || !_req->buf ++ || !list_empty (&req->queue)) ++ return -EINVAL; ++ if (_req->length > (~0 & DMA_BYTE_COUNT_MASK)) ++ return -EDOM; ++ ep = container_of (_ep, struct net2280_ep, ep); ++ if (!_ep || (!ep->desc && ep->num != 0)) ++ return -EINVAL; ++ dev = ep->dev; ++ if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) ++ return -ESHUTDOWN; ++ ++ /* FIXME implement PIO fallback for ZLPs with DMA */ ++ if (ep->dma && _req->length == 0) ++ return -EOPNOTSUPP; ++ ++ /* set up dma mapping in case the caller didn't */ ++ if (ep->dma && _req->dma == DMA_ADDR_INVALID) { ++ _req->dma = pci_map_single (dev->pdev, _req->buf, _req->length, ++ ep->is_in ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); ++ req->mapped = 1; ++ } ++ ++#if 0 ++ VDEBUG (dev, "%s queue req %p, len %d buf %p\n", ++ _ep->name, _req, _req->length, _req->buf); ++#endif ++ ++ spin_lock_irqsave (&dev->lock, flags); ++ ++ _req->status = -EINPROGRESS; ++ _req->actual = 0; ++ ++ /* kickstart this i/o queue? */ ++ if (list_empty (&ep->queue) && !ep->stopped) { ++ /* use DMA if the endpoint supports it, else pio */ ++ if (ep->dma) ++ start_dma (ep, req); ++ else { ++ /* maybe there's no control data, just status ack */ ++ if (ep->num == 0 && _req->length == 0) { ++ allow_status (ep); ++ done (ep, req, 0); ++ VDEBUG (dev, "%s status ack\n", ep->ep.name); ++ goto done; ++ } ++ ++ /* PIO ... stuff the fifo, or unblock it. */ ++ if (ep->is_in) ++ write_fifo (ep, _req); ++ else if (list_empty (&ep->queue)) { ++ u32 s; ++ ++ /* OUT FIFO might have packet(s) buffered */ ++ s = readl (&ep->regs->ep_stat); ++ if ((s & (1 << FIFO_EMPTY)) == 0) { ++ /* note: _req->short_not_ok is ++ * ignored here since PIO _always_ ++ * stops queue advance here, and ++ * _req->status doesn't change for ++ * short reads (only _req->actual) ++ */ ++ if (read_fifo (ep, req)) { ++ done (ep, req, 0); ++ if (ep->num == 0) ++ allow_status (ep); ++ /* don't queue it */ ++ req = NULL; ++ } else ++ s = readl (&ep->regs->ep_stat); ++ } ++ ++ /* don't NAK, let the fifo fill */ ++ if (req && (s & (1 << NAK_OUT_PACKETS))) ++ writel ((1 << CLEAR_NAK_OUT_PACKETS), ++ &ep->regs->ep_rsp); ++ } ++ } ++ ++ } else if (ep->dma) { ++ int valid = 1; ++ ++ if (ep->is_in) { ++ int expect; ++ ++ /* preventing magic zlps is per-engine state, not ++ * per-transfer; irq logic must recover hiccups. ++ */ ++ expect = likely (req->req.zero ++ || (req->req.length % ep->ep.maxpacket) != 0); ++ if (expect != ep->in_fifo_validate) ++ valid = 0; ++ } ++ queue_dma (ep, req, valid); ++ ++ } /* else the irq handler advances the queue. */ ++ ++ if (req) ++ list_add_tail (&req->queue, &ep->queue); ++done: ++ spin_unlock_irqrestore (&dev->lock, flags); ++ ++ /* pci writes may still be posted */ ++ return 0; ++} ++ ++static inline void ++dma_done ( ++ struct net2280_ep *ep, ++ struct net2280_request *req, ++ u32 dmacount, ++ int status ++) ++{ ++ req->req.actual = req->req.length - (DMA_BYTE_COUNT_MASK & dmacount); ++ done (ep, req, status); ++} ++ ++static void restart_dma (struct net2280_ep *ep); ++ ++static void scan_dma_completions (struct net2280_ep *ep) ++{ ++ /* only look at descriptors that were "naturally" retired, ++ * so fifo and list head state won't matter ++ */ ++ while (!list_empty (&ep->queue)) { ++ struct net2280_request *req; ++ u32 tmp; ++ ++ req = list_entry (ep->queue.next, ++ struct net2280_request, queue); ++ if (!req->valid) ++ break; ++ rmb (); ++ tmp = le32_to_cpup (&req->td->dmacount); ++ if ((tmp & (1 << VALID_BIT)) != 0) ++ break; ++ ++ /* SHORT_PACKET_TRANSFERRED_INTERRUPT handles "usb-short" ++ * cases where DMA must be aborted; this code handles ++ * all non-abort DMA completions. ++ */ ++ if (unlikely (req->td->dmadesc == 0)) { ++ /* paranoia */ ++ tmp = readl (&ep->dma->dmacount); ++ if (tmp & DMA_BYTE_COUNT_MASK) ++ break; ++ /* single transfer mode */ ++ dma_done (ep, req, tmp, 0); ++ break; ++ } else if (!ep->is_in ++ && (req->req.length % ep->ep.maxpacket) != 0) { ++ tmp = readl (&ep->regs->ep_stat); ++ ++ /* AVOID TROUBLE HERE by not issuing short reads from ++ * your gadget driver. That helps avoids errata 0121, ++ * 0122, and 0124; not all cases trigger the warning. ++ */ ++ if ((tmp & (1 << NAK_OUT_PACKETS)) == 0) { ++ WARN (ep->dev, "%s lost packet sync!\n", ++ ep->ep.name); ++ req->req.status = -EOVERFLOW; ++ } else if ((tmp = readl (&ep->regs->ep_avail)) != 0) { ++ /* fifo gets flushed later */ ++ ep->out_overflow = 1; ++ DEBUG (ep->dev, "%s dma, discard %d len %d\n", ++ ep->ep.name, tmp, ++ req->req.length); ++ req->req.status = -EOVERFLOW; ++ } ++ } ++ dma_done (ep, req, tmp, 0); ++ } ++} ++ ++static void restart_dma (struct net2280_ep *ep) ++{ ++ struct net2280_request *req; ++ u32 dmactl = dmactl_default; ++ ++ if (ep->stopped) ++ return; ++ req = list_entry (ep->queue.next, struct net2280_request, queue); ++ ++ if (!use_dma_chaining) { ++ start_dma (ep, req); ++ return; ++ } ++ ++ /* the 2280 will be processing the queue unless queue hiccups after ++ * the previous transfer: ++ * IN: wanted automagic zlp, head doesn't (or vice versa) ++ * DMA_FIFO_VALIDATE doesn't init from dma descriptors. ++ * OUT: was "usb-short", we must restart. ++ */ ++ if (ep->is_in && !req->valid) { ++ struct net2280_request *entry, *prev = NULL; ++ int reqmode, done = 0; ++ ++ DEBUG (ep->dev, "%s dma hiccup td %p\n", ep->ep.name, req->td); ++ ep->in_fifo_validate = likely (req->req.zero ++ || (req->req.length % ep->ep.maxpacket) != 0); ++ if (ep->in_fifo_validate) ++ dmactl |= (1 << DMA_FIFO_VALIDATE); ++ list_for_each_entry (entry, &ep->queue, queue) { ++ u32 dmacount; ++ ++ if (entry == req) ++ continue; ++ dmacount = entry->td->dmacount; ++ if (!done) { ++ reqmode = likely (entry->req.zero ++ || (entry->req.length ++ % ep->ep.maxpacket) != 0); ++ if (reqmode == ep->in_fifo_validate) { ++ entry->valid = 1; ++ dmacount |= valid_bit; ++ entry->td->dmacount = dmacount; ++ prev = entry; ++ continue; ++ } else { ++ /* force a hiccup */ ++ prev->td->dmacount |= dma_done_ie; ++ done = 1; ++ } ++ } ++ ++ /* walk the rest of the queue so unlinks behave */ ++ entry->valid = 0; ++ dmacount &= ~valid_bit; ++ entry->td->dmacount = dmacount; ++ prev = entry; ++ } ++ } ++ ++ writel (0, &ep->dma->dmactl); ++ start_queue (ep, dmactl, req->td_dma); ++} ++ ++static void abort_dma (struct net2280_ep *ep) ++{ ++ /* abort the current transfer */ ++ if (likely (!list_empty (&ep->queue))) { ++ /* FIXME work around errata 0121, 0122, 0124 */ ++ writel ((1 << DMA_ABORT), &ep->dma->dmastat); ++ spin_stop_dma (ep->dma); ++ } else ++ stop_dma (ep->dma); ++ scan_dma_completions (ep); ++} ++ ++/* dequeue ALL requests */ ++static void nuke (struct net2280_ep *ep) ++{ ++ struct net2280_request *req; ++ ++ /* called with spinlock held */ ++ ep->stopped = 1; ++ if (ep->dma) ++ abort_dma (ep); ++ while (!list_empty (&ep->queue)) { ++ req = list_entry (ep->queue.next, ++ struct net2280_request, ++ queue); ++ done (ep, req, -ESHUTDOWN); ++ } ++} ++ ++/* dequeue JUST ONE request */ ++static int net2280_dequeue (struct usb_ep *_ep, struct usb_request *_req) ++{ ++ struct net2280_ep *ep; ++ struct net2280_request *req; ++ unsigned long flags; ++ u32 dmactl; ++ int stopped; ++ ++ ep = container_of (_ep, struct net2280_ep, ep); ++ if (!_ep || (!ep->desc && ep->num != 0) || !_req) ++ return -EINVAL; ++ ++ spin_lock_irqsave (&ep->dev->lock, flags); ++ stopped = ep->stopped; ++ ++ /* quiesce dma while we patch the queue */ ++ dmactl = 0; ++ ep->stopped = 1; ++ if (ep->dma) { ++ dmactl = readl (&ep->dma->dmactl); ++ /* WARNING erratum 0127 may kick in ... */ ++ stop_dma (ep->dma); ++ scan_dma_completions (ep); ++ } ++ ++ /* make sure it's still queued on this endpoint */ ++ list_for_each_entry (req, &ep->queue, queue) { ++ if (&req->req == _req) ++ break; ++ } ++ if (&req->req != _req) { ++ spin_unlock_irqrestore (&ep->dev->lock, flags); ++ return -EINVAL; ++ } ++ ++ /* queue head may be partially complete. */ ++ if (ep->queue.next == &req->queue) { ++ if (ep->dma) { ++ DEBUG (ep->dev, "unlink (%s) dma\n", _ep->name); ++ _req->status = -ECONNRESET; ++ abort_dma (ep); ++ if (likely (ep->queue.next == &req->queue)) { ++ // NOTE: misreports single-transfer mode ++ req->td->dmacount = 0; /* invalidate */ ++ dma_done (ep, req, ++ readl (&ep->dma->dmacount), ++ -ECONNRESET); ++ } ++ } else { ++ DEBUG (ep->dev, "unlink (%s) pio\n", _ep->name); ++ done (ep, req, -ECONNRESET); ++ } ++ req = NULL; ++ ++ /* patch up hardware chaining data */ ++ } else if (ep->dma && use_dma_chaining) { ++ if (req->queue.prev == ep->queue.next) { ++ writel (le32_to_cpu (req->td->dmadesc), ++ &ep->dma->dmadesc); ++ if (req->td->dmacount & dma_done_ie) ++ writel (readl (&ep->dma->dmacount) ++ | dma_done_ie, ++ &ep->dma->dmacount); ++ } else { ++ struct net2280_request *prev; ++ ++ prev = list_entry (req->queue.prev, ++ struct net2280_request, queue); ++ prev->td->dmadesc = req->td->dmadesc; ++ if (req->td->dmacount & dma_done_ie) ++ prev->td->dmacount |= dma_done_ie; ++ } ++ } ++ ++ if (req) ++ done (ep, req, -ECONNRESET); ++ ep->stopped = stopped; ++ ++ if (ep->dma) { ++ /* turn off dma on inactive queues */ ++ if (list_empty (&ep->queue)) ++ stop_dma (ep->dma); ++ else if (!ep->stopped) { ++ /* resume current request, or start new one */ ++ if (req) ++ writel (dmactl, &ep->dma->dmactl); ++ else ++ start_dma (ep, list_entry (ep->queue.next, ++ struct net2280_request, queue)); ++ } ++ } ++ ++ spin_unlock_irqrestore (&ep->dev->lock, flags); ++ return req ? 0 : -EOPNOTSUPP; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int net2280_fifo_status (struct usb_ep *_ep); ++ ++static int ++net2280_set_halt (struct usb_ep *_ep, int value) ++{ ++ struct net2280_ep *ep; ++ unsigned long flags; ++ int retval = 0; ++ ++ ep = container_of (_ep, struct net2280_ep, ep); ++ if (!_ep || (!ep->desc && ep->num != 0)) ++ return -EINVAL; ++ if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) ++ return -ESHUTDOWN; ++ if (ep->desc /* not ep0 */ && (ep->desc->bmAttributes & 0x03) ++ == USB_ENDPOINT_XFER_ISOC) ++ return -EINVAL; ++ ++ spin_lock_irqsave (&ep->dev->lock, flags); ++ if (!list_empty (&ep->queue)) ++ retval = -EAGAIN; ++ else if (ep->is_in && value && net2280_fifo_status (_ep) != 0) ++ retval = -EAGAIN; ++ else { ++ VDEBUG (ep->dev, "%s %s halt\n", _ep->name, ++ value ? "set" : "clear"); ++ /* set/clear, then synch memory views with the device */ ++ if (value) { ++ if (ep->num == 0) ++ ep->dev->protocol_stall = 1; ++ else ++ set_halt (ep); ++ } else ++ clear_halt (ep); ++ (void) readl (&ep->regs->ep_rsp); ++ } ++ spin_unlock_irqrestore (&ep->dev->lock, flags); ++ ++ return retval; ++} ++ ++static int ++net2280_fifo_status (struct usb_ep *_ep) ++{ ++ struct net2280_ep *ep; ++ u32 avail; ++ ++ ep = container_of (_ep, struct net2280_ep, ep); ++ if (!_ep || (!ep->desc && ep->num != 0)) ++ return -ENODEV; ++ if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) ++ return -ESHUTDOWN; ++ ++ avail = readl (&ep->regs->ep_avail) & ((1 << 12) - 1); ++ if (avail > ep->fifo_size) ++ return -EOVERFLOW; ++ if (ep->is_in) ++ avail = ep->fifo_size - avail; ++ return avail; ++} ++ ++static void ++net2280_fifo_flush (struct usb_ep *_ep) ++{ ++ struct net2280_ep *ep; ++ ++ ep = container_of (_ep, struct net2280_ep, ep); ++ if (!_ep || (!ep->desc && ep->num != 0)) ++ return; ++ if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) ++ return; ++ ++ writel ((1 << FIFO_FLUSH), &ep->regs->ep_stat); ++ (void) readl (&ep->regs->ep_rsp); ++} ++ ++static struct usb_ep_ops net2280_ep_ops = { ++ .enable = net2280_enable, ++ .disable = net2280_disable, ++ ++ .alloc_request = net2280_alloc_request, ++ .free_request = net2280_free_request, ++ ++ .alloc_buffer = net2280_alloc_buffer, ++ .free_buffer = net2280_free_buffer, ++ ++ .queue = net2280_queue, ++ .dequeue = net2280_dequeue, ++ ++ .set_halt = net2280_set_halt, ++ .fifo_status = net2280_fifo_status, ++ .fifo_flush = net2280_fifo_flush, ++}; ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int net2280_get_frame (struct usb_gadget *_gadget) ++{ ++ struct net2280 *dev; ++ unsigned long flags; ++ u16 retval; ++ ++ if (!_gadget) ++ return -ENODEV; ++ dev = container_of (_gadget, struct net2280, gadget); ++ spin_lock_irqsave (&dev->lock, flags); ++ retval = get_idx_reg (dev->regs, REG_FRAME) & 0x03ff; ++ spin_unlock_irqrestore (&dev->lock, flags); ++ return retval; ++} ++ ++static int net2280_wakeup (struct usb_gadget *_gadget) ++{ ++ struct net2280 *dev; ++ u32 tmp; ++ unsigned long flags; ++ ++ if (!_gadget) ++ return 0; ++ dev = container_of (_gadget, struct net2280, gadget); ++ ++ spin_lock_irqsave (&dev->lock, flags); ++ tmp = readl (&dev->usb->usbctl); ++ if (tmp & (1 << DEVICE_REMOTE_WAKEUP_ENABLE)) ++ writel (1 << GENERATE_RESUME, &dev->usb->usbstat); ++ spin_unlock_irqrestore (&dev->lock, flags); ++ ++ /* pci writes may still be posted */ ++ return 0; ++} ++ ++static int net2280_set_selfpowered (struct usb_gadget *_gadget, int value) ++{ ++ struct net2280 *dev; ++ u32 tmp; ++ unsigned long flags; ++ ++ if (!_gadget) ++ return 0; ++ dev = container_of (_gadget, struct net2280, gadget); ++ ++ spin_lock_irqsave (&dev->lock, flags); ++ tmp = readl (&dev->usb->usbctl); ++ if (value) ++ tmp |= (1 << SELF_POWERED_STATUS); ++ else ++ tmp &= ~(1 << SELF_POWERED_STATUS); ++ writel (tmp, &dev->usb->usbctl); ++ spin_unlock_irqrestore (&dev->lock, flags); ++ ++ return 0; ++} ++ ++static int net2280_pullup(struct usb_gadget *_gadget, int is_on) ++{ ++ struct net2280 *dev; ++ u32 tmp; ++ unsigned long flags; ++ ++ if (!_gadget) ++ return -ENODEV; ++ dev = container_of (_gadget, struct net2280, gadget); ++ ++ spin_lock_irqsave (&dev->lock, flags); ++ tmp = readl (&dev->usb->usbctl); ++ dev->softconnect = (is_on != 0); ++ if (is_on) ++ tmp |= (1 << USB_DETECT_ENABLE); ++ else ++ tmp &= ~(1 << USB_DETECT_ENABLE); ++ writel (tmp, &dev->usb->usbctl); ++ spin_unlock_irqrestore (&dev->lock, flags); ++ ++ return 0; ++} ++ ++static const struct usb_gadget_ops net2280_ops = { ++ .get_frame = net2280_get_frame, ++ .wakeup = net2280_wakeup, ++ .set_selfpowered = net2280_set_selfpowered, ++ .pullup = net2280_pullup, ++}; ++ ++/*-------------------------------------------------------------------------*/ ++ ++#ifdef USE_SYSFS_DEBUG_FILES ++ ++/* "function" sysfs attribute */ ++static ssize_t ++show_function (struct device *_dev, char *buf) ++{ ++ struct net2280 *dev = dev_get_drvdata (_dev); ++ ++ if (!dev->driver ++ || !dev->driver->function ++ || strlen (dev->driver->function) > PAGE_SIZE) ++ return 0; ++ return snprintf (buf, PAGE_SIZE, "%s\n", dev->driver->function); ++} ++static DEVICE_ATTR (function, S_IRUGO, show_function, NULL); ++ ++static ssize_t ++show_registers (struct device *_dev, char *buf) ++{ ++ struct net2280 *dev; ++ char *next; ++ unsigned size, t; ++ unsigned long flags; ++ int i; ++ u32 t1, t2; ++ char *s; ++ ++ dev = dev_get_drvdata (_dev); ++ next = buf; ++ size = PAGE_SIZE; ++ spin_lock_irqsave (&dev->lock, flags); ++ ++ if (dev->driver) ++ s = dev->driver->driver.name; ++ else ++ s = "(none)"; ++ ++ /* Main Control Registers */ ++ t = snprintf (next, size, "%s version " DRIVER_VERSION ++ ", chiprev %04x, dma %s\n\n" ++ "devinit %03x fifoctl %08x gadget '%s'\n" ++ "pci irqenb0 %02x irqenb1 %08x " ++ "irqstat0 %04x irqstat1 %08x\n", ++ driver_name, dev->chiprev, ++ use_dma ++ ? (use_dma_chaining ? "chaining" : "enabled") ++ : "disabled", ++ readl (&dev->regs->devinit), ++ readl (&dev->regs->fifoctl), ++ s, ++ readl (&dev->regs->pciirqenb0), ++ readl (&dev->regs->pciirqenb1), ++ readl (&dev->regs->irqstat0), ++ readl (&dev->regs->irqstat1)); ++ size -= t; ++ next += t; ++ ++ /* USB Control Registers */ ++ t1 = readl (&dev->usb->usbctl); ++ t2 = readl (&dev->usb->usbstat); ++ if (t1 & (1 << VBUS_PIN)) { ++ if (t2 & (1 << HIGH_SPEED)) ++ s = "high speed"; ++ else if (dev->gadget.speed == USB_SPEED_UNKNOWN) ++ s = "powered"; ++ else ++ s = "full speed"; ++ /* full speed bit (6) not working?? */ ++ } else ++ s = "not attached"; ++ t = snprintf (next, size, ++ "stdrsp %08x usbctl %08x usbstat %08x " ++ "addr 0x%02x (%s)\n", ++ readl (&dev->usb->stdrsp), t1, t2, ++ readl (&dev->usb->ouraddr), s); ++ size -= t; ++ next += t; ++ ++ /* PCI Master Control Registers */ ++ ++ /* DMA Control Registers */ ++ ++ /* Configurable EP Control Registers */ ++ for (i = 0; i < 7; i++) { ++ struct net2280_ep *ep; ++ ++ ep = &dev->ep [i]; ++ if (i && !ep->desc) ++ continue; ++ ++ t1 = readl (&ep->regs->ep_cfg); ++ t2 = readl (&ep->regs->ep_rsp) & 0xff; ++ t = snprintf (next, size, ++ "\n%s\tcfg %05x rsp (%02x) %s%s%s%s%s%s%s%s" ++ "irqenb %02x\n", ++ ep->ep.name, t1, t2, ++ (t2 & (1 << CLEAR_NAK_OUT_PACKETS)) ++ ? "NAK " : "", ++ (t2 & (1 << CLEAR_EP_HIDE_STATUS_PHASE)) ++ ? "hide " : "", ++ (t2 & (1 << CLEAR_EP_FORCE_CRC_ERROR)) ++ ? "CRC " : "", ++ (t2 & (1 << CLEAR_INTERRUPT_MODE)) ++ ? "interrupt " : "", ++ (t2 & (1<regs->ep_irqenb)); ++ size -= t; ++ next += t; ++ ++ t = snprintf (next, size, ++ "\tstat %08x avail %04x " ++ "(ep%d%s-%s)%s\n", ++ readl (&ep->regs->ep_stat), ++ readl (&ep->regs->ep_avail), ++ t1 & 0x0f, DIR_STRING (t1), ++ type_string (t1 >> 8), ++ ep->stopped ? "*" : ""); ++ size -= t; ++ next += t; ++ ++ if (!ep->dma) ++ continue; ++ ++ t = snprintf (next, size, ++ " dma\tctl %08x stat %08x count %08x\n" ++ "\taddr %08x desc %08x\n", ++ readl (&ep->dma->dmactl), ++ readl (&ep->dma->dmastat), ++ readl (&ep->dma->dmacount), ++ readl (&ep->dma->dmaaddr), ++ readl (&ep->dma->dmadesc)); ++ size -= t; ++ next += t; ++ ++ } ++ ++ /* Indexed Registers */ ++ // none yet ++ ++ /* Statistics */ ++ t = snprintf (next, size, "\nirqs: "); ++ size -= t; ++ next += t; ++ for (i = 0; i < 7; i++) { ++ struct net2280_ep *ep; ++ ++ ep = &dev->ep [i]; ++ if (i && !ep->irqs) ++ continue; ++ t = snprintf (next, size, " %s/%lu", ep->ep.name, ep->irqs); ++ size -= t; ++ next += t; ++ ++ } ++ t = snprintf (next, size, "\n"); ++ size -= t; ++ next += t; ++ ++ spin_unlock_irqrestore (&dev->lock, flags); ++ ++ return PAGE_SIZE - size; ++} ++static DEVICE_ATTR (registers, S_IRUGO, show_registers, NULL); ++ ++static ssize_t ++show_queues (struct device *_dev, char *buf) ++{ ++ struct net2280 *dev; ++ char *next; ++ unsigned size; ++ unsigned long flags; ++ int i; ++ ++ dev = dev_get_drvdata (_dev); ++ next = buf; ++ size = PAGE_SIZE; ++ spin_lock_irqsave (&dev->lock, flags); ++ ++ for (i = 0; i < 7; i++) { ++ struct net2280_ep *ep = &dev->ep [i]; ++ struct net2280_request *req; ++ int t; ++ ++ if (i != 0) { ++ const struct usb_endpoint_descriptor *d; ++ ++ d = ep->desc; ++ if (!d) ++ continue; ++ t = d->bEndpointAddress; ++ t = snprintf (next, size, ++ "\n%s (ep%d%s-%s) max %04x %s fifo %d\n", ++ ep->ep.name, t & USB_ENDPOINT_NUMBER_MASK, ++ (t & USB_DIR_IN) ? "in" : "out", ++ ({ char *val; ++ switch (d->bmAttributes & 0x03) { ++ case USB_ENDPOINT_XFER_BULK: ++ val = "bulk"; break; ++ case USB_ENDPOINT_XFER_INT: ++ val = "intr"; break; ++ default: ++ val = "iso"; break; ++ }; val; }), ++ le16_to_cpu (d->wMaxPacketSize) & 0x1fff, ++ ep->dma ? "dma" : "pio", ep->fifo_size ++ ); ++ } else /* ep0 should only have one transfer queued */ ++ t = snprintf (next, size, "ep0 max 64 pio %s\n", ++ ep->is_in ? "in" : "out"); ++ if (t <= 0 || t > size) ++ goto done; ++ size -= t; ++ next += t; ++ ++ if (list_empty (&ep->queue)) { ++ t = snprintf (next, size, "\t(nothing queued)\n"); ++ if (t <= 0 || t > size) ++ goto done; ++ size -= t; ++ next += t; ++ continue; ++ } ++ list_for_each_entry (req, &ep->queue, queue) { ++ if (ep->dma && req->td_dma == readl (&ep->dma->dmadesc)) ++ t = snprintf (next, size, ++ "\treq %p len %d/%d " ++ "buf %p (dmacount %08x)\n", ++ &req->req, req->req.actual, ++ req->req.length, req->req.buf, ++ readl (&ep->dma->dmacount)); ++ else ++ t = snprintf (next, size, ++ "\treq %p len %d/%d buf %p\n", ++ &req->req, req->req.actual, ++ req->req.length, req->req.buf); ++ if (t <= 0 || t > size) ++ goto done; ++ size -= t; ++ next += t; ++ ++ if (ep->dma) { ++ struct net2280_dma *td; ++ ++ td = req->td; ++ t = snprintf (next, size, "\t td %08x " ++ " count %08x buf %08x desc %08x\n", ++ req->td_dma, td->dmacount, ++ td->dmaaddr, td->dmadesc); ++ if (t <= 0 || t > size) ++ goto done; ++ size -= t; ++ next += t; ++ } ++ } ++ } ++ ++done: ++ spin_unlock_irqrestore (&dev->lock, flags); ++ return PAGE_SIZE - size; ++} ++static DEVICE_ATTR (queues, S_IRUGO, show_queues, NULL); ++ ++ ++#else ++ ++#define device_create_file(a,b) do {} while (0) ++#define device_remove_file device_create_file ++ ++#endif ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* another driver-specific mode might be a request type doing dma ++ * to/from another device fifo instead of to/from memory. ++ */ ++ ++static void set_fifo_mode (struct net2280 *dev, int mode) ++{ ++ /* keeping high bits preserves BAR2 */ ++ writel ((0xffff << PCI_BASE2_RANGE) | mode, &dev->regs->fifoctl); ++ ++ /* always ep-{a,b,e,f} ... maybe not ep-c or ep-d */ ++ INIT_LIST_HEAD (&dev->gadget.ep_list); ++ list_add_tail (&dev->ep [1].ep.ep_list, &dev->gadget.ep_list); ++ list_add_tail (&dev->ep [2].ep.ep_list, &dev->gadget.ep_list); ++ switch (mode) { ++ case 0: ++ list_add_tail (&dev->ep [3].ep.ep_list, &dev->gadget.ep_list); ++ list_add_tail (&dev->ep [4].ep.ep_list, &dev->gadget.ep_list); ++ dev->ep [1].fifo_size = dev->ep [2].fifo_size = 1024; ++ break; ++ case 1: ++ dev->ep [1].fifo_size = dev->ep [2].fifo_size = 2048; ++ break; ++ case 2: ++ list_add_tail (&dev->ep [3].ep.ep_list, &dev->gadget.ep_list); ++ dev->ep [1].fifo_size = 2048; ++ dev->ep [2].fifo_size = 1024; ++ break; ++ } ++ /* fifo sizes for ep0, ep-c, ep-d, ep-e, and ep-f never change */ ++ list_add_tail (&dev->ep [5].ep.ep_list, &dev->gadget.ep_list); ++ list_add_tail (&dev->ep [6].ep.ep_list, &dev->gadget.ep_list); ++} ++ ++/** ++ * net2280_set_fifo_mode - change allocation of fifo buffers ++ * @gadget: access to the net2280 device that will be updated ++ * @mode: 0 for default, four 1kB buffers (ep-a through ep-d); ++ * 1 for two 2kB buffers (ep-a and ep-b only); ++ * 2 for one 2kB buffer (ep-a) and two 1kB ones (ep-b, ep-c). ++ * ++ * returns zero on success, else negative errno. when this succeeds, ++ * the contents of gadget->ep_list may have changed. ++ * ++ * you may only call this function when endpoints a-d are all disabled. ++ * use it whenever extra hardware buffering can help performance, such ++ * as before enabling "high bandwidth" interrupt endpoints that use ++ * maxpacket bigger than 512 (when double buffering would otherwise ++ * be unavailable). ++ */ ++int net2280_set_fifo_mode (struct usb_gadget *gadget, int mode) ++{ ++ int i; ++ struct net2280 *dev; ++ int status = 0; ++ unsigned long flags; ++ ++ if (!gadget) ++ return -ENODEV; ++ dev = container_of (gadget, struct net2280, gadget); ++ ++ spin_lock_irqsave (&dev->lock, flags); ++ ++ for (i = 1; i <= 4; i++) ++ if (dev->ep [i].desc) { ++ status = -EINVAL; ++ break; ++ } ++ if (mode < 0 || mode > 2) ++ status = -EINVAL; ++ if (status == 0) ++ set_fifo_mode (dev, mode); ++ spin_unlock_irqrestore (&dev->lock, flags); ++ ++ if (status == 0) { ++ if (mode == 1) ++ DEBUG (dev, "fifo: ep-a 2K, ep-b 2K\n"); ++ else if (mode == 2) ++ DEBUG (dev, "fifo: ep-a 2K, ep-b 1K, ep-c 1K\n"); ++ /* else all are 1K */ ++ } ++ return status; ++} ++EXPORT_SYMBOL (net2280_set_fifo_mode); ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* keeping it simple: ++ * - one bus driver, initted first; ++ * - one function driver, initted second ++ * ++ * most of the work to support multiple net2280 controllers would ++ * be to associate this gadget driver (yes?) with all of them, or ++ * perhaps to bind specific drivers to specific devices. ++ */ ++ ++static struct net2280 *the_controller; ++ ++static void usb_reset (struct net2280 *dev) ++{ ++ u32 tmp; ++ ++ dev->gadget.speed = USB_SPEED_UNKNOWN; ++ (void) readl (&dev->usb->usbctl); ++ ++ net2280_led_init (dev); ++ ++ /* disable automatic responses, and irqs */ ++ writel (0, &dev->usb->stdrsp); ++ writel (0, &dev->regs->pciirqenb0); ++ writel (0, &dev->regs->pciirqenb1); ++ ++ /* clear old dma and irq state */ ++ for (tmp = 0; tmp < 4; tmp++) { ++ struct net2280_ep *ep = &dev->ep [tmp + 1]; ++ ++ if (ep->dma) ++ abort_dma (ep); ++ } ++ writel (~0, &dev->regs->irqstat0), ++ writel (~(1 << SUSPEND_REQUEST_INTERRUPT), &dev->regs->irqstat1), ++ ++ /* reset, and enable pci */ ++ tmp = readl (&dev->regs->devinit) ++ | (1 << PCI_ENABLE) ++ | (1 << FIFO_SOFT_RESET) ++ | (1 << USB_SOFT_RESET) ++ | (1 << M8051_RESET); ++ writel (tmp, &dev->regs->devinit); ++ ++ /* standard fifo and endpoint allocations */ ++ set_fifo_mode (dev, (fifo_mode <= 2) ? fifo_mode : 0); ++} ++ ++static void usb_reinit (struct net2280 *dev) ++{ ++ u32 tmp; ++ int init_dma; ++ ++ /* use_dma changes are ignored till next device re-init */ ++ init_dma = use_dma; ++ ++ /* basic endpoint init */ ++ for (tmp = 0; tmp < 7; tmp++) { ++ struct net2280_ep *ep = &dev->ep [tmp]; ++ ++ ep->ep.name = ep_name [tmp]; ++ ep->dev = dev; ++ ep->num = tmp; ++ ++ if (tmp > 0 && tmp <= 4) { ++ ep->fifo_size = 1024; ++ if (init_dma) ++ ep->dma = &dev->dma [tmp - 1]; ++ } else ++ ep->fifo_size = 64; ++ ep->regs = &dev->epregs [tmp]; ++ ep_reset (dev->regs, ep); ++ } ++ dev->ep [0].ep.maxpacket = 64; ++ dev->ep [5].ep.maxpacket = 64; ++ dev->ep [6].ep.maxpacket = 64; ++ ++ dev->gadget.ep0 = &dev->ep [0].ep; ++ dev->ep [0].stopped = 0; ++ INIT_LIST_HEAD (&dev->gadget.ep0->ep_list); ++ ++ /* we want to prevent lowlevel/insecure access from the USB host, ++ * but erratum 0119 means this enable bit is ignored ++ */ ++ for (tmp = 0; tmp < 5; tmp++) ++ writel (EP_DONTUSE, &dev->dep [tmp].dep_cfg); ++} ++ ++static void ep0_start (struct net2280 *dev) ++{ ++ writel ( (1 << CLEAR_EP_HIDE_STATUS_PHASE) ++ | (1 << CLEAR_NAK_OUT_PACKETS) ++ | (1 << CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE) ++ , &dev->epregs [0].ep_rsp); ++ ++ /* ++ * hardware optionally handles a bunch of standard requests ++ * that the API hides from drivers anyway. have it do so. ++ * endpoint status/features are handled in software, to ++ * help pass tests for some dubious behavior. ++ */ ++ writel ( (1 << SET_TEST_MODE) ++ | (1 << SET_ADDRESS) ++ | (1 << DEVICE_SET_CLEAR_DEVICE_REMOTE_WAKEUP) ++ | (1 << GET_DEVICE_STATUS) ++ | (1 << GET_INTERFACE_STATUS) ++ , &dev->usb->stdrsp); ++ writel ( (1 << USB_ROOT_PORT_WAKEUP_ENABLE) ++ | (1 << SELF_POWERED_USB_DEVICE) ++ /* erratum 0102 workaround */ ++ | ((dev->chiprev == 0100) ? 0 : 1) << SUSPEND_IMMEDIATELY ++ | (1 << REMOTE_WAKEUP_SUPPORT) ++ | (dev->softconnect << USB_DETECT_ENABLE) ++ | (1 << SELF_POWERED_STATUS) ++ , &dev->usb->usbctl); ++ ++ /* enable irqs so we can see ep0 and general operation */ ++ writel ( (1 << SETUP_PACKET_INTERRUPT_ENABLE) ++ | (1 << ENDPOINT_0_INTERRUPT_ENABLE) ++ , &dev->regs->pciirqenb0); ++ writel ( (1 << PCI_INTERRUPT_ENABLE) ++ | (1 << PCI_MASTER_ABORT_RECEIVED_INTERRUPT_ENABLE) ++ | (1 << PCI_TARGET_ABORT_RECEIVED_INTERRUPT_ENABLE) ++ | (1 << PCI_RETRY_ABORT_INTERRUPT_ENABLE) ++ | (1 << VBUS_INTERRUPT_ENABLE) ++ | (1 << ROOT_PORT_RESET_INTERRUPT_ENABLE) ++ | (1 << SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE) ++ , &dev->regs->pciirqenb1); ++ ++ /* don't leave any writes posted */ ++ (void) readl (&dev->usb->usbctl); ++} ++ ++/* when a driver is successfully registered, it will receive ++ * control requests including set_configuration(), which enables ++ * non-control requests. then usb traffic follows until a ++ * disconnect is reported. then a host may connect again, or ++ * the driver might get unbound. ++ */ ++int usb_gadget_register_driver (struct usb_gadget_driver *driver) ++{ ++ struct net2280 *dev = the_controller; ++ int retval; ++ unsigned i; ++ ++ /* insist on high speed support from the driver, since ++ * (dev->usb->xcvrdiag & FORCE_FULL_SPEED_MODE) ++ * "must not be used in normal operation" ++ */ ++ if (!driver ++ || driver->speed != USB_SPEED_HIGH ++ || !driver->bind ++ || !driver->unbind ++ || !driver->setup) ++ return -EINVAL; ++ if (!dev) ++ return -ENODEV; ++ if (dev->driver) ++ return -EBUSY; ++ ++ for (i = 0; i < 7; i++) ++ dev->ep [i].irqs = 0; ++ ++ /* hook up the driver ... */ ++ dev->softconnect = 1; ++ dev->driver = driver; ++ retval = driver->bind (&dev->gadget); ++ if (retval) { ++ DEBUG (dev, "bind to driver %s --> %d\n", ++ driver->driver.name, retval); ++ dev->driver = 0; ++ return retval; ++ } ++ ++ /* ... then enable host detection and ep0; and we're ready ++ * for set_configuration as well as eventual disconnect. ++ */ ++ net2280_led_active (dev, 1); ++ ep0_start (dev); ++ ++ DEBUG (dev, "%s ready, usbctl %08x stdrsp %08x\n", ++ driver->driver.name, ++ readl (&dev->usb->usbctl), ++ readl (&dev->usb->stdrsp)); ++ ++ /* pci writes may still be posted */ ++ return 0; ++} ++EXPORT_SYMBOL (usb_gadget_register_driver); ++ ++static void ++stop_activity (struct net2280 *dev, struct usb_gadget_driver *driver) ++{ ++ int i; ++ ++ /* don't disconnect if it's not connected */ ++ if (dev->gadget.speed == USB_SPEED_UNKNOWN) ++ driver = NULL; ++ ++ /* stop hardware; prevent new request submissions; ++ * and kill any outstanding requests. ++ */ ++ usb_reset (dev); ++ for (i = 0; i < 7; i++) ++ nuke (&dev->ep [i]); ++ ++ /* report disconnect; the driver is already quiesced */ ++ if (driver) { ++ spin_unlock (&dev->lock); ++ driver->disconnect (&dev->gadget); ++ spin_lock (&dev->lock); ++ } ++ ++ usb_reinit (dev); ++} ++ ++int usb_gadget_unregister_driver (struct usb_gadget_driver *driver) ++{ ++ struct net2280 *dev = the_controller; ++ unsigned long flags; ++ ++ if (!dev) ++ return -ENODEV; ++ if (!driver || driver != dev->driver) ++ return -EINVAL; ++ ++ spin_lock_irqsave (&dev->lock, flags); ++ stop_activity (dev, driver); ++ spin_unlock_irqrestore (&dev->lock, flags); ++ ++ driver->unbind (&dev->gadget); ++ dev->driver = 0; ++ ++ net2280_led_active (dev, 0); ++ ++ DEBUG (dev, "unregistered driver '%s'\n", driver->driver.name); ++ return 0; ++} ++EXPORT_SYMBOL (usb_gadget_unregister_driver); ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* handle ep0, ep-e, ep-f with 64 byte packets: packet per irq. ++ * also works for dma-capable endpoints, in pio mode or just ++ * to manually advance the queue after short OUT transfers. ++ */ ++static void handle_ep_small (struct net2280_ep *ep) ++{ ++ struct net2280_request *req; ++ u32 t; ++ /* 0 error, 1 mid-data, 2 done */ ++ int mode = 1; ++ ++ if (!list_empty (&ep->queue)) ++ req = list_entry (ep->queue.next, ++ struct net2280_request, queue); ++ else ++ req = NULL; ++ ++ /* ack all, and handle what we care about */ ++ t = readl (&ep->regs->ep_stat); ++ ep->irqs++; ++#if 0 ++ VDEBUG (ep->dev, "%s ack ep_stat %08x, req %p\n", ++ ep->ep.name, t, req ? &req->req : 0); ++#endif ++ writel (t & ~(1 << NAK_OUT_PACKETS), &ep->regs->ep_stat); ++ ++ /* for ep0, monitor token irqs to catch data stage length errors ++ * and to synchronize on status. ++ * ++ * also, to defer reporting of protocol stalls ... here's where ++ * data or status first appears, handling stalls here should never ++ * cause trouble on the host side.. ++ * ++ * control requests could be slightly faster without token synch for ++ * status, but status can jam up that way. ++ */ ++ if (unlikely (ep->num == 0)) { ++ if (ep->is_in) { ++ /* status; stop NAKing */ ++ if (t & (1 << DATA_OUT_PING_TOKEN_INTERRUPT)) { ++ if (ep->dev->protocol_stall) { ++ ep->stopped = 1; ++ set_halt (ep); ++ } ++ if (!req) ++ allow_status (ep); ++ mode = 2; ++ /* reply to extra IN data tokens with a zlp */ ++ } else if (t & (1 << DATA_IN_TOKEN_INTERRUPT)) { ++ if (ep->dev->protocol_stall) { ++ ep->stopped = 1; ++ set_halt (ep); ++ mode = 2; ++ } else if (!req && ep->stopped) ++ write_fifo (ep, NULL); ++ } ++ } else { ++ /* status; stop NAKing */ ++ if (t & (1 << DATA_IN_TOKEN_INTERRUPT)) { ++ if (ep->dev->protocol_stall) { ++ ep->stopped = 1; ++ set_halt (ep); ++ } ++ mode = 2; ++ /* an extra OUT token is an error */ ++ } else if (((t & (1 << DATA_OUT_PING_TOKEN_INTERRUPT)) ++ && req ++ && req->req.actual == req->req.length) ++ || !req) { ++ ep->dev->protocol_stall = 1; ++ set_halt (ep); ++ ep->stopped = 1; ++ if (req) ++ done (ep, req, -EOVERFLOW); ++ req = NULL; ++ } ++ } ++ } ++ ++ if (unlikely (!req)) ++ return; ++ ++ /* manual DMA queue advance after short OUT */ ++ if (likely (ep->dma != 0)) { ++ if (t & (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT)) { ++ u32 count; ++ int stopped = ep->stopped; ++ ++ /* TRANSFERRED works around OUT_DONE erratum 0112. ++ * we expect (N <= maxpacket) bytes; host wrote M. ++ * iff (M < N) we won't ever see a DMA interrupt. ++ */ ++ ep->stopped = 1; ++ for (count = 0; ; t = readl (&ep->regs->ep_stat)) { ++ ++ /* any preceding dma transfers must finish. ++ * dma handles (M >= N), may empty the queue ++ */ ++ scan_dma_completions (ep); ++ if (unlikely (list_empty (&ep->queue) ++ || ep->out_overflow)) { ++ req = NULL; ++ break; ++ } ++ req = list_entry (ep->queue.next, ++ struct net2280_request, queue); ++ ++ /* here either (M < N), a "real" short rx; ++ * or (M == N) and the queue didn't empty ++ */ ++ if (likely (t & (1 << FIFO_EMPTY))) { ++ count = readl (&ep->dma->dmacount); ++ count &= DMA_BYTE_COUNT_MASK; ++ if (readl (&ep->dma->dmadesc) ++ != req->td_dma) ++ req = NULL; ++ break; ++ } ++ udelay(1); ++ } ++ ++ /* stop DMA, leave ep NAKing */ ++ writel ((1 << DMA_ABORT), &ep->dma->dmastat); ++ spin_stop_dma (ep->dma); ++ ++ if (likely (req != 0)) { ++ req->td->dmacount = 0; ++ t = readl (&ep->regs->ep_avail); ++ dma_done (ep, req, count, t); ++ } ++ ++ /* also flush to prevent erratum 0106 trouble */ ++ if (unlikely (ep->out_overflow ++ || (ep->dev->chiprev == 0x0100 ++ && ep->dev->gadget.speed ++ == USB_SPEED_FULL))) { ++ out_flush (ep); ++ ep->out_overflow = 0; ++ } ++ ++ /* (re)start dma if needed, stop NAKing */ ++ ep->stopped = stopped; ++ if (!list_empty (&ep->queue)) ++ restart_dma (ep); ++ } else ++ DEBUG (ep->dev, "%s dma ep_stat %08x ??\n", ++ ep->ep.name, t); ++ return; ++ ++ /* data packet(s) received (in the fifo, OUT) */ ++ } else if (t & (1 << DATA_PACKET_RECEIVED_INTERRUPT)) { ++ if (read_fifo (ep, req) && ep->num != 0) ++ mode = 2; ++ ++ /* data packet(s) transmitted (IN) */ ++ } else if (t & (1 << DATA_PACKET_TRANSMITTED_INTERRUPT)) { ++ unsigned len; ++ ++ len = req->req.length - req->req.actual; ++ if (len > ep->ep.maxpacket) ++ len = ep->ep.maxpacket; ++ req->req.actual += len; ++ ++ /* if we wrote it all, we're usually done */ ++ if (req->req.actual == req->req.length) { ++ if (ep->num == 0) { ++ /* wait for control status */ ++ if (mode != 2) ++ req = NULL; ++ } else if (!req->req.zero || len != ep->ep.maxpacket) ++ mode = 2; ++ } ++ ++ /* there was nothing to do ... */ ++ } else if (mode == 1) ++ return; ++ ++ /* done */ ++ if (mode == 2) { ++ /* stream endpoints often resubmit/unlink in completion */ ++ done (ep, req, 0); ++ ++ /* maybe advance queue to next request */ ++ if (ep->num == 0) { ++ /* NOTE: net2280 could let gadget driver start the ++ * status stage later. since not all controllers let ++ * them control that, the api doesn't (yet) allow it. ++ */ ++ if (!ep->stopped) ++ allow_status (ep); ++ req = NULL; ++ } else { ++ if (!list_empty (&ep->queue) && !ep->stopped) ++ req = list_entry (ep->queue.next, ++ struct net2280_request, queue); ++ else ++ req = NULL; ++ if (req && !ep->is_in) ++ stop_out_naking (ep); ++ } ++ } ++ ++ /* is there a buffer for the next packet? ++ * for best streaming performance, make sure there is one. ++ */ ++ if (req && !ep->stopped) { ++ ++ /* load IN fifo with next packet (may be zlp) */ ++ if (t & (1 << DATA_PACKET_TRANSMITTED_INTERRUPT)) ++ write_fifo (ep, &req->req); ++ } ++} ++ ++static struct net2280_ep * ++get_ep_by_addr (struct net2280 *dev, u16 wIndex) ++{ ++ struct net2280_ep *ep; ++ ++ if ((wIndex & USB_ENDPOINT_NUMBER_MASK) == 0) ++ return &dev->ep [0]; ++ list_for_each_entry (ep, &dev->gadget.ep_list, ep.ep_list) { ++ u8 bEndpointAddress; ++ ++ if (!ep->desc) ++ continue; ++ bEndpointAddress = ep->desc->bEndpointAddress; ++ if ((wIndex ^ bEndpointAddress) & USB_DIR_IN) ++ continue; ++ if ((wIndex & 0x0f) == (bEndpointAddress & 0x0f)) ++ return ep; ++ } ++ return NULL; ++} ++ ++static void handle_stat0_irqs (struct net2280 *dev, u32 stat) ++{ ++ struct net2280_ep *ep; ++ u32 num, scratch; ++ ++ /* most of these don't need individual acks */ ++ stat &= ~(1 << INTA_ASSERTED); ++ if (!stat) ++ return; ++ // DEBUG (dev, "irqstat0 %04x\n", stat); ++ ++ /* starting a control request? */ ++ if (unlikely (stat & (1 << SETUP_PACKET_INTERRUPT))) { ++ union { ++ u32 raw [2]; ++ struct usb_ctrlrequest r; ++ } u; ++ int tmp = 0; ++ struct net2280_request *req; ++ ++ if (dev->gadget.speed == USB_SPEED_UNKNOWN) { ++ if (readl (&dev->usb->usbstat) & (1 << HIGH_SPEED)) ++ dev->gadget.speed = USB_SPEED_HIGH; ++ else ++ dev->gadget.speed = USB_SPEED_FULL; ++ net2280_led_speed (dev, dev->gadget.speed); ++ DEBUG (dev, "%s speed\n", ++ (dev->gadget.speed == USB_SPEED_HIGH) ++ ? "high" : "full"); ++ } ++ ++ ep = &dev->ep [0]; ++ ep->irqs++; ++ ++ /* make sure any leftover request state is cleared */ ++ stat &= ~(1 << ENDPOINT_0_INTERRUPT); ++ while (!list_empty (&ep->queue)) { ++ req = list_entry (ep->queue.next, ++ struct net2280_request, queue); ++ done (ep, req, (req->req.actual == req->req.length) ++ ? 0 : -EPROTO); ++ } ++ ep->stopped = 0; ++ dev->protocol_stall = 0; ++ writel ( (1 << TIMEOUT) ++ | (1 << USB_STALL_SENT) ++ | (1 << USB_IN_NAK_SENT) ++ | (1 << USB_IN_ACK_RCVD) ++ | (1 << USB_OUT_PING_NAK_SENT) ++ | (1 << USB_OUT_ACK_SENT) ++ | (1 << FIFO_OVERFLOW) ++ | (1 << FIFO_UNDERFLOW) ++ | (1 << SHORT_PACKET_OUT_DONE_INTERRUPT) ++ | (1 << SHORT_PACKET_TRANSFERRED_INTERRUPT) ++ | (1 << DATA_PACKET_RECEIVED_INTERRUPT) ++ | (1 << DATA_PACKET_TRANSMITTED_INTERRUPT) ++ | (1 << DATA_OUT_PING_TOKEN_INTERRUPT) ++ | (1 << DATA_IN_TOKEN_INTERRUPT) ++ , &ep->regs->ep_stat); ++ u.raw [0] = readl (&dev->usb->setup0123); ++ u.raw [1] = readl (&dev->usb->setup4567); ++ ++ cpu_to_le32s (&u.raw [0]); ++ cpu_to_le32s (&u.raw [1]); ++ ++ le16_to_cpus (&u.r.wValue); ++ le16_to_cpus (&u.r.wIndex); ++ le16_to_cpus (&u.r.wLength); ++ ++ /* ack the irq */ ++ writel (1 << SETUP_PACKET_INTERRUPT, &dev->regs->irqstat0); ++ stat ^= (1 << SETUP_PACKET_INTERRUPT); ++ ++ /* watch control traffic at the token level, and force ++ * synchronization before letting the status stage happen. ++ * FIXME ignore tokens we'll NAK, until driver responds. ++ * that'll mean a lot less irqs for some drivers. ++ */ ++ ep->is_in = (u.r.bRequestType & USB_DIR_IN) != 0; ++ if (ep->is_in) { ++ scratch = (1 << DATA_PACKET_TRANSMITTED_INTERRUPT) ++ | (1 << DATA_OUT_PING_TOKEN_INTERRUPT) ++ | (1 << DATA_IN_TOKEN_INTERRUPT); ++ stop_out_naking (ep); ++ } else ++ scratch = (1 << DATA_PACKET_RECEIVED_INTERRUPT) ++ | (1 << DATA_OUT_PING_TOKEN_INTERRUPT) ++ | (1 << DATA_IN_TOKEN_INTERRUPT); ++ writel (scratch, &dev->epregs [0].ep_irqenb); ++ ++ /* we made the hardware handle most lowlevel requests; ++ * everything else goes uplevel to the gadget code. ++ */ ++ switch (u.r.bRequest) { ++ case USB_REQ_GET_STATUS: { ++ struct net2280_ep *e; ++ u16 status; ++ ++ /* hw handles device and interface status */ ++ if (u.r.bRequestType != (USB_DIR_IN|USB_RECIP_ENDPOINT)) ++ goto delegate; ++ if ((e = get_ep_by_addr (dev, u.r.wIndex)) == 0 ++ || u.r.wLength > 2) ++ goto do_stall; ++ ++ if (readl (&e->regs->ep_rsp) ++ & (1 << SET_ENDPOINT_HALT)) ++ status = __constant_cpu_to_le16 (1); ++ else ++ status = __constant_cpu_to_le16 (0); ++ ++ /* don't bother with a request object! */ ++ writel (0, &dev->epregs [0].ep_irqenb); ++ set_fifo_bytecount (ep, u.r.wLength); ++ writel (status, &dev->epregs [0].ep_data); ++ allow_status (ep); ++ VDEBUG (dev, "%s stat %02x\n", ep->ep.name, status); ++ goto next_endpoints; ++ } ++ break; ++ case USB_REQ_CLEAR_FEATURE: { ++ struct net2280_ep *e; ++ ++ /* hw handles device features */ ++ if (u.r.bRequestType != USB_RECIP_ENDPOINT) ++ goto delegate; ++ if (u.r.wValue != USB_ENDPOINT_HALT ++ || u.r.wLength != 0) ++ goto do_stall; ++ if ((e = get_ep_by_addr (dev, u.r.wIndex)) == 0) ++ goto do_stall; ++ clear_halt (e); ++ allow_status (ep); ++ VDEBUG (dev, "%s clear halt\n", ep->ep.name); ++ goto next_endpoints; ++ } ++ break; ++ case USB_REQ_SET_FEATURE: { ++ struct net2280_ep *e; ++ ++ /* hw handles device features */ ++ if (u.r.bRequestType != USB_RECIP_ENDPOINT) ++ goto delegate; ++ if (u.r.wValue != USB_ENDPOINT_HALT ++ || u.r.wLength != 0) ++ goto do_stall; ++ if ((e = get_ep_by_addr (dev, u.r.wIndex)) == 0) ++ goto do_stall; ++ set_halt (e); ++ allow_status (ep); ++ VDEBUG (dev, "%s set halt\n", ep->ep.name); ++ goto next_endpoints; ++ } ++ break; ++ default: ++delegate: ++ VDEBUG (dev, "setup %02x.%02x v%04x i%04x " ++ "ep_cfg %08x\n", ++ u.r.bRequestType, u.r.bRequest, ++ u.r.wValue, u.r.wIndex, ++ readl (&ep->regs->ep_cfg)); ++ spin_unlock (&dev->lock); ++ tmp = dev->driver->setup (&dev->gadget, &u.r); ++ spin_lock (&dev->lock); ++ } ++ ++ /* stall ep0 on error */ ++ if (tmp < 0) { ++do_stall: ++ VDEBUG (dev, "req %02x.%02x protocol STALL; stat %d\n", ++ u.r.bRequestType, u.r.bRequest, tmp); ++ dev->protocol_stall = 1; ++ } ++ ++ /* some in/out token irq should follow; maybe stall then. ++ * driver must queue a request (even zlp) or halt ep0 ++ * before the host times out. ++ */ ++ } ++ ++next_endpoints: ++ /* endpoint data irq ? */ ++ scratch = stat & 0x7f; ++ stat &= ~0x7f; ++ for (num = 0; scratch; num++) { ++ u32 t; ++ ++ /* do this endpoint's FIFO and queue need tending? */ ++ t = 1 << num; ++ if ((scratch & t) == 0) ++ continue; ++ scratch ^= t; ++ ++ ep = &dev->ep [num]; ++ handle_ep_small (ep); ++ } ++ ++ if (stat) ++ DEBUG (dev, "unhandled irqstat0 %08x\n", stat); ++} ++ ++#define DMA_INTERRUPTS ( \ ++ (1 << DMA_D_INTERRUPT) \ ++ | (1 << DMA_C_INTERRUPT) \ ++ | (1 << DMA_B_INTERRUPT) \ ++ | (1 << DMA_A_INTERRUPT)) ++#define PCI_ERROR_INTERRUPTS ( \ ++ (1 << PCI_MASTER_ABORT_RECEIVED_INTERRUPT) \ ++ | (1 << PCI_TARGET_ABORT_RECEIVED_INTERRUPT) \ ++ | (1 << PCI_RETRY_ABORT_INTERRUPT)) ++ ++static void handle_stat1_irqs (struct net2280 *dev, u32 stat) ++{ ++ struct net2280_ep *ep; ++ u32 tmp, num, scratch; ++ ++ /* after disconnect there's nothing else to do! */ ++ tmp = (1 << VBUS_INTERRUPT) | (1 << ROOT_PORT_RESET_INTERRUPT); ++ if (stat & tmp) { ++ writel (tmp, &dev->regs->irqstat1); ++ if (((stat & (1 << ROOT_PORT_RESET_INTERRUPT)) != 0 ++ || (readl (&dev->usb->usbctl) & (1 << VBUS_PIN)) == 0 ++ ) && dev->gadget.speed != USB_SPEED_UNKNOWN) { ++ DEBUG (dev, "disconnect %s\n", ++ dev->driver->driver.name); ++ stop_activity (dev, dev->driver); ++ ep0_start (dev); ++ return; ++ } ++ stat &= ~tmp; ++ ++ /* vBUS can bounce ... one of many reasons to ignore the ++ * notion of hotplug events on bus connect/disconnect! ++ */ ++ if (!stat) ++ return; ++ } ++ ++ /* NOTE: chip stays in PCI D0 state for now, but it could ++ * enter D1 to save more power ++ */ ++ tmp = (1 << SUSPEND_REQUEST_CHANGE_INTERRUPT); ++ if (stat & tmp) { ++ writel (tmp, &dev->regs->irqstat1); ++ if (stat & (1 << SUSPEND_REQUEST_INTERRUPT)) { ++ if (dev->driver->suspend) ++ dev->driver->suspend (&dev->gadget); ++ /* we use SUSPEND_IMMEDIATELY */ ++ stat &= ~(1 << SUSPEND_REQUEST_INTERRUPT); ++ } else { ++ if (dev->driver->resume) ++ dev->driver->resume (&dev->gadget); ++ /* at high speed, note erratum 0133 */ ++ } ++ stat &= ~tmp; ++ } ++ ++ /* clear any other status/irqs */ ++ if (stat) ++ writel (stat, &dev->regs->irqstat1); ++ ++ /* some status we can just ignore */ ++ stat &= ~((1 << CONTROL_STATUS_INTERRUPT) ++ | (1 << SUSPEND_REQUEST_INTERRUPT) ++ | (1 << RESUME_INTERRUPT) ++ | (1 << SOF_INTERRUPT)); ++ if (!stat) ++ return; ++ // DEBUG (dev, "irqstat1 %08x\n", stat); ++ ++ /* DMA status, for ep-{a,b,c,d} */ ++ scratch = stat & DMA_INTERRUPTS; ++ stat &= ~DMA_INTERRUPTS; ++ scratch >>= 9; ++ for (num = 0; scratch; num++) { ++ struct net2280_dma_regs *dma; ++ ++ tmp = 1 << num; ++ if ((tmp & scratch) == 0) ++ continue; ++ scratch ^= tmp; ++ ++ ep = &dev->ep [num + 1]; ++ dma = ep->dma; ++ ++ if (!dma) ++ continue; ++ ++ /* clear ep's dma status */ ++ tmp = readl (&dma->dmastat); ++ writel (tmp, &dma->dmastat); ++ ++ /* chaining should stop on abort, short OUT from fifo, ++ * or (stat0 codepath) short OUT transfer. ++ */ ++ if (!use_dma_chaining) { ++ if ((tmp & (1 << DMA_TRANSACTION_DONE_INTERRUPT)) ++ == 0) { ++ DEBUG (ep->dev, "%s no xact done? %08x\n", ++ ep->ep.name, tmp); ++ continue; ++ } ++ stop_dma (ep->dma); ++ } ++ ++ /* OUT transfers terminate when the data from the ++ * host is in our memory. Process whatever's done. ++ * On this path, we know transfer's last packet wasn't ++ * less than req->length. NAK_OUT_PACKETS may be set, ++ * or the FIFO may already be holding new packets. ++ * ++ * IN transfers can linger in the FIFO for a very ++ * long time ... we ignore that for now, accounting ++ * precisely (like PIO does) needs per-packet irqs ++ */ ++ scan_dma_completions (ep); ++ ++ /* disable dma on inactive queues; else maybe restart */ ++ if (list_empty (&ep->queue)) { ++ if (use_dma_chaining) ++ stop_dma (ep->dma); ++ } else { ++ tmp = readl (&dma->dmactl); ++ if (!use_dma_chaining ++ || (tmp & (1 << DMA_ENABLE)) == 0) ++ restart_dma (ep); ++ else if (ep->is_in && use_dma_chaining) { ++ struct net2280_request *req; ++ u32 dmacount; ++ ++ /* the descriptor at the head of the chain ++ * may still have VALID_BIT clear; that's ++ * used to trigger changing DMA_FIFO_VALIDATE ++ * (affects automagic zlp writes). ++ */ ++ req = list_entry (ep->queue.next, ++ struct net2280_request, queue); ++ dmacount = req->td->dmacount; ++ dmacount &= __constant_cpu_to_le32 ( ++ (1 << VALID_BIT) ++ | DMA_BYTE_COUNT_MASK); ++ if (dmacount && (dmacount & valid_bit) == 0) ++ restart_dma (ep); ++ } ++ } ++ ep->irqs++; ++ } ++ ++ /* NOTE: there are other PCI errors we might usefully notice. ++ * if they appear very often, here's where to try recovering. ++ */ ++ if (stat & PCI_ERROR_INTERRUPTS) { ++ ERROR (dev, "pci dma error; stat %08x\n", stat); ++ stat &= ~PCI_ERROR_INTERRUPTS; ++ /* these are fatal errors, but "maybe" they won't ++ * happen again ... ++ */ ++ stop_activity (dev, dev->driver); ++ ep0_start (dev); ++ stat = 0; ++ } ++ ++ if (stat) ++ DEBUG (dev, "unhandled irqstat1 %08x\n", stat); ++} ++ ++static irqreturn_t net2280_irq (int irq, void *_dev, struct pt_regs * r) ++{ ++ struct net2280 *dev = _dev; ++ ++ spin_lock (&dev->lock); ++ ++ /* handle disconnect, dma, and more */ ++ handle_stat1_irqs (dev, readl (&dev->regs->irqstat1)); ++ ++ /* control requests and PIO */ ++ handle_stat0_irqs (dev, readl (&dev->regs->irqstat0)); ++ ++ spin_unlock (&dev->lock); ++ ++ return IRQ_HANDLED; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* tear down the binding between this driver and the pci device */ ++ ++static void net2280_remove (struct pci_dev *pdev) ++{ ++ struct net2280 *dev = pci_get_drvdata (pdev); ++ ++ /* start with the driver above us */ ++ if (dev->driver) { ++ /* should have been done already by driver model core */ ++ WARN (dev, "pci remove, driver '%s' is still registered\n", ++ dev->driver->driver.name); ++ usb_gadget_unregister_driver (dev->driver); ++ } ++ ++ /* then clean up the resources we allocated during probe() */ ++ net2280_led_shutdown (dev); ++ if (dev->requests) { ++ int i; ++ for (i = 1; i < 5; i++) { ++ if (!dev->ep [i].dummy) ++ continue; ++ pci_pool_free (dev->requests, dev->ep [i].dummy, ++ dev->ep [i].td_dma); ++ } ++ pci_pool_destroy (dev->requests); ++ } ++ if (dev->got_irq) ++ free_irq (pdev->irq, dev); ++ if (dev->regs) ++ iounmap (dev->regs); ++ if (dev->region) ++ release_mem_region (pci_resource_start (pdev, 0), ++ pci_resource_len (pdev, 0)); ++ if (dev->enabled) ++ pci_disable_device (pdev); ++ pci_set_drvdata (pdev, 0); ++ ++ INFO (dev, "unbind from pci %s\n", pdev->slot_name); ++ ++ kfree (dev); ++ the_controller = 0; ++} ++ ++/* wrap this driver around the specified device, but ++ * don't respond over USB until a gadget driver binds to us. ++ */ ++ ++static int net2280_probe (struct pci_dev *pdev, const struct pci_device_id *id) ++{ ++ struct net2280 *dev; ++ unsigned long resource, len; ++ void *base = NULL; ++ int retval, i; ++ char buf [8], *bufp; ++ ++ /* if you want to support more than one controller in a system, ++ * usb_gadget_driver_{register,unregister}() must change. ++ */ ++ if (the_controller) { ++ WARN (the_controller, "ignoring %s\n", pdev->slot_name); ++ return -EBUSY; ++ } ++ ++ /* alloc, and start init */ ++ dev = kmalloc (sizeof *dev, SLAB_KERNEL); ++ if (dev == NULL){ ++ retval = -ENOMEM; ++ goto done; ++ } ++ ++ memset (dev, 0, sizeof *dev); ++ spin_lock_init (&dev->lock); ++ dev->pdev = pdev; ++ dev->gadget.ops = &net2280_ops; ++ dev->gadget.is_dualspeed = 1; ++ ++ dev->gadget.dev.bus_id = pdev->slot_name; ++ dev->gadget.name = driver_name; ++ ++ /* now all the pci goodies ... */ ++ if (pci_enable_device (pdev) < 0) { ++ retval = -ENODEV; ++ goto done; ++ } ++ dev->enabled = 1; ++ ++ /* BAR 0 holds all the registers ++ * BAR 1 is 8051 memory; unused here (note erratum 0103) ++ * BAR 2 is fifo memory; unused here ++ */ ++ resource = pci_resource_start (pdev, 0); ++ len = pci_resource_len (pdev, 0); ++ if (!request_mem_region (resource, len, driver_name)) { ++ DEBUG (dev, "controller already in use\n"); ++ retval = -EBUSY; ++ goto done; ++ } ++ dev->region = 1; ++ ++ base = ioremap_nocache (resource, len); ++ if (base == NULL) { ++ DEBUG (dev, "can't map memory\n"); ++ retval = -EFAULT; ++ goto done; ++ } ++ dev->regs = (struct net2280_regs *) base; ++ dev->usb = (struct net2280_usb_regs *) (base + 0x0080); ++ dev->pci = (struct net2280_pci_regs *) (base + 0x0100); ++ dev->dma = (struct net2280_dma_regs *) (base + 0x0180); ++ dev->dep = (struct net2280_dep_regs *) (base + 0x0200); ++ dev->epregs = (struct net2280_ep_regs *) (base + 0x0300); ++ ++ /* put into initial config, link up all endpoints */ ++ writel (0, &dev->usb->usbctl); ++ usb_reset (dev); ++ usb_reinit (dev); ++ ++ /* irq setup after old hardware is cleaned up */ ++ if (!pdev->irq) { ++ ERROR (dev, "No IRQ. Check PCI setup!\n"); ++ retval = -ENODEV; ++ goto done; ++ } ++#ifndef __sparc__ ++ snprintf (buf, sizeof buf, "%d", pdev->irq); ++ bufp = buf; ++#else ++ bufp = __irq_itoa(pdev->irq); ++#endif ++ if (request_irq (pdev->irq, net2280_irq, SA_SHIRQ, driver_name, dev) ++ != 0) { ++ ERROR (dev, "request interrupt %s failed\n", bufp); ++ retval = -EBUSY; ++ goto done; ++ } ++ dev->got_irq = 1; ++ ++ /* DMA setup */ ++ dev->requests = pci_pool_create ("requests", pdev, ++ sizeof (struct net2280_dma), ++ 0 /* no alignment requirements */, ++ 0 /* or page-crossing issues */, ++ SLAB_KERNEL /* 2.4 only */ ); ++ if (!dev->requests) { ++ DEBUG (dev, "can't get request pool\n"); ++ retval = -ENOMEM; ++ goto done; ++ } ++ for (i = 1; i < 5; i++) { ++ struct net2280_dma *td; ++ ++ td = pci_pool_alloc (dev->requests, GFP_KERNEL, ++ &dev->ep [i].td_dma); ++ if (!td) { ++ DEBUG (dev, "can't get dummy %d\n", i); ++ retval = -ENOMEM; ++ goto done; ++ } ++ td->dmacount = 0; /* not VALID */ ++ td->dmaaddr = __constant_cpu_to_le32 (DMA_ADDR_INVALID); ++ td->dmadesc = td->dmaaddr; ++ dev->ep [i].dummy = td; ++ } ++ ++ /* enable lower-overhead pci memory bursts during DMA */ ++ writel ( (1 << DMA_MEMORY_WRITE_AND_INVALIDATE_ENABLE) ++ // 256 write retries may not be enough... ++ // | (1 << PCI_RETRY_ABORT_ENABLE) ++ | (1 << DMA_READ_MULTIPLE_ENABLE) ++ | (1 << DMA_READ_LINE_ENABLE) ++ , &dev->pci->pcimstctl); ++ /* erratum 0115 shouldn't appear: Linux inits PCI_LATENCY_TIMER */ ++ pci_set_master (pdev); ++ pci_set_mwi (pdev); ++ ++ /* ... also flushes any posted pci writes */ ++ dev->chiprev = get_idx_reg (dev->regs, REG_CHIPREV) & 0xffff; ++ ++ /* done */ ++ pci_set_drvdata (pdev, dev); ++ INFO (dev, "%s\n", driver_desc); ++ INFO (dev, "irq %s, pci mem %p, chip rev %04x\n", ++ bufp, base, dev->chiprev); ++ INFO (dev, "version: " DRIVER_VERSION "; dma %s\n", ++ use_dma ++ ? (use_dma_chaining ? "chaining" : "enabled") ++ : "disabled"); ++ the_controller = dev; ++ ++ return 0; ++ ++done: ++ if (dev) ++ net2280_remove (pdev); ++ return retval; ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static struct pci_device_id pci_ids [] = { { ++ .class = ((PCI_CLASS_SERIAL_USB << 8) | 0xfe), ++ .class_mask = ~0, ++ .vendor = 0x17cc, ++ .device = 0x2280, ++ .subvendor = PCI_ANY_ID, ++ .subdevice = PCI_ANY_ID, ++ ++}, { /* end: all zeroes */ } ++}; ++MODULE_DEVICE_TABLE (pci, pci_ids); ++ ++/* pci driver glue; this is a "new style" PCI driver module */ ++static struct pci_driver net2280_pci_driver = { ++ .name = (char *) driver_name, ++ .id_table = pci_ids, ++ ++ .probe = net2280_probe, ++ .remove = net2280_remove, ++ ++ /* FIXME add power management support */ ++}; ++ ++MODULE_DESCRIPTION (DRIVER_DESC); ++MODULE_AUTHOR ("David Brownell"); ++MODULE_LICENSE ("GPL"); ++ ++static int __init init (void) ++{ ++ if (!use_dma) ++ use_dma_chaining = 0; ++ return pci_module_init (&net2280_pci_driver); ++} ++module_init (init); ++ ++static void __exit cleanup (void) ++{ ++ pci_unregister_driver (&net2280_pci_driver); ++} ++module_exit (cleanup); +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/net2280.h kernel/drivers/usb/gadget/net2280.h +--- /tmp/kernel/drivers/usb/gadget/net2280.h 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/net2280.h 2005-04-22 17:53:19.483531538 +0200 +@@ -0,0 +1,756 @@ ++/* ++ * NetChip 2280 high/full speed USB device controller. ++ * Unlike many such controllers, this one talks PCI. ++ */ ++ ++/* ++ * Copyright (C) 2002 NetChip Technology, Inc. (http://www.netchip.com) ++ * Copyright (C) 2003 David Brownell ++ * ++ * 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 ++ */ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* NET2280 MEMORY MAPPED REGISTERS ++ * ++ * The register layout came from the chip documentation, and the bit ++ * number definitions were extracted from chip specification. ++ * ++ * Use the shift operator ('<<') to build bit masks, with readl/writel ++ * to access the registers through PCI. ++ */ ++ ++/* main registers, BAR0 + 0x0000 */ ++struct net2280_regs { ++ // offset 0x0000 ++ u32 devinit; ++#define LOCAL_CLOCK_FREQUENCY 8 ++#define FORCE_PCI_RESET 7 ++#define PCI_ID 6 ++#define PCI_ENABLE 5 ++#define FIFO_SOFT_RESET 4 ++#define CFG_SOFT_RESET 3 ++#define PCI_SOFT_RESET 2 ++#define USB_SOFT_RESET 1 ++#define M8051_RESET 0 ++ u32 eectl; ++#define EEPROM_ADDRESS_WIDTH 23 ++#define EEPROM_CHIP_SELECT_ACTIVE 22 ++#define EEPROM_PRESENT 21 ++#define EEPROM_VALID 20 ++#define EEPROM_BUSY 19 ++#define EEPROM_CHIP_SELECT_ENABLE 18 ++#define EEPROM_BYTE_READ_START 17 ++#define EEPROM_BYTE_WRITE_START 16 ++#define EEPROM_READ_DATA 8 ++#define EEPROM_WRITE_DATA 0 ++ u32 eeclkfreq; ++ u32 _unused0; ++ // offset 0x0010 ++ ++ u32 pciirqenb0; /* interrupt PCI master ... */ ++#define SETUP_PACKET_INTERRUPT_ENABLE 7 ++#define ENDPOINT_F_INTERRUPT_ENABLE 6 ++#define ENDPOINT_E_INTERRUPT_ENABLE 5 ++#define ENDPOINT_D_INTERRUPT_ENABLE 4 ++#define ENDPOINT_C_INTERRUPT_ENABLE 3 ++#define ENDPOINT_B_INTERRUPT_ENABLE 2 ++#define ENDPOINT_A_INTERRUPT_ENABLE 1 ++#define ENDPOINT_0_INTERRUPT_ENABLE 0 ++ u32 pciirqenb1; ++#define PCI_INTERRUPT_ENABLE 31 ++#define POWER_STATE_CHANGE_INTERRUPT_ENABLE 27 ++#define PCI_ARBITER_TIMEOUT_INTERRUPT_ENABLE 26 ++#define PCI_PARITY_ERROR_INTERRUPT_ENABLE 25 ++#define PCI_MASTER_ABORT_RECEIVED_INTERRUPT_ENABLE 20 ++#define PCI_TARGET_ABORT_RECEIVED_INTERRUPT_ENABLE 19 ++#define PCI_TARGET_ABORT_ASSERTED_INTERRUPT_ENABLE 18 ++#define PCI_RETRY_ABORT_INTERRUPT_ENABLE 17 ++#define PCI_MASTER_CYCLE_DONE_INTERRUPT_ENABLE 16 ++#define GPIO_INTERRUPT_ENABLE 13 ++#define DMA_D_INTERRUPT_ENABLE 12 ++#define DMA_C_INTERRUPT_ENABLE 11 ++#define DMA_B_INTERRUPT_ENABLE 10 ++#define DMA_A_INTERRUPT_ENABLE 9 ++#define EEPROM_DONE_INTERRUPT_ENABLE 8 ++#define VBUS_INTERRUPT_ENABLE 7 ++#define CONTROL_STATUS_INTERRUPT_ENABLE 6 ++#define ROOT_PORT_RESET_INTERRUPT_ENABLE 4 ++#define SUSPEND_REQUEST_INTERRUPT_ENABLE 3 ++#define SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE 2 ++#define RESUME_INTERRUPT_ENABLE 1 ++#define SOF_INTERRUPT_ENABLE 0 ++ u32 cpu_irqenb0; /* ... or onboard 8051 */ ++#define SETUP_PACKET_INTERRUPT_ENABLE 7 ++#define ENDPOINT_F_INTERRUPT_ENABLE 6 ++#define ENDPOINT_E_INTERRUPT_ENABLE 5 ++#define ENDPOINT_D_INTERRUPT_ENABLE 4 ++#define ENDPOINT_C_INTERRUPT_ENABLE 3 ++#define ENDPOINT_B_INTERRUPT_ENABLE 2 ++#define ENDPOINT_A_INTERRUPT_ENABLE 1 ++#define ENDPOINT_0_INTERRUPT_ENABLE 0 ++ u32 cpu_irqenb1; ++#define CPU_INTERRUPT_ENABLE 31 ++#define POWER_STATE_CHANGE_INTERRUPT_ENABLE 27 ++#define PCI_ARBITER_TIMEOUT_INTERRUPT_ENABLE 26 ++#define PCI_PARITY_ERROR_INTERRUPT_ENABLE 25 ++#define PCI_INTA_INTERRUPT_ENABLE 24 ++#define PCI_PME_INTERRUPT_ENABLE 23 ++#define PCI_SERR_INTERRUPT_ENABLE 22 ++#define PCI_PERR_INTERRUPT_ENABLE 21 ++#define PCI_MASTER_ABORT_RECEIVED_INTERRUPT_ENABLE 20 ++#define PCI_TARGET_ABORT_RECEIVED_INTERRUPT_ENABLE 19 ++#define PCI_RETRY_ABORT_INTERRUPT_ENABLE 17 ++#define PCI_MASTER_CYCLE_DONE_INTERRUPT_ENABLE 16 ++#define GPIO_INTERRUPT_ENABLE 13 ++#define DMA_D_INTERRUPT_ENABLE 12 ++#define DMA_C_INTERRUPT_ENABLE 11 ++#define DMA_B_INTERRUPT_ENABLE 10 ++#define DMA_A_INTERRUPT_ENABLE 9 ++#define EEPROM_DONE_INTERRUPT_ENABLE 8 ++#define VBUS_INTERRUPT_ENABLE 7 ++#define CONTROL_STATUS_INTERRUPT_ENABLE 6 ++#define ROOT_PORT_RESET_INTERRUPT_ENABLE 4 ++#define SUSPEND_REQUEST_INTERRUPT_ENABLE 3 ++#define SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE 2 ++#define RESUME_INTERRUPT_ENABLE 1 ++#define SOF_INTERRUPT_ENABLE 0 ++ ++ // offset 0x0020 ++ u32 _unused1; ++ u32 usbirqenb1; ++#define USB_INTERRUPT_ENABLE 31 ++#define POWER_STATE_CHANGE_INTERRUPT_ENABLE 27 ++#define PCI_ARBITER_TIMEOUT_INTERRUPT_ENABLE 26 ++#define PCI_PARITY_ERROR_INTERRUPT_ENABLE 25 ++#define PCI_INTA_INTERRUPT_ENABLE 24 ++#define PCI_PME_INTERRUPT_ENABLE 23 ++#define PCI_SERR_INTERRUPT_ENABLE 22 ++#define PCI_PERR_INTERRUPT_ENABLE 21 ++#define PCI_MASTER_ABORT_RECEIVED_INTERRUPT_ENABLE 20 ++#define PCI_TARGET_ABORT_RECEIVED_INTERRUPT_ENABLE 19 ++#define PCI_RETRY_ABORT_INTERRUPT_ENABLE 17 ++#define PCI_MASTER_CYCLE_DONE_INTERRUPT_ENABLE 16 ++#define GPIO_INTERRUPT_ENABLE 13 ++#define DMA_D_INTERRUPT_ENABLE 12 ++#define DMA_C_INTERRUPT_ENABLE 11 ++#define DMA_B_INTERRUPT_ENABLE 10 ++#define DMA_A_INTERRUPT_ENABLE 9 ++#define EEPROM_DONE_INTERRUPT_ENABLE 8 ++#define VBUS_INTERRUPT_ENABLE 7 ++#define CONTROL_STATUS_INTERRUPT_ENABLE 6 ++#define ROOT_PORT_RESET_INTERRUPT_ENABLE 4 ++#define SUSPEND_REQUEST_INTERRUPT_ENABLE 3 ++#define SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE 2 ++#define RESUME_INTERRUPT_ENABLE 1 ++#define SOF_INTERRUPT_ENABLE 0 ++ u32 irqstat0; ++#define INTA_ASSERTED 12 ++#define SETUP_PACKET_INTERRUPT 7 ++#define ENDPOINT_F_INTERRUPT 6 ++#define ENDPOINT_E_INTERRUPT 5 ++#define ENDPOINT_D_INTERRUPT 4 ++#define ENDPOINT_C_INTERRUPT 3 ++#define ENDPOINT_B_INTERRUPT 2 ++#define ENDPOINT_A_INTERRUPT 1 ++#define ENDPOINT_0_INTERRUPT 0 ++ u32 irqstat1; ++#define POWER_STATE_CHANGE_INTERRUPT 27 ++#define PCI_ARBITER_TIMEOUT_INTERRUPT 26 ++#define PCI_PARITY_ERROR_INTERRUPT 25 ++#define PCI_INTA_INTERRUPT 24 ++#define PCI_PME_INTERRUPT 23 ++#define PCI_SERR_INTERRUPT 22 ++#define PCI_PERR_INTERRUPT 21 ++#define PCI_MASTER_ABORT_RECEIVED_INTERRUPT 20 ++#define PCI_TARGET_ABORT_RECEIVED_INTERRUPT 19 ++#define PCI_RETRY_ABORT_INTERRUPT 17 ++#define PCI_MASTER_CYCLE_DONE_INTERRUPT 16 ++#define GPIO_INTERRUPT 13 ++#define DMA_D_INTERRUPT 12 ++#define DMA_C_INTERRUPT 11 ++#define DMA_B_INTERRUPT 10 ++#define DMA_A_INTERRUPT 9 ++#define EEPROM_DONE_INTERRUPT 8 ++#define VBUS_INTERRUPT 7 ++#define CONTROL_STATUS_INTERRUPT 6 ++#define ROOT_PORT_RESET_INTERRUPT 4 ++#define SUSPEND_REQUEST_INTERRUPT 3 ++#define SUSPEND_REQUEST_CHANGE_INTERRUPT 2 ++#define RESUME_INTERRUPT 1 ++#define SOF_INTERRUPT 0 ++ // offset 0x0030 ++ u32 idxaddr; ++ u32 idxdata; ++ u32 fifoctl; ++#define PCI_BASE2_RANGE 16 ++#define IGNORE_FIFO_AVAILABILITY 3 ++#define PCI_BASE2_SELECT 2 ++#define FIFO_CONFIGURATION_SELECT 0 ++ u32 _unused2; ++ // offset 0x0040 ++ u32 memaddr; ++#define START 28 ++#define DIRECTION 27 ++#define FIFO_DIAGNOSTIC_SELECT 24 ++#define MEMORY_ADDRESS 0 ++ u32 memdata0; ++ u32 memdata1; ++ u32 _unused3; ++ // offset 0x0050 ++ u32 gpioctl; ++#define GPIO3_LED_SELECT 12 ++#define GPIO3_INTERRUPT_ENABLE 11 ++#define GPIO2_INTERRUPT_ENABLE 10 ++#define GPIO1_INTERRUPT_ENABLE 9 ++#define GPIO0_INTERRUPT_ENABLE 8 ++#define GPIO3_OUTPUT_ENABLE 7 ++#define GPIO2_OUTPUT_ENABLE 6 ++#define GPIO1_OUTPUT_ENABLE 5 ++#define GPIO0_OUTPUT_ENABLE 4 ++#define GPIO3_DATA 3 ++#define GPIO2_DATA 2 ++#define GPIO1_DATA 1 ++#define GPIO0_DATA 0 ++ u32 gpiostat; ++#define GPIO3_INTERRUPT 3 ++#define GPIO2_INTERRUPT 2 ++#define GPIO1_INTERRUPT 1 ++#define GPIO0_INTERRUPT 0 ++} __attribute__ ((packed)); ++ ++/* usb control, BAR0 + 0x0080 */ ++struct net2280_usb_regs { ++ // offset 0x0080 ++ u32 stdrsp; ++#define STALL_UNSUPPORTED_REQUESTS 31 ++#define SET_TEST_MODE 16 ++#define GET_OTHER_SPEED_CONFIGURATION 15 ++#define GET_DEVICE_QUALIFIER 14 ++#define SET_ADDRESS 13 ++#define ENDPOINT_SET_CLEAR_HALT 12 ++#define DEVICE_SET_CLEAR_DEVICE_REMOTE_WAKEUP 11 ++#define GET_STRING_DESCRIPTOR_2 10 ++#define GET_STRING_DESCRIPTOR_1 9 ++#define GET_STRING_DESCRIPTOR_0 8 ++#define GET_SET_INTERFACE 6 ++#define GET_SET_CONFIGURATION 5 ++#define GET_CONFIGURATION_DESCRIPTOR 4 ++#define GET_DEVICE_DESCRIPTOR 3 ++#define GET_ENDPOINT_STATUS 2 ++#define GET_INTERFACE_STATUS 1 ++#define GET_DEVICE_STATUS 0 ++ u32 prodvendid; ++#define PRODUCT_ID 16 ++#define VENDOR_ID 0 ++ u32 relnum; ++ u32 usbctl; ++#define SERIAL_NUMBER_INDEX 16 ++#define PRODUCT_ID_STRING_ENABLE 13 ++#define VENDOR_ID_STRING_ENABLE 12 ++#define USB_ROOT_PORT_WAKEUP_ENABLE 11 ++#define VBUS_PIN 10 ++#define TIMED_DISCONNECT 9 ++#define SUSPEND_IMMEDIATELY 7 ++#define SELF_POWERED_USB_DEVICE 6 ++#define REMOTE_WAKEUP_SUPPORT 5 ++#define PME_POLARITY 4 ++#define USB_DETECT_ENABLE 3 ++#define PME_WAKEUP_ENABLE 2 ++#define DEVICE_REMOTE_WAKEUP_ENABLE 1 ++#define SELF_POWERED_STATUS 0 ++ // offset 0x0090 ++ u32 usbstat; ++#define HIGH_SPEED 7 ++#define FULL_SPEED 6 ++#define GENERATE_RESUME 5 ++#define GENERATE_DEVICE_REMOTE_WAKEUP 4 ++ u32 xcvrdiag; ++#define FORCE_HIGH_SPEED_MODE 31 ++#define FORCE_FULL_SPEED_MODE 30 ++#define USB_TEST_MODE 24 ++#define LINE_STATE 16 ++#define TRANSCEIVER_OPERATION_MODE 2 ++#define TRANSCEIVER_SELECT 1 ++#define TERMINATION_SELECT 0 ++ u32 setup0123; ++ u32 setup4567; ++ // offset 0x0090 ++ u32 _unused0; ++ u32 ouraddr; ++#define FORCE_IMMEDIATE 7 ++#define OUR_USB_ADDRESS 0 ++ u32 ourconfig; ++} __attribute__ ((packed)); ++ ++/* pci control, BAR0 + 0x0100 */ ++struct net2280_pci_regs { ++ // offset 0x0100 ++ u32 pcimstctl; ++#define PCI_ARBITER_PARK_SELECT 13 ++#define PCI_MULTI LEVEL_ARBITER 12 ++#define PCI_RETRY_ABORT_ENABLE 11 ++#define DMA_MEMORY_WRITE_AND_INVALIDATE_ENABLE 10 ++#define DMA_READ_MULTIPLE_ENABLE 9 ++#define DMA_READ_LINE_ENABLE 8 ++#define PCI_MASTER_COMMAND_SELECT 6 ++#define MEM_READ_OR_WRITE 0 ++#define IO_READ_OR_WRITE 1 ++#define CFG_READ_OR_WRITE 2 ++#define PCI_MASTER_START 5 ++#define PCI_MASTER_READ_WRITE 4 ++#define PCI_MASTER_WRITE 0 ++#define PCI_MASTER_READ 1 ++#define PCI_MASTER_BYTE_WRITE_ENABLES 0 ++ u32 pcimstaddr; ++ u32 pcimstdata; ++ u32 pcimststat; ++#define PCI_ARBITER_CLEAR 2 ++#define PCI_EXTERNAL_ARBITER 1 ++#define PCI_HOST_MODE 0 ++} __attribute__ ((packed)); ++ ++/* dma control, BAR0 + 0x0180 ... array of four structs like this, ++ * for channels 0..3. see also struct net2280_dma: descriptor ++ * that can be loaded into some of these registers. ++ */ ++struct net2280_dma_regs { /* [11.7] */ ++ // offset 0x0180, 0x01a0, 0x01c0, 0x01e0, ++ u32 dmactl; ++#define DMA_SCATTER_GATHER_DONE_INTERRUPT_ENABLE 25 ++#define DMA_CLEAR_COUNT_ENABLE 21 ++#define DESCRIPTOR_POLLING_RATE 19 ++#define POLL_CONTINUOUS 0 ++#define POLL_1_USEC 1 ++#define POLL_100_USEC 2 ++#define POLL_1_MSEC 3 ++#define DMA_VALID_BIT_POLLING_ENABLE 18 ++#define DMA_VALID_BIT_ENABLE 17 ++#define DMA_SCATTER_GATHER_ENABLE 16 ++#define DMA_OUT_AUTO_START_ENABLE 4 ++#define DMA_PREEMPT_ENABLE 3 ++#define DMA_FIFO_VALIDATE 2 ++#define DMA_ENABLE 1 ++#define DMA_ADDRESS_HOLD 0 ++ u32 dmastat; ++#define DMA_SCATTER_GATHER_DONE_INTERRUPT 25 ++#define DMA_TRANSACTION_DONE_INTERRUPT 24 ++#define DMA_ABORT 1 ++#define DMA_START 0 ++ u32 _unused0 [2]; ++ // offset 0x0190, 0x01b0, 0x01d0, 0x01f0, ++ u32 dmacount; ++#define VALID_BIT 31 ++#define DMA_DIRECTION 30 ++#define DMA_DONE_INTERRUPT_ENABLE 29 ++#define END_OF_CHAIN 28 ++#define DMA_BYTE_COUNT_MASK ((1<<24)-1) ++#define DMA_BYTE_COUNT 0 ++ u32 dmaaddr; ++ u32 dmadesc; ++ u32 _unused1; ++} __attribute__ ((packed)); ++ ++/* dedicated endpoint registers, BAR0 + 0x0200 */ ++ ++struct net2280_dep_regs { /* [11.8] */ ++ // offset 0x0200, 0x0210, 0x220, 0x230, 0x240 ++ u32 dep_cfg; ++ // offset 0x0204, 0x0214, 0x224, 0x234, 0x244 ++ u32 dep_rsp; ++ u32 _unused [2]; ++} __attribute__ ((packed)); ++ ++/* configurable endpoint registers, BAR0 + 0x0300 ... array of seven structs ++ * like this, for ep0 then the configurable endpoints A..F ++ * ep0 reserved for control; E and F have only 64 bytes of fifo ++ */ ++struct net2280_ep_regs { /* [11.9] */ ++ // offset 0x0300, 0x0320, 0x0340, 0x0360, 0x0380, 0x03a0, 0x03c0 ++ u32 ep_cfg; ++#define ENDPOINT_BYTE_COUNT 16 ++#define ENDPOINT_ENABLE 10 ++#define ENDPOINT_TYPE 8 ++#define ENDPOINT_DIRECTION 7 ++#define ENDPOINT_NUMBER 0 ++ u32 ep_rsp; ++#define SET_NAK_OUT_PACKETS 15 ++#define SET_EP_HIDE_STATUS_PHASE 14 ++#define SET_EP_FORCE_CRC_ERROR 13 ++#define SET_INTERRUPT_MODE 12 ++#define SET_CONTROL_STATUS_PHASE_HANDSHAKE 11 ++#define SET_NAK_OUT_PACKETS_MODE 10 ++#define SET_ENDPOINT_TOGGLE 9 ++#define SET_ENDPOINT_HALT 8 ++#define CLEAR_NAK_OUT_PACKETS 7 ++#define CLEAR_EP_HIDE_STATUS_PHASE 6 ++#define CLEAR_EP_FORCE_CRC_ERROR 5 ++#define CLEAR_INTERRUPT_MODE 4 ++#define CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE 3 ++#define CLEAR_NAK_OUT_PACKETS_MODE 2 ++#define CLEAR_ENDPOINT_TOGGLE 1 ++#define CLEAR_ENDPOINT_HALT 0 ++ u32 ep_irqenb; ++#define SHORT_PACKET_OUT_DONE_INTERRUPT_ENABLE 6 ++#define SHORT_PACKET_TRANSFERRED_INTERRUPT_ENABLE 5 ++#define DATA_PACKET_RECEIVED_INTERRUPT_ENABLE 3 ++#define DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE 2 ++#define DATA_OUT_PING_TOKEN_INTERRUPT_ENABLE 1 ++#define DATA_IN_TOKEN_INTERRUPT_ENABLE 0 ++ u32 ep_stat; ++#define FIFO_VALID_COUNT 24 ++#define HIGH_BANDWIDTH_OUT_TRANSACTION_PID 22 ++#define TIMEOUT 21 ++#define USB_STALL_SENT 20 ++#define USB_IN_NAK_SENT 19 ++#define USB_IN_ACK_RCVD 18 ++#define USB_OUT_PING_NAK_SENT 17 ++#define USB_OUT_ACK_SENT 16 ++#define FIFO_OVERFLOW 13 ++#define FIFO_UNDERFLOW 12 ++#define FIFO_FULL 11 ++#define FIFO_EMPTY 10 ++#define FIFO_FLUSH 9 ++#define SHORT_PACKET_OUT_DONE_INTERRUPT 6 ++#define SHORT_PACKET_TRANSFERRED_INTERRUPT 5 ++#define NAK_OUT_PACKETS 4 ++#define DATA_PACKET_RECEIVED_INTERRUPT 3 ++#define DATA_PACKET_TRANSMITTED_INTERRUPT 2 ++#define DATA_OUT_PING_TOKEN_INTERRUPT 1 ++#define DATA_IN_TOKEN_INTERRUPT 0 ++ // offset 0x0310, 0x0330, 0x0350, 0x0370, 0x0390, 0x03b0, 0x03d0 ++ u32 ep_avail; ++ u32 ep_data; ++ u32 _unused0 [2]; ++} __attribute__ ((packed)); ++ ++/*-------------------------------------------------------------------------*/ ++ ++#ifdef __KERNEL__ ++ ++/* indexed registers [11.10] are accessed indirectly ++ * caller must own the device lock. ++ */ ++ ++static inline u32 ++get_idx_reg (struct net2280_regs *regs, u32 index) ++{ ++ writel (index, ®s->idxaddr); ++ /* NOTE: synchs device/cpu memory views */ ++ return readl (®s->idxdata); ++} ++ ++static inline void ++set_idx_reg (struct net2280_regs *regs, u32 index, u32 value) ++{ ++ writel (index, ®s->idxaddr); ++ writel (value, ®s->idxdata); ++ /* posted, may not be visible yet */ ++} ++ ++#endif /* __KERNEL__ */ ++ ++ ++#define REG_DIAG 0x0 ++#define RETRY_COUNTER 16 ++#define FORCE_PCI_SERR 11 ++#define FORCE_PCI_INTERRUPT 10 ++#define FORCE_USB_INTERRUPT 9 ++#define FORCE_CPU_INTERRUPT 8 ++#define ILLEGAL_BYTE_ENABLES 5 ++#define FAST_TIMES 4 ++#define FORCE_RECEIVE_ERROR 2 ++#define FORCE_TRANSMIT_CRC_ERROR 0 ++#define REG_FRAME 0x02 /* from last sof */ ++#define REG_CHIPREV 0x03 /* in bcd */ ++#define REG_HS_NAK_RATE 0x0a /* NAK per N uframes */ ++ ++#define CHIPREV_1 0x0100 ++#define CHIPREV_1A 0x0110 ++ ++#ifdef __KERNEL__ ++ ++/* ep a-f highspeed and fullspeed maxpacket, addresses ++ * computed from ep->num ++ */ ++#define REG_EP_MAXPKT(dev,num) (((num) + 1) * 0x10 + \ ++ (((dev)->gadget.speed == USB_SPEED_HIGH) ? 0 : 1)) ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* [8.3] for scatter/gather i/o ++ * use struct net2280_dma_regs bitfields ++ */ ++struct net2280_dma { ++ u32 dmacount; ++ u32 dmaaddr; /* the buffer */ ++ u32 dmadesc; /* next dma descriptor */ ++ u32 _reserved; ++} __attribute__ ((aligned (16))); ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* DRIVER DATA STRUCTURES and UTILITIES */ ++ ++struct net2280_ep { ++ struct usb_ep ep; ++ struct net2280_ep_regs *regs; ++ struct net2280_dma_regs *dma; ++ struct net2280_dma *dummy; ++ dma_addr_t td_dma; /* of dummy */ ++ struct net2280 *dev; ++ unsigned long irqs; ++ ++ /* analogous to a host-side qh */ ++ struct list_head queue; ++ const struct usb_endpoint_descriptor *desc; ++ unsigned num : 8, ++ fifo_size : 12, ++ in_fifo_validate : 1, ++ out_overflow : 1, ++ stopped : 1, ++ is_in : 1, ++ is_iso : 1; ++}; ++ ++static inline void allow_status (struct net2280_ep *ep) ++{ ++ /* ep0 only */ ++ writel ( (1 << CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE) ++ | (1 << CLEAR_NAK_OUT_PACKETS) ++ | (1 << CLEAR_NAK_OUT_PACKETS_MODE) ++ , &ep->regs->ep_rsp); ++ ep->stopped = 1; ++} ++ ++/* count (<= 4) bytes in the next fifo write will be valid */ ++static inline void set_fifo_bytecount (struct net2280_ep *ep, unsigned count) ++{ ++ writeb (count, 2 + (u8 *) &ep->regs->ep_cfg); ++} ++ ++struct net2280_request { ++ struct usb_request req; ++ struct net2280_dma *td; ++ dma_addr_t td_dma; ++ struct list_head queue; ++ unsigned mapped : 1, ++ valid : 1; ++}; ++ ++struct net2280 { ++ /* each pci device provides one gadget, several endpoints */ ++ struct usb_gadget gadget; ++ spinlock_t lock; ++ struct net2280_ep ep [7]; ++ struct usb_gadget_driver *driver; ++ unsigned enabled : 1, ++ protocol_stall : 1, ++ softconnect : 1, ++ got_irq : 1, ++ region : 1; ++ u16 chiprev; ++ ++ /* pci state used to access those endpoints */ ++ struct pci_dev *pdev; ++ struct net2280_regs *regs; ++ struct net2280_usb_regs *usb; ++ struct net2280_pci_regs *pci; ++ struct net2280_dma_regs *dma; ++ struct net2280_dep_regs *dep; ++ struct net2280_ep_regs *epregs; ++ ++ struct pci_pool *requests; ++ // statistics... ++}; ++ ++static inline void set_halt (struct net2280_ep *ep) ++{ ++ /* ep0 and bulk/intr endpoints */ ++ writel ( (1 << CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE) ++ /* set NAK_OUT for erratum 0114 */ ++ | ((ep->dev->chiprev == CHIPREV_1) << SET_NAK_OUT_PACKETS) ++ | (1 << SET_ENDPOINT_HALT) ++ , &ep->regs->ep_rsp); ++} ++ ++static inline void clear_halt (struct net2280_ep *ep) ++{ ++ /* ep0 and bulk/intr endpoints */ ++ writel ( (1 << CLEAR_ENDPOINT_HALT) ++ | (1 << CLEAR_ENDPOINT_TOGGLE) ++ /* unless the gadget driver left a short packet in the ++ * fifo, this reverses the erratum 0114 workaround. ++ */ ++ | ((ep->dev->chiprev == CHIPREV_1) << CLEAR_NAK_OUT_PACKETS) ++ , &ep->regs->ep_rsp); ++} ++ ++#ifdef USE_RDK_LEDS ++ ++static inline void net2280_led_init (struct net2280 *dev) ++{ ++ /* LED3 (green) is on during USB activity. note erratum 0113. */ ++ writel ((1 << GPIO3_LED_SELECT) ++ | (1 << GPIO3_OUTPUT_ENABLE) ++ | (1 << GPIO2_OUTPUT_ENABLE) ++ | (1 << GPIO1_OUTPUT_ENABLE) ++ | (1 << GPIO0_OUTPUT_ENABLE) ++ , &dev->regs->gpioctl); ++} ++ ++/* indicate speed with bi-color LED 0/1 */ ++static inline ++void net2280_led_speed (struct net2280 *dev, enum usb_device_speed speed) ++{ ++ u32 val = readl (&dev->regs->gpioctl); ++ switch (speed) { ++ case USB_SPEED_HIGH: /* green */ ++ val &= ~(1 << GPIO0_DATA); ++ val |= (1 << GPIO1_DATA); ++ break; ++ case USB_SPEED_FULL: /* red */ ++ val &= ~(1 << GPIO1_DATA); ++ val |= (1 << GPIO0_DATA); ++ break; ++ default: /* (off/black) */ ++ val &= ~((1 << GPIO1_DATA) | (1 << GPIO0_DATA)); ++ break; ++ } ++ writel (val, &dev->regs->gpioctl); ++} ++ ++/* indicate power with LED 2 */ ++static inline void net2280_led_active (struct net2280 *dev, int is_active) ++{ ++ u32 val = readl (&dev->regs->gpioctl); ++ ++ // FIXME this LED never seems to turn on. ++ if (is_active) ++ val |= GPIO2_DATA; ++ else ++ val &= ~GPIO2_DATA; ++ writel (val, &dev->regs->gpioctl); ++} ++static inline void net2280_led_shutdown (struct net2280 *dev) ++{ ++ /* turn off all four GPIO*_DATA bits */ ++ writel (readl (&dev->regs->gpioctl) & ~0x0f, ++ &dev->regs->gpioctl); ++} ++ ++#else ++ ++#define net2280_led_init(dev) do { } while (0) ++#define net2280_led_speed(dev, speed) do { } while (0) ++#define net2280_led_shutdown(dev) do { } while (0) ++ ++#endif ++ ++/*-------------------------------------------------------------------------*/ ++ ++#define xprintk(dev,level,fmt,args...) \ ++ printk(level "%s %s: " fmt , driver_name , \ ++ dev->pdev->slot_name , ## args) ++ ++#ifdef DEBUG ++#undef DEBUG ++#define DEBUG(dev,fmt,args...) \ ++ xprintk(dev , KERN_DEBUG , fmt , ## args) ++#else ++#define DEBUG(dev,fmt,args...) \ ++ do { } while (0) ++#endif /* DEBUG */ ++ ++#ifdef VERBOSE ++#define VDEBUG DEBUG ++#else ++#define VDEBUG(dev,fmt,args...) \ ++ do { } while (0) ++#endif /* VERBOSE */ ++ ++#define ERROR(dev,fmt,args...) \ ++ xprintk(dev , KERN_ERR , fmt , ## args) ++#define WARN(dev,fmt,args...) \ ++ xprintk(dev , KERN_WARNING , fmt , ## args) ++#define INFO(dev,fmt,args...) \ ++ xprintk(dev , KERN_INFO , fmt , ## args) ++ ++/*-------------------------------------------------------------------------*/ ++ ++static inline void start_out_naking (struct net2280_ep *ep) ++{ ++ /* NOTE: hardware races lurk here, and PING protocol issues */ ++ writel ((1 << SET_NAK_OUT_PACKETS), &ep->regs->ep_rsp); ++ /* synch with device */ ++ readl (&ep->regs->ep_rsp); ++} ++ ++#ifdef DEBUG ++static inline void assert_out_naking (struct net2280_ep *ep, const char *where) ++{ ++ u32 tmp = readl (&ep->regs->ep_stat); ++ ++ if ((tmp & (1 << NAK_OUT_PACKETS)) == 0) { ++ DEBUG (ep->dev, "%s %s %08x !NAK\n", ++ ep->ep.name, where, tmp); ++ writel ((1 << SET_NAK_OUT_PACKETS), ++ &ep->regs->ep_rsp); ++ } ++} ++#define ASSERT_OUT_NAKING(ep) assert_out_naking(ep,__FUNCTION__) ++#else ++#define ASSERT_OUT_NAKING(ep) do {} while (0) ++#endif ++ ++static inline void stop_out_naking (struct net2280_ep *ep) ++{ ++ u32 tmp; ++ ++ tmp = readl (&ep->regs->ep_stat); ++ if ((tmp & (1 << NAK_OUT_PACKETS)) != 0) ++ writel ((1 << CLEAR_NAK_OUT_PACKETS), &ep->regs->ep_rsp); ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* 2.5 and 2.4.older portability changes ... */ ++ ++#ifndef container_of ++#define container_of list_entry ++#endif ++ ++#ifndef likely ++#define likely(x) (x) ++#define unlikely(x) (x) ++#endif ++ ++#ifndef BUG_ON ++#define BUG_ON(condition) do { if (unlikely((condition)!=0)) BUG(); } while(0) ++#endif ++ ++#ifndef WARN_ON ++#define WARN_ON(x) do { } while (0) ++#endif ++ ++#ifndef IRQ_NONE ++typedef void irqreturn_t; ++#define IRQ_NONE ++#define IRQ_HANDLED ++#define IRQ_RETVAL(x) ++#endif ++ ++#endif /* __KERNEL__ */ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/pxa2xx_udc.c kernel/drivers/usb/gadget/pxa2xx_udc.c +--- /tmp/kernel/drivers/usb/gadget/pxa2xx_udc.c 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/pxa2xx_udc.c 2005-04-22 17:53:19.492530073 +0200 +@@ -0,0 +1,2486 @@ ++/* ++ * linux/drivers/usb/gadget/pxa2xx_udc.c ++ * Intel PXA2xx and IXP4xx on-chip full speed USB device controllers ++ * ++ * Copyright (C) 2002 Intrinsyc, Inc. (Frank Becker) ++ * Copyright (C) 2003 Robert Schwebel, Pengutronix ++ * Copyright (C) 2003 Benedikt Spranger, Pengutronix ++ * Copyright (C) 2003 David Brownell ++ * ++ * 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 ++ * ++ */ ++ ++#define DEBUG 1 ++// #define VERBOSE DBG_VERBOSE ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++// #include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++ ++/* ++ * This driver handles the USB Device Controller (UDC) in Intel's PXA 2xx ++ * series processors. The UDC for the IXP 4xx series is very similar. ++ * There are fifteen endpoints, in addition to ep0. ++ * ++ * Such controller drivers work with a gadget driver. The gadget driver ++ * returns descriptors, implements configuration and data protocols used ++ * by the host to interact with this device, and allocates endpoints to ++ * the different protocol interfaces. The controller driver virtualizes ++ * usb hardware so that the gadget drivers will be more portable. ++ * ++ * This UDC hardware wants to implement a bit too much USB protocol, so ++ * it constrains the sorts of USB configuration change events that work. ++ * The errata for these chips are misleading; some "fixed" bugs from ++ * pxa250 a0/a1 b0/b1/b2 sure act like they're still there. ++ */ ++ ++/* NOTE: the 2.6 driver is probably the most current version */ ++#define DRIVER_VERSION "5-Jan-2004" ++#define DRIVER_DESC "PXA 2xx USB Device Controller driver" ++ ++static const char driver_name [] = "pxa2xx_udc"; ++ ++static const char ep0name [] = "ep0"; ++ ++ ++// #define USE_DMA ++// #define USE_OUT_DMA ++// #define DISABLE_TEST_MODE ++ ++#ifdef CONFIG_PROC_FS ++#define UDC_PROC_FILE ++#endif ++ ++#ifdef CONFIG_ARCH_IXP425 ++#undef USE_DMA ++ ++/* cpu-specific register addresses are compiled in to this code */ ++#ifdef CONFIG_ARCH_PXA ++#error "Can't configure both IXP and PXA" ++#endif ++ ++#endif ++ ++#ifdef CONFIG_EMBEDDED ++/* few strings, and little code to use them */ ++#undef DEBUG ++#undef UDC_PROC_FILE ++#endif ++ ++ ++#include "pxa2xx_udc.h" ++ ++#ifdef USE_DMA ++static int use_dma = 1; ++MODULE_PARM (use_dma, "i"); ++MODULE_PARM_DESC (use_dma, "true to use dma"); ++ ++static void dma_nodesc_handler (int dmach, void *_ep, struct pt_regs *r); ++static void kick_dma(struct pxa2xx_ep *ep, struct pxa2xx_request *req); ++ ++#ifdef USE_OUT_DMA ++#define DMASTR " (dma support)" ++#else ++#define DMASTR " (dma in)" ++#endif ++ ++#else /* !USE_DMA */ ++#define DMASTR " (pio only)" ++#undef USE_OUT_DMA ++#endif ++ ++#ifdef CONFIG_USB_PXA2XX_SMALL ++#define SIZE_STR " (small)" ++#else ++#define SIZE_STR "" ++#endif ++ ++#ifdef DISABLE_TEST_MODE ++/* (mode == 0) == no undocumented chip tweaks ++ * (mode & 1) == double buffer bulk IN ++ * (mode & 2) == double buffer bulk OUT ++ * ... so mode = 3 (or 7, 15, etc) does it for both ++ */ ++static ushort fifo_mode = 0; ++MODULE_PARM (fifo_mode, "h"); ++MODULE_PARM_DESC (fifo_mode, "pxa2xx udc fifo mode"); ++#endif ++ ++/* --------------------------------------------------------------------------- ++ * endpoint related parts of the api to the usb controller hardware, ++ * used by gadget driver; and the inner talker-to-hardware core. ++ * --------------------------------------------------------------------------- ++ */ ++ ++static void pxa2xx_ep_fifo_flush (struct usb_ep *ep); ++static void nuke (struct pxa2xx_ep *, int status); ++ ++static void pio_irq_enable(int bEndpointAddress) ++{ ++ bEndpointAddress &= 0xf; ++ if (bEndpointAddress < 8) ++ UICR0 &= ~(1 << bEndpointAddress); ++ else { ++ bEndpointAddress -= 8; ++ UICR1 &= ~(1 << bEndpointAddress); ++ } ++} ++ ++static void pio_irq_disable(int bEndpointAddress) ++{ ++ bEndpointAddress &= 0xf; ++ if (bEndpointAddress < 8) ++ UICR0 |= 1 << bEndpointAddress; ++ else { ++ bEndpointAddress -= 8; ++ UICR1 |= 1 << bEndpointAddress; ++ } ++} ++ ++/* The UDCCR reg contains mask and interrupt status bits, ++ * so using '|=' isn't safe as it may ack an interrupt. ++ */ ++#define UDCCR_MASK_BITS (UDCCR_REM | UDCCR_SRM | UDCCR_UDE) ++ ++static inline void udc_set_mask_UDCCR(int mask) ++{ ++ UDCCR = (UDCCR & UDCCR_MASK_BITS) | (mask & UDCCR_MASK_BITS); ++} ++ ++static inline void udc_clear_mask_UDCCR(int mask) ++{ ++ UDCCR = (UDCCR & UDCCR_MASK_BITS) & ~(mask & UDCCR_MASK_BITS); ++} ++ ++static inline void udc_ack_int_UDCCR(int mask) ++{ ++ /* udccr contains the bits we dont want to change */ ++ __u32 udccr = UDCCR & UDCCR_MASK_BITS; ++ ++ UDCCR = udccr | (mask & ~UDCCR_MASK_BITS); ++} ++ ++/* ++ * endpoint enable/disable ++ * ++ * we need to verify the descriptors used to enable endpoints. since pxa2xx ++ * endpoint configurations are fixed, and are pretty much always enabled, ++ * there's not a lot to manage here. ++ * ++ * because pxa2xx can't selectively initialize bulk (or interrupt) endpoints, ++ * (resetting endpoint halt and toggle), SET_INTERFACE is unusable except ++ * for a single interface (with only the default altsetting) and for gadget ++ * drivers that don't halt endpoints (not reset by set_interface). that also ++ * means that if you use ISO, you must violate the USB spec rule that all ++ * iso endpoints must be in non-default altsettings. ++ */ ++static int pxa2xx_ep_enable (struct usb_ep *_ep, ++ const struct usb_endpoint_descriptor *desc) ++{ ++ struct pxa2xx_ep *ep; ++ struct pxa2xx_udc *dev; ++ ++ ep = container_of (_ep, struct pxa2xx_ep, ep); ++ if (!_ep || !desc || ep->desc || _ep->name == ep0name ++ || desc->bDescriptorType != USB_DT_ENDPOINT ++ || ep->bEndpointAddress != desc->bEndpointAddress ++ || ep->fifo_size < le16_to_cpu ++ (desc->wMaxPacketSize)) { ++ DMSG("%s, bad ep or descriptor\n", __FUNCTION__); ++ return -EINVAL; ++ } ++ ++ /* xfer types must match, except that interrupt ~= bulk */ ++ if (ep->bmAttributes != desc->bmAttributes ++ && ep->bmAttributes != USB_ENDPOINT_XFER_BULK ++ && desc->bmAttributes != USB_ENDPOINT_XFER_INT) { ++ DMSG("%s, %s type mismatch\n", __FUNCTION__, _ep->name); ++ return -EINVAL; ++ } ++ ++ /* hardware _could_ do smaller, but driver doesn't */ ++ if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK ++ && le16_to_cpu (desc->wMaxPacketSize) ++ != BULK_FIFO_SIZE) ++ || !desc->wMaxPacketSize) { ++ DMSG("%s, bad %s maxpacket\n", __FUNCTION__, _ep->name); ++ return -ERANGE; ++ } ++ ++ dev = ep->dev; ++ if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { ++ DMSG("%s, bogus device state\n", __FUNCTION__); ++ return -ESHUTDOWN; ++ } ++ ++ ep->desc = desc; ++ ep->dma = -1; ++ ep->stopped = 0; ++ ep->pio_irqs = ep->dma_irqs = 0; ++ ep->ep.maxpacket = le16_to_cpu (desc->wMaxPacketSize); ++ ++ /* flush fifo (mostly for OUT buffers) */ ++ pxa2xx_ep_fifo_flush (_ep); ++ ++ /* ... reset halt state too, if we could ... */ ++ ++#ifdef USE_DMA ++ /* for (some) bulk and ISO endpoints, try to get a DMA channel and ++ * bind it to the endpoint. otherwise use PIO. ++ */ ++ switch (ep->bmAttributes) { ++ case USB_ENDPOINT_XFER_ISOC: ++ if (le16_to_cpu(desc->wMaxPacketSize) % 32) ++ break; ++ // fall through ++ case USB_ENDPOINT_XFER_BULK: ++ if (!use_dma || !ep->reg_drcmr) ++ break; ++ /* no bulk-out dma yet (pointless w/o descriptors) */ ++ if ((ep->bmAttributes == USB_ENDPOINT_XFER_BULK) ++ && (ep->bEndpointAddress & USB_DIR_IN) == 0) { ++ DMSG("%s dma-out NYI\n", _ep->name); ++ break; ++ } ++ ep->dma = pxa_request_dma ((char *)_ep->name, ++ (le16_to_cpu(desc->wMaxPacketSize) > 64) ++ ? DMA_PRIO_MEDIUM /* some iso */ ++ : DMA_PRIO_LOW, ++ // FIXME or ep_out_dma .. .. ++ dma_nodesc_handler, ep); ++ if (ep->dma >= 0) { ++ *ep->reg_drcmr = DRCMR_MAPVLD | ep->dma; ++ DMSG("%s using dma%d\n", _ep->name, ep->dma); ++ } ++ } ++#endif ++ ++ DBG(DBG_VERBOSE, "enabled %s\n", _ep->name); ++ return 0; ++} ++ ++static int pxa2xx_ep_disable (struct usb_ep *_ep) ++{ ++ struct pxa2xx_ep *ep; ++ ++ ep = container_of (_ep, struct pxa2xx_ep, ep); ++ if (!_ep || !ep->desc) { ++ DMSG("%s, %s not enabled\n", __FUNCTION__, ++ _ep ? ep->ep.name : NULL); ++ return -EINVAL; ++ } ++ nuke (ep, -ESHUTDOWN); ++ ++#ifdef USE_DMA ++ if (ep->dma >= 0) { ++ *ep->reg_drcmr = 0; ++ pxa_free_dma (ep->dma); ++ ep->dma = -1; ++ } ++#endif ++ ++ /* flush fifo (mostly for IN buffers) */ ++ pxa2xx_ep_fifo_flush (_ep); ++ ++ ep->desc = 0; ++ ep->stopped = 1; ++ ++ DBG(DBG_VERBOSE, "%s disabled\n", _ep->name); ++ return 0; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* for the pxa2xx, these can just wrap kmalloc/kfree. gadget drivers ++ * must still pass correctly initialized endpoints, since other controller ++ * drivers may care about how it's currently set up (dma issues etc). ++ */ ++ ++/* ++ * pxa2xx_ep_alloc_request - allocate a request data structure ++ */ ++static struct usb_request * ++pxa2xx_ep_alloc_request (struct usb_ep *_ep, int gfp_flags) ++{ ++ struct pxa2xx_request *req; ++ ++ /* FIXME for bulk out-dma endpoints, preallocate a frame's worth of ++ * (aligned) dma descriptors at the end of the request ++ */ ++ ++ req = kmalloc (sizeof *req, gfp_flags); ++ if (!req) ++ return 0; ++ ++ memset (req, 0, sizeof *req); ++ INIT_LIST_HEAD (&req->queue); ++ return &req->req; ++} ++ ++ ++/* ++ * pxa2xx_ep_free_request - deallocate a request data structure ++ */ ++static void ++pxa2xx_ep_free_request (struct usb_ep *_ep, struct usb_request *_req) ++{ ++ struct pxa2xx_request *req; ++ ++ req = container_of (_req, struct pxa2xx_request, req); ++ WARN_ON (!list_empty (&req->queue)); ++ kfree(req); ++} ++ ++ ++/* PXA cache needs flushing with DMA I/O (it's dma-incoherent), but there's ++ * no device-affinity and the heap works perfectly well for i/o buffers. ++ */ ++static void * ++pxa2xx_ep_alloc_buffer(struct usb_ep *_ep, unsigned bytes, ++ dma_addr_t *dma, int gfp_flags) ++{ ++ char *retval; ++ ++ retval = kmalloc (bytes, gfp_flags & ~(__GFP_DMA|__GFP_HIGHMEM)); ++ if (retval) ++ *dma = virt_to_bus (retval); ++ return retval; ++} ++ ++static void ++pxa2xx_ep_free_buffer(struct usb_ep *_ep, void *buf, dma_addr_t dma, ++ unsigned bytes) ++{ ++ kfree (buf); ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* ++ * done - retire a request; caller blocked irqs ++ */ ++static void done(struct pxa2xx_ep *ep, struct pxa2xx_request *req, int status) ++{ ++ unsigned stopped = ep->stopped; ++ ++ list_del_init(&req->queue); ++ ++ if (likely (req->req.status == -EINPROGRESS)) ++ req->req.status = status; ++ else ++ status = req->req.status; ++ ++ if (status && status != -ESHUTDOWN) ++ DBG(DBG_VERBOSE, "complete %s req %p stat %d len %u/%u\n", ++ ep->ep.name, &req->req, status, ++ req->req.actual, req->req.length); ++ ++ /* don't modify queue heads during completion callback */ ++ ep->stopped = 1; ++ req->req.complete(&ep->ep, &req->req); ++ ep->stopped = stopped; ++} ++ ++ ++static inline void ep0_idle (struct pxa2xx_udc *dev) ++{ ++ dev->ep0state = EP0_IDLE; ++ LED_EP0_OFF; ++} ++ ++static int ++write_packet(volatile u32 *uddr, struct pxa2xx_request *req, unsigned max) ++{ ++ u8 *buf; ++ unsigned length, count; ++ ++ buf = req->req.buf + req->req.actual; ++ prefetch(buf); ++ ++ /* how big will this packet be? */ ++ length = min(req->req.length - req->req.actual, max); ++ req->req.actual += length; ++ ++ count = length; ++ while (likely(count--)) ++ *uddr = *buf++; ++ ++ return length; ++} ++ ++/* ++ * write to an IN endpoint fifo, as many packets as possible. ++ * irqs will use this to write the rest later. ++ * caller guarantees at least one packet buffer is ready (or a zlp). ++ */ ++static int ++write_fifo (struct pxa2xx_ep *ep, struct pxa2xx_request *req) ++{ ++ unsigned max; ++ ++ max = le16_to_cpu(ep->desc->wMaxPacketSize); ++ do { ++ unsigned count; ++ int is_last, is_short; ++ ++ count = write_packet(ep->reg_uddr, req, max); ++ ++ /* last packet is usually short (or a zlp) */ ++ if (unlikely (count != max)) ++ is_last = is_short = 1; ++ else { ++ if (likely(req->req.length != req->req.actual) ++ || req->req.zero) ++ is_last = 0; ++ else ++ is_last = 1; ++ /* interrupt/iso maxpacket may not fill the fifo */ ++ is_short = unlikely (max < ep->fifo_size); ++ } ++ ++ DBG(DBG_VERY_NOISY, "wrote %s %d bytes%s%s %d left %p\n", ++ ep->ep.name, count, ++ is_last ? "/L" : "", is_short ? "/S" : "", ++ req->req.length - req->req.actual, req); ++ ++ /* let loose that packet. maybe try writing another one, ++ * double buffering might work. TSP, TPC, and TFS ++ * bit values are the same for all normal IN endpoints. ++ */ ++ *ep->reg_udccs = UDCCS_BI_TPC; ++ if (is_short) ++ *ep->reg_udccs = UDCCS_BI_TSP; ++ ++ /* requests complete when all IN data is in the FIFO */ ++ if (is_last) { ++ done (ep, req, 0); ++ if (list_empty(&ep->queue) || unlikely(ep->dma >= 0)) ++ pio_irq_disable (ep->bEndpointAddress); ++#ifdef USE_DMA ++ if (unlikely(ep->dma >= 0) && !list_empty(&ep->queue)) { ++DMSG("%s pio2dma\n", ep->ep.name); ++ req = list_entry(ep->queue.next, ++ struct pxa2xx_request, queue); ++ kick_dma(ep,req); ++ return 0; ++ } ++#endif ++ return 1; ++ } ++ ++ // TODO experiment: how robust can fifo mode tweaking be? ++ // the double buffering could speed up I/O a bunch. ++ ++ } while (*ep->reg_udccs & UDCCS_BI_TFS); ++ return 0; ++} ++ ++/* caller asserts req->pending (ep0 irq status nyet cleared); starts ++ * ep0 data stage. these chips want very simple state transitions. ++ */ ++static inline ++void ep0start(struct pxa2xx_udc *dev, u32 flags, const char *tag) ++{ ++ UDCCS0 = flags|UDCCS0_SA|UDCCS0_OPR; ++ USIR0 = USIR0_IR0; ++ dev->req_pending = 0; ++ DBG(DBG_VERY_NOISY, "%s %s, %02x/%02x\n", ++ __FUNCTION__, tag, UDCCS0, flags); ++} ++ ++static int ++write_ep0_fifo (struct pxa2xx_ep *ep, struct pxa2xx_request *req) ++{ ++ unsigned count; ++ int is_short; ++ ++ count = write_packet(&UDDR0, req, EP0_FIFO_SIZE); ++ ep->dev->stats.write.bytes += count; ++ ++ /* last packet "must be" short (or a zlp) */ ++ is_short = (count != EP0_FIFO_SIZE); ++ ++ DBG(DBG_VERY_NOISY, "ep0in %d bytes %d left %p\n", count, ++ req->req.length - req->req.actual, req); ++ ++ if (unlikely (is_short)) { ++ if (ep->dev->req_pending) ++ ep0start(ep->dev, UDCCS0_IPR, "short IN"); ++ else ++ UDCCS0 = UDCCS0_IPR; ++ ++ count = req->req.length; ++ done (ep, req, 0); ++ ep0_idle(ep->dev); ++#if 1 ++ /* This seems to get rid of lost status irqs in some cases: ++ * host responds quickly, or next request involves config ++ * change automagic, or should have been hidden, or ... ++ * ++ * FIXME get rid of all udelays possible... ++ */ ++ if (count >= EP0_FIFO_SIZE) { ++ count = 100; ++ do { ++ if ((UDCCS0 & UDCCS0_OPR) != 0) { ++ /* clear OPR, generate ack */ ++ UDCCS0 = UDCCS0_OPR; ++ break; ++ } ++ count--; ++ udelay(1); ++ } while (count); ++ } ++#endif ++ } else if (ep->dev->req_pending) ++ ep0start(ep->dev, 0, "IN"); ++ return is_short; ++} ++ ++ ++/* ++ * read_fifo - unload packet(s) from the fifo we use for usb OUT ++ * transfers and put them into the request. caller should have made ++ * sure there's at least one packet ready. ++ * ++ * returns true if the request completed because of short packet or the ++ * request buffer having filled (and maybe overran till end-of-packet). ++ */ ++static int ++read_fifo (struct pxa2xx_ep *ep, struct pxa2xx_request *req) ++{ ++ for (;;) { ++ u32 udccs; ++ u8 *buf; ++ unsigned bufferspace, count, is_short; ++ ++ /* make sure there's a packet in the FIFO. ++ * UDCCS_{BO,IO}_RPC are all the same bit value. ++ * UDCCS_{BO,IO}_RNE are all the same bit value. ++ */ ++ udccs = *ep->reg_udccs; ++ if (unlikely ((udccs & UDCCS_BO_RPC) == 0)) ++ break; ++ buf = req->req.buf + req->req.actual; ++ prefetchw(buf); ++ bufferspace = req->req.length - req->req.actual; ++ ++ /* read all bytes from this packet */ ++ if (likely (udccs & UDCCS_BO_RNE)) { ++ count = 1 + (0x0ff & *ep->reg_ubcr); ++ req->req.actual += min (count, bufferspace); ++ } else /* zlp */ ++ count = 0; ++ is_short = (count < ep->ep.maxpacket); ++ DBG(DBG_VERY_NOISY, "read %s %02x, %d bytes%s req %p %d/%d\n", ++ ep->ep.name, udccs, count, ++ is_short ? "/S" : "", ++ req, req->req.actual, req->req.length); ++ while (likely (count-- != 0)) { ++ u8 byte = (u8) *ep->reg_uddr; ++ ++ if (unlikely (bufferspace == 0)) { ++ /* this happens when the driver's buffer ++ * is smaller than what the host sent. ++ * discard the extra data. ++ */ ++ if (req->req.status != -EOVERFLOW) ++ DMSG("%s overflow %d\n", ++ ep->ep.name, count); ++ req->req.status = -EOVERFLOW; ++ } else { ++ *buf++ = byte; ++ bufferspace--; ++ } ++ } ++ *ep->reg_udccs = UDCCS_BO_RPC; ++ /* RPC/RSP/RNE could now reflect the other packet buffer */ ++ ++ /* iso is one request per packet */ ++ if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) { ++ if (udccs & UDCCS_IO_ROF) ++ req->req.status = -EHOSTUNREACH; ++ /* more like "is_done" */ ++ is_short = 1; ++ } ++ ++ /* completion */ ++ if (is_short || req->req.actual == req->req.length) { ++ done (ep, req, 0); ++ if (list_empty(&ep->queue)) ++ pio_irq_disable (ep->bEndpointAddress); ++ return 1; ++ } ++ ++ /* finished that packet. the next one may be waiting... */ ++ } ++ return 0; ++} ++ ++/* ++ * special ep0 version of the above. no UBCR0 or double buffering; status ++ * handshaking is magic. most device protocols don't need control-OUT. ++ * CDC vendor commands (and RNDIS), mass storage CB/CBI, and some other ++ * protocols do use them. ++ */ ++static int ++read_ep0_fifo (struct pxa2xx_ep *ep, struct pxa2xx_request *req) ++{ ++ u8 *buf, byte; ++ unsigned bufferspace; ++ ++ buf = req->req.buf + req->req.actual; ++ bufferspace = req->req.length - req->req.actual; ++ ++ while (UDCCS0 & UDCCS0_RNE) { ++ byte = (u8) UDDR0; ++ ++ if (unlikely (bufferspace == 0)) { ++ /* this happens when the driver's buffer ++ * is smaller than what the host sent. ++ * discard the extra data. ++ */ ++ if (req->req.status != -EOVERFLOW) ++ DMSG("%s overflow\n", ep->ep.name); ++ req->req.status = -EOVERFLOW; ++ } else { ++ *buf++ = byte; ++ req->req.actual++; ++ bufferspace--; ++ } ++ } ++ ++ UDCCS0 = UDCCS0_OPR | UDCCS0_IPR; ++ ++ /* completion */ ++ if (req->req.actual >= req->req.length) ++ return 1; ++ ++ /* finished that packet. the next one may be waiting... */ ++ return 0; ++} ++ ++#ifdef USE_DMA ++ ++static inline void ++start_dma_nodesc(struct pxa2xx_ep *ep, struct pxa2xx_request *req, int is_in) ++{ ++ u32 dcmd = req->req.length; ++ u32 buf = virt_to_bus (req->req.buf); ++ u32 fifo = io_v2p ((u32)ep->reg_uddr); ++ ++ /* no-descriptor mode can be simple for bulk-in, iso-in, iso-out */ ++ DCSR(ep->dma) = DCSR_NODESC; ++ dcmd |= DCMD_BURST32 | DCMD_ENDIRQEN | DCMD_WIDTH1; ++ if (is_in) { ++ DSADR(ep->dma) = buf; ++ DTADR(ep->dma) = fifo; ++ dcmd |= DCMD_FLOWTRG | DCMD_INCSRCADDR; ++ } else { ++ DSADR(ep->dma) = fifo; ++ DTADR(ep->dma) = buf; ++ dcmd |= DCMD_FLOWSRC | DCMD_INCTRGADDR; ++ } ++ DCMD(ep->dma) = dcmd; ++ DCSR(ep->dma) = DCSR_RUN | DCSR_STOPIRQEN | DCSR_NODESC; ++ /* and later the dma handler gets called */ ++} ++ ++static void kick_dma(struct pxa2xx_ep *ep, struct pxa2xx_request *req) ++{ ++ if (ep->bEndpointAddress & USB_DIR_IN) { ++ /* docs imply we can't preload with pio */ ++ if ((((u32)req->req.buf) & 0x0f) != 0) { ++// VERBOSE ++ DMSG("%s bad DMA align %p\n", ++ ep->ep.name, req->req.buf); ++pio_in: ++// FIXME PIO fallback doesn't work right yet (recovery?) ++DMSG("%s dma2pio\n", ep->ep.name); ++ pio_irq_enable(ep->bEndpointAddress); ++ if ((*ep->reg_udccs & UDCCS_BI_TFS) != 0) ++ (void) write_fifo(ep, req); ++ return; ++ } ++ /* dmacount 0 means end-of-transfer */ ++ if (unlikely((req->req.length - req->req.actual) == 0)) { ++// VERBOSE ++ DMSG("%s zlp dma write...\n", ep->ep.name); ++ goto pio_in; ++ } ++ start_dma_nodesc(ep, req, USB_DIR_IN); ++ } else { ++ // if ISO, use no-descriptor DMA ++ BUG(); ++ } ++} ++ ++static void cancel_dma(struct pxa2xx_ep *ep) ++{ ++ struct pxa2xx_request *req; ++ u32 tmp; ++ ++ if (DCSR(ep->dma) == 0 || list_empty(&ep->queue)) ++ return; ++ ++ DCSR(ep->dma) = 0; ++ while ((DCSR(ep->dma) & DCSR_STOPSTATE) == 0) ++ cpu_relax(); ++ ++ req = list_entry(ep->queue.next, struct pxa2xx_request, queue); ++ tmp = DCMD(ep->dma) & DCMD_LENGTH; ++ req->req.actual = req->req.length - (tmp & DCMD_LENGTH); ++ ++ /* the last tx packet may be incomplete, so flush the fifo. ++ * FIXME correct req.actual if we can ++ */ ++ if (ep->bEndpointAddress & USB_DIR_IN) ++ *ep->reg_udccs = UDCCS_BI_FTF; ++} ++ ++static void dma_nodesc_handler(int dmach, void *_ep, struct pt_regs *r) ++{ ++ struct pxa2xx_ep *ep = _ep; ++ struct pxa2xx_request *req; ++ u32 tmp; ++ ++ req = list_entry(ep->queue.next, struct pxa2xx_request, queue); ++ ++ ep->dev->stats.irqs++; ++ HEX_DISPLAY(ep->dev->stats.irqs); ++ ++ /* ack/clear */ ++ tmp = DCSR(ep->dma); ++ DCSR(ep->dma) = tmp; ++ if ((tmp & DCSR_STOPSTATE) == 0 ++ || (DDADR(ep->dma) & DDADR_STOP) != 0) { ++ DBG(DBG_VERBOSE, "%s, dcsr %08x ddadr %08x\n", ++ ep->ep.name, DCSR(ep->dma), DDADR(ep->dma)); ++ return; ++ } ++ DCSR(ep->dma) = 0; /* clear DCSR_STOPSTATE */ ++ ++ /* wrap up the transfer, and collect status */ ++ if (unlikely(tmp & DCSR_BUSERR)) ++ req->req.status = -EIO; ++ tmp = DCMD(ep->dma); ++ req->req.actual = req->req.length - (tmp & DCMD_LENGTH); ++ tmp = 1; /* normally this is the last packet */ ++ ++ if (ep->bEndpointAddress & USB_DIR_IN) { ++ /* maybe validate final short packet */ ++ if ((ep->bmAttributes == USB_ENDPOINT_XFER_BULK ++ && req->req.actual % BULK_FIFO_SIZE) ++ || (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC ++ && req->req.actual % ISO_FIFO_SIZE)) ++ *ep->reg_udccs = UDCCS_BI_TSP /*|UDCCS_BI_TPC*/; ++ ++ /* or force a zlp, with pio ... */ ++ else if (ep->bmAttributes == USB_ENDPOINT_XFER_BULK ++ && req->req.zero) { ++ tmp = 0; ++ } ++ // if iso, maybe report underrun (TUR) ++ } else { ++ BUG(); ++ } ++ ++ if (likely(tmp != 0)) ++ done(ep, req, 0); ++ ++ /* maybe re-activate after completion */ ++ if (ep->stopped || list_empty(&ep->queue)) ++ return; ++ req = list_entry(ep->queue.next, struct pxa2xx_request, queue); ++ kick_dma(ep, req); ++} ++ ++#endif ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int ++pxa2xx_ep_queue(struct usb_ep *_ep, struct usb_request *_req, int gfp_flags) ++{ ++ struct pxa2xx_request *req; ++ struct pxa2xx_ep *ep; ++ struct pxa2xx_udc *dev; ++ unsigned long flags; ++ ++ req = container_of(_req, struct pxa2xx_request, req); ++ if (unlikely (!_req || !_req->complete || !_req->buf ++ || !list_empty(&req->queue))) { ++ DMSG("%s, bad params\n", __FUNCTION__); ++ return -EINVAL; ++ } ++ ++ ep = container_of(_ep, struct pxa2xx_ep, ep); ++ if (unlikely (!_ep || (!ep->desc && ep->ep.name != ep0name))) { ++ DMSG("%s, bad ep\n", __FUNCTION__); ++ return -EINVAL; ++ } ++ ++ dev = ep->dev; ++ if (unlikely (!dev->driver ++ || dev->gadget.speed == USB_SPEED_UNKNOWN)) { ++ DMSG("%s, bogus device state\n", __FUNCTION__); ++ return -ESHUTDOWN; ++ } ++ ++ /* iso is always one packet per request, that's the only way ++ * we can report per-packet status. that also helps with dma. ++ */ ++ if (unlikely (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC ++ && req->req.length > le16_to_cpu ++ (ep->desc->wMaxPacketSize))) ++ return -EMSGSIZE; ++ ++#ifdef USE_DMA ++ if (ep->dma >= 0) { ++ unsigned long start = (unsigned long) _req->buf; ++ ++ clean_dcache_range(start, start + _req->length); ++ /* or for USB_DIR_OUT, invalidate_dcache_range (...) */ ++ } ++#endif ++ ++ DBG(DBG_NOISY, "%s queue req %p, len %d buf %p\n", ++ _ep->name, _req, _req->length, _req->buf); ++ ++ local_irq_save(flags); ++ ++ _req->status = -EINPROGRESS; ++ _req->actual = 0; ++ ++ /* kickstart this i/o queue? */ ++ if (list_empty(&ep->queue) && !ep->stopped) { ++ if (ep->desc == 0 /* ep0 */) { ++ unsigned length = _req->length; ++ ++ switch (dev->ep0state) { ++ case EP0_IN_DATA_PHASE: ++ dev->stats.write.ops++; ++ if (write_ep0_fifo(ep, req)) ++ req = 0; ++ break; ++ ++ case EP0_OUT_DATA_PHASE: ++ dev->stats.read.ops++; ++ /* messy ... */ ++ if (dev->req_config) { ++ DBG(DBG_VERBOSE, "ep0 config ack%s\n", ++ dev->has_cfr ? "" : " raced"); ++ if (dev->has_cfr) ++ UDCCFR = UDCCFR_AREN|UDCCFR_ACM; ++ done(ep, req, 0); ++ dev->ep0state = EP0_END_XFER; ++ local_irq_restore (flags); ++ return 0; ++ } ++ if (dev->req_pending) ++ ep0start(dev, UDCCS0_IPR, "OUT"); ++ if (length == 0 || ((UDCCS0 & UDCCS0_RNE) != 0 ++ && read_ep0_fifo(ep, req))) { ++ ep0_idle(dev); ++ done(ep, req, 0); ++ req = 0; ++ } ++ break; ++ ++ default: ++ DMSG("ep0 i/o, odd state %d\n", dev->ep0state); ++ local_irq_restore (flags); ++ return -EL2HLT; ++ } ++#ifdef USE_DMA ++ /* either start dma or prime pio pump */ ++ } else if (ep->dma >= 0) { ++ kick_dma(ep, req); ++#endif ++ /* can the FIFO can satisfy the request immediately? */ ++ } else if ((ep->bEndpointAddress & USB_DIR_IN) != 0 ++ && (*ep->reg_udccs & UDCCS_BI_TFS) != 0 ++ && write_fifo(ep, req)) { ++ req = 0; ++ } else if ((*ep->reg_udccs & UDCCS_BO_RFS) != 0 ++ && read_fifo(ep, req)) { ++ req = 0; ++ } ++ ++ if (likely (req && ep->desc) && ep->dma < 0) ++ pio_irq_enable(ep->bEndpointAddress); ++ } ++ ++ /* pio or dma irq handler advances the queue. */ ++ if (likely (req != 0)) ++ list_add_tail(&req->queue, &ep->queue); ++ local_irq_restore(flags); ++ ++ return 0; ++} ++ ++ ++/* ++ * nuke - dequeue ALL requests ++ */ ++static void nuke(struct pxa2xx_ep *ep, int status) ++{ ++ struct pxa2xx_request *req; ++ ++ /* called with irqs blocked */ ++#ifdef USE_DMA ++ if (ep->dma >= 0 && !ep->stopped) ++ cancel_dma(ep); ++#endif ++ while (!list_empty(&ep->queue)) { ++ req = list_entry(ep->queue.next, ++ struct pxa2xx_request, ++ queue); ++ done(ep, req, status); ++ } ++ if (ep->desc) ++ pio_irq_disable (ep->bEndpointAddress); ++} ++ ++ ++/* dequeue JUST ONE request */ ++static int pxa2xx_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) ++{ ++ struct pxa2xx_ep *ep; ++ struct pxa2xx_request *req; ++ unsigned long flags; ++ ++ ep = container_of(_ep, struct pxa2xx_ep, ep); ++ if (!_ep || ep->ep.name == ep0name) ++ return -EINVAL; ++ ++ local_irq_save(flags); ++ ++ /* make sure it's actually queued on this endpoint */ ++ list_for_each_entry (req, &ep->queue, queue) { ++ if (&req->req == _req) ++ break; ++ } ++ if (&req->req != _req) { ++ local_irq_restore(flags); ++ return -EINVAL; ++ } ++ ++#ifdef USE_DMA ++ if (ep->dma >= 0 && ep->queue.next == &req->queue && !ep->stopped) { ++ cancel_dma(ep); ++ done(ep, req, -ECONNRESET); ++ /* restart i/o */ ++ if (!list_empty(&ep->queue)) { ++ req = list_entry(ep->queue.next, ++ struct pxa2xx_request, queue); ++ kick_dma(ep, req); ++ } ++ } else ++#endif ++ done(ep, req, -ECONNRESET); ++ ++ local_irq_restore(flags); ++ return 0; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static int pxa2xx_ep_set_halt(struct usb_ep *_ep, int value) ++{ ++ struct pxa2xx_ep *ep; ++ unsigned long flags; ++ ++ ep = container_of(_ep, struct pxa2xx_ep, ep); ++ if (unlikely (!_ep ++ || (!ep->desc && ep->ep.name != ep0name)) ++ || ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) { ++ DMSG("%s, bad ep\n", __FUNCTION__); ++ return -EINVAL; ++ } ++ if (value == 0) { ++ /* this path (reset toggle+halt) is needed to implement ++ * SET_INTERFACE on normal hardware. but it can't be ++ * done from software on the PXA UDC, and the hardware ++ * forgets to do it as part of SET_INTERFACE automagic. ++ */ ++ DMSG("only host can clear %s halt\n", _ep->name); ++ return -EROFS; ++ } ++ ++ local_irq_save(flags); ++ ++ if ((ep->bEndpointAddress & USB_DIR_IN) != 0 ++ && ((*ep->reg_udccs & UDCCS_BI_TFS) == 0 ++ || !list_empty(&ep->queue))) { ++ local_irq_restore(flags); ++ return -EAGAIN; ++ } ++ ++ /* FST bit is the same for control, bulk in, bulk out, interrupt in */ ++ *ep->reg_udccs = UDCCS_BI_FST|UDCCS_BI_FTF; ++ ++ /* ep0 needs special care */ ++ if (!ep->desc) { ++ start_watchdog(ep->dev); ++ ep->dev->req_pending = 0; ++ ep->dev->ep0state = EP0_STALL; ++ LED_EP0_OFF; ++ ++ /* and bulk/intr endpoints like dropping stalls too */ ++ } else { ++ unsigned i; ++ for (i = 0; i < 1000; i += 20) { ++ if (*ep->reg_udccs & UDCCS_BI_SST) ++ break; ++ udelay(20); ++ } ++ } ++ local_irq_restore(flags); ++ ++ DBG(DBG_VERBOSE, "%s halt\n", _ep->name); ++ return 0; ++} ++ ++static int pxa2xx_ep_fifo_status(struct usb_ep *_ep) ++{ ++ struct pxa2xx_ep *ep; ++ ++ ep = container_of(_ep, struct pxa2xx_ep, ep); ++ if (!_ep) { ++ DMSG("%s, bad ep\n", __FUNCTION__); ++ return -ENODEV; ++ } ++ /* pxa can't report unclaimed bytes from IN fifos */ ++ if ((ep->bEndpointAddress & USB_DIR_IN) != 0) ++ return -EOPNOTSUPP; ++ if (ep->dev->gadget.speed == USB_SPEED_UNKNOWN ++ || (*ep->reg_udccs & UDCCS_BO_RFS) == 0) ++ return 0; ++ else ++ return (*ep->reg_ubcr & 0xfff) + 1; ++} ++ ++static void pxa2xx_ep_fifo_flush(struct usb_ep *_ep) ++{ ++ struct pxa2xx_ep *ep; ++ ++ ep = container_of(_ep, struct pxa2xx_ep, ep); ++ if (!_ep || ep->ep.name == ep0name || !list_empty(&ep->queue)) { ++ DMSG("%s, bad ep\n", __FUNCTION__); ++ return; ++ } ++ ++ /* toggle and halt bits stay unchanged */ ++ ++ /* for OUT, just read and discard the FIFO contents. */ ++ if ((ep->bEndpointAddress & USB_DIR_IN) == 0) { ++ while (((*ep->reg_udccs) & UDCCS_BO_RNE) != 0) ++ (void) *ep->reg_uddr; ++ return; ++ } ++ ++ /* most IN status is the same, but ISO can't stall */ ++ *ep->reg_udccs = UDCCS_BI_TPC|UDCCS_BI_FTF|UDCCS_BI_TUR ++ | (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) ++ ? 0 : UDCCS_BI_SST; ++} ++ ++ ++static struct usb_ep_ops pxa2xx_ep_ops = { ++ .enable = pxa2xx_ep_enable, ++ .disable = pxa2xx_ep_disable, ++ ++ .alloc_request = pxa2xx_ep_alloc_request, ++ .free_request = pxa2xx_ep_free_request, ++ ++ .alloc_buffer = pxa2xx_ep_alloc_buffer, ++ .free_buffer = pxa2xx_ep_free_buffer, ++ ++ .queue = pxa2xx_ep_queue, ++ .dequeue = pxa2xx_ep_dequeue, ++ ++ .set_halt = pxa2xx_ep_set_halt, ++ .fifo_status = pxa2xx_ep_fifo_status, ++ .fifo_flush = pxa2xx_ep_fifo_flush, ++}; ++ ++ ++/* --------------------------------------------------------------------------- ++ * device-scoped parts of the api to the usb controller hardware ++ * --------------------------------------------------------------------------- ++ */ ++ ++static int pxa2xx_udc_get_frame(struct usb_gadget *_gadget) ++{ ++ return ((UFNRH & 0x07) << 8) | (UFNRL & 0xff); ++} ++ ++static int pxa2xx_udc_wakeup(struct usb_gadget *_gadget) ++{ ++ /* host may not have enabled remote wakeup */ ++ if ((UDCCS0 & UDCCS0_DRWF) == 0) ++ return -EHOSTUNREACH; ++ udc_set_mask_UDCCR(UDCCR_RSM); ++ return 0; ++} ++ ++static const struct usb_gadget_ops pxa2xx_udc_ops = { ++ .get_frame = pxa2xx_udc_get_frame, ++ .wakeup = pxa2xx_udc_wakeup, ++ // current versions must always be self-powered ++}; ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++#ifdef UDC_PROC_FILE ++ ++static const char proc_node_name [] = "driver/udc"; ++ ++static int ++udc_proc_read(char *page, char **start, off_t off, int count, ++ int *eof, void *_dev) ++{ ++ char *buf = page; ++ struct pxa2xx_udc *dev = _dev; ++ char *next = buf; ++ unsigned size = count; ++ unsigned long flags; ++ int i, t; ++ u32 tmp; ++ ++ if (off != 0) ++ return 0; ++ ++ local_irq_save(flags); ++ ++ /* basic device status */ ++ t = snprintf(next, size, DRIVER_DESC "\n" ++ "%s version: %s\nGadget driver: %s\nHost %s\n\n", ++ driver_name, DRIVER_VERSION SIZE_STR DMASTR, ++ dev->driver ? dev->driver->driver.name : "(none)", ++ is_usb_connected() ? "full speed" : "disconnected"); ++ size -= t; ++ next += t; ++ ++ /* registers for device and ep0 */ ++ t = snprintf(next, size, ++ "uicr %02X.%02X, usir %02X.%02x, ufnr %02X.%02X\n", ++ UICR1, UICR0, USIR1, USIR0, UFNRH, UFNRL); ++ size -= t; ++ next += t; ++ ++ tmp = UDCCR; ++ t = snprintf(next, size, ++ "udccr %02X =%s%s%s%s%s%s%s%s\n", tmp, ++ (tmp & UDCCR_REM) ? " rem" : "", ++ (tmp & UDCCR_RSTIR) ? " rstir" : "", ++ (tmp & UDCCR_SRM) ? " srm" : "", ++ (tmp & UDCCR_SUSIR) ? " susir" : "", ++ (tmp & UDCCR_RESIR) ? " resir" : "", ++ (tmp & UDCCR_RSM) ? " rsm" : "", ++ (tmp & UDCCR_UDA) ? " uda" : "", ++ (tmp & UDCCR_UDE) ? " ude" : ""); ++ size -= t; ++ next += t; ++ ++ tmp = UDCCS0; ++ t = snprintf(next, size, ++ "udccs0 %02X =%s%s%s%s%s%s%s%s\n", tmp, ++ (tmp & UDCCS0_SA) ? " sa" : "", ++ (tmp & UDCCS0_RNE) ? " rne" : "", ++ (tmp & UDCCS0_FST) ? " fst" : "", ++ (tmp & UDCCS0_SST) ? " sst" : "", ++ (tmp & UDCCS0_DRWF) ? " dwrf" : "", ++ (tmp & UDCCS0_FTF) ? " ftf" : "", ++ (tmp & UDCCS0_IPR) ? " ipr" : "", ++ (tmp & UDCCS0_OPR) ? " opr" : ""); ++ size -= t; ++ next += t; ++ ++ if (dev->has_cfr) { ++ tmp = UDCCFR; ++ t = snprintf(next, size, ++ "udccfr %02X =%s%s\n", tmp, ++ (tmp & UDCCFR_AREN) ? " aren" : "", ++ (tmp & UDCCFR_ACM) ? " acm" : ""); ++ size -= t; ++ next += t; ++ } ++ ++ if (!is_usb_connected() || !dev->driver) ++ goto done; ++ ++ t = snprintf(next, size, "ep0 IN %lu/%lu, OUT %lu/%lu\nirqs %lu\n\n", ++ dev->stats.write.bytes, dev->stats.write.ops, ++ dev->stats.read.bytes, dev->stats.read.ops, ++ dev->stats.irqs); ++ size -= t; ++ next += t; ++ ++ /* dump endpoint queues */ ++ for (i = 0; i < PXA_UDC_NUM_ENDPOINTS; i++) { ++ struct pxa2xx_ep *ep = &dev->ep [i]; ++ struct pxa2xx_request *req; ++ int t; ++ ++ if (i != 0) { ++ const struct usb_endpoint_descriptor *d; ++ ++ d = ep->desc; ++ if (!d) ++ continue; ++ tmp = *dev->ep [i].reg_udccs; ++ t = snprintf(next, size, ++ "%s max %d %s udccs %02x irqs %lu/%lu\n", ++ ep->ep.name, le16_to_cpu (d->wMaxPacketSize), ++ (ep->dma >= 0) ? "dma" : "pio", tmp, ++ ep->pio_irqs, ep->dma_irqs); ++ /* TODO translate all five groups of udccs bits! */ ++ ++ } else /* ep0 should only have one transfer queued */ ++ t = snprintf(next, size, "ep0 max 16 pio irqs %lu\n", ++ ep->pio_irqs); ++ if (t <= 0 || t > size) ++ goto done; ++ size -= t; ++ next += t; ++ ++ if (list_empty(&ep->queue)) { ++ t = snprintf(next, size, "\t(nothing queued)\n"); ++ if (t <= 0 || t > size) ++ goto done; ++ size -= t; ++ next += t; ++ continue; ++ } ++ list_for_each_entry(req, &ep->queue, queue) { ++#ifdef USE_DMA ++ if (ep->dma >= 0 && req->queue.prev == &ep->queue) ++ t = snprintf(next, size, ++ "\treq %p len %d/%d " ++ "buf %p (dma%d dcmd %08x)\n", ++ &req->req, req->req.actual, ++ req->req.length, req->req.buf, ++ ep->dma, DCMD(ep->dma) ++ // low 13 bits == bytes-to-go ++ ); ++ else ++#endif ++ t = snprintf(next, size, ++ "\treq %p len %d/%d buf %p\n", ++ &req->req, req->req.actual, ++ req->req.length, req->req.buf); ++ if (t <= 0 || t > size) ++ goto done; ++ size -= t; ++ next += t; ++ } ++ } ++ ++done: ++ local_irq_restore(flags); ++ *eof = 1; ++ return count - size; ++} ++ ++#define create_proc_files() \ ++ create_proc_read_entry(proc_node_name, 0, NULL, udc_proc_read, dev) ++#define remove_proc_files() \ ++ remove_proc_entry(proc_node_name, NULL) ++ ++#else /* !UDC_PROC_FILE */ ++#define create_proc_files() do {} while (0) ++#define remove_proc_files() do {} while (0) ++ ++#endif /* UDC_PROC_FILE */ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* ++ * udc_disable - disable USB device controller ++ */ ++static void udc_disable(struct pxa2xx_udc *dev) ++{ ++ /* block all irqs */ ++ udc_set_mask_UDCCR(UDCCR_SRM|UDCCR_REM); ++ UICR0 = UICR1 = 0xff; ++ UFNRH = UFNRH_SIM; ++ ++ /* if hardware supports it, disconnect from usb */ ++ make_usb_disappear(); ++ ++ udc_clear_mask_UDCCR(UDCCR_UDE); ++ ++#ifdef CONFIG_ARCH_PXA ++ /* Disable clock for USB device */ ++ CKEN &= ~CKEN11_USB; ++#endif ++ ++ ep0_idle (dev); ++ dev->gadget.speed = USB_SPEED_UNKNOWN; ++ LED_CONNECTED_OFF; ++} ++ ++ ++/* ++ * udc_reinit - initialize software state ++ */ ++static void udc_reinit(struct pxa2xx_udc *dev) ++{ ++ u32 i; ++ ++ /* device/ep0 records init */ ++ INIT_LIST_HEAD (&dev->gadget.ep_list); ++ INIT_LIST_HEAD (&dev->gadget.ep0->ep_list); ++ dev->ep0state = EP0_IDLE; ++ ++ /* basic endpoint records init */ ++ for (i = 0; i < PXA_UDC_NUM_ENDPOINTS; i++) { ++ struct pxa2xx_ep *ep = &dev->ep[i]; ++ ++ if (i != 0) ++ list_add_tail (&ep->ep.ep_list, &dev->gadget.ep_list); ++ ++ ep->desc = 0; ++ ep->stopped = 0; ++ INIT_LIST_HEAD (&ep->queue); ++ ep->pio_irqs = ep->dma_irqs = 0; ++ } ++ ++ /* the rest was statically initialized, and is read-only */ ++} ++ ++/* until it's enabled, this UDC should be completely invisible ++ * to any USB host. ++ */ ++static void udc_enable (struct pxa2xx_udc *dev) ++{ ++ udc_clear_mask_UDCCR(UDCCR_UDE); ++ ++#ifdef CONFIG_ARCH_PXA ++ /* Enable clock for USB device */ ++ CKEN |= CKEN11_USB; ++#endif ++ ++ /* try to clear these bits before we enable the udc */ ++ udc_ack_int_UDCCR(UDCCR_SUSIR|/*UDCCR_RSTIR|*/UDCCR_RESIR); ++ ++ ep0_idle(dev); ++ dev->gadget.speed = USB_SPEED_UNKNOWN; ++ dev->stats.irqs = 0; ++ ++ /* ++ * sequence taken from chapter 12.5.10, PXA250 AppProcDevManual: ++ * - enable UDC ++ * - if RESET is already in progress, ack interrupt ++ * - unmask reset interrupt ++ */ ++ udc_set_mask_UDCCR(UDCCR_UDE); ++ if (!(UDCCR & UDCCR_UDA)) ++ udc_ack_int_UDCCR(UDCCR_RSTIR); ++ ++ if (dev->has_cfr /* UDC_RES2 is defined */) { ++ /* pxa255 (a0+) can avoid a set_config race that could ++ * prevent gadget drivers from configuring correctly ++ */ ++ UDCCFR = UDCCFR_ACM; ++ } else { ++ /* "USB test mode" for pxa250 errata 40-42 (stepping a0, a1) ++ * which could result in missing packets and interrupts. ++ * supposedly one bit per endpoint, controlling whether it ++ * double buffers or not; ACM/AREN bits fit into the holes. ++ * zero bits (like USIR0_IRx) disable double buffering. ++ */ ++ UDC_RES1 = 0x00; ++ UDC_RES2 = 0x00; ++ } ++ ++#ifdef DISABLE_TEST_MODE ++ /* "test mode" seems to have become the default in later chip ++ * revs, preventing double buffering (and invalidating docs). ++ * this EXPERIMENT enables it for bulk endpoints by tweaking ++ * undefined/reserved register bits (that other drivers clear). ++ * Belcarra code comments noted this usage. ++ */ ++ if (fifo_mode & 1) { /* IN endpoints */ ++ UDC_RES1 |= USIR0_IR1|USIR0_IR6; ++ UDC_RES2 |= USIR1_IR11; ++ } ++ if (fifo_mode & 2) { /* OUT endpoints */ ++ UDC_RES1 |= USIR0_IR2|USIR0_IR7; ++ UDC_RES2 |= USIR1_IR12; ++ } ++#endif ++ ++ /* caller must be able to sleep in order to cope ++ * with startup transients. ++ */ ++ schedule_timeout(HZ/10); ++ ++ /* enable suspend/resume and reset irqs */ ++ udc_clear_mask_UDCCR(UDCCR_SRM | UDCCR_REM); ++ ++ /* enable ep0 irqs */ ++ UICR0 &= ~UICR0_IM0; ++ ++ /* if hardware supports it, connect to usb and wait for host */ ++ let_usb_appear(); ++} ++ ++ ++/* when a driver is successfully registered, it will receive ++ * control requests including set_configuration(), which enables ++ * non-control requests. then usb traffic follows until a ++ * disconnect is reported. then a host may connect again, or ++ * the driver might get unbound. ++ */ ++int usb_gadget_register_driver(struct usb_gadget_driver *driver) ++{ ++ struct pxa2xx_udc *dev = the_controller; ++ int retval; ++ ++ if (!driver ++ || driver->speed != USB_SPEED_FULL ++ || !driver->bind ++ || !driver->unbind ++ || !driver->disconnect ++ || !driver->setup) ++ return -EINVAL; ++ if (!dev) ++ return -ENODEV; ++ if (dev->driver) ++ return -EBUSY; ++ ++ /* first hook up the driver ... */ ++ dev->driver = driver; ++ ++ retval = driver->bind(&dev->gadget); ++ if (retval) { ++ DMSG("bind to driver %s --> error %d\n", ++ driver->driver.name, retval); ++ dev->driver = 0; ++ return retval; ++ } ++ ++ /* ... then enable host detection and ep0; and we're ready ++ * for set_configuration as well as eventual disconnect. ++ * NOTE: this shouldn't power up until later. ++ */ ++ DMSG("registered gadget driver '%s'\n", driver->driver.name); ++ udc_enable(dev); ++ dump_state(dev); ++ return 0; ++} ++EXPORT_SYMBOL(usb_gadget_register_driver); ++ ++static void ++stop_activity(struct pxa2xx_udc *dev, struct usb_gadget_driver *driver) ++{ ++ int i; ++ ++ /* don't disconnect drivers more than once */ ++ if (dev->gadget.speed == USB_SPEED_UNKNOWN) ++ driver = 0; ++ dev->gadget.speed = USB_SPEED_UNKNOWN; ++ ++ /* prevent new request submissions, kill any outstanding requests */ ++ for (i = 0; i < PXA_UDC_NUM_ENDPOINTS; i++) { ++ struct pxa2xx_ep *ep = &dev->ep[i]; ++ ++ ep->stopped = 1; ++ nuke(ep, -ESHUTDOWN); ++ } ++ del_timer_sync(&dev->timer); ++ ++ /* report disconnect; the driver is already quiesced */ ++ LED_CONNECTED_OFF; ++ if (driver) ++ driver->disconnect(&dev->gadget); ++ ++ /* re-init driver-visible data structures */ ++ udc_reinit(dev); ++} ++ ++int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) ++{ ++ struct pxa2xx_udc *dev = the_controller; ++ ++ if (!dev) ++ return -ENODEV; ++ if (!driver || driver != dev->driver) ++ return -EINVAL; ++ ++ local_irq_disable(); ++ udc_disable(dev); ++ stop_activity(dev, driver); ++ local_irq_enable(); ++ ++ driver->unbind(&dev->gadget); ++ dev->driver = 0; ++ ++ DMSG("unregistered gadget driver '%s'\n", driver->driver.name); ++ dump_state(dev); ++ return 0; ++} ++EXPORT_SYMBOL(usb_gadget_unregister_driver); ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++#ifdef CONFIG_ARCH_LUBBOCK ++#ifdef LUBBOCK_USB_DISC_IRQ ++ ++/* Lubbock can report connect or disconnect irqs. Likely more hardware ++ * could support it as a timer callback. ++ * ++ * FIXME for better power management, keep the hardware powered down ++ * until a host is powering the link. means scheduling work later ++ * in some task that can udc_enable(). ++ */ ++ ++#define enable_disconnect_irq() \ ++ if (machine_is_lubbock()) { enable_irq(LUBBOCK_USB_DISC_IRQ); } ++#define disable_disconnect_irq() \ ++ if (machine_is_lubbock()) { disable_irq(LUBBOCK_USB_DISC_IRQ); } ++ ++static irqreturn_t ++usb_connection_irq(int irq, void *_dev, struct pt_regs *r) ++{ ++ struct pxa2xx_udc *dev = _dev; ++ ++ dev->stats.irqs++; ++ HEX_DISPLAY(dev->stats.irqs); ++ ++ if (!is_usb_connected()) { ++ LED_CONNECTED_OFF; ++ disable_disconnect_irq(); ++ /* report disconnect just once */ ++ if (dev->gadget.speed != USB_SPEED_UNKNOWN) { ++ DMSG("disconnect %s\n", ++ dev->driver ? dev->driver->driver.name : 0); ++ stop_activity(dev, dev->driver); ++ ++ // udc_disable (dev); ++ // no more udc irqs ++ // maybe "ACTION=disconnect /sbin/hotplug gadget". ++ } ++ } else if (dev->gadget.speed == USB_SPEED_UNKNOWN) { ++ LED_CONNECTED_ON; ++ ++ DMSG("?? connect irq ??\n"); ++ ++ // if there's no driver bound, ignore; else ++ // udc_enable (dev); ++ // UDC irqs drive the rest. ++ // maybe "ACTION=connect /sbin/hotplug gadget". ++ } ++ return IRQ_HANDLED; ++} ++ ++#endif ++#endif ++ ++#ifndef enable_disconnect_irq ++#warning USB disconnect() is not yet reported. ++#define enable_disconnect_irq() do {} while (0) ++#define disable_disconnect_irq() do {} while (0) ++#endif ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static inline void clear_ep_state (struct pxa2xx_udc *dev) ++{ ++ unsigned i; ++ ++ /* hardware SET_{CONFIGURATION,INTERFACE} automagic resets endpoint ++ * fifos, and pending transactions mustn't be continued in any case. ++ */ ++ for (i = 1; i < PXA_UDC_NUM_ENDPOINTS; i++) ++ nuke(&dev->ep[i], -ECONNABORTED); ++} ++ ++static void udc_watchdog(unsigned long _dev) ++{ ++ struct pxa2xx_udc *dev = (void *)_dev; ++ ++ local_irq_disable(); ++ if (dev->ep0state == EP0_STALL ++ && (UDCCS0 & UDCCS0_FST) == 0 ++ && (UDCCS0 & UDCCS0_SST) == 0) { ++ UDCCS0 = UDCCS0_FST|UDCCS0_FTF; ++ DBG(DBG_VERBOSE, "ep0 re-stall\n"); ++ start_watchdog(dev); ++ } ++ local_irq_enable(); ++} ++ ++static void handle_ep0 (struct pxa2xx_udc *dev) ++{ ++ u32 udccs0 = UDCCS0; ++ struct pxa2xx_ep *ep = &dev->ep [0]; ++ struct pxa2xx_request *req; ++ union { ++ struct usb_ctrlrequest r; ++ u8 raw [8]; ++ u32 word [2]; ++ } u; ++ ++ if (list_empty(&ep->queue)) ++ req = 0; ++ else ++ req = list_entry(ep->queue.next, struct pxa2xx_request, queue); ++ ++ /* clear stall status */ ++ if (udccs0 & UDCCS0_SST) { ++ nuke(ep, -EPIPE); ++ UDCCS0 = UDCCS0_SST; ++ del_timer(&dev->timer); ++ ep0_idle(dev); ++ } ++ ++ /* previous request unfinished? non-error iff back-to-back ... */ ++ if ((udccs0 & UDCCS0_SA) != 0 && dev->ep0state != EP0_IDLE) { ++ nuke(ep, 0); ++ del_timer(&dev->timer); ++ ep0_idle(dev); ++ } ++ ++ switch (dev->ep0state) { ++ case EP0_IDLE: ++ /* late-breaking status? */ ++ udccs0 = UDCCS0; ++ ++ /* start control request? */ ++ if (likely((udccs0 & (UDCCS0_OPR|UDCCS0_SA|UDCCS0_RNE)) ++ == (UDCCS0_OPR|UDCCS0_SA|UDCCS0_RNE))) { ++ int i; ++ ++ nuke (ep, -EPROTO); ++ ++ /* read SETUP packet */ ++ for (i = 0; i < 8; i++) { ++ if (unlikely(!(UDCCS0 & UDCCS0_RNE))) { ++bad_setup: ++ DMSG("SETUP %d!\n", i); ++ goto stall; ++ } ++ u.raw [i] = (u8) UDDR0; ++ } ++ if (unlikely((UDCCS0 & UDCCS0_RNE) != 0)) ++ goto bad_setup; ++ ++got_setup: ++ le16_to_cpus (&u.r.wValue); ++ le16_to_cpus (&u.r.wIndex); ++ le16_to_cpus (&u.r.wLength); ++ ++ LED_EP0_ON; ++ DBG(DBG_VERBOSE, "SETUP %02x.%02x v%04x i%04x l%04x\n", ++ u.r.bRequestType, u.r.bRequest, ++ u.r.wValue, u.r.wIndex, u.r.wLength); ++ ++ /* cope with automagic for some standard requests. */ ++ dev->req_std = (u.r.bRequestType & USB_TYPE_MASK) ++ == USB_TYPE_STANDARD; ++ dev->req_config = 0; ++ dev->req_pending = 1; ++ switch (u.r.bRequest) { ++ /* hardware restricts gadget drivers here! */ ++ case USB_REQ_SET_CONFIGURATION: ++ if (u.r.bRequestType == USB_RECIP_DEVICE) { ++ /* reflect hardware's automagic ++ * up to the gadget driver. ++ */ ++config_change: ++ dev->req_config = 1; ++ clear_ep_state(dev); ++ /* if !has_cfr, there's no synch ++ * else use AREN (later) not SA|OPR ++ * USIR0_IR0 acts edge sensitive ++ */ ++ } ++ break; ++ /* ... and here, even more ... */ ++ case USB_REQ_SET_INTERFACE: ++ if (u.r.bRequestType == USB_RECIP_INTERFACE) { ++ /* udc hardware is broken by design: ++ * - altsetting may only be zero; ++ * - hw resets all interfaces' eps; ++ * - ep reset doesn't include halt(?). ++ */ ++ DMSG("broken set_interface (%d/%d)\n", ++ u.r.wIndex, u.r.wValue); ++ goto config_change; ++ } ++ break; ++ /* hardware was supposed to hide this */ ++ case USB_REQ_SET_ADDRESS: ++ if (u.r.bRequestType == USB_RECIP_DEVICE) { ++ ep0start(dev, 0, "address"); ++ return; ++ } ++ break; ++ } ++ ++ if (u.r.bRequestType & USB_DIR_IN) ++ dev->ep0state = EP0_IN_DATA_PHASE; ++ else ++ dev->ep0state = EP0_OUT_DATA_PHASE; ++ ++ i = dev->driver->setup(&dev->gadget, &u.r); ++ if (i < 0) { ++ /* hardware automagic preventing STALL... */ ++ if (dev->req_config) { ++ /* hardware sometimes neglects to tell ++ * tell us about config change events, ++ * so later ones may fail... ++ */ ++ WARN("config change %02x fail %d?\n", ++ u.r.bRequest, i); ++ return; ++ /* TODO experiment: if has_cfr, ++ * hardware didn't ACK; maybe we ++ * could actually STALL! ++ */ ++ } ++ DBG(DBG_VERBOSE, "protocol STALL, " ++ "%02x err %d\n", UDCCS0, i); ++stall: ++ /* the watchdog timer helps deal with cases ++ * where udc seems to clear FST wrongly, and ++ * then NAKs instead of STALLing. ++ */ ++ ep0start(dev, UDCCS0_FST|UDCCS0_FTF, "stall"); ++ start_watchdog(dev); ++ dev->ep0state = EP0_STALL; ++ LED_EP0_OFF; ++ ++ /* deferred i/o == no response yet */ ++ } else if (dev->req_pending) { ++ if (likely(dev->ep0state == EP0_IN_DATA_PHASE ++ || dev->req_std || u.r.wLength)) ++ ep0start(dev, 0, "defer"); ++ else ++ ep0start(dev, UDCCS0_IPR, "defer/IPR"); ++ } ++ ++ /* expect at least one data or status stage irq */ ++ return; ++ ++ } else if (likely((udccs0 & (UDCCS0_OPR|UDCCS0_SA)) ++ == (UDCCS0_OPR|UDCCS0_SA))) { ++ unsigned i; ++ ++ /* pxa210/250 erratum 131 for B0/B1 says RNE lies. ++ * still observed on a pxa255 a0. ++ */ ++ DBG(DBG_VERBOSE, "e131\n"); ++ nuke(ep, -EPROTO); ++ ++ /* read SETUP data, but don't trust it too much */ ++ for (i = 0; i < 8; i++) ++ u.raw [i] = (u8) UDDR0; ++ if ((u.r.bRequestType & USB_RECIP_MASK) ++ > USB_RECIP_OTHER) ++ goto stall; ++ if (u.word [0] == 0 && u.word [1] == 0) ++ goto stall; ++ goto got_setup; ++ } else { ++ /* some random early IRQ: ++ * - we acked FST ++ * - IPR cleared ++ * - OPR got set, without SA (likely status stage) ++ */ ++ UDCCS0 = udccs0 & (UDCCS0_SA|UDCCS0_OPR); ++ } ++ break; ++ case EP0_IN_DATA_PHASE: /* GET_DESCRIPTOR etc */ ++ if (udccs0 & UDCCS0_OPR) { ++ UDCCS0 = UDCCS0_OPR|UDCCS0_FTF; ++ DBG(DBG_VERBOSE, "ep0in premature status\n"); ++ if (req) ++ done(ep, req, 0); ++ ep0_idle(dev); ++ } else /* irq was IPR clearing */ { ++ if (req) { ++ /* this IN packet might finish the request */ ++ (void) write_ep0_fifo(ep, req); ++ } /* else IN token before response was written */ ++ } ++ break; ++ case EP0_OUT_DATA_PHASE: /* SET_DESCRIPTOR etc */ ++ if (udccs0 & UDCCS0_OPR) { ++ if (req) { ++ /* this OUT packet might finish the request */ ++ if (read_ep0_fifo(ep, req)) ++ done(ep, req, 0); ++ /* else more OUT packets expected */ ++ } /* else OUT token before read was issued */ ++ } else /* irq was IPR clearing */ { ++ DBG(DBG_VERBOSE, "ep0out premature status\n"); ++ if (req) ++ done(ep, req, 0); ++ ep0_idle(dev); ++ } ++ break; ++ case EP0_END_XFER: ++ if (req) ++ done(ep, req, 0); ++ /* ack control-IN status (maybe in-zlp was skipped) ++ * also appears after some config change events. ++ */ ++ if (udccs0 & UDCCS0_OPR) ++ UDCCS0 = UDCCS0_OPR; ++ ep0_idle(dev); ++ break; ++ case EP0_STALL: ++ UDCCS0 = UDCCS0_FST; ++ break; ++ } ++ USIR0 = USIR0_IR0; ++} ++ ++static void handle_ep(struct pxa2xx_ep *ep) ++{ ++ struct pxa2xx_request *req; ++ int is_in = ep->bEndpointAddress & USB_DIR_IN; ++ int completed; ++ u32 udccs, tmp; ++ ++ do { ++ completed = 0; ++ if (likely (!list_empty(&ep->queue))) ++ req = list_entry(ep->queue.next, ++ struct pxa2xx_request, queue); ++ else ++ req = 0; ++ ++ // TODO check FST handling ++ ++ udccs = *ep->reg_udccs; ++ if (unlikely(is_in)) { /* irq from TPC, SST, or (ISO) TUR */ ++ tmp = UDCCS_BI_TUR; ++ if (likely(ep->bmAttributes == USB_ENDPOINT_XFER_BULK)) ++ tmp |= UDCCS_BI_SST; ++ tmp &= udccs; ++ if (likely (tmp)) ++ *ep->reg_udccs = tmp; ++ if (req && likely ((udccs & UDCCS_BI_TFS) != 0)) ++ completed = write_fifo(ep, req); ++ ++ } else { /* irq from RPC (or for ISO, ROF) */ ++ if (likely(ep->bmAttributes == USB_ENDPOINT_XFER_BULK)) ++ tmp = UDCCS_BO_SST | UDCCS_BO_DME; ++ else ++ tmp = UDCCS_IO_ROF | UDCCS_IO_DME; ++ tmp &= udccs; ++ if (likely(tmp)) ++ *ep->reg_udccs = tmp; ++ ++ /* fifos can hold packets, ready for reading... */ ++ if (likely(req != 0)) { ++#ifdef USE_OUT_DMA ++// TODO didn't yet debug out-dma. this approach assumes ++// the worst about short packets and RPC; it might be better. ++ ++ if (likely(ep->dma >= 0)) { ++ if (!(udccs & UDCCS_BO_RSP)) { ++ *ep->reg_udccs = UDCCS_BO_RPC; ++ ep->dma_irqs++; ++ return; ++ } ++ } ++#endif ++ completed = read_fifo(ep, req); ++ } else ++ pio_irq_disable (ep->bEndpointAddress); ++ } ++ ep->pio_irqs++; ++ } while (completed); ++} ++ ++/* ++ * pxa2xx_udc_irq - interrupt handler ++ * ++ * avoid delays in ep0 processing. the control handshaking isn't always ++ * under software control (pxa250c0 and the pxa255 are better), and delays ++ * could cause usb protocol errors. ++ */ ++static irqreturn_t ++pxa2xx_udc_irq(int irq, void *_dev, struct pt_regs *r) ++{ ++ struct pxa2xx_udc *dev = _dev; ++ int handled; ++ ++ dev->stats.irqs++; ++ HEX_DISPLAY(dev->stats.irqs); ++ do { ++ u32 udccr = UDCCR; ++ ++ handled = 0; ++ ++ /* SUSpend Interrupt Request */ ++ if (unlikely(udccr & UDCCR_SUSIR)) { ++ udc_ack_int_UDCCR(UDCCR_SUSIR); ++ handled = 1; ++ DBG(DBG_VERBOSE, "USB suspend%s\n", is_usb_connected() ++ ? "" : "+disconnect"); ++ ++ if (!is_usb_connected()) ++ stop_activity(dev, dev->driver); ++ else if (dev->gadget.speed != USB_SPEED_UNKNOWN ++ && dev->driver ++ && dev->driver->suspend) ++ dev->driver->suspend(&dev->gadget); ++ ep0_idle (dev); ++ } ++ ++ /* RESume Interrupt Request */ ++ if (unlikely(udccr & UDCCR_RESIR)) { ++ udc_ack_int_UDCCR(UDCCR_RESIR); ++ handled = 1; ++ DBG(DBG_VERBOSE, "USB resume\n"); ++ ++ if (dev->gadget.speed != USB_SPEED_UNKNOWN ++ && dev->driver ++ && dev->driver->resume ++ && is_usb_connected()) ++ dev->driver->resume(&dev->gadget); ++ } ++ ++ /* ReSeT Interrupt Request - USB reset */ ++ if (unlikely(udccr & UDCCR_RSTIR)) { ++ udc_ack_int_UDCCR(UDCCR_RSTIR); ++ handled = 1; ++ ++ if ((UDCCR & UDCCR_UDA) == 0) { ++ DBG(DBG_VERBOSE, "USB reset start\n"); ++ if (dev->gadget.speed != USB_SPEED_UNKNOWN) ++ disable_disconnect_irq(); ++ ++ /* reset driver and endpoints, ++ * in case that's not yet done ++ */ ++ stop_activity (dev, dev->driver); ++ ++ } else { ++ INFO("USB reset\n"); ++ dev->gadget.speed = USB_SPEED_FULL; ++ LED_CONNECTED_ON; ++ memset(&dev->stats, 0, sizeof dev->stats); ++ /* driver and endpoints are still reset */ ++ enable_disconnect_irq(); ++ } ++ ++ } else { ++ u32 usir0 = USIR0 & ~UICR0; ++ u32 usir1 = USIR1 & ~UICR1; ++ int i; ++ ++ if (unlikely (!usir0 && !usir1)) ++ continue; ++ ++ DBG(DBG_VERY_NOISY, "irq %02x.%02x\n", usir1, usir0); ++ ++ /* control traffic */ ++ if (usir0 & USIR0_IR0) { ++ dev->ep[0].pio_irqs++; ++ handle_ep0(dev); ++ handled = 1; ++ } ++ ++ /* endpoint data transfers */ ++ for (i = 0; i < 8; i++) { ++ u32 tmp = 1 << i; ++ ++ if (i && (usir0 & tmp)) { ++ handle_ep(&dev->ep[i]); ++ USIR0 |= tmp; ++ handled = 1; ++ } ++ if (usir1 & tmp) { ++ handle_ep(&dev->ep[i+8]); ++ USIR1 |= tmp; ++ handled = 1; ++ } ++ } ++ } ++ ++ /* we could also ask for 1 msec SOF (SIR) interrupts */ ++ ++ } while (handled); ++ return IRQ_HANDLED; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* ++ * cleanup - free resources allocated during init ++ */ ++static void /*__exit and */ __init cleanup(void) ++{ ++ struct pxa2xx_udc *dev = the_controller; ++ ++ if (!dev) ++ return; ++ ++ udc_disable(dev); ++ remove_proc_files(); ++ usb_gadget_unregister_driver(dev->driver); ++ if (dev->got_irq) { ++ free_irq(IRQ_USB, dev); ++ dev->got_irq = 0; ++ } ++#ifdef LUBBOCK_USB_DISC_IRQ ++ if (dev->got_disc) { ++ free_irq(LUBBOCK_USB_DISC_IRQ, dev); ++ dev->got_disc = 0; ++ } ++#endif ++ the_controller = 0; ++ release_mem_region(REGISTER_FIRST, REGISTER_LENGTH); ++} ++module_exit (cleanup); ++ ++/* this uses load-time allocation and initialization (instead of ++ * doing it at run-time) to save code, eliminate fault paths, and ++ * be more obviously correct. ++ */ ++static struct pxa2xx_udc memory = { ++ .gadget = { ++ .ops = &pxa2xx_udc_ops, ++ .ep0 = &memory.ep[0].ep, ++ .name = driver_name, ++ .dev = { ++ .bus_id = "gadget", ++ }, ++ }, ++ ++ /* control endpoint */ ++ .ep[0] = { ++ .ep = { ++ .name = ep0name, ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = EP0_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .reg_udccs = &UDCCS0, ++ .reg_uddr = &UDDR0, ++ }, ++ ++ /* first group of endpoints */ ++ .ep[1] = { ++ .ep = { ++ .name = "ep1in-bulk", ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = BULK_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .fifo_size = BULK_FIFO_SIZE, ++ .bEndpointAddress = USB_DIR_IN | 1, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ .reg_udccs = &UDCCS1, ++ .reg_uddr = &UDDR1, ++ drcmr (25) ++ }, ++ .ep[2] = { ++ .ep = { ++ .name = "ep2out-bulk", ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = BULK_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .fifo_size = BULK_FIFO_SIZE, ++ .bEndpointAddress = 2, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ .reg_udccs = &UDCCS2, ++ .reg_ubcr = &UBCR2, ++ .reg_uddr = &UDDR2, ++ drcmr (26) ++ }, ++#ifndef CONFIG_USB_PXA2XX_SMALL ++ .ep[3] = { ++ .ep = { ++ .name = "ep3in-iso", ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = ISO_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .fifo_size = ISO_FIFO_SIZE, ++ .bEndpointAddress = USB_DIR_IN | 3, ++ .bmAttributes = USB_ENDPOINT_XFER_ISOC, ++ .reg_udccs = &UDCCS3, ++ .reg_uddr = &UDDR3, ++ drcmr (27) ++ }, ++ .ep[4] = { ++ .ep = { ++ .name = "ep4out-iso", ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = ISO_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .fifo_size = ISO_FIFO_SIZE, ++ .bEndpointAddress = 4, ++ .bmAttributes = USB_ENDPOINT_XFER_ISOC, ++ .reg_udccs = &UDCCS4, ++ .reg_ubcr = &UBCR4, ++ .reg_uddr = &UDDR4, ++ drcmr (28) ++ }, ++ .ep[5] = { ++ .ep = { ++ .name = "ep5in-int", ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = INT_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .fifo_size = INT_FIFO_SIZE, ++ .bEndpointAddress = USB_DIR_IN | 5, ++ .bmAttributes = USB_ENDPOINT_XFER_INT, ++ .reg_udccs = &UDCCS5, ++ .reg_uddr = &UDDR5, ++ }, ++ ++ /* second group of endpoints */ ++ .ep[6] = { ++ .ep = { ++ .name = "ep6in-bulk", ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = BULK_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .fifo_size = BULK_FIFO_SIZE, ++ .bEndpointAddress = USB_DIR_IN | 6, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ .reg_udccs = &UDCCS6, ++ .reg_uddr = &UDDR6, ++ drcmr (30) ++ }, ++ .ep[7] = { ++ .ep = { ++ .name = "ep7out-bulk", ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = BULK_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .fifo_size = BULK_FIFO_SIZE, ++ .bEndpointAddress = 7, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ .reg_udccs = &UDCCS7, ++ .reg_ubcr = &UBCR7, ++ .reg_uddr = &UDDR7, ++ drcmr (31) ++ }, ++ .ep[8] = { ++ .ep = { ++ .name = "ep8in-iso", ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = ISO_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .fifo_size = ISO_FIFO_SIZE, ++ .bEndpointAddress = USB_DIR_IN | 8, ++ .bmAttributes = USB_ENDPOINT_XFER_ISOC, ++ .reg_udccs = &UDCCS8, ++ .reg_uddr = &UDDR8, ++ drcmr (32) ++ }, ++ .ep[9] = { ++ .ep = { ++ .name = "ep9out-iso", ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = ISO_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .fifo_size = ISO_FIFO_SIZE, ++ .bEndpointAddress = 9, ++ .bmAttributes = USB_ENDPOINT_XFER_ISOC, ++ .reg_udccs = &UDCCS9, ++ .reg_ubcr = &UBCR9, ++ .reg_uddr = &UDDR9, ++ drcmr (33) ++ }, ++ .ep[10] = { ++ .ep = { ++ .name = "ep10in-int", ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = INT_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .fifo_size = INT_FIFO_SIZE, ++ .bEndpointAddress = USB_DIR_IN | 10, ++ .bmAttributes = USB_ENDPOINT_XFER_INT, ++ .reg_udccs = &UDCCS10, ++ .reg_uddr = &UDDR10, ++ }, ++ ++ /* third group of endpoints */ ++ .ep[11] = { ++ .ep = { ++ .name = "ep11in-bulk", ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = BULK_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .fifo_size = BULK_FIFO_SIZE, ++ .bEndpointAddress = USB_DIR_IN | 11, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ .reg_udccs = &UDCCS11, ++ .reg_uddr = &UDDR11, ++ drcmr (35) ++ }, ++ .ep[12] = { ++ .ep = { ++ .name = "ep12out-bulk", ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = BULK_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .fifo_size = BULK_FIFO_SIZE, ++ .bEndpointAddress = 12, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ .reg_udccs = &UDCCS12, ++ .reg_ubcr = &UBCR12, ++ .reg_uddr = &UDDR12, ++ drcmr (36) ++ }, ++ .ep[13] = { ++ .ep = { ++ .name = "ep13in-iso", ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = ISO_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .fifo_size = ISO_FIFO_SIZE, ++ .bEndpointAddress = USB_DIR_IN | 13, ++ .bmAttributes = USB_ENDPOINT_XFER_ISOC, ++ .reg_udccs = &UDCCS13, ++ .reg_uddr = &UDDR13, ++ drcmr (37) ++ }, ++ .ep[14] = { ++ .ep = { ++ .name = "ep14out-iso", ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = ISO_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .fifo_size = ISO_FIFO_SIZE, ++ .bEndpointAddress = 14, ++ .bmAttributes = USB_ENDPOINT_XFER_ISOC, ++ .reg_udccs = &UDCCS14, ++ .reg_ubcr = &UBCR14, ++ .reg_uddr = &UDDR14, ++ drcmr (38) ++ }, ++ .ep[15] = { ++ .ep = { ++ .name = "ep15in-int", ++ .ops = &pxa2xx_ep_ops, ++ .maxpacket = INT_FIFO_SIZE, ++ }, ++ .dev = &memory, ++ .fifo_size = INT_FIFO_SIZE, ++ .bEndpointAddress = USB_DIR_IN | 15, ++ .bmAttributes = USB_ENDPOINT_XFER_INT, ++ .reg_udccs = &UDCCS15, ++ .reg_uddr = &UDDR15, ++ }, ++#endif /* !CONFIG_USB_PXA2XX_SMALL */ ++}; ++ ++#define CP15R0_VENDOR_MASK 0xffffe000 ++ ++#if defined(CONFIG_ARCH_PXA) ++#define CP15R0_XSCALE_VALUE 0x69052000 /* intel/arm/xscale */ ++ ++#elif defined(CONFIG_ARCH_IXP425) ++#define CP15R0_XSCALE_VALUE 0x69054000 /* intel/arm/ixp425 */ ++ ++#endif ++ ++#define CP15R0_PROD_MASK 0x000003f0 ++#define PXA25x 0x00000100 /* and PXA26x */ ++#define PXA210 0x00000120 ++ ++#define CP15R0_REV_MASK 0x0000000f ++ ++#define CP15R0_PRODREV_MASK (CP15R0_PROD_MASK | CP15R0_REV_MASK) ++ ++#define PXA255_A0 0x00000106 /* or PXA260_B1 */ ++#define PXA250_C0 0x00000105 /* or PXA26x_B0 */ ++#define PXA250_B2 0x00000104 ++#define PXA250_B1 0x00000103 /* or PXA260_A0 */ ++#define PXA250_B0 0x00000102 ++#define PXA250_A1 0x00000101 ++#define PXA250_A0 0x00000100 ++ ++#define PXA210_C0 0x00000125 ++#define PXA210_B2 0x00000124 ++#define PXA210_B1 0x00000123 ++#define PXA210_B0 0x00000122 ++ ++#define IXP425_A0 0x000001c1 ++ ++/* ++ * init - allocate resources ++ */ ++static int __init init(void) ++{ ++ struct pxa2xx_udc *dev; ++ int retval, out_dma = 1; ++ u32 chiprev; ++ ++ printk(KERN_INFO "%s: version %s\n", driver_name, DRIVER_VERSION); ++ ++ /* insist on Intel/ARM/XScale */ ++ asm("mrc%? p15, 0, %0, c0, c0" : "=r" (chiprev)); ++ if ((chiprev & CP15R0_VENDOR_MASK) != CP15R0_XSCALE_VALUE) { ++ printk(KERN_ERR "%s: not XScale!\n", driver_name); ++ return -ENODEV; ++ } ++ ++ /* allocate resources */ ++ if (!request_mem_region(REGISTER_FIRST, REGISTER_LENGTH, driver_name)) ++ return -EBUSY; ++ ++ /* initialize data */ ++ dev = &memory; ++ ++ init_timer(&dev->timer); ++ dev->timer.function = udc_watchdog; ++ dev->timer.data = (unsigned long) dev; ++ ++ /* trigger chiprev-specific logic */ ++ switch (chiprev & CP15R0_PRODREV_MASK) { ++#if defined(CONFIG_ARCH_PXA) ++ case PXA255_A0: ++ dev->has_cfr = 1; ++ break; ++ case PXA250_A0: ++ case PXA250_A1: ++ /* A0/A1 "not released"; ep 13, 15 unusable */ ++ /* fall through */ ++ case PXA250_B2: case PXA210_B2: ++ case PXA250_B1: case PXA210_B1: ++ case PXA250_B0: case PXA210_B0: ++ out_dma = 0; ++ /* fall through */ ++ case PXA250_C0: case PXA210_C0: ++ break; ++#elif defined(CONFIG_ARCH_IXP425) ++ case IXP425_A0: ++ out_dma = 0; ++ break; ++#endif ++ default: ++ out_dma = 0; ++ printk(KERN_ERR "%s: unrecognized processor: %08x\n", ++ driver_name, chiprev); ++ return -ENODEV; ++ } ++ ++ pr_debug("%s: IRQ %d%s%s%s\n", driver_name, IRQ_USB, ++ dev->has_cfr ? "" : " (!cfr)", ++ out_dma ? "" : " (broken dma-out)", ++ SIZE_STR DMASTR ++ ); ++ ++#ifdef USE_DMA ++#ifndef USE_OUT_DMA ++ out_dma = 0; ++#endif ++ /* pxa 250 erratum 130 prevents using OUT dma (fixed C0) */ ++ if (!out_dma) { ++ DMSG("disabled OUT dma\n"); ++ dev->ep[ 2].reg_drcmr = dev->ep[ 4].reg_drcmr = 0; ++ dev->ep[ 7].reg_drcmr = dev->ep[ 9].reg_drcmr = 0; ++ dev->ep[12].reg_drcmr = dev->ep[14].reg_drcmr = 0; ++ } ++#endif ++ ++ the_controller = dev; ++ udc_disable(dev); ++ udc_reinit(dev); ++ ++ /* irq setup after old hardware state is cleaned up */ ++ retval = request_irq(IRQ_USB, pxa2xx_udc_irq, ++ SA_INTERRUPT, driver_name, dev); ++ if (retval != 0) { ++ printk(KERN_ERR "%s: can't get irq %i, err %d\n", ++ driver_name, IRQ_USB, retval); ++ return -EBUSY; ++ } ++ dev->got_irq = 1; ++ ++#ifdef LUBBOCK_USB_DISC_IRQ ++ if (machine_is_lubbock()) { ++ disable_irq(LUBBOCK_USB_DISC_IRQ); ++ retval = request_irq(LUBBOCK_USB_DISC_IRQ, ++ usb_connection_irq, ++ SA_INTERRUPT | SA_SAMPLE_RANDOM, ++ driver_name, dev); ++ if (retval != 0) { ++ enable_irq(LUBBOCK_USB_DISC_IRQ); ++ printk(KERN_ERR "%s: can't get irq %i, err %d\n", ++ driver_name, LUBBOCK_USB_DISC_IRQ, retval); ++ cleanup(); ++ return retval; ++ } ++ dev->got_disc = 1; ++ } ++#endif ++ ++ create_proc_files(); ++ return 0; ++} ++module_init (init); ++ ++MODULE_DESCRIPTION(DRIVER_DESC); ++MODULE_AUTHOR("Frank Becker, Robert Schwebel, David Brownell"); ++MODULE_LICENSE("GPL"); ++ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/pxa2xx_udc.h kernel/drivers/usb/gadget/pxa2xx_udc.h +--- /tmp/kernel/drivers/usb/gadget/pxa2xx_udc.h 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/pxa2xx_udc.h 2005-04-22 17:53:19.496529422 +0200 +@@ -0,0 +1,528 @@ ++/* ++ * linux/drivers/usb/gadget/pxa2xx_udc.h ++ * Intel PXA2xx on-chip full speed USB device controller ++ * ++ * Copyright (C) 2003 Robert Schwebel , Pengutronix ++ * Copyright (C) 2003 David Brownell ++ * Copyright (C) 2003 Joshua Wise ++ * ++ * ++ * 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 ++ */ ++ ++#ifndef __LINUX_USB_GADGET_PXA2XX_H ++#define __LINUX_USB_GADGET_PXA2XX_H ++ ++#include ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* pxa2xx has this (move to include/asm-arm/arch-pxa/pxa-regs.h) */ ++#define UFNRH_SIR (1 << 7) /* SOF interrupt request */ ++#define UFNRH_SIM (1 << 6) /* SOF interrupt mask */ ++#define UFNRH_IPE14 (1 << 5) /* ISO packet error, ep14 */ ++#define UFNRH_IPE9 (1 << 4) /* ISO packet error, ep9 */ ++#define UFNRH_IPE4 (1 << 3) /* ISO packet error, ep4 */ ++ ++/* pxa255 has this (move to include/asm-arm/arch-pxa/pxa-regs.h) */ ++#define UDCCFR UDC_RES2 /* UDC Control Function Register */ ++#define UDCCFR_AREN (1 << 7) /* ACK response enable (now) */ ++#define UDCCFR_ACM (1 << 2) /* ACK control mode (wait for AREN) */ ++ ++/* for address space reservation */ ++#define REGISTER_FIRST ((unsigned long)(&UDCCR)) ++#define REGISTER_LAST ((unsigned long)(&UDDR14)) /* not UDDR15! */ ++#define REGISTER_LENGTH ((REGISTER_LAST - REGISTER_FIRST) + 4) ++ ++/*-------------------------------------------------------------------------*/ ++ ++struct pxa2xx_udc; ++ ++struct pxa2xx_ep { ++ struct usb_ep ep; ++ struct pxa2xx_udc *dev; ++ ++ const struct usb_endpoint_descriptor *desc; ++ struct list_head queue; ++ unsigned long pio_irqs; ++ unsigned long dma_irqs; ++ short dma; ++ ++ unsigned short fifo_size; ++ u8 bEndpointAddress; ++ u8 bmAttributes; ++ ++ unsigned stopped : 1; ++ unsigned dma_fixup : 1; ++ ++ /* UDCCS = UDC Control/Status for this EP ++ * UBCR = UDC Byte Count Remaining (contents of OUT fifo) ++ * UDDR = UDC Endpoint Data Register (the fifo) ++ * DRCM = DMA Request Channel Map ++ */ ++ volatile u32 *reg_udccs; ++ volatile u32 *reg_ubcr; ++ volatile u32 *reg_uddr; ++#ifdef USE_DMA ++ volatile u32 *reg_drcmr; ++#define drcmr(n) .reg_drcmr = & DRCMR ## n , ++#else ++#define drcmr(n) ++#endif ++}; ++ ++struct pxa2xx_request { ++ struct usb_request req; ++ struct list_head queue; ++}; ++ ++enum ep0_state { ++ EP0_IDLE, ++ EP0_IN_DATA_PHASE, ++ EP0_OUT_DATA_PHASE, ++ EP0_END_XFER, ++ EP0_STALL, ++}; ++ ++#define EP0_FIFO_SIZE ((unsigned)16) ++#define BULK_FIFO_SIZE ((unsigned)64) ++#define ISO_FIFO_SIZE ((unsigned)256) ++#define INT_FIFO_SIZE ((unsigned)8) ++ ++struct udc_stats { ++ struct ep0stats { ++ unsigned long ops; ++ unsigned long bytes; ++ } read, write; ++ unsigned long irqs; ++}; ++ ++#ifdef CONFIG_USB_PXA2XX_SMALL ++/* when memory's tight, SMALL config saves code+data. */ ++#undef USE_DMA ++#define PXA_UDC_NUM_ENDPOINTS 3 ++#endif ++ ++#ifndef PXA_UDC_NUM_ENDPOINTS ++#define PXA_UDC_NUM_ENDPOINTS 16 ++#endif ++ ++struct pxa2xx_udc { ++ struct usb_gadget gadget; ++ struct usb_gadget_driver *driver; ++ ++ enum ep0_state ep0state; ++ struct udc_stats stats; ++ unsigned got_irq : 1, ++ got_disc : 1, ++ has_cfr : 1, ++ req_pending : 1, ++ req_std : 1, ++ req_config : 1; ++ ++#define start_watchdog(dev) mod_timer(&dev->timer, jiffies + (HZ/200)) ++ struct timer_list timer; ++ ++ struct pxa2xx_ep ep [PXA_UDC_NUM_ENDPOINTS]; ++}; ++ ++/* 2.5 changes ... */ ++ ++#ifndef container_of ++#define container_of list_entry ++#endif ++ ++#ifndef WARN_ON ++#define WARN_ON BUG_ON ++#endif ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* please keep machine-specific defines in alphabetical order. */ ++ ++// CONFIG_ARCH_ADI_COYOTE behaves ++ ++#ifdef CONFIG_ARCH_E7XX ++# include ++#endif ++ ++#ifdef CONFIG_ARCH_H1900 ++# include ++#endif ++ ++#ifdef CONFIG_ARCH_H3900 ++# include ++#endif ++ ++#ifdef CONFIG_ARCH_H5400 ++# include ++#endif ++ ++#ifdef CONFIG_ARCH_INNOKOM ++#include ++#endif ++ ++#ifdef CONFIG_ARCH_LUBBOCK ++#include ++/* lubbock can also report usb connect/disconnect irqs */ ++ ++#ifdef DEBUG ++#define HEX_DISPLAY(n) if (machine_is_lubbock()) { LUB_HEXLED = (n); } ++ ++#define LED_CONNECTED_ON if (machine_is_lubbock()) { \ ++ DISCRETE_LED_ON(D26); } ++#define LED_CONNECTED_OFF if(machine_is_lubbock()) { \ ++ DISCRETE_LED_OFF(D26); LUB_HEXLED = 0; } ++#define LED_EP0_ON if (machine_is_lubbock()) { DISCRETE_LED_ON(D25); } ++#define LED_EP0_OFF if (machine_is_lubbock()) { DISCRETE_LED_OFF(D25); } ++#endif /* DEBUG */ ++ ++#endif ++ ++#ifdef CONFIG_ARCH_PXA_CORGI ++/* Sharp Zaurus C-700, C-750, C-760, C-860 */ ++#define CORGI_CONNECT_GPIO 45 ++/* use the ARM-Linux registered symbol, not a Lineo-private one */ ++#define CONFIG_MACH_CORGI ++#endif ++ ++#ifdef CONFIG_ARCH_PXA_POODLE ++/* Sharp B-500, SL-5600 */ ++#define POODLE_CONNECT_GPIO 20 ++/* use the ARM-Linux registered symbol, not a Lineo-private one */ ++#define CONFIG_MACH_POODLE ++#endif ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* LEDs are only for debug */ ++#ifndef HEX_DISPLAY ++#define HEX_DISPLAY(n) do {} while(0) ++#endif ++ ++#ifndef LED_CONNECTED_ON ++#define LED_CONNECTED_ON do {} while(0) ++#define LED_CONNECTED_OFF do {} while(0) ++#endif ++#ifndef LED_EP0_ON ++#define LED_EP0_ON do {} while (0) ++#define LED_EP0_OFF do {} while (0) ++#endif ++ ++/*-------------------------------------------------------------------------*/ ++ ++static struct pxa2xx_udc *the_controller; ++ ++/* one GPIO should be used to detect host disconnect */ ++static int is_usb_connected(void) ++{ ++ static int first = 0; ++ ++ // CONFIG_ARCH_ADI_COYOTE cannot detect or force disconnect ++#ifdef CONFIG_ARCH_E7XX ++ if (machine_is_e7xx()) ++ return (GPLR(GPIO_E7XX_USB_DISC) ++ & GPIO_bit(GPIO_E7XX_USB_DISC)); ++#endif ++#if 0 ++#ifdef CONFIG_ARCH_H1900 ++ if (machine_is_h1900()) ++ return (!(GPLR(GPIO_NR_H1900_USB_DETECT_N) ++ & GPIO_bit(GPIO_NR_H1900_USB_DETECT_N))); ++#endif ++#ifdef CONFIG_ARCH_H3900 ++ if (machine_is_h3900()) ++ return 1; ++#endif ++#ifdef CONFIG_ARCH_H5400 ++ // h5400 ... ? ++#endif ++#endif ++#ifdef CONFIG_ARCH_INNOKOM ++ if (machine_is_innokom()) ++ return (GPLR(GPIO_INNOKOM_USB_DISC) ++ & GPIO_bit(GPIO_INNOKOM_USB_DISC)); ++#endif ++#ifdef CONFIG_ARCH_LUBBOCK ++ if (machine_is_lubbock()) ++ return ((LUB_MISC_RD & (1 << 9)) == 0); ++#endif ++ // Sharp's sources didn't show a corgi or poodle hook ++ ++ if (!first) { ++ pr_info("%s: can't check host connect\n", driver_name); ++ first++; ++ } ++ return 1; ++} ++ ++static int disc_first = 0; ++ ++/* one GPIO should force the host to see this device (or not) */ ++static void make_usb_disappear(void) ++{ ++ // CONFIG_ARCH_ADI_COYOTE cannot detect or force disconnect ++#ifdef CONFIG_ARCH_E7XX ++ if (machine_is_e7xx()) { ++ GPSR(GPIO_E7XX_USB_PULLUP) = GPIO_bit(GPIO_E7XX_USB_PULLUP); ++ return; ++ } ++#endif ++ // h1900 ... ? ++#ifdef CONFIG_ARCH_H3900 ++ if (machine_is_h3900()) { ++ GPDR0 &= ~GPIO_H3900_USBP_PULLUP; ++ return; ++ } ++#endif ++#ifdef CONFIG_ARCH_H5400 ++ if (machine_is_h5400()) { ++ GPDR(GPIO_NR_H5400_USB_PULLUP) &= ++ ~GPIO_bit(GPIO_NR_H5400_USB_PULLUP); ++ return; ++ } ++#endif ++#ifdef CONFIG_ARCH_INNOKOM ++ if (machine_is_innokom()) { ++ GPSR(GPIO_INNOKOM_USB_ONOFF) = GPIO_bit(GPIO_INNOKOM_USB_ONOFF); ++ printk("innokom: disappear\n"); ++ udelay(5); ++ return; ++ } ++#endif ++#ifdef CONFIG_ARCH_CSB226 ++ if (machine_is_csb226()) { ++ GPCR0 |= 0x00000080; ++ printk("csb226: disappear\n"); ++ udelay(5); ++ return; ++ } ++#endif ++ // lubbock has no D+ pullup ++#ifdef CONFIG_MACH_CORGI ++ if (machine_is_corgi()) { ++ GPDR(CORGI_CONNECT_GPIO) |= GPIO_bit(CORGI_CONNECT_GPIO); ++ GPCR(CORGI_CONNECT_GPIO) = GPIO_bit(CORGI_CONNECT_GPIO); ++ } ++#endif ++#ifdef CONFIG_MACH_POODLE ++ if (machine_is_poodle()) { ++ GPDR(POODLE_CONNECT_GPIO) |= GPIO_bit(POODLE_CONNECT_GPIO); ++ GPCR(POODLE_CONNECT_GPIO) = GPIO_bit(POODLE_CONNECT_GPIO); ++ } ++#endif ++ ++ if (!disc_first) { ++ pr_info("%s: can't force usb disconnect\n", driver_name); ++ disc_first++; ++ } ++} ++ ++static void let_usb_appear(void) ++{ ++ // CONFIG_ARCH_ADI_COYOTE cannot detect or force disconnect ++#ifdef CONFIG_ARCH_E7XX ++ if (machine_is_e7xx()) { ++ GPCR(GPIO_E7XX_USB_PULLUP) = GPIO_bit(GPIO_E7XX_USB_PULLUP); ++ return; ++ } ++#endif ++ // h1900 ... ? ++#ifdef CONFIG_ARCH_H3900 ++ if (machine_is_h3900()) { ++ GPDR0 |= GPIO_H3900_USBP_PULLUP; ++ GPSR0 |= GPIO_H3900_USBP_PULLUP; ++ return; ++ } ++#endif ++#ifdef CONFIG_ARCH_H5400 ++ if (machine_is_h5400()) { ++ GPDR(GPIO_NR_H5400_USB_PULLUP) |= ++ GPIO_bit(GPIO_NR_H5400_USB_PULLUP); ++ return; ++ } ++#endif ++#ifdef CONFIG_ARCH_INNOKOM ++ if (machine_is_innokom()) { ++ GPCR(GPIO_INNOKOM_USB_ONOFF) = GPIO_bit(GPIO_INNOKOM_USB_ONOFF); ++ printk("innokom: appear\n"); ++ udelay(5); ++ return; ++ } ++#endif ++#ifdef CONFIG_ARCH_CSB226 ++ if (machine_is_csb226()) { ++ GPDR0 |= 0x00000080; ++ GPSR0 |= 0x00000080; ++ printk("csb226: appear\n"); ++ udelay(5); ++ return; ++ } ++#endif ++ // lubbock has no D+ pullup ++#ifdef CONFIG_MACH_CORGI ++ if (machine_is_corgi()) { ++ GPDR(CORGI_CONNECT_GPIO) |= GPIO_bit(CORGI_CONNECT_GPIO); ++ GPSR(CORGI_CONNECT_GPIO) = GPIO_bit(CORGI_CONNECT_GPIO); ++ } ++#endif ++#ifdef CONFIG_MACH_POODLE ++ if (machine_is_poodle()) { ++ GPDR(POODLE_CONNECT_GPIO) |= GPIO_bit(POODLE_CONNECT_GPIO); ++ GPSR(POODLE_CONNECT_GPIO) = GPIO_bit(POODLE_CONNECT_GPIO); ++ } ++#endif ++ ++ if (!disc_first) { ++ pr_info("%s: can't force usb disconnect\n", driver_name); ++ disc_first++; ++ } ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* LEDs are only for debug */ ++#ifndef LED_CONNECTED_ON ++#define LED_CONNECTED_ON do {} while(0) ++#define LED_CONNECTED_OFF do {} while(0) ++#endif ++#ifndef LED_EP0_ON ++#define LED_EP0_ON do {} while (0) ++#define LED_EP0_OFF do {} while (0) ++#endif ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* ++ * Debugging support vanishes in non-debug builds. DBG_NORMAL should be ++ * mostly silent during normal use/testing, with no timing side-effects. ++ */ ++#define DBG_NORMAL 1 /* error paths, device state transitions */ ++#define DBG_VERBOSE 2 /* add some success path trace info */ ++#define DBG_NOISY 3 /* ... even more: request level */ ++#define DBG_VERY_NOISY 4 /* ... even more: packet level */ ++ ++#ifdef DEBUG ++ ++static const char *state_name[] = { ++ "EP0_IDLE", ++ "EP0_IN_DATA_PHASE", "EP0_OUT_DATA_PHASE", ++ "EP0_END_XFER", "EP0_STALL" ++}; ++ ++#define DMSG(stuff...) printk(KERN_DEBUG "udc: " stuff) ++ ++#ifdef VERBOSE ++# define UDC_DEBUG DBG_VERBOSE ++#else ++# define UDC_DEBUG DBG_NORMAL ++#endif ++ ++static void __attribute__ ((__unused__)) ++dump_udccr(const char *label) ++{ ++ u32 udccr = UDCCR; ++ DMSG("%s %02X =%s%s%s%s%s%s%s%s\n", ++ label, udccr, ++ (udccr & UDCCR_REM) ? " rem" : "", ++ (udccr & UDCCR_RSTIR) ? " rstir" : "", ++ (udccr & UDCCR_SRM) ? " srm" : "", ++ (udccr & UDCCR_SUSIR) ? " susir" : "", ++ (udccr & UDCCR_RESIR) ? " resir" : "", ++ (udccr & UDCCR_RSM) ? " rsm" : "", ++ (udccr & UDCCR_UDA) ? " uda" : "", ++ (udccr & UDCCR_UDE) ? " ude" : ""); ++} ++ ++static void __attribute__ ((__unused__)) ++dump_udccs0(const char *label) ++{ ++ u32 udccs0 = UDCCS0; ++ ++ DMSG("%s %s %02X =%s%s%s%s%s%s%s%s\n", ++ label, state_name[the_controller->ep0state], udccs0, ++ (udccs0 & UDCCS0_SA) ? " sa" : "", ++ (udccs0 & UDCCS0_RNE) ? " rne" : "", ++ (udccs0 & UDCCS0_FST) ? " fst" : "", ++ (udccs0 & UDCCS0_SST) ? " sst" : "", ++ (udccs0 & UDCCS0_DRWF) ? " dwrf" : "", ++ (udccs0 & UDCCS0_FTF) ? " ftf" : "", ++ (udccs0 & UDCCS0_IPR) ? " ipr" : "", ++ (udccs0 & UDCCS0_OPR) ? " opr" : ""); ++} ++ ++static void __attribute__ ((__unused__)) ++dump_state(struct pxa2xx_udc *dev) ++{ ++ u32 tmp; ++ unsigned i; ++ ++ DMSG("%s %s, uicr %02X.%02X, usir %02X.%02x, ufnr %02X.%02X\n", ++ is_usb_connected() ? "host " : "disconnected", ++ state_name[dev->ep0state], ++ UICR1, UICR0, USIR1, USIR0, UFNRH, UFNRL); ++ dump_udccr("udccr"); ++ if (dev->has_cfr) { ++ tmp = UDCCFR; ++ DMSG("udccfr %02X =%s%s\n", tmp, ++ (tmp & UDCCFR_AREN) ? " aren" : "", ++ (tmp & UDCCFR_ACM) ? " acm" : ""); ++ } ++ ++ if (!dev->driver) { ++ DMSG("no gadget driver bound\n"); ++ return; ++ } else ++ DMSG("ep0 driver '%s'\n", dev->driver->driver.name); ++ ++ if (!is_usb_connected()) ++ return; ++ ++ dump_udccs0 ("udccs0"); ++ DMSG("ep0 IN %lu/%lu, OUT %lu/%lu\n", ++ dev->stats.write.bytes, dev->stats.write.ops, ++ dev->stats.read.bytes, dev->stats.read.ops); ++ ++ for (i = 1; i < PXA_UDC_NUM_ENDPOINTS; i++) { ++ if (dev->ep [i].desc == 0) ++ continue; ++ DMSG ("udccs%d = %02x\n", i, *dev->ep->reg_udccs); ++ } ++} ++ ++#else ++ ++#define DMSG(stuff...) do{}while(0) ++ ++#define dump_udccr(x) do{}while(0) ++#define dump_udccs0(x) do{}while(0) ++#define dump_state(x) do{}while(0) ++ ++#define UDC_DEBUG ((unsigned)0) ++ ++#endif ++ ++#define DBG(lvl, stuff...) do{if ((lvl) <= UDC_DEBUG) DMSG(stuff);}while(0) ++ ++#define WARN(stuff...) printk(KERN_WARNING "udc: " stuff) ++#define INFO(stuff...) printk(KERN_INFO "udc: " stuff) ++ ++ ++/* 2.4 backport support */ ++#define irqreturn_t void ++#define IRQ_HANDLED ++ ++ ++#endif /* __LINUX_USB_GADGET_PXA2XX_H */ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/rndis.c kernel/drivers/usb/gadget/rndis.c +--- /tmp/kernel/drivers/usb/gadget/rndis.c 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/rndis.c 2005-04-22 17:53:19.501528608 +0200 +@@ -0,0 +1,1425 @@ ++/* ++ * RNDIS MSG parser ++ * ++ * Version: $Id: rndis.c,v 1.19 2004/03/25 21:33:46 robert Exp $ ++ * ++ * Authors: Benedikt Spranger, Pengutronix ++ * Robert Schwebel, Pengutronix ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * version 2, as published by the Free Software Foundation. ++ * ++ * This software was originally developed in conformance with ++ * Microsoft's Remote NDIS Specification License Agreement. ++ * ++ * 03/12/2004 Kai-Uwe Bloem ++ * Fixed message length bug in init_response ++ * ++ * 03/25/2004 Kai-Uwe Bloem ++ * Fixed rndis_rm_hdr length bug. ++ * ++ * Copyright (C) 2004 by David Brownell ++ * updates to merge with Linux 2.6, better match RNDIS spec ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++ ++#undef RNDIS_PM ++#undef VERBOSE ++ ++#include "rndis.h" ++ ++ ++/* The driver for your USB chip needs to support ep0 OUT to work with ++ * RNDIS, plus all three CDC Ethernet endpoints (interrupt not optional). ++ * ++ * Windows hosts need an INF file like Documentation/usb/linux.inf ++ * and will be happier if you provide the host_addr module parameter. ++ */ ++ ++#if 0 ++#define DEBUG(str,args...) do { \ ++ if (rndis_debug) \ ++ printk(KERN_DEBUG str , ## args ); \ ++ } while (0) ++static int rndis_debug = 0; ++ ++module_param (rndis_debug, bool, 0); ++MODULE_PARM_DESC (rndis_debug, "enable debugging"); ++ ++#else ++ ++#define rndis_debug 0 ++#define DEBUG(str,args...) do{}while(0) ++#endif ++ ++#define RNDIS_MAX_CONFIGS 1 ++ ++ ++static rndis_params rndis_per_dev_params [RNDIS_MAX_CONFIGS]; ++ ++/* Driver Version */ ++static const u32 rndis_driver_version = __constant_cpu_to_le32 (1); ++ ++/* Function Prototypes */ ++static int rndis_init_response (int configNr, rndis_init_msg_type *buf); ++static int rndis_query_response (int configNr, rndis_query_msg_type *buf); ++static int rndis_set_response (int configNr, rndis_set_msg_type *buf); ++static int rndis_reset_response (int configNr, rndis_reset_msg_type *buf); ++static int rndis_keepalive_response (int configNr, ++ rndis_keepalive_msg_type *buf); ++ ++static rndis_resp_t *rndis_add_response (int configNr, u32 length); ++ ++ ++/* NDIS Functions */ ++static int gen_ndis_query_resp (int configNr, u32 OID, rndis_resp_t *r) ++{ ++ int retval = -ENOTSUPP; ++ u32 length = 0; ++ u32 *tmp; ++ int i, count; ++ rndis_query_cmplt_type *resp; ++ ++ if (!r) return -ENOMEM; ++ resp = (rndis_query_cmplt_type *) r->buf; ++ ++ if (!resp) return -ENOMEM; ++ ++ switch (OID) { ++ ++ /* general oids (table 4-1) */ ++ ++ /* mandatory */ ++ case OID_GEN_SUPPORTED_LIST: ++ DEBUG ("%s: OID_GEN_SUPPORTED_LIST\n", __FUNCTION__); ++ length = sizeof (oid_supported_list); ++ count = length / sizeof (u32); ++ tmp = (u32 *) ((u8 *)resp + 24); ++ for (i = 0; i < count; i++) ++ tmp[i] = cpu_to_le32 (oid_supported_list[i]); ++ retval = 0; ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_HARDWARE_STATUS: ++ DEBUG("%s: OID_GEN_HARDWARE_STATUS\n", __FUNCTION__); ++ length = 4; ++ /* Bogus question! ++ * Hardware must be ready to receive high level protocols. ++ * BTW: ++ * reddite ergo quae sunt Caesaris Caesari ++ * et quae sunt Dei Deo! ++ */ ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_MEDIA_SUPPORTED: ++ DEBUG("%s: OID_GEN_MEDIA_SUPPORTED\n", __FUNCTION__); ++ length = 4; ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr].medium); ++ retval = 0; ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_MEDIA_IN_USE: ++ DEBUG("%s: OID_GEN_MEDIA_IN_USE\n", __FUNCTION__); ++ length = 4; ++ /* one medium, one transport... (maybe you do it better) */ ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr].medium); ++ retval = 0; ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_MAXIMUM_FRAME_SIZE: ++ DEBUG("%s: OID_GEN_MAXIMUM_FRAME_SIZE\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].dev) { ++ length = 4; ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr].dev->mtu); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_LINK_SPEED: ++ DEBUG("%s: OID_GEN_LINK_SPEED\n", __FUNCTION__); ++ length = 4; ++ if (rndis_per_dev_params [configNr].media_state ++ == NDIS_MEDIA_STATE_DISCONNECTED) ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ else ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr].speed); ++ retval = 0; ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_TRANSMIT_BLOCK_SIZE: ++ DEBUG("%s: OID_GEN_TRANSMIT_BLOCK_SIZE\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].dev) { ++ length = 4; ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr].dev->mtu); ++ retval = 0; ++ } ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_RECEIVE_BLOCK_SIZE: ++ DEBUG("%s: OID_GEN_RECEIVE_BLOCK_SIZE\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].dev) { ++ length = 4; ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr].dev->mtu); ++ retval = 0; ++ } ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_VENDOR_ID: ++ DEBUG("%s: OID_GEN_VENDOR_ID\n", __FUNCTION__); ++ length = 4; ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr].vendorID); ++ retval = 0; ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_VENDOR_DESCRIPTION: ++ DEBUG("%s: OID_GEN_VENDOR_DESCRIPTION\n", __FUNCTION__); ++ length = strlen (rndis_per_dev_params [configNr].vendorDescr); ++ memcpy ((u8 *) resp + 24, ++ rndis_per_dev_params [configNr].vendorDescr, length); ++ retval = 0; ++ break; ++ ++ case OID_GEN_VENDOR_DRIVER_VERSION: ++ DEBUG("%s: OID_GEN_VENDOR_DRIVER_VERSION\n", __FUNCTION__); ++ length = 4; ++ /* Created as LE */ ++ *((u32 *) resp + 6) = rndis_driver_version; ++ retval = 0; ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_CURRENT_PACKET_FILTER: ++ DEBUG("%s: OID_GEN_CURRENT_PACKET_FILTER\n", __FUNCTION__); ++ length = 4; ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params[configNr].filter); ++ retval = 0; ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_MAXIMUM_TOTAL_SIZE: ++ DEBUG("%s: OID_GEN_MAXIMUM_TOTAL_SIZE\n", __FUNCTION__); ++ length = 4; ++ *((u32 *) resp + 6) = __constant_cpu_to_le32( ++ RNDIS_MAX_TOTAL_SIZE); ++ retval = 0; ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_MEDIA_CONNECT_STATUS: ++ DEBUG("%s: OID_GEN_MEDIA_CONNECT_STATUS\n", __FUNCTION__); ++ length = 4; ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr] ++ .media_state); ++ retval = 0; ++ break; ++ ++ case OID_GEN_PHYSICAL_MEDIUM: ++ DEBUG("%s: OID_GEN_PHYSICAL_MEDIUM\n", __FUNCTION__); ++ length = 4; ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ break; ++ ++ /* The RNDIS specification is incomplete/wrong. Some versions ++ * of MS-Windows expect OIDs that aren't specified there. Other ++ * versions emit undefined RNDIS messages. DOCUMENT ALL THESE! ++ */ ++ case OID_GEN_MAC_OPTIONS: /* from WinME */ ++ DEBUG("%s: OID_GEN_MAC_OPTIONS\n", __FUNCTION__); ++ length = 4; ++ *((u32 *) resp + 6) = __constant_cpu_to_le32( ++ NDIS_MAC_OPTION_RECEIVE_SERIALIZED ++ | NDIS_MAC_OPTION_FULL_DUPLEX); ++ retval = 0; ++ break; ++ ++ /* statistics OIDs (table 4-2) */ ++ ++ /* mandatory */ ++ case OID_GEN_XMIT_OK: ++ DEBUG("%s: OID_GEN_XMIT_OK\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].stats) { ++ length = 4; ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr].stats->tx_packets - ++ rndis_per_dev_params [configNr].stats->tx_errors - ++ rndis_per_dev_params [configNr].stats->tx_dropped); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_RCV_OK: ++ DEBUG("%s: OID_GEN_RCV_OK\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].stats) { ++ length = 4; ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr].stats->rx_packets - ++ rndis_per_dev_params [configNr].stats->rx_errors - ++ rndis_per_dev_params [configNr].stats->rx_dropped); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_XMIT_ERROR: ++ DEBUG("%s: OID_GEN_XMIT_ERROR\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].stats) { ++ length = 4; ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr] ++ .stats->tx_errors); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_RCV_ERROR: ++ DEBUG("%s: OID_GEN_RCV_ERROR\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].stats) { ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr] ++ .stats->rx_errors); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ /* mandatory */ ++ case OID_GEN_RCV_NO_BUFFER: ++ DEBUG("%s: OID_GEN_RCV_NO_BUFFER\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].stats) { ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr] ++ .stats->rx_dropped); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++#ifdef RNDIS_OPTIONAL_STATS ++ case OID_GEN_DIRECTED_BYTES_XMIT: ++ DEBUG("%s: OID_GEN_DIRECTED_BYTES_XMIT\n", __FUNCTION__); ++ /* ++ * Aunt Tilly's size of shoes ++ * minus antarctica count of penguins ++ * divided by weight of Alpha Centauri ++ */ ++ if (rndis_per_dev_params [configNr].stats) { ++ length = 4; ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ (rndis_per_dev_params [configNr] ++ .stats->tx_packets - ++ rndis_per_dev_params [configNr] ++ .stats->tx_errors - ++ rndis_per_dev_params [configNr] ++ .stats->tx_dropped) ++ * 123); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ case OID_GEN_DIRECTED_FRAMES_XMIT: ++ DEBUG("%s: OID_GEN_DIRECTED_FRAMES_XMIT\n", __FUNCTION__); ++ /* dito */ ++ if (rndis_per_dev_params [configNr].stats) { ++ length = 4; ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ (rndis_per_dev_params [configNr] ++ .stats->tx_packets - ++ rndis_per_dev_params [configNr] ++ .stats->tx_errors - ++ rndis_per_dev_params [configNr] ++ .stats->tx_dropped) ++ / 123); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ case OID_GEN_MULTICAST_BYTES_XMIT: ++ DEBUG("%s: OID_GEN_MULTICAST_BYTES_XMIT\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].stats) { ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr] ++ .stats->multicast*1234); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ case OID_GEN_MULTICAST_FRAMES_XMIT: ++ DEBUG("%s: OID_GEN_MULTICAST_FRAMES_XMIT\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].stats) { ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr] ++ .stats->multicast); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ case OID_GEN_BROADCAST_BYTES_XMIT: ++ DEBUG("%s: OID_GEN_BROADCAST_BYTES_XMIT\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].stats) { ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr] ++ .stats->tx_packets/42*255); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ case OID_GEN_BROADCAST_FRAMES_XMIT: ++ DEBUG("%s: OID_GEN_BROADCAST_FRAMES_XMIT\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].stats) { ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr] ++ .stats->tx_packets/42); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ case OID_GEN_DIRECTED_BYTES_RCV: ++ DEBUG("%s: OID_GEN_DIRECTED_BYTES_RCV\n", __FUNCTION__); ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ break; ++ ++ case OID_GEN_DIRECTED_FRAMES_RCV: ++ DEBUG("%s: OID_GEN_DIRECTED_FRAMES_RCV\n", __FUNCTION__); ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ break; ++ ++ case OID_GEN_MULTICAST_BYTES_RCV: ++ DEBUG("%s: OID_GEN_MULTICAST_BYTES_RCV\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].stats) { ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr] ++ .stats->multicast * 1111); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ case OID_GEN_MULTICAST_FRAMES_RCV: ++ DEBUG("%s: OID_GEN_MULTICAST_FRAMES_RCV\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].stats) { ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr] ++ .stats->multicast); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ case OID_GEN_BROADCAST_BYTES_RCV: ++ DEBUG("%s: OID_GEN_BROADCAST_BYTES_RCV\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].stats) { ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr] ++ .stats->rx_packets/42*255); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ case OID_GEN_BROADCAST_FRAMES_RCV: ++ DEBUG("%s: OID_GEN_BROADCAST_FRAMES_RCV\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].stats) { ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr] ++ .stats->rx_packets/42); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ case OID_GEN_RCV_CRC_ERROR: ++ DEBUG("%s: OID_GEN_RCV_CRC_ERROR\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].stats) { ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr] ++ .stats->rx_crc_errors); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ case OID_GEN_TRANSMIT_QUEUE_LENGTH: ++ DEBUG("%s: OID_GEN_TRANSMIT_QUEUE_LENGTH\n", __FUNCTION__); ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ break; ++#endif /* RNDIS_OPTIONAL_STATS */ ++ ++ /* ieee802.3 OIDs (table 4-3) */ ++ ++ /* mandatory */ ++ case OID_802_3_PERMANENT_ADDRESS: ++ DEBUG("%s: OID_802_3_PERMANENT_ADDRESS\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].dev) { ++ length = ETH_ALEN; ++ memcpy ((u8 *) resp + 24, ++ rndis_per_dev_params [configNr].host_mac, ++ length); ++ retval = 0; ++ } else { ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ } ++ break; ++ ++ /* mandatory */ ++ case OID_802_3_CURRENT_ADDRESS: ++ DEBUG("%s: OID_802_3_CURRENT_ADDRESS\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].dev) { ++ length = ETH_ALEN; ++ memcpy ((u8 *) resp + 24, ++ rndis_per_dev_params [configNr].host_mac, ++ length); ++ retval = 0; ++ } ++ break; ++ ++ /* mandatory */ ++ case OID_802_3_MULTICAST_LIST: ++ DEBUG("%s: OID_802_3_MULTICAST_LIST\n", __FUNCTION__); ++ length = 4; ++ /* Multicast base address only */ ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0xE0000000); ++ retval = 0; ++ break; ++ ++ /* mandatory */ ++ case OID_802_3_MAXIMUM_LIST_SIZE: ++ DEBUG("%s: OID_802_3_MAXIMUM_LIST_SIZE\n", __FUNCTION__); ++ length = 4; ++ /* Multicast base address only */ ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (1); ++ retval = 0; ++ break; ++ ++ case OID_802_3_MAC_OPTIONS: ++ DEBUG("%s: OID_802_3_MAC_OPTIONS\n", __FUNCTION__); ++ break; ++ ++ /* ieee802.3 statistics OIDs (table 4-4) */ ++ ++ /* mandatory */ ++ case OID_802_3_RCV_ERROR_ALIGNMENT: ++ DEBUG("%s: OID_802_3_RCV_ERROR_ALIGNMENT\n", __FUNCTION__); ++ if (rndis_per_dev_params [configNr].stats) ++ { ++ length = 4; ++ *((u32 *) resp + 6) = cpu_to_le32 ( ++ rndis_per_dev_params [configNr] ++ .stats->rx_frame_errors); ++ retval = 0; ++ } ++ break; ++ ++ /* mandatory */ ++ case OID_802_3_XMIT_ONE_COLLISION: ++ DEBUG("%s: OID_802_3_XMIT_ONE_COLLISION\n", __FUNCTION__); ++ length = 4; ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ break; ++ ++ /* mandatory */ ++ case OID_802_3_XMIT_MORE_COLLISIONS: ++ DEBUG("%s: OID_802_3_XMIT_MORE_COLLISIONS\n", __FUNCTION__); ++ length = 4; ++ *((u32 *) resp + 6) = __constant_cpu_to_le32 (0); ++ retval = 0; ++ break; ++ ++#ifdef RNDIS_OPTIONAL_STATS ++ case OID_802_3_XMIT_DEFERRED: ++ DEBUG("%s: OID_802_3_XMIT_DEFERRED\n", __FUNCTION__); ++ /* TODO */ ++ break; ++ ++ case OID_802_3_XMIT_MAX_COLLISIONS: ++ DEBUG("%s: OID_802_3_XMIT_MAX_COLLISIONS\n", __FUNCTION__); ++ /* TODO */ ++ break; ++ ++ case OID_802_3_RCV_OVERRUN: ++ DEBUG("%s: OID_802_3_RCV_OVERRUN\n", __FUNCTION__); ++ /* TODO */ ++ break; ++ ++ case OID_802_3_XMIT_UNDERRUN: ++ DEBUG("%s: OID_802_3_XMIT_UNDERRUN\n", __FUNCTION__); ++ /* TODO */ ++ break; ++ ++ case OID_802_3_XMIT_HEARTBEAT_FAILURE: ++ DEBUG("%s: OID_802_3_XMIT_HEARTBEAT_FAILURE\n", __FUNCTION__); ++ /* TODO */ ++ break; ++ ++ case OID_802_3_XMIT_TIMES_CRS_LOST: ++ DEBUG("%s: OID_802_3_XMIT_TIMES_CRS_LOST\n", __FUNCTION__); ++ /* TODO */ ++ break; ++ ++ case OID_802_3_XMIT_LATE_COLLISIONS: ++ DEBUG("%s: OID_802_3_XMIT_LATE_COLLISIONS\n", __FUNCTION__); ++ /* TODO */ ++ break; ++#endif /* RNDIS_OPTIONAL_STATS */ ++ ++#ifdef RNDIS_PM ++ /* power management OIDs (table 4-5) */ ++ case OID_PNP_CAPABILITIES: ++ DEBUG("%s: OID_PNP_CAPABILITIES\n", __FUNCTION__); ++ ++ /* just PM, and remote wakeup on link status change ++ * (not magic packet or pattern match) ++ */ ++ length = sizeof (struct NDIS_PNP_CAPABILITIES); ++ memset (resp, 0, length); ++ { ++ struct NDIS_PNP_CAPABILITIES *caps = (void *) resp; ++ ++ caps->Flags = NDIS_DEVICE_WAKE_UP_ENABLE; ++ caps->WakeUpCapabilities.MinLinkChangeWakeUp ++ = NdisDeviceStateD3; ++ ++ /* FIXME then use usb_gadget_wakeup(), and ++ * set USB_CONFIG_ATT_WAKEUP in config desc ++ */ ++ } ++ retval = 0; ++ break; ++ case OID_PNP_QUERY_POWER: ++ DEBUG("%s: OID_PNP_QUERY_POWER\n", __FUNCTION__); ++ /* sure, handle any power state that maps to USB suspend */ ++ retval = 0; ++ break; ++#endif ++ ++ default: ++ printk (KERN_WARNING "%s: query unknown OID 0x%08X\n", ++ __FUNCTION__, OID); ++ } ++ ++ resp->InformationBufferOffset = __constant_cpu_to_le32 (16); ++ resp->InformationBufferLength = cpu_to_le32 (length); ++ resp->MessageLength = cpu_to_le32 (24 + length); ++ r->length = 24 + length; ++ return retval; ++} ++ ++static int gen_ndis_set_resp (u8 configNr, u32 OID, u8 *buf, u32 buf_len, ++ rndis_resp_t *r) ++{ ++ rndis_set_cmplt_type *resp; ++ int i, retval = -ENOTSUPP; ++ struct rndis_params *params; ++ ++ if (!r) ++ return -ENOMEM; ++ resp = (rndis_set_cmplt_type *) r->buf; ++ if (!resp) ++ return -ENOMEM; ++ ++ DEBUG("set OID %08x value, len %d:\n", OID, buf_len); ++ for (i = 0; i < buf_len; i += 16) { ++ DEBUG ("%03d: " ++ " %02x %02x %02x %02x" ++ " %02x %02x %02x %02x" ++ " %02x %02x %02x %02x" ++ " %02x %02x %02x %02x" ++ "\n", ++ i, ++ buf[i], buf [i+1], ++ buf[i+2], buf[i+3], ++ buf[i+4], buf [i+5], ++ buf[i+6], buf[i+7], ++ buf[i+8], buf [i+9], ++ buf[i+10], buf[i+11], ++ buf[i+12], buf [i+13], ++ buf[i+14], buf[i+15]); ++ } ++ ++ switch (OID) { ++ case OID_GEN_CURRENT_PACKET_FILTER: ++ params = &rndis_per_dev_params [configNr]; ++ retval = 0; ++ ++ /* FIXME use these NDIS_PACKET_TYPE_* bitflags to ++ * filter packets in hard_start_xmit() ++ * NDIS_PACKET_TYPE_x == USB_CDC_PACKET_TYPE_x for x in: ++ * PROMISCUOUS, DIRECTED, ++ * MULTICAST, ALL_MULTICAST, BROADCAST ++ */ ++ params->filter = cpu_to_le32p((u32 *)buf); ++ DEBUG("%s: OID_GEN_CURRENT_PACKET_FILTER %08x\n", ++ __FUNCTION__, params->filter); ++ ++ /* this call has a significant side effect: it's ++ * what makes the packet flow start and stop, like ++ * activating the CDC Ethernet altsetting. ++ */ ++ if (params->filter) { ++ params->state = RNDIS_DATA_INITIALIZED; ++ netif_carrier_on(params->dev); ++ if (netif_running(params->dev)) ++ netif_wake_queue (params->dev); ++ } else { ++ params->state = RNDIS_INITIALIZED; ++ netif_carrier_off (params->dev); ++ netif_stop_queue (params->dev); ++ } ++ break; ++ ++ case OID_802_3_MULTICAST_LIST: ++ /* I think we can ignore this */ ++ DEBUG("%s: OID_802_3_MULTICAST_LIST\n", __FUNCTION__); ++ retval = 0; ++ break; ++#if 0 ++ case OID_GEN_RNDIS_CONFIG_PARAMETER: ++ { ++ struct rndis_config_parameter *param; ++ param = (struct rndis_config_parameter *) buf; ++ DEBUG("%s: OID_GEN_RNDIS_CONFIG_PARAMETER '%*s'\n", ++ __FUNCTION__, ++ min(cpu_to_le32(param->ParameterNameLength),80), ++ buf + param->ParameterNameOffset); ++ retval = 0; ++ } ++ break; ++#endif ++ ++#ifdef RNDIS_PM ++ case OID_PNP_SET_POWER: ++ DEBUG ("OID_PNP_SET_POWER\n"); ++ /* sure, handle any power state that maps to USB suspend */ ++ retval = 0; ++ break; ++ ++ case OID_PNP_ENABLE_WAKE_UP: ++ /* always-connected ... */ ++ DEBUG ("OID_PNP_ENABLE_WAKE_UP\n"); ++ retval = 0; ++ break; ++ ++ // no PM resume patterns supported (specified where?) ++ // so OID_PNP_{ADD,REMOVE}_WAKE_UP_PATTERN always fails ++#endif ++ ++ default: ++ printk (KERN_WARNING "%s: set unknown OID 0x%08X, size %d\n", ++ __FUNCTION__, OID, buf_len); ++ } ++ ++ return retval; ++} ++ ++/* ++ * Response Functions ++ */ ++ ++static int rndis_init_response (int configNr, rndis_init_msg_type *buf) ++{ ++ rndis_init_cmplt_type *resp; ++ rndis_resp_t *r; ++ ++ if (!rndis_per_dev_params [configNr].dev) return -ENOTSUPP; ++ ++ r = rndis_add_response (configNr, sizeof (rndis_init_cmplt_type)); ++ ++ if (!r) return -ENOMEM; ++ ++ resp = (rndis_init_cmplt_type *) r->buf; ++ ++ if (!resp) return -ENOMEM; ++ ++ resp->MessageType = __constant_cpu_to_le32 ( ++ REMOTE_NDIS_INITIALIZE_CMPLT); ++ resp->MessageLength = __constant_cpu_to_le32 (52); ++ resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ ++ resp->Status = __constant_cpu_to_le32 (RNDIS_STATUS_SUCCESS); ++ resp->MajorVersion = __constant_cpu_to_le32 (RNDIS_MAJOR_VERSION); ++ resp->MinorVersion = __constant_cpu_to_le32 (RNDIS_MINOR_VERSION); ++ resp->DeviceFlags = __constant_cpu_to_le32 (RNDIS_DF_CONNECTIONLESS); ++ resp->Medium = __constant_cpu_to_le32 (RNDIS_MEDIUM_802_3); ++ resp->MaxPacketsPerTransfer = __constant_cpu_to_le32 (1); ++ resp->MaxTransferSize = cpu_to_le32 ( ++ rndis_per_dev_params [configNr].dev->mtu ++ + sizeof (struct ethhdr) ++ + sizeof (struct rndis_packet_msg_type) ++ + 22); ++ resp->PacketAlignmentFactor = __constant_cpu_to_le32 (0); ++ resp->AFListOffset = __constant_cpu_to_le32 (0); ++ resp->AFListSize = __constant_cpu_to_le32 (0); ++ ++ if (rndis_per_dev_params [configNr].ack) ++ rndis_per_dev_params [configNr].ack ( ++ rndis_per_dev_params [configNr].dev); ++ ++ return 0; ++} ++ ++static int rndis_query_response (int configNr, rndis_query_msg_type *buf) ++{ ++ rndis_query_cmplt_type *resp; ++ rndis_resp_t *r; ++ ++ // DEBUG("%s: OID = %08X\n", __FUNCTION__, cpu_to_le32(buf->OID)); ++ if (!rndis_per_dev_params [configNr].dev) return -ENOTSUPP; ++ ++ /* ++ * we need more memory: ++ * oid_supported_list is the largest answer ++ */ ++ r = rndis_add_response (configNr, sizeof (oid_supported_list)); ++ ++ if (!r) return -ENOMEM; ++ resp = (rndis_query_cmplt_type *) r->buf; ++ ++ if (!resp) return -ENOMEM; ++ ++ resp->MessageType = __constant_cpu_to_le32 (REMOTE_NDIS_QUERY_CMPLT); ++ resp->MessageLength = __constant_cpu_to_le32 (24); ++ resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ ++ ++ if (gen_ndis_query_resp (configNr, cpu_to_le32 (buf->OID), r)) { ++ /* OID not supported */ ++ resp->Status = __constant_cpu_to_le32 ( ++ RNDIS_STATUS_NOT_SUPPORTED); ++ resp->InformationBufferLength = __constant_cpu_to_le32 (0); ++ resp->InformationBufferOffset = __constant_cpu_to_le32 (0); ++ } else ++ resp->Status = __constant_cpu_to_le32 (RNDIS_STATUS_SUCCESS); ++ ++ if (rndis_per_dev_params [configNr].ack) ++ rndis_per_dev_params [configNr].ack ( ++ rndis_per_dev_params [configNr].dev); ++ return 0; ++} ++ ++static int rndis_set_response (int configNr, rndis_set_msg_type *buf) ++{ ++ u32 BufLength, BufOffset; ++ rndis_set_cmplt_type *resp; ++ rndis_resp_t *r; ++ ++ r = rndis_add_response (configNr, sizeof (rndis_set_cmplt_type)); ++ ++ if (!r) return -ENOMEM; ++ resp = (rndis_set_cmplt_type *) r->buf; ++ if (!resp) return -ENOMEM; ++ ++ BufLength = cpu_to_le32 (buf->InformationBufferLength); ++ BufOffset = cpu_to_le32 (buf->InformationBufferOffset); ++ ++#ifdef VERBOSE ++ DEBUG("%s: Length: %d\n", __FUNCTION__, BufLength); ++ DEBUG("%s: Offset: %d\n", __FUNCTION__, BufOffset); ++ DEBUG("%s: InfoBuffer: ", __FUNCTION__); ++ ++ for (i = 0; i < BufLength; i++) { ++ DEBUG ("%02x ", *(((u8 *) buf) + i + 8 + BufOffset)); ++ } ++ ++ DEBUG ("\n"); ++#endif ++ ++ resp->MessageType = __constant_cpu_to_le32 (REMOTE_NDIS_SET_CMPLT); ++ resp->MessageLength = __constant_cpu_to_le32 (16); ++ resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ ++ if (gen_ndis_set_resp (configNr, cpu_to_le32 (buf->OID), ++ ((u8 *) buf) + 8 + BufOffset, BufLength, r)) ++ resp->Status = __constant_cpu_to_le32 (RNDIS_STATUS_NOT_SUPPORTED); ++ else resp->Status = __constant_cpu_to_le32 (RNDIS_STATUS_SUCCESS); ++ ++ if (rndis_per_dev_params [configNr].ack) ++ rndis_per_dev_params [configNr].ack ( ++ rndis_per_dev_params [configNr].dev); ++ ++ return 0; ++} ++ ++static int rndis_reset_response (int configNr, rndis_reset_msg_type *buf) ++{ ++ rndis_reset_cmplt_type *resp; ++ rndis_resp_t *r; ++ ++ r = rndis_add_response (configNr, sizeof (rndis_reset_cmplt_type)); ++ ++ if (!r) return -ENOMEM; ++ resp = (rndis_reset_cmplt_type *) r->buf; ++ if (!resp) return -ENOMEM; ++ ++ resp->MessageType = __constant_cpu_to_le32 (REMOTE_NDIS_RESET_CMPLT); ++ resp->MessageLength = __constant_cpu_to_le32 (16); ++ resp->Status = __constant_cpu_to_le32 (RNDIS_STATUS_SUCCESS); ++ /* resent information */ ++ resp->AddressingReset = __constant_cpu_to_le32 (1); ++ ++ if (rndis_per_dev_params [configNr].ack) ++ rndis_per_dev_params [configNr].ack ( ++ rndis_per_dev_params [configNr].dev); ++ ++ return 0; ++} ++ ++static int rndis_keepalive_response (int configNr, ++ rndis_keepalive_msg_type *buf) ++{ ++ rndis_keepalive_cmplt_type *resp; ++ rndis_resp_t *r; ++ ++ /* host "should" check only in RNDIS_DATA_INITIALIZED state */ ++ ++ r = rndis_add_response (configNr, sizeof (rndis_keepalive_cmplt_type)); ++ resp = (rndis_keepalive_cmplt_type *) r->buf; ++ if (!resp) return -ENOMEM; ++ ++ resp->MessageType = __constant_cpu_to_le32 ( ++ REMOTE_NDIS_KEEPALIVE_CMPLT); ++ resp->MessageLength = __constant_cpu_to_le32 (16); ++ resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ ++ resp->Status = __constant_cpu_to_le32 (RNDIS_STATUS_SUCCESS); ++ ++ if (rndis_per_dev_params [configNr].ack) ++ rndis_per_dev_params [configNr].ack ( ++ rndis_per_dev_params [configNr].dev); ++ ++ return 0; ++} ++ ++ ++/* ++ * Device to Host Comunication ++ */ ++static int rndis_indicate_status_msg (int configNr, u32 status) ++{ ++ rndis_indicate_status_msg_type *resp; ++ rndis_resp_t *r; ++ ++ if (rndis_per_dev_params [configNr].state == RNDIS_UNINITIALIZED) ++ return -ENOTSUPP; ++ ++ r = rndis_add_response (configNr, ++ sizeof (rndis_indicate_status_msg_type)); ++ if (!r) return -ENOMEM; ++ ++ resp = (rndis_indicate_status_msg_type *) r->buf; ++ if (!resp) return -ENOMEM; ++ ++ resp->MessageType = __constant_cpu_to_le32 ( ++ REMOTE_NDIS_INDICATE_STATUS_MSG); ++ resp->MessageLength = __constant_cpu_to_le32 (20); ++ resp->Status = cpu_to_le32 (status); ++ resp->StatusBufferLength = __constant_cpu_to_le32 (0); ++ resp->StatusBufferOffset = __constant_cpu_to_le32 (0); ++ ++ if (rndis_per_dev_params [configNr].ack) ++ rndis_per_dev_params [configNr].ack ( ++ rndis_per_dev_params [configNr].dev); ++ return 0; ++} ++ ++int rndis_signal_connect (int configNr) ++{ ++ rndis_per_dev_params [configNr].media_state ++ = NDIS_MEDIA_STATE_CONNECTED; ++ return rndis_indicate_status_msg (configNr, ++ RNDIS_STATUS_MEDIA_CONNECT); ++} ++ ++int rndis_signal_disconnect (int configNr) ++{ ++ rndis_per_dev_params [configNr].media_state ++ = NDIS_MEDIA_STATE_DISCONNECTED; ++ return rndis_indicate_status_msg (configNr, ++ RNDIS_STATUS_MEDIA_DISCONNECT); ++} ++ ++void rndis_set_host_mac (int configNr, const u8 *addr) ++{ ++ rndis_per_dev_params [configNr].host_mac = addr; ++} ++ ++/* ++ * Message Parser ++ */ ++int rndis_msg_parser (u8 configNr, u8 *buf) ++{ ++ u32 MsgType, MsgLength, *tmp; ++ struct rndis_params *params; ++ ++ if (!buf) ++ return -ENOMEM; ++ ++ tmp = (u32 *) buf; ++ MsgType = cpu_to_le32p(tmp++); ++ MsgLength = cpu_to_le32p(tmp++); ++ ++ if (configNr >= RNDIS_MAX_CONFIGS) ++ return -ENOTSUPP; ++ params = &rndis_per_dev_params [configNr]; ++ ++ /* For USB: responses may take up to 10 seconds */ ++ switch (MsgType) ++ { ++ case REMOTE_NDIS_INITIALIZE_MSG: ++ DEBUG("%s: REMOTE_NDIS_INITIALIZE_MSG\n", ++ __FUNCTION__ ); ++ params->state = RNDIS_INITIALIZED; ++ return rndis_init_response (configNr, ++ (rndis_init_msg_type *) buf); ++ ++ case REMOTE_NDIS_HALT_MSG: ++ DEBUG("%s: REMOTE_NDIS_HALT_MSG\n", ++ __FUNCTION__ ); ++ params->state = RNDIS_UNINITIALIZED; ++ if (params->dev) { ++ netif_carrier_off (params->dev); ++ netif_stop_queue (params->dev); ++ } ++ return 0; ++ ++ case REMOTE_NDIS_QUERY_MSG: ++ return rndis_query_response (configNr, ++ (rndis_query_msg_type *) buf); ++ ++ case REMOTE_NDIS_SET_MSG: ++ return rndis_set_response (configNr, ++ (rndis_set_msg_type *) buf); ++ ++ case REMOTE_NDIS_RESET_MSG: ++ DEBUG("%s: REMOTE_NDIS_RESET_MSG\n", ++ __FUNCTION__ ); ++ return rndis_reset_response (configNr, ++ (rndis_reset_msg_type *) buf); ++ ++ case REMOTE_NDIS_KEEPALIVE_MSG: ++ /* For USB: host does this every 5 seconds */ ++#ifdef VERBOSE ++ DEBUG("%s: REMOTE_NDIS_KEEPALIVE_MSG\n", ++ __FUNCTION__ ); ++#endif ++ return rndis_keepalive_response (configNr, ++ (rndis_keepalive_msg_type *) ++ buf); ++ ++ default: ++ /* At least Windows XP emits some undefined RNDIS messages. ++ * In one case those messages seemed to relate to the host ++ * suspending itself. ++ */ ++ printk (KERN_WARNING ++ "%s: unknown RNDIS message 0x%08X len %d\n", ++ __FUNCTION__ , MsgType, MsgLength); ++ { ++ unsigned i; ++ for (i = 0; i < MsgLength; i += 16) { ++ DEBUG ("%03d: " ++ " %02x %02x %02x %02x" ++ " %02x %02x %02x %02x" ++ " %02x %02x %02x %02x" ++ " %02x %02x %02x %02x" ++ "\n", ++ i, ++ buf[i], buf [i+1], ++ buf[i+2], buf[i+3], ++ buf[i+4], buf [i+5], ++ buf[i+6], buf[i+7], ++ buf[i+8], buf [i+9], ++ buf[i+10], buf[i+11], ++ buf[i+12], buf [i+13], ++ buf[i+14], buf[i+15]); ++ } ++ } ++ break; ++ } ++ ++ return -ENOTSUPP; ++} ++ ++int rndis_register (int (* rndis_control_ack) (struct net_device *)) ++{ ++ u8 i; ++ ++ for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { ++ if (!rndis_per_dev_params [i].used) { ++ rndis_per_dev_params [i].used = 1; ++ rndis_per_dev_params [i].ack = rndis_control_ack; ++ DEBUG("%s: configNr = %d\n", __FUNCTION__, i); ++ return i; ++ } ++ } ++ DEBUG("failed\n"); ++ ++ return -1; ++} ++ ++void rndis_deregister (int configNr) ++{ ++ DEBUG("%s: \n", __FUNCTION__ ); ++ ++ if (configNr >= RNDIS_MAX_CONFIGS) return; ++ rndis_per_dev_params [configNr].used = 0; ++ ++ return; ++} ++ ++int rndis_set_param_dev (u8 configNr, struct net_device *dev, ++ struct net_device_stats *stats) ++{ ++ DEBUG("%s:\n", __FUNCTION__ ); ++ if (!dev || !stats) return -1; ++ if (configNr >= RNDIS_MAX_CONFIGS) return -1; ++ ++ rndis_per_dev_params [configNr].dev = dev; ++ rndis_per_dev_params [configNr].stats = stats; ++ ++ return 0; ++} ++ ++int rndis_set_param_vendor (u8 configNr, u32 vendorID, const char *vendorDescr) ++{ ++ DEBUG("%s:\n", __FUNCTION__ ); ++ if (!vendorDescr) return -1; ++ if (configNr >= RNDIS_MAX_CONFIGS) return -1; ++ ++ rndis_per_dev_params [configNr].vendorID = vendorID; ++ rndis_per_dev_params [configNr].vendorDescr = vendorDescr; ++ ++ return 0; ++} ++ ++int rndis_set_param_medium (u8 configNr, u32 medium, u32 speed) ++{ ++ DEBUG("%s:\n", __FUNCTION__ ); ++ if (configNr >= RNDIS_MAX_CONFIGS) return -1; ++ ++ rndis_per_dev_params [configNr].medium = medium; ++ rndis_per_dev_params [configNr].speed = speed; ++ ++ return 0; ++} ++ ++void rndis_add_hdr (struct sk_buff *skb) ++{ ++ if (!skb) return; ++ skb_push (skb, sizeof (struct rndis_packet_msg_type)); ++ memset (skb->data, 0, sizeof (struct rndis_packet_msg_type)); ++ *((u32 *) skb->data) = __constant_cpu_to_le32 (1); ++ *((u32 *) skb->data + 1) = cpu_to_le32(skb->len); ++ *((u32 *) skb->data + 2) = __constant_cpu_to_le32 (36); ++ *((u32 *) skb->data + 3) = cpu_to_le32(skb->len - 44); ++ ++ return; ++} ++ ++void rndis_free_response (int configNr, u8 *buf) ++{ ++ rndis_resp_t *r; ++ struct list_head *act, *tmp; ++ ++ list_for_each_safe (act, tmp, ++ &(rndis_per_dev_params [configNr].resp_queue)) ++ { ++ r = list_entry (act, rndis_resp_t, list); ++ if (r && r->buf == buf) { ++ list_del (&r->list); ++ kfree (r); ++ } ++ } ++} ++ ++u8 *rndis_get_next_response (int configNr, u32 *length) ++{ ++ rndis_resp_t *r; ++ struct list_head *act, *tmp; ++ ++ if (!length) return NULL; ++ ++ list_for_each_safe (act, tmp, ++ &(rndis_per_dev_params [configNr].resp_queue)) ++ { ++ r = list_entry (act, rndis_resp_t, list); ++ if (!r->send) { ++ r->send = 1; ++ *length = r->length; ++ return r->buf; ++ } ++ } ++ ++ return NULL; ++} ++ ++static rndis_resp_t *rndis_add_response (int configNr, u32 length) ++{ ++ rndis_resp_t *r; ++ ++ r = kmalloc (sizeof (rndis_resp_t) + length, GFP_ATOMIC); ++ if (!r) return NULL; ++ ++ r->buf = (u8 *) (r + 1); ++ r->length = length; ++ r->send = 0; ++ ++ list_add_tail (&r->list, ++ &(rndis_per_dev_params [configNr].resp_queue)); ++ return r; ++} ++ ++int rndis_rm_hdr (u8 *buf, u32 *length) ++{ ++ u32 i, messageLen, dataOffset, *tmp; ++ ++ tmp = (u32 *) buf; ++ ++ if (!buf || !length) return -1; ++ if (cpu_to_le32p(tmp++) != 1) return -1; ++ ++ messageLen = cpu_to_le32p(tmp++); ++ dataOffset = cpu_to_le32p(tmp++) + 8; ++ ++ if (messageLen < dataOffset || messageLen > *length) return -1; ++ ++ for (i = dataOffset; i < messageLen; i++) ++ buf [i - dataOffset] = buf [i]; ++ ++ *length = messageLen - dataOffset; ++ ++ return 0; ++} ++ ++#ifdef CONFIG_USB_GADGET_DEBUG_FILES ++ ++static int rndis_proc_read (char *page, char **start, off_t off, int count, int *eof, ++ void *data) ++{ ++ char *out = page; ++ int len; ++ rndis_params *param = (rndis_params *) data; ++ ++ out += snprintf (out, count, ++ "Config Nr. %d\n" ++ "used : %s\n" ++ "state : %s\n" ++ "medium : 0x%08X\n" ++ "speed : %d\n" ++ "cable : %s\n" ++ "vendor ID : 0x%08X\n" ++ "vendor : %s\n", ++ param->confignr, (param->used) ? "y" : "n", ++ ({ char *s = "?"; ++ switch (param->state) { ++ case RNDIS_UNINITIALIZED: ++ s = "RNDIS_UNINITIALIZED"; break; ++ case RNDIS_INITIALIZED: ++ s = "RNDIS_INITIALIZED"; break; ++ case RNDIS_DATA_INITIALIZED: ++ s = "RNDIS_DATA_INITIALIZED"; break; ++ }; s; }), ++ param->medium, ++ (param->media_state) ? 0 : param->speed*100, ++ (param->media_state) ? "disconnected" : "connected", ++ param->vendorID, param->vendorDescr); ++ ++ len = out - page; ++ len -= off; ++ ++ if (len < count) { ++ *eof = 1; ++ if (len <= 0) ++ return 0; ++ } else ++ len = count; ++ ++ *start = page + off; ++ return len; ++} ++ ++static int rndis_proc_write (struct file *file, const char __user *buffer, ++ unsigned long count, void *data) ++{ ++ rndis_params *p = data; ++ u32 speed = 0; ++ int i, fl_speed = 0; ++ ++ for (i = 0; i < count; i++) { ++ char c; ++ if (get_user(c, buffer)) ++ return -EFAULT; ++ switch (c) { ++ case '0': ++ case '1': ++ case '2': ++ case '3': ++ case '4': ++ case '5': ++ case '6': ++ case '7': ++ case '8': ++ case '9': ++ fl_speed = 1; ++ speed = speed*10 + c - '0'; ++ break; ++ case 'C': ++ case 'c': ++ rndis_signal_connect (p->confignr); ++ break; ++ case 'D': ++ case 'd': ++ rndis_signal_disconnect(p->confignr); ++ break; ++ default: ++ if (fl_speed) p->speed = speed; ++ else DEBUG ("%c is not valid\n", c); ++ break; ++ } ++ ++ buffer++; ++ } ++ ++ return count; ++} ++ ++#define NAME_TEMPLATE "driver/rndis-%03d" ++ ++static struct proc_dir_entry *rndis_connect_state [RNDIS_MAX_CONFIGS]; ++ ++#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ ++ ++ ++int __init rndis_init (void) ++{ ++ u8 i; ++ ++ for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { ++#ifdef CONFIG_USB_GADGET_DEBUG_FILES ++ char name [20]; ++ ++ sprintf (name, NAME_TEMPLATE, i); ++ if (!(rndis_connect_state [i] ++ = create_proc_entry (name, 0660, NULL))) ++ { ++ DEBUG ("%s :remove entries", __FUNCTION__); ++ while (i) { ++ sprintf (name, NAME_TEMPLATE, --i); ++ remove_proc_entry (name, NULL); ++ } ++ DEBUG ("\n"); ++ return -EIO; ++ } ++ ++ rndis_connect_state [i]->nlink = 1; ++ rndis_connect_state [i]->write_proc = rndis_proc_write; ++ rndis_connect_state [i]->read_proc = rndis_proc_read; ++ rndis_connect_state [i]->data = (void *) ++ (rndis_per_dev_params + i); ++#endif ++ rndis_per_dev_params [i].confignr = i; ++ rndis_per_dev_params [i].used = 0; ++ rndis_per_dev_params [i].state = RNDIS_UNINITIALIZED; ++ rndis_per_dev_params [i].media_state ++ = NDIS_MEDIA_STATE_DISCONNECTED; ++ INIT_LIST_HEAD (&(rndis_per_dev_params [i].resp_queue)); ++ } ++ ++ return 0; ++} ++ ++void rndis_exit (void) ++{ ++#ifdef CONFIG_USB_GADGET_DEBUG_FILES ++ u8 i; ++ char name [20]; ++ ++ for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { ++ sprintf (name, NAME_TEMPLATE, i); ++ remove_proc_entry (name, NULL); ++ } ++#endif ++} ++ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/rndis.h kernel/drivers/usb/gadget/rndis.h +--- /tmp/kernel/drivers/usb/gadget/rndis.h 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/rndis.h 2005-04-22 17:53:19.504528119 +0200 +@@ -0,0 +1,348 @@ ++/* ++ * RNDIS Definitions for Remote NDIS ++ * ++ * Version: $Id: rndis.h,v 1.15 2004/03/25 21:33:46 robert Exp $ ++ * ++ * Authors: Benedikt Spranger, Pengutronix ++ * Robert Schwebel, Pengutronix ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * version 2, as published by the Free Software Foundation. ++ * ++ * This software was originally developed in conformance with ++ * Microsoft's Remote NDIS Specification License Agreement. ++ */ ++ ++#ifndef _LINUX_RNDIS_H ++#define _LINUX_RNDIS_H ++ ++#include "ndis.h" ++ ++#define RNDIS_MAXIMUM_FRAME_SIZE 1518 ++#define RNDIS_MAX_TOTAL_SIZE 1558 ++ ++/* Remote NDIS Versions */ ++#define RNDIS_MAJOR_VERSION 1 ++#define RNDIS_MINOR_VERSION 0 ++ ++/* Status Values */ ++#define RNDIS_STATUS_SUCCESS 0x00000000U /* Success */ ++#define RNDIS_STATUS_FAILURE 0xC0000001U /* Unspecified error */ ++#define RNDIS_STATUS_INVALID_DATA 0xC0010015U /* Invalid data */ ++#define RNDIS_STATUS_NOT_SUPPORTED 0xC00000BBU /* Unsupported request */ ++#define RNDIS_STATUS_MEDIA_CONNECT 0x4001000BU /* Device connected */ ++#define RNDIS_STATUS_MEDIA_DISCONNECT 0x4001000CU /* Device disconnected */ ++/* For all not specified status messages: ++ * RNDIS_STATUS_Xxx -> NDIS_STATUS_Xxx ++ */ ++ ++/* Message Set for Connectionless (802.3) Devices */ ++#define REMOTE_NDIS_INITIALIZE_MSG 0x00000002U /* Initialize device */ ++#define REMOTE_NDIS_HALT_MSG 0x00000003U ++#define REMOTE_NDIS_QUERY_MSG 0x00000004U ++#define REMOTE_NDIS_SET_MSG 0x00000005U ++#define REMOTE_NDIS_RESET_MSG 0x00000006U ++#define REMOTE_NDIS_INDICATE_STATUS_MSG 0x00000007U ++#define REMOTE_NDIS_KEEPALIVE_MSG 0x00000008U ++ ++/* Message completion */ ++#define REMOTE_NDIS_INITIALIZE_CMPLT 0x80000002U ++#define REMOTE_NDIS_QUERY_CMPLT 0x80000004U ++#define REMOTE_NDIS_SET_CMPLT 0x80000005U ++#define REMOTE_NDIS_RESET_CMPLT 0x80000006U ++#define REMOTE_NDIS_KEEPALIVE_CMPLT 0x80000008U ++ ++/* Device Flags */ ++#define RNDIS_DF_CONNECTIONLESS 0x00000001U ++#define RNDIS_DF_CONNECTION_ORIENTED 0x00000002U ++ ++#define RNDIS_MEDIUM_802_3 0x00000000U ++ ++/* from drivers/net/sk98lin/h/skgepnmi.h */ ++#define OID_PNP_CAPABILITIES 0xFD010100 ++#define OID_PNP_SET_POWER 0xFD010101 ++#define OID_PNP_QUERY_POWER 0xFD010102 ++#define OID_PNP_ADD_WAKE_UP_PATTERN 0xFD010103 ++#define OID_PNP_REMOVE_WAKE_UP_PATTERN 0xFD010104 ++#define OID_PNP_ENABLE_WAKE_UP 0xFD010106 ++ ++ ++/* supported OIDs */ ++static const u32 oid_supported_list [] = ++{ ++ /* the general stuff */ ++ OID_GEN_SUPPORTED_LIST, ++ OID_GEN_HARDWARE_STATUS, ++ OID_GEN_MEDIA_SUPPORTED, ++ OID_GEN_MEDIA_IN_USE, ++ OID_GEN_MAXIMUM_FRAME_SIZE, ++ OID_GEN_LINK_SPEED, ++ OID_GEN_TRANSMIT_BLOCK_SIZE, ++ OID_GEN_RECEIVE_BLOCK_SIZE, ++ OID_GEN_VENDOR_ID, ++ OID_GEN_VENDOR_DESCRIPTION, ++ OID_GEN_VENDOR_DRIVER_VERSION, ++ OID_GEN_CURRENT_PACKET_FILTER, ++ OID_GEN_MAXIMUM_TOTAL_SIZE, ++ OID_GEN_MEDIA_CONNECT_STATUS, ++ OID_GEN_PHYSICAL_MEDIUM, ++#if 0 ++ OID_GEN_RNDIS_CONFIG_PARAMETER, ++#endif ++ ++ /* the statistical stuff */ ++ OID_GEN_XMIT_OK, ++ OID_GEN_RCV_OK, ++ OID_GEN_XMIT_ERROR, ++ OID_GEN_RCV_ERROR, ++ OID_GEN_RCV_NO_BUFFER, ++#ifdef RNDIS_OPTIONAL_STATS ++ OID_GEN_DIRECTED_BYTES_XMIT, ++ OID_GEN_DIRECTED_FRAMES_XMIT, ++ OID_GEN_MULTICAST_BYTES_XMIT, ++ OID_GEN_MULTICAST_FRAMES_XMIT, ++ OID_GEN_BROADCAST_BYTES_XMIT, ++ OID_GEN_BROADCAST_FRAMES_XMIT, ++ OID_GEN_DIRECTED_BYTES_RCV, ++ OID_GEN_DIRECTED_FRAMES_RCV, ++ OID_GEN_MULTICAST_BYTES_RCV, ++ OID_GEN_MULTICAST_FRAMES_RCV, ++ OID_GEN_BROADCAST_BYTES_RCV, ++ OID_GEN_BROADCAST_FRAMES_RCV, ++ OID_GEN_RCV_CRC_ERROR, ++ OID_GEN_TRANSMIT_QUEUE_LENGTH, ++#endif /* RNDIS_OPTIONAL_STATS */ ++ ++ /* mandatory 802.3 */ ++ /* the general stuff */ ++ OID_802_3_PERMANENT_ADDRESS, ++ OID_802_3_CURRENT_ADDRESS, ++ OID_802_3_MULTICAST_LIST, ++ OID_802_3_MAC_OPTIONS, ++ OID_802_3_MAXIMUM_LIST_SIZE, ++ ++ /* the statistical stuff */ ++ OID_802_3_RCV_ERROR_ALIGNMENT, ++ OID_802_3_XMIT_ONE_COLLISION, ++ OID_802_3_XMIT_MORE_COLLISIONS, ++#ifdef RNDIS_OPTIONAL_STATS ++ OID_802_3_XMIT_DEFERRED, ++ OID_802_3_XMIT_MAX_COLLISIONS, ++ OID_802_3_RCV_OVERRUN, ++ OID_802_3_XMIT_UNDERRUN, ++ OID_802_3_XMIT_HEARTBEAT_FAILURE, ++ OID_802_3_XMIT_TIMES_CRS_LOST, ++ OID_802_3_XMIT_LATE_COLLISIONS, ++#endif /* RNDIS_OPTIONAL_STATS */ ++ ++#ifdef RNDIS_PM ++ /* PM and wakeup are mandatory for USB: */ ++ ++ /* power management */ ++ OID_PNP_CAPABILITIES, ++ OID_PNP_QUERY_POWER, ++ OID_PNP_SET_POWER, ++ ++ /* wake up host */ ++ OID_PNP_ENABLE_WAKE_UP, ++ OID_PNP_ADD_WAKE_UP_PATTERN, ++ OID_PNP_REMOVE_WAKE_UP_PATTERN, ++#endif ++}; ++ ++ ++typedef struct rndis_init_msg_type ++{ ++ u32 MessageType; ++ u32 MessageLength; ++ u32 RequestID; ++ u32 MajorVersion; ++ u32 MinorVersion; ++ u32 MaxTransferSize; ++} rndis_init_msg_type; ++ ++typedef struct rndis_init_cmplt_type ++{ ++ u32 MessageType; ++ u32 MessageLength; ++ u32 RequestID; ++ u32 Status; ++ u32 MajorVersion; ++ u32 MinorVersion; ++ u32 DeviceFlags; ++ u32 Medium; ++ u32 MaxPacketsPerTransfer; ++ u32 MaxTransferSize; ++ u32 PacketAlignmentFactor; ++ u32 AFListOffset; ++ u32 AFListSize; ++} rndis_init_cmplt_type; ++ ++typedef struct rndis_halt_msg_type ++{ ++ u32 MessageType; ++ u32 MessageLength; ++ u32 RequestID; ++} rndis_halt_msg_type; ++ ++typedef struct rndis_query_msg_type ++{ ++ u32 MessageType; ++ u32 MessageLength; ++ u32 RequestID; ++ u32 OID; ++ u32 InformationBufferLength; ++ u32 InformationBufferOffset; ++ u32 DeviceVcHandle; ++} rndis_query_msg_type; ++ ++typedef struct rndis_query_cmplt_type ++{ ++ u32 MessageType; ++ u32 MessageLength; ++ u32 RequestID; ++ u32 Status; ++ u32 InformationBufferLength; ++ u32 InformationBufferOffset; ++} rndis_query_cmplt_type; ++ ++typedef struct rndis_set_msg_type ++{ ++ u32 MessageType; ++ u32 MessageLength; ++ u32 RequestID; ++ u32 OID; ++ u32 InformationBufferLength; ++ u32 InformationBufferOffset; ++ u32 DeviceVcHandle; ++} rndis_set_msg_type; ++ ++typedef struct rndis_set_cmplt_type ++{ ++ u32 MessageType; ++ u32 MessageLength; ++ u32 RequestID; ++ u32 Status; ++} rndis_set_cmplt_type; ++ ++typedef struct rndis_reset_msg_type ++{ ++ u32 MessageType; ++ u32 MessageLength; ++ u32 Reserved; ++} rndis_reset_msg_type; ++ ++typedef struct rndis_reset_cmplt_type ++{ ++ u32 MessageType; ++ u32 MessageLength; ++ u32 Status; ++ u32 AddressingReset; ++} rndis_reset_cmplt_type; ++ ++typedef struct rndis_indicate_status_msg_type ++{ ++ u32 MessageType; ++ u32 MessageLength; ++ u32 Status; ++ u32 StatusBufferLength; ++ u32 StatusBufferOffset; ++} rndis_indicate_status_msg_type; ++ ++typedef struct rndis_keepalive_msg_type ++{ ++ u32 MessageType; ++ u32 MessageLength; ++ u32 RequestID; ++} rndis_keepalive_msg_type; ++ ++typedef struct rndis_keepalive_cmplt_type ++{ ++ u32 MessageType; ++ u32 MessageLength; ++ u32 RequestID; ++ u32 Status; ++} rndis_keepalive_cmplt_type; ++ ++struct rndis_packet_msg_type ++{ ++ u32 MessageType; ++ u32 MessageLength; ++ u32 DataOffset; ++ u32 DataLength; ++ u32 OOBDataOffset; ++ u32 OOBDataLength; ++ u32 NumOOBDataElements; ++ u32 PerPacketInfoOffset; ++ u32 PerPacketInfoLength; ++ u32 VcHandle; ++ u32 Reserved; ++}; ++ ++struct rndis_config_parameter ++{ ++ u32 ParameterNameOffset; ++ u32 ParameterNameLength; ++ u32 ParameterType; ++ u32 ParameterValueOffset; ++ u32 ParameterValueLength; ++}; ++ ++/* implementation specific */ ++enum rndis_state ++{ ++ RNDIS_UNINITIALIZED, ++ RNDIS_INITIALIZED, ++ RNDIS_DATA_INITIALIZED, ++}; ++ ++typedef struct rndis_resp_t ++{ ++ struct list_head list; ++ u8 *buf; ++ u32 length; ++ int send; ++} rndis_resp_t; ++ ++typedef struct rndis_params ++{ ++ u8 confignr; ++ int used; ++ enum rndis_state state; ++ u32 filter; ++ u32 medium; ++ u32 speed; ++ u32 media_state; ++ const u8 *host_mac; ++ struct net_device *dev; ++ struct net_device_stats *stats; ++ u32 vendorID; ++ const char *vendorDescr; ++ int (*ack) (struct net_device *); ++ struct list_head resp_queue; ++} rndis_params; ++ ++/* RNDIS Message parser and other useless functions */ ++int rndis_msg_parser (u8 configNr, u8 *buf); ++int rndis_register (int (*rndis_control_ack) (struct net_device *)); ++void rndis_deregister (int configNr); ++int rndis_set_param_dev (u8 configNr, struct net_device *dev, ++ struct net_device_stats *stats); ++int rndis_set_param_vendor (u8 configNr, u32 vendorID, ++ const char *vendorDescr); ++int rndis_set_param_medium (u8 configNr, u32 medium, u32 speed); ++void rndis_add_hdr (struct sk_buff *skb); ++int rndis_rm_hdr (u8 *buf, u32 *length); ++u8 *rndis_get_next_response (int configNr, u32 *length); ++void rndis_free_response (int configNr, u8 *buf); ++ ++int rndis_signal_connect (int configNr); ++int rndis_signal_disconnect (int configNr); ++int rndis_state (int configNr); ++extern void rndis_set_host_mac (int configNr, const u8 *addr); ++ ++int __init rndis_init (void); ++void rndis_exit (void); ++ ++#endif /* _LINUX_RNDIS_H */ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/superh_udc.c kernel/drivers/usb/gadget/superh_udc.c +--- /tmp/kernel/drivers/usb/gadget/superh_udc.c 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/superh_udc.c 2005-04-22 17:53:19.510527142 +0200 +@@ -0,0 +1,1819 @@ ++/* ++ * Renesas SuperH USB 1.1 device controller (found on SH7705, SH7727...) ++ * ++ * Copyright (C) 2003 Renesas Technology Europe Limited ++ * Copyright (C) 2003 Julian Back (jback@mpc-data.co.uk), MPC Data Limited ++ * ++ * 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 ++ */ ++ ++/* ++ * This is a driver for the USB Device Controller found on Renesas SH ++ * processors. This is a full-speed controller which has four ++ * endpoints in a single fixed configuration. ++ * ++ * Limitations ++ * ++ * Only tested on SH7705. Mostly tested with Mass Storage gadget ++ * using Bulk-Only Transport. It has been tested with Linux 2.4, ++ * Linux 2.6, Windows 2000 and Windows XP hosts. ++ * ++ * DMA is not (yet) implemented. ++ * ++ * Handling of application stalls is tricky. We set a bit to stall an ++ * endpoint. When the host tries to access the ep it gets a stall and ++ * another stall bit is latched by the device. The host clears the ++ * stall with a clear feature but the hardware doesn't inform us, the ++ * latched bit is cleared but not the bit we have set, so the next ++ * time the host accesses the ep it will get another stall and the ++ * latch will be set again unless we have cleared our stall bit. The ++ * solution adopted in this driver is to use a timer to clear the ++ * application stall bit some time after setting the stall. This ++ * seems to work most of the time but is not 100% reliable. Because ++ * of this it is best to avoid USB protocols that require the USB ++ * device to stall the host. Unfortunately USB mass storage does ++ * require the device to stall when it gets unsupported commands, ++ * Linux hosts don't send any of these unsupported commands but ++ * Windows hosts do. ++ * ++ * Another place where the hardware is too clever is in the handling ++ * of setup packets. Many setup packets including SET_INTERFACE and ++ * SET_CONFIGURATION are handled by the hardware without informing the ++ * driver software. But we need to inform the gadget driver of at ++ * least one of these as it uses this to kick of it's data processing. ++ * The solution adopted is that after we have recieved N setup packets ++ * following a bus reset a fake SET_CONFIGURATION is sent to the ++ * gadget. We also have to arrange things so that the reply to the ++ * fake packet is not sent out. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#undef DEBUG ++#undef VERY_NOISY ++ ++#define DRIVER_DESC "SuperH USB Peripheral Controller" ++#define DRIVER_VERSION "alpha (11 November 2003)" ++ ++#ifdef USE_DMA ++#error "DMA not supported" ++#endif ++ ++static const char driver_name [] = "superh_udc"; ++static const char driver_desc [] = DRIVER_DESC; ++ ++static const char ep0name [] = "ep0"; ++static const char *ep_name [] = { ++ ep0name, ++ "ep1out-bulk", ++ "ep2in-bulk", ++ "ep3in-bulk", ++}; ++ ++static struct superh_udc *the_controller; ++ ++#include "superh_udc.h" ++ ++/* High priority interrupts */ ++#define F0_HIGH (EP1_FULL | EP2_TR | EP2_EMPTY ) ++#define F1_HIGH (0) ++ ++/* Low priority interrupts */ ++#define F0_LOW (BRST | SETUP_TS | EP0o_TS | EP0i_TR | EP0i_TS) ++#define F1_LOW (EP3_TR | EP3_TS | VBUSF) ++ ++/* How long to leave the stall bit set - this value is quite critical ++ * to making stalls work. Unfortunately it doesn't seem possible to ++ * get a value that will work reliably with both fast and slow ++ * machines. ++ */ ++#define STALL_TIME (HZ/75) ++ ++/* Number of endpoints to check in the unstall timer. It should not ++ * be necessary to unstall bulk endpoints using the timer as long as ++ * the gadget code is aware that this device cannot stall properly ++ * (see the file backed storage gadget for an example). But if the ++ * UDC driver stalls ep0 due to a bad SETUP then the timer is still ++ * required otherwise the stall will never get cleared. If it is ++ * necessary to unstall all endpoints using the timer then set this to ++ * 4. ++ */ ++#define EP_TO_UNSTALL 1 ++ ++/* Number of packets to wait for before sending a fake ++ * SET_CONFIGURATION to the gadget driver ++ */ ++#define DEFAULT_SETUP_COUNT 7 ++#define RESET_SETUP_COUNT 2 ++ ++/* How long to wait for the number of packets specified above */ ++#define SETUP_TIME (HZ/10 ) ++ ++static void superh_ep_fifo_flush(struct usb_ep *_ep); ++static void stop_activity(struct superh_udc *dev, struct usb_gadget_driver *driver); ++static int superh_ep_set_halt(struct usb_ep *_ep, int value); ++static void udc_timer(unsigned long _dev); ++static struct superh_request* process_ep_req(struct superh_ep *ep, ++ struct superh_request *req); ++static void done(struct superh_ep *ep, struct superh_request *req, int status); ++ ++/* ++ * IO ++ */ ++ ++static inline void and_b(u8 mask, unsigned long addr) ++{ ++ ctrl_outb(ctrl_inb(addr) & mask, addr); ++} ++ ++ ++static inline void or_b(u8 mask, unsigned long addr) ++{ ++ ctrl_outb(ctrl_inb(addr) | mask, addr); ++} ++ ++ ++static inline void ep0_idle (struct superh_udc *dev) ++{ ++ DBG(DBG_VERY_NOISY, "ep0_idle\n"); ++ dev->ep0state = EP0_IDLE; ++} ++ ++ ++static void init_udc_timer(struct superh_udc *dev) ++{ ++ init_timer(&dev->timer); ++ dev->timer.function = udc_timer; ++ dev->timer.data = (unsigned long) dev; ++ dev->timer.expires = jiffies + STALL_TIME; ++ add_timer(&dev->timer); ++} ++ ++/* Send a fake SET_CONFIGURATION to the gadget to start it up. ++ * Needed because the hardware doesn't let us know when the real packet ++ * has arrived. ++ */ ++static void send_fake_config(struct superh_udc *dev) ++{ ++ struct usb_ctrlrequest r; ++ dev->fake_config = 1; ++ dev->setup_countdown = 0; ++ r.bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD ++ | USB_RECIP_DEVICE; ++ r.bRequest = USB_REQ_SET_CONFIGURATION; ++ r.wValue = 1; /* configuration to select */ ++ r.wIndex = 0; ++ r.wLength = 0; ++ if (dev->driver->setup(&dev->gadget, &r) < 0) { ++ DMSG("SET_CONFIGURATION failed.\n"); ++ } ++} ++ ++/* ++ * Timer function. Clears stall from any stalled endpoints as we ++ * don't get informed when the host has sent a clear feature. ++ */ ++static void udc_timer(unsigned long _dev) ++{ ++ struct superh_udc *dev = (void *)_dev; ++ int i; ++ unsigned long flags; ++ ++ local_irq_save(flags); ++ ++ if (atomic_read(&dev->in_interrupt) == 0) { ++ ++ /* Check if a bus reset has been done and we haven't faked a SET_CONFIGURATION */ ++ if (dev->gadget.speed != USB_SPEED_UNKNOWN ++ && dev->setup_countdown > 0 ++ && jiffies - dev->reset_time > SETUP_TIME ++ &&list_empty(&dev->ep[0].queue)) { ++ send_fake_config(dev); ++ } ++ ++ /* Check if any end points are halted and restart them */ ++ for (i = 0; i < EP_TO_UNSTALL; i++) { ++ struct superh_ep *ep = &dev->ep[i]; ++ if (ep->halted) { ++ DBG(DBG_VERBOSE, "unstalling ep %d\n", i); ++ superh_ep_set_halt(&ep->ep, 0); ++ if (likely (!list_empty(&ep->queue))) { ++ struct superh_request *req ++ = list_entry(ep->queue.next, ++ struct superh_request, queue); ++ process_ep_req(ep, req); ++ } ++ } ++ } ++ } ++ ++ init_udc_timer(dev); ++ ++ local_irq_restore(flags); ++} ++ ++/* ++ * done - retire a request; caller blocked irqs ++ */ ++static void done(struct superh_ep *ep, struct superh_request *req, int status) ++{ ++ unsigned stopped = ep->stopped; ++ ++ DBG(DBG_NOISY, "done: %s %p %d\n", ep->ep.name, req, status); ++ ++ list_del_init(&req->queue); ++ ++ if (likely (req->req.status == -EINPROGRESS)) ++ req->req.status = status; ++ else ++ status = req->req.status; ++ ++ if (status && status != -ESHUTDOWN) ++ DBG(DBG_VERBOSE, "complete %s req %p stat %d len %u/%u\n", ++ ep->ep.name, &req->req, status, ++ req->req.actual, req->req.length); ++ ++ /* don't modify queue heads during completion callback */ ++ ep->stopped = 1; ++ req->req.complete(&ep->ep, &req->req); ++ ep->stopped = stopped; ++} ++ ++/* ++ * Enable interrupts for the specified endpoint ++ */ ++static inline void pio_irq_enable(struct superh_ep *ep) ++{ ++ or_b(ep->interrupt_mask, ep->interrupt_reg); ++} ++ ++/* ++ * Disable interrupts for the specified endpoint ++ */ ++static inline void pio_irq_disable(struct superh_ep *ep) ++{ ++ and_b(~ep->interrupt_mask, ep->interrupt_reg); ++} ++ ++/* ++ * nuke - dequeue ALL requests ++ */ ++static void nuke(struct superh_ep *ep, int status) ++{ ++ struct superh_request *req; ++ ++ DBG(DBG_NOISY, "nuke %s %d\n", ep->ep.name, status); ++ ++ /* called with irqs blocked */ ++#ifdef USE_DMA ++ if (ep->dma >= 0 && !ep->stopped) ++ cancel_dma(ep); ++#endif ++ while (!list_empty(&ep->queue)) { ++ req = list_entry(ep->queue.next, ++ struct superh_request, ++ queue); ++ done(ep, req, status); ++ } ++ ++ if (ep->desc) ++ pio_irq_disable (ep); ++} ++ ++static inline void clear_ep_state (struct superh_udc *dev) ++{ ++ unsigned i; ++ ++ /* hardware SET_{CONFIGURATION,INTERFACE} automagic resets endpoint ++ * fifos, and pending transactions mustn't be continued in any case. ++ */ ++ for (i = 1; i < 4; i++) ++ nuke(&dev->ep[i], -ECONNABORTED); ++} ++ ++/* ++ * write a packet to an endpoint data register ++ */ ++static int ++write_packet(u32 epdr, struct superh_request *req, unsigned max) ++{ ++ u8 *buf; ++ unsigned length, count; ++ ++ buf = req->req.buf + req->req.actual; ++ prefetch(buf); ++ ++ /* how big will this packet be? */ ++ length = min(req->req.length - req->req.actual, max); ++ req->req.actual += length; ++ ++ count = length; ++ while (likely(count--)) ++ ctrl_outb(*buf++, epdr); ++ ++ return length; ++} ++ ++static int ++write_ep0_fifo (struct superh_ep *ep, struct superh_request *req) ++{ ++ unsigned count; ++ int is_short; ++ ++ count = write_packet(USBEPDR0I, req, EP0_FIFO_SIZE); ++ ep->dev->stats.write.bytes += count; ++ ++ /* last packet "must be" short (or a zlp) */ ++ is_short = (count != EP0_FIFO_SIZE); ++ ++ DBG(DBG_VERY_NOISY, "ep0in %d bytes %d left %p\n", count, ++ req->req.length - req->req.actual, req); ++ ++ ctrl_outb(EP0i_PKTE, USBTRG); ++ ++ if (unlikely (is_short)) { ++ ep->dev->ep0state = EP0_END_XFER; ++ ++ count = req->req.length; ++ done (ep, req, 0); ++ /* ++ * If we have received a specified number of setups ++ * after a bus reset or connect then fake a ++ * SET_CONFIGURATION to the driver (as we don't get ++ * them from the hardware). ++ */ ++ if (ep->dev->setup_countdown >= 0) ++ ep->dev->setup_countdown--; ++ if (ep->dev->setup_countdown == 0) { ++ send_fake_config(ep->dev); ++ } ++ } ++ ++ return is_short; ++} ++ ++/* ++ * handle_ep0_setup ++ * ++ * Handles a SETUP request on EP0 ++ */ ++static void handle_ep0_setup(struct superh_udc* dev) ++{ ++ int i; ++ union { u8 raw [8]; struct usb_ctrlrequest r; } u; ++ ++ for (i = 0; i < 8; i++) { ++ u.raw[i] = ctrl_inb(USBEPDR0S); ++ } ++ ++ /* Send ACK */ ++ ctrl_outb(EP0s_RDFN, USBTRG); ++ ++ le16_to_cpus (&u.r.wValue); ++ le16_to_cpus (&u.r.wIndex); ++ le16_to_cpus (&u.r.wLength); ++ ++ DBG(DBG_VERBOSE, "SETUP %02x.%02x v%04x i%04x l%04x\n", ++ u.r.bRequestType, u.r.bRequest, ++ u.r.wValue, u.r.wIndex, u.r.wLength); ++ ++ if (u.r.bRequestType & USB_DIR_IN) { ++ DBG(DBG_VERY_NOISY, "handle_ep0_setup: EP0_IN_DATA_PHASE\n"); ++ dev->ep0state = EP0_IN_DATA_PHASE; ++ } ++ else { ++ DBG(DBG_VERY_NOISY, "handle_ep0_setup: EP0_OUT_DATA_PHASE\n"); ++ dev->ep0state = EP0_OUT_DATA_PHASE; ++ } ++ ++ i = dev->driver->setup(&dev->gadget, &u.r); ++ if (i < 0) { ++ DMSG("SETUP %02x.%02x v%04x i%04x l%04x failed\n", ++ u.r.bRequestType, u.r.bRequest, ++ u.r.wValue, u.r.wIndex, u.r.wLength); ++ superh_ep_set_halt(&dev->ep[0].ep, 1); ++ } ++} ++ ++/* ++ * write to an IN endpoint fifo, as many packets as possible. ++ * irqs will use this to write the rest later. ++ * caller guarantees at least one packet buffer is ready. ++ */ ++static int ++write_fifo (struct superh_ep *ep, struct superh_request *req) ++{ ++ unsigned max; ++ ++ DBG(DBG_VERY_NOISY, "write_fifo\n"); ++ ++ if ((ep->bEndpointAddress & USB_DIR_IN) != USB_DIR_IN) { ++ DMSG("write_fifo from invalid EP (%s)\n", ep->ep.name); ++ return -EINVAL; ++ } ++ ++ max = ep->desc->wMaxPacketSize; ++ do { ++ unsigned count; ++ int is_last, is_short; ++ ++ count = write_packet(ep->fifo_reg, req, max); ++ ++ /* last packet is usually short (or a zlp) */ ++ if (unlikely (count != max)) ++ is_last = is_short = 1; ++ else { ++ if (likely(req->req.length != req->req.actual) ++ || req->req.zero) ++ is_last = 0; ++ else ++ is_last = 1; ++ /* interrupt/iso maxpacket may not fill the fifo */ ++ is_short = unlikely (max < ep->ep.maxpacket); ++ ++ /* FIXME ep.maxpacket should be the current size, ++ * modified (for periodic endpoints) when the ++ * ep is enabled. do that, re-init as needed, ++ * and change maxpacket refs accordingly. ++ */ ++ } ++ ++ DBG(DBG_VERY_NOISY, "wrote %s %d bytes%s%s %d left %p\n", ++ ep->ep.name, count, ++ is_last ? "/L" : "", is_short ? "/S" : "", ++ req->req.length - req->req.actual, req); ++ ++ /* let loose that packet. maybe try writing another one, ++ * double buffering might work. ++ */ ++ or_b(ep->packet_enable_mask, USBTRG); ++ ++ /* requests complete when all IN data is in the FIFO */ ++ if (is_last) { ++ done (ep, req, 0); ++ if (list_empty(&ep->queue) || unlikely(ep->dma >= 0)) { ++ pio_irq_disable (ep); ++ } ++#ifdef USE_DMA ++ /* TODO */ ++ if (unlikely(ep->dma >= 0) && !list_empty(&ep->queue)) { ++ DMSG("%s pio2dma\n", ep->ep.name); ++ req = list_entry(ep->queue.next, ++ struct superh_request, queue); ++ kick_dma(ep,req); ++ return 0; ++ } ++#endif ++ return 1; ++ } ++ /* Only loop if on EP2 as it is double buffered */ ++ } while (ep->bEndpointAddress == (2|USB_DIR_IN) ++ && ctrl_inb(USBIFR0) & EP2_EMPTY); ++ return 0; ++} ++ ++/* ++ * read_ep0_fifo - unload packets from ep0 control-out fifo. caller ++ * should have made sure there's at least one packet ready. ++ * ++ * returns true if the request completed because of short packet or the ++ * request buffer having filled (and maybe overran till end-of-packet). ++ */ ++static int ++read_ep0_fifo(struct superh_ep *ep, struct superh_request *req) ++{ ++ u8 *buf; ++ unsigned bufferspace, count; ++ ++ DBG(DBG_VERY_NOISY, "read_ep0_fifo\n"); ++ ++ if (!ep) { ++ DMSG("read_ep0_fifo invalid ep\n"); ++ return -EINVAL; ++ } ++ ++ if (!req) { ++ DMSG("read_ep0_fifo invalid req\n"); ++ return -EINVAL; ++ } ++ ++ if (ep->desc != 0) { ++ DMSG("read_ep0_fifo from invalid EP (%s)\n", ep->ep.name); ++ return -EINVAL; ++ } ++ ++ /* make sure there's a packet in the FIFO. ++ */ ++ if (likely ((ctrl_inb(USBIFR0) & EP0o_TS) == 0)) { ++ buf = req->req.buf + req->req.actual; ++ bufferspace = req->req.length - req->req.actual; ++ ++ /* read all bytes from this packet */ ++ count = ctrl_inb(USBEPSZ0O); ++ req->req.actual += min (count, bufferspace); ++ DBG(DBG_VERY_NOISY, "read %s %d bytes req %p %d/%d\n", ++ ep->ep.name, count, ++ req, req->req.actual, req->req.length); ++ while (likely (count-- != 0)) { ++ u8 byte = ctrl_inb(USBEPDR0O); ++ ++ if (unlikely (bufferspace == 0)) { ++ /* this happens when the driver's buffer ++ * is smaller than what the host sent. ++ * discard the extra data. ++ */ ++ if (req->req.status != -EOVERFLOW) ++ DMSG("%s overflow %d\n", ++ ep->ep.name, count); ++ req->req.status = -EOVERFLOW; ++ } else { ++ *buf++ = byte; ++ bufferspace--; ++ } ++ } ++ ++ /* Send ACK */ ++ or_b(EP0o_RDFN, USBTRG); ++ ++ /* completion */ ++ if (req->req.actual >= req->req.length) { ++ done (ep, req, 0); ++ ep0_idle(ep->dev); ++ return 1; ++ } ++ } ++ ++ return 0; ++} ++ ++/* ++ * read_fifo - unload packet(s) from the fifo we use for usb OUT ++ * transfers and put them into the request. caller should have made ++ * sure there's at least one packet ready. ++ * ++ * returns true if the request completed because of short packet or the ++ * request buffer having filled (and maybe overran till end-of-packet). ++ */ ++static int ++read_fifo (struct superh_ep *ep, struct superh_request *req) ++{ ++ DBG(DBG_VERY_NOISY, "read_fifo\n"); ++ ++ if ((ep->bEndpointAddress & 0x0f) != 1) { ++ DMSG("read_fifo from invalid EP (%s)\n", ep->ep.name); ++ return -EINVAL; ++ } ++ ++ for (;;) { ++ u8 *buf; ++ unsigned bufferspace, count, is_short; ++ ++ /* make sure there's a packet in the FIFO. ++ */ ++ if (unlikely ((ctrl_inb(USBIFR0) & EP1_FULL) == 0)) ++ break; ++ buf = req->req.buf + req->req.actual; ++ bufferspace = req->req.length - req->req.actual; ++ ++ /* read all bytes from this packet */ ++ count = ctrl_inb(USBEPSZ1); ++ req->req.actual += min (count, bufferspace); ++ is_short = (count < ep->desc->wMaxPacketSize); ++ DBG(DBG_VERY_NOISY, "read %s %d bytes%s req %p %d/%d\n", ++ ep->ep.name, count, ++ is_short ? "/S" : "", ++ req, req->req.actual, req->req.length); ++ while (likely (count-- != 0)) { ++ u8 byte = ctrl_inb(USBEPDR1); ++ ++ if (unlikely (bufferspace == 0)) { ++ /* this happens when the driver's buffer ++ * is smaller than what the host sent. ++ * discard the extra data. ++ */ ++ if (req->req.status != -EOVERFLOW) ++ DMSG("%s overflow %d\n", ++ ep->ep.name, count); ++ req->req.status = -EOVERFLOW; ++ } else { ++ *buf++ = byte; ++ bufferspace--; ++ } ++ } ++ ++ or_b(EP1_RDFN, USBTRG); ++ /* There could now be another packet because of dual buffer */ ++ ++ /* completion */ ++ if (is_short || req->req.actual == req->req.length) { ++ done (ep, req, 0); ++ if (list_empty(&ep->queue)) ++ pio_irq_disable (ep); ++ return 1; ++ } ++ ++ /* finished that packet. the next one may be waiting... */ ++ } ++ return 0; ++} ++ ++/*--------------------------------------------------------------------------*/ ++/* Interrupt Handler(s) ++ */ ++ ++/* ++ * superh_udc_irq_f0 - high priority interrupt handler ++ * this deals with data to & from the bulk pipes ++ */ ++static void superh_udc_irq_f0(int irq, void *_dev, struct pt_regs *regs) ++{ ++ unsigned char f0_status; ++ struct superh_udc *dev = (struct superh_udc*) _dev; ++ struct superh_request *req; ++ struct superh_ep *ep; ++ ++ DBG(DBG_NOISY, "superh_udc_irq_f0 %p\n", dev); ++ ++ atomic_inc(&dev->in_interrupt); ++ ++ dev->stats.irqs++; ++ dev->stats.irq0s++; ++ f0_status = ctrl_inb(USBIFR0); ++ ++ /* Acknowledge interrupts */ ++ ctrl_outb(~(f0_status & F0_HIGH), USBIFR0); ++ ++ if (f0_status & EP1_FULL) { ++ DBG(DBG_NOISY, "superh_udc_irq_f0 %p: EP1 FULL\n", dev); ++ ep = &dev->ep[1]; ++ ++ if (likely (!list_empty(&ep->queue))) ++ req = list_entry(ep->queue.next, ++ struct superh_request, queue); ++ else ++ req = 0; ++ ++ if (req) ++ read_fifo(ep, req); ++ else ++ pio_irq_disable(ep); ++ } ++ ++ if ( f0_status & (EP2_TR | EP2_EMPTY) ) { ++ DBG(DBG_NOISY, "superh_udc_irq_f0 %p: EP2 TR | EP2_EMPTY\n", dev); ++ ep = &dev->ep[2]; ++ ++ if (likely (!list_empty(&ep->queue))) ++ req = list_entry(ep->queue.next, ++ struct superh_request, queue); ++ else ++ req = 0; ++ ++ if (req) { ++ if ((f0_status & EP2_TR) && (f0_status & EP2_EMPTY)) ++ write_fifo(ep, req); ++ else ++ and_b(~EP2_EMPTY, USBIER0); ++ ++ } ++ else { ++ pio_irq_disable(ep); ++ } ++ } ++ ++ atomic_dec(&dev->in_interrupt); ++} ++ ++/** ++ * superh_udc_irq_f1 - low priority interrupt handler ++ * ++ */ ++static void superh_udc_irq_f1(int irq, void *_dev, struct pt_regs *regs) ++{ ++ unsigned char f0_status; ++ unsigned char f1_status; ++ struct superh_udc *dev = (struct superh_udc*) _dev; ++ ++ atomic_inc(&dev->in_interrupt);; ++ ++ dev->stats.irqs++; ++ dev->stats.irq1s++; ++ ++ f0_status = ctrl_inb(USBIFR0); ++ f1_status = ctrl_inb(USBIFR1); ++ ++ /* Acknowledge interrupts */ ++ ctrl_outb(~(f0_status & F0_LOW), USBIFR0); ++ ctrl_outb(~(f1_status & F1_LOW), USBIFR1); ++ ++ /* VBUSF indicates the USB being connected/disconnected */ ++ if (f1_status & VBUSF) { ++ DBG(DBG_VERY_NOISY, "superh_udc_irq_f1[%lx] VBUSF\n", dev->stats.irqs); ++ if (!is_usb_connected) { ++ /* report disconnect just once */ ++ if (dev->gadget.speed != USB_SPEED_UNKNOWN) { ++ DMSG("disconnect %s\n", ++ dev->driver ? dev->driver->driver.name : 0); ++ stop_activity(dev, dev->driver); ++ } ++ } ++ else if (dev->gadget.speed == USB_SPEED_UNKNOWN) { ++ DMSG("connect\n"); ++ dev->setup_countdown = DEFAULT_SETUP_COUNT; ++ } ++ } ++ ++ ++ ++ /* Bus Reset */ ++ if (f0_status & BRST) { ++ int i; ++ DBG(DBG_VERBOSE, "superh_udc_irq_f1[%lx]: BRST bus reset\n", dev->stats.irqs); ++ /* kill any outstanding requests */ ++ for (i = 0; i < 4; i++) { ++ struct superh_ep *ep = &dev->ep[i]; ++ nuke(ep, -ESHUTDOWN); ++ ep->halted = 0; ++ ep->stopped = 0; ++ } ++ ++ /* reset fifo's and stall's */ ++ ctrl_outb( EP3_CLEAR | EP1_CLEAR | EP2_CLEAR | EP0o_CLEAR | EP0i_CLEAR, USBFCLR ); ++ ctrl_outb( 0, USBEPSTL ); ++ DMSG("gadget driver '%s', address zero\n", dev->driver->driver.name); ++ if (dev->gadget.speed == USB_SPEED_UNKNOWN) ++ init_udc_timer(dev); ++ dev->gadget.speed = USB_SPEED_FULL; ++ memset(&dev->stats, 0, sizeof dev->stats); ++ if (dev->setup_countdown < 0) ++ dev->setup_countdown = RESET_SETUP_COUNT; ++ dev->reset_time = jiffies; ++ dev->fake_config = 0; ++ ep0_idle(dev); ++ } ++ ++ /* EPOi Transmit Complete - data to host on EP0 ACKed ++ * EP0i Transfer Request - no data in FIFO to send on EP0 ++ * either way we send next data if there is any and the FIFO is not busy ++ * it will interrupt again if we later if we don't send anything. ++ */ ++ if ((f0_status & EP0i_TR || f0_status & EP0i_TS) ++ && (ctrl_inb(USBDASTS) & EP0i_DE) == 0) { ++ struct superh_ep *ep = &dev->ep[0]; ++ struct superh_request *req; ++ DBG(DBG_VERY_NOISY, "superh_udc_irq_f1[%lx]: ep0i TR\n", dev->stats.irqs); ++ if (!list_empty(&ep->queue)) { ++ req = list_entry(ep->queue.next, struct superh_request, queue); ++ write_ep0_fifo(ep, req); ++ } ++ or_b(EP0i_PKTE, USBTRG); ++ } ++ ++ /* Setup Command Receive Complete */ ++ if (f0_status & SETUP_TS) { ++ DBG(DBG_NOISY, "superh_udc_irq_f1[%lx]: SETUP TS\n", dev->stats.irqs); ++ or_b( EP0o_CLEAR | EP0i_CLEAR, USBFCLR); ++ handle_ep0_setup(dev); ++ } ++ ++ /* EPOo Receive Complete - EP0 has received data from host */ ++ if (f0_status & EP0o_TS) { ++ struct superh_request *req; ++ struct superh_ep *ep; ++ DBG(DBG_VERY_NOISY, "superh_int_hndlr_f1[%lx]: ep0o TS\n", dev->stats.irqs); ++ ep = &dev->ep[0]; ++ ++ if (likely (!list_empty(&ep->queue))) ++ req = list_entry(ep->queue.next, ++ struct superh_request, queue); ++ else ++ req = 0; ++ ++ if (req) ++ read_ep0_fifo(ep, req); ++ } ++ ++ /* EP3 Transmit Request & Transmit Complete */ ++ if ( f1_status & (EP3_TR | EP3_TS) ) { ++ struct superh_request *req; ++ struct superh_ep *ep; ++ DBG(DBG_VERY_NOISY, "superh_udc_irq_f1[%lx]: EP3 TR | EP3_TS (%x)\n", dev->stats.irqs, f1_status); ++ ep = &dev->ep[3]; ++ ++ if (likely (!list_empty(&ep->queue))) ++ req = list_entry(ep->queue.next, ++ struct superh_request, queue); ++ else ++ req = 0; ++ ++ if (req) { ++ if ((f1_status & EP3_TR) && (ctrl_inb(USBDASTS) & EP3_DE) == 0) ++ write_fifo(ep, req); ++ ++ } ++ else { ++ pio_irq_disable(ep); ++ } ++ } ++ ++ atomic_dec(&dev->in_interrupt);; ++} ++ ++ ++/*--------------------------------------------------------------------------*/ ++ ++/* ++ * endpoint enable/disable ++ * ++ * we need to verify the descriptors used to enable endpoints. since superh ++ * endpoint configurations are fixed, and are pretty much always enabled, ++ * there's not a lot to manage here. ++ * ++ */ ++static int superh_ep_enable (struct usb_ep *_ep, ++ const struct usb_endpoint_descriptor *desc) ++{ ++ struct superh_ep *ep; ++ struct superh_udc *dev; ++ ++ DBG(DBG_NOISY, "superh_ep_enable\n"); ++ ++ ep = container_of (_ep, struct superh_ep, ep); ++ if (!_ep || !desc || ep->desc || _ep->name == ep0name ++ || desc->bDescriptorType != USB_DT_ENDPOINT ++ || ep->bEndpointAddress != desc->bEndpointAddress ++ || ep->ep.maxpacket < desc->wMaxPacketSize) { ++ DMSG("%s, bad ep or descriptor\n", __FUNCTION__); ++ return -EINVAL; ++ } ++ ++ /* xfer types must match, except that interrupt ~= bulk */ ++ if (ep->bmAttributes != desc->bmAttributes ++ && ep->bmAttributes != USB_ENDPOINT_XFER_BULK ++ && desc->bmAttributes != USB_ENDPOINT_XFER_INT) { ++ DMSG("%s, %s type mismatch\n", __FUNCTION__, _ep->name); ++ return -EINVAL; ++ } ++ ++#if 0 ++ /* hardware _could_ do smaller, but driver doesn't */ ++ if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK ++ && desc->wMaxPacketSize != BULK_FIFO_SIZE) ++ || !desc->wMaxPacketSize) { ++ DMSG("%s, bad %s maxpacket\n", __FUNCTION__, _ep->name); ++ return -ERANGE; ++ } ++#endif ++ ++ dev = ep->dev; ++ if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { ++ DMSG("%s, bogus device state\n", __FUNCTION__); ++ return -ESHUTDOWN; ++ } ++ ++ ep->desc = desc; ++ ep->dma = -1; ++ ep->stopped = 0; ++ ++ /* flush fifo (mostly for OUT buffers), enable irq */ ++ superh_ep_fifo_flush (_ep); ++ ++ /* ... reset halt state too, if we could ... */ ++ ++#ifdef USE_DMA ++ ++#endif ++ ++ DBG(DBG_VERBOSE, "enabled %s\n", _ep->name); ++ return 0; ++} ++ ++static int superh_ep_disable (struct usb_ep *_ep) ++{ ++ struct superh_ep *ep; ++ ++ DBG(DBG_NOISY, "superh_ep_disable\n"); ++ ++ ep = container_of (_ep, struct superh_ep, ep); ++ if (!_ep || !ep->desc) { ++ DMSG("%s, %s not enabled\n", __FUNCTION__, ++ _ep ? ep->ep.name : NULL); ++ return -EINVAL; ++ } ++ nuke (ep, -ESHUTDOWN); ++ ++#ifdef USE_DMA ++ /* TODO */ ++ if (ep->dma >= 0) { ++ *ep->reg_drcmr = 0; ++ pxa_free_dma (ep->dma); ++ ep->dma = -1; ++ } ++#endif ++ ++ /* flush fifo (mostly for IN buffers) */ ++ superh_ep_fifo_flush (_ep); ++ ++ ep->desc = 0; ++ ep->stopped = 1; ++ ++ DBG(DBG_VERBOSE, "%s disabled\n", _ep->name); ++ return 0; ++} ++ ++/* for the superh, these can just wrap kmalloc/kfree. gadget drivers ++ * must still pass correctly initialized endpoints, since other controller ++ * drivers may care about how it's currently set up (dma issues etc). ++ */ ++ ++/* ++ * superh_ep_alloc_request - allocate a request data structure ++ */ ++static struct usb_request * ++superh_ep_alloc_request (struct usb_ep *_ep, int gfp_flags) ++{ ++ struct superh_request *req; ++ ++ /* FIXME for bulk out-dma endpoints, preallocate a frame's worth of ++ * (aligned) dma descriptors at the end of the request ++ */ ++ ++ req = kmalloc (sizeof *req, gfp_flags); ++ if (!req) ++ return 0; ++ ++ memset (req, 0, sizeof *req); ++ INIT_LIST_HEAD (&req->queue); ++ DBG(DBG_VERY_NOISY, "superh_ep_alloc_request: %p %d\n", req, list_empty(&req->queue)); ++ ++ return &req->req; ++} ++ ++/* ++ * superh_ep_free_request - deallocate a request data structure ++ */ ++static void ++superh_ep_free_request (struct usb_ep *_ep, struct usb_request *_req) ++{ ++ struct superh_request *req; ++ ++ req = container_of (_req, struct superh_request, req); ++ WARN_ON (!list_empty (&req->queue)); ++ kfree(req); ++} ++ ++/* SH cache needs flushing with DMA I/O (it's dma-incoherent), but there's ++ * no device-affinity and the heap works perfectly well for i/o buffers. ++ * TODO: check this ++ */ ++static void * ++superh_ep_alloc_buffer(struct usb_ep *_ep, unsigned bytes, ++ dma_addr_t *dma, int gfp_flags) ++{ ++ char *retval; ++ ++ retval = kmalloc (bytes, gfp_flags); ++ if (retval) ++ *dma = virt_to_bus (retval); ++ return retval; ++} ++ ++static void ++superh_ep_free_buffer(struct usb_ep *_ep, void *buf, dma_addr_t dma, ++ unsigned bytes) ++{ ++ kfree (buf); ++} ++ ++static struct superh_request* ++process_ep_req(struct superh_ep *ep, struct superh_request *req) ++{ ++ struct superh_udc *dev = ep->dev; ++ ++ if (ep->desc == 0 /* ep0 */) { ++ switch (dev->ep0state) { ++ case EP0_IN_DATA_PHASE: ++ DBG(DBG_VERY_NOISY, "superh_ep_queue: EP0_IN_DATA_PHASE\n"); ++ dev->stats.write.ops++; ++ if (write_ep0_fifo(ep, req)) ++ req = 0; ++ break; ++ ++ case EP0_OUT_DATA_PHASE: ++ DBG(DBG_VERY_NOISY, "superh_ep_queue: EP0_OUT_DATA_PHASE\n"); ++ dev->stats.read.ops++; ++ if (read_ep0_fifo(ep, req)) ++ req = 0; ++ break; ++ ++ default: ++ DMSG("ep0 i/o, odd state %d\n", dev->ep0state); ++ return 0; ++ } ++#ifdef USE_DMA ++ /* either start dma or prime pio pump */ ++ } ++ else if (ep->dma >= 0) { ++ kick_dma(ep, req); ++#endif ++ /* can the FIFO can satisfy the request immediately? */ ++ } ++ else if ((ep->bEndpointAddress & USB_DIR_IN) != 0) { ++ if ((ep->desc->bEndpointAddress & 0x0f) == 2 ++ && (ctrl_inb(USBIFR0) & EP2_TR) != 0 ++ && write_fifo(ep, req)) { ++ req = 0; ++ } ++ else if ((ep->desc->bEndpointAddress & 0x0f) == 3 ++ && (ctrl_inb(USBIFR1) & EP3_TR) != 0 ++ && write_fifo(ep, req)) { ++ req = 0; ++ } ++ } ++ ++ if (likely (((req && ep->desc) && ep->dma < 0) || ep->desc == 0)) ++ pio_irq_enable(ep); ++ ++ return req; ++} ++ ++ ++static int ++superh_ep_queue(struct usb_ep *_ep, struct usb_request *_req, int gfp_flags) ++{ ++ struct superh_request *req; ++ struct superh_ep *ep; ++ struct superh_udc *dev; ++ unsigned long flags; ++ ++ req = container_of(_req, struct superh_request, req); ++ ep = container_of(_ep, struct superh_ep, ep); ++ ++ DBG(DBG_VERY_NOISY, "superh_ep_queue\n"); ++ ++ /* If we have just sent a fake configuration request then ++ * this is the reply. We don't want to send it to the host ++ * so just ignore it. ++ */ ++ if (ep->desc == 0 /* ep0 */ && ep->dev->fake_config) { ++ DBG(DBG_NOISY, "Ignoring bogus SET_CONFIGURATION response\n"); ++ done(ep, req, 0); ++ ep->dev->fake_config = 0; ++ return 1; ++ } ++ ++ if (unlikely (!_req || !_req->complete || !_req->buf ++ || !list_empty(&req->queue))) { ++ DMSG("%s, bad params %s, %p, %p, %p, %d\n", __FUNCTION__, ++ ep->ep.name, _req, _req->complete, _req->buf, ++ list_empty(&req->queue)); ++ return -EINVAL; ++ } ++ ++ if (unlikely (!_ep || (!ep->desc && ep->ep.name != ep0name))) { ++ DMSG("%s, bad ep\n", __FUNCTION__); ++ return -EINVAL; ++ } ++ ++ dev = ep->dev; ++ if (unlikely (!dev->driver ++ || dev->gadget.speed == USB_SPEED_UNKNOWN)) { ++ DMSG("%s, bogus device state\n", __FUNCTION__); ++ return -ESHUTDOWN; ++ } ++ ++#ifdef USE_DMA ++ /* TODO */ ++ if (ep->dma >= 0) { ++ unsigned long start = (unsigned long) _req->buf; ++ ++ clean_dcache_range(start, start + _req->length); ++ /* or for USB_DIR_OUT, invalidate_dcache_range (...) */ ++ } ++#endif ++ ++ DBG(DBG_NOISY, "%s queue req %p, len %d buf %p\n", ++ _ep->name, _req, _req->length, _req->buf); ++ ++ local_irq_save(flags); ++ ++ _req->status = -EINPROGRESS; ++ _req->actual = 0; ++ ++ /* kickstart this i/o queue? */ ++ if (list_empty(&ep->queue) && !ep->stopped && !ep->halted) { ++ req = process_ep_req(ep, req); ++ } ++ ++ /* pio or dma irq handler advances the queue. */ ++ if (likely (req != 0)) ++ list_add_tail(&req->queue, &ep->queue); ++ ++ local_irq_restore(flags); ++ ++ return 0; ++} ++ ++static int ++superh_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) ++{ ++ struct superh_ep *ep; ++ struct superh_request *req; ++ unsigned long flags; ++ ++ DBG(DBG_NOISY, "superh_ep_dequeue %s\n", _ep->name); ++ ++ ep = container_of(_ep, struct superh_ep, ep); ++ req = container_of(_req, struct superh_request, req); ++ if (!_ep || !_req || ep->ep.name == ep0name) ++ return -EINVAL; ++ ++ local_irq_save(flags); ++#ifdef USE_DMA ++ if (ep->dma >= 0 && ep->queue.next == &req->queue && !ep->stopped) { ++ cancel_dma(ep); ++ done(ep, req, -ECONNRESET); ++ /* restart i/o */ ++ if (!list_empty(&ep->queue)) { ++ req = list_entry(ep->queue.next, ++ struct superh_request, queue); ++ kick_dma(ep, req); ++ } ++ } else ++#endif ++ if (!list_empty(&req->queue)) ++ done(ep, req, -ECONNRESET); ++ else ++ req = 0; ++ local_irq_restore(flags); ++ ++ return req ? 0 : -EOPNOTSUPP; ++} ++ ++/* stall/unstall an endpoint, 0 clears the stall, 1 sets it */ ++static int ++superh_ep_set_halt(struct usb_ep *_ep, int value) ++{ ++ struct superh_ep *ep; ++ unsigned long flags; ++ ++ ep = container_of(_ep, struct superh_ep, ep); ++ if (unlikely (!_ep ++ || (!ep->desc && ep->ep.name != ep0name)) ++ || ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) { ++ DMSG("%s, bad ep\n", __FUNCTION__); ++ return -EINVAL; ++ } ++ ++ if (ep->halted == value) ++ return 0; ++ ++ local_irq_save(flags); ++ ++ if (value == 1 && (ep->bEndpointAddress & USB_DIR_IN) != 0 ++ && ((ctrl_inb(USBDASTS) & ep->data_present_mask) != 0 ++ || !list_empty(&ep->queue))) { ++ local_irq_restore(flags); ++ DBG(DBG_VERBOSE, "Can't %s on %s\n", value ? " halt" : "clear halt", _ep->name); ++ return -EAGAIN; ++ } ++ ++ if (value) { ++ or_b(ep->stall_mask, USBEPSTL); ++ if (!ep->desc) { ++ ep->dev->ep0state = EP0_STALL; ++ } ++ /* disable ep interrupts and set a timer to clear the stall */ ++ pio_irq_disable(ep); ++ mod_timer(&ep->dev->timer, jiffies + STALL_TIME); ++ } ++ else { ++ and_b(~ep->stall_mask, USBEPSTL); ++ } ++ ++ ep->halted = value; ++ ++ local_irq_restore(flags); ++ ++ DBG(DBG_VERBOSE, "%s %s\n", _ep->name, value ? " halt" : "clear halt"); ++ ++ return 0; ++} ++ ++static int superh_ep_fifo_status(struct usb_ep *_ep) ++{ ++ struct superh_ep *ep; ++ ++ DBG(DBG_NOISY, "superh_ep_fifo_status\n"); ++ ++ ep = container_of(_ep, struct superh_ep, ep); ++ if (!_ep) { ++ DMSG("%s, bad ep\n", __FUNCTION__); ++ return -ENODEV; ++ } ++ if ((ep->bEndpointAddress & USB_DIR_IN) != 0) ++ return -EOPNOTSUPP; ++ if (ep->dev->gadget.speed == USB_SPEED_UNKNOWN) ++ return 0; ++ else { ++ switch (ep->desc->bEndpointAddress & 0x0f) { ++ case 0: ++ return ctrl_inb(USBEPSZ0O); ++ case 1: ++ return ctrl_inb(USBEPSZ1); ++ } ++ } ++ ++ return 0; ++} ++ ++static void superh_ep_fifo_flush(struct usb_ep *_ep) ++{ ++ struct superh_ep *ep; ++ ++ DBG(DBG_NOISY, "superh_ep_fifo_flush\n"); ++ ++ ep = container_of(_ep, struct superh_ep, ep); ++ if (!_ep || ep->ep.name == ep0name || !list_empty(&ep->queue)) { ++ DMSG("%s, bad ep\n", __FUNCTION__); ++ return; ++ } ++ ++ or_b(ep->clear_mask, USBFCLR); ++} ++ ++static struct usb_ep_ops superh_ep_ops = { ++ .enable = superh_ep_enable, ++ .disable = superh_ep_disable, ++ ++ .alloc_request = superh_ep_alloc_request, ++ .free_request = superh_ep_free_request, ++ ++ .alloc_buffer = superh_ep_alloc_buffer, ++ .free_buffer = superh_ep_free_buffer, ++ ++ .queue = superh_ep_queue, ++ .dequeue = superh_ep_dequeue, ++ ++ .set_halt = superh_ep_set_halt, ++ .fifo_status = superh_ep_fifo_status, ++ .fifo_flush = superh_ep_fifo_flush, ++}; ++ ++/* --------------------------------------------------------------------------- ++ * device-scoped parts of the api to the usb controller hardware ++ * --------------------------------------------------------------------------- ++ */ ++ ++static int superh_udc_get_frame(struct usb_gadget *_gadget) ++{ ++ DBG(DBG_VERY_NOISY, "superh_udc_get_frame\n"); ++ ++ return -EOPNOTSUPP; ++} ++ ++static const struct usb_gadget_ops superh_udc_ops = { ++ .get_frame = superh_udc_get_frame, ++ // no remote wakeup ++ // always selfpowered ++}; ++ ++ ++/* if we're trying to save space, don't bother with this proc file */ ++ ++#if defined(CONFIG_PROC_FS) && !defined(CONFIG_EMBEDDED) ++# define UDC_PROC_FILE ++#endif ++ ++#ifdef UDC_PROC_FILE ++ ++static const char proc_node_name [] = "driver/udc"; ++ ++static int ++udc_proc_read(char *page, char **start, off_t off, int count, ++ int *eof, void *_dev) ++{ ++ char *buf = page; ++ struct superh_udc *dev = _dev; ++ char *next = buf; ++ unsigned size = count; ++ unsigned long flags; ++ int t; ++ int i; ++ ++ local_irq_save(flags); ++ ++ /* basic device status */ ++ t = snprintf(next, size, ++ "%s\n%s version: %s\nGadget driver: %s\nHost %s\n\n", ++ driver_desc, ++ driver_name, DRIVER_VERSION, ++ dev->driver ? dev->driver->driver.name : "(none)", ++ is_usb_connected ? "full speed" : "disconnected"); ++ size -= t; ++ next += t; ++ ++ /* device registers */ ++ t = snprintf(next, size, ++ "ifr0 %02X, ifr1 %02X, isr0 %02X, isr1 %02X, ier0 %02X, ier1 %02X\n", ++ ctrl_inb(USBIFR0), ctrl_inb(USBIFR1), ++ ctrl_inb(USBISR0), ctrl_inb(USBISR1), ++ ctrl_inb(USBIER0), ctrl_inb(USBIER1)); ++ size -= t; ++ next += t; ++ ++ t = snprintf(next, size, ++ "epsz0o %02X, epsz1 %02X, dasts %02X, dmar %02X\n", ++ ctrl_inb(USBEPSZ0O), ctrl_inb(USBEPSZ1), ++ ctrl_inb(USBDASTS), ctrl_inb(USBDMA)); ++ size -= t; ++ next += t; ++ ++ t = snprintf(next, size, ++ "epstl %02X, xvercr %02X\n", ++ ctrl_inb(USBEPSTL), ctrl_inb(USBXVERCR)); ++ size -= t; ++ next += t; ++ ++ if (!is_usb_connected || !dev->driver) ++ goto done; ++ ++ t = snprintf(next, size, "ep0 IN %lu/%lu, OUT %lu/%lu; irq0s %lu; irq1s %lu\n\n", ++ dev->stats.write.bytes, dev->stats.write.ops, ++ dev->stats.read.bytes, dev->stats.read.ops, ++ dev->stats.irq0s, dev->stats.irq1s); ++ size -= t; ++ next += t; ++ ++ /* dump endpoint queues */ ++ for (i = 0; i < 4; i++) { ++ struct superh_ep *ep = &dev->ep [i]; ++ struct superh_request *req; ++ int t; ++ ++ if (i != 0) { ++ const struct usb_endpoint_descriptor *d; ++ ++ d = ep->desc; ++ if (!d) ++ continue; ++ t = snprintf(next, size, ++ "%s max %d %s\n", ++ ep->ep.name, le16_to_cpu (d->wMaxPacketSize), ++ (ep->dma >= 0) ? "dma" : "pio"); ++ ++ } else /* ep0 should only have one transfer queued */ ++ t = snprintf(next, size, "ep0 max 8 pio\n"); ++ if (t <= 0 || t > size) ++ goto done; ++ size -= t; ++ next += t; ++ ++ if (list_empty(&ep->queue)) { ++ t = snprintf(next, size, "\t(nothing queued)\n"); ++ if (t <= 0 || t > size) ++ goto done; ++ size -= t; ++ next += t; ++ continue; ++ } ++ list_for_each_entry(req, &ep->queue, queue) { ++#ifdef USE_DMA ++ if (ep->dma >= 0 && req->queue.prev == &ep->queue) ++ t = snprintf(next, size, ++ "\treq %p len %d/%d " ++ "buf %p (dma%d dcmd %08x)\n", ++ &req->req, req->req.actual, ++ req->req.length, req->req.buf, ++ ep->dma, DCMD(ep->dma) ++ // low 13 bits == bytes-to-go ++ ); ++ else ++#endif ++ t = snprintf(next, size, ++ "\treq %p len %d/%d buf %p\n", ++ &req->req, req->req.actual, ++ req->req.length, req->req.buf); ++ if (t <= 0 || t > size) ++ goto done; ++ size -= t; ++ next += t; ++ } ++ } ++ ++done: ++ local_irq_restore(flags); ++ return count - size; ++} ++ ++#endif /* UDC_PROC_FILE */ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* ++ * udc_disable - disable USB device controller ++ */ ++static void udc_disable(struct superh_udc *dev) ++{ ++ /* block all irqs */ ++ ctrl_outb( 0, USBIER0); ++ ctrl_outb( 0, USBIER1); ++ ++ /* Disable the USB module */ ++ or_b(0x80, STBCR3); ++ ++ /* Disable the USB clock */ ++ ctrl_outw(0xA500, UCLKCR); ++ ++ ep0_idle (dev); ++ dev->gadget.speed = USB_SPEED_UNKNOWN; ++} ++ ++/* ++ * udc_reinit - initialize software state ++ */ ++static void udc_reinit(struct superh_udc *dev) ++{ ++ u32 i; ++ ++ /* device/ep0 records init */ ++ INIT_LIST_HEAD (&dev->gadget.ep_list); ++ dev->gadget.ep0 = &dev->ep[0].ep; ++ INIT_LIST_HEAD (&dev->gadget.ep0->ep_list); ++ dev->ep0state = EP0_IDLE; ++ ++ /* basic endpoint records init */ ++ for (i = 0; i < 4; i++) { ++ struct superh_ep *ep = &dev->ep[i]; ++ ++ ep->ep.name = ep_name[i]; ++ ep->ep.ops = &superh_ep_ops; ++ if (i != 0) ++ list_add_tail (&ep->ep.ep_list, &dev->gadget.ep_list); ++ ++ ep->dev = dev; ++ ep->desc = 0; ++ ep->stopped = 0; ++ ep->halted = 0; ++ ep->dma = -1; ++ INIT_LIST_HEAD (&ep->queue); ++ ++ /* address may need USB_DIR_IN, attributes likely wrong */ ++ ep->bEndpointAddress = i; ++ ep->bmAttributes = USB_ENDPOINT_XFER_BULK; ++ } ++ ++ /* TODO at least from here on, static initialization ++ * would work just as well and would need less code space ++ */ ++ ++ /* ep0 == control */ ++ dev->ep[ 0].ep.maxpacket = EP0_FIFO_SIZE; ++ dev->ep[ 0].data_present_mask = EP0i_DE; ++ dev->ep[ 0].stall_mask = EP0_STL; ++ dev->ep[ 0].interrupt_mask = EP0o_TS | EP0i_TR | EP0i_TS; ++ dev->ep[ 0].interrupt_reg = USBIER0; ++ dev->ep[ 0].clear_mask = EP0i_CLEAR | EP0o_CLEAR; ++ dev->ep[ 0].fifo_reg = 0; ++ dev->ep[ 0].packet_enable_mask = 0; ++ ++ dev->ep[ 1].ep.maxpacket = BULK_FIFO_SIZE; ++ dev->ep[ 1].bEndpointAddress |= USB_DIR_OUT; ++ dev->ep[ 1].data_present_mask = 0x00; ++ dev->ep[ 1].stall_mask = EP1_STL; ++ dev->ep[ 1].interrupt_mask = EP1_FULL; ++ dev->ep[ 1].interrupt_reg = USBIER0; ++ dev->ep[ 1].clear_mask = EP1_CLEAR; ++ dev->ep[ 1].fifo_reg = 0; ++ dev->ep[ 1].packet_enable_mask = 0; ++ ++ dev->ep[ 2].ep.maxpacket = BULK_FIFO_SIZE; ++ dev->ep[ 2].bEndpointAddress |= USB_DIR_IN; ++ dev->ep[ 2].data_present_mask = EP2_DE; ++ dev->ep[ 2].stall_mask = EP2_STL; ++ dev->ep[ 2].interrupt_mask = EP2_TR | EP2_EMPTY; ++ dev->ep[ 2].interrupt_reg = USBIER0; ++ dev->ep[ 2].clear_mask = EP2_CLEAR; ++ dev->ep[ 2].fifo_reg = USBEPDR2; ++ dev->ep[ 2].packet_enable_mask = EP2_PKTE; ++ ++ dev->ep[ 3].ep.maxpacket = INT_FIFO_SIZE; ++ dev->ep[ 3].bEndpointAddress |= USB_DIR_IN; ++ dev->ep[ 3].data_present_mask = EP3_DE; ++ dev->ep[ 3].stall_mask = EP3_STL; ++ dev->ep[ 3].interrupt_mask = EP3_TR | EP3_TS; ++ dev->ep[ 3].interrupt_reg = USBIER1; ++ dev->ep[ 3].clear_mask = EP3_CLEAR; ++ dev->ep[ 3].fifo_reg = USBEPDR3; ++ dev->ep[ 3].packet_enable_mask = EP3_PKTE; ++} ++ ++/* until it's enabled, this UDC should be completely invisible ++ * to any USB host. ++ */ ++static void udc_enable (struct superh_udc *dev) ++{ ++#if defined(CONFIG_CPU_SUBTYPE_SH7727) ++ // Reset and then Select Function USB1_pwr_en out (USB) c.f. Section 26, Table 26.1 PTE2 ++ and_w(PN_PB2_MSK, PECR); ++ or_w(PN_PB2_OF, PECR); ++ ++ // Reset and then Select Function UCLK c.f. Section 26, Table 26.1, PTD6 ++ and_w(PN_PB6_MSK, PDCR); ++ or_w(PN_PB6_OF, PDCR); ++ ++ // Stop USB module prior to setting clocks c.f. Section 9.2.3 ++ and_b(~MSTP14, STBCR3); ++ or_b(MSTP14, STBCR3); ++ ++ // Select external clock, 1/1 divisor c.f. Section 11.3.1 ++ or_b(USBDIV_11|USBCKS_EC, EXCPGCR); ++ ++ // Start USB c.f. Section 9.2.3 ++ and_b(~MSTP14, STBCR3); ++ ++ // Disable pullup c.f. Section 23.5.19 ++ or_b(PULLUP_E, USBDMA); ++ //and_b(~PULLUP_E, USBDMA); ++ ++ // Set port 1 to function, disabled c.f. Section 22.2.1 ++ or_w(USB_TRANS_TRAN | USB_SEL_FUNC, EXPFC); ++ ++ // Enable pullup c.f. Section 23.5.19a ++ and_b(~PULLUP_E, USBDMA); ++ //or_b(PULLUP_E, USBDMA); ++#elif defined(CONFIG_CPU_SUBTYPE_SH7705) ++ /* Disable the USB module */ ++ or_b(0x80, STBCR3); ++ ++ /* Set the clock to external & enable */ ++ ctrl_outw(0xA5E0, UCLKCR); ++ ++ /* Enable the USB module */ ++ and_b(0x7f, STBCR3); ++ ++ /* Enable USB pins. */ ++ ctrl_outw(0x01FD, PMCR); /* VBUS */ ++ or_b(PULLUP_E, PMDR); ++#endif ++ dev->gadget.speed = USB_SPEED_UNKNOWN; ++ dev->stats.irqs = 0; ++ dev->stats.irq0s = 0; ++ dev->stats.irq1s = 0; ++ ++ // reset fifo's and stall's ++ or_b( EP3_CLEAR | EP1_CLEAR | EP2_CLEAR | EP0o_CLEAR | EP0i_CLEAR, USBFCLR); ++ or_b(0, USBEPSTL); ++ ++ /* Setup interrupt priority by using the interrupt select registers */ ++ ctrl_outb(F0_LOW, USBISR0); ++ ctrl_outb(F1_LOW, USBISR1); ++ ++ /* Enable some interrupts */ ++ or_b( BRST | SETUP_TS | EP0o_TS | EP0i_TR | EP0i_TS, USBIER0); ++ or_b( VBUSF, USBIER1); ++} ++ ++/* when a driver is successfully registered, it will receive ++ * control requests including set_configuration(), which enables ++ * non-control requests. then usb traffic follows until a ++ * disconnect is reported. then a host may connect again, or ++ * the driver might get unbound. ++ */ ++int usb_gadget_register_driver(struct usb_gadget_driver *driver) ++{ ++ struct superh_udc *dev = the_controller; ++ int retval; ++ ++ if (!driver ++ /*|| driver->speed != USB_SPEED_FULL ++ || !driver->bind ++ || !driver->unbind ++ || !driver->disconnect ++ || !driver->setup*/) ++ return -EINVAL; ++ if (!dev) ++ return -ENODEV; ++ if (dev->driver) ++ return -EBUSY; ++ ++ /* first hook up the driver ... */ ++ dev->driver = driver; ++ ++ retval = driver->bind(&dev->gadget); ++ if (retval) { ++ DMSG("bind to driver %s --> error %d\n", ++ driver->driver.name, retval); ++ dev->driver = 0; ++ return retval; ++ } ++ ++ /* ... then enable host detection and ep0; and we're ready ++ * for set_configuration as well as eventual disconnect. ++ * NOTE: this shouldn't power up until later. ++ */ ++ udc_enable(dev); ++ ++ DMSG("registered gadget driver '%s'\n", driver->driver.name); ++ dump_state(dev); ++ return 0; ++} ++ ++EXPORT_SYMBOL(usb_gadget_register_driver); ++ ++static void ++stop_activity(struct superh_udc *dev, struct usb_gadget_driver *driver) ++{ ++ int i; ++ ++ /* don't disconnect drivers more than once */ ++ if (dev->gadget.speed == USB_SPEED_UNKNOWN) ++ driver = 0; ++ dev->gadget.speed = USB_SPEED_UNKNOWN; ++ ++ /* prevent new request submissions, kill any outstanding requests */ ++ for (i = 0; i < 4; i++) { ++ struct superh_ep *ep = &dev->ep[i]; ++ ++ ep->stopped = 1; ++ nuke(ep, -ESHUTDOWN); ++ } ++ ++ del_timer_sync(&dev->timer); ++ ++ /* report disconnect; the driver is already quiesced */ ++ if (driver) ++ driver->disconnect(&dev->gadget); ++ ++ /* re-init driver-visible data structures */ ++ udc_reinit(dev); ++} ++ ++int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) ++{ ++ struct superh_udc *dev = the_controller; ++ ++ if (!dev) ++ return -ENODEV; ++ if (!driver || driver != dev->driver) ++ return -EINVAL; ++ ++ local_irq_disable(); ++ udc_disable(dev); ++ stop_activity(dev, driver); ++ driver->unbind(&dev->gadget); ++ dev->driver = 0; ++ local_irq_enable(); ++ ++ DMSG("unregistered gadget driver '%s'\n", driver->driver.name); ++ dump_state(dev); ++ return 0; ++} ++ ++EXPORT_SYMBOL(usb_gadget_unregister_driver); ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,40) ++MODULE_DESCRIPTION(driver_desc); ++#endif ++MODULE_AUTHOR("Julian Back"); ++MODULE_LICENSE("GPL"); ++ ++/* ++ * cleanup - free resources allocated during init ++ */ ++static void /*__exit and */ __init cleanup(void) ++{ ++ struct superh_udc *dev = the_controller; ++ ++ if (!dev) ++ return; ++ ++ udc_disable(dev); ++#ifdef UDC_PROC_FILE ++ remove_proc_entry(proc_node_name, NULL); ++#endif ++ usb_gadget_unregister_driver(dev->driver); ++ ++ if (dev->got_irq0) { ++ free_irq(USBF0_IRQ, dev); ++ dev->got_irq0 = 0; ++ } ++ ++ if (dev->got_irq1) { ++ free_irq(USBF1_IRQ, dev); ++ dev->got_irq1 = 0; ++ } ++ ++ the_controller = 0; ++} ++module_exit (cleanup); ++ ++/* ++ * init - allocate resources ++ */ ++static int __init init(void) ++{ ++ static struct superh_udc memory; ++ ++ struct superh_udc *dev; ++ int retval; ++ ++ printk(KERN_DEBUG "%s: version %s\n", driver_name, DRIVER_VERSION); ++ ++ /* initialize data */ ++ dev = &memory; ++ ++ memset(dev, 0, sizeof *dev); ++ dev->gadget.ops = &superh_udc_ops; ++ dev->gadget.name = driver_name; ++ dev->gadget.dev.bus_id = "udc"; ++ dev->gadget.speed = USB_SPEED_UNKNOWN; ++ ++ dev->vbusmn = 0; ++ ++ atomic_set(&dev->in_interrupt, 0); ++ ++ the_controller = dev; ++ udc_disable(dev); ++ udc_reinit(dev); ++ ++ /* irq setup after old hardware state is cleaned up */ ++ retval = request_irq(USBF0_IRQ, superh_udc_irq_f0, ++ 0/*SA_INTERRUPT | SA_SAMPLE_RANDOM*/, ++ driver_name, dev); ++ if (retval != 0) { ++ printk(KERN_ERR "%s: can't get irq %i, err %d\n", ++ driver_name, USBF0_IRQ, retval); ++ goto failed; ++ } ++ dev->got_irq0 = 1; ++ ++ retval = request_irq(USBF1_IRQ, superh_udc_irq_f1, ++ 0/*SA_INTERRUPT | SA_SAMPLE_RANDOM*/, ++ driver_name, dev); ++ if (retval != 0) { ++ printk(KERN_ERR "%s: can't get irq %i, err %d\n", ++ driver_name, USBF1_IRQ, retval); ++ goto failed; ++ } ++ dev->got_irq1 = 1; ++ ++ printk(KERN_INFO "%s, IRQs %d %d\n", driver_desc, ++ USBF0_IRQ, USBF1_IRQ); ++ dump_state(dev); ++ ++ dev->setup_countdown = DEFAULT_SETUP_COUNT; ++ ++#ifdef UDC_PROC_FILE ++ create_proc_read_entry(proc_node_name, 0, NULL, udc_proc_read, dev); ++#endif ++ ++ return 0; ++ ++failed: ++ cleanup(); ++ return retval; ++} ++module_init (init); +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/superh_udc.h kernel/drivers/usb/gadget/superh_udc.h +--- /tmp/kernel/drivers/usb/gadget/superh_udc.h 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/superh_udc.h 2005-04-22 17:53:19.513526654 +0200 +@@ -0,0 +1,363 @@ ++/* ++ * Renesas SuperH USB 1.1 device controller (found on SH7705, SH7727...) ++ * ++ * Copyright (C) 2003 Renesas Technology Europe Limited ++ * Copyright (C) 2003 Julian Back (jback@mpc-data.co.uk), MPC Data Limited ++ * ++ * 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 ++ */ ++ ++#ifndef __LINUX_USB_GADGET_SUPERH_UDC_H ++#define __LINUX_USB_GADGET_SUPERH_UDC_H ++ ++#include ++ ++struct superh_udc; ++ ++struct superh_ep { ++ struct usb_ep ep; ++ struct superh_udc *dev; ++ ++ const struct usb_endpoint_descriptor *desc; ++ struct list_head queue; ++ int dma; ++ ++ u8 bEndpointAddress; ++ u8 bmAttributes; ++ ++ unsigned stopped : 1; ++ unsigned halted : 1; ++ ++ u8 data_present_mask; ++ u8 stall_mask; ++ u8 interrupt_mask; ++ u8 clear_mask; ++ u8 packet_enable_mask; ++ unsigned interrupt_reg; ++ unsigned fifo_reg; ++}; ++ ++struct superh_request { ++ struct usb_request req; ++ struct list_head queue; ++}; ++ ++enum ep0_state { ++ EP0_IDLE, ++ EP0_IN_DATA_PHASE, ++ EP0_OUT_DATA_PHASE, ++ EP0_END_XFER, ++ EP0_STALL, ++}; ++ ++#define EP0_FIFO_SIZE ((unsigned)8) ++#define BULK_FIFO_SIZE ((unsigned)64) ++#define ISO_FIFO_SIZE ((unsigned)0) ++#define INT_FIFO_SIZE ((unsigned)8) ++ ++struct udc_stats { ++ struct ep0stats { ++ unsigned long ops; ++ unsigned long bytes; ++ } read, write; ++ unsigned long irqs; ++ unsigned long irq0s; ++ unsigned long irq1s; ++}; ++ ++struct superh_udc { ++ struct usb_gadget gadget; ++ struct usb_gadget_driver *driver; ++ atomic_t in_interrupt; ++ ++ enum ep0_state ep0state; ++ struct udc_stats stats; ++ unsigned int vbusmn; ++ unsigned long vbusf_time; ++ unsigned got_irq0 : 1, ++ got_irq1 : 1, ++ fake_config: 1; ++ int setup_countdown; ++ unsigned long reset_time; ++ struct timer_list timer; ++ struct superh_ep ep [4]; ++}; ++ ++/* 2.5 changes ... */ ++ ++#ifndef container_of ++#define container_of list_entry ++#endif ++ ++#ifndef WARN_ON ++#define WARN_ON BUG_ON ++#endif ++ ++/* one I/O pin should be used to detect disconnect */ ++#define is_usb_connected ((ctrl_inb(USBIFR1) & VBUSF) != 0) ++ ++/* Register addresses - should really be in include/asm-sh */ ++ ++#ifdef CONFIG_CPU_SUBTYPE_SH7705 ++ ++#define USBEPDR0I 0xA4480000 ++#define USBEPDR0O 0xA4480004 ++#define USBEPDR0S 0xA4480008 ++#define USBEPDR1 0xA448000C ++#define USBEPDR2 0xA4480010 ++#define USBEPDR3 0xA4480014 ++#define USBIFR0 0xA4480018 ++#define USBIFR1 0xA448001C ++#define USBTRG 0xA4480020 ++#define USBFCLR 0xA4480024 ++#define USBEPSZ0O 0xA4480028 ++#define USBDASTS 0xA448002C ++#define USBEPSTL 0xA4480030 ++#define USBIER0 0xA4480034 ++#define USBIER1 0xA4480038 ++#define USBEPSZ1 0xA448003C ++#define USBDMA 0xA4480040 ++#define USBISR0 0xA4480044 ++#define USBISR1 0xA4480048 ++ ++#define USBXVERCR 0xA4480060 ++ ++#define STBCR3 0xA40A0000 ++#define UCLKCR 0xA40A0008 ++ ++#define PMCR 0xA4000118 ++#define PNCR 0xA400011A ++#define PNCR2 0xA405015A ++ ++#define PMDR 0xA4000138 ++ ++#endif ++ ++/* ++ * Standby Control Register (STBCR3) c.f. 9.2.3 ++ */ ++ ++#define MSTP14 0x10 ++ ++/* ++ * EXCPG Control Register (EXCPGCR) c.f. Section 11.3.1 ++ */ ++ ++#define USBDIVS_EL0 0x00 ++#define USBDIVS_EL1 0x01 ++#define USBDIVS_EL2 0x02 ++ ++#define USBCKS_EL1 0x04 ++#define USBCKS_EL2 0x10 ++#define USBCKS_EL3 0x20 ++ ++#define USBDIV_11 0x00 ++#define USBDIV_12 0x01 ++#define USBDIV_13 0x02 ++ ++#define USBCKS_PC 0x00 ++#define USBCKS_IC 0x20 ++#define USBCKS_BC 0x24 ++#define USBCKS_EC 0x30 ++ ++ ++/* ++ * Extra Pin Function Controller (EXPFC) c.f. Section 22.2.1 ++ */ ++ ++#define USB_TRANS_TRAN 0x00 ++#define USB_TRANS_DIG 0x02 ++ ++#define USB_SEL_HOST 0x00 ++#define USB_SEL_FUNC 0x01 ++ ++ ++/* ++ * USBDMA Setting Register (USBDMAR) c.f. Section 23.5.19 ++ */ ++ ++#define EP1_DMAE 0x01 ++#define EP2_DMAE 0x02 ++ ++#if defined(CONFIG_CPU_SUBTYPE_SH7727) ++#define PULLUP_E 0x04 ++#endif ++ ++#if defined(CONFIG_SH_EDOSK7705) ++#define PULLUP_E 0x01 ++#endif ++ ++/* ++ * USB Interrupt Flag Register 0 (USBIFR0) c.f. Section 23.5.7 ++ */ ++ ++#define BRST 0x80 ++#define EP1_FULL 0x40 ++#define EP2_TR 0x20 ++#define EP2_EMPTY 0x10 ++#define SETUP_TS 0x08 ++#define EP0o_TS 0x04 ++#define EP0i_TR 0x02 ++#define EP0i_TS 0x01 ++ ++ ++/* ++ * USB Interrupt Flag Register 1 (USBIFR1) c.f. Section 23.5.8 ++ */ ++ ++#define VBUSMN 0x08 ++#define EP3_TR 0x04 ++#define EP3_TS 0x02 ++#define VBUSF 0x01 ++ ++/* ++ * USB Trigger Register (USBTRG) c.f. Section 23.5.9 ++ */ ++ ++#define EP0i_PKTE 0x01 ++#define EP0o_PKTE 0x02 ++#define EP0o_RDFN 0x02 ++#define EP0s_PKTE 0x04 ++#define EP0s_RDFN 0x04 ++ ++#define EP2_PKTE 0x10 ++#define EP1_PKTE 0x20 ++#define EP1_RDFN 0x20 ++#define EP3_PKTE 0x40 ++ ++ ++/* ++ * USBFIFO Clear Register (USBFCLR) c.f. Section 23.5.10 ++ */ ++ ++#define EP3_CLEAR 0x40 ++#define EP1_CLEAR 0x20 ++#define EP2_CLEAR 0x10 ++#define EP0o_CLEAR 0x02 ++#define EP0i_CLEAR 0x01 ++ ++ ++/* ++ * USBEPSTL Endpoint Stall Register ++ */ ++#define EP3_STL 0x08 ++#define EP2_STL 0x04 ++#define EP1_STL 0x02 ++#define EP0_STL 0x01 ++ ++/* ++ * USBDASTS Data Status Register ++ */ ++#define EP3_DE 0x20 ++#define EP2_DE 0x10 ++#define EP0i_DE 0x01 ++ ++/* ++ * Port Control Registers (PNCR) c.f. Section 26.2 ++ */ ++#define PN_PB0_OF 0x0000 ++#define PN_PB0_PO 0x0001 ++#define PN_PB0_PI_ON 0x0002 ++#define PN_PB0_PI_OFF 0x0003 ++#define PN_PB0_MSK ~0x0003 ++ ++#define PN_PB1_OF 0x0000 ++#define PN_PB1_PO 0x0004 ++#define PN_PB1_PI_ON 0x0008 ++#define PN_PB1_PI_OFF 0x000c ++#define PN_PB1_MSK ~0x000c ++ ++#define PN_PB2_OF 0x0000 ++#define PN_PB2_PO 0x0010 ++#define PN_PB2_PI_ON 0x0020 ++#define PN_PB2_PI_OFF 0x0030 ++#define PN_PB2_MSK ~0x0030 ++ ++#define PN_PB3_OF 0x0000 ++#define PN_PB3_PO 0x0040 ++#define PN_PB3_PI_ON 0x0080 ++#define PN_PB3_PI_OFF 0x00c0 ++#define PN_PB3_MSK ~0x00c0 ++ ++#define PN_PB4_OF 0x0000 ++#define PN_PB4_PO 0x0100 ++#define PN_PB4_PI_ON 0x0200 ++#define PN_PB4_PI_OFF 0x0300 ++#define PN_PB4_MSK ~0x0300 ++ ++#define PN_PB5_OF 0x0000 ++#define PN_PB5_PO 0x0400 ++#define PN_PB5_PI_ON 0x0800 ++#define PN_PB5_PI_OFF 0x0c00 ++#define PN_PB5_MSK ~0x0c00 ++ ++#define PN_PB6_OF 0x0000 ++#define PN_PB6_PO 0x1000 ++#define PN_PB6_PI_ON 0x2000 ++#define PN_PB6_PI_OFF 0x3000 ++#define PN_PB6_MSK ~0x3000 ++ ++#define PN_PB7_OF 0x0000 ++#define PN_PB7_PO 0x4000 ++#define PN_PB7_PI_ON 0x8000 ++#define PN_PB7_PI_OFF 0xc000 ++#define PN_PB7_MSK ~0xc000 ++ ++/* ++ * Debugging support vanishes in non-debug builds. DBG_NORMAL should be ++ * mostly silent during normal use/testing, with no timing side-effects. ++ */ ++#define DBG_NORMAL 1 /* error paths, device state transitions */ ++#define DBG_VERBOSE 2 /* add some success path trace info */ ++#define DBG_NOISY 3 /* ... even more: request level */ ++#define DBG_VERY_NOISY 4 /* ... even more: packet level */ ++ ++#ifdef DEBUG ++ ++#define DMSG(stuff...) printk(KERN_DEBUG "udc: " stuff) ++ ++#if defined(VERY_NOISY) ++# define UDC_DEBUG DBG_VERY_NOISY ++#elif defined(NOISY) ++# define UDC_DEBUG DBG_NOISY ++#elif defined(VERBOSE) ++# define UDC_DEBUG DBG_VERBOSE ++#else ++# define UDC_DEBUG DBG_NORMAL ++#endif ++ ++static void __attribute__ ((__unused__)) ++dump_state(struct superh_udc *dev) ++{ ++ if (!is_usb_connected) ++ return; ++} ++ ++ ++#else ++ ++#define DMSG(stuff...) do{}while(0) ++ ++#define UDC_DEBUG ((unsigned)0) ++ ++#define dump_state(x) do{}while(0) ++ ++#endif ++ ++#define DBG(lvl, stuff...) do{if ((lvl) <= UDC_DEBUG) DMSG(stuff);}while(0) ++ ++#define WARN(stuff...) printk(KERN_WARNING "udc: " stuff) ++ ++#endif /* __LINUX_USB_GADGET_SUPERH_UDC_H */ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/usbstring.c kernel/drivers/usb/gadget/usbstring.c +--- /tmp/kernel/drivers/usb/gadget/usbstring.c 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/usbstring.c 2005-04-22 17:53:19.516526166 +0200 +@@ -0,0 +1,136 @@ ++/* ++ * Copyright (C) 2003 David Brownell ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU Lesser General Public License as published ++ * by the Free Software Foundation; either version 2.1 of the License, or ++ * (at your option) any later version. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++ ++ ++static int utf8_to_utf16le(const char *s, u16 *cp, unsigned len) ++{ ++ int count = 0; ++ u8 c; ++ u16 uchar; ++ ++ /* this insists on correct encodings, though not minimal ones. ++ * BUT it currently rejects legit 4-byte UTF-8 code points, ++ * which need surrogate pairs. (Unicode 3.1 can use them.) ++ */ ++ while (len != 0 && (c = (u8) *s++) != 0) { ++ if (unlikely(c & 0x80)) { ++ // 2-byte sequence: ++ // 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx ++ if ((c & 0xe0) == 0xc0) { ++ uchar = (c & 0x1f) << 6; ++ ++ c = (u8) *s++; ++ if ((c & 0xc0) != 0xc0) ++ goto fail; ++ c &= 0x3f; ++ uchar |= c; ++ ++ // 3-byte sequence (most CJKV characters): ++ // zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx ++ } else if ((c & 0xf0) == 0xe0) { ++ uchar = (c & 0x0f) << 12; ++ ++ c = (u8) *s++; ++ if ((c & 0xc0) != 0xc0) ++ goto fail; ++ c &= 0x3f; ++ uchar |= c << 6; ++ ++ c = (u8) *s++; ++ if ((c & 0xc0) != 0xc0) ++ goto fail; ++ c &= 0x3f; ++ uchar |= c; ++ ++ /* no bogus surrogates */ ++ if (0xd800 <= uchar && uchar <= 0xdfff) ++ goto fail; ++ ++ // 4-byte sequence (surrogate pairs, currently rare): ++ // 11101110wwwwzzzzyy + 110111yyyyxxxxxx ++ // = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx ++ // (uuuuu = wwww + 1) ++ // FIXME accept the surrogate code points (only) ++ ++ } else ++ goto fail; ++ } else ++ uchar = c; ++ put_unaligned (cpu_to_le16 (uchar), cp++); ++ count++; ++ len--; ++ } ++ return count; ++fail: ++ return -1; ++} ++ ++ ++/** ++ * usb_gadget_get_string - fill out a string descriptor ++ * @table: of c strings encoded using UTF-8 ++ * @id: string id, from low byte of wValue in get string descriptor ++ * @buf: at least 256 bytes ++ * ++ * Finds the UTF-8 string matching the ID, and converts it into a ++ * string descriptor in utf16-le. ++ * Returns length of descriptor (always even) or negative errno ++ * ++ * If your driver needs stings in multiple languages, you'll probably ++ * "switch (wIndex) { ... }" in your ep0 string descriptor logic, ++ * using this routine after choosing which set of UTF-8 strings to use. ++ * Note that US-ASCII is a strict subset of UTF-8; any string bytes with ++ * the eighth bit set will be multibyte UTF-8 characters, not ISO-8859/1 ++ * characters (which are also widely used in C strings). ++ */ ++int ++usb_gadget_get_string (struct usb_gadget_strings *table, int id, u8 *buf) ++{ ++ struct usb_string *s; ++ int len; ++ ++ /* descriptor 0 has the language id */ ++ if (id == 0) { ++ buf [0] = 4; ++ buf [1] = USB_DT_STRING; ++ buf [2] = (u8) table->language; ++ buf [3] = (u8) (table->language >> 8); ++ return 4; ++ } ++ for (s = table->strings; s && s->s; s++) ++ if (s->id == id) ++ break; ++ ++ /* unrecognized: stall. */ ++ if (!s || !s->s) ++ return -EINVAL; ++ ++ /* string descriptors have length, tag, then UTF16-LE text */ ++ len = min ((size_t) 126, strlen (s->s)); ++ memset (buf + 2, 0, 2 * len); /* zero all the bytes */ ++ len = utf8_to_utf16le(s->s, (u16 *)&buf[2], len); ++ if (len < 0) ++ return -EINVAL; ++ buf [0] = (len + 1) * 2; ++ buf [1] = USB_DT_STRING; ++ return buf [0]; ++} ++ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/drivers/usb/gadget/zero.c kernel/drivers/usb/gadget/zero.c +--- /tmp/kernel/drivers/usb/gadget/zero.c 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/usb/gadget/zero.c 2005-04-22 17:53:19.521525352 +0200 +@@ -0,0 +1,1363 @@ ++/* ++ * zero.c -- Gadget Zero, for USB development ++ * ++ * Copyright (C) 2003-2004 David Brownell ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions, and the following disclaimer, ++ * without modification. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. The names of the above-listed copyright holders may not be used ++ * to endorse or promote products derived from this software without ++ * specific prior written permission. ++ * ++ * ALTERNATIVELY, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") as published by the Free Software ++ * Foundation, either version 2 of that License or (at your option) any ++ * later version. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS ++ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ++ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR ++ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ++ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, ++ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR ++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF ++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++ ++/* ++ * Gadget Zero only needs two bulk endpoints, and is an example of how you ++ * can write a hardware-agnostic gadget driver running inside a USB device. ++ * ++ * Hardware details are visible (see CONFIG_USB_ZERO_* below) but don't ++ * affect most of the driver. ++ * ++ * Use it with the Linux host/master side "usbtest" driver to get a basic ++ * functional test of your device-side usb stack, or with "usb-skeleton". ++ * ++ * It supports two similar configurations. One sinks whatever the usb host ++ * writes, and in return sources zeroes. The other loops whatever the host ++ * writes back, so the host can read it. Module options include: ++ * ++ * buflen=N default N=4096, buffer size used ++ * qlen=N default N=32, how many buffers in the loopback queue ++ * loopdefault default false, list loopback config first ++ * ++ * Many drivers will only have one configuration, letting them be much ++ * simpler if they also don't support high speed operation (like this ++ * driver does). ++ */ ++ ++#define DEBUG 1 ++// #define VERBOSE ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include "gadget_chips.h" ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++#define DRIVER_VERSION "St Patrick's Day 2004" ++ ++static const char shortname [] = "zero"; ++static const char longname [] = "Gadget Zero"; ++ ++static const char source_sink [] = "source and sink data"; ++static const char loopback [] = "loop input to output"; ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* ++ * driver assumes self-powered hardware, and ++ * has no way for users to trigger remote wakeup. ++ * ++ * this version autoconfigures as much as possible, ++ * which is reasonable for most "bulk-only" drivers. ++ */ ++static const char *EP_IN_NAME; /* source */ ++static const char *EP_OUT_NAME; /* sink */ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* big enough to hold our biggest descriptor */ ++#define USB_BUFSIZ 256 ++ ++struct zero_dev { ++ spinlock_t lock; ++ struct usb_gadget *gadget; ++ struct usb_request *req; /* for control responses */ ++ ++ /* when configured, we have one of two configs: ++ * - source data (in to host) and sink it (out from host) ++ * - or loop it back (out from host back in to host) ++ */ ++ u8 config; ++ struct usb_ep *in_ep, *out_ep; ++ ++ /* autoresume timer */ ++ struct timer_list resume; ++}; ++ ++#define xprintk(d,level,fmt,args...) \ ++ printk(level "%s %s: " fmt , shortname , (d)->gadget->dev.bus_id , \ ++ ## args) ++ ++#ifdef DEBUG ++#define DBG(dev,fmt,args...) \ ++ xprintk(dev , KERN_DEBUG , fmt , ## args) ++#else ++#define DBG(dev,fmt,args...) \ ++ do { } while (0) ++#endif /* DEBUG */ ++ ++#ifdef VERBOSE ++#define VDBG DBG ++#else ++#define VDBG(dev,fmt,args...) \ ++ do { } while (0) ++#endif /* VERBOSE */ ++ ++#define ERROR(dev,fmt,args...) \ ++ xprintk(dev , KERN_ERR , fmt , ## args) ++#define WARN(dev,fmt,args...) \ ++ xprintk(dev , KERN_WARNING , fmt , ## args) ++#define INFO(dev,fmt,args...) \ ++ xprintk(dev , KERN_INFO , fmt , ## args) ++ ++/*-------------------------------------------------------------------------*/ ++ ++static unsigned buflen = 4096; ++static unsigned qlen = 32; ++static unsigned pattern = 0; ++ ++/* ++ * Normally the "loopback" configuration is second (index 1) so ++ * it's not the default. Here's where to change that order, to ++ * work better with hosts where config changes are problematic. ++ * Or controllers (like superh) that only support one config. ++ */ ++static int loopdefault = 0; ++ ++ ++MODULE_PARM (buflen, "i"); ++MODULE_PARM_DESC (buflen, "size of i/o buffers"); ++ ++MODULE_PARM (qlen, "i"); ++MODULE_PARM_DESC (qlen, "depth of loopback buffering"); ++ ++MODULE_PARM (pattern, "i"); ++MODULE_PARM_DESC (pattern, "0 for default all-zeroes, 1 for mod63"); ++ ++MODULE_PARM (loopdefault, "b"); ++MODULE_PARM_DESC (loopdefault, "true to have default config be loopback"); ++ ++/* ++ * if it's nonzero, autoresume says how many seconds to wait ++ * before trying to wake up the host after suspend. ++ */ ++static unsigned autoresume = 0; ++MODULE_PARM (autoresume, "i"); ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* Thanks to NetChip Technologies for donating this product ID. ++ * ++ * DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! ++ * Instead: allocate your own, using normal USB-IF procedures. ++ */ ++#ifndef CONFIG_USB_ZERO_HNPTEST ++#define DRIVER_VENDOR_NUM 0x0525 /* NetChip */ ++#define DRIVER_PRODUCT_NUM 0xa4a0 /* Linux-USB "Gadget Zero" */ ++#else ++#define DRIVER_VENDOR_NUM 0x1a0a /* OTG test device IDs */ ++#define DRIVER_PRODUCT_NUM 0xbadd ++#endif ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* ++ * DESCRIPTORS ... most are static, but strings and (full) ++ * configuration descriptors are built on demand. ++ */ ++ ++#define STRING_MANUFACTURER 25 ++#define STRING_PRODUCT 42 ++#define STRING_SERIAL 101 ++#define STRING_SOURCE_SINK 250 ++#define STRING_LOOPBACK 251 ++ ++/* ++ * This device advertises two configurations; these numbers work ++ * on a pxa250 as well as more flexible hardware. ++ */ ++#define CONFIG_SOURCE_SINK 3 ++#define CONFIG_LOOPBACK 2 ++ ++static struct usb_device_descriptor ++device_desc = { ++ .bLength = sizeof device_desc, ++ .bDescriptorType = USB_DT_DEVICE, ++ ++ .bcdUSB = __constant_cpu_to_le16 (0x0200), ++ .bDeviceClass = USB_CLASS_VENDOR_SPEC, ++ ++ .idVendor = __constant_cpu_to_le16 (DRIVER_VENDOR_NUM), ++ .idProduct = __constant_cpu_to_le16 (DRIVER_PRODUCT_NUM), ++ .iManufacturer = STRING_MANUFACTURER, ++ .iProduct = STRING_PRODUCT, ++ .iSerialNumber = STRING_SERIAL, ++ .bNumConfigurations = 2, ++}; ++ ++static struct usb_config_descriptor ++source_sink_config = { ++ .bLength = sizeof source_sink_config, ++ .bDescriptorType = USB_DT_CONFIG, ++ ++ /* compute wTotalLength on the fly */ ++ .bNumInterfaces = 1, ++ .bConfigurationValue = CONFIG_SOURCE_SINK, ++ .iConfiguration = STRING_SOURCE_SINK, ++ .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, ++ .bMaxPower = 1, /* self-powered */ ++}; ++ ++static struct usb_config_descriptor ++loopback_config = { ++ .bLength = sizeof loopback_config, ++ .bDescriptorType = USB_DT_CONFIG, ++ ++ /* compute wTotalLength on the fly */ ++ .bNumInterfaces = 1, ++ .bConfigurationValue = CONFIG_LOOPBACK, ++ .iConfiguration = STRING_LOOPBACK, ++ .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, ++ .bMaxPower = 1, /* self-powered */ ++}; ++ ++static struct usb_otg_descriptor ++otg_descriptor = { ++ .bLength = sizeof otg_descriptor, ++ .bDescriptorType = USB_DT_OTG, ++ ++ .bmAttributes = USB_OTG_SRP, ++}; ++ ++/* one interface in each configuration */ ++ ++static const struct usb_interface_descriptor ++source_sink_intf = { ++ .bLength = sizeof source_sink_intf, ++ .bDescriptorType = USB_DT_INTERFACE, ++ ++ .bNumEndpoints = 2, ++ .bInterfaceClass = USB_CLASS_VENDOR_SPEC, ++ .iInterface = STRING_SOURCE_SINK, ++}; ++ ++static const struct usb_interface_descriptor ++loopback_intf = { ++ .bLength = sizeof loopback_intf, ++ .bDescriptorType = USB_DT_INTERFACE, ++ ++ .bNumEndpoints = 2, ++ .bInterfaceClass = USB_CLASS_VENDOR_SPEC, ++ .iInterface = STRING_LOOPBACK, ++}; ++ ++/* two full speed bulk endpoints; their use is config-dependent */ ++ ++static struct usb_endpoint_descriptor ++fs_source_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ .bEndpointAddress = USB_DIR_IN, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++}; ++ ++static struct usb_endpoint_descriptor ++fs_sink_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ .bEndpointAddress = USB_DIR_OUT, ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++}; ++ ++static const struct usb_descriptor_header *fs_source_sink_function [] = { ++ (struct usb_descriptor_header *) &otg_descriptor, ++ (struct usb_descriptor_header *) &source_sink_intf, ++ (struct usb_descriptor_header *) &fs_sink_desc, ++ (struct usb_descriptor_header *) &fs_source_desc, ++ NULL, ++}; ++ ++static const struct usb_descriptor_header *fs_loopback_function [] = { ++ (struct usb_descriptor_header *) &otg_descriptor, ++ (struct usb_descriptor_header *) &loopback_intf, ++ (struct usb_descriptor_header *) &fs_sink_desc, ++ (struct usb_descriptor_header *) &fs_source_desc, ++ NULL, ++}; ++ ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ ++/* ++ * usb 2.0 devices need to expose both high speed and full speed ++ * descriptors, unless they only run at full speed. ++ * ++ * that means alternate endpoint descriptors (bigger packets) ++ * and a "device qualifier" ... plus more construction options ++ * for the config descriptor. ++ */ ++ ++static struct usb_endpoint_descriptor ++hs_source_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ .wMaxPacketSize = __constant_cpu_to_le16 (512), ++}; ++ ++static struct usb_endpoint_descriptor ++hs_sink_desc = { ++ .bLength = USB_DT_ENDPOINT_SIZE, ++ .bDescriptorType = USB_DT_ENDPOINT, ++ ++ .bmAttributes = USB_ENDPOINT_XFER_BULK, ++ .wMaxPacketSize = __constant_cpu_to_le16 (512), ++}; ++ ++static struct usb_qualifier_descriptor ++dev_qualifier = { ++ .bLength = sizeof dev_qualifier, ++ .bDescriptorType = USB_DT_DEVICE_QUALIFIER, ++ ++ .bcdUSB = __constant_cpu_to_le16 (0x0200), ++ .bDeviceClass = USB_CLASS_VENDOR_SPEC, ++ ++ .bNumConfigurations = 2, ++}; ++ ++static const struct usb_descriptor_header *hs_source_sink_function [] = { ++ (struct usb_descriptor_header *) &otg_descriptor, ++ (struct usb_descriptor_header *) &source_sink_intf, ++ (struct usb_descriptor_header *) &hs_source_desc, ++ (struct usb_descriptor_header *) &hs_sink_desc, ++ NULL, ++}; ++ ++static const struct usb_descriptor_header *hs_loopback_function [] = { ++ (struct usb_descriptor_header *) &otg_descriptor, ++ (struct usb_descriptor_header *) &loopback_intf, ++ (struct usb_descriptor_header *) &hs_source_desc, ++ (struct usb_descriptor_header *) &hs_sink_desc, ++ NULL, ++}; ++ ++/* maxpacket and other transfer characteristics vary by speed. */ ++#define ep_desc(g,hs,fs) (((g)->speed==USB_SPEED_HIGH)?(hs):(fs)) ++ ++#else ++ ++/* if there's no high speed support, maxpacket doesn't change. */ ++#define ep_desc(g,hs,fs) fs ++ ++#endif /* !CONFIG_USB_GADGET_DUALSPEED */ ++ ++static char manufacturer [50]; ++static char serial [40]; ++ ++/* static strings, in UTF-8 */ ++static struct usb_string strings [] = { ++ { STRING_MANUFACTURER, manufacturer, }, ++ { STRING_PRODUCT, longname, }, ++ { STRING_SERIAL, serial, }, ++ { STRING_LOOPBACK, loopback, }, ++ { STRING_SOURCE_SINK, source_sink, }, ++ { } /* end of list */ ++}; ++ ++static struct usb_gadget_strings stringtab = { ++ .language = 0x0409, /* en-us */ ++ .strings = strings, ++}; ++ ++/* ++ * config descriptors are also handcrafted. these must agree with code ++ * that sets configurations, and with code managing interfaces and their ++ * altsettings. other complexity may come from: ++ * ++ * - high speed support, including "other speed config" rules ++ * - multiple configurations ++ * - interfaces with alternate settings ++ * - embedded class or vendor-specific descriptors ++ * ++ * this handles high speed, and has a second config that could as easily ++ * have been an alternate interface setting (on most hardware). ++ * ++ * NOTE: to demonstrate (and test) more USB capabilities, this driver ++ * should include an altsetting to test interrupt transfers, including ++ * high bandwidth modes at high speed. (Maybe work like Intel's test ++ * device?) ++ */ ++static int ++config_buf (struct usb_gadget *gadget, ++ u8 *buf, u8 type, unsigned index) ++{ ++ int is_source_sink; ++ int len; ++ const struct usb_descriptor_header **function; ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ int hs = (gadget->speed == USB_SPEED_HIGH); ++#endif ++ ++ /* two configurations will always be index 0 and index 1 */ ++ if (index > 1) ++ return -EINVAL; ++ is_source_sink = loopdefault ? (index == 1) : (index == 0); ++ ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ if (type == USB_DT_OTHER_SPEED_CONFIG) ++ hs = !hs; ++ if (hs) ++ function = is_source_sink ++ ? hs_source_sink_function ++ : hs_loopback_function; ++ else ++#endif ++ function = is_source_sink ++ ? fs_source_sink_function ++ : fs_loopback_function; ++ ++ /* for now, don't advertise srp-only devices */ ++ if (!gadget->is_otg) ++ function++; ++ ++ len = usb_gadget_config_buf (is_source_sink ++ ? &source_sink_config ++ : &loopback_config, ++ buf, USB_BUFSIZ, function); ++ if (len < 0) ++ return len; ++ ((struct usb_config_descriptor *) buf)->bDescriptorType = type; ++ return len; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static struct usb_request * ++alloc_ep_req (struct usb_ep *ep, unsigned length) ++{ ++ struct usb_request *req; ++ ++ req = usb_ep_alloc_request (ep, GFP_ATOMIC); ++ if (req) { ++ req->length = length; ++ req->buf = usb_ep_alloc_buffer (ep, length, ++ &req->dma, GFP_ATOMIC); ++ if (!req->buf) { ++ usb_ep_free_request (ep, req); ++ req = NULL; ++ } ++ } ++ return req; ++} ++ ++static void free_ep_req (struct usb_ep *ep, struct usb_request *req) ++{ ++ if (req->buf) ++ usb_ep_free_buffer (ep, req->buf, req->dma, req->length); ++ usb_ep_free_request (ep, req); ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* optionally require specific source/sink data patterns */ ++ ++static int ++check_read_data ( ++ struct zero_dev *dev, ++ struct usb_ep *ep, ++ struct usb_request *req ++) ++{ ++ unsigned i; ++ u8 *buf = req->buf; ++ ++ for (i = 0; i < req->actual; i++, buf++) { ++ switch (pattern) { ++ /* all-zeroes has no synchronization issues */ ++ case 0: ++ if (*buf == 0) ++ continue; ++ break; ++ /* mod63 stays in sync with short-terminated transfers, ++ * or otherwise when host and gadget agree on how large ++ * each usb transfer request should be. resync is done ++ * with set_interface or set_config. ++ */ ++ case 1: ++ if (*buf == (u8)(i % 63)) ++ continue; ++ break; ++ } ++ ERROR (dev, "bad OUT byte, buf [%d] = %d\n", i, *buf); ++ usb_ep_set_halt (ep); ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++static void ++reinit_write_data ( ++ struct zero_dev *dev, ++ struct usb_ep *ep, ++ struct usb_request *req ++) ++{ ++ unsigned i; ++ u8 *buf = req->buf; ++ ++ switch (pattern) { ++ case 0: ++ memset (req->buf, 0, req->length); ++ break; ++ case 1: ++ for (i = 0; i < req->length; i++) ++ *buf++ = (u8) (i % 63); ++ break; ++ } ++} ++ ++/* if there is only one request in the queue, there'll always be an ++ * irq delay between end of one request and start of the next. ++ * that prevents using hardware dma queues. ++ */ ++static void source_sink_complete (struct usb_ep *ep, struct usb_request *req) ++{ ++ struct zero_dev *dev = ep->driver_data; ++ int status = req->status; ++ ++ switch (status) { ++ ++ case 0: /* normal completion? */ ++ if (ep == dev->out_ep) ++ check_read_data (dev, ep, req); ++ else ++ reinit_write_data (dev, ep, req); ++ break; ++ ++ /* this endpoint is normally active while we're configured */ ++ case -ECONNABORTED: /* hardware forced ep reset */ ++ case -ECONNRESET: /* request dequeued */ ++ case -ESHUTDOWN: /* disconnect from host */ ++ VDBG (dev, "%s gone (%d), %d/%d\n", ep->name, status, ++ req->actual, req->length); ++ if (ep == dev->out_ep) ++ check_read_data (dev, ep, req); ++ free_ep_req (ep, req); ++ return; ++ ++ case -EOVERFLOW: /* buffer overrun on read means that ++ * we didn't provide a big enough ++ * buffer. ++ */ ++ default: ++#if 1 ++ DBG (dev, "%s complete --> %d, %d/%d\n", ep->name, ++ status, req->actual, req->length); ++#endif ++ case -EREMOTEIO: /* short read */ ++ break; ++ } ++ ++ status = usb_ep_queue (ep, req, GFP_ATOMIC); ++ if (status) { ++ ERROR (dev, "kill %s: resubmit %d bytes --> %d\n", ++ ep->name, req->length, status); ++ usb_ep_set_halt (ep); ++ /* FIXME recover later ... somehow */ ++ } ++} ++ ++static struct usb_request * ++source_sink_start_ep (struct usb_ep *ep, int gfp_flags) ++{ ++ struct usb_request *req; ++ int status; ++ ++ req = alloc_ep_req (ep, buflen); ++ if (!req) ++ return NULL; ++ ++ memset (req->buf, 0, req->length); ++ req->complete = source_sink_complete; ++ ++ if (strcmp (ep->name, EP_IN_NAME) == 0) ++ reinit_write_data (ep->driver_data, ep, req); ++ ++ status = usb_ep_queue (ep, req, gfp_flags); ++ if (status) { ++ struct zero_dev *dev = ep->driver_data; ++ ++ ERROR (dev, "start %s --> %d\n", ep->name, status); ++ free_ep_req (ep, req); ++ req = NULL; ++ } ++ ++ return req; ++} ++ ++static int ++set_source_sink_config (struct zero_dev *dev, int gfp_flags) ++{ ++ int result = 0; ++ struct usb_ep *ep; ++ struct usb_gadget *gadget = dev->gadget; ++ ++ gadget_for_each_ep (ep, gadget) { ++ const struct usb_endpoint_descriptor *d; ++ ++ /* one endpoint writes (sources) zeroes in (to the host) */ ++ if (strcmp (ep->name, EP_IN_NAME) == 0) { ++ d = ep_desc (gadget, &hs_source_desc, &fs_source_desc); ++ result = usb_ep_enable (ep, d); ++ if (result == 0) { ++ ep->driver_data = dev; ++ if (source_sink_start_ep (ep, gfp_flags) != 0) { ++ dev->in_ep = ep; ++ continue; ++ } ++ usb_ep_disable (ep); ++ result = -EIO; ++ } ++ ++ /* one endpoint reads (sinks) anything out (from the host) */ ++ } else if (strcmp (ep->name, EP_OUT_NAME) == 0) { ++ d = ep_desc (gadget, &hs_sink_desc, &fs_sink_desc); ++ result = usb_ep_enable (ep, d); ++ if (result == 0) { ++ ep->driver_data = dev; ++ if (source_sink_start_ep (ep, gfp_flags) != 0) { ++ dev->out_ep = ep; ++ continue; ++ } ++ usb_ep_disable (ep); ++ result = -EIO; ++ } ++ ++ /* ignore any other endpoints */ ++ } else ++ continue; ++ ++ /* stop on error */ ++ ERROR (dev, "can't start %s, result %d\n", ep->name, result); ++ break; ++ } ++ if (result == 0) ++ DBG (dev, "buflen %d\n", buflen); ++ ++ /* caller is responsible for cleanup on error */ ++ return result; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void loopback_complete (struct usb_ep *ep, struct usb_request *req) ++{ ++ struct zero_dev *dev = ep->driver_data; ++ int status = req->status; ++ ++ switch (status) { ++ ++ case 0: /* normal completion? */ ++ if (ep == dev->out_ep) { ++ /* loop this OUT packet back IN to the host */ ++ req->zero = (req->actual < req->length); ++ req->length = req->actual; ++ status = usb_ep_queue (dev->in_ep, req, GFP_ATOMIC); ++ if (status == 0) ++ return; ++ ++ /* "should never get here" */ ++ ERROR (dev, "can't loop %s to %s: %d\n", ++ ep->name, dev->in_ep->name, ++ status); ++ } ++ ++ /* queue the buffer for some later OUT packet */ ++ req->length = buflen; ++ status = usb_ep_queue (dev->out_ep, req, GFP_ATOMIC); ++ if (status == 0) ++ return; ++ ++ /* "should never get here" */ ++ /* FALLTHROUGH */ ++ ++ default: ++ ERROR (dev, "%s loop complete --> %d, %d/%d\n", ep->name, ++ status, req->actual, req->length); ++ /* FALLTHROUGH */ ++ ++ /* NOTE: since this driver doesn't maintain an explicit record ++ * of requests it submitted (just maintains qlen count), we ++ * rely on the hardware driver to clean up on disconnect or ++ * endpoint disable. ++ */ ++ case -ECONNABORTED: /* hardware forced ep reset */ ++ case -ECONNRESET: /* request dequeued */ ++ case -ESHUTDOWN: /* disconnect from host */ ++ free_ep_req (ep, req); ++ return; ++ } ++} ++ ++static int ++set_loopback_config (struct zero_dev *dev, int gfp_flags) ++{ ++ int result = 0; ++ struct usb_ep *ep; ++ struct usb_gadget *gadget = dev->gadget; ++ ++ gadget_for_each_ep (ep, gadget) { ++ const struct usb_endpoint_descriptor *d; ++ ++ /* one endpoint writes data back IN to the host */ ++ if (strcmp (ep->name, EP_IN_NAME) == 0) { ++ d = ep_desc (gadget, &hs_source_desc, &fs_source_desc); ++ result = usb_ep_enable (ep, d); ++ if (result == 0) { ++ ep->driver_data = dev; ++ dev->in_ep = ep; ++ continue; ++ } ++ ++ /* one endpoint just reads OUT packets */ ++ } else if (strcmp (ep->name, EP_OUT_NAME) == 0) { ++ d = ep_desc (gadget, &hs_sink_desc, &fs_sink_desc); ++ result = usb_ep_enable (ep, d); ++ if (result == 0) { ++ ep->driver_data = dev; ++ dev->out_ep = ep; ++ continue; ++ } ++ ++ /* ignore any other endpoints */ ++ } else ++ continue; ++ ++ /* stop on error */ ++ ERROR (dev, "can't enable %s, result %d\n", ep->name, result); ++ break; ++ } ++ ++ /* allocate a bunch of read buffers and queue them all at once. ++ * we buffer at most 'qlen' transfers; fewer if any need more ++ * than 'buflen' bytes each. ++ */ ++ if (result == 0) { ++ struct usb_request *req; ++ unsigned i; ++ ++ ep = dev->out_ep; ++ for (i = 0; i < qlen && result == 0; i++) { ++ req = alloc_ep_req (ep, buflen); ++ if (req) { ++ req->complete = loopback_complete; ++ result = usb_ep_queue (ep, req, GFP_ATOMIC); ++ if (result) ++ DBG (dev, "%s queue req --> %d\n", ++ ep->name, result); ++ } else ++ result = -ENOMEM; ++ } ++ } ++ if (result == 0) ++ DBG (dev, "qlen %d, buflen %d\n", qlen, buflen); ++ ++ /* caller is responsible for cleanup on error */ ++ return result; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void zero_reset_config (struct zero_dev *dev) ++{ ++ if (dev->config == 0) ++ return; ++ ++ DBG (dev, "reset config\n"); ++ ++ /* just disable endpoints, forcing completion of pending i/o. ++ * all our completion handlers free their requests in this case. ++ */ ++ if (dev->in_ep) { ++ usb_ep_disable (dev->in_ep); ++ dev->in_ep = NULL; ++ } ++ if (dev->out_ep) { ++ usb_ep_disable (dev->out_ep); ++ dev->out_ep = NULL; ++ } ++ dev->config = 0; ++ del_timer (&dev->resume); ++} ++ ++/* change our operational config. this code must agree with the code ++ * that returns config descriptors, and altsetting code. ++ * ++ * it's also responsible for power management interactions. some ++ * configurations might not work with our current power sources. ++ * ++ * note that some device controller hardware will constrain what this ++ * code can do, perhaps by disallowing more than one configuration or ++ * by limiting configuration choices (like the pxa2xx). ++ */ ++static int ++zero_set_config (struct zero_dev *dev, unsigned number, int gfp_flags) ++{ ++ int result = 0; ++ struct usb_gadget *gadget = dev->gadget; ++ ++ if (number == dev->config) ++ return 0; ++ ++ if (gadget_is_sa1100 (gadget) && dev->config) { ++ /* tx fifo is full, but we can't clear it...*/ ++ INFO (dev, "can't change configurations\n"); ++ return -ESPIPE; ++ } ++ zero_reset_config (dev); ++ ++ switch (number) { ++ case CONFIG_SOURCE_SINK: ++ result = set_source_sink_config (dev, gfp_flags); ++ break; ++ case CONFIG_LOOPBACK: ++ result = set_loopback_config (dev, gfp_flags); ++ break; ++ default: ++ result = -EINVAL; ++ /* FALL THROUGH */ ++ case 0: ++ return result; ++ } ++ ++ if (!result && (!dev->in_ep || !dev->out_ep)) ++ result = -ENODEV; ++ if (result) ++ zero_reset_config (dev); ++ else { ++ char *speed; ++ ++ switch (gadget->speed) { ++ case USB_SPEED_LOW: speed = "low"; break; ++ case USB_SPEED_FULL: speed = "full"; break; ++ case USB_SPEED_HIGH: speed = "high"; break; ++ default: speed = "?"; break; ++ } ++ ++ dev->config = number; ++ INFO (dev, "%s speed config #%d: %s\n", speed, number, ++ (number == CONFIG_SOURCE_SINK) ++ ? source_sink : loopback); ++ } ++ return result; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void zero_setup_complete (struct usb_ep *ep, struct usb_request *req) ++{ ++ if (req->status || req->actual != req->length) ++ DBG ((struct zero_dev *) ep->driver_data, ++ "setup complete --> %d, %d/%d\n", ++ req->status, req->actual, req->length); ++} ++ ++/* ++ * The setup() callback implements all the ep0 functionality that's ++ * not handled lower down, in hardware or the hardware driver (like ++ * device and endpoint feature flags, and their status). It's all ++ * housekeeping for the gadget function we're implementing. Most of ++ * the work is in config-specific setup. ++ */ ++static int ++zero_setup (struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) ++{ ++ struct zero_dev *dev = get_gadget_data (gadget); ++ struct usb_request *req = dev->req; ++ int value = -EOPNOTSUPP; ++ ++ /* usually this stores reply data in the pre-allocated ep0 buffer, ++ * but config change events will reconfigure hardware. ++ */ ++ req->zero = 0; ++ switch (ctrl->bRequest) { ++ ++ case USB_REQ_GET_DESCRIPTOR: ++ if (ctrl->bRequestType != USB_DIR_IN) ++ goto unknown; ++ switch (ctrl->wValue >> 8) { ++ ++ case USB_DT_DEVICE: ++ value = min (ctrl->wLength, (u16) sizeof device_desc); ++ memcpy (req->buf, &device_desc, value); ++ break; ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ case USB_DT_DEVICE_QUALIFIER: ++ if (!gadget->is_dualspeed) ++ break; ++ value = min (ctrl->wLength, (u16) sizeof dev_qualifier); ++ memcpy (req->buf, &dev_qualifier, value); ++ break; ++ ++ case USB_DT_OTHER_SPEED_CONFIG: ++ if (!gadget->is_dualspeed) ++ break; ++ // FALLTHROUGH ++#endif /* CONFIG_USB_GADGET_DUALSPEED */ ++ case USB_DT_CONFIG: ++ value = config_buf (gadget, req->buf, ++ ctrl->wValue >> 8, ++ ctrl->wValue & 0xff); ++ if (value >= 0) ++ value = min (ctrl->wLength, (u16) value); ++ break; ++ ++ case USB_DT_STRING: ++ /* wIndex == language code. ++ * this driver only handles one language, you can ++ * add string tables for other languages, using ++ * any UTF-8 characters ++ */ ++ value = usb_gadget_get_string (&stringtab, ++ ctrl->wValue & 0xff, req->buf); ++ if (value >= 0) ++ value = min (ctrl->wLength, (u16) value); ++ break; ++ } ++ break; ++ ++ /* currently two configs, two speeds */ ++ case USB_REQ_SET_CONFIGURATION: ++ if (ctrl->bRequestType != 0) ++ goto unknown; ++ if (gadget->a_hnp_support) ++ DBG (dev, "HNP available\n"); ++ else if (gadget->a_alt_hnp_support) ++ DBG (dev, "HNP needs a different root port\n"); ++ else ++ VDBG (dev, "HNP inactive\n"); ++ spin_lock (&dev->lock); ++ value = zero_set_config (dev, ctrl->wValue, GFP_ATOMIC); ++ spin_unlock (&dev->lock); ++ break; ++ case USB_REQ_GET_CONFIGURATION: ++ if (ctrl->bRequestType != USB_DIR_IN) ++ goto unknown; ++ *(u8 *)req->buf = dev->config; ++ value = min (ctrl->wLength, (u16) 1); ++ break; ++ ++ /* until we add altsetting support, or other interfaces, ++ * only 0/0 are possible. pxa2xx only supports 0/0 (poorly) ++ * and already killed pending endpoint I/O. ++ */ ++ case USB_REQ_SET_INTERFACE: ++ if (ctrl->bRequestType != USB_RECIP_INTERFACE) ++ goto unknown; ++ spin_lock (&dev->lock); ++ if (dev->config && ctrl->wIndex == 0 && ctrl->wValue == 0) { ++ u8 config = dev->config; ++ ++ /* resets interface configuration, forgets about ++ * previous transaction state (queued bufs, etc) ++ * and re-inits endpoint state (toggle etc) ++ * no response queued, just zero status == success. ++ * if we had more than one interface we couldn't ++ * use this "reset the config" shortcut. ++ */ ++ zero_reset_config (dev); ++ zero_set_config (dev, config, GFP_ATOMIC); ++ value = 0; ++ } ++ spin_unlock (&dev->lock); ++ break; ++ case USB_REQ_GET_INTERFACE: ++ if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE)) ++ goto unknown; ++ if (!dev->config) ++ break; ++ if (ctrl->wIndex != 0) { ++ value = -EDOM; ++ break; ++ } ++ *(u8 *)req->buf = 0; ++ value = min (ctrl->wLength, (u16) 1); ++ break; ++ ++ /* ++ * These are the same vendor-specific requests supported by ++ * Intel's USB 2.0 compliance test devices. We exceed that ++ * device spec by allowing multiple-packet requests. ++ */ ++ case 0x5b: /* control WRITE test -- fill the buffer */ ++ if (ctrl->bRequestType != (USB_DIR_OUT|USB_TYPE_VENDOR)) ++ goto unknown; ++ if (ctrl->wValue || ctrl->wIndex) ++ break; ++ /* just read that many bytes into the buffer */ ++ if (ctrl->wLength > USB_BUFSIZ) ++ break; ++ value = ctrl->wLength; ++ break; ++ case 0x5c: /* control READ test -- return the buffer */ ++ if (ctrl->bRequestType != (USB_DIR_IN|USB_TYPE_VENDOR)) ++ goto unknown; ++ if (ctrl->wValue || ctrl->wIndex) ++ break; ++ /* expect those bytes are still in the buffer; send back */ ++ if (ctrl->wLength > USB_BUFSIZ ++ || ctrl->wLength != req->length) ++ break; ++ value = ctrl->wLength; ++ break; ++ ++ default: ++unknown: ++ VDBG (dev, ++ "unknown control req%02x.%02x v%04x i%04x l%d\n", ++ ctrl->bRequestType, ctrl->bRequest, ++ ctrl->wValue, ctrl->wIndex, ctrl->wLength); ++ } ++ ++ /* respond with data transfer before status phase? */ ++ if (value >= 0) { ++ req->length = value; ++ req->zero = value < ctrl->wLength ++ && (value % gadget->ep0->maxpacket) == 0; ++ value = usb_ep_queue (gadget->ep0, req, GFP_ATOMIC); ++ if (value < 0) { ++ DBG (dev, "ep_queue --> %d\n", value); ++ req->status = 0; ++ zero_setup_complete (gadget->ep0, req); ++ } ++ } ++ ++ /* device either stalls (value < 0) or reports success */ ++ return value; ++} ++ ++static void ++zero_disconnect (struct usb_gadget *gadget) ++{ ++ struct zero_dev *dev = get_gadget_data (gadget); ++ unsigned long flags; ++ ++ spin_lock_irqsave (&dev->lock, flags); ++ zero_reset_config (dev); ++ ++ /* a more significant application might have some non-usb ++ * activities to quiesce here, saving resources like power ++ * or pushing the notification up a network stack. ++ */ ++ spin_unlock_irqrestore (&dev->lock, flags); ++ ++ /* next we may get setup() calls to enumerate new connections; ++ * or an unbind() during shutdown (including removing module). ++ */ ++} ++ ++static void ++zero_autoresume (unsigned long _dev) ++{ ++ struct zero_dev *dev = (struct zero_dev *) _dev; ++ int status; ++ ++ /* normally the host would be woken up for something ++ * more significant than just a timer firing... ++ */ ++ if (dev->gadget->speed != USB_SPEED_UNKNOWN) { ++ status = usb_gadget_wakeup (dev->gadget); ++ DBG (dev, "wakeup --> %d\n", status); ++ } ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void ++zero_unbind (struct usb_gadget *gadget) ++{ ++ struct zero_dev *dev = get_gadget_data (gadget); ++ ++ DBG (dev, "unbind\n"); ++ ++ /* we've already been disconnected ... no i/o is active */ ++ if (dev->req) ++ free_ep_req (gadget->ep0, dev->req); ++ del_timer_sync (&dev->resume); ++ kfree (dev); ++ set_gadget_data (gadget, NULL); ++} ++ ++static int ++zero_bind (struct usb_gadget *gadget) ++{ ++ struct zero_dev *dev; ++ struct usb_ep *ep; ++ ++ /* Bulk-only drivers like this one SHOULD be able to ++ * autoconfigure on any sane usb controller driver, ++ * but there may also be important quirks to address. ++ */ ++ usb_ep_autoconfig_reset (gadget); ++ ep = usb_ep_autoconfig (gadget, &fs_source_desc); ++ if (!ep) { ++autoconf_fail: ++ printk (KERN_ERR "%s: can't autoconfigure on %s\n", ++ shortname, gadget->name); ++ return -ENODEV; ++ } ++ EP_IN_NAME = ep->name; ++ ep->driver_data = ep; /* claim */ ++ ++ ep = usb_ep_autoconfig (gadget, &fs_sink_desc); ++ if (!ep) ++ goto autoconf_fail; ++ EP_OUT_NAME = ep->name; ++ ep->driver_data = ep; /* claim */ ++ ++ ++ /* ++ * DRIVER POLICY CHOICE: you may want to do this differently. ++ * One thing to avoid is reusing a bcdDevice revision code ++ * with different host-visible configurations or behavior ++ * restrictions -- using ep1in/ep2out vs ep1out/ep3in, etc ++ */ ++ if (gadget_is_net2280 (gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0201); ++ } else if (gadget_is_pxa (gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0203); ++#if 0 ++ } else if (gadget_is_sh(gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0204); ++ /* SH has only one configuration; see "loopdefault" */ ++ device_desc.bNumConfigurations = 1; ++ /* FIXME make 1 == default.bConfigurationValue */ ++#endif ++ } else if (gadget_is_sa1100 (gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0205); ++ } else if (gadget_is_goku (gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0206); ++ } else if (gadget_is_mq11xx (gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0207); ++ } else if (gadget_is_omap (gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0208); ++ } else if (gadget_is_lh7a40x(gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0209); ++ } else if (gadget_is_n9604(gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0210); ++ } else if (gadget_is_pxa27x(gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0211); ++ } else if (gadget_is_s3c2410(gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0212); ++ } else if (gadget_is_at91(gadget)) { ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x0213); ++ } else { ++ /* gadget zero is so simple (for now, no altsettings) that ++ * it SHOULD NOT have problems with bulk-capable hardware. ++ * so warn about unrcognized controllers, don't panic. ++ * ++ * things like configuration and altsetting numbering ++ * can need hardware-specific attention though. ++ */ ++ printk (KERN_WARNING "%s: controller '%s' not recognized\n", ++ shortname, gadget->name); ++ device_desc.bcdDevice = __constant_cpu_to_le16 (0x9999); ++ } ++ ++ ++ /* ok, we made sense of the hardware ... */ ++ dev = kmalloc (sizeof *dev, SLAB_KERNEL); ++ if (!dev) ++ return -ENOMEM; ++ memset (dev, 0, sizeof *dev); ++ spin_lock_init (&dev->lock); ++ dev->gadget = gadget; ++ set_gadget_data (gadget, dev); ++ ++ /* preallocate control response and buffer */ ++ dev->req = usb_ep_alloc_request (gadget->ep0, GFP_KERNEL); ++ if (!dev->req) ++ goto enomem; ++ dev->req->buf = usb_ep_alloc_buffer (gadget->ep0, USB_BUFSIZ, ++ &dev->req->dma, GFP_KERNEL); ++ if (!dev->req->buf) ++ goto enomem; ++ ++ dev->req->complete = zero_setup_complete; ++ ++ device_desc.bMaxPacketSize0 = gadget->ep0->maxpacket; ++ ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ /* assume ep0 uses the same value for both speeds ... */ ++ dev_qualifier.bMaxPacketSize0 = device_desc.bMaxPacketSize0; ++ ++ /* and that all endpoints are dual-speed */ ++ hs_source_desc.bEndpointAddress = fs_source_desc.bEndpointAddress; ++ hs_sink_desc.bEndpointAddress = fs_sink_desc.bEndpointAddress; ++#endif ++ ++ if (gadget->is_otg) { ++ otg_descriptor.bmAttributes |= USB_OTG_HNP, ++ source_sink_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP; ++ loopback_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP; ++ } ++ ++ if (gadget->is_otg) { ++ otg_descriptor.bmAttributes |= USB_OTG_HNP, ++ source_sink_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP; ++ loopback_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP; ++ } ++ ++ usb_gadget_set_selfpowered (gadget); ++ ++ init_timer (&dev->resume); ++ dev->resume.function = zero_autoresume; ++ dev->resume.data = (unsigned long) dev; ++ if (autoresume) { ++ source_sink_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP; ++ loopback_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP; ++ } ++ ++ gadget->ep0->driver_data = dev; ++ ++ INFO (dev, "%s, version: " DRIVER_VERSION "\n", longname); ++ INFO (dev, "using %s, OUT %s IN %s\n", gadget->name, ++ EP_OUT_NAME, EP_IN_NAME); ++ ++ snprintf (manufacturer, sizeof manufacturer, ++ UTS_SYSNAME " " UTS_RELEASE " with %s", ++ gadget->name); ++ ++ return 0; ++ ++enomem: ++ zero_unbind (gadget); ++ return -ENOMEM; ++} ++ ++/*-------------------------------------------------------------------------*/ ++ ++static void ++zero_suspend (struct usb_gadget *gadget) ++{ ++ struct zero_dev *dev = get_gadget_data (gadget); ++ ++ if (gadget->speed == USB_SPEED_UNKNOWN) ++ return; ++ ++ if (autoresume) { ++ mod_timer (&dev->resume, jiffies + (HZ * autoresume)); ++ DBG (dev, "suspend, wakeup in %d seconds\n", autoresume); ++ } else ++ DBG (dev, "suspend\n"); ++} ++ ++static void ++zero_resume (struct usb_gadget *gadget) ++{ ++ struct zero_dev *dev = get_gadget_data (gadget); ++ ++ DBG (dev, "resume\n"); ++ del_timer (&dev->resume); ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++static struct usb_gadget_driver zero_driver = { ++#ifdef CONFIG_USB_GADGET_DUALSPEED ++ .speed = USB_SPEED_HIGH, ++#else ++ .speed = USB_SPEED_FULL, ++#endif ++ .function = (char *) longname, ++ .bind = zero_bind, ++ .unbind = zero_unbind, ++ ++ .setup = zero_setup, ++ .disconnect = zero_disconnect, ++ ++ .suspend = zero_suspend, ++ .resume = zero_resume, ++ ++ .driver = { ++ .name = (char *) shortname, ++ // .shutdown = ... ++ // .suspend = ... ++ // .resume = ... ++ }, ++}; ++ ++MODULE_AUTHOR ("David Brownell"); ++MODULE_LICENSE ("Dual BSD/GPL"); ++ ++ ++static int __init init (void) ++{ ++ /* a real value would likely come through some id prom ++ * or module option. this one takes at least two packets. ++ */ ++ strncpy (serial, "0123456789.0123456789.0123456789", sizeof serial); ++ serial [sizeof serial - 1] = 0; ++ ++ return usb_gadget_register_driver (&zero_driver); ++} ++module_init (init); ++ ++static void __exit cleanup (void) ++{ ++ usb_gadget_unregister_driver (&zero_driver); ++} ++module_exit (cleanup); ++ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/include/linux/moduleparam.h kernel/include/linux/moduleparam.h +--- /tmp/kernel/include/linux/moduleparam.h 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/include/linux/moduleparam.h 2005-04-22 17:53:19.357552052 +0200 +@@ -0,0 +1,25 @@ ++#ifndef _LINUX_MODULE_PARAMS_H ++#define _LINUX_MODULE_PARAMS_H ++/* Macros for (very simple) module parameter compatibility with 2.6. */ ++#include ++ ++/* type is byte, short, ushort, int, uint, long, ulong, bool. (2.6 ++ has more, but they are not supported). perm is permissions when ++ it appears in sysfs: 0 means doens't appear, 0444 means read-only ++ by everyone, 0644 means changable dynamically by root, etc. name ++ must be in scope (unlike MODULE_PARM). ++*/ ++#define module_param(name, type, perm) \ ++ static inline void *__check_existence_##name(void) { return &name; } \ ++ MODULE_PARM(name, _MODULE_PARM_STRING_ ## type) ++ ++#define _MODULE_PARM_STRING_byte "b" ++#define _MODULE_PARM_STRING_short "h" ++#define _MODULE_PARM_STRING_ushort "h" ++#define _MODULE_PARM_STRING_int "i" ++#define _MODULE_PARM_STRING_uint "i" ++#define _MODULE_PARM_STRING_long "l" ++#define _MODULE_PARM_STRING_ulong "l" ++#define _MODULE_PARM_STRING_bool "i" ++ ++#endif /* _LINUX_MODULE_PARAM_TYPES_H */ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/include/linux/usb_cdc.h kernel/include/linux/usb_cdc.h +--- /tmp/kernel/include/linux/usb_cdc.h 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/include/linux/usb_cdc.h 2005-04-22 17:53:19.360551563 +0200 +@@ -0,0 +1,204 @@ ++/* ++ * USB Communications Device Class (CDC) definitions ++ * ++ * CDC says how to talk to lots of different types of network adapters, ++ * notably ethernet adapters and various modems. It's used mostly with ++ * firmware based USB peripherals. ++ * ++ * (C) Copyright 2005 by David Brownell ++ * All Rights Reserved. ++ * ++ * This software is licensed under the GNU GPL version 2. ++ */ ++ ++#define USB_CDC_SUBCLASS_ACM 0x02 ++#define USB_CDC_SUBCLASS_ETHERNET 0x06 ++#define USB_CDC_SUBCLASS_WHCM 0x08 ++#define USB_CDC_SUBCLASS_DMM 0x09 ++#define USB_CDC_SUBCLASS_MDLM 0x0a ++#define USB_CDC_SUBCLASS_OBEX 0x0b ++ ++#define USB_CDC_PROTO_NONE 0 ++ ++#define USB_CDC_ACM_PROTO_AT_V25TER 1 ++#define USB_CDC_ACM_PROTO_AT_PCCA101 2 ++#define USB_CDC_ACM_PROTO_AT_PCCA101_WAKE 3 ++#define USB_CDC_ACM_PROTO_AT_GSM 4 ++#define USB_CDC_ACM_PROTO_AT_3G 5 ++#define USB_CDC_ACM_PROTO_AT_CDMA 6 ++#define USB_CDC_ACM_PROTO_VENDOR 0xff ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* 2.6 "sparse" support for checking beyond what GCC does */ ++ ++#define __le16 u16 ++#define __le32 u32 ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* ++ * Class-Specific descriptors ... there are a couple dozen of them ++ */ ++ ++#define USB_CDC_HEADER_TYPE 0x00 /* header_desc */ ++#define USB_CDC_CALL_MANAGEMENT_TYPE 0x01 /* call_mgmt_descriptor */ ++#define USB_CDC_ACM_TYPE 0x02 /* acm_descriptor */ ++#define USB_CDC_UNION_TYPE 0x06 /* union_desc */ ++#define USB_CDC_COUNTRY_TYPE 0x07 ++#define USB_CDC_ETHERNET_TYPE 0x0f /* ether_desc */ ++#define USB_CDC_WHCM_TYPE 0x11 ++#define USB_CDC_MDLM_TYPE 0x12 /* mdlm_desc */ ++#define USB_CDC_MDLM_DETAIL_TYPE 0x13 /* mdlm_detail_desc */ ++#define USB_CDC_DMM_TYPE 0x14 ++#define USB_CDC_OBEX_TYPE 0x15 ++ ++/* "Header Functional Descriptor" from CDC spec 5.2.3.1 */ ++struct usb_cdc_header_desc { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ __u8 bDescriptorSubType; ++ ++ __le16 bcdCDC; ++} __attribute__ ((packed)); ++ ++/* "Call Management Descriptor" from CDC spec 5.2.3.2 */ ++struct usb_cdc_call_mgmt_descriptor { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ __u8 bDescriptorSubType; ++ ++ __u8 bmCapabilities; ++#define USB_CDC_CALL_MGMT_CAP_CALL_MGMT 0x01 ++#define USB_CDC_CALL_MGMT_CAP_DATA_INTF 0x02 ++ ++ __u8 bDataInterface; ++} __attribute__ ((packed)); ++ ++/* "Abstract Control Management Descriptor" from CDC spec 5.2.3.3 */ ++struct usb_cdc_acm_descriptor { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ __u8 bDescriptorSubType; ++ ++ __u8 bmCapabilities; ++} __attribute__ ((packed)); ++ ++/* "Union Functional Descriptor" from CDC spec 5.2.3.8 */ ++struct usb_cdc_union_desc { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ __u8 bDescriptorSubType; ++ ++ __u8 bMasterInterface0; ++ __u8 bSlaveInterface0; ++ /* ... and there could be other slave interfaces */ ++} __attribute__ ((packed)); ++ ++/* "Ethernet Networking Functional Descriptor" from CDC spec 5.2.3.16 */ ++struct usb_cdc_ether_desc { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ __u8 bDescriptorSubType; ++ ++ __u8 iMACAddress; ++ __le32 bmEthernetStatistics; ++ __le16 wMaxSegmentSize; ++ __le16 wNumberMCFilters; ++ __u8 bNumberPowerFilters; ++} __attribute__ ((packed)); ++ ++/* "MDLM Functional Descriptor" from CDC WMC spec 6.7.2.3 */ ++struct usb_cdc_mdlm_desc { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ __u8 bDescriptorSubType; ++ ++ __le16 bcdVersion; ++ __u8 bGUID[16]; ++} __attribute__ ((packed)); ++ ++/* "MDLM Detail Functional Descriptor" from CDC WMC spec 6.7.2.4 */ ++struct usb_cdc_mdlm_detail_desc { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ __u8 bDescriptorSubType; ++ ++ /* type is associated with mdlm_desc.bGUID */ ++ __u8 bGuidDescriptorType; ++ __u8 bDetailData[]; ++} __attribute__ ((packed)); ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* ++ * Class-Specific Control Requests (6.2) ++ * ++ * section 3.6.2.1 table 4 has the ACM profile, for modems. ++ * section 3.8.2 table 10 has the ethernet profile. ++ * ++ * Microsoft's RNDIS stack for Ethernet is a vendor-specific CDC ACM variant, ++ * heavily dependent on the encapsulated (proprietary) command mechanism. ++ */ ++ ++#define USB_CDC_SEND_ENCAPSULATED_COMMAND 0x00 ++#define USB_CDC_GET_ENCAPSULATED_RESPONSE 0x01 ++#define USB_CDC_REQ_SET_LINE_CODING 0x20 ++#define USB_CDC_REQ_GET_LINE_CODING 0x21 ++#define USB_CDC_REQ_SET_CONTROL_LINE_STATE 0x22 ++#define USB_CDC_REQ_SEND_BREAK 0x23 ++#define USB_CDC_SET_ETHERNET_MULTICAST_FILTERS 0x40 ++#define USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER 0x41 ++#define USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER 0x42 ++#define USB_CDC_SET_ETHERNET_PACKET_FILTER 0x43 ++#define USB_CDC_GET_ETHERNET_STATISTIC 0x44 ++ ++/* Line Coding Structure from CDC spec 6.2.13 */ ++struct usb_cdc_line_coding { ++ __le32 dwDTERate; ++ __u8 bCharFormat; ++#define USB_CDC_1_STOP_BITS 0 ++#define USB_CDC_1_5_STOP_BITS 1 ++#define USB_CDC_2_STOP_BITS 2 ++ ++ __u8 bParityType; ++#define USB_CDC_NO_PARITY 0 ++#define USB_CDC_ODD_PARITY 1 ++#define USB_CDC_EVEN_PARITY 2 ++#define USB_CDC_MARK_PARITY 3 ++#define USB_CDC_SPACE_PARITY 4 ++ ++ __u8 bDataBits; ++} __attribute__ ((packed)); ++ ++/* table 62; bits in multicast filter */ ++#define USB_CDC_PACKET_TYPE_PROMISCUOUS (1 << 0) ++#define USB_CDC_PACKET_TYPE_ALL_MULTICAST (1 << 1) /* no filter */ ++#define USB_CDC_PACKET_TYPE_DIRECTED (1 << 2) ++#define USB_CDC_PACKET_TYPE_BROADCAST (1 << 3) ++#define USB_CDC_PACKET_TYPE_MULTICAST (1 << 4) /* filtered */ ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* ++ * Class-Specific Notifications (6.3) sent by interrupt transfers ++ * ++ * section 3.8.2 table 11 of the CDC spec lists Ethernet notifications ++ * section 3.6.2.1 table 5 specifies ACM notifications, accepted by RNDIS ++ * RNDIS also defines its own bit-incompatible notifications ++ */ ++ ++#define USB_CDC_NOTIFY_NETWORK_CONNECTION 0x00 ++#define USB_CDC_NOTIFY_RESPONSE_AVAILABLE 0x01 ++#define USB_CDC_NOTIFY_SERIAL_STATE 0x20 ++#define USB_CDC_NOTIFY_SPEED_CHANGE 0x2a ++ ++struct usb_cdc_notification { ++ __u8 bmRequestType; ++ __u8 bNotificationType; ++ __le16 wValue; ++ __le16 wIndex; ++ __le16 wLength; ++} __attribute__ ((packed)); ++ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/include/linux/usb_ch9.h kernel/include/linux/usb_ch9.h +--- /tmp/kernel/include/linux/usb_ch9.h 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/include/linux/usb_ch9.h 2005-04-22 17:53:19.363551075 +0200 +@@ -0,0 +1,384 @@ ++/* ++ * This file holds USB constants and structures that are needed for USB ++ * device APIs. These are used by the USB device model, which is defined ++ * in chapter 9 of the USB 2.0 specification. Linux has several APIs in C ++ * that need these: ++ * ++ * - the master/host side Linux-USB kernel driver API; ++ * - the "usbfs" user space API; and ++ * - (eventually) a Linux "gadget" slave/device side driver API. ++ * ++ * USB 2.0 adds an additional "On The Go" (OTG) mode, which lets systems ++ * act either as a USB master/host or as a USB slave/device. That means ++ * the master and slave side APIs will benefit from working well together. ++ */ ++ ++#ifndef __LINUX_USB_CH9_H ++#define __LINUX_USB_CH9_H ++ ++#include /* __u8 etc */ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* CONTROL REQUEST SUPPORT */ ++ ++/* ++ * USB directions ++ * ++ * This bit flag is used in endpoint descriptors' bEndpointAddress field. ++ * It's also one of three fields in control requests bRequestType. ++ */ ++#define USB_DIR_OUT 0 /* to device */ ++#define USB_DIR_IN 0x80 /* to host */ ++ ++/* ++ * USB types, the second of three bRequestType fields ++ */ ++#define USB_TYPE_MASK (0x03 << 5) ++#define USB_TYPE_STANDARD (0x00 << 5) ++#define USB_TYPE_CLASS (0x01 << 5) ++#define USB_TYPE_VENDOR (0x02 << 5) ++#define USB_TYPE_RESERVED (0x03 << 5) ++ ++/* ++ * USB recipients, the third of three bRequestType fields ++ */ ++#define USB_RECIP_MASK 0x1f ++#define USB_RECIP_DEVICE 0x00 ++#define USB_RECIP_INTERFACE 0x01 ++#define USB_RECIP_ENDPOINT 0x02 ++#define USB_RECIP_OTHER 0x03 ++ ++/* ++ * Standard requests, for the bRequest field of a SETUP packet. ++ * ++ * These are qualified by the bRequestType field, so that for example ++ * TYPE_CLASS or TYPE_VENDOR specific feature flags could be retrieved ++ * by a GET_STATUS request. ++ */ ++#define USB_REQ_GET_STATUS 0x00 ++#define USB_REQ_CLEAR_FEATURE 0x01 ++#define USB_REQ_SET_FEATURE 0x03 ++#define USB_REQ_SET_ADDRESS 0x05 ++#define USB_REQ_GET_DESCRIPTOR 0x06 ++#define USB_REQ_SET_DESCRIPTOR 0x07 ++#define USB_REQ_GET_CONFIGURATION 0x08 ++#define USB_REQ_SET_CONFIGURATION 0x09 ++#define USB_REQ_GET_INTERFACE 0x0A ++#define USB_REQ_SET_INTERFACE 0x0B ++#define USB_REQ_SYNCH_FRAME 0x0C ++ ++/* ++ * USB feature flags are written using USB_REQ_{CLEAR,SET}_FEATURE, and ++ * are read as a bit array returned by USB_REQ_GET_STATUS. (So there ++ * are at most sixteen features of each type.) ++ */ ++#define USB_DEVICE_SELF_POWERED 0 /* (read only) */ ++#define USB_DEVICE_REMOTE_WAKEUP 1 /* dev may initiate wakeup */ ++#define USB_DEVICE_TEST_MODE 2 /* (high speed only) */ ++#define USB_DEVICE_B_HNP_ENABLE 3 /* dev may initiate HNP */ ++#define USB_DEVICE_A_HNP_SUPPORT 4 /* RH port supports HNP */ ++#define USB_DEVICE_A_ALT_HNP_SUPPORT 5 /* other RH port does */ ++#define USB_DEVICE_DEBUG_MODE 6 /* (special devices only) */ ++ ++#define USB_ENDPOINT_HALT 0 /* IN/OUT will STALL */ ++ ++ ++/** ++ * struct usb_ctrlrequest - SETUP data for a USB device control request ++ * @bRequestType: matches the USB bmRequestType field ++ * @bRequest: matches the USB bRequest field ++ * @wValue: matches the USB wValue field (le16 byte order) ++ * @wIndex: matches the USB wIndex field (le16 byte order) ++ * @wLength: matches the USB wLength field (le16 byte order) ++ * ++ * This structure is used to send control requests to a USB device. It matches ++ * the different fields of the USB 2.0 Spec section 9.3, table 9-2. See the ++ * USB spec for a fuller description of the different fields, and what they are ++ * used for. ++ * ++ * Note that the driver for any interface can issue control requests. ++ * For most devices, interfaces don't coordinate with each other, so ++ * such requests may be made at any time. ++ */ ++struct usb_ctrlrequest { ++ __u8 bRequestType; ++ __u8 bRequest; ++ __u16 wValue; ++ __u16 wIndex; ++ __u16 wLength; ++} __attribute__ ((packed)); ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* ++ * STANDARD DESCRIPTORS ... as returned by GET_DESCRIPTOR, or ++ * (rarely) accepted by SET_DESCRIPTOR. ++ * ++ * Note that all multi-byte values here are encoded in little endian ++ * byte order "on the wire". But when exposed through Linux-USB APIs, ++ * they've been converted to cpu byte order. ++ */ ++ ++/* ++ * Descriptor types ... USB 2.0 spec table 9.5 ++ */ ++#define USB_DT_DEVICE 0x01 ++#define USB_DT_CONFIG 0x02 ++#define USB_DT_STRING 0x03 ++#define USB_DT_INTERFACE 0x04 ++#define USB_DT_ENDPOINT 0x05 ++#define USB_DT_DEVICE_QUALIFIER 0x06 ++#define USB_DT_OTHER_SPEED_CONFIG 0x07 ++#define USB_DT_INTERFACE_POWER 0x08 ++/* these are from a minor usb 2.0 revision (ECN) */ ++#define USB_DT_OTG 0x09 ++#define USB_DT_DEBUG 0x0a ++#define USB_DT_INTERFACE_ASSOCIATION 0x0b ++ ++/* conventional codes for class-specific descriptors */ ++#define USB_DT_CS_DEVICE 0x21 ++#define USB_DT_CS_CONFIG 0x22 ++#define USB_DT_CS_STRING 0x23 ++#define USB_DT_CS_INTERFACE 0x24 ++#define USB_DT_CS_ENDPOINT 0x25 ++ ++/* All standard descriptors have these 2 fields at the beginning */ ++struct usb_descriptor_header { ++ __u8 bLength; ++ __u8 bDescriptorType; ++} __attribute__ ((packed)); ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* USB_DT_DEVICE: Device descriptor */ ++struct usb_device_descriptor { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ ++ __u16 bcdUSB; ++ __u8 bDeviceClass; ++ __u8 bDeviceSubClass; ++ __u8 bDeviceProtocol; ++ __u8 bMaxPacketSize0; ++ __u16 idVendor; ++ __u16 idProduct; ++ __u16 bcdDevice; ++ __u8 iManufacturer; ++ __u8 iProduct; ++ __u8 iSerialNumber; ++ __u8 bNumConfigurations; ++} __attribute__ ((packed)); ++ ++#define USB_DT_DEVICE_SIZE 18 ++ ++ ++/* ++ * Device and/or Interface Class codes ++ * as found in bDeviceClass or bInterfaceClass ++ * and defined by www.usb.org documents ++ */ ++#define USB_CLASS_PER_INTERFACE 0 /* for DeviceClass */ ++#define USB_CLASS_AUDIO 1 ++#define USB_CLASS_COMM 2 ++#define USB_CLASS_HID 3 ++#define USB_CLASS_PHYSICAL 5 ++#define USB_CLASS_STILL_IMAGE 6 ++#define USB_CLASS_PRINTER 7 ++#define USB_CLASS_MASS_STORAGE 8 ++#define USB_CLASS_HUB 9 ++#define USB_CLASS_CDC_DATA 0x0a ++#define USB_CLASS_CSCID 0x0b /* chip+ smart card */ ++#define USB_CLASS_CONTENT_SEC 0x0d /* content security */ ++#define USB_CLASS_VIDEO 0x0e ++#define USB_CLASS_APP_SPEC 0xfe ++#define USB_CLASS_VENDOR_SPEC 0xff ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* USB_DT_CONFIG: Configuration descriptor information. ++ * ++ * USB_DT_OTHER_SPEED_CONFIG is the same descriptor, except that the ++ * descriptor type is different. Highspeed-capable devices can look ++ * different depending on what speed they're currently running. Only ++ * devices with a USB_DT_DEVICE_QUALIFIER have any OTHER_SPEED_CONFIG ++ * descriptors. ++ */ ++struct usb_config_descriptor { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ ++ __u16 wTotalLength; ++ __u8 bNumInterfaces; ++ __u8 bConfigurationValue; ++ __u8 iConfiguration; ++ __u8 bmAttributes; ++ __u8 bMaxPower; ++} __attribute__ ((packed)); ++ ++#define USB_DT_CONFIG_SIZE 9 ++ ++/* from config descriptor bmAttributes */ ++#define USB_CONFIG_ATT_ONE (1 << 7) /* must be set */ ++#define USB_CONFIG_ATT_SELFPOWER (1 << 6) /* self powered */ ++#define USB_CONFIG_ATT_WAKEUP (1 << 5) /* can wakeup */ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* USB_DT_STRING: String descriptor */ ++struct usb_string_descriptor { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ ++ __u16 wData[1]; /* UTF-16LE encoded */ ++} __attribute__ ((packed)); ++ ++/* note that "string" zero is special, it holds language codes that ++ * the device supports, not Unicode characters. ++ */ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* USB_DT_INTERFACE: Interface descriptor */ ++struct usb_interface_descriptor { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ ++ __u8 bInterfaceNumber; ++ __u8 bAlternateSetting; ++ __u8 bNumEndpoints; ++ __u8 bInterfaceClass; ++ __u8 bInterfaceSubClass; ++ __u8 bInterfaceProtocol; ++ __u8 iInterface; ++} __attribute__ ((packed)); ++ ++#define USB_DT_INTERFACE_SIZE 9 ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* USB_DT_ENDPOINT: Endpoint descriptor */ ++struct usb_endpoint_descriptor { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ ++ __u8 bEndpointAddress; ++ __u8 bmAttributes; ++ __u16 wMaxPacketSize; ++ __u8 bInterval; ++ ++ // NOTE: these two are _only_ in audio endpoints. ++ // use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. ++ __u8 bRefresh; ++ __u8 bSynchAddress; ++} __attribute__ ((packed)); ++ ++#define USB_DT_ENDPOINT_SIZE 7 ++#define USB_DT_ENDPOINT_AUDIO_SIZE 9 /* Audio extension */ ++ ++ ++/* ++ * Endpoints ++ */ ++#define USB_ENDPOINT_NUMBER_MASK 0x0f /* in bEndpointAddress */ ++#define USB_ENDPOINT_DIR_MASK 0x80 ++ ++#define USB_ENDPOINT_XFERTYPE_MASK 0x03 /* in bmAttributes */ ++#define USB_ENDPOINT_XFER_CONTROL 0 ++#define USB_ENDPOINT_XFER_ISOC 1 ++#define USB_ENDPOINT_XFER_BULK 2 ++#define USB_ENDPOINT_XFER_INT 3 ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* USB_DT_DEVICE_QUALIFIER: Device Qualifier descriptor */ ++struct usb_qualifier_descriptor { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ ++ __u16 bcdUSB; ++ __u8 bDeviceClass; ++ __u8 bDeviceSubClass; ++ __u8 bDeviceProtocol; ++ __u8 bMaxPacketSize0; ++ __u8 bNumConfigurations; ++ __u8 bRESERVED; ++} __attribute__ ((packed)); ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* USB_DT_OTG (from OTG 1.0a supplement) */ ++struct usb_otg_descriptor { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ ++ __u8 bmAttributes; /* support for HNP, SRP, etc */ ++} __attribute__ ((packed)); ++ ++/* from usb_otg_descriptor.bmAttributes */ ++#define USB_OTG_SRP (1 << 0) ++#define USB_OTG_HNP (1 << 1) /* swap host/device roles */ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* USB_DT_DEBUG: for special highspeed devices, replacing serial console */ ++struct usb_debug_descriptor { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ ++ /* bulk endpoints with 8 byte maxpacket */ ++ __u8 bDebugInEndpoint; ++ __u8 bDebugOutEndpoint; ++}; ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* USB_DT_INTERFACE_ASSOCIATION: groups interfaces */ ++struct usb_interface_assoc_descriptor { ++ __u8 bLength; ++ __u8 bDescriptorType; ++ ++ __u8 bFirstInterface; ++ __u8 bInterfaceCount; ++ __u8 bFunctionClass; ++ __u8 bFunctionSubClass; ++ __u8 bFunctionProtocol; ++ __u8 iFunction; ++} __attribute__ ((packed)); ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* USB 2.0 defines three speeds, here's how Linux identifies them */ ++ ++enum usb_device_speed { ++ USB_SPEED_UNKNOWN = 0, /* enumerating */ ++ USB_SPEED_LOW, USB_SPEED_FULL, /* usb 1.1 */ ++ USB_SPEED_HIGH /* usb 2.0 */ ++}; ++ ++enum usb_device_state { ++ /* NOTATTACHED isn't in the USB spec, and this state acts ++ * the same as ATTACHED ... but it's clearer this way. ++ */ ++ USB_STATE_NOTATTACHED = 0, ++ ++ /* the chapter 9 device states */ ++ USB_STATE_ATTACHED, ++ USB_STATE_POWERED, ++ USB_STATE_DEFAULT, /* limited function */ ++ USB_STATE_ADDRESS, ++ USB_STATE_CONFIGURED, /* most functions */ ++ ++ USB_STATE_SUSPENDED ++ ++ /* NOTE: there are actually four different SUSPENDED ++ * states, returning to POWERED, DEFAULT, ADDRESS, or ++ * CONFIGURED respectively when SOF tokens flow again. ++ */ ++}; ++ ++#endif /* __LINUX_USB_CH9_H */ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/include/linux/usb_gadget.h kernel/include/linux/usb_gadget.h +--- /tmp/kernel/include/linux/usb_gadget.h 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/include/linux/usb_gadget.h 2005-04-22 17:53:19.367550424 +0200 +@@ -0,0 +1,896 @@ ++/* ++ * ++ * ++ * We call the USB code inside a Linux-based peripheral device a "gadget" ++ * driver, except for the hardware-specific bus glue. One USB host can ++ * master many USB gadgets, but the gadgets are only slaved to one host. ++ * ++ * ++ * (C) Copyright 2002-2004 by David Brownell ++ * All Rights Reserved. ++ * ++ * This software is licensed under the GNU GPL version 2. ++ */ ++ ++#ifndef __LINUX_USB_GADGET_H ++#define __LINUX_USB_GADGET_H ++ ++#ifdef __KERNEL__ ++ ++struct usb_ep; ++ ++/** ++ * struct usb_request - describes one i/o request ++ * @buf: Buffer used for data. Always provide this; some controllers ++ * only use PIO, or don't use DMA for some endpoints. ++ * @dma: DMA address corresponding to 'buf'. If you don't set this ++ * field, and the usb controller needs one, it is responsible ++ * for mapping and unmapping the buffer. ++ * @length: Length of that data ++ * @no_interrupt: If true, hints that no completion irq is needed. ++ * Helpful sometimes with deep request queues that are handled ++ * directly by DMA controllers. ++ * @zero: If true, when writing data, makes the last packet be "short" ++ * by adding a zero length packet as needed; ++ * @short_not_ok: When reading data, makes short packets be ++ * treated as errors (queue stops advancing till cleanup). ++ * @complete: Function called when request completes, so this request and ++ * its buffer may be re-used. ++ * Reads terminate with a short packet, or when the buffer fills, ++ * whichever comes first. When writes terminate, some data bytes ++ * will usually still be in flight (often in a hardware fifo). ++ * Errors (for reads or writes) stop the queue from advancing ++ * until the completion function returns, so that any transfers ++ * invalidated by the error may first be dequeued. ++ * @context: For use by the completion callback ++ * @list: For use by the gadget driver. ++ * @status: Reports completion code, zero or a negative errno. ++ * Normally, faults block the transfer queue from advancing until ++ * the completion callback returns. ++ * Code "-ESHUTDOWN" indicates completion caused by device disconnect, ++ * or when the driver disabled the endpoint. ++ * @actual: Reports bytes transferred to/from the buffer. For reads (OUT ++ * transfers) this may be less than the requested length. If the ++ * short_not_ok flag is set, short reads are treated as errors ++ * even when status otherwise indicates successful completion. ++ * Note that for writes (IN transfers) some data bytes may still ++ * reside in a device-side FIFO when the request is reported as ++ * complete. ++ * ++ * These are allocated/freed through the endpoint they're used with. The ++ * hardware's driver can add extra per-request data to the memory it returns, ++ * which often avoids separate memory allocations (potential failures), ++ * later when the request is queued. ++ * ++ * Request flags affect request handling, such as whether a zero length ++ * packet is written (the "zero" flag), whether a short read should be ++ * treated as an error (blocking request queue advance, the "short_not_ok" ++ * flag), or hinting that an interrupt is not required (the "no_interrupt" ++ * flag, for use with deep request queues). ++ * ++ * Bulk endpoints can use any size buffers, and can also be used for interrupt ++ * transfers. interrupt-only endpoints can be much less functional. ++ */ ++ // NOTE this is analagous to 'struct urb' on the host side, ++ // except that it's thinner and promotes more pre-allocation. ++ ++struct usb_request { ++ void *buf; ++ unsigned length; ++ dma_addr_t dma; ++ ++ unsigned no_interrupt:1; ++ unsigned zero:1; ++ unsigned short_not_ok:1; ++ ++ void (*complete)(struct usb_ep *ep, ++ struct usb_request *req); ++ void *context; ++ struct list_head list; ++ ++ int status; ++ unsigned actual; ++}; ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* endpoint-specific parts of the api to the usb controller hardware. ++ * unlike the urb model, (de)multiplexing layers are not required. ++ * (so this api could slash overhead if used on the host side...) ++ * ++ * note that device side usb controllers commonly differ in how many ++ * endpoints they support, as well as their capabilities. ++ */ ++struct usb_ep_ops { ++ int (*enable) (struct usb_ep *ep, ++ const struct usb_endpoint_descriptor *desc); ++ int (*disable) (struct usb_ep *ep); ++ ++ struct usb_request *(*alloc_request) (struct usb_ep *ep, ++ int gfp_flags); ++ void (*free_request) (struct usb_ep *ep, struct usb_request *req); ++ ++ void *(*alloc_buffer) (struct usb_ep *ep, unsigned bytes, ++ dma_addr_t *dma, int gfp_flags); ++ void (*free_buffer) (struct usb_ep *ep, void *buf, dma_addr_t dma, ++ unsigned bytes); ++ // NOTE: on 2.6, drivers may also use dma_map() and ++ // dma_sync_single_*() to directly manage dma overhead. ++ ++ int (*queue) (struct usb_ep *ep, struct usb_request *req, ++ int gfp_flags); ++ int (*dequeue) (struct usb_ep *ep, struct usb_request *req); ++ ++ int (*set_halt) (struct usb_ep *ep, int value); ++ int (*fifo_status) (struct usb_ep *ep); ++ void (*fifo_flush) (struct usb_ep *ep); ++}; ++ ++/** ++ * struct usb_ep - device side representation of USB endpoint ++ * @name:identifier for the endpoint, such as "ep-a" or "ep9in-bulk" ++ * @ops: Function pointers used to access hardware-specific operations. ++ * @ep_list:the gadget's ep_list holds all of its endpoints ++ * @maxpacket:The maximum packet size used on this endpoint. The initial ++ * value can sometimes be reduced (hardware allowing), according to ++ * the endpoint descriptor used to configure the endpoint. ++ * @driver_data:for use by the gadget driver. all other fields are ++ * read-only to gadget drivers. ++ * ++ * the bus controller driver lists all the general purpose endpoints in ++ * gadget->ep_list. the control endpoint (gadget->ep0) is not in that list, ++ * and is accessed only in response to a driver setup() callback. ++ */ ++struct usb_ep { ++ void *driver_data; ++ ++ const char *name; ++ const struct usb_ep_ops *ops; ++ struct list_head ep_list; ++ unsigned maxpacket:16; ++}; ++ ++/*-------------------------------------------------------------------------*/ ++ ++/** ++ * usb_ep_enable - configure endpoint, making it usable ++ * @ep:the endpoint being configured. may not be the endpoint named "ep0". ++ * drivers discover endpoints through the ep_list of a usb_gadget. ++ * @desc:descriptor for desired behavior. caller guarantees this pointer ++ * remains valid until the endpoint is disabled; the data byte order ++ * is little-endian (usb-standard). ++ * ++ * when configurations are set, or when interface settings change, the driver ++ * will enable or disable the relevant endpoints. while it is enabled, an ++ * endpoint may be used for i/o until the driver receives a disconnect() from ++ * the host or until the endpoint is disabled. ++ * ++ * the ep0 implementation (which calls this routine) must ensure that the ++ * hardware capabilities of each endpoint match the descriptor provided ++ * for it. for example, an endpoint named "ep2in-bulk" would be usable ++ * for interrupt transfers as well as bulk, but it likely couldn't be used ++ * for iso transfers or for endpoint 14. some endpoints are fully ++ * configurable, with more generic names like "ep-a". (remember that for ++ * USB, "in" means "towards the USB master".) ++ * ++ * returns zero, or a negative error code. ++ */ ++static inline int ++usb_ep_enable (struct usb_ep *ep, const struct usb_endpoint_descriptor *desc) ++{ ++ return ep->ops->enable (ep, desc); ++} ++ ++/** ++ * usb_ep_disable - endpoint is no longer usable ++ * @ep:the endpoint being unconfigured. may not be the endpoint named "ep0". ++ * ++ * no other task may be using this endpoint when this is called. ++ * any pending and uncompleted requests will complete with status ++ * indicating disconnect (-ESHUTDOWN) before this call returns. ++ * gadget drivers must call usb_ep_enable() again before queueing ++ * requests to the endpoint. ++ * ++ * returns zero, or a negative error code. ++ */ ++static inline int ++usb_ep_disable (struct usb_ep *ep) ++{ ++ return ep->ops->disable (ep); ++} ++ ++/** ++ * usb_ep_alloc_request - allocate a request object to use with this endpoint ++ * @ep:the endpoint to be used with with the request ++ * @gfp_flags:GFP_* flags to use ++ * ++ * Request objects must be allocated with this call, since they normally ++ * need controller-specific setup and may even need endpoint-specific ++ * resources such as allocation of DMA descriptors. ++ * Requests may be submitted with usb_ep_queue(), and receive a single ++ * completion callback. Free requests with usb_ep_free_request(), when ++ * they are no longer needed. ++ * ++ * Returns the request, or null if one could not be allocated. ++ */ ++static inline struct usb_request * ++usb_ep_alloc_request (struct usb_ep *ep, int gfp_flags) ++{ ++ return ep->ops->alloc_request (ep, gfp_flags); ++} ++ ++/** ++ * usb_ep_free_request - frees a request object ++ * @ep:the endpoint associated with the request ++ * @req:the request being freed ++ * ++ * Reverses the effect of usb_ep_alloc_request(). ++ * Caller guarantees the request is not queued, and that it will ++ * no longer be requeued (or otherwise used). ++ */ ++static inline void ++usb_ep_free_request (struct usb_ep *ep, struct usb_request *req) ++{ ++ ep->ops->free_request (ep, req); ++} ++ ++/** ++ * usb_ep_alloc_buffer - allocate an I/O buffer ++ * @ep:the endpoint associated with the buffer ++ * @len:length of the desired buffer ++ * @dma:pointer to the buffer's DMA address; must be valid ++ * @gfp_flags:GFP_* flags to use ++ * ++ * Returns a new buffer, or null if one could not be allocated. ++ * The buffer is suitably aligned for dma, if that endpoint uses DMA, ++ * and the caller won't have to care about dma-inconsistency ++ * or any hidden "bounce buffer" mechanism. No additional per-request ++ * DMA mapping will be required for such buffers. ++ * Free it later with usb_ep_free_buffer(). ++ * ++ * You don't need to use this call to allocate I/O buffers unless you ++ * want to make sure drivers don't incur costs for such "bounce buffer" ++ * copies or per-request DMA mappings. ++ */ ++static inline void * ++usb_ep_alloc_buffer (struct usb_ep *ep, unsigned len, dma_addr_t *dma, ++ int gfp_flags) ++{ ++ return ep->ops->alloc_buffer (ep, len, dma, gfp_flags); ++} ++ ++/** ++ * usb_ep_free_buffer - frees an i/o buffer ++ * @ep:the endpoint associated with the buffer ++ * @buf:CPU view address of the buffer ++ * @dma:the buffer's DMA address ++ * @len:length of the buffer ++ * ++ * reverses the effect of usb_ep_alloc_buffer(). ++ * caller guarantees the buffer will no longer be accessed ++ */ ++static inline void ++usb_ep_free_buffer (struct usb_ep *ep, void *buf, dma_addr_t dma, unsigned len) ++{ ++ ep->ops->free_buffer (ep, buf, dma, len); ++} ++ ++/** ++ * usb_ep_queue - queues (submits) an I/O request to an endpoint. ++ * @ep:the endpoint associated with the request ++ * @req:the request being submitted ++ * @gfp_flags: GFP_* flags to use in case the lower level driver couldn't ++ * pre-allocate all necessary memory with the request. ++ * ++ * This tells the device controller to perform the specified request through ++ * that endpoint (reading or writing a buffer). When the request completes, ++ * including being canceled by usb_ep_dequeue(), the request's completion ++ * routine is called to return the request to the driver. Any endpoint ++ * (except control endpoints like ep0) may have more than one transfer ++ * request queued; they complete in FIFO order. Once a gadget driver ++ * submits a request, that request may not be examined or modified until it ++ * is given back to that driver through the completion callback. ++ * ++ * Each request is turned into one or more packets. The controller driver ++ * never merges adjacent requests into the same packet. OUT transfers ++ * will sometimes use data that's already buffered in the hardware. ++ * Drivers can rely on the fact that the first byte of the request's buffer ++ * always corresponds to the first byte of some USB packet, for both ++ * IN and OUT transfers. ++ * ++ * Bulk endpoints can queue any amount of data; the transfer is packetized ++ * automatically. The last packet will be short if the request doesn't fill it ++ * out completely. Zero length packets (ZLPs) should be avoided in portable ++ * protocols since not all usb hardware can successfully handle zero length ++ * packets. (ZLPs may be explicitly written, and may be implicitly written if ++ * the request 'zero' flag is set.) Bulk endpoints may also be used ++ * for interrupt transfers; but the reverse is not true, and some endpoints ++ * won't support every interrupt transfer. (Such as 768 byte packets.) ++ * ++ * Interrupt-only endpoints are less functional than bulk endpoints, for ++ * example by not supporting queueing or not handling buffers that are ++ * larger than the endpoint's maxpacket size. They may also treat data ++ * toggle differently. ++ * ++ * Control endpoints ... after getting a setup() callback, the driver queues ++ * one response (even if it would be zero length). That enables the ++ * status ack, after transfering data as specified in the response. Setup ++ * functions may return negative error codes to generate protocol stalls. ++ * (Note that some USB device controllers disallow protocol stall responses ++ * in some cases.) When control responses are deferred (the response is ++ * written after the setup callback returns), then usb_ep_set_halt() may be ++ * used on ep0 to trigger protocol stalls. ++ * ++ * For periodic endpoints, like interrupt or isochronous ones, the usb host ++ * arranges to poll once per interval, and the gadget driver usually will ++ * have queued some data to transfer at that time. ++ * ++ * Returns zero, or a negative error code. Endpoints that are not enabled ++ * report errors; errors will also be ++ * reported when the usb peripheral is disconnected. ++ */ ++static inline int ++usb_ep_queue (struct usb_ep *ep, struct usb_request *req, int gfp_flags) ++{ ++ return ep->ops->queue (ep, req, gfp_flags); ++} ++ ++/** ++ * usb_ep_dequeue - dequeues (cancels, unlinks) an I/O request from an endpoint ++ * @ep:the endpoint associated with the request ++ * @req:the request being canceled ++ * ++ * if the request is still active on the endpoint, it is dequeued and its ++ * completion routine is called (with status -ECONNRESET); else a negative ++ * error code is returned. ++ * ++ * note that some hardware can't clear out write fifos (to unlink the request ++ * at the head of the queue) except as part of disconnecting from usb. such ++ * restrictions prevent drivers from supporting configuration changes, ++ * even to configuration zero (a "chapter 9" requirement). ++ */ ++static inline int usb_ep_dequeue (struct usb_ep *ep, struct usb_request *req) ++{ ++ return ep->ops->dequeue (ep, req); ++} ++ ++/** ++ * usb_ep_set_halt - sets the endpoint halt feature. ++ * @ep: the non-isochronous endpoint being stalled ++ * ++ * Use this to stall an endpoint, perhaps as an error report. ++ * Except for control endpoints, ++ * the endpoint stays halted (will not stream any data) until the host ++ * clears this feature; drivers may need to empty the endpoint's request ++ * queue first, to make sure no inappropriate transfers happen. ++ * ++ * Note that while an endpoint CLEAR_FEATURE will be invisible to the ++ * gadget driver, a SET_INTERFACE will not be. To reset endpoints for the ++ * current altsetting, see usb_ep_clear_halt(). When switching altsettings, ++ * it's simplest to use usb_ep_enable() or usb_ep_disable() for the endpoints. ++ * ++ * Returns zero, or a negative error code. On success, this call sets ++ * underlying hardware state that blocks data transfers. ++ * Attempts to halt IN endpoints will fail (returning -EAGAIN) if any ++ * transfer requests are still queued, or if the controller hardware ++ * (usually a FIFO) still holds bytes that the host hasn't collected. ++ */ ++static inline int ++usb_ep_set_halt (struct usb_ep *ep) ++{ ++ return ep->ops->set_halt (ep, 1); ++} ++ ++/** ++ * usb_ep_clear_halt - clears endpoint halt, and resets toggle ++ * @ep:the bulk or interrupt endpoint being reset ++ * ++ * Use this when responding to the standard usb "set interface" request, ++ * for endpoints that aren't reconfigured, after clearing any other state ++ * in the endpoint's i/o queue. ++ * ++ * Returns zero, or a negative error code. On success, this call clears ++ * the underlying hardware state reflecting endpoint halt and data toggle. ++ * Note that some hardware can't support this request (like pxa2xx_udc), ++ * and accordingly can't correctly implement interface altsettings. ++ */ ++static inline int ++usb_ep_clear_halt (struct usb_ep *ep) ++{ ++ return ep->ops->set_halt (ep, 0); ++} ++ ++/** ++ * usb_ep_fifo_status - returns number of bytes in fifo, or error ++ * @ep: the endpoint whose fifo status is being checked. ++ * ++ * FIFO endpoints may have "unclaimed data" in them in certain cases, ++ * such as after aborted transfers. Hosts may not have collected all ++ * the IN data written by the gadget driver (and reported by a request ++ * completion). The gadget driver may not have collected all the data ++ * written OUT to it by the host. Drivers that need precise handling for ++ * fault reporting or recovery may need to use this call. ++ * ++ * This returns the number of such bytes in the fifo, or a negative ++ * errno if the endpoint doesn't use a FIFO or doesn't support such ++ * precise handling. ++ */ ++static inline int ++usb_ep_fifo_status (struct usb_ep *ep) ++{ ++ if (ep->ops->fifo_status) ++ return ep->ops->fifo_status (ep); ++ else ++ return -EOPNOTSUPP; ++} ++ ++/** ++ * usb_ep_fifo_flush - flushes contents of a fifo ++ * @ep: the endpoint whose fifo is being flushed. ++ * ++ * This call may be used to flush the "unclaimed data" that may exist in ++ * an endpoint fifo after abnormal transaction terminations. The call ++ * must never be used except when endpoint is not being used for any ++ * protocol translation. ++ */ ++static inline void ++usb_ep_fifo_flush (struct usb_ep *ep) ++{ ++ if (ep->ops->fifo_flush) ++ ep->ops->fifo_flush (ep); ++} ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++struct usb_gadget; ++ ++/* the rest of the api to the controller hardware: device operations, ++ * which don't involve endpoints (or i/o). ++ */ ++struct usb_gadget_ops { ++ int (*get_frame)(struct usb_gadget *); ++ int (*wakeup)(struct usb_gadget *); ++ int (*set_selfpowered) (struct usb_gadget *, int is_selfpowered); ++ int (*vbus_session) (struct usb_gadget *, int is_active); ++ int (*vbus_draw) (struct usb_gadget *, unsigned mA); ++ int (*pullup) (struct usb_gadget *, int is_on); ++ int (*ioctl)(struct usb_gadget *, ++ unsigned code, unsigned long param); ++}; ++ ++/** ++ * struct usb_gadget - represents a usb slave device ++ * @ops: Function pointers used to access hardware-specific operations. ++ * @ep0: Endpoint zero, used when reading or writing responses to ++ * driver setup() requests ++ * @ep_list: List of other endpoints supported by the device. ++ * @speed: Speed of current connection to USB host. ++ * @is_dualspeed: True if the controller supports both high and full speed ++ * operation. If it does, the gadget driver must also support both. ++ * @is_otg: True if the USB device port uses a Mini-AB jack, so that the ++ * gadget driver must provide a USB OTG descriptor. ++ * @is_a_peripheral: False unless is_otg, the "A" end of a USB cable ++ * is in the Mini-AB jack, and HNP has been used to switch roles ++ * so that the "A" device currently acts as A-Peripheral, not A-Host. ++ * @a_hnp_support: OTG device feature flag, indicating that the A-Host ++ * supports HNP at this port. ++ * @a_alt_hnp_support: OTG device feature flag, indicating that the A-Host ++ * only supports HNP on a different root port. ++ * @b_hnp_enable: OTG device feature flag, indicating that the A-Host ++ * enabled HNP support. ++ * @name: Identifies the controller hardware type. Used in diagnostics ++ * and sometimes configuration. ++ * @dev: Driver model state for this abstract device. ++ * ++ * Gadgets have a mostly-portable "gadget driver" implementing device ++ * functions, handling all usb configurations and interfaces. Gadget ++ * drivers talk to hardware-specific code indirectly, through ops vectors. ++ * That insulates the gadget driver from hardware details, and packages ++ * the hardware endpoints through generic i/o queues. The "usb_gadget" ++ * and "usb_ep" interfaces provide that insulation from the hardware. ++ * ++ * Except for the driver data, all fields in this structure are ++ * read-only to the gadget driver. That driver data is part of the ++ * "driver model" infrastructure in 2.6 (and later) kernels, and for ++ * earlier systems is grouped in a similar structure that's not known ++ * to the rest of the kernel. ++ * ++ * Values of the three OTG device feature flags are updated before the ++ * setup() call corresponding to USB_REQ_SET_CONFIGURATION, and before ++ * driver suspend() calls. They are valid only when is_otg, and when the ++ * device is acting as a B-Peripheral (so is_a_peripheral is false). ++ */ ++struct usb_gadget { ++ /* readonly to gadget driver */ ++ const struct usb_gadget_ops *ops; ++ struct usb_ep *ep0; ++ struct list_head ep_list; /* of usb_ep */ ++ enum usb_device_speed speed; ++ unsigned is_dualspeed:1; ++ unsigned is_otg:1; ++ unsigned is_a_peripheral:1; ++ unsigned b_hnp_enable:1; ++ unsigned a_hnp_support:1; ++ unsigned a_alt_hnp_support:1; ++ const char *name; ++ ++ struct __gadget_device { ++ const char *bus_id; ++ void *driver_data; ++ } dev; ++}; ++ ++static inline void set_gadget_data (struct usb_gadget *gadget, void *data) ++ { gadget->dev.driver_data = data; } ++static inline void *get_gadget_data (struct usb_gadget *gadget) ++ { return gadget->dev.driver_data; } ++ ++ ++/* iterates the non-control endpoints; 'tmp' is a struct usb_ep pointer */ ++#define gadget_for_each_ep(tmp,gadget) \ ++ list_for_each_entry(tmp, &(gadget)->ep_list, ep_list) ++ ++#ifndef list_for_each_entry ++/* not available in 2.4.18 */ ++#define list_for_each_entry(pos, head, member) \ ++ for (pos = list_entry((head)->next, typeof(*pos), member), \ ++ prefetch(pos->member.next); \ ++ &pos->member != (head); \ ++ pos = list_entry(pos->member.next, typeof(*pos), member), \ ++ prefetch(pos->member.next)) ++#endif ++ ++ ++/** ++ * usb_gadget_frame_number - returns the current frame number ++ * @gadget: controller that reports the frame number ++ * ++ * Returns the usb frame number, normally eleven bits from a SOF packet, ++ * or negative errno if this device doesn't support this capability. ++ */ ++static inline int usb_gadget_frame_number (struct usb_gadget *gadget) ++{ ++ return gadget->ops->get_frame (gadget); ++} ++ ++/** ++ * usb_gadget_wakeup - tries to wake up the host connected to this gadget ++ * @gadget: controller used to wake up the host ++ * ++ * Returns zero on success, else negative error code if the hardware ++ * doesn't support such attempts, or its support has not been enabled ++ * by the usb host. Drivers must return device descriptors that report ++ * their ability to support this, or hosts won't enable it. ++ * ++ * This may also try to use SRP to wake the host and start enumeration, ++ * even if OTG isn't otherwise in use. OTG devices may also start ++ * remote wakeup even when hosts don't explicitly enable it. ++ */ ++static inline int usb_gadget_wakeup (struct usb_gadget *gadget) ++{ ++ if (!gadget->ops->wakeup) ++ return -EOPNOTSUPP; ++ return gadget->ops->wakeup (gadget); ++} ++ ++/** ++ * usb_gadget_set_selfpowered - sets the device selfpowered feature. ++ * @gadget:the device being declared as self-powered ++ * ++ * this affects the device status reported by the hardware driver ++ * to reflect that it now has a local power supply. ++ * ++ * returns zero on success, else negative errno. ++ */ ++static inline int ++usb_gadget_set_selfpowered (struct usb_gadget *gadget) ++{ ++ if (!gadget->ops->set_selfpowered) ++ return -EOPNOTSUPP; ++ return gadget->ops->set_selfpowered (gadget, 1); ++} ++ ++/** ++ * usb_gadget_clear_selfpowered - clear the device selfpowered feature. ++ * @gadget:the device being declared as bus-powered ++ * ++ * this affects the device status reported by the hardware driver. ++ * some hardware may not support bus-powered operation, in which ++ * case this feature's value can never change. ++ * ++ * returns zero on success, else negative errno. ++ */ ++static inline int ++usb_gadget_clear_selfpowered (struct usb_gadget *gadget) ++{ ++ if (!gadget->ops->set_selfpowered) ++ return -EOPNOTSUPP; ++ return gadget->ops->set_selfpowered (gadget, 0); ++} ++ ++/** ++ * usb_gadget_vbus_connect - Notify controller that VBUS is powered ++ * @gadget:The device which now has VBUS power. ++ * ++ * This call is used by a driver for an external transceiver (or GPIO) ++ * that detects a VBUS power session starting. Common responses include ++ * resuming the controller, activating the D+ (or D-) pullup to let the ++ * host detect that a USB device is attached, and starting to draw power ++ * (8mA or possibly more, especially after SET_CONFIGURATION). ++ * ++ * Returns zero on success, else negative errno. ++ */ ++static inline int ++usb_gadget_vbus_connect(struct usb_gadget *gadget) ++{ ++ if (!gadget->ops->vbus_session) ++ return -EOPNOTSUPP; ++ return gadget->ops->vbus_session (gadget, 1); ++} ++ ++/** ++ * usb_gadget_vbus_draw - constrain controller's VBUS power usage ++ * @gadget:The device whose VBUS usage is being described ++ * @mA:How much current to draw, in milliAmperes. This should be twice ++ * the value listed in the configuration descriptor bMaxPower field. ++ * ++ * This call is used by gadget drivers during SET_CONFIGURATION calls, ++ * reporting how much power the device may consume. For example, this ++ * could affect how quickly batteries are recharged. ++ * ++ * Returns zero on success, else negative errno. ++ */ ++static inline int ++usb_gadget_vbus_draw(struct usb_gadget *gadget, unsigned mA) ++{ ++ if (!gadget->ops->vbus_draw) ++ return -EOPNOTSUPP; ++ return gadget->ops->vbus_draw (gadget, mA); ++} ++ ++/** ++ * usb_gadget_vbus_disconnect - notify controller about VBUS session end ++ * @gadget:the device whose VBUS supply is being described ++ * ++ * This call is used by a driver for an external transceiver (or GPIO) ++ * that detects a VBUS power session ending. Common responses include ++ * reversing everything done in usb_gadget_vbus_connect(). ++ * ++ * Returns zero on success, else negative errno. ++ */ ++static inline int ++usb_gadget_vbus_disconnect(struct usb_gadget *gadget) ++{ ++ if (!gadget->ops->vbus_session) ++ return -EOPNOTSUPP; ++ return gadget->ops->vbus_session (gadget, 0); ++} ++ ++/** ++ * usb_gadget_connect - software-controlled connect to USB host ++ * @gadget:the peripheral being connected ++ * ++ * Enables the D+ (or potentially D-) pullup. The host will start ++ * enumerating this gadget when the pullup is active and a VBUS session ++ * is active (the link is powered). This pullup is always enabled unless ++ * usb_gadget_disconnect() has been used to disable it. ++ * ++ * Returns zero on success, else negative errno. ++ */ ++static inline int ++usb_gadget_connect (struct usb_gadget *gadget) ++{ ++ if (!gadget->ops->pullup) ++ return -EOPNOTSUPP; ++ return gadget->ops->pullup (gadget, 1); ++} ++ ++/** ++ * usb_gadget_disconnect - software-controlled disconnect from USB host ++ * @gadget:the peripheral being disconnected ++ * ++ * Disables the D+ (or potentially D-) pullup, which the host may see ++ * as a disconnect (when a VBUS session is active). Not all systems ++ * support software pullup controls. ++ * ++ * This routine may be used during the gadget driver bind() call to prevent ++ * the peripheral from ever being visible to the USB host, unless later ++ * usb_gadget_connect() is called. For example, user mode components may ++ * need to be activated before the system can talk to hosts. ++ * ++ * Returns zero on success, else negative errno. ++ */ ++static inline int ++usb_gadget_disconnect (struct usb_gadget *gadget) ++{ ++ if (!gadget->ops->pullup) ++ return -EOPNOTSUPP; ++ return gadget->ops->pullup (gadget, 0); ++} ++ ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/** ++ * struct usb_gadget_driver - driver for usb 'slave' devices ++ * @function: String describing the gadget's function ++ * @speed: Highest speed the driver handles. ++ * @bind: Invoked when the driver is bound to a gadget, usually ++ * after registering the driver. ++ * At that point, ep0 is fully initialized, and ep_list holds ++ * the currently-available endpoints. ++ * Called in a context that permits sleeping. ++ * @setup: Invoked for ep0 control requests that aren't handled by ++ * the hardware level driver. Most calls must be handled by ++ * the gadget driver, including descriptor and configuration ++ * management. The 16 bit members of the setup data are in ++ * cpu order. Called in_interrupt; this may not sleep. Driver ++ * queues a response to ep0, or returns negative to stall. ++ * @disconnect: Invoked after all transfers have been stopped, ++ * when the host is disconnected. May be called in_interrupt; this ++ * may not sleep. Some devices can't detect disconnect, so this might ++ * not be called except as part of controller shutdown. ++ * @unbind: Invoked when the driver is unbound from a gadget, ++ * usually from rmmod (after a disconnect is reported). ++ * Called in a context that permits sleeping. ++ * @suspend: Invoked on USB suspend. May be called in_interrupt. ++ * @resume: Invoked on USB resume. May be called in_interrupt. ++ * @driver: Driver model state for this driver. ++ * ++ * Devices are disabled till a gadget driver successfully bind()s, which ++ * means the driver will handle setup() requests needed to enumerate (and ++ * meet "chapter 9" requirements) then do some useful work. ++ * ++ * If gadget->is_otg is true, the gadget driver must provide an OTG ++ * descriptor during enumeration, or else fail the bind() call. In such ++ * cases, no USB traffic may flow until both bind() returns without ++ * having called usb_gadget_disconnect(), and the USB host stack has ++ * initialized. ++ * ++ * Drivers use hardware-specific knowledge to configure the usb hardware. ++ * endpoint addressing is only one of several hardware characteristics that ++ * are in descriptors the ep0 implementation returns from setup() calls. ++ * ++ * Except for ep0 implementation, most driver code shouldn't need change to ++ * run on top of different usb controllers. It'll use endpoints set up by ++ * that ep0 implementation. ++ * ++ * The usb controller driver handles a few standard usb requests. Those ++ * include set_address, and feature flags for devices, interfaces, and ++ * endpoints (the get_status, set_feature, and clear_feature requests). ++ * ++ * Accordingly, the driver's setup() callback must always implement all ++ * get_descriptor requests, returning at least a device descriptor and ++ * a configuration descriptor. Drivers must make sure the endpoint ++ * descriptors match any hardware constraints. Some hardware also constrains ++ * other descriptors. (The pxa250 allows only configurations 1, 2, or 3). ++ * ++ * The driver's setup() callback must also implement set_configuration, ++ * and should also implement set_interface, get_configuration, and ++ * get_interface. Setting a configuration (or interface) is where ++ * endpoints should be activated or (config 0) shut down. ++ * ++ * (Note that only the default control endpoint is supported. Neither ++ * hosts nor devices generally support control traffic except to ep0.) ++ * ++ * Most devices will ignore USB suspend/resume operations, and so will ++ * not provide those callbacks. However, some may need to change modes ++ * when the host is not longer directing those activities. For example, ++ * local controls (buttons, dials, etc) may need to be re-enabled since ++ * the (remote) host can't do that any longer; or an error state might ++ * be cleared, to make the device behave identically whether or not ++ * power is maintained. ++ */ ++struct usb_gadget_driver { ++ char *function; ++ enum usb_device_speed speed; ++ int (*bind)(struct usb_gadget *); ++ void (*unbind)(struct usb_gadget *); ++ int (*setup)(struct usb_gadget *, ++ const struct usb_ctrlrequest *); ++ void (*disconnect)(struct usb_gadget *); ++ void (*suspend)(struct usb_gadget *); ++ void (*resume)(struct usb_gadget *); ++ ++ // FIXME support safe rmmod ++ struct __gadget_driver { ++ const char *name; ++ void *driver_data; ++ } driver; ++}; ++ ++ ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* driver modules register and unregister, as usual. ++ * these calls must be made in a context that can sleep. ++ * ++ * these will usually be implemented directly by the hardware-dependent ++ * usb bus interface driver, which will only support a single driver. ++ */ ++ ++/** ++ * usb_gadget_register_driver - register a gadget driver ++ * @driver:the driver being registered ++ * ++ * Call this in your gadget driver's module initialization function, ++ * to tell the underlying usb controller driver about your driver. ++ * The driver's bind() function will be called to bind it to a ++ * gadget. This function must be called in a context that can sleep. ++ */ ++int usb_gadget_register_driver (struct usb_gadget_driver *driver); ++ ++/** ++ * usb_gadget_unregister_driver - unregister a gadget driver ++ * @driver:the driver being unregistered ++ * ++ * Call this in your gadget driver's module cleanup function, ++ * to tell the underlying usb controller that your driver is ++ * going away. If the controller is connected to a USB host, ++ * it will first disconnect(). The driver is also requested ++ * to unbind() and clean up any device state, before this procedure ++ * finally returns. ++ * This function must be called in a context that can sleep. ++ */ ++int usb_gadget_unregister_driver (struct usb_gadget_driver *driver); ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* utility to simplify dealing with string descriptors */ ++ ++/** ++ * struct usb_string - wraps a C string and its USB id ++ * @id:the (nonzero) ID for this string ++ * @s:the string, in UTF-8 encoding ++ * ++ * If you're using usb_gadget_get_string(), use this to wrap a string ++ * together with its ID. ++ */ ++struct usb_string { ++ u8 id; ++ const char *s; ++}; ++ ++/** ++ * struct usb_gadget_strings - a set of USB strings in a given language ++ * @language:identifies the strings' language (0x0409 for en-us) ++ * @strings:array of strings with their ids ++ * ++ * If you're using usb_gadget_get_string(), use this to wrap all the ++ * strings for a given language. ++ */ ++struct usb_gadget_strings { ++ u16 language; /* 0x0409 for en-us */ ++ struct usb_string *strings; ++}; ++ ++/* put descriptor for string with that id into buf (buflen >= 256) */ ++int usb_gadget_get_string (struct usb_gadget_strings *table, int id, u8 *buf); ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* utility to simplify managing config descriptors */ ++ ++/* write vector of descriptors into buffer */ ++int usb_descriptor_fillbuf(void *, unsigned, ++ const struct usb_descriptor_header **); ++ ++/* build config descriptor from single descriptor vector */ ++int usb_gadget_config_buf(const struct usb_config_descriptor *config, ++ void *buf, unsigned buflen, const struct usb_descriptor_header **desc); ++ ++/*-------------------------------------------------------------------------*/ ++ ++/* utility wrapping a simple endpoint selection policy */ ++ ++extern struct usb_ep *usb_ep_autoconfig (struct usb_gadget *, ++ struct usb_endpoint_descriptor *) __init; ++ ++extern void usb_ep_autoconfig_reset (struct usb_gadget *) __init; ++ ++#endif /* __KERNEL__ */ ++ ++#endif /* __LINUX_USB_GADGET_H */ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/include/linux/usb_gadgetfs.h kernel/include/linux/usb_gadgetfs.h +--- /tmp/kernel/include/linux/usb_gadgetfs.h 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/include/linux/usb_gadgetfs.h 2005-04-22 17:53:19.370549935 +0200 +@@ -0,0 +1,75 @@ ++ ++#include ++#include ++ ++#include ++ ++/* ++ * Filesystem based user-mode API to USB Gadget controller hardware ++ * ++ * Almost everything can be done with only read and write operations, ++ * on endpoint files found in one directory. They are configured by ++ * writing descriptors, and then may be used for normal stream style ++ * i/o requests. When ep0 is configured, the device can enumerate; ++ * when it's closed, the device disconnects from usb. ++ * ++ * Configuration and device descriptors get written to /dev/gadget/$CHIP, ++ * which may then be used to read usb_gadgetfs_event structs. The driver ++ * may activate endpoints as it handles SET_CONFIGURATION setup events, ++ * or earlier; writing endpoint descriptors to /dev/gadget/$ENDPOINT ++ * then performing data transfers by reading or writing. ++ */ ++ ++/* ++ * Events are delivered on the ep0 file descriptor, if the user mode driver ++ * reads from this file descriptor after writing the descriptors. Don't ++ * stop polling this descriptor, if you write that kind of driver. ++ */ ++ ++enum usb_gadgetfs_event_type { ++ GADGETFS_NOP = 0, ++ ++ GADGETFS_CONNECT, ++ GADGETFS_DISCONNECT, ++ GADGETFS_SETUP, ++ GADGETFS_SUSPEND, ++ // and likely more ! ++}; ++ ++struct usb_gadgetfs_event { ++ enum usb_gadgetfs_event_type type; ++ union { ++ // NOP, DISCONNECT, SUSPEND: nothing ++ // ... some hardware can't report disconnection ++ ++ // CONNECT: just the speed ++ enum usb_device_speed speed; ++ ++ // SETUP: packet; DATA phase i/o precedes next event ++ // (setup.bmRequestType & USB_DIR_IN) flags direction ++ // ... includes SET_CONFIGURATION, SET_INTERFACE ++ struct usb_ctrlrequest setup; ++ } u; ++}; ++ ++ ++/* endpoint ioctls */ ++ ++/* IN transfers may be reported to the gadget driver as complete ++ * when the fifo is loaded, before the host reads the data; ++ * OUT transfers may be reported to the host's "client" driver as ++ * complete when they're sitting in the FIFO unread. ++ * THIS returns how many bytes are "unclaimed" in the endpoint fifo ++ * (needed for precise fault handling, when the hardware allows it) ++ */ ++#define GADGETFS_FIFO_STATUS _IO('g',1) ++ ++/* discards any unclaimed data in the fifo. */ ++#define GADGETFS_FIFO_FLUSH _IO('g',2) ++ ++/* resets endpoint halt+toggle; used to implement set_interface. ++ * some hardware (like pxa2xx) can't support this. ++ */ ++#define GADGETFS_CLEAR_HALT _IO('g',3) ++ ++ +diff -x '*~' -x '.*' -r -N -u /tmp/kernel/include/linux/usb_scanner_ioctl.h kernel/include/linux/usb_scanner_ioctl.h +--- /tmp/kernel/include/linux/usb_scanner_ioctl.h 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/include/linux/usb_scanner_ioctl.h 2005-04-22 17:53:19.372549610 +0200 +@@ -0,0 +1,9 @@ ++/* USB Scanner IOCTLS */ ++ ++/* read vendor and product IDs from the scanner */ ++#define SCANNER_IOCTL_VENDOR _IOR('U', 0x20, int) ++#define SCANNER_IOCTL_PRODUCT _IOR('U', 0x21, int) ++/* send/recv a control message to the scanner */ ++#define SCANNER_IOCTL_CTRLMSG _IOWR('U', 0x22, struct usb_ctrlrequest ) ++ ++ diff --git a/recipes/linux/files/linux-2.4.18-list_move.patch b/recipes/linux/files/linux-2.4.18-list_move.patch new file mode 100644 index 0000000000..faec56330b --- /dev/null +++ b/recipes/linux/files/linux-2.4.18-list_move.patch @@ -0,0 +1,32 @@ +--- linux/include/linux/list.h~ 2001-12-21 17:42:03.000000000 +0000 ++++ linux/include/linux/list.h 2004-06-14 23:41:33.000000000 +0100 +@@ -105,6 +105,29 @@ + } + + /** ++ * list_move - delete from one list and add as another's head ++ * @list: the entry to move ++ * @head: the head that will precede our entry ++ */ ++static inline void list_move(struct list_head *list, struct list_head *head) ++{ ++ __list_del(list->prev, list->next); ++ list_add(list, head); ++} ++ ++/** ++ * list_move_tail - delete from one list and add as another's tail ++ * @list: the entry to move ++ * @head: the head that will follow our entry ++ */ ++static inline void list_move_tail(struct list_head *list, ++ struct list_head *head) ++{ ++ __list_del(list->prev, list->next); ++ list_add_tail(list, head); ++} ++ ++/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ diff --git a/recipes/linux/files/mipv6-1.1-v2.4.25.patch b/recipes/linux/files/mipv6-1.1-v2.4.25.patch new file mode 100644 index 0000000000..b7f9b8fc8a --- /dev/null +++ b/recipes/linux/files/mipv6-1.1-v2.4.25.patch @@ -0,0 +1,19832 @@ +diff -uprN linux-2.4.25.old/Documentation/Configure.help linux-2.4.25/Documentation/Configure.help +--- linux-2.4.25.old/Documentation/Configure.help 2004-06-26 11:22:00.000000000 +0100 ++++ linux-2.4.25/Documentation/Configure.help 2004-06-26 11:29:29.000000000 +0100 +@@ -6204,6 +6204,57 @@ CONFIG_IPV6 + + It is safe to say N here for now. + ++IPv6: IPv6 over IPv6 Tunneling (EXPERIMENTAL) ++CONFIG_IPV6_TUNNEL ++ Experimental IP6-IP6 tunneling. You must select this, if you want ++ to use CONFIG_IPV6_MOBILITY. More information in MIPL Mobile IPv6 ++ instructions. ++ ++ If you don't want IP6-IP6 tunnels and Mobile IPv6, say N. ++ ++IPv6: Mobility Support (EXPERIMENTAL) ++CONFIG_IPV6_MOBILITY ++ This is experimental support for the upcoming specification of ++ Mobile IPv6. Mobile IPv6 allows nodes to seamlessly move between ++ networks without changing their IP addresses, thus allowing them to ++ maintain upper layer connections (e.g. TCP). Selecting this option ++ allows your computer to act as a Correspondent Node (CN). A MIPv6 ++ Mobile Node will be able to communicate with the CN and use route ++ optimization. ++ ++ For more information and configuration details, see ++ http://www.mipl.mediapoli.com/. ++ ++ If unsure, say N. ++ ++MIPv6: Mobile Node Support ++CONFIG_IPV6_MOBILITY_MN ++ If you want your computer to be a MIPv6 Mobile Node (MN), select ++ this option. You must configure MN using the userspace tools ++ available at http://www.mipl.mediapoli.com/download/mipv6-tools/. ++ ++ If your computer is stationary, or you are unsure if you need this, ++ say N. Note that you will need a properly configured MIPv6 Home ++ Agent to use any Mobile Nodes. ++ ++MIPv6: Home Agent Support ++CONFIG_IPV6_MOBILITY_HA ++ If you want your router to serve as a MIPv6 Home Agent (HA), select ++ this option. You must configure HA using the userspace tools ++ available at http://www.mipl.mediapoli.com/download/mipv6-tools/. ++ ++ If your computer is not a router, or you are unsure if you need ++ this, say N. ++ ++MIPv6: Debug messages ++CONFIG_IPV6_MOBILITY_DEBUG ++ MIPL Mobile IPv6 can produce a lot of debugging messages. There are ++ eight debug levels (0 through 7) and the level is controlled via ++ /proc/sys/net/ipv6/mobility/debuglevel. Since MIPL is still ++ experimental, you might want to say Y here. ++ ++ Be sure to say Y and record debug messages when submitting a bug ++ report. + The SCTP Protocol (EXPERIMENTAL) + CONFIG_IP_SCTP + Stream Control Transmission Protocol +diff -uprN linux-2.4.25.old/Documentation/DocBook/Makefile linux-2.4.25/Documentation/DocBook/Makefile +--- linux-2.4.25.old/Documentation/DocBook/Makefile 2002-11-28 23:53:08.000000000 +0000 ++++ linux-2.4.25/Documentation/DocBook/Makefile 2004-06-26 11:29:29.000000000 +0100 +@@ -2,7 +2,7 @@ BOOKS := wanbook.sgml z8530book.sgml mca + kernel-api.sgml parportbook.sgml kernel-hacking.sgml \ + kernel-locking.sgml via-audio.sgml mousedrivers.sgml sis900.sgml \ + deviceiobook.sgml procfs-guide.sgml tulip-user.sgml \ +- journal-api.sgml ++ journal-api.sgml mip6-func.sgml + + PS := $(patsubst %.sgml, %.ps, $(BOOKS)) + PDF := $(patsubst %.sgml, %.pdf, $(BOOKS)) +@@ -86,6 +86,9 @@ videobook.sgml: videobook.tmpl $(TOPDIR) + procfs-guide.sgml: procfs-guide.tmpl procfs_example.sgml + $(TOPDIR)/scripts/docgen < procfs-guide.tmpl >$@ + ++mip6-func.sgml: mip6-func.tmpl ++ $(TOPDIR)/scripts/docgen <$< >$@ ++ + APISOURCES := $(TOPDIR)/drivers/media/video/videodev.c \ + $(TOPDIR)/arch/i386/kernel/irq.c \ + $(TOPDIR)/arch/i386/kernel/mca.c \ +diff -uprN linux-2.4.25.old/Documentation/DocBook/mip6-func.tmpl linux-2.4.25/Documentation/DocBook/mip6-func.tmpl +--- linux-2.4.25.old/Documentation/DocBook/mip6-func.tmpl 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/Documentation/DocBook/mip6-func.tmpl 2004-06-26 11:29:29.000000000 +0100 +@@ -0,0 +1,756 @@ ++ ++ ++ ++ MIPL Mobile IPv6 Function Reference Guide ++ ++ ++ ++ MIPL Mobile IPv6 for Linux Team ++ ++ Helsinki University of Technology ++ Telecommunications Software and Multimedia Lab ++
++ PO BOX 9201 ++ FIN-02015 HUT ++ Finland ++ mipl@list.mipl.mediapoli.com ++
++
++
++
++ ++ ++ 2000-2001 ++ Helsinki University of Technology ++ ++ ++ ++ ++ Copyright (c) 2001, 2002 MIPL Mobile IPv6 for Linux Team. ++ ++ ++ Permission is granted to copy, distribute and/or modify this ++ document under the terms of the GNU Free Documentation License, ++ Version 1.1 published by the Free Software Foundation; with the ++ Invariant Sections being "Introduction", with the Front-Cover ++ Texts being "MIPL Mobile IPv6 Function Reference Guide", "MIPL ++ Mobile IPv6 for Linux Team" and "Helsinki University of ++ Technology". A copy of the license is included in . ++ ++ ++ ++
++ ++ ++ ++ ++ Introduction ++ ++ ++ MIPL Mobile IPv6 for Linux is an implementation of Mobility ++ Support in IPv6 IETF mobile-ip working groups Internet-Draft ++ (draft-ietf-mobileip-ipv6). This implementation has been ++ developed in the Telecommunications Software and Multimedia ++ Laboratory at Helsinki University of Technology. ++ ++ ++ ++ MIPL is fully open source, licensed under the GNU General ++ Public License. Latest source for MIPL can be downloaded from ++ the MIPL website at: ++ ++ ++ http://www.mipl.mediapoli.com/. ++ ++ ++ Developers and users interested in MIPL can subscribe to the ++ MIPL mailing list by sending e-mail to ++ majordomo@list.mipl.mediapoli.com with ++ ++ ++ subscribe mipl ++ ++ ++ in the body of the message. ++ ++ ++ ++ This document is a reference guide to MIPL functions. Intended ++ audience is developers wishing to contribute to the project. ++ Hopefully this document will make it easier and quicker to ++ understand and adopt the inner workings of MIPL Mobile IPv6. ++ ++ ++ ++ MIPL Mobile IPv6 for Linux Team members (past and present): ++ ++ ++ ++
++ Sami Kivisaari Sami.Kivisaari@hut.fi ++
++
++ ++
++ Niklas Kampe Niklas.Kampe@hut.fi ++
++
++ ++
++ Juha Mynttinen Juha.Mynttinen@hut.fi ++
++
++ ++
++ Toni Nykanen Toni.Nykanen@iki.fi ++
++
++ ++
++ Henrik Petander Henrik.Petander@hut.fi ++
++
++ ++
++ Antti Tuominen ajtuomin@tml.hut.fi ++
++
++
++ ++ ++ ++
++ Marko Myllynen ++
++
++ ++
++ Ville Nuorvala vnuorval@tcs.hut.fi ++
++
++ ++
++ Jaakko Laine Jaakko.Laine@hut.fi ++
++
++
++
++ ++
++ ++ ++ Common functions for all entities ++ ++ Low-level functions ++ ++ These functions implement memory allocation used by others. ++ Hashlist functions implement a linked list with hash lookup, ++ which is used with Binding Update List, Binding Cache, Home ++ Agents List etc. ++ ++!Inet/ipv6/mobile_ip6/mempool.h ++!Inet/ipv6/mobile_ip6/hashlist.h ++ ++ ++ Debug functions ++ ++ Debug and utility functions. These functions are available if ++ CONFIG_IPV6_MOBILITY_DEBUG is set. ++ Otherwise macros expand to no operation. ++ ++!Inet/ipv6/mobile_ip6/debug.h ++!Inet/ipv6/mobile_ip6/mipv6.c ++ ++ ++ Extension Header functions ++ ++ These functions create and handle extension headers that are ++ specific to MIPv6. ++ ++!Inet/ipv6/mobile_ip6/exthdrs.c ++ ++ ++ Mobility Header functions ++ ++ MIPv6 specifies a new protocol called Mobility Header. ++ Mobility Header has several message types. Messages may also ++ carry Mobility Options. These functions are used to create and ++ handle Mobility Headers and Mobility Options. ++ ++!Inet/ipv6/mobile_ip6/sendopts.c ++!Inet/ipv6/mobile_ip6/mh_recv.c ++!Inet/ipv6/mobile_ip6/auth_subopt.c ++ ++ ++ Binding Cache ++ ++ All Mobile IPv6 entities have a binding cache. These functions ++ provide easy manipulation of the binding cache. ++ ++!Inet/ipv6/mobile_ip6/bcache.c ++ ++ ++ Security ++ ++ ++ These functions are common authentication functions and ++ implement Draft 13 style IPSec AH support for Binding Updates. ++ ++!Inet/ipv6/mobile_ip6/ah_algo.c ++!Inet/ipv6/mobile_ip6/sadb.c ++!Inet/ipv6/mobile_ip6/ah.c ++ ++ ++ Utility functions ++ ++ ++ These functions are general utility functions commonly used by ++ all entities. ++ ++!Inet/ipv6/mobile_ip6/util.c ++ ++ ++ ++ ++ ++ Mobile Node functions ++ General functions ++ ++ ++!Inet/ipv6/mobile_ip6/mn.c ++ ++ ++ Binding Update List ++ ++ Mobile Node keeps track of sent binding updates in Binding ++ Update List. ++ ++!Inet/ipv6/mobile_ip6/bul.c ++ ++ ++ Movement detection ++ ++ ++ These functions are used by the mobile node for movement ++ detection. ++ ++!Inet/ipv6/mobile_ip6/mdetect.c ++ ++ ++ ++ ++ Home Agent functions ++ General functions ++ ++ ++!Inet/ipv6/mobile_ip6/ha.c ++ ++ ++ Duplicate Address Detection functions ++ ++ Home Agent does Duplicate Address Detection for Mobile Nodes' ++ addresses. These functions implement MIPv6 specific DAD ++ functionality. ++ ++!Inet/ipv6/mobile_ip6/dad.c ++ ++ ++ ++ ++ GNU Free Documentation License ++ ++ ++ Version 1.1, March 2000 ++ ++ ++ ++ Copyright (C) 2000 Free Software Foundation, Inc. ++ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ Everyone is permitted to copy and distribute verbatim copies ++ of this license document, but changing it is not allowed. ++ ++ ++ 0. PREAMBLE ++ ++ ++ The purpose of this License is to make a manual, textbook, or ++ other written document "free" in the sense of freedom: to ++ assure everyone the effective freedom to copy and redistribute ++ it, with or without modifying it, either commercially or ++ noncommercially. Secondarily, this License preserves for the ++ author and publisher a way to get credit for their work, while ++ not being considered responsible for modifications made by ++ others. ++ ++ ++ ++ This License is a kind of "copyleft", which means that ++ derivative works of the document must themselves be free in the ++ same sense. It complements the GNU General Public License, ++ which is a copyleft license designed for free software. ++ ++ ++ ++ We have designed this License in order to use it for manuals ++ for free software, because free software needs free ++ documentation: a free program should come with manuals ++ providing the same freedoms that the software does. But this ++ License is not limited to software manuals; it can be used for ++ any textual work, regardless of subject matter or whether it is ++ published as a printed book. We recommend this License ++ principally for works whose purpose is instruction or ++ reference. ++ ++ ++ ++ 1. APPLICABILITY AND DEFINITIONS ++ ++ ++ This License applies to any manual or other work that contains ++ a notice placed by the copyright holder saying it can be ++ distributed under the terms of this License. The "Document", ++ below, refers to any such manual or work. Any member of the ++ public is a licensee, and is addressed as "you". ++ ++ ++ ++ A "Modified Version" of the Document means any work containing ++ the Document or a portion of it, either copied verbatim, or ++ with modifications and/or translated into another language. ++ ++ ++ ++ A "Secondary Section" is a named appendix or a front-matter ++ section of the Document that deals exclusively with the ++ relationship of the publishers or authors of the Document to ++ the Document's overall subject (or to related matters) and ++ contains nothing that could fall directly within that overall ++ subject. (For example, if the Document is in part a textbook of ++ mathematics, a Secondary Section may not explain any ++ mathematics.) The relationship could be a matter of historical ++ connection with the subject or with related matters, or of ++ legal, commercial, philosophical, ethical or political position ++ regarding them. ++ ++ ++ ++ The "Invariant Sections" are certain Secondary Sections whose ++ titles are designated, as being those of Invariant Sections, in ++ the notice that says that the Document is released under this ++ License. ++ ++ ++ ++ The "Cover Texts" are certain short passages of text that are ++ listed, as Front-Cover Texts or Back-Cover Texts, in the notice ++ that says that the Document is released under this License. ++ ++ ++ ++ A "Transparent" copy of the Document means a machine-readable ++ copy, represented in a format whose specification is available ++ to the general public, whose contents can be viewed and edited ++ directly and straightforwardly with generic text editors or ++ (for images composed of pixels) generic paint programs or (for ++ drawings) some widely available drawing editor, and that is ++ suitable for input to text formatters or for automatic ++ translation to a variety of formats suitable for input to text ++ formatters. A copy made in an otherwise Transparent file format ++ whose markup has been designed to thwart or discourage ++ subsequent modification by readers is not Transparent. A copy ++ that is not "Transparent" is called "Opaque". ++ ++ ++ ++ Examples of suitable formats for Transparent copies include ++ plain ASCII without markup, Texinfo input format, LaTeX input ++ format, SGML or XML using a publicly available DTD, and ++ standard-conforming simple HTML designed for human ++ modification. Opaque formats include PostScript, PDF, ++ proprietary formats that can be read and edited only by ++ proprietary word processors, SGML or XML for which the DTD ++ and/or processing tools are not generally available, and the ++ machine-generated HTML produced by some word processors for ++ output purposes only. ++ ++ ++ ++ The "Title Page" means, for a printed book, the title page ++ itself, plus such following pages as are needed to hold, ++ legibly, the material this License requires to appear in the ++ title page. For works in formats which do not have any title ++ page as such, "Title Page" means the text near the most ++ prominent appearance of the work's title, preceding the ++ beginning of the body of the text. ++ ++ ++ ++ 2. VERBATIM COPYING ++ ++ ++ You may copy and distribute the Document in any medium, either ++ commercially or noncommercially, provided that this License, ++ the copyright notices, and the license notice saying this ++ License applies to the Document are reproduced in all copies, ++ and that you add no other conditions whatsoever to those of ++ this License. You may not use technical measures to obstruct or ++ control the reading or further copying of the copies you make ++ or distribute. However, you may accept compensation in exchange ++ for copies. If you distribute a large enough number of copies ++ you must also follow the conditions in section 3. ++ ++ ++ ++ You may also lend copies, under the same conditions stated ++ above, and you may publicly display copies. ++ ++ ++ ++ 3. COPYING IN QUANTITY ++ ++ ++ If you publish printed copies of the Document numbering more ++ than 100, and the Document's license notice requires Cover ++ Texts, you must enclose the copies in covers that carry, ++ clearly and legibly, all these Cover Texts: Front-Cover Texts ++ on the front cover, and Back-Cover Texts on the back ++ cover. Both covers must also clearly and legibly identify you ++ as the publisher of these copies. The front cover must present ++ the full title with all words of the title equally prominent ++ and visible. You may add other material on the covers in ++ addition. Copying with changes limited to the covers, as long ++ as they preserve the title of the Document and satisfy these ++ conditions, can be treated as verbatim copying in other ++ respects. ++ ++ ++ ++ If the required texts for either cover are too voluminous to ++ fit legibly, you should put the first ones listed (as many as ++ fit reasonably) on the actual cover, and continue the rest onto ++ adjacent pages. ++ ++ ++ ++ If you publish or distribute Opaque copies of the Document ++ numbering more than 100, you must either include a ++ machine-readable Transparent copy along with each Opaque copy, ++ or state in or with each Opaque copy a publicly-accessible ++ computer-network location containing a complete Transparent ++ copy of the Document, free of added material, which the general ++ network-using public has access to download anonymously at no ++ charge using public-standard network protocols. If you use the ++ latter option, you must take reasonably prudent steps, when you ++ begin distribution of Opaque copies in quantity, to ensure that ++ this Transparent copy will remain thus accessible at the stated ++ location until at least one year after the last time you ++ distribute an Opaque copy (directly or through your agents or ++ retailers) of that edition to the public. ++ ++ ++ ++ It is requested, but not required, that you contact the authors ++ of the Document well before redistributing any large number of ++ copies, to give them a chance to provide you with an updated ++ version of the Document. ++ ++ ++ ++ 4. MODIFICATIONS ++ ++ ++ You may copy and distribute a Modified Version of the Document ++ under the conditions of sections 2 and 3 above, provided that ++ you release the Modified Version under precisely this License, ++ with the Modified Version filling the role of the Document, ++ thus licensing distribution and modification of the Modified ++ Version to whoever possesses a copy of it. In addition, you ++ must do these things in the Modified Version: ++ ++ ++ ++ ++ ++ ++ A. Use in the Title Page (and on the covers, if any) a title ++ distinct from that of the Document, and from those of previous ++ versions (which should, if there were any, be listed in the ++ History section of the Document). You may use the same title ++ as a previous version if the original publisher of that ++ version gives permission. ++ ++ ++ ++ ++ B. List on the Title Page, as authors, one or more persons ++ or entities responsible for authorship of the modifications in ++ the Modified Version, together with at least five of the ++ principal authors of the Document (all of its principal ++ authors, if it has less than five). ++ ++ ++ ++ ++ C. State on the Title page the name of the publisher of the ++ Modified Version, as the publisher. ++ ++ ++ ++ ++ D. Preserve all the copyright notices of the Document. ++ ++ ++ ++ ++ E. Add an appropriate copyright notice for your ++ modifications adjacent to the other copyright notices. ++ ++ ++ ++ ++ F. Include, immediately after the copyright notices, a ++ license notice giving the public permission to use the ++ Modified Version under the terms of this License, in the form ++ shown in the Addendum below. ++ ++ ++ ++ ++ G. Preserve in that license notice the full lists of ++ Invariant Sections and required Cover Texts given in the ++ Document's license notice. ++ ++ ++ ++ ++ H. Include an unaltered copy of this License. ++ ++ ++ ++ ++ I. Preserve the section entitled "History", and its title, ++ and add to it an item stating at least the title, year, new ++ authors, and publisher of the Modified Version as given on the ++ Title Page. If there is no section entitled "History" in the ++ Document, create one stating the title, year, authors, and ++ publisher of the Document as given on its Title Page, then add ++ an item describing the Modified Version as stated in the ++ previous sentence. ++ ++ ++ ++ ++ J. Preserve the network location, if any, given in the ++ Document for public access to a Transparent copy of the ++ Document, and likewise the network locations given in the ++ Document for previous versions it was based on. These may be ++ placed in the "History" section. You may omit a network ++ location for a work that was published at least four years ++ before the Document itself, or if the original publisher of ++ the version it refers to gives permission. ++ ++ ++ ++ ++ K. In any section entitled "Acknowledgements" or ++ "Dedications", preserve the section's title, and preserve in ++ the section all the substance and tone of each of the ++ contributor acknowledgements and/or dedications given therein. ++ ++ ++ ++ ++ L. Preserve all the Invariant Sections of the Document, ++ unaltered in their text and in their titles. Section numbers ++ or the equivalent are not considered part of the section ++ titles. ++ ++ ++ ++ ++ M. Delete any section entitled "Endorsements". Such a ++ section may not be included in the Modified Version. ++ ++ ++ ++ ++ N. Do not retitle any existing section as "Endorsements" or ++ to conflict in title with any Invariant Section. ++ ++ ++ ++ ++ ++ ++ If the Modified Version includes new front-matter sections or ++ appendices that qualify as Secondary Sections and contain no ++ material copied from the Document, you may at your option ++ designate some or all of these sections as invariant. To do ++ this, add their titles to the list of Invariant Sections in the ++ Modified Version's license notice. These titles must be ++ distinct from any other section titles. ++ ++ ++ ++ You may add a section entitled "Endorsements", provided it ++ contains nothing but endorsements of your Modified Version by ++ various parties--for example, statements of peer review or that ++ the text has been approved by an organization as the ++ authoritative definition of a standard. ++ ++ ++ ++ You may add a passage of up to five words as a Front-Cover ++ Text, and a passage of up to 25 words as a Back-Cover Text, to ++ the end of the list of Cover Texts in the Modified ++ Version. Only one passage of Front-Cover Text and one of ++ Back-Cover Text may be added by (or through arrangements made ++ by) any one entity. If the Document already includes a cover ++ text for the same cover, previously added by you or by ++ arrangement made by the same entity you are acting on behalf ++ of, you may not add another; but you may replace the old one, ++ on explicit permission from the previous publisher that added ++ the old one. ++ ++ ++ ++ The author(s) and publisher(s) of the Document do not by this ++ License give permission to use their names for publicity for or ++ to assert or imply endorsement of any Modified Version. ++ ++ ++ ++ 5. COMBINING DOCUMENTS ++ ++ ++ You may combine the Document with other documents released ++ under this License, under the terms defined in section 4 above ++ for modified versions, provided that you include in the ++ combination all of the Invariant Sections of all of the ++ original documents, unmodified, and list them all as Invariant ++ Sections of your combined work in its license notice. ++ ++ ++ ++ The combined work need only contain one copy of this License, ++ and multiple identical Invariant Sections may be replaced with ++ a single copy. If there are multiple Invariant Sections with ++ the same name but different contents, make the title of each ++ such section unique by adding at the end of it, in parentheses, ++ the name of the original author or publisher of that section if ++ known, or else a unique number. Make the same adjustment to the ++ section titles in the list of Invariant Sections in the license ++ notice of the combined work. ++ ++ ++ ++ In the combination, you must combine any sections entitled ++ "History" in the various original documents, forming one ++ section entitled "History"; likewise combine any sections ++ entitled "Acknowledgements", and any sections entitled ++ "Dedications". You must delete all sections entitled ++ "Endorsements." ++ ++ ++ ++ 6. COLLECTIONS OF DOCUMENTS ++ ++ ++ You may make a collection consisting of the Document and other ++ documents released under this License, and replace the ++ individual copies of this License in the various documents with ++ a single copy that is included in the collection, provided that ++ you follow the rules of this License for verbatim copying of ++ each of the documents in all other respects. ++ ++ ++ ++ You may extract a single document from such a collection, and ++ distribute it individually under this License, provided you ++ insert a copy of this License into the extracted document, and ++ follow this License in all other respects regarding verbatim ++ copying of that document. ++ ++ ++ ++ 7. AGGREGATION WITH INDEPENDENT WORKS ++ ++ ++ A compilation of the Document or its derivatives with other ++ separate and independent documents or works, in or on a volume ++ of a storage or distribution medium, does not as a whole count ++ as a Modified Version of the Document, provided no compilation ++ copyright is claimed for the compilation. Such a compilation is ++ called an "aggregate", and this License does not apply to the ++ other self-contained works thus compiled with the Document, on ++ account of their being thus compiled, if they are not ++ themselves derivative works of the Document. ++ ++ ++ ++ If the Cover Text requirement of section 3 is applicable to ++ these copies of the Document, then if the Document is less than ++ one quarter of the entire aggregate, the Document's Cover Texts ++ may be placed on covers that surround only the Document within ++ the aggregate. Otherwise they must appear on covers around the ++ whole aggregate. ++ ++ ++ ++ 8. TRANSLATION ++ ++ ++ Translation is considered a kind of modification, so you may ++ distribute translations of the Document under the terms of ++ section 4. Replacing Invariant Sections with translations ++ requires special permission from their copyright holders, but ++ you may include translations of some or all Invariant Sections ++ in addition to the original versions of these Invariant ++ Sections. You may include a translation of this License ++ provided that you also include the original English version of ++ this License. In case of a disagreement between the translation ++ and the original English version of this License, the original ++ English version will prevail. ++ ++ ++ ++ 9. TERMINATION ++ ++ ++ You may not copy, modify, sublicense, or distribute the ++ Document except as expressly provided for under this ++ License. Any other attempt to copy, modify, sublicense or ++ distribute the Document is void, and will automatically ++ terminate your rights under this License. However, parties who ++ have received copies, or rights, from you under this License ++ will not have their licenses terminated so long as such parties ++ remain in full compliance. ++ ++ ++ ++ 10. FUTURE REVISIONS OF THIS LICENSE ++ ++ ++ The Free Software Foundation may publish new, revised versions ++ of the GNU Free Documentation License from time to time. Such ++ new versions will be similar in spirit to the present version, ++ but may differ in detail to address new problems or ++ concerns. See http://www.gnu.org/copyleft/. ++ ++ ++ ++ Each version of the License is given a distinguishing version ++ number. If the Document specifies that a particular numbered ++ version of this License "or any later version" applies to it, ++ you have the option of following the terms and conditions ++ either of that specified version or of any later version that ++ has been published (not as a draft) by the Free Software ++ Foundation. If the Document does not specify a version number ++ of this License, you may choose any version ever published (not ++ as a draft) by the Free Software Foundation. ++ ++ ++ ++ ++
+diff -uprN linux-2.4.25.old/include/linux/icmpv6.h linux-2.4.25/include/linux/icmpv6.h +--- linux-2.4.25.old/include/linux/icmpv6.h 2003-08-25 12:44:44.000000000 +0100 ++++ linux-2.4.25/include/linux/icmpv6.h 2004-06-26 11:29:29.000000000 +0100 +@@ -40,14 +40,16 @@ struct icmp6hdr { + struct icmpv6_nd_ra { + __u8 hop_limit; + #if defined(__LITTLE_ENDIAN_BITFIELD) +- __u8 reserved:6, ++ __u8 reserved:5, ++ home_agent:1, + other:1, + managed:1; + + #elif defined(__BIG_ENDIAN_BITFIELD) + __u8 managed:1, + other:1, +- reserved:6; ++ home_agent:1, ++ reserved:5; + #else + #error "Please fix " + #endif +@@ -70,6 +72,7 @@ struct icmp6hdr { + #define icmp6_addrconf_managed icmp6_dataun.u_nd_ra.managed + #define icmp6_addrconf_other icmp6_dataun.u_nd_ra.other + #define icmp6_rt_lifetime icmp6_dataun.u_nd_ra.rt_lifetime ++#define icmp6_home_agent icmp6_dataun.u_nd_ra.home_agent + }; + + +diff -uprN linux-2.4.25.old/include/linux/if_arp.h linux-2.4.25/include/linux/if_arp.h +--- linux-2.4.25.old/include/linux/if_arp.h 2002-02-25 19:38:13.000000000 +0000 ++++ linux-2.4.25/include/linux/if_arp.h 2004-06-26 11:29:29.000000000 +0100 +@@ -59,7 +59,7 @@ + #define ARPHRD_RAWHDLC 518 /* Raw HDLC */ + + #define ARPHRD_TUNNEL 768 /* IPIP tunnel */ +-#define ARPHRD_TUNNEL6 769 /* IPIP6 tunnel */ ++#define ARPHRD_TUNNEL6 769 /* IP6IP6 tunnel */ + #define ARPHRD_FRAD 770 /* Frame Relay Access Device */ + #define ARPHRD_SKIP 771 /* SKIP vif */ + #define ARPHRD_LOOPBACK 772 /* Loopback device */ +diff -uprN linux-2.4.25.old/include/linux/in6.h linux-2.4.25/include/linux/in6.h +--- linux-2.4.25.old/include/linux/in6.h 2003-06-13 15:51:38.000000000 +0100 ++++ linux-2.4.25/include/linux/in6.h 2004-06-26 11:29:29.000000000 +0100 +@@ -142,6 +142,11 @@ struct in6_flowlabel_req + #define IPV6_TLV_JUMBO 194 + + /* ++ * Mobile IPv6 TLV options. ++ */ ++#define MIPV6_TLV_HOMEADDR 201 ++ ++/* + * IPV6 socket options + */ + +diff -uprN linux-2.4.25.old/include/linux/ipv6.h linux-2.4.25/include/linux/ipv6.h +--- linux-2.4.25.old/include/linux/ipv6.h 2003-11-28 18:26:21.000000000 +0000 ++++ linux-2.4.25/include/linux/ipv6.h 2004-06-26 11:29:29.000000000 +0100 +@@ -29,6 +29,7 @@ struct in6_ifreq { + + #define IPV6_SRCRT_STRICT 0x01 /* this hop must be a neighbor */ + #define IPV6_SRCRT_TYPE_0 0 /* IPv6 type 0 Routing Header */ ++#define IPV6_SRCRT_TYPE_2 2 /* type 2 for Mobile IPv6 */ + + /* + * routing header +@@ -71,6 +72,19 @@ struct rt0_hdr { + struct in6_addr addr[0]; + + #define rt0_type rt_hdr.type ++ ++}; ++ ++/* ++ * routing header type 2 ++ */ ++ ++struct rt2_hdr { ++ struct ipv6_rt_hdr rt_hdr; ++ __u32 reserved; ++ struct in6_addr addr; ++ ++#define rt2_type rt_hdr.type; + }; + + /* +@@ -156,12 +170,16 @@ enum { + struct inet6_skb_parm + { + int iif; ++ __u8 mipv6_flags; + __u16 ra; + __u16 hop; + __u16 auth; + __u16 dst0; + __u16 srcrt; ++ __u16 srcrt2; ++ __u16 hao; + __u16 dst1; ++ struct in6_addr hoa; + }; + + #endif +diff -uprN linux-2.4.25.old/include/linux/ipv6_route.h linux-2.4.25/include/linux/ipv6_route.h +--- linux-2.4.25.old/include/linux/ipv6_route.h 2003-11-28 18:26:21.000000000 +0000 ++++ linux-2.4.25/include/linux/ipv6_route.h 2004-06-26 11:29:29.000000000 +0100 +@@ -33,6 +33,7 @@ enum + #define RTF_CACHE 0x01000000 /* cache entry */ + #define RTF_FLOW 0x02000000 /* flow significant route */ + #define RTF_POLICY 0x04000000 /* policy route */ ++#define RTF_MOBILENODE 0x10000000 /* for routing to Mobile Node */ + + #define RTF_LOCAL 0x80000000 + +diff -uprN linux-2.4.25.old/include/linux/ipv6_tunnel.h linux-2.4.25/include/linux/ipv6_tunnel.h +--- linux-2.4.25.old/include/linux/ipv6_tunnel.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/include/linux/ipv6_tunnel.h 2004-06-26 11:29:29.000000000 +0100 +@@ -0,0 +1,34 @@ ++/* ++ * $Id$ ++ */ ++ ++#ifndef _IPV6_TUNNEL_H ++#define _IPV6_TUNNEL_H ++ ++#define IPV6_TLV_TNL_ENCAP_LIMIT 4 ++#define IPV6_DEFAULT_TNL_ENCAP_LIMIT 4 ++ ++/* don't add encapsulation limit if one isn't present in inner packet */ ++#define IP6_TNL_F_IGN_ENCAP_LIMIT 0x1 ++/* copy the traffic class field from the inner packet */ ++#define IP6_TNL_F_USE_ORIG_TCLASS 0x2 ++/* copy the flowlabel from the inner packet */ ++#define IP6_TNL_F_USE_ORIG_FLOWLABEL 0x4 ++/* created and maintained from within the kernel */ ++#define IP6_TNL_F_KERNEL_DEV 0x8 ++/* being used for Mobile IPv6 */ ++#define IP6_TNL_F_MIP6_DEV 0x10 ++ ++struct ip6_tnl_parm { ++ char name[IFNAMSIZ]; /* name of tunnel device */ ++ int link; /* ifindex of underlying L2 interface */ ++ __u8 proto; /* tunnel protocol */ ++ __u8 encap_limit; /* encapsulation limit for tunnel */ ++ __u8 hop_limit; /* hop limit for tunnel */ ++ __u32 flowinfo; /* traffic class and flowlabel for tunnel */ ++ __u32 flags; /* tunnel flags */ ++ struct in6_addr laddr; /* local tunnel end-point address */ ++ struct in6_addr raddr; /* remote tunnel end-point address */ ++}; ++ ++#endif +diff -uprN linux-2.4.25.old/include/linux/rtnetlink.h linux-2.4.25/include/linux/rtnetlink.h +--- linux-2.4.25.old/include/linux/rtnetlink.h 2004-02-18 13:36:32.000000000 +0000 ++++ linux-2.4.25/include/linux/rtnetlink.h 2004-06-26 11:29:29.000000000 +0100 +@@ -309,15 +309,17 @@ enum + IFA_LABEL, + IFA_BROADCAST, + IFA_ANYCAST, +- IFA_CACHEINFO ++ IFA_CACHEINFO, ++ IFA_HOMEAGENT + }; + +-#define IFA_MAX IFA_CACHEINFO ++#define IFA_MAX IFA_HOMEAGENT + + /* ifa_flags */ + + #define IFA_F_SECONDARY 0x01 + ++#define IFA_F_HOMEADDR 0x10 + #define IFA_F_DEPRECATED 0x20 + #define IFA_F_TENTATIVE 0x40 + #define IFA_F_PERMANENT 0x80 +diff -uprN linux-2.4.25.old/include/linux/skbuff.h linux-2.4.25/include/linux/skbuff.h +--- linux-2.4.25.old/include/linux/skbuff.h 2003-08-25 12:44:44.000000000 +0100 ++++ linux-2.4.25/include/linux/skbuff.h 2004-06-26 11:29:29.000000000 +0100 +@@ -177,7 +177,7 @@ struct sk_buff { + * want to keep them across layers you have to do a skb_clone() + * first. This is owned by whoever has the skb queued ATM. + */ +- char cb[48]; ++ char cb[64]; + + unsigned int len; /* Length of actual data */ + unsigned int data_len; +diff -uprN linux-2.4.25.old/include/linux/sysctl.h linux-2.4.25/include/linux/sysctl.h +--- linux-2.4.25.old/include/linux/sysctl.h 2004-02-18 13:36:32.000000000 +0000 ++++ linux-2.4.25/include/linux/sysctl.h 2004-06-26 11:29:29.000000000 +0100 +@@ -387,7 +387,24 @@ enum { + NET_IPV6_NEIGH=17, + NET_IPV6_ROUTE=18, + NET_IPV6_ICMP=19, +- NET_IPV6_BINDV6ONLY=20 ++ NET_IPV6_BINDV6ONLY=20, ++ NET_IPV6_MOBILITY=26 ++}; ++ ++/* /proc/sys/net/ipv6/mobility */ ++enum { ++ NET_IPV6_MOBILITY_DEBUG=1, ++ NET_IPV6_MOBILITY_TUNNEL_SITELOCAL=2, ++ NET_IPV6_MOBILITY_ROUTER_SOLICITATION_MAX_SENDTIME=3, ++ NET_IPV6_MOBILITY_ROUTER_REACH=4, ++ NET_IPV6_MOBILITY_MDETECT_MECHANISM=5, ++ NET_IPV6_MOBILITY_RETROUT=6, ++ NET_IPV6_MOBILITY_MAX_TNLS=7, ++ NET_IPV6_MOBILITY_MIN_TNLS=8, ++ NET_IPV6_MOBILITY_BINDING_REFRESH=9, ++ NET_IPV6_MOBILITY_BU_F_LLADDR=10, ++ NET_IPV6_MOBILITY_BU_F_KEYMGM=11, ++ NET_IPV6_MOBILITY_BU_F_CN_ACK=12 + }; + + enum { +diff -uprN linux-2.4.25.old/include/net/addrconf.h linux-2.4.25/include/net/addrconf.h +--- linux-2.4.25.old/include/net/addrconf.h 2003-08-25 12:44:44.000000000 +0100 ++++ linux-2.4.25/include/net/addrconf.h 2004-06-26 11:29:29.000000000 +0100 +@@ -16,9 +16,11 @@ struct prefix_info { + #if defined(__BIG_ENDIAN_BITFIELD) + __u8 onlink : 1, + autoconf : 1, +- reserved : 6; ++ router_address : 1, ++ reserved : 5; + #elif defined(__LITTLE_ENDIAN_BITFIELD) +- __u8 reserved : 6, ++ __u8 reserved : 5, ++ router_address : 1, + autoconf : 1, + onlink : 1; + #else +@@ -55,6 +57,7 @@ extern int ipv6_chk_addr(struct in6_ad + struct net_device *dev); + extern struct inet6_ifaddr * ipv6_get_ifaddr(struct in6_addr *addr, + struct net_device *dev); ++extern void ipv6_del_addr(struct inet6_ifaddr *ifp); + extern int ipv6_get_saddr(struct dst_entry *dst, + struct in6_addr *daddr, + struct in6_addr *saddr); +@@ -85,7 +88,9 @@ extern void ipv6_mc_up(struct inet6_dev + extern void ipv6_mc_down(struct inet6_dev *idev); + extern void ipv6_mc_init_dev(struct inet6_dev *idev); + extern void ipv6_mc_destroy_dev(struct inet6_dev *idev); ++extern void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags); + extern void addrconf_dad_failure(struct inet6_ifaddr *ifp); ++extern void addrconf_dad_completed(struct inet6_ifaddr *ifp); + + extern int ipv6_chk_mcast_addr(struct net_device *dev, struct in6_addr *group, + struct in6_addr *src_addr); +@@ -116,6 +121,9 @@ extern int ipv6_chk_acast_addr(struct + extern int register_inet6addr_notifier(struct notifier_block *nb); + extern int unregister_inet6addr_notifier(struct notifier_block *nb); + ++extern int ipv6_generate_eui64(u8 *eui, struct net_device *dev); ++extern int ipv6_inherit_eui64(u8 *eui, struct inet6_dev *idev); ++ + static inline struct inet6_dev * + __in6_dev_get(struct net_device *dev) + { +diff -uprN linux-2.4.25.old/include/net/ip6_route.h linux-2.4.25/include/net/ip6_route.h +--- linux-2.4.25.old/include/net/ip6_route.h 2003-06-13 15:51:39.000000000 +0100 ++++ linux-2.4.25/include/net/ip6_route.h 2004-06-26 11:29:29.000000000 +0100 +@@ -2,6 +2,7 @@ + #define _NET_IP6_ROUTE_H + + #define IP6_RT_PRIO_FW 16 ++#define IP6_RT_PRIO_MIPV6 64 + #define IP6_RT_PRIO_USER 1024 + #define IP6_RT_PRIO_ADDRCONF 256 + #define IP6_RT_PRIO_KERN 512 +@@ -40,6 +41,9 @@ extern int ipv6_route_ioctl(unsigned i + + extern int ip6_route_add(struct in6_rtmsg *rtmsg, + struct nlmsghdr *); ++ ++extern int ip6_route_del(struct in6_rtmsg *rtmsg, ++ struct nlmsghdr *); + extern int ip6_del_rt(struct rt6_info *, + struct nlmsghdr *); + +@@ -99,7 +103,8 @@ extern rwlock_t rt6_lock; + */ + + static inline void ip6_dst_store(struct sock *sk, struct dst_entry *dst, +- struct in6_addr *daddr) ++ struct in6_addr *daddr, ++ struct in6_addr *saddr) + { + struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6; + struct rt6_info *rt = (struct rt6_info *) dst; +@@ -107,6 +112,9 @@ static inline void ip6_dst_store(struct + write_lock(&sk->dst_lock); + __sk_dst_set(sk, dst); + np->daddr_cache = daddr; ++#ifdef CONFIG_IPV6_SUBTREES ++ np->saddr_cache = saddr; ++#endif + np->dst_cookie = rt->rt6i_node ? rt->rt6i_node->fn_sernum : 0; + write_unlock(&sk->dst_lock); + } +diff -uprN linux-2.4.25.old/include/net/ipv6.h linux-2.4.25/include/net/ipv6.h +--- linux-2.4.25.old/include/net/ipv6.h 2003-11-28 18:26:21.000000000 +0000 ++++ linux-2.4.25/include/net/ipv6.h 2004-06-26 11:29:29.000000000 +0100 +@@ -37,6 +37,7 @@ + #define NEXTHDR_ICMP 58 /* ICMP for IPv6. */ + #define NEXTHDR_NONE 59 /* No next header */ + #define NEXTHDR_DEST 60 /* Destination options header. */ ++#define NEXTHDR_MH 135 /* Mobility header, RFC 3775 */ + + #define NEXTHDR_MAX 255 + +@@ -145,9 +146,12 @@ struct ipv6_txoptions + __u16 opt_flen; /* after fragment hdr */ + __u16 opt_nflen; /* before fragment hdr */ + ++ __u8 mipv6_flags; /* flags set by MIPv6 */ ++ + struct ipv6_opt_hdr *hopopt; + struct ipv6_opt_hdr *dst0opt; +- struct ipv6_rt_hdr *srcrt; /* Routing Header */ ++ struct ipv6_rt_hdr *srcrt; /* Routing Header Type 0 */ ++ struct ipv6_rt_hdr *srcrt2; /* Routing Header Type 2 */ + struct ipv6_opt_hdr *auth; + struct ipv6_opt_hdr *dst1opt; + +@@ -256,6 +260,38 @@ static inline int ipv6_addr_any(const st + a->s6_addr32[2] | a->s6_addr32[3] ) == 0); + } + ++static inline void ipv6_addr_prefix(struct in6_addr *pfx, ++ const struct in6_addr *addr, int plen) ++{ ++ /* caller must guarantee 0 <= plen <= 128 */ ++ int o = plen >> 3, ++ b = plen & 0x7; ++ ++ memcpy(pfx->s6_addr, addr, o); ++ if (b != 0) { ++ pfx->s6_addr[o] = addr->s6_addr[o] & (0xff00 >> b); ++ o++; ++ } ++ if (o < 16) ++ memset(pfx->s6_addr + o, 0, 16 - o); ++} ++ ++static inline int ipv6_prefix_cmp(const struct in6_addr *p1, ++ const struct in6_addr *p2, int plen) ++{ ++ int b = plen&0x7; ++ int o = plen>>3; ++ int res = 0; ++ ++ if (o > 0) ++ res = memcmp(&p1->s6_addr[0], &p2->s6_addr[0], o); ++ if (res == 0 && b > 0) { ++ __u8 m = (0xff00 >> b) & 0xff; ++ res = (p1->s6_addr[o] & m) - (p2->s6_addr[o] & m); ++ } ++ return res; ++} ++ + /* + * Prototypes exported by ipv6 + */ +diff -uprN linux-2.4.25.old/include/net/ipv6_tunnel.h linux-2.4.25/include/net/ipv6_tunnel.h +--- linux-2.4.25.old/include/net/ipv6_tunnel.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/include/net/ipv6_tunnel.h 2004-06-26 11:29:29.000000000 +0100 +@@ -0,0 +1,92 @@ ++/* ++ * $Id$ ++ */ ++ ++#ifndef _NET_IPV6_TUNNEL_H ++#define _NET_IPV6_TUNNEL_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++/* capable of sending packets */ ++#define IP6_TNL_F_CAP_XMIT 0x10000 ++/* capable of receiving packets */ ++#define IP6_TNL_F_CAP_RCV 0x20000 ++ ++#define IP6_TNL_MAX 128 ++ ++/* IPv6 tunnel */ ++ ++struct ip6_tnl { ++ struct ip6_tnl *next; /* next tunnel in list */ ++ struct net_device *dev; /* virtual device associated with tunnel */ ++ struct net_device_stats stat; /* statistics for tunnel device */ ++ int recursion; /* depth of hard_start_xmit recursion */ ++ struct ip6_tnl_parm parms; /* tunnel configuration paramters */ ++ struct flowi fl; /* flowi template for xmit */ ++ atomic_t refcnt; /* nr of identical tunnels used by kernel */ ++ struct socket *sock; ++}; ++ ++#define IP6_TNL_PRE_ENCAP 0 ++#define IP6_TNL_PRE_DECAP 1 ++#define IP6_TNL_MAXHOOKS 2 ++ ++#define IP6_TNL_DROP 0 ++#define IP6_TNL_ACCEPT 1 ++ ++typedef int ip6_tnl_hookfn(struct ip6_tnl *t, struct sk_buff *skb); ++ ++struct ip6_tnl_hook_ops { ++ struct list_head list; ++ unsigned int hooknum; ++ int priority; ++ ip6_tnl_hookfn *hook; ++}; ++ ++enum ip6_tnl_hook_priorities { ++ IP6_TNL_PRI_FIRST = INT_MIN, ++ IP6_TNL_PRI_LAST = INT_MAX ++}; ++ ++/* Tunnel encapsulation limit destination sub-option */ ++ ++struct ipv6_tlv_tnl_enc_lim { ++ __u8 type; /* type-code for option */ ++ __u8 length; /* option length */ ++ __u8 encap_limit; /* tunnel encapsulation limit */ ++} __attribute__ ((packed)); ++ ++#ifdef __KERNEL__ ++extern int ip6ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt); ++ ++extern struct ip6_tnl *ip6ip6_tnl_lookup(struct in6_addr *remote, ++ struct in6_addr *local); ++ ++void ip6ip6_tnl_change(struct ip6_tnl *t, struct ip6_tnl_parm *p); ++ ++extern int ip6ip6_kernel_tnl_add(struct ip6_tnl_parm *p); ++ ++extern int ip6ip6_kernel_tnl_del(struct ip6_tnl *t); ++ ++extern unsigned int ip6ip6_tnl_inc_max_kdev_count(unsigned int n); ++ ++extern unsigned int ip6ip6_tnl_dec_max_kdev_count(unsigned int n); ++ ++extern unsigned int ip6ip6_tnl_inc_min_kdev_count(unsigned int n); ++ ++extern unsigned int ip6ip6_tnl_dec_min_kdev_count(unsigned int n); ++ ++extern void ip6ip6_tnl_register_hook(struct ip6_tnl_hook_ops *reg); ++ ++extern void ip6ip6_tnl_unregister_hook(struct ip6_tnl_hook_ops *reg); ++ ++#ifdef CONFIG_IPV6_TUNNEL ++extern int __init ip6_tunnel_init(void); ++extern void ip6_tunnel_cleanup(void); ++#endif ++#endif ++#endif +diff -uprN linux-2.4.25.old/include/net/mipglue.h linux-2.4.25/include/net/mipglue.h +--- linux-2.4.25.old/include/net/mipglue.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/include/net/mipglue.h 2004-06-26 11:29:29.000000000 +0100 +@@ -0,0 +1,266 @@ ++/* ++ * Glue for Mobility support integration to IPv6 ++ * ++ * Authors: ++ * Antti Tuominen ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#ifndef _NET_MIPGLUE_H ++#define _NET_MIPGLUE_H ++ ++#ifndef USE_IPV6_MOBILITY ++#if defined(CONFIG_IPV6_MOBILITY) || defined(CONFIG_IPV6_MOBILITY_MODULE) ++#define USE_IPV6_MOBILITY ++#endif ++#endif ++ ++/* symbols to indicate whether destination options received should take ++ * effect or not (see exthdrs.c, procrcv.c) ++ */ ++#define MIPV6_DSTOPTS_ACCEPT 1 ++#define MIPV6_DSTOPTS_DISCARD 0 ++ ++#define MIPV6_IGN_RTR 0 ++#define MIPV6_ADD_RTR 1 ++#define MIPV6_CHG_RTR 2 ++ ++/* MIPV6: Approximate maximum for mobile IPv6 options and headers */ ++#define MIPV6_HEADERS 48 ++ ++#ifdef __KERNEL__ ++#include ++#include ++#include ++ ++struct sk_buff; ++struct ndisc_options; ++struct sock; ++struct ipv6_txoptions; ++struct flowi; ++struct dst_entry; ++struct in6_addr; ++struct inet6_ifaddr; ++ ++#ifdef USE_IPV6_MOBILITY ++ ++/* calls a procedure from mipv6-module */ ++#define MIPV6_CALLPROC(X) if(mipv6_functions.X) mipv6_functions.X ++ ++/* calls a function from mipv6-module, default-value if function not defined ++ */ ++#define MIPV6_CALLFUNC(X,Y) (!mipv6_functions.X)?(Y):mipv6_functions.X ++ ++/* sets a handler-function to process a call */ ++#define MIPV6_SETCALL(X,Y) if(mipv6_functions.X) printk("mipv6: Warning, function assigned twice!\n"); \ ++ mipv6_functions.X = Y ++#define MIPV6_RESETCALL(X) mipv6_functions.X = NULL ++ ++/* pointers to mipv6 callable functions */ ++struct mipv6_callable_functions { ++ void (*mipv6_initialize_dstopt_rcv) (struct sk_buff *skb); ++ int (*mipv6_finalize_dstopt_rcv) (int process); ++ int (*mipv6_handle_homeaddr) (struct sk_buff *skb, int optoff); ++ int (*mipv6_ra_rcv) (struct sk_buff *skb, ++ struct ndisc_options *ndopts); ++ void (*mipv6_icmp_rcv) (struct sk_buff *skb); ++ struct ipv6_txoptions * (*mipv6_modify_txoptions) ( ++ struct sock *sk, ++ struct sk_buff *skb, ++ struct ipv6_txoptions *opt, ++ struct flowi *fl, ++ struct dst_entry **dst); ++ void (*mipv6_set_home) (int ifindex, struct in6_addr *homeaddr, ++ int plen, struct in6_addr *homeagent, ++ int plen2); ++ void (*mipv6_get_home_address) (struct in6_addr *home_addr); ++ void (*mipv6_get_care_of_address)(struct in6_addr *homeaddr, ++ struct in6_addr *coa); ++ int (*mipv6_is_home_addr)(struct in6_addr *addr); ++ void (*mipv6_change_router)(void); ++ void (*mipv6_check_dad)(struct in6_addr *home_addr); ++ void (*mipv6_icmp_swap_addrs)(struct sk_buff *skb); ++ int (*mipv6_forward)(struct sk_buff *skb); ++ int (*mipv6_mn_ha_probe)(struct inet6_ifaddr *ifp, u8 *lladdr); ++}; ++ ++extern struct mipv6_callable_functions mipv6_functions; ++ ++extern void mipv6_invalidate_calls(void); ++ ++extern int mipv6_handle_dstopt(struct sk_buff *skb, int optoff); ++ ++static inline int ++ndisc_mip_mn_ha_probe(struct inet6_ifaddr *ifp, u8 *lladdr) ++{ ++ return MIPV6_CALLFUNC(mipv6_mn_ha_probe, 0)(ifp, lladdr); ++} ++ ++/* Must only be called for HA, no checks here */ ++static inline int ip6_mipv6_forward(struct sk_buff *skb) ++{ ++ return MIPV6_CALLFUNC(mipv6_forward, 0)(skb); ++} ++ ++/* ++ * Avoid adding new default routers if the old one is still in use ++ */ ++ ++static inline int ndisc_mipv6_ra_rcv(struct sk_buff *skb, ++ struct ndisc_options *ndopts) ++{ ++ return MIPV6_CALLFUNC(mipv6_ra_rcv, MIPV6_ADD_RTR)(skb, ndopts); ++} ++ ++static inline int ipv6_chk_mip_home_addr(struct in6_addr *addr) ++{ ++ return MIPV6_CALLFUNC(mipv6_is_home_addr, 0)(addr); ++} ++ ++static inline void ndisc_mipv6_change_router(int change_rtr) ++{ ++ if (change_rtr == MIPV6_CHG_RTR) ++ MIPV6_CALLPROC(mipv6_change_router)(); ++} ++ ++static inline void ndisc_check_mipv6_dad(struct in6_addr *target) ++{ ++ MIPV6_CALLPROC(mipv6_check_dad)(target); ++} ++ ++static inline void icmpv6_swap_mipv6_addrs(struct sk_buff *skb) ++{ ++ MIPV6_CALLPROC(mipv6_icmp_swap_addrs)(skb); ++} ++ ++static inline void mipv6_icmp_rcv(struct sk_buff *skb) ++{ ++ MIPV6_CALLPROC(mipv6_icmp_rcv)(skb); ++} ++ ++static inline int tcp_v6_get_mipv6_header_len(void) ++{ ++ return MIPV6_HEADERS; ++} ++ ++static inline struct in6_addr * ++mipv6_get_fake_hdr_daddr(struct in6_addr *hdaddr, struct in6_addr *daddr) ++{ ++ return daddr; ++} ++ ++static inline void ++addrconf_set_mipv6_mn_home(int ifindex, struct in6_addr *homeaddr, int plen, ++ struct in6_addr *homeagent, int plen2) ++{ ++ MIPV6_CALLPROC(mipv6_set_home)(ifindex, homeaddr, plen, homeagent, plen2); ++} ++ ++static inline void addrconf_get_mipv6_home_address(struct in6_addr *saddr) ++{ ++ MIPV6_CALLPROC(mipv6_get_home_address)(saddr); ++} ++ ++static inline struct ipv6_txoptions * ++ip6_add_mipv6_txoptions(struct sock *sk, struct sk_buff *skb, ++ struct ipv6_txoptions *opt, struct flowi *fl, ++ struct dst_entry **dst) ++{ ++ return MIPV6_CALLFUNC(mipv6_modify_txoptions, opt)(sk, skb, opt, fl, dst); ++ ++} ++ ++static inline void ++ip6_mark_mipv6_packet(struct ipv6_txoptions *txopt, struct sk_buff *skb) ++{ ++ struct inet6_skb_parm *opt; ++ if (txopt) { ++ opt = (struct inet6_skb_parm *)skb->cb; ++ opt->mipv6_flags = txopt->mipv6_flags; ++ } ++} ++ ++static inline void ++ip6_free_mipv6_txoptions(struct ipv6_txoptions *opt, ++ struct ipv6_txoptions *orig_opt) ++{ ++ if (opt && opt != orig_opt) ++ kfree(opt); ++} ++ ++#else /* USE_IPV6_MOBILITY */ ++ ++#define mipv6_handle_dstopt ip6_tlvopt_unknown ++ ++static inline int ++ndisc_mip_mn_ha_probe(struct inet6_ifaddr *ifp, u8 *lladdr) ++{ ++ return 0; ++} ++ ++static inline int ip6_mipv6_forward(struct sk_buff *skb) ++{ ++ return 0; ++} ++ ++static inline int ndisc_mipv6_ra_rcv(struct sk_buff *skb, ++ struct ndisc_options *ndopts) ++{ ++ return MIPV6_ADD_RTR; ++} ++ ++static inline int ipv6_chk_mip_home_addr(struct in6_addr *addr) ++{ ++ return 0; ++} ++ ++static inline void ndisc_mipv6_change_router(int change_rtr) {} ++ ++static inline void ndisc_check_mipv6_dad(struct in6_addr *target) {} ++ ++static inline void icmpv6_swap_mipv6_addrs(struct sk_buff *skb) {} ++ ++static inline void mipv6_icmp_rcv(struct sk_buff *skb) {} ++ ++static inline int tcp_v6_get_mipv6_header_len(void) ++{ ++ return 0; ++} ++ ++static inline struct in6_addr * ++mipv6_get_fake_hdr_daddr(struct in6_addr *hdaddr, struct in6_addr *daddr) ++{ ++ return hdaddr; ++} ++ ++static inline void ++addrconf_set_mipv6_mn_home(int ifindex, struct in6_addr *homeaddr, int plen, ++ struct in6_addr *homeagent, int plen2) {} ++ ++static inline void addrconf_get_mipv6_home_address(struct in6_addr *saddr) {} ++ ++static inline struct ipv6_txoptions * ++ip6_add_mipv6_txoptions(struct sock *sk, struct sk_buff *skb, ++ struct ipv6_txoptions *opt, struct flowi *fl, ++ struct dst_entry **dst) ++{ ++ return opt; ++} ++ ++static inline void ++ip6_mark_mipv6_packet(struct ipv6_txoptions *txopt, struct sk_buff *skb) {} ++ ++static inline void ++ip6_free_mipv6_txoptions(struct ipv6_txoptions *opt, ++ struct ipv6_txoptions *orig_opt) {} ++ ++#endif /* USE_IPV6_MOBILITY */ ++#endif /* __KERNEL__ */ ++#endif /* _NET_MIPGLUE_H */ +diff -uprN linux-2.4.25.old/include/net/mipv6.h linux-2.4.25/include/net/mipv6.h +--- linux-2.4.25.old/include/net/mipv6.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/include/net/mipv6.h 2004-06-26 11:29:29.000000000 +0100 +@@ -0,0 +1,258 @@ ++/* ++ * Mobile IPv6 header-file ++ * ++ * Authors: ++ * Sami Kivisaari ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#ifndef _NET_MIPV6_H ++#define _NET_MIPV6_H ++ ++#include ++#include ++#include ++ ++/* ++ * ++ * Mobile IPv6 Protocol constants ++ * ++ */ ++#define DHAAD_RETRIES 4 /* transmissions */ ++#define INITIAL_BINDACK_TIMEOUT 1 /* seconds */ ++#define INITIAL_DHAAD_TIMEOUT 3 /* seconds */ ++#define INITIAL_SOLICIT_TIMER 3 /* seconds */ ++#define MAX_BINDACK_TIMEOUT 32 /* seconds */ ++#define MAX_NONCE_LIFE 240 /* seconds */ ++#define MAX_TOKEN_LIFE 210 /* seconds */ ++#define MAX_RR_BINDING_LIFE 420 /* seconds */ ++#define MAX_UPDATE_RATE 3 /* 1/s (min delay=1s) */ ++#define PREFIX_ADV_RETRIES 3 /* transmissions */ ++#define PREFIX_ADV_TIMEOUT 3 /* seconds */ ++ ++#define MAX_FAST_UPDATES 5 /* transmissions */ ++#define MAX_PFX_ADV_DELAY 1000 /* seconds */ ++#define SLOW_UPDATE_RATE 10 /* 1/10s (max delay=10s)*/ ++#define INITIAL_BINDACK_DAD_TIMEOUT 2 /* seconds */ ++ ++/* ++ * ++ * Mobile IPv6 (RFC 3775) Protocol configuration variable defaults ++ * ++ */ ++#define DefHomeRtrAdvInterval 1000 /* seconds */ ++#define DefMaxMobPfxAdvInterval 86400 /* seconds */ ++#define DefMinDelayBetweenRAs 3 /* seconds (min 0.03) */ ++#define DefMinMobPfxAdvInterval 600 /* seconds */ ++#define DefInitialBindackTimeoutFirstReg 1.5 /* seconds */ ++ ++/* This is not actually specified in the draft, but is needed to avoid ++ * prefix solicitation storm when valid lifetime of a prefix is smaller ++ * than MAX_PFX_ADV_DELAY ++ */ ++#define MIN_PFX_SOL_DELAY 5 /* seconds */ ++ ++/* Mobile IPv6 ICMP types */ ++/* ++ * Official numbers from RFC 3775 ++ */ ++#define MIPV6_DHAAD_REQUEST 144 ++#define MIPV6_DHAAD_REPLY 145 ++#define MIPV6_PREFIX_SOLICIT 146 ++#define MIPV6_PREFIX_ADV 147 ++ ++/* Binding update flag codes */ ++#define MIPV6_BU_F_ACK 0x80 ++#define MIPV6_BU_F_HOME 0x40 ++#define MIPV6_BU_F_LLADDR 0x20 ++#define MIPV6_BU_F_KEYMGM 0x10 ++ ++/* Binding ackknowledgment flag codes */ ++#define MIPV6_BA_F_KEYMGM 0x80 ++ ++/* Binding error status */ ++#define MIPV6_BE_HAO_WO_BINDING 1 ++#define MIPV6_BE_UNKNOWN_MH_TYPE 2 ++ ++/* Mobility Header */ ++struct mipv6_mh ++{ ++ __u8 payload; /* Payload Protocol */ ++ __u8 length; /* MH Length */ ++ __u8 type; /* MH Type */ ++ __u8 reserved; /* Reserved */ ++ __u16 checksum; /* Checksum */ ++ __u8 data[0]; /* Message specific data */ ++} __attribute__ ((packed)); ++ ++/* Mobility Header type */ ++#define IPPROTO_MOBILITY 135 /* RFC 3775*/ ++/* Mobility Header Message Types */ ++ ++#define MIPV6_MH_BRR 0 ++#define MIPV6_MH_HOTI 1 ++#define MIPV6_MH_COTI 2 ++#define MIPV6_MH_HOT 3 ++#define MIPV6_MH_COT 4 ++#define MIPV6_MH_BU 5 ++#define MIPV6_MH_BA 6 ++#define MIPV6_MH_BE 7 ++ ++/* ++ * Status codes for Binding Acknowledgements ++ */ ++#define SUCCESS 0 ++#define REASON_UNSPECIFIED 128 ++#define ADMINISTRATIVELY_PROHIBITED 129 ++#define INSUFFICIENT_RESOURCES 130 ++#define HOME_REGISTRATION_NOT_SUPPORTED 131 ++#define NOT_HOME_SUBNET 132 ++#define NOT_HA_FOR_MN 133 ++#define DUPLICATE_ADDR_DETECT_FAIL 134 ++#define SEQUENCE_NUMBER_OUT_OF_WINDOW 135 ++#define EXPIRED_HOME_NONCE_INDEX 136 ++#define EXPIRED_CAREOF_NONCE_INDEX 137 ++#define EXPIRED_NONCES 138 ++#define REG_TYPE_CHANGE_FORBIDDEN 139 ++/* ++ * Values for mipv6_flags in struct inet6_skb_parm ++ */ ++ ++#define MIPV6_RCV_TUNNEL 0x1 ++#define MIPV6_SND_HAO 0x2 ++#define MIPV6_SND_BU 0x4 ++ ++/* ++ * Mobility Header Message structures ++ */ ++ ++struct mipv6_mh_brr ++{ ++ __u16 reserved; ++ /* Mobility options */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mh_bu ++{ ++ __u16 sequence; /* sequence number of BU */ ++ __u8 flags; /* flags */ ++ __u8 reserved; /* reserved bits */ ++ __u16 lifetime; /* lifetime of BU */ ++ /* Mobility options */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mh_ba ++{ ++ __u8 status; /* statuscode */ ++ __u8 reserved; /* reserved bits */ ++ __u16 sequence; /* sequence number of BA */ ++ __u16 lifetime; /* lifetime in CN's bcache */ ++ /* Mobility options */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mh_be ++{ ++ __u8 status; ++ __u8 reserved; ++ struct in6_addr home_addr; ++ /* Mobility options */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mh_addr_ti ++{ ++ __u16 reserved; /* Reserved */ ++ u_int8_t init_cookie[8]; /* HoT/CoT Init Cookie */ ++ /* Mobility options */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mh_addr_test ++{ ++ __u16 nonce_index; /* Home/Care-of Nonce Index */ ++ u_int8_t init_cookie[8]; /* HoT/CoT Init Cookie */ ++ u_int8_t kgen_token[8]; /* Home/Care-of key generation token */ ++ /* Mobility options */ ++} __attribute__ ((packed)); ++ ++/* ++ * Mobility Options for various MH types. ++ */ ++#define MIPV6_OPT_PAD1 0x00 ++#define MIPV6_OPT_PADN 0x01 ++#define MIPV6_OPT_BIND_REFRESH_ADVICE 0x02 ++#define MIPV6_OPT_ALTERNATE_COA 0x03 ++#define MIPV6_OPT_NONCE_INDICES 0x04 ++#define MIPV6_OPT_AUTH_DATA 0x05 ++ ++#define MIPV6_SEQ_GT(x,y) \ ++ ((short int)(((__u16)(x)) - ((__u16)(y))) > 0) ++ ++/* ++ * Mobility Option structures ++ */ ++ ++struct mipv6_mo ++{ ++ __u8 type; ++ __u8 length; ++ __u8 value[0]; /* type specific data */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mo_pad1 ++{ ++ __u8 type; ++} __attribute__ ((packed)); ++ ++struct mipv6_mo_padn ++{ ++ __u8 type; ++ __u8 length; ++ __u8 data[0]; ++} __attribute__ ((packed)); ++ ++struct mipv6_mo_alt_coa ++{ ++ __u8 type; ++ __u8 length; ++ struct in6_addr addr; /* alternate care-of-address */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mo_nonce_indices ++{ ++ __u8 type; ++ __u8 length; ++ __u16 home_nonce_i; /* Home Nonce Index */ ++ __u16 careof_nonce_i; /* Careof Nonce Index */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mo_bauth_data ++{ ++ __u8 type; ++ __u8 length; ++ __u8 data[0]; ++} __attribute__ ((packed)); ++ ++struct mipv6_mo_br_advice ++{ ++ __u8 type; ++ __u8 length; ++ __u16 refresh_interval; /* Refresh Interval */ ++} __attribute__ ((packed)); ++ ++/* ++ * Home Address Destination Option structure ++ */ ++struct mipv6_dstopt_homeaddr ++{ ++ __u8 type; /* type-code for option */ ++ __u8 length; /* option length */ ++ struct in6_addr addr; /* home address */ ++} __attribute__ ((packed)); ++ ++#endif /* _NET_MIPV6_H */ +diff -uprN linux-2.4.25.old/include/net/ndisc.h linux-2.4.25/include/net/ndisc.h +--- linux-2.4.25.old/include/net/ndisc.h 2002-11-28 23:53:15.000000000 +0000 ++++ linux-2.4.25/include/net/ndisc.h 2004-06-26 11:29:29.000000000 +0100 +@@ -21,6 +21,10 @@ + #define ND_OPT_REDIRECT_HDR 4 + #define ND_OPT_MTU 5 + ++/* Mobile IPv6 specific ndisc options */ ++#define ND_OPT_RTR_ADV_INTERVAL 7 ++#define ND_OPT_HOME_AGENT_INFO 8 ++ + #define MAX_RTR_SOLICITATION_DELAY HZ + + #define ND_REACHABLE_TIME (30*HZ) +@@ -57,7 +61,7 @@ struct nd_opt_hdr { + } __attribute__((__packed__)); + + struct ndisc_options { +- struct nd_opt_hdr *nd_opt_array[7]; ++ struct nd_opt_hdr *nd_opt_array[10]; + struct nd_opt_hdr *nd_opt_piend; + }; + +@@ -67,6 +71,8 @@ struct ndisc_options { + #define nd_opts_pi_end nd_opt_piend + #define nd_opts_rh nd_opt_array[ND_OPT_REDIRECT_HDR] + #define nd_opts_mtu nd_opt_array[ND_OPT_MTU] ++#define nd_opts_rai nd_opt_array[ND_OPT_RTR_ADV_INTERVAL] ++#define nd_opts_hai nd_opt_array[ND_OPT_HOME_AGENT_INFO] + + extern struct nd_opt_hdr *ndisc_next_option(struct nd_opt_hdr *cur, struct nd_opt_hdr *end); + extern struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len, struct ndisc_options *ndopts); +@@ -83,6 +89,15 @@ extern void ndisc_send_ns(struct net_d + struct in6_addr *daddr, + struct in6_addr *saddr); + ++extern void ndisc_send_na(struct net_device *dev, ++ struct neighbour *neigh, ++ struct in6_addr *daddr, ++ struct in6_addr *solicited_addr, ++ int router, ++ int solicited, ++ int override, ++ int inc_opt); ++ + extern void ndisc_send_rs(struct net_device *dev, + struct in6_addr *saddr, + struct in6_addr *daddr); +diff -uprN linux-2.4.25.old/include/net/sock.h linux-2.4.25/include/net/sock.h +--- linux-2.4.25.old/include/net/sock.h 2004-02-18 13:36:32.000000000 +0000 ++++ linux-2.4.25/include/net/sock.h 2004-06-26 11:29:30.000000000 +0100 +@@ -149,7 +149,9 @@ struct ipv6_pinfo { + struct in6_addr rcv_saddr; + struct in6_addr daddr; + struct in6_addr *daddr_cache; +- ++#if defined(CONFIG_IPV6_SUBTREES) ++ struct in6_addr *saddr_cache; ++#endif + __u32 flow_label; + __u32 frag_size; + int hop_limit; +diff -uprN linux-2.4.25.old/net/Makefile linux-2.4.25/net/Makefile +--- linux-2.4.25.old/net/Makefile 2004-06-26 11:22:00.000000000 +0100 ++++ linux-2.4.25/net/Makefile 2004-06-26 11:29:30.000000000 +0100 +@@ -7,7 +7,7 @@ + + O_TARGET := network.o + +-mod-subdirs := ipv4/netfilter ipv6/netfilter ipx irda bluetooth atm netlink sched core sctp ++mod-subdirs := ipv4/netfilter ipv6/netfilter ipx irda bluetooth atm netlink sched core sctp ipv6 + export-objs := netsyms.o + + subdir-y := core ethernet +@@ -25,6 +25,7 @@ subdir-$(CONFIG_IP_SCTP) += sctp + ifneq ($(CONFIG_IPV6),n) + ifneq ($(CONFIG_IPV6),) + subdir-$(CONFIG_NETFILTER) += ipv6/netfilter ++subdir-$(CONFIG_IPV6_MOBILITY) += ipv6/mobile_ip6 + endif + endif + +diff -uprN linux-2.4.25.old/net/core/neighbour.c linux-2.4.25/net/core/neighbour.c +--- linux-2.4.25.old/net/core/neighbour.c 2004-02-18 13:36:32.000000000 +0000 ++++ linux-2.4.25/net/core/neighbour.c 2004-06-26 11:29:30.000000000 +0100 +@@ -386,7 +386,7 @@ struct pneigh_entry * pneigh_lookup(stru + if (!creat) + return NULL; + +- n = kmalloc(sizeof(*n) + key_len, GFP_KERNEL); ++ n = kmalloc(sizeof(*n) + key_len, GFP_ATOMIC); + if (n == NULL) + return NULL; + +diff -uprN linux-2.4.25.old/net/ipv6/Config.in linux-2.4.25/net/ipv6/Config.in +--- linux-2.4.25.old/net/ipv6/Config.in 2001-12-21 17:42:05.000000000 +0000 ++++ linux-2.4.25/net/ipv6/Config.in 2004-06-26 11:29:30.000000000 +0100 +@@ -1,10 +1,16 @@ + # + # IPv6 configuration + # +- ++bool ' IPv6: routing by source address (EXPERIMENTAL)' CONFIG_IPV6_SUBTREES + #bool ' IPv6: flow policy support' CONFIG_RT6_POLICY + #bool ' IPv6: firewall support' CONFIG_IPV6_FIREWALL + ++if [ "$CONFIG_IPV6" != "n" ]; then ++ dep_tristate ' IPv6: IPv6 over IPv6 Tunneling (EXPERIMENTAL)' CONFIG_IPV6_TUNNEL $CONFIG_IPV6 ++fi ++ ++source net/ipv6/mobile_ip6/Config.in ++ + if [ "$CONFIG_NETFILTER" != "n" ]; then + source net/ipv6/netfilter/Config.in + fi +diff -uprN linux-2.4.25.old/net/ipv6/Makefile linux-2.4.25/net/ipv6/Makefile +--- linux-2.4.25.old/net/ipv6/Makefile 2003-11-28 18:26:21.000000000 +0000 ++++ linux-2.4.25/net/ipv6/Makefile 2004-06-26 11:29:30.000000000 +0100 +@@ -6,18 +6,28 @@ + # unless it's something special (ie not a .c file). + # + ++export-objs := ipv6_syms.o ipv6_tunnel.o + +-O_TARGET := ipv6.o ++#list-multi := ipv6.o ipv6_tunnel.o + +-obj-y := af_inet6.o anycast.o ip6_output.o ip6_input.o addrconf.o sit.o \ +- route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o raw.o \ +- protocol.o icmp.o mcast.o reassembly.o tcp_ipv6.o \ +- exthdrs.o sysctl_net_ipv6.o datagram.o proc.o \ +- ip6_flowlabel.o ipv6_syms.o ++ipv6-objs := af_inet6.o anycast.o ip6_output.o ip6_input.o addrconf.o \ ++ sit.o route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o \ ++ raw.o protocol.o icmp.o mcast.o reassembly.o tcp_ipv6.o \ ++ exthdrs.o sysctl_net_ipv6.o datagram.o proc.o \ ++ ip6_flowlabel.o ipv6_syms.o ++ ++ifneq ($(CONFIG_IPV6_MOBILITY),n) ++ifneq ($(CONFIG_IPV6_MOBILITY),) ++ipv6-objs += mipglue.o ++endif ++endif + +-export-objs := ipv6_syms.o ++obj-$(CONFIG_IPV6) += ipv6.o ++obj-$(CONFIG_IPV6_TUNNEL) += ipv6_tunnel.o ++ ++ipv6.o: $(ipv6-objs) ++ $(LD) -r -o $@ $(ipv6-objs) + +-obj-m := $(O_TARGET) + + #obj-$(CONFIG_IPV6_FIREWALL) += ip6_fw.o + +diff -uprN linux-2.4.25.old/net/ipv6/addrconf.c linux-2.4.25/net/ipv6/addrconf.c +--- linux-2.4.25.old/net/ipv6/addrconf.c 2003-11-28 18:26:21.000000000 +0000 ++++ linux-2.4.25/net/ipv6/addrconf.c 2004-06-26 11:29:30.000000000 +0100 +@@ -68,6 +68,8 @@ + + #include + ++#include ++ + #define IPV6_MAX_ADDRESSES 16 + + /* Set to 3 to get tracing... */ +@@ -103,9 +105,9 @@ static spinlock_t addrconf_verify_lock = + + static int addrconf_ifdown(struct net_device *dev, int how); + +-static void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags); ++void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags); + static void addrconf_dad_timer(unsigned long data); +-static void addrconf_dad_completed(struct inet6_ifaddr *ifp); ++void addrconf_dad_completed(struct inet6_ifaddr *ifp); + static void addrconf_rs_timer(unsigned long data); + static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifa); + +@@ -330,38 +332,6 @@ static struct inet6_dev * ipv6_find_idev + return idev; + } + +-void ipv6_addr_prefix(struct in6_addr *prefix, +- struct in6_addr *addr, int prefix_len) +-{ +- unsigned long mask; +- int ncopy, nbits; +- +- memset(prefix, 0, sizeof(*prefix)); +- +- if (prefix_len <= 0) +- return; +- if (prefix_len > 128) +- prefix_len = 128; +- +- ncopy = prefix_len / 32; +- switch (ncopy) { +- case 4: prefix->s6_addr32[3] = addr->s6_addr32[3]; +- case 3: prefix->s6_addr32[2] = addr->s6_addr32[2]; +- case 2: prefix->s6_addr32[1] = addr->s6_addr32[1]; +- case 1: prefix->s6_addr32[0] = addr->s6_addr32[0]; +- case 0: break; +- } +- nbits = prefix_len % 32; +- if (nbits == 0) +- return; +- +- mask = ~((1 << (32 - nbits)) - 1); +- mask = htonl(mask); +- +- prefix->s6_addr32[ncopy] = addr->s6_addr32[ncopy] & mask; +-} +- +- + static void dev_forward_change(struct inet6_dev *idev) + { + struct net_device *dev; +@@ -513,7 +483,7 @@ ipv6_add_addr(struct inet6_dev *idev, co + + /* This function wants to get referenced ifp and releases it before return */ + +-static void ipv6_del_addr(struct inet6_ifaddr *ifp) ++void ipv6_del_addr(struct inet6_ifaddr *ifp) + { + struct inet6_ifaddr *ifa, **ifap; + struct inet6_dev *idev = ifp->idev; +@@ -662,6 +632,12 @@ out: + if (match) + in6_ifa_put(match); + ++ /* The home address is always used as source address in ++ * MIPL mobile IPv6 ++ */ ++ if (scope != IFA_HOST && scope != IFA_LINK) ++ addrconf_get_mipv6_home_address(saddr); ++ + return err; + } + +@@ -815,7 +791,7 @@ void addrconf_leave_solict(struct net_de + } + + +-static int ipv6_generate_eui64(u8 *eui, struct net_device *dev) ++int ipv6_generate_eui64(u8 *eui, struct net_device *dev) + { + switch (dev->type) { + case ARPHRD_ETHER: +@@ -840,7 +816,7 @@ static int ipv6_generate_eui64(u8 *eui, + return -1; + } + +-static int ipv6_inherit_eui64(u8 *eui, struct inet6_dev *idev) ++int ipv6_inherit_eui64(u8 *eui, struct inet6_dev *idev) + { + int err = -1; + struct inet6_ifaddr *ifp; +@@ -1407,6 +1383,24 @@ static void addrconf_sit_config(struct n + sit_route_add(dev); + } + ++/** ++ * addrconf_ipv6_tunnel_config - configure IPv6 tunnel device ++ * @dev: tunnel device ++ **/ ++ ++static void addrconf_ipv6_tunnel_config(struct net_device *dev) ++{ ++ struct inet6_dev *idev; ++ ++ ASSERT_RTNL(); ++ ++ /* Assign inet6_dev structure to tunnel device */ ++ if ((idev = ipv6_find_idev(dev)) == NULL) { ++ printk(KERN_DEBUG "init ipv6 tunnel: add_dev failed\n"); ++ return; ++ } ++} ++ + + int addrconf_notify(struct notifier_block *this, unsigned long event, + void * data) +@@ -1421,6 +1415,10 @@ int addrconf_notify(struct notifier_bloc + addrconf_sit_config(dev); + break; + ++ case ARPHRD_TUNNEL6: ++ addrconf_ipv6_tunnel_config(dev); ++ break; ++ + case ARPHRD_LOOPBACK: + init_loopback(dev); + break; +@@ -1602,7 +1600,7 @@ out: + /* + * Duplicate Address Detection + */ +-static void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags) ++void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags) + { + struct net_device *dev; + unsigned long rand_num; +@@ -1667,7 +1665,7 @@ static void addrconf_dad_timer(unsigned + in6_ifa_put(ifp); + } + +-static void addrconf_dad_completed(struct inet6_ifaddr *ifp) ++void addrconf_dad_completed(struct inet6_ifaddr *ifp) + { + struct net_device * dev = ifp->idev->dev; + +@@ -1676,7 +1674,7 @@ static void addrconf_dad_completed(struc + */ + + ipv6_ifa_notify(RTM_NEWADDR, ifp); +- ++ notifier_call_chain(&inet6addr_chain,NETDEV_UP,ifp); + /* If added prefix is link local and forwarding is off, + start sending router solicitations. + */ +@@ -1877,8 +1875,20 @@ inet6_rtm_newaddr(struct sk_buff *skb, s + if (rta[IFA_LOCAL-1]) { + if (pfx && memcmp(pfx, RTA_DATA(rta[IFA_LOCAL-1]), sizeof(*pfx))) + return -EINVAL; ++ if (ifm->ifa_flags & IFA_F_HOMEADDR && !rta[IFA_HOMEAGENT-1]) ++ return -EINVAL; + pfx = RTA_DATA(rta[IFA_LOCAL-1]); + } ++ if (rta[IFA_HOMEAGENT-1]) { ++ struct in6_addr *ha; ++ if (pfx == NULL || !(ifm->ifa_flags & IFA_F_HOMEADDR)) ++ return -EINVAL; ++ if (RTA_PAYLOAD(rta[IFA_HOMEAGENT-1]) < sizeof(*ha)) ++ return -EINVAL; ++ ha = RTA_DATA(rta[IFA_HOMEAGENT-1]); ++ addrconf_set_mipv6_mn_home(ifm->ifa_index, pfx, ifm->ifa_prefixlen, ++ ha, ifm->ifa_prefixlen); ++ } + if (pfx == NULL) + return -EINVAL; + +diff -uprN linux-2.4.25.old/net/ipv6/af_inet6.c linux-2.4.25/net/ipv6/af_inet6.c +--- linux-2.4.25.old/net/ipv6/af_inet6.c 2003-11-28 18:26:21.000000000 +0000 ++++ linux-2.4.25/net/ipv6/af_inet6.c 2004-06-26 11:29:30.000000000 +0100 +@@ -58,6 +58,9 @@ + #include + #include + #include ++#ifdef CONFIG_IPV6_TUNNEL ++#include ++#endif + + #include + #include +@@ -646,6 +649,11 @@ static int __init inet6_init(void) + err = ndisc_init(&inet6_family_ops); + if (err) + goto ndisc_fail; ++#ifdef CONFIG_IPV6_TUNNEL ++ err = ip6_tunnel_init(); ++ if (err) ++ goto ip6_tunnel_fail; ++#endif + err = igmp6_init(&inet6_family_ops); + if (err) + goto igmp_fail; +@@ -698,6 +706,10 @@ proc_raw6_fail: + #endif + igmp_fail: + ndisc_cleanup(); ++#ifdef CONFIG_IPV6_TUNNEL ++ ip6_tunnel_cleanup(); ++ip6_tunnel_fail: ++#endif + ndisc_fail: + icmpv6_cleanup(); + icmp_fail: +@@ -730,6 +742,9 @@ static void inet6_exit(void) + ip6_route_cleanup(); + ipv6_packet_cleanup(); + igmp6_cleanup(); ++#ifdef CONFIG_IPV6_TUNNEL ++ ip6_tunnel_cleanup(); ++#endif + ndisc_cleanup(); + icmpv6_cleanup(); + #ifdef CONFIG_SYSCTL +diff -uprN linux-2.4.25.old/net/ipv6/exthdrs.c linux-2.4.25/net/ipv6/exthdrs.c +--- linux-2.4.25.old/net/ipv6/exthdrs.c 2003-08-25 12:44:44.000000000 +0100 ++++ linux-2.4.25/net/ipv6/exthdrs.c 2004-06-26 11:29:30.000000000 +0100 +@@ -41,6 +41,9 @@ + #include + #include + ++#include ++#include ++ + #include + + /* +@@ -160,7 +163,8 @@ bad: + *****************************/ + + struct tlvtype_proc tlvprocdestopt_lst[] = { +- /* No destination options are defined now */ ++ /* Mobility Support destination options */ ++ {MIPV6_TLV_HOMEADDR, mipv6_handle_dstopt}, + {-1, NULL} + }; + +@@ -210,6 +214,7 @@ static int ipv6_routing_header(struct sk + + struct ipv6_rt_hdr *hdr; + struct rt0_hdr *rthdr; ++ struct rt2_hdr *rt2hdr; + + if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8) || + !pskb_may_pull(skb, (skb->h.raw-skb->data)+((skb->h.raw[1]+1)<<3))) { +@@ -225,17 +230,25 @@ static int ipv6_routing_header(struct sk + kfree_skb(skb); + return -1; + } +- ++ /* Silently discard invalid packets containing RTH type 2 */ ++ if (hdr->type == IPV6_SRCRT_TYPE_2 && ++ (hdr->hdrlen != 2 || hdr->segments_left != 1)) { ++ kfree_skb(skb); ++ return -1; ++ } + looped_back: + if (hdr->segments_left == 0) { +- opt->srcrt = skb->h.raw - skb->nh.raw; ++ if (hdr->type == IPV6_SRCRT_TYPE_0) ++ opt->srcrt = skb->h.raw - skb->nh.raw; ++ else if (hdr->type == IPV6_SRCRT_TYPE_2) ++ opt->srcrt2 = skb->h.raw - skb->nh.raw; + skb->h.raw += (hdr->hdrlen + 1) << 3; + opt->dst0 = opt->dst1; + opt->dst1 = 0; + return (&hdr->nexthdr) - skb->nh.raw; + } + +- if (hdr->type != IPV6_SRCRT_TYPE_0) { ++ if (hdr->type != IPV6_SRCRT_TYPE_0 && hdr->type != IPV6_SRCRT_TYPE_2) { + icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, (&hdr->type) - skb->nh.raw); + return -1; + } +@@ -275,9 +288,20 @@ looped_back: + + i = n - --hdr->segments_left; + +- rthdr = (struct rt0_hdr *) hdr; +- addr = rthdr->addr; +- addr += i - 1; ++ if (hdr->type == IPV6_SRCRT_TYPE_0) { ++ rthdr = (struct rt0_hdr *) hdr; ++ addr = rthdr->addr; ++ addr += i - 1; ++ } else { ++ /* check that address is this node's home address */ ++ rt2hdr = (struct rt2_hdr *) hdr; ++ addr = &rt2hdr->addr; ++ if (!ipv6_chk_addr(addr, NULL) || ++ !ipv6_chk_mip_home_addr(addr)) { ++ kfree_skb(skb); ++ return -1; ++ } ++ } + + addr_type = ipv6_addr_type(addr); + +@@ -330,6 +354,10 @@ looped_back: + temporary (or permanent) backdoor. + If listening socket set IPV6_RTHDR to 2, then we invert header. + --ANK (980729) ++ ++ By the Mobile IPv6 specification Type 2 routing header MUST NOT be ++ inverted. ++ --AJT (20020917) + */ + + struct ipv6_txoptions * +@@ -352,6 +380,18 @@ ipv6_invert_rthdr(struct sock *sk, struc + struct ipv6_txoptions *opt; + int hdrlen = ipv6_optlen(hdr); + ++ if (hdr->type == IPV6_SRCRT_TYPE_2) { ++ opt = sock_kmalloc(sk, sizeof(*opt) + hdrlen, GFP_ATOMIC); ++ if (opt == NULL) ++ return NULL; ++ memset(opt, 0, sizeof(*opt)); ++ opt->tot_len = sizeof(*opt) + hdrlen; ++ opt->srcrt = (void*)(opt+1); ++ opt->opt_nflen = hdrlen; ++ memcpy(opt->srcrt, hdr, sizeof(struct rt2_hdr)); ++ return opt; ++ } ++ + if (hdr->segments_left || + hdr->type != IPV6_SRCRT_TYPE_0 || + hdr->hdrlen & 0x01) +@@ -622,8 +662,18 @@ u8 *ipv6_build_nfrag_opts(struct sk_buff + if (opt) { + if (opt->dst0opt) + prev_hdr = ipv6_build_exthdr(skb, prev_hdr, NEXTHDR_DEST, opt->dst0opt); +- if (opt->srcrt) +- prev_hdr = ipv6_build_rthdr(skb, prev_hdr, opt->srcrt, daddr); ++ if (opt->srcrt) { ++ if (opt->srcrt2) { ++ struct in6_addr *rt2_hop = &((struct rt2_hdr *)opt->srcrt2)->addr; ++ prev_hdr = ipv6_build_rthdr(skb, prev_hdr, opt->srcrt, rt2_hop); ++ } else ++ prev_hdr = ipv6_build_rthdr(skb, prev_hdr, opt->srcrt, daddr); ++ } ++ if (opt->srcrt2) { ++ struct inet6_skb_parm *parm = (struct inet6_skb_parm *)skb->cb; ++ ipv6_addr_copy(&parm->hoa, daddr); ++ prev_hdr = ipv6_build_rthdr(skb, prev_hdr, opt->srcrt2, daddr); ++ } + } + return prev_hdr; + } +@@ -684,6 +734,11 @@ void ipv6_push_nfrag_opts(struct sk_buff + u8 *proto, + struct in6_addr **daddr) + { ++ if (opt->srcrt2) { ++ struct inet6_skb_parm *parm = (struct inet6_skb_parm *)skb->cb; ++ ipv6_addr_copy(&parm->hoa, *daddr); ++ ipv6_push_rthdr(skb, proto, opt->srcrt2, daddr); ++ } + if (opt->srcrt) + ipv6_push_rthdr(skb, proto, opt->srcrt, daddr); + if (opt->dst0opt) +@@ -719,6 +774,8 @@ ipv6_dup_options(struct sock *sk, struct + *((char**)&opt2->auth) += dif; + if (opt2->srcrt) + *((char**)&opt2->srcrt) += dif; ++ if (opt2->srcrt2) ++ *((char**)&opt2->srcrt2) += dif; + } + return opt2; + } +diff -uprN linux-2.4.25.old/net/ipv6/icmp.c linux-2.4.25/net/ipv6/icmp.c +--- linux-2.4.25.old/net/ipv6/icmp.c 2003-11-28 18:26:21.000000000 +0000 ++++ linux-2.4.25/net/ipv6/icmp.c 2004-06-26 11:29:30.000000000 +0100 +@@ -61,6 +61,8 @@ + #include + #include + ++#include ++ + #include + #include + +@@ -364,6 +366,8 @@ void icmpv6_send(struct sk_buff *skb, in + + msg.len = len; + ++ icmpv6_swap_mipv6_addrs(skb); ++ + ip6_build_xmit(sk, icmpv6_getfrag, &msg, &fl, len, NULL, -1, + MSG_DONTWAIT); + if (type >= ICMPV6_DEST_UNREACH && type <= ICMPV6_PARAMPROB) +@@ -562,13 +566,13 @@ int icmpv6_rcv(struct sk_buff *skb) + rt6_pmtu_discovery(&orig_hdr->daddr, &orig_hdr->saddr, dev, + ntohl(hdr->icmp6_mtu)); + +- /* +- * Drop through to notify +- */ ++ icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu); ++ break; + + case ICMPV6_DEST_UNREACH: +- case ICMPV6_TIME_EXCEED: + case ICMPV6_PARAMPROB: ++ mipv6_icmp_rcv(skb); ++ case ICMPV6_TIME_EXCEED: + icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu); + break; + +@@ -597,6 +601,13 @@ int icmpv6_rcv(struct sk_buff *skb) + case ICMPV6_MGM_REDUCTION: + break; + ++ case MIPV6_DHAAD_REQUEST: ++ case MIPV6_DHAAD_REPLY: ++ case MIPV6_PREFIX_SOLICIT: ++ case MIPV6_PREFIX_ADV: ++ mipv6_icmp_rcv(skb); ++ break; ++ + default: + if (net_ratelimit()) + printk(KERN_DEBUG "icmpv6: msg of unkown type\n"); +diff -uprN linux-2.4.25.old/net/ipv6/ip6_fib.c linux-2.4.25/net/ipv6/ip6_fib.c +--- linux-2.4.25.old/net/ipv6/ip6_fib.c 2003-08-25 12:44:44.000000000 +0100 ++++ linux-2.4.25/net/ipv6/ip6_fib.c 2004-06-26 11:29:30.000000000 +0100 +@@ -18,6 +18,7 @@ + * Yuji SEKIYA @USAGI: Support default route on router node; + * remove ip6_null_entry from the top of + * routing table. ++ * Ville Nuorvala: Fixes to source address based routing + */ + #include + #include +@@ -40,7 +41,6 @@ + #include + + #define RT6_DEBUG 2 +-#undef CONFIG_IPV6_SUBTREES + + #if RT6_DEBUG >= 3 + #define RT6_TRACE(x...) printk(KERN_DEBUG x) +@@ -500,6 +500,8 @@ static __inline__ void fib6_start_gc(str + mod_timer(&ip6_fib_timer, jiffies + ip6_rt_gc_interval); + } + ++static struct rt6_info * fib6_find_prefix(struct fib6_node *fn); ++ + /* + * Add routing information to the routing tree. + * / +@@ -508,17 +510,19 @@ static __inline__ void fib6_start_gc(str + + int fib6_add(struct fib6_node *root, struct rt6_info *rt, struct nlmsghdr *nlh) + { +- struct fib6_node *fn; ++ struct fib6_node *fn = root; + int err = -ENOMEM; + +- fn = fib6_add_1(root, &rt->rt6i_dst.addr, sizeof(struct in6_addr), +- rt->rt6i_dst.plen, (u8*) &rt->rt6i_dst - (u8*) rt); ++#ifdef CONFIG_IPV6_SUBTREES ++ struct fib6_node *pn = NULL; + ++ fn = fib6_add_1(root, &rt->rt6i_src.addr, sizeof(struct in6_addr), ++ rt->rt6i_src.plen, (u8*) &rt->rt6i_src - (u8*) rt); ++ + if (fn == NULL) + goto out; + +-#ifdef CONFIG_IPV6_SUBTREES +- if (rt->rt6i_src.plen) { ++ if (rt->rt6i_dst.plen) { + struct fib6_node *sn; + + if (fn->subtree == NULL) { +@@ -546,9 +550,9 @@ int fib6_add(struct fib6_node *root, str + + /* Now add the first leaf node to new subtree */ + +- sn = fib6_add_1(sfn, &rt->rt6i_src.addr, +- sizeof(struct in6_addr), rt->rt6i_src.plen, +- (u8*) &rt->rt6i_src - (u8*) rt); ++ sn = fib6_add_1(sfn, &rt->rt6i_dst.addr, ++ sizeof(struct in6_addr), rt->rt6i_dst.plen, ++ (u8*) &rt->rt6i_dst - (u8*) rt); + + if (sn == NULL) { + /* If it is failed, discard just allocated +@@ -562,21 +566,30 @@ int fib6_add(struct fib6_node *root, str + /* Now link new subtree to main tree */ + sfn->parent = fn; + fn->subtree = sfn; +- if (fn->leaf == NULL) { +- fn->leaf = rt; +- atomic_inc(&rt->rt6i_ref); +- } + } else { +- sn = fib6_add_1(fn->subtree, &rt->rt6i_src.addr, +- sizeof(struct in6_addr), rt->rt6i_src.plen, +- (u8*) &rt->rt6i_src - (u8*) rt); ++ sn = fib6_add_1(fn->subtree, &rt->rt6i_dst.addr, ++ sizeof(struct in6_addr), rt->rt6i_dst.plen, ++ (u8*) &rt->rt6i_dst - (u8*) rt); + + if (sn == NULL) + goto st_failure; + } + ++ /* fib6_add_1 might have cleared the old leaf pointer */ ++ if (fn->leaf == NULL) { ++ fn->leaf = rt; ++ atomic_inc(&rt->rt6i_ref); ++ } ++ ++ pn = fn; + fn = sn; + } ++#else ++ fn = fib6_add_1(root, &rt->rt6i_dst.addr, sizeof(struct in6_addr), ++ rt->rt6i_dst.plen, (u8*) &rt->rt6i_dst - (u8*) rt); ++ ++ if (fn == NULL) ++ goto out; + #endif + + err = fib6_add_rt2node(fn, rt, nlh); +@@ -588,8 +601,30 @@ int fib6_add(struct fib6_node *root, str + } + + out: +- if (err) ++ if (err) { ++#ifdef CONFIG_IPV6_SUBTREES ++ ++ /* If fib6_add_1 has cleared the old leaf pointer in the ++ super-tree leaf node, we have to find a new one for it. ++ ++ This situation will never arise in the sub-tree since ++ the node will at least have the route that caused ++ fib6_add_rt2node to fail. ++ */ ++ ++ if (pn && !(pn->fn_flags & RTN_RTINFO)) { ++ pn->leaf = fib6_find_prefix(pn); ++#if RT6_DEBUG >= 2 ++ if (!pn->leaf) { ++ BUG_TRAP(pn->leaf); ++ pn->leaf = &ip6_null_entry; ++ } ++#endif ++ atomic_inc(&pn->leaf->rt6i_ref); ++ } ++#endif + dst_free(&rt->u.dst); ++ } + return err; + + #ifdef CONFIG_IPV6_SUBTREES +@@ -597,8 +632,8 @@ out: + is orphan. If it is, shoot it. + */ + st_failure: +- if (fn && !(fn->fn_flags&RTN_RTINFO|RTN_ROOT)) +- fib_repair_tree(fn); ++ if (fn && !(fn->fn_flags & (RTN_RTINFO | RTN_ROOT))) ++ fib6_repair_tree(fn); + dst_free(&rt->u.dst); + return err; + #endif +@@ -641,22 +676,28 @@ static struct fib6_node * fib6_lookup_1( + break; + } + +- while ((fn->fn_flags & RTN_ROOT) == 0) { ++ for (;;) { + #ifdef CONFIG_IPV6_SUBTREES + if (fn->subtree) { +- struct fib6_node *st; +- struct lookup_args *narg; +- +- narg = args + 1; +- +- if (narg->addr) { +- st = fib6_lookup_1(fn->subtree, narg); ++ struct rt6key *key; + +- if (st && !(st->fn_flags & RTN_ROOT)) +- return st; ++ key = (struct rt6key *) ((u8 *) fn->leaf + ++ args->offset); ++ ++ if (addr_match(&key->addr, args->addr, key->plen)) { ++ struct fib6_node *st; ++ struct lookup_args *narg = args + 1; ++ if (!ipv6_addr_any(narg->addr)) { ++ st = fib6_lookup_1(fn->subtree, narg); ++ ++ if (st && !(st->fn_flags & RTN_ROOT)) ++ return st; ++ } + } + } + #endif ++ if (fn->fn_flags & RTN_ROOT) ++ break; + + if (fn->fn_flags & RTN_RTINFO) { + struct rt6key *key; +@@ -680,13 +721,22 @@ struct fib6_node * fib6_lookup(struct fi + struct lookup_args args[2]; + struct rt6_info *rt = NULL; + struct fib6_node *fn; ++#ifdef CONFIG_IPV6_SUBTREES ++ struct in6_addr saddr_buf; ++#endif + ++#ifdef CONFIG_IPV6_SUBTREES ++ if (saddr == NULL) { ++ memset(&saddr_buf, 0, sizeof(struct in6_addr)); ++ saddr = &saddr_buf; ++ } ++ args[0].offset = (u8*) &rt->rt6i_src - (u8*) rt; ++ args[0].addr = saddr; ++ args[1].offset = (u8*) &rt->rt6i_dst - (u8*) rt; ++ args[1].addr = daddr; ++#else + args[0].offset = (u8*) &rt->rt6i_dst - (u8*) rt; + args[0].addr = daddr; +- +-#ifdef CONFIG_IPV6_SUBTREES +- args[1].offset = (u8*) &rt->rt6i_src - (u8*) rt; +- args[1].addr = saddr; + #endif + + fn = fib6_lookup_1(root, args); +@@ -739,19 +789,25 @@ struct fib6_node * fib6_locate(struct fi + { + struct rt6_info *rt = NULL; + struct fib6_node *fn; +- +- fn = fib6_locate_1(root, daddr, dst_len, +- (u8*) &rt->rt6i_dst - (u8*) rt); +- + #ifdef CONFIG_IPV6_SUBTREES +- if (src_len) { +- BUG_TRAP(saddr!=NULL); +- if (fn == NULL) +- fn = fn->subtree; ++ struct in6_addr saddr_buf; ++ ++ if (saddr == NULL) { ++ memset(&saddr_buf, 0, sizeof(struct in6_addr)); ++ saddr = &saddr_buf; ++ } ++ fn = fib6_locate_1(root, saddr, src_len, ++ (u8*) &rt->rt6i_src - (u8*) rt); ++ if (dst_len) { + if (fn) +- fn = fib6_locate_1(fn, saddr, src_len, +- (u8*) &rt->rt6i_src - (u8*) rt); ++ fn = fib6_locate_1(fn->subtree, daddr, dst_len, ++ (u8*) &rt->rt6i_dst - (u8*) rt); ++ else ++ return NULL; + } ++#else ++ fn = fib6_locate_1(root, daddr, dst_len, ++ (u8*) &rt->rt6i_dst - (u8*) rt); + #endif + + if (fn && fn->fn_flags&RTN_RTINFO) +@@ -939,7 +995,7 @@ static void fib6_del_route(struct fib6_n + } + fn = fn->parent; + } +- /* No more references are possiible at this point. */ ++ /* No more references are possible at this point. */ + if (atomic_read(&rt->rt6i_ref) != 1) BUG(); + } + +diff -uprN linux-2.4.25.old/net/ipv6/ip6_input.c linux-2.4.25/net/ipv6/ip6_input.c +--- linux-2.4.25.old/net/ipv6/ip6_input.c 2003-08-25 12:44:44.000000000 +0100 ++++ linux-2.4.25/net/ipv6/ip6_input.c 2004-06-26 11:29:30.000000000 +0100 +@@ -40,13 +40,42 @@ + #include + #include + ++static inline int ip6_proxy_chk(struct sk_buff *skb) ++{ ++ struct ipv6hdr *hdr = skb->nh.ipv6h; + +- ++ if (ipv6_addr_type(&hdr->daddr)&IPV6_ADDR_UNICAST && ++ pneigh_lookup(&nd_tbl, &hdr->daddr, skb->dev, 0)) { ++ u8 nexthdr = hdr->nexthdr; ++ int offset; ++ struct icmp6hdr msg; ++ ++ if (ipv6_ext_hdr(nexthdr)) { ++ offset = ipv6_skip_exthdr(skb, sizeof(*hdr), &nexthdr, ++ skb->len - sizeof(*hdr)); ++ if (offset < 0) ++ return 0; ++ } else ++ offset = sizeof(*hdr); ++ ++ /* capture unicast NUD probes on behalf of the proxied node */ ++ ++ if (nexthdr == IPPROTO_ICMPV6 && ++ !skb_copy_bits(skb, offset, &msg, sizeof(msg)) && ++ msg.icmp6_type == NDISC_NEIGHBOUR_SOLICITATION) { ++ return 1; ++ } ++ } ++ return 0; ++} ++ + static inline int ip6_rcv_finish( struct sk_buff *skb) + { +- if (skb->dst == NULL) +- ip6_route_input(skb); +- ++ if (skb->dst == NULL) { ++ if (ip6_proxy_chk(skb)) ++ return ip6_input(skb); ++ ip6_route_input(skb); ++ } + return skb->dst->input(skb); + } + +diff -uprN linux-2.4.25.old/net/ipv6/ip6_output.c linux-2.4.25/net/ipv6/ip6_output.c +--- linux-2.4.25.old/net/ipv6/ip6_output.c 2003-08-25 12:44:44.000000000 +0100 ++++ linux-2.4.25/net/ipv6/ip6_output.c 2004-06-26 11:29:30.000000000 +0100 +@@ -50,6 +50,8 @@ + #include + #include + ++#include ++ + static __inline__ void ipv6_select_ident(struct sk_buff *skb, struct frag_hdr *fhdr) + { + static u32 ipv6_fragmentation_id = 1; +@@ -194,7 +196,14 @@ int ip6_xmit(struct sock *sk, struct sk_ + u8 proto = fl->proto; + int seg_len = skb->len; + int hlimit; ++ int retval; ++ struct ipv6_txoptions *orig_opt = opt; ++ ++ opt = ip6_add_mipv6_txoptions(sk, skb, orig_opt, fl, &dst); + ++ if(orig_opt && !opt) ++ return -ENOMEM; ++ + if (opt) { + int head_room; + +@@ -209,8 +218,11 @@ int ip6_xmit(struct sock *sk, struct sk_ + struct sk_buff *skb2 = skb_realloc_headroom(skb, head_room); + kfree_skb(skb); + skb = skb2; +- if (skb == NULL) ++ if (skb == NULL) { ++ ip6_free_mipv6_txoptions(opt, orig_opt); ++ + return -ENOBUFS; ++ } + if (sk) + skb_set_owner_w(skb, sk); + } +@@ -242,7 +254,10 @@ int ip6_xmit(struct sock *sk, struct sk_ + + if (skb->len <= dst->pmtu) { + IP6_INC_STATS(Ip6OutRequests); +- return NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, ip6_maybe_reroute); ++ ip6_mark_mipv6_packet(opt, skb); ++ retval = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, ip6_maybe_reroute); ++ ip6_free_mipv6_txoptions(opt, orig_opt); ++ return retval; + } + + if (net_ratelimit()) +@@ -250,6 +265,9 @@ int ip6_xmit(struct sock *sk, struct sk_ + skb->dev = dst->dev; + icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, dst->pmtu, skb->dev); + kfree_skb(skb); ++ ++ ip6_free_mipv6_txoptions(opt, orig_opt); ++ + return -EMSGSIZE; + } + +@@ -473,6 +491,7 @@ static int ip6_frag_xmit(struct sock *sk + + IP6_INC_STATS(Ip6FragCreates); + IP6_INC_STATS(Ip6OutRequests); ++ ip6_mark_mipv6_packet(opt, skb); + err = NF_HOOK(PF_INET6,NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, ip6_maybe_reroute); + if (err) { + kfree_skb(last_skb); +@@ -499,6 +518,7 @@ static int ip6_frag_xmit(struct sock *sk + IP6_INC_STATS(Ip6FragCreates); + IP6_INC_STATS(Ip6FragOKs); + IP6_INC_STATS(Ip6OutRequests); ++ ip6_mark_mipv6_packet(opt, last_skb); + return NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, last_skb, NULL,dst->dev, ip6_maybe_reroute); + } + +@@ -509,26 +529,43 @@ int ip6_build_xmit(struct sock *sk, inet + struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6; + struct in6_addr *final_dst = NULL; + struct dst_entry *dst; ++ struct rt6_info *rt; + int err = 0; + unsigned int pktlength, jumbolen, mtu; + struct in6_addr saddr; ++ struct ipv6_txoptions *orig_opt = opt; ++#ifdef CONFIG_IPV6_SUBTREES ++ struct dst_entry *org_dst; ++#endif ++ ++ opt = ip6_add_mipv6_txoptions(sk, NULL, orig_opt, fl, NULL); ++ ++ if(orig_opt && !opt) ++ return -ENOMEM; + + if (opt && opt->srcrt) { + struct rt0_hdr *rt0 = (struct rt0_hdr *) opt->srcrt; + final_dst = fl->fl6_dst; + fl->fl6_dst = rt0->addr; +- } ++ } else if (opt && opt->srcrt2) { ++ struct rt2_hdr *rt2 = (struct rt2_hdr *) opt->srcrt2; ++ final_dst = fl->fl6_dst; ++ fl->fl6_dst = &rt2->addr; ++ } + + if (!fl->oif && ipv6_addr_is_multicast(fl->nl_u.ip6_u.daddr)) + fl->oif = np->mcast_oif; + + dst = __sk_dst_check(sk, np->dst_cookie); ++#ifdef CONFIG_IPV6_SUBTREES ++ org_dst = dst; ++#endif + if (dst) { +- struct rt6_info *rt = (struct rt6_info*)dst; ++ rt = (struct rt6_info*)dst; + + /* Yes, checking route validity in not connected + case is not very simple. Take into account, +- that we do not support routing by source, TOS, ++ that we do not support routing by TOS, + and MSG_DONTROUTE --ANK (980726) + + 1. If route was host route, check that +@@ -548,6 +585,13 @@ int ip6_build_xmit(struct sock *sk, inet + ipv6_addr_cmp(fl->fl6_dst, &rt->rt6i_dst.addr)) + && (np->daddr_cache == NULL || + ipv6_addr_cmp(fl->fl6_dst, np->daddr_cache))) ++#ifdef CONFIG_IPV6_SUBTREES ++ || (fl->fl6_src != NULL ++ && (rt->rt6i_src.plen != 128 || ++ ipv6_addr_cmp(fl->fl6_src, &rt->rt6i_src.addr)) ++ && (np->saddr_cache == NULL || ++ ipv6_addr_cmp(fl->fl6_src, np->saddr_cache))) ++#endif + || (fl->oif && fl->oif != dst->dev->ifindex)) { + dst = NULL; + } else +@@ -560,21 +604,42 @@ int ip6_build_xmit(struct sock *sk, inet + if (dst->error) { + IP6_INC_STATS(Ip6OutNoRoutes); + dst_release(dst); ++ ip6_free_mipv6_txoptions(opt, orig_opt); + return -ENETUNREACH; + } + + if (fl->fl6_src == NULL) { + err = ipv6_get_saddr(dst, fl->fl6_dst, &saddr); +- + if (err) { + #if IP6_DEBUG >= 2 + printk(KERN_DEBUG "ip6_build_xmit: " + "no available source address\n"); + #endif ++ ++#ifdef CONFIG_IPV6_SUBTREES ++ if (dst != org_dst) { ++ dst_release(dst); ++ dst = org_dst; ++ } ++#endif + goto out; + } + fl->fl6_src = &saddr; + } ++#ifdef CONFIG_IPV6_SUBTREES ++ rt = (struct rt6_info*)dst; ++ if (dst != org_dst || rt->rt6i_src.plen != 128 || ++ ipv6_addr_cmp(fl->fl6_src, &rt->rt6i_src.addr)) { ++ dst_release(dst); ++ dst = ip6_route_output(sk, fl); ++ if (dst->error) { ++ IP6_INC_STATS(Ip6OutNoRoutes); ++ dst_release(dst); ++ ip6_free_mipv6_txoptions(opt, orig_opt); ++ return -ENETUNREACH; ++ } ++ } ++#endif + pktlength = length; + + if (hlimit < 0) { +@@ -667,6 +732,7 @@ int ip6_build_xmit(struct sock *sk, inet + + if (!err) { + IP6_INC_STATS(Ip6OutRequests); ++ ip6_mark_mipv6_packet(opt, skb); + err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, ip6_maybe_reroute); + } else { + err = -EFAULT; +@@ -688,9 +754,14 @@ int ip6_build_xmit(struct sock *sk, inet + * cleanup + */ + out: +- ip6_dst_store(sk, dst, fl->nl_u.ip6_u.daddr == &np->daddr ? &np->daddr : NULL); ++ ip6_dst_store(sk, dst, ++ fl->nl_u.ip6_u.daddr == &np->daddr ? &np->daddr : NULL, ++ fl->nl_u.ip6_u.saddr == &np->saddr ? &np->saddr : NULL); + if (err > 0) + err = np->recverr ? net_xmit_errno(err) : 0; ++ ++ ip6_free_mipv6_txoptions(opt, orig_opt); ++ + return err; + } + +@@ -769,6 +840,15 @@ int ip6_forward(struct sk_buff *skb) + return -ETIMEDOUT; + } + ++ /* The proxying router can't forward traffic sent to a link-local ++ address, so signal the sender and discard the packet. This ++ behavior is required by the MIPv6 specification. */ ++ ++ if (ipv6_addr_type(&hdr->daddr) & IPV6_ADDR_LINKLOCAL && ++ skb->dev && pneigh_lookup(&nd_tbl, &hdr->daddr, skb->dev, 0)) { ++ dst_link_failure(skb); ++ goto drop; ++ } + /* IPv6 specs say nothing about it, but it is clear that we cannot + send redirects to source routed frames. + */ +diff -uprN linux-2.4.25.old/net/ipv6/ipv6_syms.c linux-2.4.25/net/ipv6/ipv6_syms.c +--- linux-2.4.25.old/net/ipv6/ipv6_syms.c 2003-11-28 18:26:21.000000000 +0000 ++++ linux-2.4.25/net/ipv6/ipv6_syms.c 2004-06-26 11:29:30.000000000 +0100 +@@ -6,6 +6,8 @@ + #include + #include + #include ++#include ++#include + + EXPORT_SYMBOL(ipv6_addr_type); + EXPORT_SYMBOL(icmpv6_send); +@@ -33,3 +35,48 @@ EXPORT_SYMBOL(inet6_ioctl); + EXPORT_SYMBOL(ipv6_get_saddr); + EXPORT_SYMBOL(ipv6_chk_addr); + EXPORT_SYMBOL(in6_dev_finish_destroy); ++ ++#if defined(CONFIG_IPV6_TUNNEL_MODULE) || defined(CONFIG_IPV6_MOBILITY_MODULE) ++EXPORT_SYMBOL(ip6_build_xmit); ++EXPORT_SYMBOL(rt6_lookup); ++EXPORT_SYMBOL(ipv6_ext_hdr); ++#endif ++#ifdef CONFIG_IPV6_MOBILITY_MODULE ++EXPORT_SYMBOL(mipv6_functions); ++EXPORT_SYMBOL(mipv6_invalidate_calls); ++#if defined(CONFIG_IPV6_MOBILITY_HA_MODULE) || defined(CONFIG_IPV6_MOBILITY_MN_MODULE) ++EXPORT_SYMBOL(ip6_route_add); ++EXPORT_SYMBOL(ip6_route_del); ++EXPORT_SYMBOL(ipv6_get_lladdr); ++EXPORT_SYMBOL(ipv6_get_ifaddr); ++EXPORT_SYMBOL(nd_tbl); ++EXPORT_SYMBOL(ndisc_send_ns); ++EXPORT_SYMBOL(ndisc_send_na); ++EXPORT_SYMBOL(ndisc_next_option); ++EXPORT_SYMBOL(inet6_ifa_finish_destroy); ++#endif ++#ifdef CONFIG_IPV6_MOBILITY_HA_MODULE ++EXPORT_SYMBOL(ipv6_dev_ac_dec); ++EXPORT_SYMBOL(ipv6_dev_ac_inc); ++EXPORT_SYMBOL(ipv6_dev_mc_dec); ++EXPORT_SYMBOL(ipv6_dev_mc_inc); ++EXPORT_SYMBOL(ip6_forward); ++EXPORT_SYMBOL(ip6_input); ++EXPORT_SYMBOL(ipv6_chk_acast_addr); ++#endif ++#ifdef CONFIG_IPV6_MOBILITY_MN_MODULE ++#endif ++EXPORT_SYMBOL(addrconf_add_ifaddr); ++EXPORT_SYMBOL(addrconf_del_ifaddr); ++EXPORT_SYMBOL(addrconf_dad_start); ++EXPORT_SYMBOL(ip6_del_rt); ++EXPORT_SYMBOL(ip6_routing_table); ++EXPORT_SYMBOL(rt6_get_dflt_router); ++EXPORT_SYMBOL(rt6_purge_dflt_routers); ++EXPORT_SYMBOL(rt6_lock); ++EXPORT_SYMBOL(ndisc_send_rs); ++EXPORT_SYMBOL(fib6_clean_tree); ++EXPORT_SYMBOL(ipv6_del_addr); ++EXPORT_SYMBOL(ipv6_generate_eui64); ++EXPORT_SYMBOL(ipv6_inherit_eui64); ++#endif +diff -uprN linux-2.4.25.old/net/ipv6/ipv6_tunnel.c linux-2.4.25/net/ipv6/ipv6_tunnel.c +--- linux-2.4.25.old/net/ipv6/ipv6_tunnel.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/ipv6_tunnel.c 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,1604 @@ ++/* ++ * IPv6 over IPv6 tunnel device ++ * Linux INET6 implementation ++ * ++ * Authors: ++ * Ville Nuorvala ++ * ++ * $Id$ ++ * ++ * Based on: ++ * linux/net/ipv6/sit.c ++ * ++ * RFC 2473 ++ * ++ * 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. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++MODULE_AUTHOR("Ville Nuorvala"); ++MODULE_DESCRIPTION("IPv6-in-IPv6 tunnel"); ++MODULE_LICENSE("GPL"); ++ ++#define IPV6_TLV_TEL_DST_SIZE 8 ++ ++#ifdef IP6_TNL_DEBUG ++#define IP6_TNL_TRACE(x...) printk(KERN_DEBUG "%s:" x "\n", __FUNCTION__) ++#else ++#define IP6_TNL_TRACE(x...) do {;} while(0) ++#endif ++ ++#define IPV6_TCLASS_MASK (IPV6_FLOWINFO_MASK & ~IPV6_FLOWLABEL_MASK) ++ ++#define HASH_SIZE 32 ++ ++#define HASH(addr) (((addr)->s6_addr32[0] ^ (addr)->s6_addr32[1] ^ \ ++ (addr)->s6_addr32[2] ^ (addr)->s6_addr32[3]) & \ ++ (HASH_SIZE - 1)) ++ ++static int ip6ip6_fb_tnl_dev_init(struct net_device *dev); ++static int ip6ip6_tnl_dev_init(struct net_device *dev); ++ ++/* the IPv6 IPv6 tunnel fallback device */ ++static struct net_device ip6ip6_fb_tnl_dev = { ++ name: "ip6tnl0", ++ init: ip6ip6_fb_tnl_dev_init ++}; ++ ++/* the IPv6 IPv6 fallback tunnel */ ++static struct ip6_tnl ip6ip6_fb_tnl = { ++ dev: &ip6ip6_fb_tnl_dev, ++ parms:{name: "ip6tnl0", proto: IPPROTO_IPV6} ++}; ++ ++/* lists for storing tunnels in use */ ++static struct ip6_tnl *tnls_r_l[HASH_SIZE]; ++static struct ip6_tnl *tnls_wc[1]; ++static struct ip6_tnl **tnls[2] = { tnls_wc, tnls_r_l }; ++ ++/* list for unused cached kernel tunnels */ ++static struct ip6_tnl *tnls_kernel[1]; ++/* maximum number of cached kernel tunnels */ ++static unsigned int max_kdev_count = 0; ++/* minimum number of cached kernel tunnels */ ++static unsigned int min_kdev_count = 0; ++/* current number of cached kernel tunnels */ ++static unsigned int kdev_count = 0; ++ ++/* lists for tunnel hook functions */ ++static struct list_head hooks[IP6_TNL_MAXHOOKS]; ++ ++/* locks for the different lists */ ++static rwlock_t ip6ip6_lock = RW_LOCK_UNLOCKED; ++static rwlock_t ip6ip6_kernel_lock = RW_LOCK_UNLOCKED; ++static rwlock_t ip6ip6_hook_lock = RW_LOCK_UNLOCKED; ++ ++/* flag indicating if the module is being removed */ ++static int shutdown = 0; ++ ++/** ++ * ip6ip6_tnl_lookup - fetch tunnel matching the end-point addresses ++ * @remote: the address of the tunnel exit-point ++ * @local: the address of the tunnel entry-point ++ * ++ * Return: ++ * tunnel matching given end-points if found, ++ * else fallback tunnel if its device is up, ++ * else %NULL ++ **/ ++ ++struct ip6_tnl * ++ip6ip6_tnl_lookup(struct in6_addr *remote, struct in6_addr *local) ++{ ++ unsigned h0 = HASH(remote); ++ unsigned h1 = HASH(local); ++ struct ip6_tnl *t; ++ ++ for (t = tnls_r_l[h0 ^ h1]; t; t = t->next) { ++ if (!ipv6_addr_cmp(local, &t->parms.laddr) && ++ !ipv6_addr_cmp(remote, &t->parms.raddr) && ++ (t->dev->flags & IFF_UP)) ++ return t; ++ } ++ if ((t = tnls_wc[0]) != NULL && (t->dev->flags & IFF_UP)) ++ return t; ++ ++ return NULL; ++} ++ ++/** ++ * ip6ip6_bucket - get head of list matching given tunnel parameters ++ * @p: parameters containing tunnel end-points ++ * ++ * Description: ++ * ip6ip6_bucket() returns the head of the list matching the ++ * &struct in6_addr entries laddr and raddr in @p. ++ * ++ * Return: head of IPv6 tunnel list ++ **/ ++ ++static struct ip6_tnl ** ++ip6ip6_bucket(struct ip6_tnl_parm *p) ++{ ++ struct in6_addr *remote = &p->raddr; ++ struct in6_addr *local = &p->laddr; ++ unsigned h = 0; ++ int prio = 0; ++ ++ if (!ipv6_addr_any(remote) || !ipv6_addr_any(local)) { ++ prio = 1; ++ h = HASH(remote) ^ HASH(local); ++ } ++ return &tnls[prio][h]; ++} ++ ++/** ++ * ip6ip6_kernel_tnl_link - add new kernel tunnel to cache ++ * @t: kernel tunnel ++ * ++ * Note: ++ * %IP6_TNL_F_KERNEL_DEV is assumed to be raised in t->parms.flags. ++ * See the comments on ip6ip6_kernel_tnl_add() for more information. ++ **/ ++ ++static inline void ++ip6ip6_kernel_tnl_link(struct ip6_tnl *t) ++{ ++ write_lock_bh(&ip6ip6_kernel_lock); ++ t->next = tnls_kernel[0]; ++ tnls_kernel[0] = t; ++ kdev_count++; ++ write_unlock_bh(&ip6ip6_kernel_lock); ++} ++ ++/** ++ * ip6ip6_kernel_tnl_unlink - remove first kernel tunnel from cache ++ * ++ * Return: first free kernel tunnel ++ * ++ * Note: ++ * See the comments on ip6ip6_kernel_tnl_add() for more information. ++ **/ ++ ++static inline struct ip6_tnl * ++ip6ip6_kernel_tnl_unlink(void) ++{ ++ struct ip6_tnl *t; ++ ++ write_lock_bh(&ip6ip6_kernel_lock); ++ if ((t = tnls_kernel[0]) != NULL) { ++ tnls_kernel[0] = t->next; ++ kdev_count--; ++ } ++ write_unlock_bh(&ip6ip6_kernel_lock); ++ return t; ++} ++ ++/** ++ * ip6ip6_tnl_link - add tunnel to hash table ++ * @t: tunnel to be added ++ **/ ++ ++static void ++ip6ip6_tnl_link(struct ip6_tnl *t) ++{ ++ struct ip6_tnl **tp = ip6ip6_bucket(&t->parms); ++ ++ write_lock_bh(&ip6ip6_lock); ++ t->next = *tp; ++ *tp = t; ++ write_unlock_bh(&ip6ip6_lock); ++} ++ ++/** ++ * ip6ip6_tnl_unlink - remove tunnel from hash table ++ * @t: tunnel to be removed ++ **/ ++ ++static void ++ip6ip6_tnl_unlink(struct ip6_tnl *t) ++{ ++ struct ip6_tnl **tp; ++ ++ write_lock_bh(&ip6ip6_lock); ++ for (tp = ip6ip6_bucket(&t->parms); *tp; tp = &(*tp)->next) { ++ if (t == *tp) { ++ *tp = t->next; ++ break; ++ } ++ } ++ write_unlock_bh(&ip6ip6_lock); ++} ++ ++/** ++ * ip6ip6_tnl_create() - create a new tunnel ++ * @p: tunnel parameters ++ * @pt: pointer to new tunnel ++ * ++ * Description: ++ * Create tunnel matching given parameters. New kernel managed devices are ++ * not put in the normal hash structure, but are instead cached for later ++ * use. ++ * ++ * Return: ++ * 0 on success ++ **/ ++ ++ ++static int __ip6ip6_tnl_create(struct ip6_tnl_parm *p, ++ struct ip6_tnl **pt, ++ int kernel_list) ++{ ++ struct net_device *dev; ++ int err = -ENOBUFS; ++ struct ip6_tnl *t; ++ ++ MOD_INC_USE_COUNT; ++ dev = kmalloc(sizeof (*dev) + sizeof (*t), GFP_KERNEL); ++ if (!dev) { ++ MOD_DEC_USE_COUNT; ++ return err; ++ } ++ memset(dev, 0, sizeof (*dev) + sizeof (*t)); ++ dev->priv = (void *) (dev + 1); ++ t = (struct ip6_tnl *) dev->priv; ++ t->dev = dev; ++ dev->init = ip6ip6_tnl_dev_init; ++ dev->features |= NETIF_F_DYNALLOC; ++ if (kernel_list) { ++ memcpy(t->parms.name, p->name, IFNAMSIZ - 1); ++ t->parms.proto = IPPROTO_IPV6; ++ t->parms.flags = IP6_TNL_F_KERNEL_DEV; ++ } else { ++ memcpy(&t->parms, p, sizeof (*p)); ++ } ++ t->parms.name[IFNAMSIZ - 1] = '\0'; ++ strcpy(dev->name, t->parms.name); ++ if (!dev->name[0]) { ++ int i; ++ for (i = 0; i < IP6_TNL_MAX; i++) { ++ sprintf(dev->name, "ip6tnl%d", i); ++ if (__dev_get_by_name(dev->name) == NULL) ++ break; ++ } ++ ++ if (i == IP6_TNL_MAX) { ++ goto failed; ++ } ++ memcpy(t->parms.name, dev->name, IFNAMSIZ); ++ } ++ if ((err = register_netdevice(dev)) < 0) { ++ goto failed; ++ } ++ dev_hold(dev); ++ if (kernel_list) { ++ ip6ip6_kernel_tnl_link(t); ++ } else { ++ ip6ip6_tnl_link(t); ++ } ++ *pt = t; ++ return 0; ++failed: ++ kfree(dev); ++ MOD_DEC_USE_COUNT; ++ return err; ++} ++ ++ ++int ip6ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt) ++{ ++ return __ip6ip6_tnl_create(p, pt, 0); ++} ++ ++ ++static void manage_kernel_tnls(void *foo); ++ ++static struct tq_struct manager_task = { ++ routine:manage_kernel_tnls, ++ data:NULL ++}; ++ ++/** ++ * manage_kernel_tnls() - create and destroy kernel tunnels ++ * ++ * Description: ++ * manage_kernel_tnls() creates new kernel devices if there ++ * are less than $min_kdev_count of them and deletes old ones if ++ * there are less than $max_kdev_count of them in the cache ++ * ++ * Note: ++ * Schedules itself to be run later in process context if called from ++ * interrupt. Therefore only works synchronously when called from process ++ * context. ++ **/ ++ ++static void ++manage_kernel_tnls(void *foo) ++{ ++ struct ip6_tnl *t = NULL; ++ struct ip6_tnl_parm parm; ++ ++ /* We can't do this processing in interrupt ++ context so schedule it for later */ ++ if (in_interrupt()) { ++ read_lock(&ip6ip6_kernel_lock); ++ if (!shutdown && ++ (kdev_count < min_kdev_count || ++ kdev_count > max_kdev_count)) { ++ schedule_task(&manager_task); ++ } ++ read_unlock(&ip6ip6_kernel_lock); ++ return; ++ } ++ ++ rtnl_lock(); ++ read_lock_bh(&ip6ip6_kernel_lock); ++ memset(&parm, 0, sizeof (parm)); ++ parm.flags = IP6_TNL_F_KERNEL_DEV; ++ /* Create tunnels until there are at least min_kdev_count */ ++ while (kdev_count < min_kdev_count) { ++ read_unlock_bh(&ip6ip6_kernel_lock); ++ if (!__ip6ip6_tnl_create(&parm, &t, 1)) { ++ dev_open(t->dev); ++ } else { ++ goto err; ++ } ++ read_lock_bh(&ip6ip6_kernel_lock); ++ } ++ ++ /* Destroy tunnels until there are at most max_kdev_count */ ++ while (kdev_count > max_kdev_count) { ++ read_unlock_bh(&ip6ip6_kernel_lock); ++ if ((t = ip6ip6_kernel_tnl_unlink()) != NULL) { ++ unregister_netdevice(t->dev); ++ } else { ++ goto err; ++ } ++ read_lock_bh(&ip6ip6_kernel_lock); ++ } ++ read_unlock_bh(&ip6ip6_kernel_lock); ++err: ++ rtnl_unlock(); ++} ++ ++/** ++ * ip6ip6_tnl_inc_max_kdev_count() - increase max kernel dev cache size ++ * @n: size increase ++ * Description: ++ * Increase the upper limit for the number of kernel devices allowed in the ++ * cache at any on time. ++ **/ ++ ++unsigned int ++ip6ip6_tnl_inc_max_kdev_count(unsigned int n) ++{ ++ write_lock_bh(&ip6ip6_kernel_lock); ++ max_kdev_count += n; ++ write_unlock_bh(&ip6ip6_kernel_lock); ++ manage_kernel_tnls(NULL); ++ return max_kdev_count; ++} ++ ++/** ++ * ip6ip6_tnl_dec_max_kdev_count() - decrease max kernel dev cache size ++ * @n: size decrement ++ * Description: ++ * Decrease the upper limit for the number of kernel devices allowed in the ++ * cache at any on time. ++ **/ ++ ++unsigned int ++ip6ip6_tnl_dec_max_kdev_count(unsigned int n) ++{ ++ write_lock_bh(&ip6ip6_kernel_lock); ++ max_kdev_count -= min(max_kdev_count, n); ++ if (max_kdev_count < min_kdev_count) ++ min_kdev_count = max_kdev_count; ++ write_unlock_bh(&ip6ip6_kernel_lock); ++ manage_kernel_tnls(NULL); ++ return max_kdev_count; ++} ++ ++/** ++ * ip6ip6_tnl_inc_min_kdev_count() - increase min kernel dev cache size ++ * @n: size increase ++ * Description: ++ * Increase the lower limit for the number of kernel devices allowed in the ++ * cache at any on time. ++ **/ ++ ++unsigned int ++ip6ip6_tnl_inc_min_kdev_count(unsigned int n) ++{ ++ write_lock_bh(&ip6ip6_kernel_lock); ++ min_kdev_count += n; ++ if (min_kdev_count > max_kdev_count) ++ max_kdev_count = min_kdev_count; ++ write_unlock_bh(&ip6ip6_kernel_lock); ++ manage_kernel_tnls(NULL); ++ return min_kdev_count; ++} ++ ++/** ++ * ip6ip6_tnl_dec_min_kdev_count() - decrease min kernel dev cache size ++ * @n: size decrement ++ * Description: ++ * Decrease the lower limit for the number of kernel devices allowed in the ++ * cache at any on time. ++ **/ ++ ++unsigned int ++ip6ip6_tnl_dec_min_kdev_count(unsigned int n) ++{ ++ write_lock_bh(&ip6ip6_kernel_lock); ++ min_kdev_count -= min(min_kdev_count, n); ++ write_unlock_bh(&ip6ip6_kernel_lock); ++ manage_kernel_tnls(NULL); ++ return min_kdev_count; ++} ++ ++/** ++ * ip6ip6_tnl_locate - find or create tunnel matching given parameters ++ * @p: tunnel parameters ++ * @create: != 0 if allowed to create new tunnel if no match found ++ * ++ * Description: ++ * ip6ip6_tnl_locate() first tries to locate an existing tunnel ++ * based on @parms. If this is unsuccessful, but @create is set a new ++ * tunnel device is created and registered for use. ++ * ++ * Return: ++ * 0 if tunnel located or created, ++ * -EINVAL if parameters incorrect, ++ * -ENODEV if no matching tunnel available ++ **/ ++ ++int ip6ip6_tnl_locate(struct ip6_tnl_parm *p, struct ip6_tnl **pt, int create) ++{ ++ struct in6_addr *remote = &p->raddr; ++ struct in6_addr *local = &p->laddr; ++ struct ip6_tnl *t; ++ ++ if (p->proto != IPPROTO_IPV6) ++ return -EINVAL; ++ ++ for (t = *ip6ip6_bucket(p); t; t = t->next) { ++ if (!ipv6_addr_cmp(local, &t->parms.laddr) && ++ !ipv6_addr_cmp(remote, &t->parms.raddr)) { ++ *pt = t; ++ return (create ? -EEXIST : 0); ++ } ++ } ++ return ip6ip6_tnl_create(p, pt); ++} ++ ++/** ++ * ip6ip6_tnl_dev_destructor - tunnel device destructor ++ * @dev: the device to be destroyed ++ **/ ++ ++static void ++ip6ip6_tnl_dev_destructor(struct net_device *dev) ++{ ++ if (dev != &ip6ip6_fb_tnl_dev) { ++ MOD_DEC_USE_COUNT; ++ } ++} ++ ++/** ++ * ip6ip6_tnl_dev_uninit - tunnel device uninitializer ++ * @dev: the device to be destroyed ++ * ++ * Description: ++ * ip6ip6_tnl_dev_uninit() removes tunnel from its list ++ **/ ++ ++static void ++ip6ip6_tnl_dev_uninit(struct net_device *dev) ++{ ++ struct ip6_tnl *t = (struct ip6_tnl *) dev->priv; ++ ++ if (dev == &ip6ip6_fb_tnl_dev) { ++ write_lock_bh(&ip6ip6_lock); ++ tnls_wc[0] = NULL; ++ write_unlock_bh(&ip6ip6_lock); ++ } else { ++ ip6ip6_tnl_unlink(t); ++ } ++ sock_release(t->sock); ++ dev_put(dev); ++} ++ ++/** ++ * parse_tvl_tnl_enc_lim - handle encapsulation limit option ++ * @skb: received socket buffer ++ * ++ * Return: ++ * 0 if none was found, ++ * else index to encapsulation limit ++ **/ ++ ++static __u16 ++parse_tlv_tnl_enc_lim(struct sk_buff *skb, __u8 * raw) ++{ ++ struct ipv6hdr *ipv6h = (struct ipv6hdr *) raw; ++ __u8 nexthdr = ipv6h->nexthdr; ++ __u16 off = sizeof (*ipv6h); ++ ++ while (ipv6_ext_hdr(nexthdr) && nexthdr != NEXTHDR_NONE) { ++ __u16 optlen = 0; ++ struct ipv6_opt_hdr *hdr; ++ if (raw + off + sizeof (*hdr) > skb->data && ++ !pskb_may_pull(skb, raw - skb->data + off + sizeof (*hdr))) ++ break; ++ ++ hdr = (struct ipv6_opt_hdr *) (raw + off); ++ if (nexthdr == NEXTHDR_FRAGMENT) { ++ struct frag_hdr *frag_hdr = (struct frag_hdr *) hdr; ++ if (frag_hdr->frag_off) ++ break; ++ optlen = 8; ++ } else if (nexthdr == NEXTHDR_AUTH) { ++ optlen = (hdr->hdrlen + 2) << 2; ++ } else { ++ optlen = ipv6_optlen(hdr); ++ } ++ if (nexthdr == NEXTHDR_DEST) { ++ __u16 i = off + 2; ++ while (1) { ++ struct ipv6_tlv_tnl_enc_lim *tel; ++ ++ /* No more room for encapsulation limit */ ++ if (i + sizeof (*tel) > off + optlen) ++ break; ++ ++ tel = (struct ipv6_tlv_tnl_enc_lim *) &raw[i]; ++ /* return index of option if found and valid */ ++ if (tel->type == IPV6_TLV_TNL_ENCAP_LIMIT && ++ tel->length == 1) ++ return i; ++ /* else jump to next option */ ++ if (tel->type) ++ i += tel->length + 2; ++ else ++ i++; ++ } ++ } ++ nexthdr = hdr->nexthdr; ++ off += optlen; ++ } ++ return 0; ++} ++ ++/** ++ * ip6ip6_err - tunnel error handler ++ * ++ * Description: ++ * ip6ip6_err() should handle errors in the tunnel according ++ * to the specifications in RFC 2473. ++ **/ ++ ++void ip6ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ++ int type, int code, int offset, __u32 info) ++{ ++ struct ipv6hdr *ipv6h = (struct ipv6hdr *) skb->data; ++ struct ip6_tnl *t; ++ int rel_msg = 0; ++ int rel_type = ICMPV6_DEST_UNREACH; ++ int rel_code = ICMPV6_ADDR_UNREACH; ++ __u32 rel_info = 0; ++ __u16 len; ++ ++ /* If the packet doesn't contain the original IPv6 header we are ++ in trouble since we might need the source address for furter ++ processing of the error. */ ++ ++ read_lock(&ip6ip6_lock); ++ if ((t = ip6ip6_tnl_lookup(&ipv6h->daddr, &ipv6h->saddr)) == NULL) ++ goto out; ++ ++ switch (type) { ++ __u32 teli; ++ struct ipv6_tlv_tnl_enc_lim *tel; ++ __u32 mtu; ++ case ICMPV6_DEST_UNREACH: ++ if (net_ratelimit()) ++ printk(KERN_WARNING ++ "%s: Path to destination invalid " ++ "or inactive!\n", t->parms.name); ++ rel_msg = 1; ++ break; ++ case ICMPV6_TIME_EXCEED: ++ if (code == ICMPV6_EXC_HOPLIMIT) { ++ if (net_ratelimit()) ++ printk(KERN_WARNING ++ "%s: Too small hop limit or " ++ "routing loop in tunnel!\n", ++ t->parms.name); ++ rel_msg = 1; ++ } ++ break; ++ case ICMPV6_PARAMPROB: ++ /* ignore if parameter problem not caused by a tunnel ++ encapsulation limit sub-option */ ++ if (code != ICMPV6_HDR_FIELD) { ++ break; ++ } ++ teli = parse_tlv_tnl_enc_lim(skb, skb->data); ++ ++ if (teli && teli == ntohl(info) - 2) { ++ tel = (struct ipv6_tlv_tnl_enc_lim *) &skb->data[teli]; ++ if (tel->encap_limit == 0) { ++ if (net_ratelimit()) ++ printk(KERN_WARNING ++ "%s: Too small encapsulation " ++ "limit or routing loop in " ++ "tunnel!\n", t->parms.name); ++ rel_msg = 1; ++ } ++ } ++ break; ++ case ICMPV6_PKT_TOOBIG: ++ mtu = ntohl(info) - offset; ++ if (mtu < IPV6_MIN_MTU) ++ mtu = IPV6_MIN_MTU; ++ t->dev->mtu = mtu; ++ ++ if ((len = sizeof (*ipv6h) + ipv6h->payload_len) > mtu) { ++ rel_type = ICMPV6_PKT_TOOBIG; ++ rel_code = 0; ++ rel_info = mtu; ++ rel_msg = 1; ++ } ++ break; ++ } ++ if (rel_msg && pskb_may_pull(skb, offset + sizeof (*ipv6h))) { ++ struct rt6_info *rt; ++ struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC); ++ if (!skb2) ++ goto out; ++ ++ dst_release(skb2->dst); ++ skb2->dst = NULL; ++ skb_pull(skb2, offset); ++ skb2->nh.raw = skb2->data; ++ ++ /* Try to guess incoming interface */ ++ rt = rt6_lookup(&skb2->nh.ipv6h->saddr, NULL, 0, 0); ++ ++ if (rt && rt->rt6i_dev) ++ skb2->dev = rt->rt6i_dev; ++ ++ icmpv6_send(skb2, rel_type, rel_code, rel_info, skb2->dev); ++ ++ if (rt) ++ dst_release(&rt->u.dst); ++ ++ kfree_skb(skb2); ++ } ++out: ++ read_unlock(&ip6ip6_lock); ++} ++ ++/** ++ * call_hooks - call ipv6 tunnel hooks ++ * @hooknum: hook number, either %IP6_TNL_PRE_ENCAP, or ++ * %IP6_TNL_PRE_DECAP ++ * @t: the current tunnel ++ * @skb: the tunneled packet ++ * ++ * Description: ++ * Pass packet to all the hook functions until %IP6_TNL_DROP ++ * ++ * Return: ++ * %IP6_TNL_ACCEPT or %IP6_TNL_DROP ++ **/ ++ ++static inline int ++call_hooks(unsigned int hooknum, struct ip6_tnl *t, struct sk_buff *skb) ++{ ++ struct ip6_tnl_hook_ops *h; ++ int accept = IP6_TNL_ACCEPT; ++ ++ if (hooknum < IP6_TNL_MAXHOOKS) { ++ struct list_head *i; ++ read_lock(&ip6ip6_hook_lock); ++ for (i = hooks[hooknum].next; i != &hooks[hooknum]; i = i->next) { ++ h = (struct ip6_tnl_hook_ops *) i; ++ ++ if (h->hook) { ++ accept = h->hook(t, skb); ++ ++ if (accept != IP6_TNL_ACCEPT) ++ break; ++ } ++ } ++ read_unlock(&ip6ip6_hook_lock); ++ } ++ return accept; ++} ++ ++/** ++ * ip6ip6_rcv - decapsulate IPv6 packet and retransmit it locally ++ * @skb: received socket buffer ++ * ++ * Return: 0 ++ **/ ++ ++int ip6ip6_rcv(struct sk_buff *skb) ++{ ++ struct ipv6hdr *ipv6h; ++ struct ip6_tnl *t; ++ ++ if (!pskb_may_pull(skb, sizeof (*ipv6h))) ++ goto discard; ++ ++ ipv6h = skb->nh.ipv6h; ++ ++ read_lock(&ip6ip6_lock); ++ ++ if ((t = ip6ip6_tnl_lookup(&ipv6h->saddr, &ipv6h->daddr)) != NULL) { ++ if (!(t->parms.flags & IP6_TNL_F_CAP_RCV) || ++ call_hooks(IP6_TNL_PRE_DECAP, t, skb) != IP6_TNL_ACCEPT) { ++ t->stat.rx_dropped++; ++ read_unlock(&ip6ip6_lock); ++ goto discard; ++ } ++ skb->mac.raw = skb->nh.raw; ++ skb->nh.raw = skb->data; ++ skb->protocol = htons(ETH_P_IPV6); ++ skb->pkt_type = PACKET_HOST; ++ memset(skb->cb, 0, sizeof(struct inet6_skb_parm)); ++ skb->dev = t->dev; ++ dst_release(skb->dst); ++ skb->dst = NULL; ++ t->stat.rx_packets++; ++ t->stat.rx_bytes += skb->len; ++ netif_rx(skb); ++ read_unlock(&ip6ip6_lock); ++ return 0; ++ } ++ read_unlock(&ip6ip6_lock); ++ icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH, 0, skb->dev); ++discard: ++ kfree_skb(skb); ++ return 0; ++} ++ ++static inline struct ipv6_txoptions *create_tel(__u8 encap_limit) ++{ ++ struct ipv6_tlv_tnl_enc_lim *tel; ++ struct ipv6_txoptions *opt; ++ __u8 *raw; ++ ++ int opt_len = sizeof(*opt) + IPV6_TLV_TEL_DST_SIZE; ++ ++ if (!(opt = kmalloc(opt_len, GFP_ATOMIC))) { ++ return NULL; ++ } ++ memset(opt, 0, opt_len); ++ opt->tot_len = opt_len; ++ opt->dst0opt = (struct ipv6_opt_hdr *) (opt + 1); ++ opt->opt_nflen = 8; ++ ++ tel = (struct ipv6_tlv_tnl_enc_lim *) (opt->dst0opt + 1); ++ tel->type = IPV6_TLV_TNL_ENCAP_LIMIT; ++ tel->length = 1; ++ tel->encap_limit = encap_limit; ++ ++ raw = (__u8 *) opt->dst0opt; ++ raw[5] = IPV6_TLV_PADN; ++ raw[6] = 1; ++ ++ return opt; ++} ++ ++static int ++ip6ip6_getfrag(const void *data, struct in6_addr *addr, ++ char *buff, unsigned int offset, unsigned int len) ++{ ++ memcpy(buff, data + offset, len); ++ return 0; ++} ++ ++/** ++ * ip6ip6_tnl_addr_conflict - compare packet addresses to tunnel's own ++ * @t: the outgoing tunnel device ++ * @hdr: IPv6 header from the incoming packet ++ * ++ * Description: ++ * Avoid trivial tunneling loop by checking that tunnel exit-point ++ * doesn't match source of incoming packet. ++ * ++ * Return: ++ * 1 if conflict, ++ * 0 else ++ **/ ++ ++static inline int ++ip6ip6_tnl_addr_conflict(struct ip6_tnl *t, struct ipv6hdr *hdr) ++{ ++ return !ipv6_addr_cmp(&t->parms.raddr, &hdr->saddr); ++} ++ ++/** ++ * ip6ip6_tnl_xmit - encapsulate packet and send ++ * @skb: the outgoing socket buffer ++ * @dev: the outgoing tunnel device ++ * ++ * Description: ++ * Build new header and do some sanity checks on the packet before sending ++ * it to ip6_build_xmit(). ++ * ++ * Return: ++ * 0 ++ **/ ++ ++int ip6ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev) ++{ ++ struct ip6_tnl *t = (struct ip6_tnl *) dev->priv; ++ struct net_device_stats *stats = &t->stat; ++ struct ipv6hdr *ipv6h = skb->nh.ipv6h; ++ struct ipv6_txoptions *opt = NULL; ++ int encap_limit = -1; ++ __u16 offset; ++ struct flowi fl; ++ int err = 0; ++ struct dst_entry *dst; ++ struct sock *sk = t->sock->sk; ++ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6; ++ int mtu; ++ ++ if (t->recursion++) { ++ stats->collisions++; ++ goto tx_err; ++ } ++ if (skb->protocol != htons(ETH_P_IPV6) || ++ !(t->parms.flags & IP6_TNL_F_CAP_XMIT) || ++ ip6ip6_tnl_addr_conflict(t, ipv6h)) { ++ goto tx_err; ++ } ++ if ((offset = parse_tlv_tnl_enc_lim(skb, skb->nh.raw)) > 0) { ++ struct ipv6_tlv_tnl_enc_lim *tel; ++ tel = (struct ipv6_tlv_tnl_enc_lim *) &skb->nh.raw[offset]; ++ if (tel->encap_limit == 0) { ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, offset + 2, skb->dev); ++ goto tx_err; ++ } ++ encap_limit = tel->encap_limit - 1; ++ } else if (!(t->parms.flags & IP6_TNL_F_IGN_ENCAP_LIMIT)) { ++ encap_limit = t->parms.encap_limit; ++ } ++ if (call_hooks(IP6_TNL_PRE_ENCAP, t, skb) != IP6_TNL_ACCEPT) ++ goto discard; ++ memcpy(&fl, &t->fl, sizeof (fl)); ++ ++ if ((t->parms.flags & IP6_TNL_F_USE_ORIG_TCLASS)) ++ fl.fl6_flowlabel |= (*(__u32 *) ipv6h & IPV6_TCLASS_MASK); ++ if ((t->parms.flags & IP6_TNL_F_USE_ORIG_FLOWLABEL)) ++ fl.fl6_flowlabel |= (*(__u32 *) ipv6h & IPV6_FLOWLABEL_MASK); ++ ++ if (encap_limit >= 0 && (opt = create_tel(encap_limit)) == NULL) ++ goto tx_err; ++ ++ dst = __sk_dst_check(sk, np->dst_cookie); ++ ++ if (dst) { ++ if (np->daddr_cache == NULL || ++ ipv6_addr_cmp(fl.fl6_dst, np->daddr_cache) || ++#ifdef CONFIG_IPV6_SUBTREES ++ np->saddr_cache == NULL || ++ ipv6_addr_cmp(fl.fl6_src, np->saddr_cache) || ++#endif ++ (fl.oif && fl.oif != dst->dev->ifindex)) { ++ dst = NULL; ++ } else { ++ dst_hold(dst); ++ } ++ } ++ if (dst == NULL) { ++ dst = ip6_route_output(sk, &fl); ++ if (dst->error) { ++ stats->tx_carrier_errors++; ++ dst_link_failure(skb); ++ goto tx_err_dst_release; ++ } ++ /* local routing loop */ ++ if (dst->dev == dev) { ++ stats->collisions++; ++ if (net_ratelimit()) ++ printk(KERN_WARNING ++ "%s: Local routing loop detected!\n", ++ t->parms.name); ++ goto tx_err_dst_release; ++ } ++ } ++ mtu = dst->pmtu - sizeof (*ipv6h); ++ if (opt) { ++ mtu -= (opt->opt_nflen + opt->opt_flen); ++ } ++ if (mtu < IPV6_MIN_MTU) ++ mtu = IPV6_MIN_MTU; ++ if (skb->dst && mtu < skb->dst->pmtu) { ++ struct rt6_info *rt = (struct rt6_info *) skb->dst; ++ rt->rt6i_flags |= RTF_MODIFIED; ++ rt->u.dst.pmtu = mtu; ++ } ++ if (skb->len > mtu) { ++ icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, dev); ++ goto tx_err_dst_release; ++ } ++ ip6_dst_store(sk, dst, &np->daddr, &np->saddr); ++ err = ip6_build_xmit(sk, ip6ip6_getfrag, (void *) skb->nh.raw, ++ &fl, skb->len, opt, t->parms.hop_limit, ++ MSG_DONTWAIT); ++ ++ if (err == NET_XMIT_SUCCESS || err == NET_XMIT_CN) { ++ stats->tx_bytes += skb->len; ++ stats->tx_packets++; ++ } else { ++ stats->tx_errors++; ++ stats->tx_aborted_errors++; ++ } ++ if (opt) ++ kfree(opt); ++ kfree_skb(skb); ++ t->recursion--; ++ return 0; ++tx_err_dst_release: ++ dst_release(dst); ++ if (opt) ++ kfree(opt); ++tx_err: ++ stats->tx_errors++; ++discard: ++ stats->tx_dropped++; ++ kfree_skb(skb); ++ t->recursion--; ++ return 0; ++} ++ ++static void ip6_tnl_set_cap(struct ip6_tnl *t) ++{ ++ struct ip6_tnl_parm *p = &t->parms; ++ struct in6_addr *laddr = &p->laddr; ++ struct in6_addr *raddr = &p->raddr; ++ int ltype = ipv6_addr_type(laddr); ++ int rtype = ipv6_addr_type(raddr); ++ ++ p->flags &= ~(IP6_TNL_F_CAP_XMIT|IP6_TNL_F_CAP_RCV); ++ ++ if (ltype != IPV6_ADDR_ANY && rtype != IPV6_ADDR_ANY && ++ ((ltype|rtype) & ++ (IPV6_ADDR_UNICAST| ++ IPV6_ADDR_LOOPBACK|IPV6_ADDR_LINKLOCAL| ++ IPV6_ADDR_MAPPED|IPV6_ADDR_RESERVED)) == IPV6_ADDR_UNICAST) { ++ struct net_device *ldev = NULL; ++ int l_ok = 1; ++ int r_ok = 1; ++ ++ if (p->link) ++ ldev = dev_get_by_index(p->link); ++ ++ if ((ltype&IPV6_ADDR_UNICAST) && !ipv6_chk_addr(laddr, ldev)) ++ l_ok = 0; ++ ++ if ((rtype&IPV6_ADDR_UNICAST) && ipv6_chk_addr(raddr, NULL)) ++ r_ok = 0; ++ ++ if (l_ok && r_ok) { ++ if (ltype&IPV6_ADDR_UNICAST) ++ p->flags |= IP6_TNL_F_CAP_XMIT; ++ if (rtype&IPV6_ADDR_UNICAST) ++ p->flags |= IP6_TNL_F_CAP_RCV; ++ } ++ if (ldev) ++ dev_put(ldev); ++ } ++} ++ ++static void ip6ip6_tnl_link_config(struct ip6_tnl *t) ++{ ++ struct net_device *dev = t->dev; ++ struct ip6_tnl_parm *p = &t->parms; ++ struct flowi *fl = &t->fl; ++ ++ /* Set up flowi template */ ++ fl->fl6_src = &p->laddr; ++ fl->fl6_dst = &p->raddr; ++ fl->oif = p->link; ++ fl->fl6_flowlabel = 0; ++ ++ if (!(p->flags&IP6_TNL_F_USE_ORIG_TCLASS)) ++ fl->fl6_flowlabel |= IPV6_TCLASS_MASK & htonl(p->flowinfo); ++ if (!(p->flags&IP6_TNL_F_USE_ORIG_FLOWLABEL)) ++ fl->fl6_flowlabel |= IPV6_FLOWLABEL_MASK & htonl(p->flowinfo); ++ ++ ip6_tnl_set_cap(t); ++ ++ if (p->flags&IP6_TNL_F_CAP_XMIT && p->flags&IP6_TNL_F_CAP_RCV) ++ dev->flags |= IFF_POINTOPOINT; ++ else ++ dev->flags &= ~IFF_POINTOPOINT; ++ ++ if (p->flags & IP6_TNL_F_CAP_XMIT) { ++ struct rt6_info *rt = rt6_lookup(&p->raddr, &p->laddr, ++ p->link, 0); ++ ++ if (rt == NULL) ++ return; ++ ++ if (rt->rt6i_dev) { ++ dev->iflink = rt->rt6i_dev->ifindex; ++ ++ dev->hard_header_len = rt->rt6i_dev->hard_header_len + ++ sizeof (struct ipv6hdr); ++ ++ dev->mtu = rt->rt6i_dev->mtu - sizeof (struct ipv6hdr); ++ ++ if (dev->mtu < IPV6_MIN_MTU) ++ dev->mtu = IPV6_MIN_MTU; ++ } ++ dst_release(&rt->u.dst); ++ } ++} ++ ++/** ++ * __ip6ip6_tnl_change - update the tunnel parameters ++ * @t: tunnel to be changed ++ * @p: tunnel configuration parameters ++ * ++ * Description: ++ * __ip6ip6_tnl_change() updates the tunnel parameters ++ **/ ++ ++static void ++__ip6ip6_tnl_change(struct ip6_tnl *t, struct ip6_tnl_parm *p) ++{ ++ ipv6_addr_copy(&t->parms.laddr, &p->laddr); ++ ipv6_addr_copy(&t->parms.raddr, &p->raddr); ++ t->parms.flags = p->flags; ++ t->parms.hop_limit = p->hop_limit; ++ t->parms.encap_limit = p->encap_limit; ++ t->parms.flowinfo = p->flowinfo; ++ ip6ip6_tnl_link_config(t); ++} ++ ++void ip6ip6_tnl_change(struct ip6_tnl *t, struct ip6_tnl_parm *p) ++{ ++ ip6ip6_tnl_unlink(t); ++ __ip6ip6_tnl_change(t, p); ++ ip6ip6_tnl_link(t); ++} ++ ++/** ++ * ip6ip6_kernel_tnl_add - configure and add kernel tunnel to hash ++ * @p: kernel tunnel configuration parameters ++ * ++ * Description: ++ * ip6ip6_kernel_tnl_add() fetches an unused kernel tunnel configures ++ * it according to @p and places it among the active tunnels. ++ * ++ * Return: ++ * number of references to tunnel on success, ++ * %-EEXIST if there is already a device matching description ++ * %-EINVAL if p->flags doesn't have %IP6_TNL_F_KERNEL_DEV raised, ++ * %-ENODEV if there are no unused kernel tunnels available ++ * ++ * Note: ++ * The code for creating, opening, closing and destroying network devices ++ * must be called from process context, while the Mobile IP code, which ++ * needs the tunnel devices, unfortunately runs in interrupt context. ++ * ++ * The devices must be created and opened in advance, then placed in a ++ * list where the kernel can fetch and ready them for use at a later time. ++ * ++ **/ ++ ++int ++ip6ip6_kernel_tnl_add(struct ip6_tnl_parm *p) ++{ ++ struct ip6_tnl *t; ++ ++ if (!(p->flags & IP6_TNL_F_KERNEL_DEV)) ++ return -EINVAL; ++ if ((t = ip6ip6_tnl_lookup(&p->raddr, &p->laddr)) != NULL && ++ t != &ip6ip6_fb_tnl) { ++ /* Handle duplicate tunnels by incrementing ++ reference count */ ++ atomic_inc(&t->refcnt); ++ goto out; ++ } ++ if ((t = ip6ip6_kernel_tnl_unlink()) == NULL) ++ return -ENODEV; ++ __ip6ip6_tnl_change(t, p); ++ ++ atomic_inc(&t->refcnt); ++ ++ ip6ip6_tnl_link(t); ++ ++ manage_kernel_tnls(NULL); ++out: ++ return atomic_read(&t->refcnt); ++} ++ ++/** ++ * ip6ip6_kernel_tnl_del - delete no longer needed kernel tunnel ++ * @t: kernel tunnel to be removed from hash ++ * ++ * Description: ++ * ip6ip6_kernel_tnl_del() removes and deconfigures the tunnel @t ++ * and places it among the unused kernel devices. ++ * ++ * Return: ++ * number of references on success, ++ * %-EINVAL if p->flags doesn't have %IP6_TNL_F_KERNEL_DEV raised, ++ * ++ * Note: ++ * See the comments on ip6ip6_kernel_tnl_add() for more information. ++ **/ ++ ++int ++ip6ip6_kernel_tnl_del(struct ip6_tnl *t) ++{ ++ if (!t) ++ return -ENODEV; ++ ++ if (!(t->parms.flags & IP6_TNL_F_KERNEL_DEV)) ++ return -EINVAL; ++ ++ if (atomic_dec_and_test(&t->refcnt)) { ++ struct ip6_tnl_parm p; ++ ip6ip6_tnl_unlink(t); ++ memset(&p, 0, sizeof (p)); ++ p.flags = IP6_TNL_F_KERNEL_DEV; ++ ++ __ip6ip6_tnl_change(t, &p); ++ ++ ip6ip6_kernel_tnl_link(t); ++ ++ manage_kernel_tnls(NULL); ++ } ++ return atomic_read(&t->refcnt); ++} ++ ++/** ++ * ip6ip6_tnl_ioctl - configure ipv6 tunnels from userspace ++ * @dev: virtual device associated with tunnel ++ * @ifr: parameters passed from userspace ++ * @cmd: command to be performed ++ * ++ * Description: ++ * ip6ip6_tnl_ioctl() is used for managing IPv6 tunnels ++ * from userspace. ++ * ++ * The possible commands are the following: ++ * %SIOCGETTUNNEL: get tunnel parameters for device ++ * %SIOCADDTUNNEL: add tunnel matching given tunnel parameters ++ * %SIOCCHGTUNNEL: change tunnel parameters to those given ++ * %SIOCDELTUNNEL: delete tunnel ++ * ++ * The fallback device "ip6tnl0", created during module ++ * initialization, can be used for creating other tunnel devices. ++ * ++ * Return: ++ * 0 on success, ++ * %-EFAULT if unable to copy data to or from userspace, ++ * %-EPERM if current process hasn't %CAP_NET_ADMIN set or attempting ++ * to configure kernel devices from userspace, ++ * %-EINVAL if passed tunnel parameters are invalid, ++ * %-EEXIST if changing a tunnel's parameters would cause a conflict ++ * %-ENODEV if attempting to change or delete a nonexisting device ++ * ++ * Note: ++ * See the comments on ip6ip6_kernel_tnl_add() for more information ++ * about kernel tunnels. ++ * **/ ++ ++static int ++ip6ip6_tnl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) ++{ ++ int err = 0; ++ int create; ++ struct ip6_tnl_parm p; ++ struct ip6_tnl *t = NULL; ++ ++ MOD_INC_USE_COUNT; ++ ++ switch (cmd) { ++ case SIOCGETTUNNEL: ++ if (dev == &ip6ip6_fb_tnl_dev) { ++ if (copy_from_user(&p, ++ ifr->ifr_ifru.ifru_data, ++ sizeof (p))) { ++ err = -EFAULT; ++ break; ++ } ++ if ((err = ip6ip6_tnl_locate(&p, &t, 0)) == -ENODEV) ++ t = (struct ip6_tnl *) dev->priv; ++ else if (err) ++ break; ++ } else ++ t = (struct ip6_tnl *) dev->priv; ++ ++ memcpy(&p, &t->parms, sizeof (p)); ++ if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof (p))) { ++ err = -EFAULT; ++ } ++ break; ++ case SIOCADDTUNNEL: ++ case SIOCCHGTUNNEL: ++ err = -EPERM; ++ create = (cmd == SIOCADDTUNNEL); ++ if (!capable(CAP_NET_ADMIN)) ++ break; ++ if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof (p))) { ++ err = -EFAULT; ++ break; ++ } ++ if (p.flags & IP6_TNL_F_KERNEL_DEV) { ++ break; ++ } ++ if (!create && dev != &ip6ip6_fb_tnl_dev) { ++ t = (struct ip6_tnl *) dev->priv; ++ } ++ if (!t && (err = ip6ip6_tnl_locate(&p, &t, create))) { ++ break; ++ } ++ if (cmd == SIOCCHGTUNNEL) { ++ if (t->dev != dev) { ++ err = -EEXIST; ++ break; ++ } ++ if (t->parms.flags & IP6_TNL_F_KERNEL_DEV) { ++ err = -EPERM; ++ break; ++ } ++ ip6ip6_tnl_change(t, &p); ++ netdev_state_change(dev); ++ } ++ if (copy_to_user(ifr->ifr_ifru.ifru_data, ++ &t->parms, sizeof (p))) { ++ err = -EFAULT; ++ } else { ++ err = 0; ++ } ++ break; ++ case SIOCDELTUNNEL: ++ err = -EPERM; ++ if (!capable(CAP_NET_ADMIN)) ++ break; ++ ++ if (dev == &ip6ip6_fb_tnl_dev) { ++ if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, ++ sizeof (p))) { ++ err = -EFAULT; ++ break; ++ } ++ err = ip6ip6_tnl_locate(&p, &t, 0); ++ if (err) ++ break; ++ if (t == &ip6ip6_fb_tnl) { ++ err = -EPERM; ++ break; ++ } ++ } else { ++ t = (struct ip6_tnl *) dev->priv; ++ } ++ if (t->parms.flags & IP6_TNL_F_KERNEL_DEV) ++ err = -EPERM; ++ else ++ err = unregister_netdevice(t->dev); ++ break; ++ default: ++ err = -EINVAL; ++ } ++ MOD_DEC_USE_COUNT; ++ return err; ++} ++ ++/** ++ * ip6ip6_tnl_get_stats - return the stats for tunnel device ++ * @dev: virtual device associated with tunnel ++ * ++ * Return: stats for device ++ **/ ++ ++static struct net_device_stats * ++ip6ip6_tnl_get_stats(struct net_device *dev) ++{ ++ return &(((struct ip6_tnl *) dev->priv)->stat); ++} ++ ++/** ++ * ip6ip6_tnl_change_mtu - change mtu manually for tunnel device ++ * @dev: virtual device associated with tunnel ++ * @new_mtu: the new mtu ++ * ++ * Return: ++ * 0 on success, ++ * %-EINVAL if mtu too small ++ **/ ++ ++static int ++ip6ip6_tnl_change_mtu(struct net_device *dev, int new_mtu) ++{ ++ if (new_mtu < IPV6_MIN_MTU) { ++ return -EINVAL; ++ } ++ dev->mtu = new_mtu; ++ return 0; ++} ++ ++/** ++ * ip6ip6_tnl_dev_init_gen - general initializer for all tunnel devices ++ * @dev: virtual device associated with tunnel ++ * ++ * Description: ++ * Set function pointers and initialize the &struct flowi template used ++ * by the tunnel. ++ **/ ++ ++static int ++ip6ip6_tnl_dev_init_gen(struct net_device *dev) ++{ ++ struct ip6_tnl *t = (struct ip6_tnl *) dev->priv; ++ struct flowi *fl = &t->fl; ++ int err; ++ struct sock *sk; ++ ++ if ((err = sock_create(PF_INET6, SOCK_RAW, IPPROTO_IPV6, &t->sock))) { ++ printk(KERN_ERR ++ "Failed to create IPv6 tunnel socket (err %d).\n", err); ++ return err; ++ } ++ t->sock->inode->i_uid = 0; ++ t->sock->inode->i_gid = 0; ++ ++ sk = t->sock->sk; ++ sk->allocation = GFP_ATOMIC; ++ sk->net_pinfo.af_inet6.hop_limit = 254; ++ sk->net_pinfo.af_inet6.mc_loop = 0; ++ sk->prot->unhash(sk); ++ ++ memset(fl, 0, sizeof (*fl)); ++ fl->proto = IPPROTO_IPV6; ++ ++ dev->destructor = ip6ip6_tnl_dev_destructor; ++ dev->uninit = ip6ip6_tnl_dev_uninit; ++ dev->hard_start_xmit = ip6ip6_tnl_xmit; ++ dev->get_stats = ip6ip6_tnl_get_stats; ++ dev->do_ioctl = ip6ip6_tnl_ioctl; ++ dev->change_mtu = ip6ip6_tnl_change_mtu; ++ ++ dev->type = ARPHRD_TUNNEL6; ++ dev->hard_header_len = LL_MAX_HEADER + sizeof (struct ipv6hdr); ++ dev->mtu = ETH_DATA_LEN - sizeof (struct ipv6hdr); ++ dev->flags |= IFF_NOARP; ++ dev->iflink = 0; ++ /* Hmm... MAX_ADDR_LEN is 8, so the ipv6 addresses can't be ++ copied to dev->dev_addr and dev->broadcast, like the ipv4 ++ addresses were in ipip.c, ip_gre.c and sit.c. */ ++ dev->addr_len = 0; ++ return 0; ++} ++ ++/** ++ * ip6ip6_tnl_dev_init - initializer for all non fallback tunnel devices ++ * @dev: virtual device associated with tunnel ++ **/ ++ ++static int ++ip6ip6_tnl_dev_init(struct net_device *dev) ++{ ++ struct ip6_tnl *t = (struct ip6_tnl *) dev->priv; ++ ip6ip6_tnl_dev_init_gen(dev); ++ ip6ip6_tnl_link_config(t); ++ return 0; ++} ++ ++#ifdef MODULE ++ ++/** ++ * ip6ip6_fb_tnl_open - function called when fallback device opened ++ * @dev: fallback device ++ * ++ * Return: 0 ++ **/ ++ ++static int ++ip6ip6_fb_tnl_open(struct net_device *dev) ++{ ++ MOD_INC_USE_COUNT; ++ return 0; ++} ++ ++/** ++ * ip6ip6_fb_tnl_close - function called when fallback device closed ++ * @dev: fallback device ++ * ++ * Return: 0 ++ **/ ++ ++static int ++ip6ip6_fb_tnl_close(struct net_device *dev) ++{ ++ MOD_DEC_USE_COUNT; ++ return 0; ++} ++#endif ++ ++/** ++ * ip6ip6_fb_tnl_dev_init - initializer for fallback tunnel device ++ * @dev: fallback device ++ * ++ * Return: 0 ++ **/ ++ ++int __init ++ip6ip6_fb_tnl_dev_init(struct net_device *dev) ++{ ++ ip6ip6_tnl_dev_init_gen(dev); ++#ifdef MODULE ++ dev->open = ip6ip6_fb_tnl_open; ++ dev->stop = ip6ip6_fb_tnl_close; ++#endif ++ dev_hold(dev); ++ tnls_wc[0] = &ip6ip6_fb_tnl; ++ return 0; ++} ++ ++/** ++ * ip6ip6_tnl_register_hook - add hook for processing of tunneled packets ++ * @reg: hook function and its parameters ++ * ++ * Description: ++ * Add a netfilter like hook function for special handling of tunneled ++ * packets. The hook functions are called before encapsulation ++ * (%IP6_TNL_PRE_ENCAP) and before decapsulation ++ * (%IP6_TNL_PRE_DECAP). The possible return values by the hook ++ * functions are %IP6_TNL_DROP, %IP6_TNL_ACCEPT and ++ * %IP6_TNL_STOLEN (in case the hook function took care of the packet ++ * and it doesn't have to be processed any further). ++ **/ ++ ++void ++ip6ip6_tnl_register_hook(struct ip6_tnl_hook_ops *reg) ++{ ++ if (reg->hooknum < IP6_TNL_MAXHOOKS) { ++ struct list_head *i; ++ ++ write_lock_bh(&ip6ip6_hook_lock); ++ for (i = hooks[reg->hooknum].next; ++ i != &hooks[reg->hooknum]; i = i->next) { ++ if (reg->priority < ++ ((struct ip6_tnl_hook_ops *) i)->priority) { ++ break; ++ } ++ } ++ list_add(®->list, i->prev); ++ write_unlock_bh(&ip6ip6_hook_lock); ++ } ++} ++ ++/** ++ * ip6ip6_tnl_unregister_hook - remove tunnel hook ++ * @reg: hook function and its parameters ++ **/ ++ ++void ++ip6ip6_tnl_unregister_hook(struct ip6_tnl_hook_ops *reg) ++{ ++ if (reg->hooknum < IP6_TNL_MAXHOOKS) { ++ write_lock_bh(&ip6ip6_hook_lock); ++ list_del(®->list); ++ write_unlock_bh(&ip6ip6_hook_lock); ++ } ++} ++ ++ ++/* the IPv6 over IPv6 protocol structure */ ++static struct inet6_protocol ip6ip6_protocol = { ++ ip6ip6_rcv, /* IPv6 handler */ ++ ip6ip6_err, /* IPv6 error control */ ++ NULL, /* next */ ++ IPPROTO_IPV6, /* protocol ID */ ++ 0, /* copy */ ++ NULL, /* data */ ++ "IPv6 over IPv6" /* name */ ++}; ++ ++/** ++ * ip6_tunnel_init - register protocol and reserve needed resources ++ * ++ * Return: 0 on success ++ **/ ++ ++int __init ip6_tunnel_init(void) ++{ ++ int i, err; ++ ++ ip6ip6_fb_tnl_dev.priv = (void *) &ip6ip6_fb_tnl; ++ ++ for (i = 0; i < IP6_TNL_MAXHOOKS; i++) { ++ INIT_LIST_HEAD(&hooks[i]); ++ } ++ if ((err = register_netdev(&ip6ip6_fb_tnl_dev))) ++ return err; ++ ++ inet6_add_protocol(&ip6ip6_protocol); ++ return 0; ++} ++ ++/** ++ * ip6_tunnel_cleanup - free resources and unregister protocol ++ **/ ++ ++void ip6_tunnel_cleanup(void) ++{ ++ write_lock_bh(&ip6ip6_kernel_lock); ++ shutdown = 1; ++ write_unlock_bh(&ip6ip6_kernel_lock); ++ flush_scheduled_tasks(); ++ manage_kernel_tnls(NULL); ++ inet6_del_protocol(&ip6ip6_protocol); ++ unregister_netdev(&ip6ip6_fb_tnl_dev); ++} ++ ++#ifdef MODULE ++module_init(ip6_tunnel_init); ++module_exit(ip6_tunnel_cleanup); ++#endif ++ ++#if defined(CONFIG_IPV6_MOBILITY_HA_MODULE) || defined(CONFIG_IPV6_MOBILITY_MN_MODULE) ++EXPORT_SYMBOL(ip6ip6_tnl_register_hook); ++EXPORT_SYMBOL(ip6ip6_tnl_unregister_hook); ++#endif ++#ifdef CONFIG_IPV6_MOBILITY_HA_MODULE ++EXPORT_SYMBOL(ip6ip6_tnl_dec_max_kdev_count); ++EXPORT_SYMBOL(ip6ip6_tnl_inc_max_kdev_count); ++EXPORT_SYMBOL(ip6ip6_tnl_dec_min_kdev_count); ++EXPORT_SYMBOL(ip6ip6_tnl_inc_min_kdev_count); ++EXPORT_SYMBOL(ip6ip6_kernel_tnl_add); ++EXPORT_SYMBOL(ip6ip6_kernel_tnl_del); ++EXPORT_SYMBOL(ip6ip6_tnl_lookup); ++#endif ++#ifdef CONFIG_IPV6_MOBILITY_MN_MODULE ++EXPORT_SYMBOL(ip6ip6_tnl_create); ++EXPORT_SYMBOL(ip6ip6_tnl_change); ++#endif ++ +diff -uprN linux-2.4.25.old/net/ipv6/mipglue.c linux-2.4.25/net/ipv6/mipglue.c +--- linux-2.4.25.old/net/ipv6/mipglue.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mipglue.c 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,63 @@ ++/* ++ * Glue for Mobility support integration to IPv6 ++ * ++ * Authors: ++ * Antti Tuominen ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++extern int ip6_tlvopt_unknown(struct sk_buff *skb, int optoff); ++ ++/* Initialize all zero */ ++struct mipv6_callable_functions mipv6_functions = { NULL }; ++ ++/* Sets mipv6_functions struct to zero to invalidate all successive ++ * calls to mipv6 functions. Used on module unload. */ ++ ++void mipv6_invalidate_calls(void) ++{ ++ memset(&mipv6_functions, 0, sizeof(mipv6_functions)); ++} ++ ++ ++/* Selects correct handler for tlv encoded destination option. Called ++ * by ip6_parse_tlv. Checks if mipv6 calls are valid before calling. */ ++ ++int mipv6_handle_dstopt(struct sk_buff *skb, int optoff) ++{ ++ int ret; ++ ++ switch (skb->nh.raw[optoff]) { ++ case MIPV6_TLV_HOMEADDR: ++ ret = MIPV6_CALLFUNC(mipv6_handle_homeaddr, 0)(skb, optoff); ++ break; ++ default: ++ /* Should never happen */ ++ printk(KERN_ERR __FILE__ ": Invalid destination option code (%d)\n", ++ skb->nh.raw[optoff]); ++ ret = 1; ++ break; ++ } ++ ++ /* If mipv6 handlers are not valid, pass the packet to ++ * ip6_tlvopt_unknown() for correct handling. */ ++ if (!ret) ++ return ip6_tlvopt_unknown(skb, optoff); ++ ++ return ret; ++} ++ +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/Config.in linux-2.4.25/net/ipv6/mobile_ip6/Config.in +--- linux-2.4.25.old/net/ipv6/mobile_ip6/Config.in 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/Config.in 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,12 @@ ++# ++# Mobile IPv6 Configuration ++# ++dep_tristate ' IPv6: Mobility Support (Correspondent Node)' CONFIG_IPV6_MOBILITY $CONFIG_IPV6 ++if [ "$CONFIG_IPV6_IPV6_TUNNEL" != "n" ]; then ++ dep_tristate ' MIPv6: Mobile Node Support' CONFIG_IPV6_MOBILITY_MN $CONFIG_IPV6_MOBILITY ++ ++ dep_tristate ' MIPv6: Home Agent Support' CONFIG_IPV6_MOBILITY_HA $CONFIG_IPV6_MOBILITY ++fi ++if [ "$CONFIG_IPV6_MOBILITY" != "n" ]; then ++ bool ' MIPv6: Debug messages' CONFIG_IPV6_MOBILITY_DEBUG ++fi +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/Makefile linux-2.4.25/net/ipv6/mobile_ip6/Makefile +--- linux-2.4.25.old/net/ipv6/mobile_ip6/Makefile 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/Makefile 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,35 @@ ++# ++# Makefile for the MIPL Mobile IPv6 for Linux. ++# ++# Note! Dependencies are done automagically by 'make dep', which also ++# removes any old dependencies. DON'T put your own dependencies here ++# unless it's something special (ie not a .c file). ++# ++ ++ ++O_TARGET := mip6_base.o ++ ++list-multi := mip6_ha.o mip6_mn.o ++ ++obj-y := hashlist.o bcache.o mobhdr_common.o stats.o exthdrs.o \ ++ rr_crypto.o hmac.o auth_opt.o mipv6_icmp.o module_cn.o ++ ++obj-m := $(O_TARGET) ++ ++mip6_ha-objs := halist.o mipv6_icmp_ha.o tunnel_ha.o \ ++ ndisc_ha.o ha.o module_ha.o ++ ++mip6_mn-objs := mipv6_icmp_mn.o ioctl_mn.o tunnel_mn.o \ ++ mdetect.o bul.o multiaccess_ctl.o mobhdr_mn.o mn.o \ ++ module_mn.o ++ ++obj-$(CONFIG_IPV6_MOBILITY_HA) += mip6_ha.o ++obj-$(CONFIG_IPV6_MOBILITY_MN) += mip6_mn.o ++ ++include $(TOPDIR)/Rules.make ++ ++mip6_ha.o: $(mip6_ha-objs) ++ $(LD) -r -o $@ $(mip6_ha-objs) ++ ++mip6_mn.o: $(mip6_mn-objs) ++ $(LD) -r -o $@ $(mip6_mn-objs) +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/README linux-2.4.25/net/ipv6/mobile_ip6/README +--- linux-2.4.25.old/net/ipv6/mobile_ip6/README 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/README 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,15 @@ ++MIPL Mobile IPv6 for Linux ++ ++More information at http://www.mipl.mediapoli.com/. ++ ++To join MIPL Mobile IPv6 for Linux mailing lists go to: ++ ++ http://www.mipl.mediapoli.com/cgi-bin/mailman/listinfo ++ ++Or send mail with subject "subscribe" for the general list to: ++ ++ mipl-request@list.mipl.mediapoli.com ++ ++or for the developer list to: ++ ++ mipl-devel-request@list.mail.mediapoli.com +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/auth_opt.c linux-2.4.25/net/ipv6/mobile_ip6/auth_opt.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/auth_opt.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/auth_opt.c 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,121 @@ ++/* ++ * MIPv6 Binding Authentication Data Option functions ++ * ++ * Authors: ++ * Henrik Petander ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++ ++#include "debug.h" ++#include "hmac.h" ++#include "mobhdr.h" ++ ++#define DBG_KEY 5 ++ ++int mipv6_auth_build(struct in6_addr *cn_addr, struct in6_addr *coa, ++ __u8 *mh, __u8 *aud_data, __u8 *k_bu) ++{ ++ /* First look up the peer from sadb based on his address */ ++ struct ah_processing ahp; ++ ++ /* Don't add any other options or this system is screwed */ ++ ++ __u8 buf[MAX_HASH_LENGTH]; ++ ++ ++ if (!k_bu) { ++ DEBUG(DBG_ERROR, "k_bu missing, aborting"); ++ return -1; ++ } ++ DEBUG(DBG_KEY, "Key for building authenticator:"); ++ debug_print_buffer(DBG_KEY, k_bu, HMAC_SHA1_KEY_SIZE); ++ ++ if (ah_hmac_sha1_init(&ahp, k_bu, HMAC_SHA1_KEY_SIZE) < 0) { ++ DEBUG(DBG_ERROR, "Failed to initialize hmac sha1"); ++ return -1; ++ } ++ ++ DEBUG(DBG_KEY, "coa: "); ++ debug_print_buffer(DBG_KEY, coa, 16); ++ DEBUG(DBG_KEY, "cn_addr: "); ++ debug_print_buffer(DBG_KEY, cn_addr, 16); ++ DEBUG(DBG_KEY, "MH contents: "); ++ debug_print_buffer(DBG_KEY, mh, aud_data - mh); ++ ++ /* First the common part */ ++ ah_hmac_sha1_loop(&ahp, coa, sizeof(struct in6_addr)); ++ ah_hmac_sha1_loop(&ahp, cn_addr, sizeof(struct in6_addr)); ++ ah_hmac_sha1_loop(&ahp, mh, aud_data - mh); ++ ah_hmac_sha1_result(&ahp, buf); ++ ++ memcpy(aud_data, buf, MIPV6_RR_MAC_LENGTH); ++ ++ return 0; ++} ++ ++int mipv6_auth_check(struct in6_addr *cn_addr, struct in6_addr *coa, ++ __u8 *opt, __u8 optlen, ++ struct mipv6_mo_bauth_data *aud, __u8 *k_bu) ++{ ++ int ret = -1; ++ struct ah_processing ahp; ++ __u8 htarget[MAX_HASH_LENGTH]; ++ ++ /* Look up peer by home address */ ++ if (!k_bu) { ++ DEBUG(DBG_ERROR, "k_bu missing, aborting"); ++ return -1; ++ } ++ ++ DEBUG(DBG_KEY, "Key for checking authenticator:"); ++ debug_print_buffer(DBG_KEY, k_bu, HMAC_SHA1_KEY_SIZE); ++ ++ if (!aud || !coa) { ++ DEBUG(DBG_INFO, "%s is NULL", aud ? "coa" : "aud"); ++ goto out; ++ } ++ ++ if (aud->length != MIPV6_RR_MAC_LENGTH) { ++ DEBUG(DBG_ERROR, ++ ": Incorrect authentication option length %d", aud->length); ++ goto out; ++ } ++ ++ if (ah_hmac_sha1_init(&ahp, k_bu, HMAC_SHA1_KEY_SIZE) < 0) { ++ DEBUG(DBG_ERROR, ++ "internal error in initialization of authentication algorithm"); ++ goto out; ++ } ++ DEBUG(DBG_KEY, "coa: "); ++ debug_print_buffer(DBG_KEY, coa, 16); ++ DEBUG(DBG_KEY, "cn_addr: "); ++ debug_print_buffer(DBG_KEY, cn_addr, 16); ++ DEBUG(DBG_KEY, "MH contents: "); ++ debug_print_buffer(DBG_KEY, opt, (u8*) aud->data - opt); ++ ++ ah_hmac_sha1_loop(&ahp, coa, sizeof(struct in6_addr)); ++ ah_hmac_sha1_loop(&ahp, cn_addr, sizeof(struct in6_addr)); ++ ++ /* ++ * Process MH + options till the start of the authenticator in ++ * Auth. data option ++ */ ++ ah_hmac_sha1_loop(&ahp, opt, (u8 *)aud->data - opt); ++ ah_hmac_sha1_result(&ahp, htarget); ++ if (memcmp(htarget, aud->data, MIPV6_RR_MAC_LENGTH) == 0) ++ ret = 0; ++ ++ DEBUG(DBG_ERROR, "returning %d", ret); ++out: ++ return ret; ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/bcache.c linux-2.4.25/net/ipv6/mobile_ip6/bcache.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/bcache.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/bcache.c 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,746 @@ ++/* ++ * Binding Cache ++ * ++ * Authors: ++ * Juha Mynttinen ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++/* ++ * Changes: ++ * ++ * Nanno Langstraat : Timer code cleaned up, active socket ++ * test rewritten ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "bcache.h" ++#include "hashlist.h" ++#include "debug.h" ++#include "mobhdr.h" ++#include "tunnel.h" ++#include "config.h" ++ ++#define TIMERDELAY HZ/10 ++ ++struct mipv6_bcache { ++ struct hashlist *entries; ++ __u32 size; ++ struct timer_list callback_timer; ++}; ++ ++struct in6_addr_pair { ++ struct in6_addr *a1; ++ struct in6_addr *a2; ++}; ++ ++static rwlock_t bcache_lock = RW_LOCK_UNLOCKED; ++ ++static struct mipv6_bcache bcache; ++ ++static int bcache_proc_info(char *buffer, char **start, off_t offset, ++ int length); ++ ++#define MIPV6_BCACHE_HASHSIZE 32 ++ ++/* Moment of transmission of a BR, in seconds before bcache entry expiry */ ++#define BCACHE_BR_SEND_LEAD 3 ++ ++#define MIPV6_MAX_BRR 3 /* Send 3 BRRs before deleting BC entry */ ++#define MIPV6_BRR_RATE HZ /* Send BRRs once per second */ ++ ++/* ++ * Internal functions. ++ */ ++ ++struct cache_entry_iterator_args { ++ struct mipv6_bce **entry; ++}; ++ ++static int find_first_cache_entry_iterator(void *data, void *args, ++ unsigned long *lifetime) ++{ ++ struct mipv6_bce *entry = ++ (struct mipv6_bce *) data; ++ struct cache_entry_iterator_args *state = ++ (struct cache_entry_iterator_args *) args; ++ ++ ASSERT(entry != NULL); ++ ++ if (entry->type == CACHE_ENTRY) { ++ *(state->entry) = entry; ++ return ITERATOR_STOP; /* stop iteration */ ++ } else { ++ return ITERATOR_CONT; /* continue iteration */ ++ } ++} ++ ++ ++/* ++ * Get memory for a new bcache entry. If bcache is full, a cache ++ * entry may be deleted to get space for a home registration, but not ++ * vice versa. ++ */ ++static struct mipv6_bce *mipv6_bce_alloc(__u8 type) ++{ ++ struct mipv6_bce *entry; ++ struct cache_entry_iterator_args args; ++ ++ DEBUG_FUNC(); ++ ++ entry = (struct mipv6_bce *) ++ hashlist_alloc(bcache.entries, SLAB_ATOMIC); ++ ++ /* Cache replacement policy: always replace the CACHE_ENTRY ++ closest to expiration. Type HOME_REGISTRATION entry may ++ never be deleted before expiration. */ ++ if (entry == NULL) { ++ /* cache full, try to delete a CACHE_ENTRY */ ++ args.entry = &entry; ++ hashlist_iterate(bcache.entries, &args, ++ find_first_cache_entry_iterator); ++ if (entry == NULL) ++ return NULL; ++ hashlist_delete(bcache.entries, ++ (struct hashlist_entry *)entry); ++ entry = (struct mipv6_bce *) ++ hashlist_alloc(bcache.entries, SLAB_ATOMIC); ++ } ++ return entry; ++} ++ ++/* ++ * Frees entry's memory allocated with mipv6_bce_alloc ++ */ ++static void mipv6_bce_free(struct mipv6_bce *entry) ++{ ++ hashlist_free(bcache.entries, (void *) entry); ++} ++ ++/* ++ * Removes all expired entries ++ */ ++static void expire(void) ++{ ++ struct mipv6_bce *entry; ++ struct br_addrs { ++ struct in6_addr daddr; ++ struct in6_addr saddr; ++ struct br_addrs *next; ++ }; ++ struct br_addrs *br_info = NULL; ++ ++ DEBUG_FUNC(); ++ ++ write_lock(&bcache_lock); ++ ++ while ((entry = (struct mipv6_bce *) ++ hashlist_get_first(bcache.entries)) != NULL) { ++ struct rt6_info *rt; ++ if (time_after_eq(jiffies, entry->callback_time)) { ++ ++ DEBUG(DBG_INFO, "an entry expired"); ++ ++ if (entry->type & HOME_REGISTRATION) { ++ mip6_fn.proxy_del(&entry->home_addr, entry); ++ } ++ hashlist_delete(bcache.entries, (void *)entry); ++ mipv6_bce_free(entry); ++ entry = NULL; ++ } else if (entry->br_callback_time != 0 && ++ time_after_eq(jiffies, entry->br_callback_time) && ++ entry->br_count < MIPV6_MAX_BRR && ++ (rt = rt6_lookup(&entry->home_addr, &entry->our_addr, 0, 0)) != NULL){ ++ /* Do we have a destination cache entry for the home address */ ++ if (rt->rt6i_flags & RTF_CACHE) { ++ struct br_addrs *tmp; ++ tmp = br_info; ++ DEBUG(DBG_INFO, ++ "bcache entry recently used. Sending BR."); ++ /* queue for sending */ ++ br_info = kmalloc(sizeof(struct br_addrs), ++ GFP_ATOMIC); ++ if (br_info) { ++ ipv6_addr_copy(&br_info->saddr, ++ &entry->our_addr); ++ ipv6_addr_copy(&br_info->daddr, ++ &entry->home_addr); ++ br_info->next = tmp; ++ entry->last_br = jiffies; ++ entry->br_callback_time = jiffies + MIPV6_BRR_RATE; ++ entry->br_count++; ++ } else { ++ br_info = tmp; ++ DEBUG(DBG_ERROR, "Out of memory"); ++ } ++ ++ } else ++ entry->br_callback_time = 0; ++ dst_release(&rt->u.dst); ++ } else { ++ entry->br_callback_time = 0; ++ break; ++ } ++ } ++ write_unlock(&bcache_lock); ++ ++ while (br_info) { ++ struct br_addrs *tmp = br_info->next; ++ if (mipv6_send_brr(&br_info->saddr, &br_info->daddr, NULL) < 0) ++ DEBUG(DBG_WARNING, ++ "BR send for %x:%x:%x:%x:%x:%x:%x:%x failed", ++ NIPV6ADDR(&br_info->daddr)); ++ kfree(br_info); ++ br_info = tmp; ++ } ++} ++ ++static void set_timer(void) ++{ ++ struct mipv6_bce *entry; ++ unsigned long callback_time; ++ ++ DEBUG_FUNC(); ++ ++ entry = (struct mipv6_bce *) ++ hashlist_get_first(bcache.entries); ++ if (entry != NULL) { ++ if (entry->br_callback_time > 0 && ++ time_after(entry->br_callback_time, jiffies)) ++ callback_time = entry->br_callback_time; ++ else if (time_after(entry->callback_time, jiffies)) ++ callback_time = entry->callback_time; ++ else { ++ DEBUG(DBG_WARNING, ++ "bcache timer attempted to schedule" ++ " for a historical jiffies count!"); ++ callback_time = jiffies + TIMERDELAY; ++ } ++ ++ DEBUG(DBG_INFO, "setting timer to now"); ++ mod_timer(&bcache.callback_timer, callback_time); ++ } else { ++ del_timer(&bcache.callback_timer); ++ DEBUG(DBG_INFO, "BC empty, not setting a new timer"); ++ } ++} ++ ++/* ++ * The function that is scheduled to do the callback functions. May be ++ * modified e.g to allow Binding Requests, now only calls expire() and ++ * schedules a new timer. ++ */ ++static void timer_handler(unsigned long dummy) ++{ ++ expire(); ++ write_lock(&bcache_lock); ++ set_timer(); ++ write_unlock(&bcache_lock); ++} ++ ++/* ++ * Interface functions visible to other modules ++ */ ++ ++/** ++ * mipv6_bcache_add - add Binding Cache entry ++ * @ifindex: interface index ++ * @our_addr: own address ++ * @home_addr_org: MN's home address ++ * @coa: MN's care-of address ++ * @lifetime: lifetime for this binding ++ * @prefix: prefix length ++ * @seq: sequence number ++ * @flags: flags received in BU ++ * @type: type of entry ++ * ++ * Adds an entry for this @home_addr_org in the Binding Cache. If entry ++ * already exists, old entry is updated. @type may be %CACHE_ENTRY or ++ * %HOME_REGISTRATION. ++ **/ ++int mipv6_bcache_add(int ifindex, ++ struct in6_addr *our_addr, ++ struct in6_addr *home_addr, ++ struct in6_addr *coa, ++ __u32 lifetime, __u16 seq, __u8 flags, __u8 type) ++{ ++ struct mipv6_bce *entry; ++ int update = 0; ++ int create_tunnel = 0; ++ unsigned long now = jiffies; ++ struct in6_addr_pair hashkey; ++ int ret = -1; ++ ++ DEBUG_FUNC(); ++ ++ hashkey.a1 = home_addr; ++ hashkey.a2 = our_addr; ++ ++ write_lock(&bcache_lock); ++ ++ if (type == HOME_REGISTRATION && !(mip6node_cnf.capabilities&CAP_HA)) ++ return 0; ++ ++ if (unlikely(bcache.entries == NULL)) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ ++ if ((entry = (struct mipv6_bce *) ++ hashlist_get(bcache.entries, &hashkey)) != NULL) { ++ /* if an entry for this home_addr exists (with smaller ++ * seq than the new seq), update it by removing it ++ * first ++ */ ++ if (!MIPV6_SEQ_GT(seq, entry->seq)) { ++ DEBUG(DBG_INFO, "smaller seq than existing, not updating"); ++ goto out; ++ } ++ DEBUG(DBG_INFO, "updating an existing entry"); ++ update = 1; ++ ++ /* H-flag is already checked in BU handler. */ ++ /* XXX: Should we care about the other flags?*/ ++ if (flags != entry->flags) { ++ DEBUG(DBG_INFO, "entry/BU flag mismatch"); ++ } ++ ++ if (type == HOME_REGISTRATION) { ++ create_tunnel = (ipv6_addr_cmp(&entry->coa, coa) || ++ entry->ifindex != ifindex); ++ } ++ } else { ++ /* no entry for this home_addr, try to create a new entry */ ++ DEBUG(DBG_INFO, "creating a new entry"); ++ update = 0; ++ ++ entry = mipv6_bce_alloc(type); ++ if (entry == NULL) { ++ DEBUG(DBG_INFO, "cache full, entry not added"); ++ goto err; ++ } ++ ++ create_tunnel = (type == HOME_REGISTRATION); ++ } ++ ++ if (create_tunnel) { ++ if (update) ++ mip6_fn.proxy_del(&entry->home_addr, entry); ++ if (mip6_fn.proxy_create(flags, ifindex, coa, our_addr, home_addr) < 0) { ++ goto err_proxy; ++ } ++ } ++ ++ ipv6_addr_copy(&(entry->our_addr), our_addr); ++ ipv6_addr_copy(&(entry->home_addr), home_addr); ++ ipv6_addr_copy(&(entry->coa), coa); ++ entry->ifindex = ifindex; ++ entry->seq = seq; ++ entry->type = type; ++ entry->flags = flags; ++ ++ entry->last_br = 0; ++ entry->destunr_count = 0; ++ entry->callback_time = now + lifetime * HZ; ++ if (entry->type & HOME_REGISTRATION) ++ entry->br_callback_time = 0; ++ else ++ entry->br_callback_time = now + ++ (lifetime - BCACHE_BR_SEND_LEAD) * HZ; ++ ++ if (update) { ++ DEBUG(DBG_INFO, "updating entry : %x", entry); ++ hashlist_reposition(bcache.entries, (void *)entry, ++ entry->callback_time); ++ } else { ++ DEBUG(DBG_INFO, "adding entry: %x", entry); ++ if ((hashlist_add(bcache.entries, ++ &hashkey, ++ entry->callback_time, entry)) < 0) { ++ ++ DEBUG(DBG_ERROR, "Hash add failed"); ++ goto err_hashlist; ++ } ++ } ++ ++ set_timer(); ++ ++out: ++ write_unlock(&bcache_lock); ++ return 0; ++ ++err_hashlist: ++ if (create_tunnel) { ++ mip6_fn.proxy_del(home_addr, entry); ++ } ++err_proxy: ++ if (update) { ++ hashlist_delete(bcache.entries, (void *)entry); ++ } ++ mipv6_bce_free(entry); ++err: ++ write_unlock(&bcache_lock); ++ return ret; ++} ++ ++int mipv6_bcache_icmp_err(struct in6_addr *home_addr, ++ struct in6_addr *our_addr, ++ int destunr_count) ++{ ++ struct mipv6_bce *entry; ++ struct in6_addr_pair hashkey; ++ ++ int ret = -ENOENT; ++ ++ DEBUG_FUNC(); ++ ++ hashkey.a1 = home_addr; ++ hashkey.a2 = our_addr; ++ ++ write_lock(&bcache_lock); ++ if (unlikely(bcache.entries == NULL)) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ ++ if ((entry = (struct mipv6_bce *) ++ hashlist_get(bcache.entries, &hashkey)) != NULL) { ++ entry->last_destunr = jiffies; ++ entry->destunr_count = destunr_count; ++ ret = 0; ++ } ++err: ++ write_unlock(&bcache_lock); ++ return ret; ++} ++ ++ ++/** ++ * mipv6_bcache_delete - delete Binding Cache entry ++ * @home_addr: MN's home address ++ * @our_addr: our address ++ * @type: type of entry ++ * ++ * Deletes an entry associated with @home_addr from Binding Cache. ++ * Valid values for @type are %CACHE_ENTRY, %HOME_REGISTRATION and ++ * %ANY_ENTRY. %ANY_ENTRY deletes any type of entry. ++ **/ ++int mipv6_bcache_delete(struct in6_addr *home_addr, ++ struct in6_addr *our_addr, __u8 type) ++{ ++ struct mipv6_bce *entry; ++ struct in6_addr_pair hashkey; ++ int err = 0; ++ ++ DEBUG_FUNC(); ++ ++ if (home_addr == NULL || our_addr == NULL) { ++ DEBUG(DBG_INFO, "error in arguments"); ++ return -EINVAL; ++ } ++ ++ hashkey.a1 = home_addr; ++ hashkey.a2 = our_addr; ++ ++ write_lock(&bcache_lock); ++ ++ if (unlikely(bcache.entries == NULL) || ++ (entry = (struct mipv6_bce *) ++ hashlist_get(bcache.entries, &hashkey)) == NULL || ++ !(entry->type & type)) { ++ DEBUG(DBG_INFO, "No matching entry found"); ++ err = -ENOENT; ++ goto out; ++ } ++ ++ hashlist_delete(bcache.entries, (void *) entry); ++ mipv6_bce_free(entry); ++ ++ set_timer(); ++out: ++ write_unlock(&bcache_lock); ++ return err; ++} ++ ++/** ++ * mipv6_bcache_exists - check if entry exists ++ * @home_addr: home address to check ++ * @our_addr: our address ++ * ++ * Determines if a binding exists for @home_addr. Returns type of the ++ * entry or negative if entry does not exist. ++ **/ ++int mipv6_bcache_exists(struct in6_addr *home_addr, ++ struct in6_addr *our_addr) ++{ ++ struct mipv6_bce *entry; ++ struct in6_addr_pair hashkey; ++ int type = -ENOENT; ++ ++ DEBUG_FUNC(); ++ ++ if (home_addr == NULL || our_addr == NULL) ++ return -EINVAL; ++ ++ hashkey.a1 = home_addr; ++ hashkey.a2 = our_addr; ++ ++ read_lock(&bcache_lock); ++ if (likely(bcache.entries != NULL) && ++ (entry = (struct mipv6_bce *) ++ hashlist_get(bcache.entries, &hashkey)) != NULL) { ++ type = entry->type; ++ } ++ read_unlock(&bcache_lock); ++ ++ return type; ++} ++ ++/** ++ * mipv6_bcache_get - get entry from Binding Cache ++ * @home_addr: home address to search ++ * @our_addr: our address ++ * @entry: pointer to buffer ++ * ++ * Gets a copy of Binding Cache entry for @home_addr. If entry ++ * exists entry is copied to @entry and zero is returned. ++ * Otherwise returns negative. ++ **/ ++int mipv6_bcache_get(struct in6_addr *home_addr, ++ struct in6_addr *our_addr, ++ struct mipv6_bce *entry) ++{ ++ struct mipv6_bce *entry2; ++ struct in6_addr_pair hashkey; ++ int ret = -ENOENT; ++ ++ DEBUG_FUNC(); ++ ++ if (home_addr == NULL || our_addr == NULL || entry == NULL) ++ return -EINVAL; ++ ++ hashkey.a1 = home_addr; ++ hashkey.a2 = our_addr; ++ ++ read_lock_bh(&bcache_lock); ++ ++ entry2 = (struct mipv6_bce *) ++ hashlist_get(bcache.entries, &hashkey); ++ if (entry2 != NULL) { ++ memcpy(entry, entry2, sizeof(struct mipv6_bce)); ++ ret = 0; ++ } ++ read_unlock_bh(&bcache_lock); ++ return ret; ++} ++ ++int mipv6_bcache_iterate(hashlist_iterator_t func, void *args) ++{ ++ int ret; ++ ++ read_lock_bh(&bcache_lock); ++ ret = hashlist_iterate(bcache.entries, args, func); ++ read_unlock_bh(&bcache_lock); ++ ++ return ret; ++} ++ ++/* ++ * Proc-filesystem functions ++ */ ++ ++#define BC_INFO_LEN 80 ++ ++struct procinfo_iterator_args { ++ char *buffer; ++ int offset; ++ int length; ++ int skip; ++ int len; ++}; ++ ++static int procinfo_iterator(void *data, void *args, unsigned long *pref) ++{ ++ struct procinfo_iterator_args *arg = ++ (struct procinfo_iterator_args *) args; ++ struct mipv6_bce *entry = ++ (struct mipv6_bce *) data; ++ ++ ASSERT(entry != NULL); ++ ++ if (arg->skip < arg->offset / BC_INFO_LEN) { ++ arg->skip++; ++ return ITERATOR_CONT; ++ } ++ ++ if (arg->len >= arg->length) ++ return ITERATOR_CONT; ++ ++ /* HoA CoA CallbackInSecs Type */ ++ arg->len += sprintf(arg->buffer + arg->len, ++ "%08x%08x%08x%08x %08x%08x%08x%08x %010lu %02d\n", ++ ntohl(entry->home_addr.s6_addr32[0]), ++ ntohl(entry->home_addr.s6_addr32[1]), ++ ntohl(entry->home_addr.s6_addr32[2]), ++ ntohl(entry->home_addr.s6_addr32[3]), ++ ntohl(entry->coa.s6_addr32[0]), ++ ntohl(entry->coa.s6_addr32[1]), ++ ntohl(entry->coa.s6_addr32[2]), ++ ntohl(entry->coa.s6_addr32[3]), ++ ((entry->callback_time) - jiffies) / HZ, ++ (int) entry->type); ++ ++ return ITERATOR_CONT; ++} ++ ++ /* ++ * Callback function for proc filesystem. ++ */ ++static int bcache_proc_info(char *buffer, char **start, off_t offset, ++ int length) ++{ ++ struct procinfo_iterator_args args; ++ ++ DEBUG_FUNC(); ++ ++ args.buffer = buffer; ++ args.offset = offset; ++ args.length = length; ++ args.skip = 0; ++ args.len = 0; ++ ++ read_lock_bh(&bcache_lock); ++ hashlist_iterate(bcache.entries, &args, procinfo_iterator); ++ read_unlock_bh(&bcache_lock); ++ ++ *start = buffer; ++ if (offset) ++ *start += offset % BC_INFO_LEN; ++ ++ args.len -= offset % BC_INFO_LEN; ++ ++ if (args.len > length) ++ args.len = length; ++ if (args.len < 0) ++ args.len = 0; ++ ++ return args.len; ++} ++ ++static int bcache_compare(void *data, void *hashkey) ++{ ++ struct in6_addr_pair *p = (struct in6_addr_pair *) hashkey; ++ struct mipv6_bce *e = (struct mipv6_bce *) data; ++ ++ if (ipv6_addr_cmp(&e->home_addr, p->a1) == 0 ++ && ipv6_addr_cmp(&e->our_addr, p->a2) == 0) ++ return 0; ++ else ++ return -1; ++} ++ ++static __u32 bcache_hash(void *hashkey) ++{ ++ struct in6_addr_pair *p = (struct in6_addr_pair *) hashkey; ++ ++ return p->a1->s6_addr32[0] ^ p->a1->s6_addr32[1] ^ ++ p->a2->s6_addr32[2] ^ p->a2->s6_addr32[3]; ++} ++ ++/* ++ * Initialization and shutdown functions ++ */ ++ ++int __init mipv6_bcache_init(__u32 size) ++{ ++ if (size < 1) { ++ DEBUG(DBG_ERROR, "Binding cache size must be at least 1"); ++ return -EINVAL; ++ } ++ bcache.entries = hashlist_create(MIPV6_BCACHE_HASHSIZE, size, ++ sizeof(struct mipv6_bce), ++ "mip6_bcache", NULL, NULL, ++ bcache_compare, bcache_hash); ++ ++ if (bcache.entries == NULL) { ++ DEBUG(DBG_ERROR, "Failed to initialize hashlist"); ++ return -ENOMEM; ++ } ++ ++ init_timer(&bcache.callback_timer); ++ bcache.callback_timer.data = 0; ++ bcache.callback_timer.function = timer_handler; ++ bcache.size = size; ++ ++ proc_net_create("mip6_bcache", 0, bcache_proc_info); ++ ++ DEBUG(DBG_INFO, "Binding cache initialized"); ++ return 0; ++} ++ ++static int ++bce_cleanup_iterator(void *rawentry, void *args, unsigned long *sortkey) ++{ ++ int type = (int) args; ++ struct mipv6_bce *entry = (struct mipv6_bce *) rawentry; ++ if (entry->type == type) { ++ if (entry->type & HOME_REGISTRATION) { ++ if (unlikely(mip6_fn.proxy_del == NULL)) ++ DEBUG(DBG_ERROR, "proxy_del unitialized"); ++ else ++ mip6_fn.proxy_del(&entry->home_addr, entry); ++ } ++ return ITERATOR_DELETE_ENTRY; ++ } ++ return ITERATOR_CONT; ++ ++} ++ ++void mipv6_bcache_cleanup(int type) ++{ ++ write_lock_bh(&bcache_lock); ++ hashlist_iterate(bcache.entries,(void *) type, bce_cleanup_iterator); ++ write_unlock_bh(&bcache_lock); ++} ++ ++int __exit mipv6_bcache_exit(void) ++{ ++ struct hashlist *entries; ++ ++ DEBUG_FUNC(); ++ ++ proc_net_remove("mip6_bcache"); ++ ++ write_lock_bh(&bcache_lock); ++ DEBUG(DBG_INFO, "Stopping the bcache timer"); ++ del_timer(&bcache.callback_timer); ++ hashlist_iterate(bcache.entries,(void *)CACHE_ENTRY, ++ bce_cleanup_iterator); ++ ++ entries = bcache.entries; ++ bcache.entries = NULL; ++ write_unlock_bh(&bcache_lock); ++ ++ hashlist_destroy(entries); ++ return 0; ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/bcache.h linux-2.4.25/net/ipv6/mobile_ip6/bcache.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/bcache.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/bcache.h 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,72 @@ ++/* ++ * MIPL Mobile IPv6 Binding Cache header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _BCACHE_H ++#define _BCACHE_H ++ ++#include ++#include ++#include "hashlist.h" ++ ++#define CACHE_ENTRY 1 /* this and HOME_REGISTRATION are the entry types */ ++#define HOME_REGISTRATION 2 ++#define ANY_ENTRY 3 ++ ++#define MIPV6_MAX_DESTUNREACH 5 /* Delete CN BCEs after 5 destination unreachables */ ++#define MIPV6_DEST_UNR_IVAL 10 /* What is the max interval of destination ++ unreacahable error messages for them to be persistent*/ ++ ++struct mipv6_bce { ++ struct hashlist_entry e; ++ int ifindex; /* Interface identifier */ ++ struct in6_addr our_addr; /* our address (as seen by the MN) */ ++ struct in6_addr home_addr; /* MN home address */ ++ struct in6_addr coa; /* MN care-of address */ ++ unsigned long callback_time; /* time of expiration (in jiffies) */ ++ unsigned long br_callback_time; /* time for sending a BR (in jiffies) */ ++ int (*callback_function)(struct mipv6_bce *entry); ++ __u8 type; /* home registration */ ++ __u8 router; /* mn is router */ ++ __u8 flags; /* flags received in BU */ ++ __u16 seq; /* sequence number */ ++ unsigned long last_br; /* time when last BR sent */ ++ unsigned long last_destunr; /* time when last ICMP destination unreachable received */ ++ int br_count; /* How many BRRs have sent */ ++ int destunr_count; /* Number of destination unreachables received */ ++}; ++ ++int mipv6_bcache_add(int ifindex, struct in6_addr *our_addr, ++ struct in6_addr *home_addr, struct in6_addr *coa, ++ __u32 lifetime, __u16 seq, __u8 flags, __u8 type); ++ ++int mipv6_bcache_icmp_err(struct in6_addr *home_addr, ++ struct in6_addr *our_addr, ++ int destunr_count); ++ ++int mipv6_bcache_delete(struct in6_addr *home_addr, struct in6_addr *our_addr, ++ __u8 type); ++ ++int mipv6_bcache_exists(struct in6_addr *home_addr, ++ struct in6_addr *our_addr); ++ ++int mipv6_bcache_get(struct in6_addr *home_addr, ++ struct in6_addr *our_addr, ++ struct mipv6_bce *entry); ++ ++int mipv6_bcache_iterate(int (*func)(void *, void *, unsigned long *), void *args); ++ ++void mipv6_bcache_cleanup(int type); ++ ++int mipv6_bcache_init(__u32 size); ++ ++int mipv6_bcache_exit(void); ++ ++#endif /* _BCACHE_H */ +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/bul.c linux-2.4.25/net/ipv6/mobile_ip6/bul.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/bul.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/bul.c 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,634 @@ ++/* ++ * Binding update list ++ * ++ * Authors: ++ * Juha Mynttinen ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++/* ++ * Changes: ++ * ++ * Nanno Langstraat : Timer code cleaned up ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "bul.h" ++#include "debug.h" ++#include "hashlist.h" ++#include "tunnel_mn.h" ++#include "mobhdr.h" ++ ++#define MIPV6_BUL_HASHSIZE 32 ++ ++rwlock_t bul_lock = RW_LOCK_UNLOCKED; ++ ++struct mipv6_bul { ++ struct hashlist *entries; ++ struct timer_list callback_timer; ++}; ++ ++static struct mipv6_bul bul; ++ ++struct in6_addr_pair { ++ struct in6_addr *a1; ++ struct in6_addr *a2; ++}; ++ ++/********************************************************************** ++ * ++ * Private functions ++ * ++ **********************************************************************/ ++ ++static int bul_compare(void *data, void *hashkey) ++{ ++ struct in6_addr_pair *p = (struct in6_addr_pair *)hashkey; ++ struct mipv6_bul_entry *e = (struct mipv6_bul_entry *)data; ++ ++ if (ipv6_addr_cmp(&e->cn_addr, p->a1) == 0 ++ && ipv6_addr_cmp(&e->home_addr, p->a2) == 0) ++ return 0; ++ else ++ return -1; ++} ++ ++struct test_keys { ++ struct in6_addr *addr; ++ u8 *cookie; ++}; ++ ++static int bul_compare_cookie(void *data, void *keys) ++{ ++ struct test_keys *p = (struct test_keys *)keys; ++ struct mipv6_bul_entry *e = (struct mipv6_bul_entry *)data; ++ ++ if (ipv6_addr_cmp(&e->cn_addr, p->addr) == 0 && e->rr ++ && memcmp(&e->rr->cot_cookie, p->cookie, 8) == 0) ++ return 0; ++ else ++ return -1; ++} ++ ++static u32 bul_hash(void *hashkey) ++{ ++ struct in6_addr_pair *p = (struct in6_addr_pair *)hashkey; ++ ++ return p->a1->s6_addr32[0] ^ ++ p->a1->s6_addr32[1] ^ ++ p->a1->s6_addr32[2] ^ ++ p->a1->s6_addr32[3]; ++} ++ ++static int bul_proc_info(char *buffer, char **start, off_t offset, ++ int length); ++ ++static struct mipv6_bul_entry *mipv6_bul_get_entry(void) ++{ ++ DEBUG_FUNC(); ++ return ((struct mipv6_bul_entry *) ++ hashlist_alloc(bul.entries, SLAB_ATOMIC)); ++} ++ ++static void mipv6_bul_entry_free(struct mipv6_bul_entry *entry) ++{ ++ DEBUG_FUNC(); ++ ++ if (entry->rr) { ++ if (entry->rr->kbu) ++ kfree(entry->rr->kbu); ++ kfree(entry->rr); ++ } ++ if (entry->ops) ++ kfree(entry->ops); ++ hashlist_free(bul.entries, (void *)entry); ++} ++ ++static __inline__ int del_bul_entry_tnl(struct mipv6_bul_entry *entry) ++{ ++ if (entry->flags & MIPV6_BU_F_HOME) { ++ return mipv6_mv_tnl_to_ha(&entry->cn_addr, ++ &entry->coa, ++ &entry->home_addr); ++ } ++ return 0; ++} ++ ++static void timer_update(void) ++{ ++ struct mipv6_bul_entry *entry; ++ ++ DEBUG_FUNC(); ++ ++ entry = hashlist_get_first(bul.entries); ++ ++ while (entry && time_after_eq(jiffies, entry->callback_time)) { ++ if (time_after_eq(jiffies, entry->expire) || ++ entry->callback(entry) != 0) { ++ /* ++ * Either the entry has expired, or the callback ++ * indicated that it should be deleted. ++ */ ++ hashlist_delete(bul.entries, (void *)entry); ++ ++ del_bul_entry_tnl(entry); ++ mipv6_bul_entry_free(entry); ++ DEBUG(DBG_INFO, "Entry deleted (was expired) from " ++ "binding update list"); ++ } else { ++ /* move entry to its right place in the hashlist */ ++ DEBUG(DBG_INFO, "Rescheduling"); ++ hashlist_reposition(bul.entries, (void *)entry, ++ entry->callback_time); ++ } ++ entry = (struct mipv6_bul_entry *) ++ hashlist_get_first(bul.entries); ++ } ++ ++ if (entry == NULL) { ++ DEBUG(DBG_INFO, "bul empty, not setting a new timer"); ++ del_timer(&bul.callback_timer); ++ } else { ++ mod_timer(&bul.callback_timer, entry->callback_time); ++ } ++} ++ ++static void timer_handler(unsigned long dummy) ++{ ++ DEBUG_FUNC(); ++ ++ write_lock(&bul_lock); ++ timer_update(); ++ write_unlock(&bul_lock); ++} ++ ++/********************************************************************** ++ * ++ * Public interface functions ++ * ++ **********************************************************************/ ++ ++/** ++ * mipv6_bul_iterate - apply interator function to all entries ++ * @func: function to apply ++ * @args: extra arguments for iterator ++ * ++ * Applies @func for each entry in Binding Update List. Extra ++ * arguments given in @args are also passed to the iterator function. ++ * Caller must hold @bul_lock. ++ **/ ++int mipv6_bul_iterate(hashlist_iterator_t func, void *args) ++{ ++ DEBUG_FUNC(); ++ ++ return hashlist_iterate(bul.entries, args, func); ++} ++ ++/** ++ * mipv6_bul_exists - check if Binding Update List entry exists ++ * @cn: address to check ++ * ++ * Checks if Binding Update List has an entry for @cn. Returns true ++ * if entry exists, false otherwise. Caller may not hold @bul_lock. ++ **/ ++int mipv6_bul_exists(struct in6_addr *cn, struct in6_addr *haddr) ++{ ++ int exists; ++ struct in6_addr_pair hashkey; ++ ++ DEBUG_FUNC(); ++ ++ hashkey.a1 = cn; ++ hashkey.a2 = haddr; ++ ++ read_lock_bh(&bul_lock); ++ ++ if (unlikely(bul.entries == NULL)) ++ exists = 0; ++ else ++ exists = (hashlist_get(bul.entries, &hashkey) != NULL); ++ ++ read_unlock_bh(&bul_lock); ++ return exists; ++} ++ ++/** ++ * mipv6_bul_get - get Binding Update List entry ++ * @cn_addr: CN address to search ++ * @home_addr: home address to search ++ * ++ * Returns Binding Update List entry for @cn_addr if it exists. ++ * Otherwise returns %NULL. Caller must hold @bul_lock. ++ **/ ++struct mipv6_bul_entry *mipv6_bul_get(struct in6_addr *cn_addr, ++ struct in6_addr *home_addr) ++{ ++ struct mipv6_bul_entry *entry; ++ struct in6_addr_pair hashkey; ++ ++ DEBUG_FUNC(); ++ ++ if (unlikely(bul.entries == NULL)) { ++ return NULL; ++ } ++ hashkey.a1 = cn_addr; ++ hashkey.a2 = home_addr; ++ ++ entry = (struct mipv6_bul_entry *) ++ hashlist_get(bul.entries, &hashkey); ++ ++ return entry; ++} ++ ++struct mipv6_bul_entry *mipv6_bul_get_by_ccookie( ++ struct in6_addr *cn_addr, u8 *cookie) ++{ ++ struct test_keys key; ++ ++ DEBUG_FUNC(); ++ ++ if (unlikely(bul.entries == NULL)) ++ return NULL; ++ key.addr = cn_addr; ++ key.cookie = cookie; ++ ++ return (struct mipv6_bul_entry *) ++ hashlist_get_ex(bul.entries, &key, ++ bul_compare_cookie); ++} ++ ++/** ++ * mipv6_bul_reschedule - reschedule Binding Update List entry ++ * @entry: entry to reschedule ++ * ++ * Reschedules a Binding Update List entry. Must be called after ++ * modifying entry lifetime. Caller must hold @bul_lock (write). ++ **/ ++void mipv6_bul_reschedule(struct mipv6_bul_entry *entry) ++{ ++ DEBUG_FUNC(); ++ ++ hashlist_reposition(bul.entries, ++ (void *)entry, ++ entry->callback_time); ++ timer_update(); ++} ++ ++/** ++ * mipv6_bul_add - add binding update to Binding Update List ++ * @cn_addr: IPv6 address where BU was sent ++ * @home_addr: Home address for this binding ++ * @coa: Care-of address for this binding ++ * @lifetime: expiration time of the binding in seconds ++ * @seq: sequence number of the BU ++ * @flags: %MIPV6_BU_F_* flags ++ * @callback: callback function called on expiration ++ * @callback_time: expiration time for callback ++ * @state: binding send state ++ * @delay: retransmission delay ++ * @maxdelay: retransmission maximum delay ++ * @ops: Mobility header options for BU ++ * @rr: Return routability information ++ * ++ * Adds a binding update sent to @cn_addr for @home_addr to the ++ * Binding Update List. If entry already exists, it is updated. ++ * Entry is set to expire in @lifetime seconds. Entry has a callback ++ * function @callback that is called at @callback_time. Entry @state ++ * controls resending of this binding update and it can be set to ++ * %ACK_OK, %RESEND_EXP or %ACK_ERROR. Returns a pointer to the newly ++ * created or updated entry. Caller must hold @bul_lock (write). ++ **/ ++struct mipv6_bul_entry *mipv6_bul_add( ++ struct in6_addr *cn_addr, struct in6_addr *home_addr, ++ struct in6_addr *coa, ++ __u32 lifetime, __u16 seq, __u8 flags, ++ int (*callback)(struct mipv6_bul_entry *entry), ++ __u32 callback_time, ++ __u8 state, __u32 delay, __u32 maxdelay, ++ struct mipv6_mh_opt *ops, ++ struct mipv6_rr_info *rr) ++{ ++ struct mipv6_bul_entry *entry; ++ int update = 0; ++ struct in6_addr_pair hashkey; ++ ++ DEBUG_FUNC(); ++ ++ if (unlikely(bul.entries == NULL)) ++ return NULL; ++ ++ if (cn_addr == NULL || home_addr == NULL || coa == NULL || ++ lifetime < 0 || callback == NULL || callback_time < 0 || ++ (state != ACK_OK && state != RESEND_EXP && state != ACK_ERROR) || ++ delay < 0 || maxdelay < 0) { ++ DEBUG(DBG_ERROR, "invalid arguments"); ++ return NULL; ++ } ++ DEBUG(DBG_INFO, "cn_addr: %x:%x:%x:%x:%x:%x:%x:%x, " ++ "home_addr: %x:%x:%x:%x:%x:%x:%x:%x" ++ "coaddr: %x:%x:%x:%x:%x:%x:%x:%x", NIPV6ADDR(cn_addr), ++ NIPV6ADDR(home_addr), NIPV6ADDR(coa)); ++ hashkey.a1 = cn_addr; ++ hashkey.a2 = home_addr; ++ ++ /* ++ * decide whether to add a new entry or update existing, also ++ * check if there's room for a new entry when adding a new ++ * entry (latter is handled by mipv6_bul_get_entry() ++ */ ++ if ((entry = (struct mipv6_bul_entry *) ++ hashlist_get(bul.entries, &hashkey)) != NULL) { ++ /* if an entry for this cn_addr exists (with smaller ++ * seq than the new entry's seq), update it */ ++ ++ if (MIPV6_SEQ_GT(seq, entry->seq)) { ++ DEBUG(DBG_INFO, "updating an existing entry"); ++ update = 1; ++ } else { ++ DEBUG(DBG_INFO, "smaller seq than existing, not updating"); ++ return NULL; ++ } ++ } else { ++ entry = mipv6_bul_get_entry(); ++ if (entry == NULL) { ++ DEBUG(DBG_WARNING, "binding update list full, can't add!!!"); ++ return NULL; ++ } ++ memset(entry, 0, sizeof(*entry)); ++ /* First BU send happens here, save count in the entry */ ++ entry->consecutive_sends = 1; ++ } ++ ++ if (!update) { ++ ipv6_addr_copy(&(entry->cn_addr), cn_addr); ++ ipv6_addr_copy(&(entry->home_addr), home_addr); ++ entry->ops = ops; ++ } ++ /* Add Return Routability info to bul entry */ ++ if (rr) { ++ if(entry->rr) ++ kfree(entry->rr); ++ entry->rr = rr; ++ } ++ ++ ipv6_addr_copy(&(entry->coa), coa); ++ entry->lifetime = lifetime; ++ if (lifetime) ++ entry->expire = jiffies + lifetime * HZ; ++ else if (flags & MIPV6_BU_F_ACK) ++ entry->expire = jiffies + HOME_RESEND_EXPIRE * HZ; ++ entry->seq = seq; ++ entry->flags = flags; ++ entry->lastsend = jiffies; /* current time = last use of the entry */ ++ entry->state = state; ++ entry->delay = delay; ++ entry->maxdelay = maxdelay; ++ entry->callback_time = jiffies + callback_time * HZ; ++ entry->callback = callback; ++ ++ if (flags & MIPV6_BU_F_HOME && ++ mipv6_mv_tnl_to_ha(cn_addr, coa, home_addr)) { ++ DEBUG(DBG_ERROR, "reconfiguration of the tunnel failed"); ++ } ++ if (update) { ++ DEBUG(DBG_INFO, "updating entry: %x", entry); ++ hashlist_reposition(bul.entries, (void *)entry, ++ entry->callback_time); ++ } else { ++ DEBUG(DBG_INFO, "adding entry: %x", entry); ++ ++ hashkey.a1 = &entry->cn_addr; ++ hashkey.a2 = &entry->home_addr; ++ ++ if ((hashlist_add(bul.entries, &hashkey, ++ entry->callback_time, ++ entry)) < 0) { ++ DEBUG(DBG_ERROR, "Hash add failed"); ++ mipv6_bul_entry_free(entry); ++ return NULL; ++ } ++ } ++ timer_update(); ++ ++ return entry; ++} ++ ++/** ++ * mipv6_bul_delete - delete Binding Update List entry ++ * @cn_addr: address for entry to delete ++ * ++ * Deletes the entry for @cn_addr from the Binding Update List. ++ * Returns zero if entry was deleted succesfully, otherwise returns ++ * negative. Caller may not hold @bul_lock. ++ **/ ++int mipv6_bul_delete(struct in6_addr *cn_addr, struct in6_addr *home_addr) ++{ ++ struct mipv6_bul_entry *entry; ++ struct in6_addr_pair hashkey; ++ ++ DEBUG_FUNC(); ++ ++ hashkey.a1 = cn_addr; ++ hashkey.a2 = home_addr; ++ ++ write_lock(&bul_lock); ++ ++ if (unlikely(bul.entries == NULL) || ++ (entry = (struct mipv6_bul_entry *) ++ hashlist_get(bul.entries, &hashkey)) == NULL) { ++ write_unlock(&bul_lock); ++ DEBUG(DBG_INFO, "No such entry"); ++ return -ENOENT; ++ } ++ ++ hashlist_delete(bul.entries, (void *)entry); ++ ++ del_bul_entry_tnl(entry); ++ ++ mipv6_bul_entry_free(entry); ++ timer_update(); ++ write_unlock(&bul_lock); ++ ++ DEBUG(DBG_INFO, "Binding update list entry deleted"); ++ ++ return 0; ++} ++ ++/********************************************************************** ++ * ++ * Proc interface functions ++ * ++ **********************************************************************/ ++ ++#define BUL_INFO_LEN 152 ++ ++struct procinfo_iterator_args { ++ char *buffer; ++ int offset; ++ int length; ++ int skip; ++ int len; ++}; ++ ++static int procinfo_iterator(void *data, void *args, ++ unsigned long *sortkey) ++{ ++ struct procinfo_iterator_args *arg = ++ (struct procinfo_iterator_args *)args; ++ struct mipv6_bul_entry *entry = ++ (struct mipv6_bul_entry *)data; ++ unsigned long callback_seconds; ++ ++ DEBUG_FUNC(); ++ ++ if (entry == NULL) return ITERATOR_ERR; ++ ++ if (time_after(jiffies, entry->callback_time)) ++ callback_seconds = 0; ++ else ++ callback_seconds = (entry->callback_time - jiffies) / HZ; ++ ++ if (arg->skip < arg->offset / BUL_INFO_LEN) { ++ arg->skip++; ++ return ITERATOR_CONT; ++ } ++ ++ if (arg->len >= arg->length) ++ return ITERATOR_CONT; ++ ++ /* CN HoA CoA ExpInSecs SeqNum State Delay MaxDelay CallbackInSecs */ ++ arg->len += sprintf(arg->buffer + arg->len, ++ "%08x%08x%08x%08x %08x%08x%08x%08x %08x%08x%08x%08x\n" ++ "%010lu %05d %02d %010d %010d %010lu\n", ++ ntohl(entry->cn_addr.s6_addr32[0]), ++ ntohl(entry->cn_addr.s6_addr32[1]), ++ ntohl(entry->cn_addr.s6_addr32[2]), ++ ntohl(entry->cn_addr.s6_addr32[3]), ++ ntohl(entry->home_addr.s6_addr32[0]), ++ ntohl(entry->home_addr.s6_addr32[1]), ++ ntohl(entry->home_addr.s6_addr32[2]), ++ ntohl(entry->home_addr.s6_addr32[3]), ++ ntohl(entry->coa.s6_addr32[0]), ++ ntohl(entry->coa.s6_addr32[1]), ++ ntohl(entry->coa.s6_addr32[2]), ++ ntohl(entry->coa.s6_addr32[3]), ++ (entry->expire - jiffies) / HZ, ++ entry->seq, entry->state, entry->delay, ++ entry->maxdelay, callback_seconds); ++ ++ return ITERATOR_CONT; ++} ++ ++ ++/* ++ * Callback function for proc filesystem. ++ */ ++static int bul_proc_info(char *buffer, char **start, off_t offset, ++ int length) ++{ ++ struct procinfo_iterator_args args; ++ ++ DEBUG_FUNC(); ++ ++ args.buffer = buffer; ++ args.offset = offset; ++ args.length = length; ++ args.skip = 0; ++ args.len = 0; ++ ++ read_lock_bh(&bul_lock); ++ hashlist_iterate(bul.entries, &args, procinfo_iterator); ++ read_unlock_bh(&bul_lock); ++ ++ *start = buffer; ++ if (offset) ++ *start += offset % BUL_INFO_LEN; ++ ++ args.len -= offset % BUL_INFO_LEN; ++ ++ if (args.len > length) ++ args.len = length; ++ if (args.len < 0) ++ args.len = 0; ++ ++ return args.len; ++} ++ ++/********************************************************************** ++ * ++ * Code module init/fini functions ++ * ++ **********************************************************************/ ++ ++int __init mipv6_bul_init(__u32 size) ++{ ++ DEBUG_FUNC(); ++ ++ if (size < 1) { ++ DEBUG(DBG_CRITICAL, ++ "Binding update list size must be at least 1"); ++ return -EINVAL; ++ } ++ bul.entries = hashlist_create(MIPV6_BUL_HASHSIZE, size, ++ sizeof(struct mipv6_bul_entry), ++ "mip6_bul", NULL, NULL, ++ bul_compare, bul_hash); ++ ++ if (bul.entries == NULL) { ++ DEBUG(DBG_CRITICAL, "Couldn't allocate memory for " ++ "hashlist when creating a binding update list"); ++ return -ENOMEM; ++ } ++ init_timer(&bul.callback_timer); ++ bul.callback_timer.data = 0; ++ bul.callback_timer.function = timer_handler; ++ proc_net_create("mip6_bul", 0, bul_proc_info); ++ DEBUG(DBG_INFO, "Binding update list initialized"); ++ return 0; ++} ++ ++void __exit mipv6_bul_exit() ++{ ++ struct mipv6_bul_entry *entry; ++ struct hashlist *entries; ++ ++ DEBUG_FUNC(); ++ ++ proc_net_remove("mip6_bul"); ++ ++ write_lock_bh(&bul_lock); ++ ++ DEBUG(DBG_INFO, "Stopping the bul timer"); ++ del_timer(&bul.callback_timer); ++ ++ while ((entry = (struct mipv6_bul_entry *) ++ hashlist_get_first(bul.entries)) != NULL) { ++ hashlist_delete(bul.entries, (void *)entry); ++ ++ del_bul_entry_tnl(entry); ++ ++ mipv6_bul_entry_free(entry); ++ } ++ entries = bul.entries; ++ bul.entries = NULL; ++ write_unlock_bh(&bul_lock); ++ ++ hashlist_destroy(entries); ++ ++ DEBUG(DBG_INFO, "binding update list destroyed"); ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/bul.h linux-2.4.25/net/ipv6/mobile_ip6/bul.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/bul.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/bul.h 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,91 @@ ++/* ++ * MIPL Mobile IPv6 Binding Update List header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _BUL_H ++#define _BUL_H ++ ++#include "hashlist.h" ++ ++#define ACK_OK 0x01 ++#define RESEND_EXP 0x02 ++#define ACK_ERROR 0x04 ++ ++#define HOME_RESEND_EXPIRE 3600 ++#define MIPV6_COOKIE_LEN 8 ++struct mipv6_rr_info { ++ /* RR information */ ++ u16 rr_state; /* State of the RR */ ++ u16 rr_flags; /* Flags for the RR */ ++ u8 hot_cookie[MIPV6_COOKIE_LEN]; /* HoT Cookie */ ++ u8 cot_cookie[MIPV6_COOKIE_LEN]; /* CoT Cookie */ ++ u8 home_cookie[MIPV6_COOKIE_LEN]; /* Home Cookie */ ++ u8 careof_cookie[MIPV6_COOKIE_LEN]; /* Careof Cookie */ ++ u32 lastsend_hoti; /* When HoTI was last sent (jiffies) */ ++ u32 lastsend_coti; /* When CoTI was last sent (jiffies) */ ++ u32 home_time; /* when Care-of cookie was received */ ++ u32 careof_time; /* when Home cookie was received */ ++ int home_nonce_index; /* Home cookie nonce index */ ++ int careof_nonce_index; /* Care-of cookie nonce index */ ++ u8 *kbu; /* Binding authentication key */ ++}; ++struct mipv6_bul_entry { ++ struct hashlist_entry e; ++ struct in6_addr cn_addr; /* CN to which BU was sent */ ++ struct in6_addr home_addr; /* home address of this binding */ ++ struct in6_addr coa; /* care-of address of the sent BU */ ++ ++ unsigned long expire; /* entry's expiration time (jiffies) */ ++ __u32 lifetime; /* lifetime sent in this BU */ ++ __u32 lastsend; /* last time when BU sent (jiffies) */ ++ __u32 consecutive_sends; /* Number of consecutive BU's sent */ ++ __u16 seq; /* sequence number of the latest BU */ ++ __u8 flags; /* BU send flags */ ++ __u8 state; /* resend state */ ++ __u32 initdelay; /* initial ack wait */ ++ __u32 delay; /* current ack wait */ ++ __u32 maxdelay; /* maximum ack wait */ ++ ++ struct mipv6_rr_info *rr; ++ struct mipv6_mh_opt *ops; /* saved option values */ ++ ++ unsigned long callback_time; ++ int (*callback)(struct mipv6_bul_entry *entry); ++}; ++ ++extern rwlock_t bul_lock; ++ ++int mipv6_bul_init(__u32 size); ++ ++void mipv6_bul_exit(void); ++ ++struct mipv6_bul_entry *mipv6_bul_add( ++ struct in6_addr *cn_addr, struct in6_addr *home_addr, ++ struct in6_addr *coa, __u32 lifetime, __u16 seq, __u8 flags, ++ int (*callback)(struct mipv6_bul_entry *entry), __u32 callback_time, ++ __u8 state, __u32 delay, __u32 maxdelay, struct mipv6_mh_opt *ops, ++ struct mipv6_rr_info *rr); ++ ++int mipv6_bul_delete(struct in6_addr *cn_addr, struct in6_addr *home_addr); ++ ++int mipv6_bul_exists(struct in6_addr *cnaddr, struct in6_addr *home_addr); ++ ++struct mipv6_bul_entry *mipv6_bul_get(struct in6_addr *cnaddr, ++ struct in6_addr *home_addr); ++struct mipv6_bul_entry *mipv6_bul_get_by_ccookie(struct in6_addr *cn_addr, ++ u8 *cookie); ++ ++int bul_entry_expired(struct mipv6_bul_entry *bulentry); ++ ++void mipv6_bul_reschedule(struct mipv6_bul_entry *entry); ++ ++int mipv6_bul_iterate(int (*func)(void *, void *, unsigned long *), void *args); ++ ++#endif /* BUL_H */ +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/config.h linux-2.4.25/net/ipv6/mobile_ip6/config.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/config.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/config.h 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,72 @@ ++/* ++ * Configuration parameters ++ * ++ * $Id$ ++ */ ++ ++#define MIPV6VERSION "D24" ++#define MIPLVERSION "v1.0" ++ ++#define CAP_CN 0x01 ++#define CAP_HA 0x02 ++#define CAP_MN 0x04 ++ ++struct mip6_conf { ++ int capabilities; ++ int debug_level; ++ int accept_ret_rout; ++ int max_rtr_reachable_time; ++ int eager_cell_switching; ++ int max_num_tunnels; ++ int min_num_tunnels; ++ int binding_refresh_advice; ++ int bu_lladdr; ++ int bu_keymgm; ++ int bu_cn_ack; ++}; ++ ++extern struct mip6_conf mip6node_cnf; ++ ++struct mipv6_bce; ++ ++struct mip6_func { ++ void (*bce_home_add) (int ifindex, struct in6_addr *daddr, ++ struct in6_addr *haddr, struct in6_addr *coa, ++ struct in6_addr *rep_coa, __u32 lifetime, ++ __u16 sequence, __u8 flags, __u8 *k_bu); ++ void (*bce_cache_add) (int ifindex, struct in6_addr *daddr, ++ struct in6_addr *haddr, struct in6_addr *coa, ++ struct in6_addr *rep_coa, __u32 lifetime, ++ __u16 sequence, __u8 flags, __u8 *k_bu); ++ void (*bce_home_del) (struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u16 sequence, __u8 flags, ++ __u8 *k_bu); ++ void (*bce_cache_del) (struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u16 sequence, __u8 flags, ++ __u8 *k_bu); ++ ++ int (*bce_tnl_rt_add) (struct in6_addr *coa, ++ struct in6_addr *ha_addr, ++ struct in6_addr *home_addr); ++ ++ void (*bce_tnl_rt_del) (struct in6_addr *coa, ++ struct in6_addr *ha_addr, ++ struct in6_addr *home_addr); ++ ++ void (*proxy_del) (struct in6_addr *home_addr, struct mipv6_bce *entry); ++ int (*proxy_create) (int flags, int ifindex, struct in6_addr *coa, ++ struct in6_addr *our_addr, struct in6_addr *home_addr); ++ ++ int (*icmpv6_dhaad_rep_rcv) (struct sk_buff *skb); ++ int (*icmpv6_dhaad_req_rcv) (struct sk_buff *skb); ++ int (*icmpv6_pfxadv_rcv) (struct sk_buff *skb); ++ int (*icmpv6_pfxsol_rcv) (struct sk_buff *skb); ++ int (*icmpv6_paramprob_rcv) (struct sk_buff *skb); ++ ++ int (*mn_use_hao) (struct in6_addr *daddr, struct in6_addr *saddr); ++ void (*mn_check_tunneled_packet) (struct sk_buff *skb); ++}; ++ ++extern struct mip6_func mip6_fn; +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/debug.h linux-2.4.25/net/ipv6/mobile_ip6/debug.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/debug.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/debug.h 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,112 @@ ++/* ++ * MIPL Mobile IPv6 Debugging macros and functions ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _DEBUG_H ++#define _DEBUG_H ++ ++#include ++ ++/* priorities for different debug conditions */ ++ ++#define DBG_CRITICAL 0 /* unrecoverable error */ ++#define DBG_ERROR 1 /* error (recoverable) */ ++#define DBG_WARNING 2 /* unusual situation but not a real error */ ++#define DBG_INFO 3 /* generally useful information */ ++#define DBG_EXTRA 4 /* extra information */ ++#define DBG_FUNC_ENTRY 6 /* use to indicate function entry and exit */ ++#define DBG_DATADUMP 7 /* packet dumps, etc. lots of flood */ ++ ++/** ++ * NIPV6ADDR - macro for IPv6 addresses ++ * @addr: Network byte order IPv6 address ++ * ++ * Macro for printing IPv6 addresses. Used in conjunction with ++ * printk() or derivatives (such as DEBUG macro). ++ **/ ++#define NIPV6ADDR(addr) \ ++ ntohs(((u16 *)addr)[0]), \ ++ ntohs(((u16 *)addr)[1]), \ ++ ntohs(((u16 *)addr)[2]), \ ++ ntohs(((u16 *)addr)[3]), \ ++ ntohs(((u16 *)addr)[4]), \ ++ ntohs(((u16 *)addr)[5]), \ ++ ntohs(((u16 *)addr)[6]), \ ++ ntohs(((u16 *)addr)[7]) ++ ++#ifdef CONFIG_IPV6_MOBILITY_DEBUG ++extern int mipv6_debug; ++ ++/** ++ * debug_print - print debug message ++ * @debug_level: message priority ++ * @fname: calling function's name ++ * @fmt: printf-style formatting string ++ * ++ * Prints a debug message to system log if @debug_level is less or ++ * equal to @mipv6_debug. Should always be called using DEBUG() ++ * macro, not directly. ++ **/ ++static void debug_print(int debug_level, const char *fname, const char* fmt, ...) ++{ ++ char s[1024]; ++ va_list args; ++ ++ if (mipv6_debug < debug_level) ++ return; ++ ++ va_start(args, fmt); ++ vsprintf(s, fmt, args); ++ printk("mip6[%s]: %s\n", fname, s); ++ va_end(args); ++} ++ ++/** ++ * debug_print_buffer - print arbitrary buffer to system log ++ * @debug_level: message priority ++ * @data: pointer to buffer ++ * @len: number of bytes to print ++ * ++ * Prints @len bytes from buffer @data to system log. @debug_level ++ * tells on which debug level message gets printed. For ++ * debug_print_buffer() priority %DBG_DATADUMP should be used. ++ **/ ++#define debug_print_buffer(debug_level,data,len) { \ ++ if (mipv6_debug >= debug_level) { \ ++ int i; \ ++ for (i=0; i ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include "debug.h" ++#include "stats.h" ++#include "mobhdr.h" ++#include "bcache.h" ++#include "config.h" ++ ++/** ++ * mipv6_append_home_addr - Add Home Address Option ++ * @opt: buffer for Home Address Option ++ * @offset: offset from beginning of @opt ++ * @addr: address for HAO ++ * ++ * Adds a Home Address Option to a packet. Option is stored in ++ * @offset from beginning of @opt. The option is created but the ++ * original source address in IPv6 header is left intact. The source ++ * address will be changed from home address to CoA after the checksum ++ * has been calculated in getfrag. Padding is done automatically, and ++ * @opt must have allocated space for both actual option and pad. ++ * Returns offset from @opt to end of options. ++ **/ ++int mipv6_append_home_addr(__u8 *opt, int offset, struct in6_addr *addr) ++{ ++ int pad; ++ struct mipv6_dstopt_homeaddr *ho; ++ ++ DEBUG(DBG_DATADUMP, "HAO: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(addr)); ++ ++ pad = (6 - offset) & 7; ++ mipv6_add_pad(opt + offset, pad); ++ ++ ho = (struct mipv6_dstopt_homeaddr *)(opt + offset + pad); ++ ho->type = MIPV6_TLV_HOMEADDR; ++ ho->length = sizeof(*ho) - 2; ++ ipv6_addr_copy(&ho->addr, addr); ++ ++ return offset + pad + sizeof(*ho); ++} ++static inline int check_hao_validity(struct mipv6_dstopt_homeaddr *haopt, ++ u8 *dst1, ++ struct in6_addr *saddr, ++ struct in6_addr *daddr) ++{ ++ int addr_type = ipv6_addr_type(&haopt->addr); ++ struct mipv6_bce bc_entry; ++ ++ if (addr_type & IPV6_ADDR_LINKLOCAL || ++ !(addr_type & IPV6_ADDR_UNICAST)) { ++ DEBUG(DBG_INFO, "HAO with link local or non-unicast HoA, " ++ "not sending BE to " ++ "home address " ++ "%x:%x:%x:%x:%x:%x:%x:%x ", ++ "care-of address %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&haopt->addr), ++ NIPV6ADDR(saddr)); ++ return -EINVAL; ++ } else if (dst1[0] != IPPROTO_MOBILITY && ++ (mipv6_bcache_get(&haopt->addr, ++ daddr, &bc_entry) != 0 || ++ ipv6_addr_cmp(saddr, &bc_entry.coa))) { ++ DEBUG(DBG_INFO, "HAO without binding or incorrect CoA, " ++ "sending BE code 1: " ++ "home address %x:%x:%x:%x:%x:%x:%x:%x", ++ "to care-of address %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&haopt->addr), ++ NIPV6ADDR(saddr)); ++ return -ENOENT; ++ } ++ return 0; ++} ++/** ++ * mipv6_handle_homeaddr - Home Address Destination Option handler ++ * @skb: packet buffer ++ * @optoff: offset to where option begins ++ * ++ * Handles Home Address Option in IPv6 Destination Option header. ++ * Packet and offset to option are passed. If HAO is used without ++ * binding, sends a Binding Error code 1. When sending BE, notify bit ++ * is cleared to prevent IPv6 error handling from sending ICMP ++ * Parameter Problem. Returns 1 on success, otherwise zero. ++ **/ ++int mipv6_handle_homeaddr(struct sk_buff *skb, int optoff) ++{ ++ struct in6_addr *saddr = &skb->nh.ipv6h->saddr; ++ struct in6_addr coaddr; ++ struct inet6_skb_parm *opt = (struct inet6_skb_parm *) skb->cb; ++ struct mipv6_dstopt_homeaddr *haopt = ++ (struct mipv6_dstopt_homeaddr *) &skb->nh.raw[optoff]; ++ u8 *dst1; ++ int err; ++ ++ DEBUG_FUNC(); ++ ++ if (haopt->length != sizeof(*haopt) - 2) { ++ DEBUG(DBG_WARNING, "HAO has invalid length"); ++ MIPV6_INC_STATS(n_ha_drop.invalid); ++ return 0; ++ } ++ dst1 = (u8 *)skb->h.raw; ++ err = check_hao_validity(haopt, dst1, saddr, &skb->nh.ipv6h->daddr); ++ ++ if (err) { ++ haopt->type &= ~(0x80); /* clear notify bit */ ++ if (err == -ENOENT) ++ mipv6_send_be(&skb->nh.ipv6h->daddr, saddr, ++ &haopt->addr, MIPV6_BE_HAO_WO_BINDING); ++ MIPV6_INC_STATS(n_ha_drop.misc); ++ return 0; ++ } ++ ipv6_addr_copy(&coaddr, saddr); ++ ipv6_addr_copy(saddr, &haopt->addr); ++ ipv6_addr_copy(&haopt->addr, &coaddr); ++ opt->hao = optoff; ++ if (mip6_fn.mn_check_tunneled_packet != NULL) ++ mip6_fn.mn_check_tunneled_packet(skb); ++ ++ MIPV6_INC_STATS(n_ha_rcvd); ++ return 1; ++} ++ ++/** ++ * mipv6_icmp_swap_addrs - Switch HAO and src and RT2 and dest for ICMP errors ++ * @skb: packet buffer ++ * ++ * Reset the source address and the Home Address option in skb before ++ * appending it to an ICMP error message, so original packet appears ++ * in the error message rather than mangled. ++ **/ ++void mipv6_icmp_swap_addrs(struct sk_buff *skb) ++{ ++ struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb; ++ struct in6_addr tmp; ++ struct in6_addr *hoa; ++ DEBUG_FUNC(); ++ if (opt->srcrt2) { ++ struct rt2_hdr *rt2; ++ rt2 = (struct rt2_hdr *)(skb->nh.raw + opt->srcrt2); ++ hoa = &rt2->addr; ++ ++ ipv6_addr_copy(&tmp, hoa); ++ ipv6_addr_copy(hoa, &skb->nh.ipv6h->daddr); ++ ipv6_addr_copy(&skb->nh.ipv6h->daddr, &tmp); ++ rt2->rt_hdr.segments_left++; ++ skb->nh.ipv6h->hop_limit++; ++ } ++ if (opt->hao) { ++ struct mipv6_dstopt_homeaddr *hao; ++ hao = (struct mipv6_dstopt_homeaddr *)(skb->nh.raw + opt->hao); ++ hoa = &hao->addr; ++ ++ ipv6_addr_copy(&tmp, hoa); ++ ipv6_addr_copy(hoa, &skb->nh.ipv6h->saddr); ++ ipv6_addr_copy(&skb->nh.ipv6h->saddr, &tmp); ++ } ++} ++ ++/** ++ * mipv6_append_rt2hdr - Add Type 2 Routing Header ++ * @rt: buffer for new routing header ++ * @addr: intermediate hop address ++ * ++ * Adds a Routing Header Type 2 in a packet. Stores newly created ++ * routing header in buffer @rt. Type 2 RT only carries one address, ++ * so there is no need to process old routing header. @rt must have ++ * allocated space for 24 bytes. ++ **/ ++void mipv6_append_rt2hdr(struct ipv6_rt_hdr *rt, struct in6_addr *addr) ++{ ++ struct rt2_hdr *rt2 = (struct rt2_hdr *)rt; ++ ++ DEBUG(DBG_DATADUMP, "RT2: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(addr)); ++ ++ if (ipv6_addr_type(addr) == IPV6_ADDR_MULTICAST) { ++ DEBUG(DBG_ERROR, "destination address not unicast"); ++ return; ++ } ++ ++ memset(rt2, 0, sizeof(*rt2)); ++ rt2->rt_hdr.type = 2; ++ rt2->rt_hdr.hdrlen = 2; ++ rt2->rt_hdr.segments_left = 1; ++ ipv6_addr_copy(&rt2->addr, addr); ++} ++ ++/** ++ * mipv6_append_dst1opts - Add Destination Option (1) Headers ++ * @dst1opt: buffer for new destination options ++ * @saddr: address for Home Address Option ++ * @old_dst1opt: old destination options ++ * @len: length of options ++ * ++ * Adds Destination Option (1) Header to a packet. New options are ++ * stored in @dst1opt. If old destination options exist, they are ++ * copied from @old_dst1opt. Only Home Address Option is destination ++ * option. @dstopt must have allocated space for @len bytes. @len ++ * includes Destination Option Header (2 bytes), Home Address Option ++ * (18 bytes) and possible HAO pad (8n+6). ++ **/ ++/* ++ * ISSUE: Home Address Destination Option should really be added to a ++ * new destination option header specified in Mobile IPv6 spec which ++ * should be placed after routing header(s), but before fragmentation ++ * header. Putting HAO in DO1 works for now, but support for the new ++ * placement should be added to the IPv6 stack. ++ */ ++void ++mipv6_append_dst1opts(struct ipv6_opt_hdr *dst1opt, struct in6_addr *saddr, ++ struct ipv6_opt_hdr *old_dst1opt, int len) ++{ ++ int offset; ++ ++ if (old_dst1opt) { ++ memcpy(dst1opt, old_dst1opt, ipv6_optlen(old_dst1opt)); ++ offset = ipv6_optlen(old_dst1opt); ++ } else { ++ offset = sizeof (*dst1opt); ++ } ++ dst1opt->hdrlen = (len >> 3) - 1; ++ mipv6_append_home_addr((__u8 *) dst1opt, offset, saddr); ++} ++ ++/** ++ * mipv6_modify_txoptions - Modify outgoing packets ++ * @sk: socket ++ * @skb: packet buffer for outgoing packet ++ * @old_opt: transmit options ++ * @fl: packet flow structure ++ * @dst: pointer to destination cache entry ++ * ++ * Adds Home Address Option (for MN packets, when not at home) and ++ * Routing Header Type 2 (for CN packets when sending to an MN) to ++ * data packets. Old extension headers are copied from @old_opt (if ++ * any). Extension headers are _explicitly_ added for packets with ++ * Mobility Header. Returns the new header structure, or old if no ++ * changes. ++ **/ ++struct ipv6_txoptions * ++mipv6_modify_txoptions(struct sock *sk, struct sk_buff *skb, ++ struct ipv6_txoptions *old_opt, struct flowi *fl, ++ struct dst_entry **dst) ++{ ++ struct ipv6_opt_hdr *old_hopopt = NULL; ++ struct ipv6_opt_hdr *old_dst1opt = NULL; ++ struct ipv6_rt_hdr *old_srcrt = NULL; ++ ++ int srcrtlen = 0, dst1len = 0; ++ int tot_len, use_hao = 0; ++ struct ipv6_txoptions *opt; ++ struct mipv6_bce bc_entry; ++ struct in6_addr tmpaddr, *saddr, *daddr, coaddr; ++ __u8 *opt_ptr; ++ ++ DEBUG_FUNC(); ++ ++ if (fl->proto == IPPROTO_MOBILITY) return old_opt; ++ /* ++ * we have to be prepared to the fact that saddr might not be present, ++ * if that is the case, we acquire saddr just as kernel does. ++ */ ++ saddr = fl ? fl->fl6_src : NULL; ++ daddr = fl ? fl->fl6_dst : NULL; ++ ++ if (daddr == NULL) ++ return old_opt; ++ if (saddr == NULL) { ++ int err = ipv6_get_saddr(NULL, daddr, &tmpaddr); ++ if (err) ++ return old_opt; ++ else ++ saddr = &tmpaddr; ++ } ++ ++ DEBUG(DBG_DATADUMP, ++ "dest. address of packet: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(daddr)); ++ DEBUG(DBG_DATADUMP, " and src. address: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(saddr)); ++ ++ if (old_opt) { ++ old_hopopt = old_opt->hopopt; ++ old_dst1opt = old_opt->dst1opt; ++ old_srcrt = old_opt->srcrt; ++ } ++ ++ if (mip6_fn.mn_use_hao != NULL) ++ use_hao = mip6_fn.mn_use_hao(daddr, saddr); ++ ++ if (use_hao) { ++ if (old_dst1opt) ++ dst1len = ipv6_optlen(old_dst1opt); ++ dst1len += sizeof(struct mipv6_dstopt_homeaddr) + ++ ((6 - dst1len) & 7); /* padding */ ++ } ++ ++ if (mipv6_bcache_get(daddr, saddr, &bc_entry) == 0) ++ srcrtlen = sizeof(struct rt2_hdr); ++ ++ if ((tot_len = srcrtlen + dst1len) == 0) { ++ return old_opt; ++ } ++ ++ tot_len += sizeof(*opt); ++ ++ if (!(opt = kmalloc(tot_len, GFP_ATOMIC))) { ++ return NULL; ++ } ++ memset(opt, 0, tot_len); ++ opt->tot_len = tot_len; ++ opt_ptr = (__u8 *) (opt + 1); ++ ++ if (old_srcrt) { ++ opt->srcrt = old_srcrt; ++ opt->opt_nflen += ipv6_optlen(old_srcrt); ++ } ++ ++ if (srcrtlen) { ++ DEBUG(DBG_DATADUMP, "Binding exists. Adding routing header"); ++ ++ opt->srcrt2 = (struct ipv6_rt_hdr *) opt_ptr; ++ opt->opt_nflen += srcrtlen; ++ opt_ptr += srcrtlen; ++ ++ /* ++ * Append care-of-address to routing header (original ++ * destination address is home address, the first ++ * source route segment gets put to the destination ++ * address and the home address gets to the last ++ * segment of source route (just as it should)) ++ */ ++ ++ ipv6_addr_copy(&coaddr, &bc_entry.coa); ++ ++ mipv6_append_rt2hdr(opt->srcrt2, &coaddr); ++ ++ /* ++ * reroute output (we have to do this in case of TCP ++ * segment) unless a routing header of type 0 is also added ++ */ ++ if (dst && !opt->srcrt) { ++ struct in6_addr *tmp = fl->fl6_dst; ++ fl->fl6_dst = &coaddr; ++ ++ dst_release(*dst); ++ *dst = ip6_route_output(sk, fl); ++ if (skb) ++ skb->dst = *dst; ++ fl->fl6_dst = tmp; ++ ++ DEBUG(DBG_DATADUMP, "Rerouted outgoing packet"); ++ } ++ } ++ ++ /* Only home address option is inserted to first dst opt header */ ++ if (dst1len) { ++ opt->dst1opt = (struct ipv6_opt_hdr *) opt_ptr; ++ opt->opt_flen += dst1len; ++ opt_ptr += dst1len; ++ mipv6_append_dst1opts(opt->dst1opt, saddr, ++ old_dst1opt, dst1len); ++ opt->mipv6_flags = MIPV6_SND_HAO; ++ } else if (old_dst1opt) { ++ opt->dst1opt = old_dst1opt; ++ opt->opt_flen += ipv6_optlen(old_dst1opt); ++ } ++ if (old_hopopt) { ++ opt->hopopt = old_hopopt; ++ opt->opt_nflen += ipv6_optlen(old_hopopt); ++ } ++ ++ return opt; ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/exthdrs.h linux-2.4.25/net/ipv6/mobile_ip6/exthdrs.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/exthdrs.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/exthdrs.h 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,47 @@ ++/* ++ * MIPL Mobile IPv6 Extension Headers header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _MIPV6_EXTHDRS_H ++#define _MIPV6_EXTHDRS_H ++ ++struct in6_addr; ++struct sk_buff; ++struct ipv6_rt_hdr; ++struct ipv6_opt_hdr; ++struct ipv6_txoptions; ++struct flowi; ++struct dst_entry; ++/* ++ * Home Address Destination Option function prototypes ++ */ ++int mipv6_append_home_addr(__u8 *opt, int offset, struct in6_addr *addr); ++ ++int mipv6_handle_homeaddr(struct sk_buff *skb, int optoff); ++ ++void mipv6_icmp_swap_addrs(struct sk_buff *skb); ++ ++/* ++ * Creates a routing header of type 2. ++ */ ++void mipv6_append_rt2hdr(struct ipv6_rt_hdr *srcrt, struct in6_addr *addr); ++ ++/* Function to add the first destination option header, which may ++ * include a home address option. ++ */ ++void mipv6_append_dst1opts(struct ipv6_opt_hdr *dst1opt, struct in6_addr *saddr, ++ struct ipv6_opt_hdr *old_dst1opt, int len); ++ ++struct ipv6_txoptions *mipv6_modify_txoptions( ++ struct sock *sk, struct sk_buff *skb, ++ struct ipv6_txoptions *old_opt, struct flowi *fl, ++ struct dst_entry **dst); ++ ++#endif /* _MIPV6_EXTHDRS_H */ +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/ha.c linux-2.4.25/net/ipv6/mobile_ip6/ha.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/ha.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/ha.c 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,553 @@ ++/* ++ * Home-agent functionality ++ * ++ * Authors: ++ * Sami Kivisaari ++ * Henrik Petander ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ * Changes: Venkata Jagana, ++ * Krishna Kumar : Statistics fix ++ * Masahide Nakamura : Use of mipv6_forward ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#ifdef CONFIG_SYSCTL ++#include ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "tunnel_ha.h" ++#include "bcache.h" ++#include "stats.h" ++#include "debug.h" ++#include "util.h" ++#include "ha.h" ++#include "config.h" ++#include "mobhdr.h" ++ ++static int mipv6_ha_tunnel_sitelocal = 0; ++ ++#ifdef CONFIG_SYSCTL ++ ++static struct ctl_table_header *mipv6_ha_sysctl_header; ++ ++static struct mipv6_ha_sysctl_table ++{ ++ struct ctl_table_header *sysctl_header; ++ ctl_table mipv6_vars[3]; ++ ctl_table mipv6_mobility_table[2]; ++ ctl_table mipv6_proto_table[2]; ++ ctl_table mipv6_root_table[2]; ++} mipv6_ha_sysctl = { ++ NULL, ++ ++ {{NET_IPV6_MOBILITY_TUNNEL_SITELOCAL, "tunnel_sitelocal", ++ &mipv6_ha_tunnel_sitelocal, sizeof(int), 0644, NULL, ++ &proc_dointvec}, ++ {0}}, ++ ++ {{NET_IPV6_MOBILITY, "mobility", NULL, 0, 0555, ++ mipv6_ha_sysctl.mipv6_vars}, {0}}, ++ {{NET_IPV6, "ipv6", NULL, 0, 0555, ++ mipv6_ha_sysctl.mipv6_mobility_table}, {0}}, ++ {{CTL_NET, "net", NULL, 0, 0555, ++ mipv6_ha_sysctl.mipv6_proto_table}, {0}} ++}; ++ ++#endif /* CONFIG_SYSCTL */ ++ ++ ++/* this is defined in kernel IPv6 module (sockglue.c) */ ++extern struct packet_type ipv6_packet_type; ++ ++/* mipv6_forward: Intercept NS packets destined to home address of MN */ ++int mipv6_forward(struct sk_buff *skb) ++{ ++ struct ipv6hdr *ipv6h; ++ struct in6_addr *daddr, *saddr; ++ __u8 nexthdr; ++ int nhoff; ++ ++ if (skb == NULL) return 0; ++ ++ ipv6h = skb->nh.ipv6h; ++ daddr = &ipv6h->daddr; ++ saddr = &ipv6h->saddr; ++ ++ nexthdr = ipv6h->nexthdr; ++ nhoff = sizeof(*ipv6h); ++ ++ if (ipv6_ext_hdr(nexthdr)) ++ nhoff = ipv6_skip_exthdr(skb, nhoff, &nexthdr, ++ skb->len - sizeof(*ipv6h)); ++ ++ /* Do not to forward Neighbor Solicitation to Home Address of MN */ ++ if (nexthdr == IPPROTO_ICMPV6) { ++ struct icmp6hdr *icmp6h; ++ int dest_type; ++ ++ if (nhoff < 0 || !pskb_may_pull(skb, nhoff + ++ sizeof(struct icmp6hdr))) { ++ kfree_skb(skb); ++ return 0; ++ } ++ ++ dest_type = ipv6_addr_type(daddr); ++ icmp6h = (struct icmp6hdr *)&skb->nh.raw[nhoff]; ++ ++ /* Intercepts NS to HoA of MN */ ++ ++ if ((icmp6h->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION) || ++ ((dest_type & IPV6_ADDR_MULTICAST) && ++ (icmp6h->icmp6_type == NDISC_ROUTER_ADVERTISEMENT))) { ++ ip6_input(skb); ++ } else { ++ ip6_forward(skb); ++ } ++ } else { ++ ip6_forward(skb); ++ } ++ return 0; ++} ++ ++ ++/** ++ * mipv6_proxy_nd_rem - stop acting as a proxy for @home_address ++ * @home_addr: address to remove ++ * @ha_addr: home agent's address on home link ++ * @linklocal: link-local compatibility bit ++ * ++ * When Home Agent acts as a proxy for an address it must leave the ++ * solicited node multicast group for that address and stop responding ++ * to neighbour solicitations. ++ **/ ++static int mipv6_proxy_nd_rem(struct in6_addr *home_addr, ++ int ifindex, int linklocal) ++{ ++ /* When MN returns home HA leaves the solicited mcast groups ++ * for MNs home addresses ++ */ ++ int err; ++ struct net_device *dev; ++ ++ DEBUG_FUNC(); ++ ++ if ((dev = dev_get_by_index(ifindex)) == NULL) { ++ DEBUG(DBG_ERROR, "couldn't get dev"); ++ return -ENODEV; ++ } ++#if 1 /* TEST */ ++ /* Remove link-local entry */ ++ if (linklocal) { ++ struct in6_addr ll_addr; ++ mipv6_generate_ll_addr(&ll_addr, home_addr); ++ if ((err = pneigh_delete(&nd_tbl, &ll_addr, dev)) < 0) { ++ DEBUG(DBG_INFO, ++ "peigh_delete failed for " ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&ll_addr)); ++ } ++ } ++#endif ++ /* Remove global (or site-local) entry */ ++ if ((err = pneigh_delete(&nd_tbl, home_addr, dev)) < 0) { ++ DEBUG(DBG_INFO, ++ "peigh_delete failed for " ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(home_addr)); ++ } ++ dev_put(dev); ++ return err; ++} ++ ++/** ++ * mipv6_proxy_nd - join multicast group for this address ++ * @home_addr: address to defend ++ * @ha_addr: home agent's address on home link ++ * @linklocal: link-local compatibility bit ++ * ++ * While Mobile Node is away from home, Home Agent acts as a proxy for ++ * @home_address. HA responds to neighbour solicitations for @home_address ++ * thus getting all packets destined to home address of MN. ++ **/ ++static int mipv6_proxy_nd(struct in6_addr *home_addr, ++ int ifindex, int linklocal) ++{ ++ /* The HA sends a proxy ndisc_na message to all hosts on MN's ++ * home subnet by sending a neighbor advertisement with the ++ * home address or all addresses of the mobile node if the ++ * prefix is not 0. The addresses are formed by combining the ++ * suffix or the host part of the address with each subnet ++ * prefix that exists in the home subnet ++ */ ++ ++ /* Since no previous entry for MN exists a proxy_nd advertisement ++ * is sent to all nodes link local multicast address ++ */ ++ int err = -1; ++ ++ struct net_device *dev; ++ struct in6_addr na_saddr; ++ struct in6_addr ll_addr; ++ struct pneigh_entry *ll_pneigh; ++ struct in6_addr mcdest; ++ int send_ll_na = 0; ++ int inc_opt = 1; ++ int solicited = 0; ++ int override = 1; ++ ++ DEBUG_FUNC(); ++ ++ if ((dev = dev_get_by_index(ifindex)) == NULL) { ++ DEBUG(DBG_ERROR, "couldn't get dev"); ++ return -ENODEV; ++ } ++ ++ if (!pneigh_lookup(&nd_tbl, home_addr, dev, 1)) { ++ DEBUG(DBG_INFO, ++ "peigh_lookup failed for " ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(home_addr)); ++ goto free_dev; ++ } ++#if 1 /* TEST */ ++ if (linklocal) { ++ mipv6_generate_ll_addr(&ll_addr, home_addr); ++ ++ if ((ll_pneigh = pneigh_lookup(&nd_tbl, &ll_addr, ++ dev, 1)) == NULL) { ++ DEBUG(DBG_INFO, ++ "peigh_lookup failed for " ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&ll_addr)); ++ pneigh_delete(&nd_tbl, home_addr, dev); ++ goto free_dev; ++ } else { ++ send_ll_na = 1; ++ } ++ } else { ++ ll_pneigh = NULL; ++ } ++#endif ++ /* Proxy neighbor advertisement of MN's home address ++ * to all nodes solicited multicast address ++ */ ++ if (!ipv6_get_lladdr(dev, &na_saddr)) { ++ ipv6_addr_all_nodes(&mcdest); ++ ndisc_send_na(dev, NULL, &mcdest, home_addr, 0, ++ solicited, override, inc_opt); ++#if 1 /* TEST */ ++ if (send_ll_na) { ++ ndisc_send_na(dev, NULL, &mcdest, &ll_addr, ++ 0, solicited, override, inc_opt); ++ } ++#endif ++ err = 0; ++ } else { ++ DEBUG(DBG_ERROR, "failed to get link local address for sending proxy NA"); ++ } ++free_dev: ++ dev_put(dev); ++ return err; ++ ++} ++ ++struct inet6_ifaddr *is_on_link_ipv6_address(struct in6_addr *mn_haddr, ++ struct in6_addr *ha_addr) ++{ ++ struct inet6_ifaddr *ifp; ++ struct inet6_dev *in6_dev; ++ struct inet6_ifaddr *oifp = NULL; ++ ++ if ((ifp = ipv6_get_ifaddr(ha_addr, 0)) == NULL) ++ return NULL; ++ ++ if ((in6_dev = ifp->idev) != NULL) { ++ in6_dev_hold(in6_dev); ++ oifp = in6_dev->addr_list; ++ while (oifp != NULL) { ++ spin_lock(&oifp->lock); ++ if (mipv6_prefix_compare(&oifp->addr, mn_haddr, ++ oifp->prefix_len) && ++ !(oifp->flags & IFA_F_TENTATIVE)) { ++ spin_unlock(&oifp->lock); ++ DEBUG(DBG_INFO, "Home Addr Opt: on-link"); ++ in6_ifa_hold(oifp); ++ break; ++ } ++ spin_unlock(&oifp->lock); ++ oifp = oifp->if_next; ++ } ++ in6_dev_put(in6_dev); ++ } ++ in6_ifa_put(ifp); ++/* DEBUG(DBG_WARNING, "Home Addr Opt NOT on-link"); */ ++ return oifp; ++ ++} ++ ++/* ++ * Lifetime checks. ifp->valid_lft >= ifp->prefered_lft always (see addrconf.c) ++ * Returned value is in seconds. ++ */ ++ ++static __u32 get_min_lifetime(struct inet6_ifaddr *ifp, __u32 lifetime) ++{ ++ __u32 rem_lifetime = 0; ++ unsigned long now = jiffies; ++ ++ if (ifp->valid_lft == 0) { ++ rem_lifetime = lifetime; ++ } else { ++ __u32 valid_lft_left = ++ ifp->valid_lft - ((now - ifp->tstamp) / HZ); ++ rem_lifetime = ++ min_t(unsigned long, valid_lft_left, lifetime); ++ } ++ ++ return rem_lifetime; ++} ++ ++#define MAX_LIFETIME 1000 ++ ++/** ++ * mipv6_lifetime_check - check maximum lifetime is not exceeded ++ * @lifetime: lifetime to check ++ * ++ * Checks @lifetime does not exceed %MAX_LIFETIME. Returns @lifetime ++ * if not exceeded, otherwise returns %MAX_LIFETIME. ++ **/ ++static int mipv6_lifetime_check(int lifetime) ++{ ++ return (lifetime > MAX_LIFETIME) ? MAX_LIFETIME : lifetime; ++} ++ ++/* Generic routine handling finish of BU processing */ ++void mipv6_bu_finish(struct inet6_ifaddr *ifp, int ifindex, __u8 ba_status, ++ struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u32 ba_lifetime, __u16 sequence, __u8 flags, __u8 *k_bu) ++{ ++ int err; ++ ++ if (ba_status >= REASON_UNSPECIFIED) { ++ /* DAD failed */ ++ goto out; ++ } ++ ++ ba_lifetime = get_min_lifetime(ifp, ba_lifetime); ++ ba_lifetime = mipv6_lifetime_check(ba_lifetime); ++ ++ if ((err = mipv6_bcache_add(ifindex, daddr, haddr, coa, ++ ba_lifetime, sequence, flags, ++ HOME_REGISTRATION)) != 0 ) { ++ DEBUG(DBG_WARNING, "home reg failed."); ++ ++ if (err == -ENOMEDIUM) ++ return; ++ ++ ba_status = INSUFFICIENT_RESOURCES; ++ } else { ++ DEBUG(DBG_INFO, "home reg succeeded."); ++ } ++ ++ DEBUG(DBG_DATADUMP, "home_addr: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(haddr)); ++ DEBUG(DBG_DATADUMP, "coa: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(coa)); ++ DEBUG(DBG_DATADUMP, "lifet:%d, seq:%d", ba_lifetime, sequence); ++out: ++ mipv6_send_ba(daddr, haddr, coa, rep_coa, ba_status, sequence, ++ ba_lifetime, k_bu); ++} ++ ++static int ha_proxy_create(int flags, int ifindex, struct in6_addr *coa, ++ struct in6_addr *our_addr, struct in6_addr *home_addr) ++{ ++ int ret; ++ ++ if ((ret = mipv6_add_tnl_to_mn(coa, our_addr, home_addr)) <= 0) { ++ if (ret != -ENOMEDIUM) { ++ DEBUG(DBG_ERROR, "unable to configure tunnel to MN!"); ++ } ++ return -1; ++ } ++ if (mipv6_proxy_nd(home_addr, ifindex, ++ flags & MIPV6_BU_F_LLADDR) != 0) { ++ DEBUG(DBG_ERROR, "mipv6_proxy_nd failed!"); ++ mipv6_del_tnl_to_mn(coa, our_addr, home_addr); ++ return -2; ++ } ++ return 0; ++} ++ ++static void ha_proxy_del(struct in6_addr *home_addr, struct mipv6_bce *entry) ++{ ++ if (mipv6_proxy_nd_rem(&entry->home_addr, entry->ifindex, ++ entry->flags & MIPV6_BU_F_LLADDR) == 0) { ++ DEBUG(DBG_INFO, "proxy_nd succ"); ++ } else { ++ DEBUG(DBG_INFO, "proxy_nd fail"); ++ } ++ mipv6_del_tnl_to_mn(&entry->coa, &entry->our_addr, home_addr); ++} ++ ++static void bc_home_add(int ifindex, ++ struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u32 lifetime, __u16 sequence, __u8 flags, ++ __u8 *k_bu) ++{ ++ struct inet6_ifaddr *ifp = NULL; ++ __u8 ba_status = SUCCESS; ++ ++ DEBUG_FUNC(); ++ ++ ifp = is_on_link_ipv6_address(haddr, daddr); ++ ++ if (ifp == NULL) { ++ ba_status = NOT_HOME_SUBNET; ++ } else if (((ipv6_addr_type(haddr) & IPV6_ADDR_SITELOCAL) || ++ (ipv6_addr_type(coa) & IPV6_ADDR_SITELOCAL)) ++ && !mipv6_ha_tunnel_sitelocal) { ++ /* Site-local home or care-of addresses are not ++ accepted by default */ ++ ba_status = ADMINISTRATIVELY_PROHIBITED; ++ } else { ++ int ret; ++ ++ ifindex = ifp->idev->dev->ifindex; ++ ++ if ((ret = mipv6_dad_start(ifp, ifindex, daddr, ++ haddr, coa, rep_coa, lifetime, ++ sequence, flags)) < 0) { ++ /* An error occurred */ ++ ba_status = -ret; ++ } else if (ret) { ++ /* DAD is needed to be performed. */ ++ in6_ifa_put(ifp); ++ return; ++ } ++ } ++ ++ mipv6_bu_finish(ifp, ifindex, ba_status, daddr, haddr, coa, ++ rep_coa, lifetime, sequence, flags, k_bu); ++ if (ifp) ++ in6_ifa_put(ifp); ++} ++ ++static void bc_home_delete(struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u16 sequence, __u8 flags, __u8 *k_bu) ++{ ++ __u8 status = SUCCESS; ++ struct mipv6_bce bce; ++ ++ /* Primary Care-of Address Deregistration */ ++ if (mipv6_bcache_get(haddr, daddr, &bce) < 0) { ++ DEBUG(DBG_INFO, "entry is not in cache"); ++ status = NOT_HA_FOR_MN; ++ } else { ++ ha_proxy_del(&bce.home_addr, &bce); ++ mipv6_bcache_delete(haddr, daddr, HOME_REGISTRATION); ++ } ++ mipv6_send_ba(daddr, haddr, coa, rep_coa, status, sequence, 0, k_bu); ++} ++ ++extern int mipv6_ra_rcv_ptr(struct sk_buff *skb, struct icmp6hdr *msg); ++ ++ ++static int ++mipv6_ha_tnl_xmit_stats_hook(struct ip6_tnl *t, struct sk_buff *skb) ++{ ++ DEBUG_FUNC(); ++ if (is_mip6_tnl(t)) ++ MIPV6_INC_STATS(n_encapsulations); ++ return IP6_TNL_ACCEPT; ++} ++ ++static struct ip6_tnl_hook_ops mipv6_ha_tnl_xmit_stats_ops = { ++ {NULL, NULL}, ++ IP6_TNL_PRE_ENCAP, ++ IP6_TNL_PRI_LAST, ++ mipv6_ha_tnl_xmit_stats_hook ++}; ++ ++static int ++mipv6_ha_tnl_rcv_stats_hook(struct ip6_tnl *t, struct sk_buff *skb) ++{ ++ DEBUG_FUNC(); ++ if (is_mip6_tnl(t)) ++ MIPV6_INC_STATS(n_decapsulations); ++ return IP6_TNL_ACCEPT; ++} ++ ++static struct ip6_tnl_hook_ops mipv6_ha_tnl_rcv_stats_ops = { ++ {NULL, NULL}, ++ IP6_TNL_PRE_DECAP, ++ IP6_TNL_PRI_LAST, ++ mipv6_ha_tnl_rcv_stats_hook ++}; ++ ++static struct mip6_func old; ++ ++int __init mipv6_ha_init(void) ++{ ++ DEBUG_FUNC(); ++ ++#ifdef CONFIG_SYSCTL ++ if (!(mipv6_ha_sysctl_header = ++ register_sysctl_table(mipv6_ha_sysctl.mipv6_root_table, 0))) ++ printk(KERN_ERR "Failed to register sysctl handlers!"); ++#endif ++ memcpy(&old, &mip6_fn, sizeof(struct mip6_func)); ++ mip6_fn.bce_home_add = bc_home_add; ++ mip6_fn.bce_home_del = bc_home_delete; ++ mip6_fn.proxy_del = ha_proxy_del; ++ mip6_fn.proxy_create = ha_proxy_create; ++ /* register packet interception hooks */ ++ ip6ip6_tnl_register_hook(&mipv6_ha_tnl_xmit_stats_ops); ++ ip6ip6_tnl_register_hook(&mipv6_ha_tnl_rcv_stats_ops); ++ return 0; ++} ++ ++void __exit mipv6_ha_exit(void) ++{ ++ DEBUG_FUNC(); ++ ++#ifdef CONFIG_SYSCTL ++ unregister_sysctl_table(mipv6_ha_sysctl_header); ++#endif ++ ++ /* remove packet interception hooks */ ++ ip6ip6_tnl_unregister_hook(&mipv6_ha_tnl_rcv_stats_ops); ++ ip6ip6_tnl_unregister_hook(&mipv6_ha_tnl_xmit_stats_ops); ++ ++ mip6_fn.bce_home_add = old.bce_home_add; ++ mip6_fn.bce_home_del = old.bce_home_del; ++ mip6_fn.proxy_del = old.proxy_del; ++ mip6_fn.proxy_create = old.proxy_create; ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/ha.h linux-2.4.25/net/ipv6/mobile_ip6/ha.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/ha.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/ha.h 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,39 @@ ++/* ++ * MIPL Mobile IPv6 Home Agent header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _HA_H ++#define _HA_H ++ ++int mipv6_ha_init(void); ++void mipv6_ha_exit(void); ++ ++int mipv6_dad_start(struct inet6_ifaddr *ifp, int ifindex, ++ struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u32 ba_lifetime, __u16 sequence, __u8 flags); ++ ++void mipv6_bu_finish(struct inet6_ifaddr *ifp, int ifindex, ++ __u8 ba_status, struct in6_addr *daddr, ++ struct in6_addr *haddr, struct in6_addr *coa, ++ struct in6_addr *rep_coa, __u32 ba_lifetime, ++ __u16 sequence, __u8 flags, __u8 *k_bu); ++ ++ ++static __inline__ void mipv6_generate_ll_addr(struct in6_addr *ll_addr, ++ struct in6_addr *addr) ++{ ++ ll_addr->s6_addr32[0] = htonl(0xfe800000); ++ ll_addr->s6_addr32[1] = 0; ++ ll_addr->s6_addr32[2] = addr->s6_addr32[2]; ++ ll_addr->s6_addr32[3] = addr->s6_addr32[3]; ++} ++ ++#endif +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/halist.c linux-2.4.25/net/ipv6/mobile_ip6/halist.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/halist.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/halist.c 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,507 @@ ++/* ++ * Home Agents List ++ * ++ * Authors: ++ * Antti Tuominen ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#define PREF_BASE 0xffff /* MAX value for u16 field in RA */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "hashlist.h" ++#include "util.h" ++#include "debug.h" ++ ++struct mipv6_halist { ++ struct hashlist *entries; ++ struct timer_list expire_timer; ++}; ++ ++static rwlock_t home_agents_lock = RW_LOCK_UNLOCKED; ++ ++static struct mipv6_halist home_agents; ++ ++struct mipv6_halist_entry { ++ struct hashlist_entry e; ++ int ifindex; /* Link identifier */ ++ struct in6_addr link_local_addr; /* HA's link-local address */ ++ struct in6_addr global_addr; /* HA's Global address */ ++ int plen; ++ long preference; /* The preference for this HA */ ++ unsigned long expire; /* expiration time (jiffies) */ ++}; ++ ++static inline void mipv6_ha_ac_add(struct in6_addr *ll_addr, int ifindex, ++ struct in6_addr *glob_addr, int plen) ++{ ++ struct net_device *dev; ++ ++ if ((dev = __dev_get_by_index(ifindex)) && ipv6_chk_addr(ll_addr, dev)) { ++ struct in6_addr addr; ++ mipv6_ha_anycast(&addr, glob_addr, plen); ++ ipv6_dev_ac_inc(dev, &addr); ++ } ++} ++ ++static inline void mipv6_ha_ac_del(struct in6_addr *ll_addr, int ifindex, ++ struct in6_addr *glob_addr, int plen) ++{ ++ struct net_device *dev; ++ ++ if ((dev = __dev_get_by_index(ifindex)) && ipv6_chk_addr(ll_addr, dev)) { ++ struct in6_addr addr; ++ mipv6_ha_anycast(&addr, glob_addr, plen); ++ ipv6_dev_ac_dec(dev, &addr); ++ } ++} ++ ++struct preflist_iterator_args { ++ int count; ++ int requested; ++ int ifindex; ++ struct in6_addr *list; ++}; ++ ++static int preflist_iterator(void *data, void *args, ++ unsigned long *pref) ++{ ++ struct preflist_iterator_args *state = ++ (struct preflist_iterator_args *)args; ++ struct mipv6_halist_entry *entry = ++ (struct mipv6_halist_entry *)data; ++ struct in6_addr *newaddr = ++ (struct in6_addr *)state->list + state->count; ++ ++ if (state->count >= state->requested) ++ return ITERATOR_STOP; ++ ++ if (time_after(jiffies, entry->expire)) { ++ if (!ipv6_addr_any(&entry->link_local_addr)) { ++ mipv6_ha_ac_del(&entry->link_local_addr, ++ entry->ifindex, ++ &entry->global_addr, entry->plen); ++ } ++ DEBUG(DBG_INFO, "preflist_iterator: Deleting entry with address %x:%x:%x:%x:%x:%x:%x:%x to list", NIPV6ADDR(&entry->global_addr)); ++ return ITERATOR_DELETE_ENTRY; ++ } ++ if (state->ifindex != entry->ifindex) ++ return ITERATOR_CONT; ++ ++ ipv6_addr_copy(newaddr, &entry->global_addr); ++ DEBUG(DBG_INFO, "preflist_iterator: adding new entry with address %x:%x:%x:%x:%x:%x:%x:%x to list", NIPV6ADDR(&entry->global_addr)); ++ state->count++; ++ ++ return ITERATOR_CONT; ++} ++ ++static int gc_iterator(void *data, void *args, ++ unsigned long *pref) ++{ ++ struct mipv6_halist_entry *entry = ++ (struct mipv6_halist_entry *)data; ++ ++ int *type = (int *)args; ++ ++ if (*type == 1 || time_after(jiffies, entry->expire)) { ++ if (!ipv6_addr_any(&entry->link_local_addr)) { ++ mipv6_ha_ac_del(&entry->link_local_addr, ++ entry->ifindex, ++ &entry->global_addr, entry->plen); ++ } ++ return ITERATOR_DELETE_ENTRY; ++ } ++ ++ return ITERATOR_CONT; ++} ++ ++static int mipv6_halist_gc(int type) ++{ ++ DEBUG_FUNC(); ++ hashlist_iterate(home_agents.entries, &type, gc_iterator); ++ return 0; ++} ++ ++static void mipv6_halist_expire(unsigned long dummy) ++{ ++ DEBUG_FUNC(); ++ ++ write_lock(&home_agents_lock); ++ mipv6_halist_gc(0); ++ write_unlock(&home_agents_lock); ++} ++ ++ ++static struct mipv6_halist_entry *mipv6_halist_new_entry(void) ++{ ++ struct mipv6_halist_entry *entry; ++ ++ DEBUG_FUNC(); ++ ++ entry = hashlist_alloc(home_agents.entries, SLAB_ATOMIC); ++ ++ return entry; ++} ++ ++ ++ ++/** ++ * mipv6_halist_add - Add new home agent to the Home Agents List ++ * @ifindex: interface identifier ++ * @glob_addr: home agent's global address ++ * @ll_addr: home agent's link-local address ++ * @pref: relative preference for this home agent ++ * @lifetime: lifetime for the entry ++ * ++ * Adds new home agent to the Home Agents List. The list is interface ++ * specific and @ifindex tells through which interface the home agent ++ * was heard. Returns zero on success and negative on failure. ++ **/ ++ ++int mipv6_halist_add(int ifindex, struct in6_addr *glob_addr, int plen, ++ struct in6_addr *ll_addr, unsigned int pref, __u32 lifetime) ++{ ++ int update = 0, ret = 0; ++ unsigned int mpref; ++ struct mipv6_halist_entry *entry = NULL; ++ ++ DEBUG_FUNC(); ++ ++ write_lock(&home_agents_lock); ++ ++ if (glob_addr == NULL || lifetime <= 0) { ++ DEBUG(DBG_WARNING, "invalid arguments"); ++ ret = -EINVAL; ++ goto out; ++ } ++ mpref = PREF_BASE - pref; ++ if ((entry = (struct mipv6_halist_entry *) ++ hashlist_get(home_agents.entries, glob_addr)) != NULL) { ++ if (entry->ifindex == ifindex) { ++ DEBUG(DBG_DATADUMP, "updating old entry with address %x:%x:%x:%x:%x:%x:%x:%x", NIPV6ADDR(glob_addr)); ++ update = 1; ++ } else { ++ DEBUG(DBG_INFO, "halist_add : adding new entry with address %x:%x:%x:%x:%x:%x:%x:%x", NIPV6ADDR(glob_addr)); ++ update = 0; ++ } ++ } ++ if (update) { ++ entry->expire = jiffies + lifetime * HZ; ++ if (entry->preference != mpref) { ++ entry->preference = mpref; ++ ret = hashlist_reposition(home_agents.entries, ++ (void *)entry, mpref); ++ } ++ } else { ++ entry = mipv6_halist_new_entry(); ++ if (entry == NULL) { ++ DEBUG(DBG_INFO, "list full"); ++ ret = -ENOMEM; ++ goto out; ++ } ++ entry->ifindex = ifindex; ++ if (ll_addr) { ++ ipv6_addr_copy(&entry->link_local_addr, ll_addr); ++ mipv6_ha_ac_add(ll_addr, ifindex, glob_addr, plen); ++ } else ++ ipv6_addr_set(&entry->link_local_addr, 0, 0, 0, 0); ++ ++ ipv6_addr_copy(&entry->global_addr, glob_addr); ++ entry->plen = plen; ++ entry->preference = mpref; ++ entry->expire = jiffies + lifetime * HZ; ++ ret = hashlist_add(home_agents.entries, glob_addr, mpref, ++ entry); ++ } ++out: ++ write_unlock(&home_agents_lock); ++ return ret; ++} ++ ++/** ++ * mipv6_halist_delete - delete home agent from Home Agents List ++ * @glob_addr: home agent's global address ++ * ++ * Deletes entry for home agent @glob_addr from the Home Agent List. ++ **/ ++int mipv6_halist_delete(struct in6_addr *glob_addr) ++{ ++ struct hashlist_entry *e; ++ struct mipv6_halist_entry *entry; ++ DEBUG_FUNC(); ++ ++ if (glob_addr == NULL) { ++ DEBUG(DBG_WARNING, "invalid glob addr"); ++ return -EINVAL; ++ } ++ write_lock(&home_agents_lock); ++ if ((e = hashlist_get(home_agents.entries, glob_addr)) == NULL) { ++ write_unlock(&home_agents_lock); ++ return -ENOENT; ++ } ++ hashlist_delete(home_agents.entries, e); ++ entry = (struct mipv6_halist_entry *)e; ++ if (!ipv6_addr_any(&entry->link_local_addr)) { ++ mipv6_ha_ac_del(&entry->link_local_addr, entry->ifindex, ++ &entry->global_addr, entry->plen); ++ } ++ hashlist_free(home_agents.entries, e); ++ write_unlock(&home_agents_lock); ++ return 0; ++} ++ ++/** ++ * mipv6_ha_get_pref_list - Get list of preferred home agents ++ * @ifindex: interface identifier ++ * @addrs: pointer to a buffer to store the list ++ * @max: maximum number of home agents to return ++ * ++ * Creates a list of @max preferred (or all known if less than @max) ++ * home agents. Home Agents List is interface specific so you must ++ * supply @ifindex. Stores list in addrs and returns number of home ++ * agents stored. On failure, returns a negative value. ++ **/ ++int mipv6_ha_get_pref_list(int ifindex, struct in6_addr **addrs, int max) ++{ ++ struct preflist_iterator_args args; ++ ++ DEBUG_FUNC(); ++ if (max <= 0) { ++ *addrs = NULL; ++ return 0; ++ } ++ ++ args.count = 0; ++ args.requested = max; ++ args.ifindex = ifindex; ++ args.list = kmalloc(max * sizeof(struct in6_addr), GFP_ATOMIC); ++ ++ if (args.list == NULL) return -ENOMEM; ++ ++ read_lock(&home_agents_lock); ++ hashlist_iterate(home_agents.entries, &args, preflist_iterator); ++ read_unlock(&home_agents_lock); ++ ++ if (args.count >= 0) { ++ *addrs = args.list; ++ } else { ++ kfree(args.list); ++ *addrs = NULL; ++ } ++ ++ return args.count; ++} ++ ++struct getaddr_iterator_args { ++ struct net_device *dev; ++ struct in6_addr *addr; ++}; ++ ++static int getaddr_iterator(void *data, void *args, ++ unsigned long *pref) ++{ ++ struct mipv6_halist_entry *entry = ++ (struct mipv6_halist_entry *)data; ++ struct getaddr_iterator_args *state = ++ (struct getaddr_iterator_args *)args; ++ ++ if (entry->ifindex != state->dev->ifindex) ++ return ITERATOR_CONT; ++ ++ if (ipv6_chk_addr(&entry->global_addr, state->dev)) { ++ ipv6_addr_copy(state->addr, &entry->global_addr); ++ return ITERATOR_STOP; ++ } ++ return ITERATOR_CONT; ++} ++ ++/* ++ * Get Home Agent Address for given interface. If node is not serving ++ * as a HA for this interface returns negative error value. ++ */ ++int mipv6_ha_get_addr(int ifindex, struct in6_addr *addr) ++{ ++ struct getaddr_iterator_args args; ++ struct net_device *dev; ++ ++ if (ifindex <= 0) ++ return -EINVAL; ++ ++ if ((dev = dev_get_by_index(ifindex)) == NULL) ++ return -ENODEV; ++ ++ memset(addr, 0, sizeof(struct in6_addr)); ++ args.dev = dev; ++ args.addr = addr; ++ read_lock(&home_agents_lock); ++ hashlist_iterate(home_agents.entries, &args, getaddr_iterator); ++ read_unlock(&home_agents_lock); ++ dev_put(dev); ++ ++ if (ipv6_addr_any(addr)) ++ return -ENOENT; ++ ++ return 0; ++} ++ ++#define HALIST_INFO_LEN 81 ++ ++struct procinfo_iterator_args { ++ char *buffer; ++ int offset; ++ int length; ++ int skip; ++ int len; ++}; ++ ++static int procinfo_iterator(void *data, void *args, ++ unsigned long *pref) ++{ ++ struct procinfo_iterator_args *arg = ++ (struct procinfo_iterator_args *)args; ++ struct mipv6_halist_entry *entry = ++ (struct mipv6_halist_entry *)data; ++ unsigned long int expire; ++ ++ DEBUG_FUNC(); ++ ++ if (entry == NULL) return ITERATOR_ERR; ++ ++ if (time_after(jiffies, entry->expire)) { ++ if (!ipv6_addr_any(&entry->link_local_addr)) { ++ mipv6_ha_ac_del(&entry->link_local_addr, ++ entry->ifindex, ++ &entry->global_addr, entry->plen); ++ } ++ return ITERATOR_DELETE_ENTRY; ++ } ++ if (arg->skip < arg->offset / HALIST_INFO_LEN) { ++ arg->skip++; ++ return ITERATOR_CONT; ++ } ++ ++ if (arg->len >= arg->length) ++ return ITERATOR_CONT; ++ ++ expire = (entry->expire - jiffies) / HZ; ++ ++ arg->len += sprintf(arg->buffer + arg->len, ++ "%02d %08x%08x%08x%08x %08x%08x%08x%08x %05ld %05ld\n", ++ entry->ifindex, ++ ntohl(entry->global_addr.s6_addr32[0]), ++ ntohl(entry->global_addr.s6_addr32[1]), ++ ntohl(entry->global_addr.s6_addr32[2]), ++ ntohl(entry->global_addr.s6_addr32[3]), ++ ntohl(entry->link_local_addr.s6_addr32[0]), ++ ntohl(entry->link_local_addr.s6_addr32[1]), ++ ntohl(entry->link_local_addr.s6_addr32[2]), ++ ntohl(entry->link_local_addr.s6_addr32[3]), ++ -(entry->preference - PREF_BASE), expire); ++ ++ return ITERATOR_CONT; ++} ++ ++static int halist_proc_info(char *buffer, char **start, off_t offset, ++ int length) ++{ ++ struct procinfo_iterator_args args; ++ ++ DEBUG_FUNC(); ++ ++ args.buffer = buffer; ++ args.offset = offset; ++ args.length = length; ++ args.skip = 0; ++ args.len = 0; ++ ++ read_lock_bh(&home_agents_lock); ++ hashlist_iterate(home_agents.entries, &args, procinfo_iterator); ++ read_unlock_bh(&home_agents_lock); ++ ++ *start = buffer; ++ if (offset) ++ *start += offset % HALIST_INFO_LEN; ++ ++ args.len -= offset % HALIST_INFO_LEN; ++ ++ if (args.len > length) ++ args.len = length; ++ if (args.len < 0) ++ args.len = 0; ++ ++ return args.len; ++} ++ ++static int halist_compare(void *data, void *hashkey) ++{ ++ struct mipv6_halist_entry *e = (struct mipv6_halist_entry *)data; ++ struct in6_addr *key = (struct in6_addr *)hashkey; ++ ++ return ipv6_addr_cmp(&e->global_addr, key); ++} ++ ++static __u32 halist_hash(void *hashkey) ++{ ++ struct in6_addr *key = (struct in6_addr *)hashkey; ++ __u32 hash; ++ ++ hash = key->s6_addr32[0] ^ ++ key->s6_addr32[1] ^ ++ key->s6_addr32[2] ^ ++ key->s6_addr32[3]; ++ ++ return hash; ++} ++ ++int __init mipv6_halist_init(__u32 size) ++{ ++ DEBUG_FUNC(); ++ ++ if (size <= 0) { ++ DEBUG(DBG_ERROR, "size must be at least 1"); ++ return -EINVAL; ++ } ++ init_timer(&home_agents.expire_timer); ++ home_agents.expire_timer.data = 0; ++ home_agents.expire_timer.function = mipv6_halist_expire; ++ home_agents_lock = RW_LOCK_UNLOCKED; ++ ++ home_agents.entries = hashlist_create(16, size, sizeof(struct mipv6_halist_entry), ++ "mip6_halist", NULL, NULL, ++ halist_compare, halist_hash); ++ ++ if (home_agents.entries == NULL) { ++ DEBUG(DBG_ERROR, "Failed to initialize hashlist"); ++ return -ENOMEM; ++ } ++ ++ proc_net_create("mip6_home_agents", 0, halist_proc_info); ++ DEBUG(DBG_INFO, "Home Agents List initialized"); ++ return 0; ++} ++ ++void __exit mipv6_halist_exit(void) ++{ ++ DEBUG_FUNC(); ++ proc_net_remove("mip6_home_agents"); ++ write_lock_bh(&home_agents_lock); ++ DEBUG(DBG_INFO, "Stopping the halist timer"); ++ del_timer(&home_agents.expire_timer); ++ mipv6_halist_gc(1); ++ write_unlock_bh(&home_agents_lock); ++ hashlist_destroy(home_agents.entries); ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/halist.h linux-2.4.25/net/ipv6/mobile_ip6/halist.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/halist.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/halist.h 2004-06-26 11:29:30.000000000 +0100 +@@ -0,0 +1,28 @@ ++/* ++ * MIPL Mobile IPv6 Home Agents List header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _HALIST_H ++#define _HALIST_H ++ ++int mipv6_halist_init(__u32 size); ++ ++void mipv6_halist_exit(void); ++ ++int mipv6_halist_add(int ifindex, struct in6_addr *glob_addr, int plen, ++ struct in6_addr *ll_addr, unsigned int pref, __u32 lifetime); ++ ++int mipv6_halist_delete(struct in6_addr *glob_addr); ++ ++int mipv6_ha_get_pref_list(int ifindex, struct in6_addr **addrs, int max); ++ ++int mipv6_ha_get_addr(int ifindex, struct in6_addr *addr); ++ ++#endif /* _HALIST_H */ +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/hashlist.c linux-2.4.25/net/ipv6/mobile_ip6/hashlist.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/hashlist.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/hashlist.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,351 @@ ++/* ++ * Generic hashtable with chaining. Supports secodary sort order ++ * with doubly linked-list. ++ * ++ * Authors: ++ * Sami Kivisaari ++ * Antti Tuominen ++ * ++ * $Id: s.hashlist.c 1.21 02/10/07 19:31:52+03:00 antti@traci.mipl.mediapoli.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. ++ */ ++ ++#include ++#include "hashlist.h" ++#include "debug.h" ++ ++struct hashlist { ++ int count; /* entry count */ ++ int maxcount; /* max entries */ ++ __u32 bucketnum; /* hash buckets */ ++ ++ kmem_cache_t *kmem; ++ ++ struct list_head *hashtable; ++ struct list_head sortedlist; ++ ++ int (*compare)(void *data, void *hashkey); ++ __u32 (*hash_function)(void *hashkey); ++}; ++ ++/** ++ * hashlist_create - Create new hashlist ++ * @bucketnum: number of hash buckets ++ * @maxentries: maximum number of entries (0 = no limit) ++ * @size: entry size in bytes ++ * @name: name for kmem_cache_t ++ * @ctor: kmem_cache_t constructor ++ * @dtor: kmem_cache_t destructor ++ * @compare: compare function for key ++ * @hash_function: hash function ++ * ++ * Creates a hashlist structure with @max_entries entries of @size ++ * bytes. User must supply @hash_function and @compare function for ++ * the hashlist. User can also supply @ctor and @dtor for kmem_cache. ++ **/ ++struct hashlist *hashlist_create(int bucketnum, int max_entries, size_t size, ++ char *name, ++ void (*ctor)(void *, kmem_cache_t *, unsigned long), ++ void (*dtor)(void *, kmem_cache_t *, unsigned long), ++ int (*compare)(void *data, void *hashkey), ++ __u32 (*hash_function)(void *hashkey)) ++{ ++ int i; ++ struct hashlist *hl; ++ ++ if (!compare || !hash_function) ++ goto hlfailed; ++ ++ hl = kmalloc(sizeof(struct hashlist), GFP_ATOMIC); ++ if (!hl) goto hlfailed; ++ ++ hl->kmem = kmem_cache_create(name, size, 0, 0, ctor, dtor); ++ if (!hl->kmem) goto poolfailed; ++ ++ hl->hashtable = kmalloc( ++ sizeof(struct list_head) * bucketnum, GFP_ATOMIC); ++ if (!hl->hashtable) goto hashfailed; ++ ++ for (i = 0; i < bucketnum; i++) ++ INIT_LIST_HEAD(&hl->hashtable[i]); ++ ++ INIT_LIST_HEAD(&hl->sortedlist); ++ ++ hl->maxcount = max_entries; ++ hl->count = 0; ++ hl->bucketnum = bucketnum; ++ hl->compare = compare; ++ hl->hash_function = hash_function; ++ ++ return hl; ++ ++hashfailed: ++ kmem_cache_destroy(hl->kmem); ++ hl->kmem = NULL; ++ ++poolfailed: ++ kfree(hl); ++ ++hlfailed: ++ DEBUG(DBG_ERROR, "could not create hashlist"); ++ ++ return NULL; ++} ++ ++/** ++ * hashlist_destroy - Destroy hashlist ++ * @hashlist: hashlist to destroy ++ * ++ * Frees all memory allocated for a hashlist. ++ **/ ++void hashlist_destroy(struct hashlist *hashlist) ++{ ++ DEBUG_FUNC(); ++ ++ if (hashlist == NULL) return; ++ ++ if (hashlist->hashtable) { ++ kfree(hashlist->hashtable); ++ hashlist->hashtable = NULL; ++ } ++ ++ if (hashlist->kmem) { ++ kmem_cache_destroy(hashlist->kmem); ++ hashlist->kmem = NULL; ++ } ++ ++ kfree(hashlist); ++ ++ return; ++} ++ ++/* ++ * Insert a chain of entries to hashlist into correct order. The ++ * entries are assumed to have valid hashkeys. We use time_after_eq ++ * for comparing, since it handles wrap-around correctly, and the ++ * sortkey is usually jiffies. ++ */ ++static void sorted_insert(struct list_head *lh, struct hashlist_entry *he) ++{ ++ struct list_head *p; ++ struct hashlist_entry *hlp = NULL; ++ unsigned long sortkey = he->sortkey; ++ ++ if (list_empty(lh)) { ++ list_add(&he->sorted, lh); ++ return; ++ } ++ ++ list_for_each(p, lh) { ++ hlp = list_entry(p, typeof(*hlp), sorted); ++ if (time_after_eq(hlp->sortkey, sortkey)) { ++ list_add(&he->sorted, hlp->sorted.prev); ++ return; ++ } ++ } ++ list_add(&he->sorted, &hlp->sorted); ++} ++ ++/** ++ * hashlist_iterate - Apply function for all elements in a hash list ++ * @hashlist: pointer to hashlist ++ * @args: data to pass to the function ++ * @func: pointer to a function ++ * ++ * Apply arbitrary function @func to all elements in a hash list. ++ * @func must be a pointer to a function with the following prototype: ++ * int func(void *entry, void *arg, struct in6_addr *hashkey, unsigned ++ * long *sortkey). Function must return %ITERATOR_STOP, ++ * %ITERATOR_CONT or %ITERATOR_DELETE_ENTRY. %ITERATOR_STOP stops ++ * iterator and returns last return value from the function. ++ * %ITERATOR_CONT continues with iteration. %ITERATOR_DELETE_ENTRY ++ * deletes current entry from the hashlist. If function changes ++ * hashlist element's sortkey, iterator automatically schedules ++ * element to be reinserted after all elements have been processed. ++ */ ++int hashlist_iterate( ++ struct hashlist *hashlist, void *args, ++ hashlist_iterator_t func) ++{ ++ int res = ITERATOR_CONT; ++ unsigned long skey; ++ struct list_head *p, *n, repos; ++ struct hashlist_entry *he; ++ ++ DEBUG_FUNC(); ++ INIT_LIST_HEAD(&repos); ++ ++ list_for_each_safe(p, n, &hashlist->sortedlist) { ++ he = list_entry(p, typeof(*he), sorted); ++ if (res == ITERATOR_STOP) ++ break; ++ skey = he->sortkey; ++ res = func(he, args, &he->sortkey); ++ if (res == ITERATOR_DELETE_ENTRY) { ++ hashlist_delete(hashlist, he); ++ hashlist_free(hashlist, he); ++ } else if (skey != he->sortkey) { ++ /* iterator changed the sortkey, schedule for ++ * repositioning */ ++ list_move(&he->sorted, &repos); ++ } ++ } ++ list_for_each_safe(p, n, &repos) { ++ he = list_entry(p, typeof(*he), sorted); ++ sorted_insert(&hashlist->sortedlist, he); ++ } ++ return res; ++} ++ ++/** ++ * hashlist_alloc - Allocate memory for a hashlist entry ++ * @hashlist: hashlist for allocated entry ++ * @size: size of entry in bytes ++ * ++ * Allocates @size bytes memory from @hashlist->kmem. ++ **/ ++void *hashlist_alloc(struct hashlist *hashlist, int type) ++{ ++ if (hashlist == NULL) return NULL; ++ return kmem_cache_alloc(hashlist->kmem, type); ++} ++ ++/** ++ * hashlist_free - Free hashlist entry ++ * @hashlist: hashlist where @he is ++ * @he: entry to free ++ * ++ * Frees an allocated hashlist entry. ++ **/ ++void hashlist_free(struct hashlist *hashlist, struct hashlist_entry *he) ++{ ++ kmem_cache_free(hashlist->kmem, he); ++} ++ ++/** ++ * hashlist_add - Add element to hashlist ++ * @hashlist: pointer to hashlist ++ * @hashkey: hashkey for the element ++ * @sortkey: key for sorting ++ * @data: element data ++ * ++ * Add element to hashlist. Hashlist is also sorted in a linked list ++ * by @sortkey. ++ */ ++int hashlist_add(struct hashlist *hashlist, void *hashkey, ++ unsigned long sortkey, void *entry) ++{ ++ struct hashlist_entry *he = (struct hashlist_entry *)entry; ++ unsigned int hash; ++ ++ if (hashlist->count >= hashlist->maxcount) ++ return -1; ++ ++ hashlist->count++; ++ ++ /* link the entry to sorted order */ ++ he->sortkey = sortkey; ++ sorted_insert(&hashlist->sortedlist, he); ++ ++ /* hash the entry */ ++ hash = hashlist->hash_function(hashkey) % hashlist->bucketnum; ++ list_add(&he->hashlist, &hashlist->hashtable[hash]); ++ ++ return 0; ++} ++ ++/** ++ * hashlist_get_ex - Get element from hashlist ++ * @hashlist: hashlist ++ * @hashkey: hashkey of the desired entry ++ * ++ * Lookup entry with @hashkey from the hash table using @compare ++ * function for entry comparison. Returns entry on success, otherwise ++ * %NULL. ++ **/ ++struct hashlist_entry *hashlist_get_ex( ++ struct hashlist *hashlist, void *hashkey, ++ int (*compare)(void *data, void *hashkey)) ++{ ++ struct list_head *p, *bkt; ++ __u32 hash; ++ ++ hash = hashlist->hash_function(hashkey) % hashlist->bucketnum; ++ bkt = &hashlist->hashtable[hash]; ++ ++ /* scan the entries within the same hashbucket */ ++ list_for_each(p, bkt) { ++ struct hashlist_entry *he = list_entry(p, typeof(*he), ++ hashlist); ++ if (compare(he, hashkey) == 0) ++ return he; ++ } ++ ++ return NULL; ++} ++ ++/** ++ * hashlist_get - Get element from hashlist ++ * @hashlist: hashlist ++ * @hashkey: hashkey of the desired entry ++ * ++ * Lookup entry with @hashkey from the hash table. Returns entry on ++ * success, otherwise %NULL. ++ **/ ++struct hashlist_entry *hashlist_get(struct hashlist *hashlist, void *hashkey) ++{ ++ return hashlist_get_ex(hashlist, hashkey, hashlist->compare); ++} ++ ++/** ++ * hashlist_reposition - set entry to new position in the list ++ * @hashlist: hashlist ++ * @he: entry to reposition ++ * @sortkey: new sortkey of the entry ++ * ++ * If secondary order sortkey changes, entry must be repositioned in ++ * the sorted list. ++ **/ ++int hashlist_reposition(struct hashlist *hashlist, struct hashlist_entry *he, ++ unsigned long sortkey) ++{ ++ list_del(&he->sorted); ++ he->sortkey = sortkey; ++ sorted_insert(&hashlist->sortedlist, he); ++ ++ return 0; ++} ++ ++/** ++ * hashlist_delete - Delete entry from hashlist ++ * @hashlist: hashlist where entry is ++ * @he: entry to delete ++ * ++ * Deletes an entry from the hashlist and sorted list. ++ **/ ++void hashlist_delete(struct hashlist *hashlist, ++ struct hashlist_entry *he) ++{ ++ list_del_init(&he->hashlist); ++ list_del_init(&he->sorted); ++ ++ hashlist->count--; ++} ++ ++/** ++ * hashlist_get_first - Get first item from sorted list ++ * @hashlist: pointer to hashlist ++ * ++ * Returns first item in the secondary sort order. ++ **/ ++void * hashlist_get_first(struct hashlist *hashlist) ++{ ++ if (list_empty(&hashlist->sortedlist)) ++ return NULL; ++ ++ return list_entry(hashlist->sortedlist.next, struct hashlist_entry, sorted); ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/hashlist.h linux-2.4.25/net/ipv6/mobile_ip6/hashlist.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/hashlist.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/hashlist.h 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,63 @@ ++/* ++ * MIPL Mobile IPv6 Hashlist header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _HASHLIST_H ++#define _HASHLIST_H ++ ++#define ITERATOR_ERR -1 ++#define ITERATOR_CONT 0 ++#define ITERATOR_STOP 1 ++#define ITERATOR_DELETE_ENTRY 2 ++ ++struct kmem_cache_t; ++ ++struct hashlist_entry { ++ unsigned long sortkey; ++ struct list_head sorted; ++ struct list_head hashlist; ++}; ++ ++struct hashlist * hashlist_create( ++ int bucketnum, int max_entries, size_t size, char *name, ++ void (*ctor)(void *, kmem_cache_t *, unsigned long), ++ void (*dtor)(void *, kmem_cache_t *, unsigned long), ++ int (*compare)(void *data, void *hashkey), ++ __u32 (*hash_function)(void *hashkey)); ++ ++void hashlist_destroy(struct hashlist *hashlist); ++ ++void *hashlist_alloc(struct hashlist *hashlist, int type); ++ ++void hashlist_free(struct hashlist *hashlist, struct hashlist_entry *he); ++ ++struct hashlist_entry *hashlist_get(struct hashlist *hashlist, void *hashkey); ++ ++struct hashlist_entry *hashlist_get_ex( ++ struct hashlist *hashlist, void *hashkey, ++ int (*compare)(void *data, void *hashkey)); ++ ++int hashlist_add(struct hashlist *hashlist, void *hashkey, ++ unsigned long sortkey, void *data); ++ ++void hashlist_delete(struct hashlist *hashlist, struct hashlist_entry *he); ++ ++/* iterator function */ ++typedef int (*hashlist_iterator_t)(void *, void *, unsigned long *); ++ ++int hashlist_iterate(struct hashlist *hashlist, void *args, ++ hashlist_iterator_t func); ++ ++void * hashlist_get_first(struct hashlist *hashlist); ++ ++int hashlist_reposition(struct hashlist *hashlist, struct hashlist_entry *he, ++ unsigned long sortkey); ++ ++#endif +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/hmac.c linux-2.4.25/net/ipv6/mobile_ip6/hmac.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/hmac.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/hmac.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,658 @@ ++/* Authentication algorithms ++ * ++ * Authors: ++ * Alexis Olivereau ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ * Changes: ++ * Henrik Petander : Cleaned up unused parts ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "hmac.h" ++#define LROLL(x, s) (((x) << (s)) | ((x) >> (32 - (s)))) ++ ++/* MD5 */ ++#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) ++#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) ++#define H(x, y, z) ((x) ^ (y) ^ (z)) ++#define I(x, y, z) ((y) ^ ((x) | ~(z))) ++ ++#define FF(a, b, c, d, m, s, t) { \ ++ (a) += F ((b), (c), (d)) + (m) + (t); \ ++ (a) = LROLL((a), (s)); \ ++ (a) += (b); \ ++ } ++#define GG(a, b, c, d, m, s, t) { \ ++ (a) += G ((b), (c), (d)) + (m) + (t); \ ++ (a) = LROLL((a), (s)); \ ++ (a) += (b); \ ++ } ++#define HH(a, b, c, d, m, s, t) { \ ++ (a) += H ((b), (c), (d)) + (m) + (t); \ ++ (a) = LROLL((a), (s)); \ ++ (a) += (b); \ ++ } ++#define II(a, b, c, d, m, s, t) { \ ++ (a) += I ((b), (c), (d)) + (m) + (t); \ ++ (a) = LROLL((a), (s)); \ ++ (a) += (b); \ ++ } ++ ++#define s11 7 ++#define s12 12 ++#define s13 17 ++#define s14 22 ++#define s21 5 ++#define s22 9 ++#define s23 14 ++#define s24 20 ++#define s31 4 ++#define s32 11 ++#define s33 16 ++#define s34 23 ++#define s41 6 ++#define s42 10 ++#define s43 15 ++#define s44 21 ++ ++/* SHA-1 */ ++#define f(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) ++#define g(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) ++#define h(x, y, z) ((x) ^ (y) ^ (z)) ++ ++#define K1 0x5a827999 ++#define K2 0x6ed9eba1 ++#define K3 0x8f1bbcdc ++#define K4 0xca62c1d6 ++ ++int ah_hmac_md5_init(struct ah_processing *ahp, u_int8_t *key, u_int32_t key_len) ++{ ++ int i; ++ int key_up4; ++ uint32_t ipad = 0x36363636; ++ uint8_t extkey[64]; ++ ++ ahp->key_auth = key; ++ ahp->key_auth_len = key_len; ++ ahp->context = (void *) kmalloc(sizeof(MD5_CTX), GFP_ATOMIC); ++ if (ahp->context == NULL) ++ return -1; ++ md5_init((MD5_CTX *) ahp->context); ++ if ((64 * sizeof(uint8_t)) < ahp->key_auth_len) { ++ printk("buffer overflow!"); ++ return -1; ++ } ++ memcpy(extkey, ahp->key_auth, ahp->key_auth_len); ++ if (ahp->key_auth_len % 4) { ++ memset(extkey + ahp->key_auth_len, 0, ++ 4 - (ahp->key_auth_len % 4)); ++ } ++ key_up4 = ((ahp->key_auth_len + 0x3) & 0xFFFFFFFC) / 4; ++ ++ for (i = 0; i < key_up4; i++) ++ ((uint32_t *) extkey)[i] = ((uint32_t *) extkey)[i] ^ ipad; ++ for (i = key_up4; i < 16; i++) ++ ((uint32_t *) extkey)[i] = ipad; ++ ++ md5_compute((MD5_CTX *) ahp->context, extkey, 64); ++ return 0; ++} ++ ++void ah_hmac_md5_loop(struct ah_processing *ahp, void *str, uint32_t len) ++{ ++ md5_compute((MD5_CTX *) ahp->context, str, len); ++} ++ ++void ah_hmac_md5_result(struct ah_processing *ahp, char *digest) ++{ ++ uint8_t inner[HMAC_MD5_HASH_LEN]; ++ int i; ++ int key_up4; ++ uint32_t opad = 0x5c5c5c5c; ++ uint8_t extkey[64]; ++ ++ md5_final((MD5_CTX *) ahp->context, inner); ++ md5_init((MD5_CTX *) ahp->context); ++ ++ memcpy(extkey, ahp->key_auth, ahp->key_auth_len); ++ if (ahp->key_auth_len % 4) { ++ memset(extkey + ahp->key_auth_len, 0, ++ 4 - (ahp->key_auth_len % 4)); ++ } ++ key_up4 = ((ahp->key_auth_len + 0x3) & 0xFFFFFFFC) / 4; ++ ++ for (i = 0; i < key_up4; i++) ++ ((uint32_t *) extkey)[i] = ((uint32_t *) extkey)[i] ^ opad; ++ for (i = key_up4; i < 16; i++) ++ ((uint32_t *) extkey)[i] = opad; ++ ++ md5_compute((MD5_CTX *) ahp->context, extkey, 64); ++ md5_compute((MD5_CTX *) ahp->context, inner, HMAC_MD5_HASH_LEN); ++ ++ md5_final((MD5_CTX *) ahp->context, digest); ++ ++ kfree(ahp->context); ++} ++ ++int ah_hmac_sha1_init(struct ah_processing *ahp, u_int8_t *key, u_int32_t key_len) ++{ ++ int i; ++ int key_up4; ++ uint32_t ipad = 0x36363636; ++ uint8_t extkey[64]; ++ ++ ahp->key_auth = key; ++ ahp->key_auth_len = key_len; ++ ++ ahp->context = (void *) kmalloc(sizeof(SHA1_CTX), GFP_ATOMIC); ++ //if (ahp->context == NULL) ++ // return -1; ++ ++ sha1_init((SHA1_CTX *) ahp->context); ++ ++ memcpy(extkey, ahp->key_auth, ahp->key_auth_len); ++ if (ahp->key_auth_len % 4) { ++ memset(extkey + ahp->key_auth_len, 0, ++ 4 - (ahp->key_auth_len % 4)); ++ } ++ key_up4 = ((ahp->key_auth_len + 0x3) & 0xFFFFFFFC) / 4; ++ ++ for (i = 0; i < key_up4; i++) ++ ((uint32_t *) extkey)[i] = ((uint32_t *) extkey)[i] ^ ipad; ++ for (i = key_up4; i < 16; i++) ++ ((uint32_t *) extkey)[i] = ipad; ++ ++ sha1_compute((SHA1_CTX *) ahp->context, extkey, 64); ++ return 0; ++} ++ ++void ah_hmac_sha1_loop(struct ah_processing *ahp, void *str, uint32_t len) ++{ ++ if (!ahp) ++ return; ++ sha1_compute((SHA1_CTX *) ahp->context, str, len); ++} ++ ++void ah_hmac_sha1_result(struct ah_processing *ahp, char *digest) ++{ ++ uint8_t inner[HMAC_SHA1_HASH_LEN]; ++ int i; ++ int key_up4; ++ uint32_t opad = 0x5c5c5c5c; ++ uint8_t extkey[64]; ++ ++ if (!ahp) ++ return; ++ sha1_final((SHA1_CTX *) ahp->context, inner); ++ sha1_init((SHA1_CTX *) ahp->context); ++ ++ memcpy(extkey, ahp->key_auth, ahp->key_auth_len); ++ if (ahp->key_auth_len % 4) { ++ memset(extkey + ahp->key_auth_len, 0, ++ 4 - (ahp->key_auth_len % 4)); ++ } ++ key_up4 = ((ahp->key_auth_len + 0x3) & 0xFFFFFFFC) / 4; ++ ++ for (i = 0; i < key_up4; i++) ++ ((uint32_t *) extkey)[i] = ((uint32_t *) extkey)[i] ^ opad; ++ for (i = key_up4; i < 16; i++) ++ ((uint32_t *) extkey)[i] = opad; ++ ++ sha1_compute((SHA1_CTX *) ahp->context, extkey, 64); ++ sha1_compute((SHA1_CTX *) ahp->context, inner, ++ HMAC_SHA1_HASH_LEN); ++ ++ sha1_final((SHA1_CTX *) ahp->context, digest); ++ ++ kfree(ahp->context); ++} ++ ++void md5_init(MD5_CTX * ctx) ++{ ++ ctx->A = 0x67452301; ++ ctx->B = 0xefcdab89; ++ ctx->C = 0x98badcfe; ++ ctx->D = 0x10325476; ++ ctx->buf_cur = ctx->buf; ++ ctx->bitlen[0] = ctx->bitlen[1] = 0; ++ memset(ctx->buf, 0, 64); ++} ++ ++void md5_over_block(MD5_CTX * ctx, uint8_t * data) ++{ ++ uint32_t M[16]; ++ uint32_t a = ctx->A; ++ uint32_t b = ctx->B; ++ uint32_t c = ctx->C; ++ uint32_t d = ctx->D; ++ ++ create_M_blocks(M, data); ++ ++ /* Round 1 */ ++ FF(a, b, c, d, M[0], s11, 0xd76aa478); /* 1 */ ++ FF(d, a, b, c, M[1], s12, 0xe8c7b756); /* 2 */ ++ FF(c, d, a, b, M[2], s13, 0x242070db); /* 3 */ ++ FF(b, c, d, a, M[3], s14, 0xc1bdceee); /* 4 */ ++ FF(a, b, c, d, M[4], s11, 0xf57c0faf); /* 5 */ ++ FF(d, a, b, c, M[5], s12, 0x4787c62a); /* 6 */ ++ FF(c, d, a, b, M[6], s13, 0xa8304613); /* 7 */ ++ FF(b, c, d, a, M[7], s14, 0xfd469501); /* 8 */ ++ FF(a, b, c, d, M[8], s11, 0x698098d8); /* 9 */ ++ FF(d, a, b, c, M[9], s12, 0x8b44f7af); /* 10 */ ++ FF(c, d, a, b, M[10], s13, 0xffff5bb1); /* 11 */ ++ FF(b, c, d, a, M[11], s14, 0x895cd7be); /* 12 */ ++ FF(a, b, c, d, M[12], s11, 0x6b901122); /* 13 */ ++ FF(d, a, b, c, M[13], s12, 0xfd987193); /* 14 */ ++ FF(c, d, a, b, M[14], s13, 0xa679438e); /* 15 */ ++ FF(b, c, d, a, M[15], s14, 0x49b40821); /* 16 */ ++ ++ /* Round 2 */ ++ GG(a, b, c, d, M[1], s21, 0xf61e2562); /* 17 */ ++ GG(d, a, b, c, M[6], s22, 0xc040b340); /* 18 */ ++ GG(c, d, a, b, M[11], s23, 0x265e5a51); /* 19 */ ++ GG(b, c, d, a, M[0], s24, 0xe9b6c7aa); /* 20 */ ++ GG(a, b, c, d, M[5], s21, 0xd62f105d); /* 21 */ ++ GG(d, a, b, c, M[10], s22, 0x02441453); /* 22 */ ++ GG(c, d, a, b, M[15], s23, 0xd8a1e681); /* 23 */ ++ GG(b, c, d, a, M[4], s24, 0xe7d3fbc8); /* 24 */ ++ GG(a, b, c, d, M[9], s21, 0x21e1cde6); /* 25 */ ++ GG(d, a, b, c, M[14], s22, 0xc33707d6); /* 26 */ ++ GG(c, d, a, b, M[3], s23, 0xf4d50d87); /* 27 */ ++ GG(b, c, d, a, M[8], s24, 0x455a14ed); /* 28 */ ++ GG(a, b, c, d, M[13], s21, 0xa9e3e905); /* 29 */ ++ GG(d, a, b, c, M[2], s22, 0xfcefa3f8); /* 30 */ ++ GG(c, d, a, b, M[7], s23, 0x676f02d9); /* 31 */ ++ GG(b, c, d, a, M[12], s24, 0x8d2a4c8a); /* 32 */ ++ ++ /* Round 3 */ ++ HH(a, b, c, d, M[5], s31, 0xfffa3942); /* 33 */ ++ HH(d, a, b, c, M[8], s32, 0x8771f681); /* 34 */ ++ HH(c, d, a, b, M[11], s33, 0x6d9d6122); /* 35 */ ++ HH(b, c, d, a, M[14], s34, 0xfde5380c); /* 36 */ ++ HH(a, b, c, d, M[1], s31, 0xa4beea44); /* 37 */ ++ HH(d, a, b, c, M[4], s32, 0x4bdecfa9); /* 38 */ ++ HH(c, d, a, b, M[7], s33, 0xf6bb4b60); /* 39 */ ++ HH(b, c, d, a, M[10], s34, 0xbebfbc70); /* 40 */ ++ HH(a, b, c, d, M[13], s31, 0x289b7ec6); /* 41 */ ++ HH(d, a, b, c, M[0], s32, 0xeaa127fa); /* 42 */ ++ HH(c, d, a, b, M[3], s33, 0xd4ef3085); /* 43 */ ++ HH(b, c, d, a, M[6], s34, 0x4881d05); /* 44 */ ++ HH(a, b, c, d, M[9], s31, 0xd9d4d039); /* 45 */ ++ HH(d, a, b, c, M[12], s32, 0xe6db99e5); /* 46 */ ++ HH(c, d, a, b, M[15], s33, 0x1fa27cf8); /* 47 */ ++ HH(b, c, d, a, M[2], s34, 0xc4ac5665); /* 48 */ ++ ++ /* Round 4 */ ++ II(a, b, c, d, M[0], s41, 0xf4292244); /* 49 */ ++ II(d, a, b, c, M[7], s42, 0x432aff97); /* 50 */ ++ II(c, d, a, b, M[14], s43, 0xab9423a7); /* 51 */ ++ II(b, c, d, a, M[5], s44, 0xfc93a039); /* 52 */ ++ II(a, b, c, d, M[12], s41, 0x655b59c3); /* 53 */ ++ II(d, a, b, c, M[3], s42, 0x8f0ccc92); /* 54 */ ++ II(c, d, a, b, M[10], s43, 0xffeff47d); /* 55 */ ++ II(b, c, d, a, M[1], s44, 0x85845dd1); /* 56 */ ++ II(a, b, c, d, M[8], s41, 0x6fa87e4f); /* 57 */ ++ II(d, a, b, c, M[15], s42, 0xfe2ce6e0); /* 58 */ ++ II(c, d, a, b, M[6], s43, 0xa3014314); /* 59 */ ++ II(b, c, d, a, M[13], s44, 0x4e0811a1); /* 60 */ ++ II(a, b, c, d, M[4], s41, 0xf7537e82); /* 61 */ ++ II(d, a, b, c, M[11], s42, 0xbd3af235); /* 62 */ ++ II(c, d, a, b, M[2], s43, 0x2ad7d2bb); /* 63 */ ++ II(b, c, d, a, M[9], s44, 0xeb86d391); /* 64 */ ++ ++ ctx->A += a; ++ ctx->B += b; ++ ctx->C += c; ++ ctx->D += d; ++} ++ ++void create_M_blocks(uint32_t * M, uint8_t * data) ++{ ++#ifdef HAVE_LITTLE_ENDIAN ++ memcpy((uint8_t *) M, data, 64); ++#endif /* HAVE_LITTLE_ENDIAN */ ++ ++#ifdef HAVE_BIG_ENDIAN ++ int i; ++ for (i = 0; i < 16; i++, data += 4) { ++ ((uint8_t *) (&M[i]))[0] = data[3]; ++ ((uint8_t *) (&M[i]))[1] = data[2]; ++ ((uint8_t *) (&M[i]))[2] = data[1]; ++ ((uint8_t *) (&M[i]))[3] = data[0]; ++ } ++#endif /* HAVE_BIG_ENDIAN */ ++} ++ ++void md5_compute(MD5_CTX * ctx, uint8_t * data, uint32_t len) ++{ ++ uint8_t pos = ((ctx->bitlen[0] >> 3) & 0x3f); ++ ++ /* First we update the bit length */ ++ if ((ctx->bitlen[0] += (len << 3)) < (len << 3)) ++ ctx->bitlen[1]++; ++ ctx->bitlen[1] += (len >> 29); /* len is expressed in bytes */ ++ ++ if (pos) { ++ /* Buffer is not empty */ ++ if (64 - pos >= len) { ++ memcpy(ctx->buf_cur, data, len); ++ ctx->buf_cur += len; ++ pos += len; ++ if (pos == 64) { ++ /* The current block is over */ ++ md5_over_block(ctx, ctx->buf); ++ ctx->buf_cur = ctx->buf; ++ } ++ return; ++ } else { ++ memcpy(ctx->buf_cur, data, 64 - pos); ++ md5_over_block(ctx, ctx->buf); ++ len -= (64 - pos); ++ data += (64 - pos); ++ ctx->buf_cur = ctx->buf; ++ } ++ } ++ while (len >= 64) { ++ md5_over_block(ctx, data); ++ len -= 64; ++ data += 64; ++ } ++ if (len) { ++ memcpy(ctx->buf_cur, data, len); ++ ctx->buf_cur += len; ++ } ++} ++ ++void md5_final(MD5_CTX * ctx, uint8_t * digest) ++{ ++ uint32_t rem_size; ++ uint8_t *buf_cur = ctx->buf_cur; ++ int i; ++ ++ rem_size = 64 - ((ctx->bitlen[0] >> 3) & 0x3f); ++ *(buf_cur++) = 0x80; ++ ++ if (rem_size > 8 + 1) { ++ /* We have enough room in the current block */ ++ for (i = 0; i < rem_size - 8 - 1; i++) { ++ *(buf_cur++) = 0; ++ } ++ } else { ++ /* We do not have enough room and need therefore to add a new ++ 64-byte block */ ++ for (i = 0; i < rem_size - 1; i++) { ++ *(buf_cur++) = 0; ++ } ++ md5_over_block(ctx, ctx->buf); ++ ++ buf_cur = ctx->buf; ++ for (i = 0; i < 64 - 8; i++) { ++ *(buf_cur++) = 0; ++ } ++ } ++#ifdef HAVE_LITTLE_ENDIAN ++ memcpy(buf_cur, (uint8_t *) ctx->bitlen, 8); ++#endif /* HAVE_LITTLE_ENDIAN */ ++ ++#ifdef HAVE_BIG_ENDIAN ++ *(buf_cur++) = (ctx->bitlen[0] >> 24) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[0] >> 16) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[0] >> 8) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[0] >> 0) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[1] >> 24) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[1] >> 16) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[1] >> 8) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[1] >> 0) & 0xff; ++#endif /* HAVE_BIG_ENDIAN */ ++ ++ md5_over_block(ctx, ctx->buf); ++ ++#ifdef HAVE_LITTLE_ENDIAN ++ memcpy(digest + 0, (uint8_t *) (&(ctx->A)), sizeof(uint32_t)); ++ memcpy(digest + 4, (uint8_t *) (&(ctx->B)), sizeof(uint32_t)); ++ memcpy(digest + 8, (uint8_t *) (&(ctx->C)), sizeof(uint32_t)); ++ memcpy(digest + 12, (uint8_t *) (&(ctx->D)), sizeof(uint32_t)); ++#endif /* HAVE_LITTLE_ENDIAN */ ++ ++#ifdef HAVE_BIG_ENDIAN ++ digest[0] = ((ctx->A) >> 24) & 0xff; ++ digest[1] = ((ctx->A) >> 16) & 0xff; ++ digest[2] = ((ctx->A) >> 8) & 0xff; ++ digest[3] = ((ctx->A) >> 0) & 0xff; ++ digest[4] = ((ctx->B) >> 24) & 0xff; ++ digest[5] = ((ctx->B) >> 16) & 0xff; ++ digest[6] = ((ctx->B) >> 8) & 0xff; ++ digest[7] = ((ctx->B) >> 0) & 0xff; ++ digest[8] = ((ctx->C) >> 24) & 0xff; ++ digest[9] = ((ctx->C) >> 16) & 0xff; ++ digest[10] = ((ctx->C) >> 8) & 0xff; ++ digest[11] = ((ctx->C) >> 0) & 0xff; ++ digest[12] = ((ctx->D) >> 24) & 0xff; ++ digest[13] = ((ctx->D) >> 16) & 0xff; ++ digest[14] = ((ctx->D) >> 8) & 0xff; ++ digest[15] = ((ctx->D) >> 0) & 0xff; ++#endif /* HAVE_BIG_ENDIAN */ ++} ++ ++void sha1_init(SHA1_CTX * ctx) ++{ ++ ctx->A = 0x67452301; ++ ctx->B = 0xefcdab89; ++ ctx->C = 0x98badcfe; ++ ctx->D = 0x10325476; ++ ctx->E = 0xc3d2e1f0; ++ ctx->buf_cur = ctx->buf; ++ ctx->bitlen[0] = ctx->bitlen[1] = 0; ++ memset(ctx->buf, 0, 64); ++} ++ ++void sha1_over_block(SHA1_CTX * ctx, uint8_t * data) ++{ ++ int i; ++ uint32_t W[80]; ++ uint32_t a = ctx->A; ++ uint32_t b = ctx->B; ++ uint32_t c = ctx->C; ++ uint32_t d = ctx->D; ++ uint32_t e = ctx->E; ++ uint32_t temp; ++ ++ create_W_blocks(W, data); ++ ++ /* Round 1 */ ++ for (i = 0; i < 20; i++) { ++ temp = LROLL(a, 5) + f(b, c, d) + e + W[i] + K1; ++ e = d; ++ d = c; ++ c = LROLL(b, 30); ++ b = a; ++ a = temp; ++ } ++ ++ /* Round 2 */ ++ for (i = 20; i < 40; i++) { ++ temp = LROLL(a, 5) + h(b, c, d) + e + W[i] + K2; ++ e = d; ++ d = c; ++ c = LROLL(b, 30); ++ b = a; ++ a = temp; ++ } ++ ++ /* Round 3 */ ++ for (i = 40; i < 60; i++) { ++ temp = LROLL(a, 5) + g(b, c, d) + e + W[i] + K3; ++ e = d; ++ d = c; ++ c = LROLL(b, 30); ++ b = a; ++ a = temp; ++ } ++ ++ /* Round 4 */ ++ for (i = 60; i < 80; i++) { ++ temp = LROLL(a, 5) + h(b, c, d) + e + W[i] + K4; ++ e = d; ++ d = c; ++ c = LROLL(b, 30); ++ b = a; ++ a = temp; ++ } ++ ++ ctx->A += a; ++ ctx->B += b; ++ ctx->C += c; ++ ctx->D += d; ++ ctx->E += e; ++} ++ ++void create_W_blocks(uint32_t * W, uint8_t * data) ++{ ++ int i; ++ ++#ifdef HAVE_BIG_ENDIAN ++ memcpy((uint8_t *) W, data, 64); ++#endif /* HAVE_BIG_ENDIAN */ ++ ++#ifdef HAVE_LITTLE_ENDIAN ++ for (i = 0; i < 16; i++, data += 4) { ++ ((uint8_t *) (&W[i]))[0] = data[3]; ++ ((uint8_t *) (&W[i]))[1] = data[2]; ++ ((uint8_t *) (&W[i]))[2] = data[1]; ++ ((uint8_t *) (&W[i]))[3] = data[0]; ++ } ++#endif /* HAVE_LITTLE_ENDIAN */ ++ for (i = 16; i < 80; i++) { ++ W[i] = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]; ++ W[i] = LROLL(W[i], 1); ++ } ++} ++ ++void sha1_compute(SHA1_CTX * ctx, uint8_t * data, uint32_t len) ++{ ++ uint8_t pos = ((ctx->bitlen[0] >> 3) & 0x3f); ++ ++ /* First we update the bit length */ ++ if ((ctx->bitlen[0] += (len << 3)) < (len << 3)) ++ ctx->bitlen[1]++; ++ ctx->bitlen[1] += (len >> 29); /* len is expressed in bytes */ ++ ++ if (pos) { ++ /* Buffer is not empty */ ++ if (64 - pos >= len) { ++ memcpy(ctx->buf_cur, data, len); ++ ctx->buf_cur += len; ++ pos += len; ++ if (pos == 64) { ++ /* The current block is over */ ++ sha1_over_block(ctx, ctx->buf); ++ ctx->buf_cur = ctx->buf; ++ } ++ return; ++ } else { ++ memcpy(ctx->buf_cur, data, 64 - pos); ++ sha1_over_block(ctx, ctx->buf); ++ len -= (64 - pos); ++ data += (64 - pos); ++ ctx->buf_cur = ctx->buf; ++ } ++ } ++ while (len >= 64) { ++ sha1_over_block(ctx, data); ++ len -= 64; ++ data += 64; ++ } ++ if (len) { ++ memcpy(ctx->buf_cur, data, len); ++ ctx->buf_cur += len; ++ } ++} ++ ++void sha1_final(SHA1_CTX * ctx, uint8_t * digest) ++{ ++ uint32_t rem_size; ++ uint8_t *buf_cur = ctx->buf_cur; ++ int i; ++ ++ rem_size = 64 - ((ctx->bitlen[0] >> 3) & 0x3f); ++ *(buf_cur++) = 0x80; ++ ++ if (rem_size > 8 + 1) { ++ /* We have enough room in the current block */ ++ for (i = 0; i < rem_size - 8 - 1; i++) { ++ *(buf_cur++) = 0; ++ } ++ } else { ++ /* We do not have enough room and need therefore to add a new ++ 64-byte block */ ++ for (i = 0; i < rem_size - 1; i++) { ++ *(buf_cur++) = 0; ++ } ++ sha1_over_block(ctx, ctx->buf); ++ ++ buf_cur = ctx->buf; ++ for (i = 0; i < 64 - 8; i++) { ++ *(buf_cur++) = 0; ++ } ++ } ++#ifdef HAVE_BIG_ENDIAN ++ memcpy(buf_cur, (uint8_t *) ctx->bitlen, 8); ++#endif /* HAVE_BIG_ENDIAN */ ++ ++#ifdef HAVE_LITTLE_ENDIAN ++ *(buf_cur++) = (ctx->bitlen[1] >> 24) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[1] >> 16) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[1] >> 8) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[1] >> 0) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[0] >> 24) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[0] >> 16) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[0] >> 8) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[0] >> 0) & 0xff; ++#endif /* HAVE_LITTLE_ENDIAN */ ++ ++ sha1_over_block(ctx, ctx->buf); ++ ++#ifdef HAVE_BIG_ENDIAN ++ memcpy(digest + 0, (uint8_t *) (&(ctx->A)), sizeof(uint32_t)); ++ memcpy(digest + 4, (uint8_t *) (&(ctx->B)), sizeof(uint32_t)); ++ memcpy(digest + 8, (uint8_t *) (&(ctx->C)), sizeof(uint32_t)); ++ memcpy(digest + 12, (uint8_t *) (&(ctx->D)), sizeof(uint32_t)); ++ memcpy(digest + 16, (uint8_t *) (&(ctx->E)), sizeof(uint32_t)); ++#endif /* HAVE_BIG_ENDIAN */ ++ ++#ifdef HAVE_LITTLE_ENDIAN ++ digest[0] = ((ctx->A) >> 24) & 0xff; ++ digest[1] = ((ctx->A) >> 16) & 0xff; ++ digest[2] = ((ctx->A) >> 8) & 0xff; ++ digest[3] = ((ctx->A) >> 0) & 0xff; ++ digest[4] = ((ctx->B) >> 24) & 0xff; ++ digest[5] = ((ctx->B) >> 16) & 0xff; ++ digest[6] = ((ctx->B) >> 8) & 0xff; ++ digest[7] = ((ctx->B) >> 0) & 0xff; ++ digest[8] = ((ctx->C) >> 24) & 0xff; ++ digest[9] = ((ctx->C) >> 16) & 0xff; ++ digest[10] = ((ctx->C) >> 8) & 0xff; ++ digest[11] = ((ctx->C) >> 0) & 0xff; ++ digest[12] = ((ctx->D) >> 24) & 0xff; ++ digest[13] = ((ctx->D) >> 16) & 0xff; ++ digest[14] = ((ctx->D) >> 8) & 0xff; ++ digest[15] = ((ctx->D) >> 0) & 0xff; ++ digest[16] = ((ctx->E) >> 24) & 0xff; ++ digest[17] = ((ctx->E) >> 16) & 0xff; ++ digest[18] = ((ctx->E) >> 8) & 0xff; ++ digest[19] = ((ctx->E) >> 0) & 0xff; ++#endif /* HAVE_LITTLE_ENDIAN */ ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/hmac.h linux-2.4.25/net/ipv6/mobile_ip6/hmac.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/hmac.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/hmac.h 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,94 @@ ++/* ++ * MIPL Mobile IPv6 Message authentication algorithms ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _HMAC_H ++#define _HMAC_H ++ ++#include ++#include ++ ++#define HAVE_LITTLE_ENDIAN ++ ++#define NO_EXPIRY 1 /* For sec_as */ ++ ++#define ALG_AUTH_NONE 0 ++#define ALG_AUTH_HMAC_MD5 1 ++#define ALG_AUTH_HMAC_SHA1 2 ++ ++struct sec_as; ++struct ah_processing { ++ void *context; ++ struct sec_as *sas; ++ u_int8_t *key_auth; ++ u_int32_t key_auth_len; ++}; ++ ++struct antireplay { ++ u_int32_t count; ++ u_int32_t bitmap; ++}; ++ ++typedef struct { ++ u_int32_t A, B, C, D; ++ u_int32_t bitlen[2]; ++ u_int8_t* buf_cur; ++ u_int8_t buf[64]; ++} MD5_CTX; ++ ++typedef struct { ++ u_int32_t A, B, C, D, E; ++ u_int32_t bitlen[2]; ++ u_int8_t* buf_cur; ++ u_int8_t buf[64]; ++} SHA1_CTX; ++ ++ ++ ++int ah_hmac_md5_init (struct ah_processing *ahp, u_int8_t *key, u_int32_t key_len); ++void ah_hmac_md5_loop(struct ah_processing*, void*, u_int32_t); ++void ah_hmac_md5_result(struct ah_processing*, char*); ++int ah_hmac_sha1_init(struct ah_processing*, u_int8_t *key, u_int32_t key_len); ++void ah_hmac_sha1_loop(struct ah_processing*, void*, u_int32_t); ++void ah_hmac_sha1_result(struct ah_processing*, char*); ++ ++ ++#define AH_HDR_LEN 12 /* # of bytes for Next Header, Payload Length, ++ RESERVED, Security Parameters Index and ++ ++ Sequence Number Field */ ++ ++void md5_init(MD5_CTX *ctx); ++void md5_over_block(MD5_CTX *ctx, u_int8_t* data); ++void create_M_blocks(u_int32_t* M, u_int8_t* data); ++void md5_compute(MD5_CTX *ctx, u_int8_t* data, u_int32_t len); ++void md5_final(MD5_CTX *ctx, u_int8_t* digest); ++ ++void sha1_init(SHA1_CTX *ctx); ++void sha1_over_block(SHA1_CTX *ctx, u_int8_t* data); ++void create_W_blocks(u_int32_t* W, u_int8_t* data); ++void sha1_compute(SHA1_CTX *ctx, u_int8_t* data, u_int32_t len); ++void sha1_final(SHA1_CTX *ctx, u_int8_t* digest); ++ ++struct mipv6_acq { ++ struct in6_addr coa; ++ struct in6_addr haddr; ++ struct in6_addr peer; ++ u_int32_t spi; ++}; ++#define MIPV6_MAX_AUTH_DATA 20 ++ ++#define HMAC_MD5_HASH_LEN 16 ++#define HMAC_SHA1_HASH_LEN 20 ++#define HMAC_SHA1_KEY_SIZE 20 ++#define HMAC_MD5_ICV_LEN 12 /* RFC 2403 */ ++#define HMAC_SHA1_ICV_LEN 12 /* RFC 2404 */ ++ ++#endif /* _HMAC_H */ +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/ioctl_mn.c linux-2.4.25/net/ipv6/mobile_ip6/ioctl_mn.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/ioctl_mn.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/ioctl_mn.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,142 @@ ++/* ++ * Mobile Node IOCTL Control device ++ * ++ * Authors: ++ * Henrik Petander ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "debug.h" ++#include "mdetect.h" ++#include "multiaccess_ctl.h" ++ ++/* Reserved for local / experimental use */ ++#define MAJOR_NUM 0xf9 ++ ++/* Get Care-of address information for Mobile Node */ ++#define IOCTL_GET_CAREOFADDR _IOWR(MAJOR_NUM, 9, void *) ++ ++#define MA_IOCTL_SET_IFACE_PREFERENCE _IOR (MAJOR_NUM, 13, void *) ++ ++/* The name of the device file */ ++#define CTLFILE "mipv6_dev" ++ ++static int inuse = 0; ++ ++static int mipv6_open(struct inode *inode, struct file *file) ++{ ++ DEBUG(DBG_INFO, "(%p)\n", file); ++ ++ if (inuse) ++ return -EBUSY; ++ ++ inuse++; ++ ++ MOD_INC_USE_COUNT; ++ ++ return 0; ++} ++ ++static int mipv6_close(struct inode *inode, struct file *file) ++{ ++ DEBUG(DBG_INFO, "(%p,%p)\n", inode, file); ++ inuse--; ++ ++ MOD_DEC_USE_COUNT; ++ ++ return 0; ++} ++ ++int mipv6_ioctl(struct inode *inode, struct file *file, ++ unsigned int ioctl_num, /* The number of the ioctl */ ++ unsigned long arg) /* The parameter to it */ ++{ ++ struct in6_addr careofaddr; ++ ++ /* Switch according to the ioctl called */ ++ switch (ioctl_num) { ++ case IOCTL_GET_CAREOFADDR: ++ DEBUG(DBG_DATADUMP, "IOCTL_GET_CAREOFADDR"); ++ /* First get home address from user and then look up ++ * the care-of address and return it ++ */ ++ if (copy_from_user(&careofaddr, (struct in6_addr *)arg, ++ sizeof(struct in6_addr)) < 0) { ++ DEBUG(DBG_WARNING, "Copy from user failed"); ++ return -EFAULT; ++ } ++ mipv6_get_care_of_address(&careofaddr, &careofaddr); ++ if (copy_to_user((struct in6_addr *)arg, &careofaddr, ++ sizeof(struct in6_addr)) < 0) { ++ DEBUG(DBG_WARNING, "copy_to_user failed"); ++ return -EFAULT; ++ } ++ break; ++ case MA_IOCTL_SET_IFACE_PREFERENCE: ++ DEBUG(DBG_INFO, "MA_IOCTL_SET_IFACE_PREFERENCE"); ++ ma_ctl_set_preference(arg); ++ break; ++ ++ default: ++ DEBUG(DBG_WARNING, "Unknown ioctl cmd (%d)", ioctl_num); ++ return -ENOENT; ++ } ++ return 0; ++} ++ ++struct file_operations Fops = { ++ owner: THIS_MODULE, ++ read: NULL, ++ write: NULL, ++ poll: NULL, ++ ioctl: mipv6_ioctl, ++ open: mipv6_open, ++ release: mipv6_close ++}; ++ ++ ++/* Initialize the module - Register the character device */ ++int mipv6_ioctl_mn_init(void) ++{ ++ int ret_val; ++ ++ /* Register the character device (atleast try) */ ++ ret_val = register_chrdev(MAJOR_NUM, CTLFILE, &Fops); ++ ++ /* Negative values signify an error */ ++ if (ret_val < 0) { ++ DEBUG(DBG_ERROR, "failed registering char device (err=%d)", ++ ret_val); ++ return ret_val; ++ } ++ ++ DEBUG(DBG_INFO, "Device number %x, success", MAJOR_NUM); ++ return 0; ++} ++ ++ ++/* Cleanup - unregister the appropriate file from /proc */ ++void mipv6_ioctl_mn_exit(void) ++{ ++ int ret; ++ /* Unregister the device */ ++ ret = unregister_chrdev(MAJOR_NUM, CTLFILE); ++ ++ /* If there's an error, report it */ ++ if (ret < 0) ++ DEBUG(DBG_ERROR, "errorcode: %d\n", ret); ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mdetect.c linux-2.4.25/net/ipv6/mobile_ip6/mdetect.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/mdetect.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/mdetect.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,1153 @@ ++/* ++ * Movement Detection Module ++ * ++ * Authors: ++ * Henrik Petander ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ * Handles the L3 movement detection of mobile node and also ++ * changing of its routes. ++ * ++ */ ++ ++/* ++ * Changes: ++ * ++ * Nanno Langstraat : Locking fixes ++ * Venkata Jagana : Locking fix ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#ifdef CONFIG_SYSCTL ++#include ++#endif /* CONFIG_SYSCTL */ ++ ++#include "util.h" ++#include "mdetect.h" ++#include "mn.h" ++#include "debug.h" ++#include "multiaccess_ctl.h" ++ ++#define START 0 ++#define CONTINUE 1 ++#define OK 2 ++#define DEBUG_MDETECT 7 ++ ++#define DEF_RTR_POLL_IVAL 5 /* In seconds */ ++ ++#define NO_RTR 0 ++#define RTR_SUSPECT 1 ++#define CURR_RTR_OK 2 ++ ++#define RA_RCVD 0 ++#define NA_RCVD 1 ++#define TIMEOUT 2 ++ ++#define MIPV6_MDF_NONE 0x0 ++#define MIPV6_MDF_HAS_RTR_PREV 0x1 ++ ++#define ROUTER_REACHABLE 1 ++#define RADV_MISSED 2 ++#define NOT_REACHABLE 3 ++ ++/* R_TIME_OUT paramater is used to make the decision when to change the ++ * default router, if the current one is unreachable. 2s is pretty aggressive ++ * and may result in hopping between two routers. OTOH a small value enhances ++ * the performance ++ */ ++#define R_TIME_OUT 30*HZ ++ ++/* maximum RA interval for router unreachability detection */ ++#define MAX_RADV_INTERVAL 6*HZ /* 6000 ms... */ ++ ++/* Threshold for exponential resending of router solicitations */ ++#define RS_RESEND_LINEAR 10*HZ ++ ++#define EAGER_CELL_SWITCHING 1 ++#define LAZY_CELL_SWITCHING 0 ++#define RESPECT_DAD 1 ++ ++#define ROUTER_ADDRESS 0x20 ++ ++/* RA flags */ ++#define ND_RA_FLAG_MANAGED 0x80 ++#define ND_RA_FLAG_OTHER 0x40 ++#define ND_RA_FLAG_HA 0x20 ++ ++/* DAD flags for global and link local addresses */ ++ ++#define COA_TENTATIVE 0x10 ++#define LLADDR_TENTATIVE 0x01 ++ ++struct router { ++ struct list_head list; ++ struct in6_addr ll_addr; ++ struct in6_addr raddr; /* Also contains prefix */ ++ __u8 link_addr[MAX_ADDR_LEN]; /* link layer address */ ++ __u8 link_addr_len; ++ __u8 state; ++ __u8 is_current; ++ __u8 reachable; ++ int ifindex; ++ int pfix_len; /* Length of the network prefix */ ++ unsigned long lifetime; /* from ra */ ++ __u32 last_ns_sent; ++ __u32 last_ra_rcvd; ++ __u32 interval; /* ra interval in milliseconds, 0 if not set */ ++ int glob_addr; /*Whether raddr contains also routers global address*/ ++ __u8 flags; /* RA flags, for example ha */ ++ struct in6_addr CoA; /* care-off address used with this router */ ++ int extra_addr_route; ++}; ++ ++/* dad could also be RESPECT_DAD for duplicate address detection of ++ new care-of addresses */ ++static int dad = 0; ++ ++/* Only one choice, nothing else implemented */ ++int max_rtr_reach_time = DEF_RTR_POLL_IVAL; ++ ++ ++int eager_cell_switching = EAGER_CELL_SWITCHING; /* Can be set to 0 via proc */ ++static spinlock_t router_lock; ++static spinlock_t ho_lock; ++ ++static void coa_timer_handler(unsigned long arg); ++static void timer_handler(unsigned long foo); ++static struct router *curr_router = NULL, *next_router = NULL; ++static struct timer_list r_timer = { function: timer_handler }; ++static struct timer_list coa_timer = { function: coa_timer_handler }; ++#define MAX_ROUTERS 1000 ++static LIST_HEAD(rtr_list); ++static int num_routers = 0; ++static struct handoff *_ho = NULL; ++/* ++ * Functions for handling the default router list, which movement ++ * detection uses for avoiding loops etc. ++ */ ++ ++/* TODO: Send NS to router after MAX interval has passed from last RA */ ++static int mipv6_router_state(struct router *rtr) { ++ if (rtr->interval) { ++ if (time_before(jiffies, (rtr->last_ra_rcvd + (rtr->interval * HZ) / 1000))) ++ return ROUTER_REACHABLE; ++ else ++ return NOT_REACHABLE; ++ } ++ else ++ if (time_after(jiffies, rtr->last_ra_rcvd + (rtr->lifetime * HZ))) ++ return NOT_REACHABLE; ++ return ROUTER_REACHABLE; ++} ++ ++/* searches for a specific router or any router that is reachable, ++ * if address is NULL. Also deletes obsolete routers. ++ */ ++static void mipv6_router_gc(void) ++{ ++ struct router *curr = NULL; ++ struct list_head *lh, *lh_tmp; ++ ++ DEBUG_FUNC(); ++ ++ list_for_each_safe(lh, lh_tmp, &rtr_list) { ++ curr = list_entry(lh, struct router, list); ++ if (mipv6_router_state(curr) == NOT_REACHABLE && !curr->is_current) { ++ num_routers--; ++ list_del_init(&curr->list); ++ DEBUG(DBG_DATADUMP, "Deleting unreachable router %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&curr->raddr)); ++ kfree(curr); ++ } ++ else { ++ DEBUG(DBG_DATADUMP, "NOT Deleting router %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&curr->raddr)); ++ } ++ } ++} ++ ++static struct router *mipv6_rtr_get(struct in6_addr *search_addr) ++{ ++ struct router *rtr = NULL; ++ struct list_head *lh; ++ ++ DEBUG_FUNC(); ++ ++ if (search_addr == NULL) ++ return NULL; ++ list_for_each(lh, &rtr_list) { ++ rtr = list_entry(lh, struct router, list); ++ if(!ipv6_addr_cmp(search_addr, &rtr->raddr)) { ++ return rtr; ++ } ++ } ++ return NULL; ++} ++ ++/* ++ * Adds router to list ++ */ ++static struct router *mipv6_rtr_add(struct router *nrt) ++{ ++ ++ struct router *rptr; ++ ++ DEBUG_FUNC(); ++ ++ /* check if someone is trying DoS attack, or we just have some ++ memory leaks... */ ++ if (num_routers > MAX_ROUTERS) { ++ DEBUG(DBG_CRITICAL, ++ "failed to add new router, MAX_ROUTERS exceeded"); ++ return NULL; ++ } ++ ++ rptr = kmalloc(sizeof(struct router), GFP_ATOMIC); ++ if (rptr) { ++ memcpy(rptr, nrt, sizeof(struct router)); ++ list_add(&rptr->list, &rtr_list); ++ num_routers++; ++ } ++ DEBUG(DBG_INFO, "Adding router: %x:%x:%x:%x:%x:%x:%x:%x, " ++ "lifetime : %d sec, adv.interval: %d millisec", ++ NIPV6ADDR(&rptr->raddr), rptr->lifetime, rptr->interval); ++ ++ DEBUG(DBG_INFO, "num_routers after addition: %d", num_routers); ++ return rptr; ++} ++ ++/* Cleans up the list */ ++static void list_free(struct router **curr_router_p) ++{ ++ struct router *tmp; ++ struct list_head *lh, *lh_tmp; ++ ++ DEBUG_FUNC(); ++ ++ DEBUG(DBG_INFO, "Freeing the router list"); ++ /* set curr_router->prev_router and curr_router NULL */ ++ *curr_router_p = NULL; ++ list_for_each_safe(lh, lh_tmp, &rtr_list) { ++ tmp = list_entry(lh, struct router, list); ++ DEBUG(DBG_INFO, "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&tmp->ll_addr)); ++ list_del(&tmp->list); ++ kfree(tmp); ++ num_routers--; ++ } ++} ++ ++int rs_state = START; ++ ++/* Sends router solicitations to all valid devices ++ * source = link local address (of sending interface) ++ * dstaddr = all routers multicast address ++ * Solicitations are sent at an exponentially decreasing rate ++ * ++ * TODO: send solicitation first at a normal rate (from ipv6) and ++ * after that use the exponentially increasing intervals ++ */ ++static int rs_send(void) ++{ ++ struct net_device *dev; ++ struct in6_addr raddr, lladdr; ++ struct inet6_dev *in6_dev = NULL; ++ static int num_rs; ++ ++ if (rs_state == START) { ++ num_rs = 0; ++ rs_state = CONTINUE; ++ } else if (num_rs++ > MAX_RTR_SOLICITATIONS) ++ return HZ; ++ ++ ipv6_addr_all_routers(&raddr); ++ read_lock(&dev_base_lock); ++ ++ /* Send router solicitations to all interfaces */ ++ for (dev = dev_base; dev; dev = dev->next) { ++ if ((dev->flags & IFF_UP) && dev->type == ARPHRD_ETHER) { ++ DEBUG(DBG_DATADUMP, "Sending RS to device %s", ++ dev->name); ++ if (!ipv6_get_lladdr(dev, &lladdr)) { ++ ndisc_send_rs(dev, &lladdr, &raddr); ++ in6_dev = in6_dev_get(dev); ++ in6_dev->if_flags |= IF_RS_SENT; ++ in6_dev_put(in6_dev); ++ } else { ++ DEBUG(DBG_DATADUMP, "%s: device doesn't have link-local address!\n", dev->name); ++ continue; ++ } ++ } ++ ++ } ++ read_unlock(&dev_base_lock); ++ return RTR_SOLICITATION_INTERVAL; ++} ++ ++/* Create a new CoA for MN and also add a route to it if it is still tentative ++ to allow MN to get packets to the address immediately ++ */ ++static int form_coa(struct in6_addr *coa, struct in6_addr *pfix, ++ int plen, int ifindex) ++{ ++ struct net_device *dev; ++ struct inet6_dev *in6_dev; ++ int ret = 0; ++ ++ if ((dev = dev_get_by_index(ifindex)) == NULL) { ++ DEBUG(DBG_WARNING, "Device is not present"); ++ return -1; ++ } ++ if ((in6_dev = in6_dev_get(dev)) == NULL) { ++ DEBUG(DBG_WARNING, "inet6_dev is not present"); ++ dev_put(dev); ++ return -1; ++ } ++ coa->s6_addr32[0] = pfix->s6_addr32[0]; ++ coa->s6_addr32[1] = pfix->s6_addr32[1]; ++ ++ if (ipv6_generate_eui64(coa->s6_addr + 8, dev) && ++ ipv6_inherit_eui64(coa->s6_addr + 8, in6_dev)) { ++ in6_dev_put(in6_dev); ++ dev_put(dev); ++ return -1; ++ } ++ if (ipv6_chk_addr(coa, dev) == 0) { ++ DEBUG(DBG_WARNING, "care-of address still tentative"); ++ ret = 1; ++ } ++ DEBUG(DBG_INFO, "Formed new CoA: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(coa)); ++ ++ in6_dev_put(in6_dev); ++ dev_put(dev); ++ return ret; ++} ++ ++static inline int rtr_is_gw(struct router *rtr, struct rt6_info *rt) ++{ ++ return ((rt->rt6i_flags & RTF_GATEWAY) && ++ !ipv6_addr_cmp(&rt->rt6i_gateway, &rtr->ll_addr)); ++} ++ ++static inline int is_prefix_route(struct router *rtr, struct rt6_info *rt) ++{ ++ return (!(rt->rt6i_flags & RTF_GATEWAY) && ++ mipv6_prefix_compare(&rt->rt6i_dst.addr, &rtr->raddr, ++ rtr->pfix_len)); ++} ++ ++/* ++ * Function that determines whether given rt6_info should be destroyed ++ * (negative => destroy rt6_info, zero or positive => do nothing) ++ */ ++static int mn_route_cleaner(struct rt6_info *rt, void *arg) ++{ ++ int type; ++ ++ struct router *rtr = (struct router *)arg; ++ ++ int ret = -1; ++ ++ DEBUG_FUNC(); ++ ++ if (!rt || !rtr) { ++ DEBUG(DBG_ERROR, "mn_route_cleaner: rt or rtr NULL"); ++ return 0; ++ } ++ ++ /* Do not delete routes to local addresses or to multicast ++ * addresses, since we need them to get router advertisements ++ * etc. Multicast addresses are more tricky, but we don't ++ * delete them in any case. The routing mechanism is not optimal for ++ * multihoming. ++ * ++ * Also keep all new prefix routes, gateway routes through rtr and ++ * all remaining default routes (including those used for reverse ++ * tunneling) ++ */ ++ type = ipv6_addr_type(&rt->rt6i_dst.addr); ++ ++ if ((type & (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL)) || ++ rt->rt6i_dev == &loopback_dev || rtr_is_gw(rtr, rt) || ++ is_prefix_route(rtr, rt) || (rt->rt6i_flags & RTF_DEFAULT)) ++ ret = 0; ++ ++ /* delete all others */ ++ ++ if (rt->rt6i_dev != &loopback_dev) { ++ DEBUG(DEBUG_MDETECT, ++ "%s route:\n" ++ "dev: %s,\n" ++ "gw: %x:%x:%x:%x:%x:%x:%x:%x,\n" ++ "flags: %x,\n" ++ "metric: %d,\n" ++ "src: %x:%x:%x:%x:%x:%x:%x:%x,\n" ++ "dst: %x:%x:%x:%x:%x:%x:%x:%x,\n" ++ "plen: %d\n", ++ (ret ? "Deleting" : "Keeping"), ++ rt->rt6i_dev->name, ++ NIPV6ADDR(&rt->rt6i_gateway), ++ rt->rt6i_flags, ++ rt->rt6i_metric, ++ NIPV6ADDR(&rt->rt6i_src.addr), ++ NIPV6ADDR(&rt->rt6i_dst.addr), ++ rt->rt6i_dst.plen); ++ } ++ return ret; ++} ++ ++/* ++ * Deletes old routes ++ */ ++static __inline__ void delete_routes(struct router *rtr) ++{ ++ DEBUG_FUNC(); ++ ++ /* Routing table is locked to ensure that nobody uses its */ ++ write_lock_bh(&rt6_lock); ++ DEBUG(DBG_INFO, "mipv6: Purging routes"); ++ /* TODO: Does not prune, should it? */ ++ fib6_clean_tree(&ip6_routing_table, ++ mn_route_cleaner, 0, rtr); ++ write_unlock_bh(&rt6_lock); ++ ++} ++ ++ ++static __inline__ void delete_coas(struct router *rtr) ++{ ++ struct net_device *dev; ++ struct inet6_dev *idev; ++ struct inet6_ifaddr *ifa; ++ ++ dev = dev_get_by_index(rtr->ifindex); ++ if (!dev) ++ return; ++ ++ idev = in6_dev_get(dev); ++ ++ if (idev) { ++ read_lock_bh(&idev->lock); ++ ifa = idev->addr_list; ++ while (ifa) { ++ int keep; ++ spin_lock(&ifa->lock); ++ ++ keep = (ifa->flags&(IFA_F_PERMANENT|IFA_F_HOMEADDR) || ++ !ipv6_addr_cmp(&ifa->addr, &rtr->CoA)); ++ ++ spin_unlock(&ifa->lock); ++ ++ if (keep) ++ ifa = ifa->if_next; ++ else { ++ in6_ifa_hold(ifa); ++ read_unlock_bh(&idev->lock); ++ ++ ipv6_del_addr(ifa); ++ ++ read_lock_bh(&idev->lock); ++ ifa = idev->addr_list; ++ } ++ } ++ read_unlock_bh(&idev->lock); ++ in6_dev_put(idev); ++ } ++ dev_put(dev); ++} ++ ++int next_mdet_state[3][3] = {{CURR_RTR_OK, NO_RTR, NO_RTR}, ++ {CURR_RTR_OK, CURR_RTR_OK, NO_RTR}, ++ {CURR_RTR_OK, CURR_RTR_OK, RTR_SUSPECT}}; ++ ++char *states[3] = {"NO_RTR", "RTR_SUSPECT", "CURR_RTR_OK"}; ++char *events[3] = {"RA_RCVD", "NA_RCVD", "TIMEOUT"}; ++ ++/* State transitions ++ * NO_RTR, RA_RCVD -> CURR_RTR_OK ++ * NO_RTR, NA_RCVD -> NO_RTR ++ * NO_RTR, TIMEOUT -> NO_RTR ++ ++ * RTR_SUSPECT, RA_RCVD -> CURR_RTR_OK ++ * RTR_SUSPECT, NA_RCVD -> CURR_RTR_OK ++ * RTR_SUSPECT, TIMEOUT -> NO_RTR ++ ++ * CURR_RTR_OK, RA_RCVD -> CURR_RTR_OK ++ * CURR_RTR_OK, NA_RCVD -> CURR_RTR_OK ++ * CURR_RTR_OK, TIMEOUT -> RTR_SUSPECT ++ */ ++static int _curr_state = NO_RTR; ++ ++#if 0 ++static int get_mdet_state(void){ ++ int state; ++ spin_lock_bh(&router_lock); ++ state = _curr_state; ++ spin_unlock_bh(&router_lock); ++ return state; ++} ++#endif ++ ++/* Needs to be called with router_lock locked */ ++static int mdet_statemachine(int event) ++{ ++ ++ if (event > 2 || _curr_state > 2) { ++ DEBUG(DBG_ERROR, "Got illegal event or curr_state"); ++ return -1; ++ } ++ ++ DEBUG(DBG_DATADUMP, "Got event %s and curr_state is %s", ++ events[event], states[_curr_state]); ++ ++ _curr_state = next_mdet_state[_curr_state][event]; ++ DEBUG(DBG_DATADUMP, "Next state is %s", states[_curr_state]); ++ return _curr_state; ++} ++ ++static void mipv6_do_ll_dad(int ifindex) ++{ ++ struct net_device *dev = dev_get_by_index(ifindex); ++ if (dev) { ++ struct in6_addr lladdr; ++ struct inet6_ifaddr *ifa; ++ if (!ipv6_get_lladdr(dev, &lladdr) && ++ (ifa = ipv6_get_ifaddr(&lladdr, dev)) != NULL) { ++ spin_lock_bh(&ifa->lock); ++ if (!(ifa->flags & IFA_F_TENTATIVE)) { ++ ifa->flags |= IFA_F_TENTATIVE; ++ spin_unlock_bh(&ifa->lock); ++ addrconf_dad_start(ifa, 0); ++ } else ++ spin_unlock_bh(&ifa->lock); ++ ++ } ++ dev_put(dev); ++ } ++} ++/* ++ * Changes the router, called from ndisc.c if mipv6_router_event ++ * returns true. ++ */ ++ ++static void mipv6_change_router(void) ++{ ++ struct in6_addr coa; ++ int ret, ifindex; ++ ++ DEBUG_FUNC(); ++ ++ ++ if (next_router == NULL) ++ return; ++ ++ spin_lock(&router_lock); ++ ++ ++ if (curr_router != NULL && ++ !ipv6_addr_cmp(&curr_router->ll_addr, &next_router->ll_addr)) { ++ DEBUG(DBG_INFO,"Trying to handoff from: " ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&curr_router->ll_addr)); ++ DEBUG(DBG_INFO,"Trying to handoff to: " ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&next_router->ll_addr)); ++ next_router = NULL; /* Let's not leave dangling pointers */ ++ spin_unlock(&router_lock); ++ return; ++ } ++ ret = form_coa(&next_router->CoA, &next_router->raddr, ++ next_router->pfix_len, next_router->ifindex); ++ if (ret < 0) { ++ DEBUG(DBG_ERROR, "handoff: Creation of coa failed"); ++ spin_unlock(&router_lock); ++ return; ++ } else if (ret > 0) ++ next_router->flags |= COA_TENTATIVE; ++ ++ mdet_statemachine(RA_RCVD); /* TODO: What if DAD fails... */ ++ if (next_router->interval) ++ mod_timer(&r_timer, jiffies + ++ (next_router->interval * HZ)/1000); ++ else ++ mod_timer(&r_timer, jiffies + max_rtr_reach_time * HZ); ++ ++ ++ if (ret == 0) { ++ ipv6_addr_copy(&coa, &next_router->CoA); ++ ifindex = next_router->ifindex; ++ spin_unlock(&router_lock); ++ mipv6_mdet_finalize_ho(&coa, ifindex); ++ return; ++ } ++ spin_unlock(&router_lock); ++ ++} ++static unsigned long ns_send(void) ++{ ++ struct neighbour *neigh; ++ struct net_device *dev; ++ struct in6_addr *raddr; ++ ++ DEBUG(DBG_DATADUMP, "Sending Neighbour solicitation to default router to verify its reachability"); ++ if (!curr_router) ++ return HZ; ++ if ((dev = dev_get_by_index(curr_router->ifindex)) == NULL) ++ return HZ; ++ if ((neigh = ndisc_get_neigh(dev, &curr_router->ll_addr)) == NULL) { ++ dev_put(dev); ++ return HZ; ++ } ++ if (curr_router->glob_addr) ++ raddr = &curr_router->raddr; ++ else ++ raddr = &curr_router->ll_addr; ++ ++ curr_router->last_ns_sent = jiffies; ++ ndisc_send_ns(dev, neigh, raddr, raddr, NULL); ++ ++ neigh_release(neigh); ++ dev_put(dev); ++ return HZ/5; /* Wait 200ms for a reply */ ++} ++ ++static int na_rcvd(void) ++{ ++ int neigh_ok = 0; ++ struct neighbour *neigh; ++ struct net_device *dev; ++ ++ if (!curr_router) ++ return 0; ++ if ((dev = dev_get_by_index(curr_router->ifindex)) == NULL) ++ return 0; ++ if ((neigh = ndisc_get_neigh(dev, &curr_router->ll_addr)) == NULL) { ++ dev_put(dev); ++ return 0; ++ } ++ if (neigh->flags & NTF_ROUTER && ++ (time_after(neigh->confirmed, curr_router->last_ns_sent) || ++ neigh->confirmed == curr_router->last_ns_sent)) { ++ neigh_ok = 1; ++ DEBUG(DBG_DATADUMP, "Mdetect event: NA rcvd from curr rtr"); ++ } else ++ DEBUG(DBG_DATADUMP, "Mdetect event: NA NOT rcvd from curr rtr within time limit"); ++ neigh_release(neigh); ++ dev_put(dev); ++ return neigh_ok; ++} ++ ++static void coa_timer_handler(unsigned long dummy) ++{ ++ ++ spin_lock_bh(&ho_lock); ++ if (_ho) { ++ DEBUG(DBG_INFO, "Starting handoff after DAD"); ++ mipv6_mobile_node_moved(_ho); ++ kfree(_ho); ++ _ho = NULL; ++ } ++ spin_unlock_bh(&ho_lock); ++} ++static void timer_handler(unsigned long foo) ++{ ++ unsigned long timeout; ++ int state; ++ spin_lock_bh(&router_lock); ++ ++ if (_curr_state != NO_RTR) ++ rs_state = START; ++ ++ if (_curr_state == RTR_SUSPECT && na_rcvd()) { ++ state = mdet_statemachine(NA_RCVD); ++ timeout = curr_router->interval ? curr_router->interval : max_rtr_reach_time * HZ; ++ } else { ++ state = mdet_statemachine(TIMEOUT); ++ if (state == NO_RTR) ++ timeout = rs_send(); ++ else /* RTR_SUSPECT */ ++ timeout = ns_send(); ++ } ++ if (!timeout) ++ timeout = HZ; ++ ++ mipv6_router_gc(); ++ mod_timer(&r_timer, jiffies + timeout); ++ spin_unlock_bh(&router_lock); ++} ++ ++/** ++ * mipv6_get_care_of_address - get node's care-of primary address ++ * @homeaddr: one of node's home addresses ++ * @coaddr: buffer to store care-of address ++ * ++ * Stores the current care-of address in the @coaddr, assumes ++ * addresses in EUI-64 format. Since node might have several home ++ * addresses caller MUST supply @homeaddr. If node is at home ++ * @homeaddr is stored in @coaddr. Returns 0 on success, otherwise a ++ * negative value. ++ **/ ++int mipv6_get_care_of_address( ++ struct in6_addr *homeaddr, struct in6_addr *coaddr) ++{ ++ ++ DEBUG_FUNC(); ++ ++ if (homeaddr == NULL) ++ return -1; ++ spin_lock_bh(&router_lock); ++ if (curr_router == NULL || mipv6_mn_is_at_home(homeaddr) || ++ mipv6_prefix_compare(homeaddr, &curr_router->raddr, 64) || ++ curr_router->flags&COA_TENTATIVE) { ++ DEBUG(DBG_INFO, ++ "mipv6_get_care_of_address: returning home address"); ++ ipv6_addr_copy(coaddr, homeaddr); ++ spin_unlock_bh(&router_lock); ++ return 0; ++ ++ } ++ ++ /* At home or address check failure probably due to dad wait */ ++ if (mipv6_prefix_compare(&curr_router->raddr, homeaddr, ++ curr_router->pfix_len) ++ || (dad == RESPECT_DAD && ++ (ipv6_chk_addr(coaddr, NULL) == 0))) { ++ ipv6_addr_copy(coaddr, homeaddr); ++ } else { ++ ipv6_addr_copy(coaddr, &curr_router->CoA); ++ } ++ ++ spin_unlock_bh(&router_lock); ++ return 0; ++} ++ ++int mipv6_mdet_del_if(int ifindex) ++{ ++ struct router *curr = NULL; ++ struct list_head *lh, *lh_tmp; ++ ++ spin_lock_bh(&router_lock); ++ list_for_each_safe(lh, lh_tmp, &rtr_list) { ++ curr = list_entry(lh, struct router, list); ++ if (curr->ifindex == ifindex) { ++ num_routers--; ++ list_del_init(&curr->list); ++ DEBUG(DBG_DATADUMP, "Deleting router %x:%x:%x:%x:%x:%x:%x:%x on interface %d", ++ NIPV6ADDR(&curr->raddr), ifindex); ++ if (curr_router == curr) ++ curr_router = NULL; ++ kfree(curr); ++ } ++ } ++ spin_unlock_bh(&router_lock); ++ return 0; ++} ++ ++void mipv6_mdet_retrigger_ho(void) ++{ ++ struct handoff ho; ++ ++ spin_lock_bh(&router_lock); ++ if (curr_router != NULL) { ++ ho.coa = &curr_router->CoA; ++ ho.plen = curr_router->pfix_len; ++ ho.ifindex = curr_router->ifindex; ++ ipv6_addr_copy(&ho.rtr_addr, &curr_router->raddr); ++ ho.home_address = (curr_router->glob_addr && ++ curr_router->flags&ND_RA_FLAG_HA); ++ } ++ spin_unlock_bh(&router_lock); ++ mipv6_mobile_node_moved(&ho); ++} ++ ++void mipv6_mdet_set_curr_rtr_reachable(int reachable) ++{ ++ spin_lock_bh(&router_lock); ++ if (curr_router != NULL) { ++ curr_router->reachable = reachable; ++ } ++ spin_unlock_bh(&router_lock); ++ ++} ++ ++int mipv6_mdet_finalize_ho(const struct in6_addr *coa, const int ifindex) ++{ ++ int dummy; ++ struct handoff ho; ++ struct router *tmp; ++ struct net_device *dev; ++ struct in6_addr ll_addr; ++ ++ spin_lock_bh(&router_lock); ++ ++ if (!next_router) { ++ spin_unlock_bh(&router_lock); ++ return 0; ++ } ++ ++ dev = dev_get_by_index(next_router->ifindex); ++ ++ if (ipv6_get_lladdr(dev, &ll_addr) == 0) { ++ if (ipv6_addr_cmp(&ll_addr, coa) == 0) ++ DEBUG(DBG_INFO, "DAD for link local address completed"); ++ next_router->flags &= ~LLADDR_TENTATIVE; ++ } ++ ++ dev_put(dev); ++ ++ if (mipv6_prefix_compare(coa, &next_router->CoA, ++ next_router->pfix_len)) { ++ DEBUG(DBG_INFO, "DAD for Care-of address completed"); ++ next_router->flags &= ~COA_TENTATIVE; ++ } ++ if (!(next_router->flags&LLADDR_TENTATIVE) && !(next_router->flags&COA_TENTATIVE)) { ++ DEBUG(DBG_INFO, "%s: Proceeding with handoff after DAD\n", __FUNCTION__); ++ tmp = curr_router; ++ curr_router = next_router; ++ curr_router->is_current = 1; ++ next_router = NULL; ++ curr_router->flags &= ~COA_TENTATIVE; ++ delete_routes(curr_router); ++ delete_coas(curr_router); ++ if (tmp) { ++ struct net_device *dev_old = dev_get_by_index(tmp->ifindex); ++ struct rt6_info *rt = NULL; ++ if (dev_old) { ++ rt = rt6_get_dflt_router(&tmp->ll_addr, dev_old); ++ dev_put(dev_old); ++ } ++ if (rt) ++ ip6_del_rt(rt, NULL); ++ tmp->is_current = 0; ++ } ++ ++ ma_ctl_upd_iface(curr_router->ifindex, MA_IFACE_CURRENT, &dummy); ++ ma_ctl_upd_iface(curr_router->ifindex, MA_IFACE_CURRENT, &dummy); ++ ++ ++ ho.coa = &curr_router->CoA; ++ ho.plen = curr_router->pfix_len; ++ ho.ifindex = curr_router->ifindex; ++ ipv6_addr_copy(&ho.rtr_addr, &curr_router->raddr); ++ ho.home_address = (curr_router->glob_addr && ++ curr_router->flags&ND_RA_FLAG_HA); ++ ++ spin_unlock_bh(&router_lock); ++ mipv6_mobile_node_moved(&ho); ++ } else ++ spin_unlock_bh(&router_lock); ++ return 0; ++} ++/* Decides whether router candidate is the same router as current rtr ++ * based on prefix / global addresses of the routers and their link local ++ * addresses ++ */ ++static int is_current_rtr(struct router *nrt, struct router *crt) ++{ ++ DEBUG_FUNC(); ++ ++ DEBUG(DEBUG_MDETECT, "Current router: " ++ "%x:%x:%x:%x:%x:%x:%x:%x and", NIPV6ADDR(&crt->raddr)); ++ DEBUG(DEBUG_MDETECT, "Candidate router: " ++ "%x:%x:%x:%x:%x:%x:%x:%x", NIPV6ADDR(&nrt->raddr)); ++ ++ return (!ipv6_addr_cmp(&nrt->raddr,&crt->raddr) && ++ !ipv6_addr_cmp(&nrt->ll_addr, &crt->ll_addr)); ++} ++ ++/* ++ * Change next router to nrtr ++ * Returns 1, if router has been changed. ++ */ ++ ++static int change_next_rtr(struct router *nrtr, struct router *ortr) ++{ ++ int changed = 0; ++ DEBUG_FUNC(); ++ ++ if (!next_router || ipv6_addr_cmp(&nrtr->raddr, &next_router->raddr)) { ++ changed = 1; ++ } ++ next_router = nrtr; ++ return changed; ++} ++static int clean_ncache(struct router *nrt, struct router *ort, int same_if) ++{ ++ struct net_device *ortdev; ++ DEBUG_FUNC(); ++ ++ /* Always call ifdown after a handoff to ensure proper routing */ ++ ++ if (!ort) ++ return 0; ++ if ((ortdev = dev_get_by_index(ort->ifindex)) == NULL) { ++ DEBUG(DBG_WARNING, "Device is not present"); ++ return -1; ++ } ++ neigh_ifdown(&nd_tbl, ortdev); ++ dev_put(ortdev); ++ return 0; ++} ++ ++static int mdet_get_if_preference(int ifi) ++{ ++ int pref = 0; ++ ++ DEBUG_FUNC(); ++ ++ pref = ma_ctl_get_preference(ifi); ++ ++ DEBUG(DEBUG_MDETECT, "ifi: %d preference %d", ifi, pref); ++ ++ return pref; ++} ++ ++/* ++ * Called from mipv6_mn_ra_rcv to determine whether to do a handoff. ++ */ ++static int mipv6_router_event(struct router *rptr) ++{ ++ struct router *nrt = NULL; ++ int new_router = 0, same_if = 1; ++ int oldstate = _curr_state; ++ int addrtype = ipv6_addr_type(&rptr->raddr); ++ ++ DEBUG_FUNC(); ++ ++ if (rptr->lifetime == 0) ++ return MIPV6_IGN_RTR; ++ DEBUG(DEBUG_MDETECT, "Received a RA from router: " ++ "%x:%x:%x:%x:%x:%x:%x:%x", NIPV6ADDR(&rptr->raddr)); ++ spin_lock(&router_lock); ++ ++ /* Add or update router entry */ ++ if ((nrt = mipv6_rtr_get(&rptr->raddr)) == NULL) { ++ if (addrtype == IPV6_ADDR_ANY || (nrt = mipv6_rtr_add(rptr)) == NULL) { ++ spin_unlock(&router_lock); ++ return MIPV6_IGN_RTR; ++ } ++ DEBUG(DBG_INFO, "Router not on list,adding it to the list"); ++ new_router = 1; ++ } ++ nrt->last_ra_rcvd = jiffies; ++ nrt->state = ROUTER_REACHABLE; ++ nrt->interval = rptr->interval; ++ nrt->lifetime = rptr->lifetime; ++ nrt->ifindex = rptr->ifindex; ++ nrt->flags = rptr->flags; ++ nrt->glob_addr = rptr->glob_addr; ++ ++ /* Whether from current router */ ++ if (curr_router && curr_router->reachable && ++ is_current_rtr(nrt, curr_router)) { ++ if (nrt->interval) ++ mod_timer(&r_timer, jiffies + (nrt->interval * HZ)/1000); ++ else ++ mod_timer(&r_timer, jiffies + max_rtr_reach_time * HZ); ++ mdet_statemachine(RA_RCVD); ++ spin_unlock(&router_lock); ++ return MIPV6_ADD_RTR; ++ } else if (oldstate == NO_RTR) { ++ rt6_purge_dflt_routers(0); /* For multiple interface case */ ++ DEBUG(DBG_INFO, "No router or router not reachable, switching to new one"); ++ goto handoff; ++ } ++ if (!curr_router) { ++ /* Startup */ ++ goto handoff; ++ } ++ /* Router behind same interface as current one ?*/ ++ same_if = (nrt->ifindex == curr_router->ifindex); ++ /* Switch to new router behind same interface if eager cell ++ * switching is used or if the interface is preferred ++ */ ++ if ((new_router && eager_cell_switching && same_if) || ++ (mdet_get_if_preference(nrt->ifindex) > ++ mdet_get_if_preference(curr_router->ifindex))) { ++ DEBUG(DBG_INFO, "Switching to new router."); ++ goto handoff; ++ } ++ ++ /* No handoff, don't add default route */ ++ DEBUG(DEBUG_MDETECT, "Ignoring RA"); ++ spin_unlock(&router_lock); ++ return MIPV6_IGN_RTR; ++handoff: ++ clean_ncache(nrt, curr_router, same_if); ++ nrt->reachable = 1; ++ if (same_if && change_next_rtr(nrt, curr_router)) { ++ mipv6_do_ll_dad(nrt->ifindex); ++ nrt->flags |= LLADDR_TENTATIVE; ++ } ++ spin_unlock(&router_lock); ++ ++ return MIPV6_CHG_RTR; ++} ++ ++/* ++ * Called from ndisc.c's router_discovery. ++ */ ++ ++static inline int ret_to_ha(struct in6_addr *addr) ++{ ++ int res = 0; ++ struct mn_info *minfo; ++ read_lock(&mn_info_lock); ++ minfo = mipv6_mninfo_get_by_ha(addr); ++ if (minfo != NULL) { ++ spin_lock(&minfo->lock); ++ if (minfo->has_home_reg) { ++ res = 1; ++ } ++ spin_unlock(&minfo->lock); ++ } ++ read_unlock(&mn_info_lock); ++ return res; ++} ++ ++static int mipv6_mn_ra_rcv(struct sk_buff *skb, struct ndisc_options *ndopts) ++{ ++ int ifi = ((struct inet6_skb_parm *)skb->cb)->iif; ++ struct ra_msg *ra = (struct ra_msg *) skb->h.raw; ++ struct in6_addr *saddr = &skb->nh.ipv6h->saddr; ++ struct router nrt; ++ struct in6_addr *ha = NULL; ++ u8 *lladdr = NULL; ++ int res; ++ DEBUG_FUNC(); ++ ++ memset(&nrt, 0, sizeof(struct router)); ++ ++ if (ra->icmph.icmp6_home_agent) { ++ nrt.flags |= ND_RA_FLAG_HA; ++ DEBUG(DBG_DATADUMP, "RA has ND_RA_FLAG_HA up"); ++ } ++ ++ if (ra->icmph.icmp6_addrconf_managed) { ++ nrt.flags |= ND_RA_FLAG_MANAGED; ++ DEBUG(DBG_DATADUMP, "RA has ND_RA_FLAG_MANAGED up"); ++ } ++ ++ if (ra->icmph.icmp6_addrconf_other) { ++ nrt.flags |= ND_RA_FLAG_OTHER; ++ DEBUG(DBG_DATADUMP, "RA has ND_RA_FLAG_OTHER up"); ++ } ++ ++ ipv6_addr_copy(&nrt.ll_addr, saddr); ++ nrt.ifindex = ifi; ++ nrt.lifetime = ntohs(ra->icmph.icmp6_rt_lifetime); ++ ++ if (ndopts->nd_opts_src_lladdr) { ++ lladdr = (u8 *) ndopts->nd_opts_src_lladdr+2; ++ nrt.link_addr_len = skb->dev->addr_len; ++ memcpy(nrt.link_addr, lladdr, nrt.link_addr_len); ++ } ++ if (ndopts->nd_opts_pi) { ++ struct nd_opt_hdr *p; ++ for (p = ndopts->nd_opts_pi; ++ p; ++ p = ndisc_next_option(p, ndopts->nd_opts_pi_end)) { ++ struct prefix_info *pinfo; ++ int update = 0; ++ ++ pinfo = (struct prefix_info *) p; ++ ++ if (!pinfo->autoconf) ++ continue; ++ ++ if ((pinfo->router_address && ++ (update = ret_to_ha(&pinfo->prefix))) || ++ ipv6_addr_type(&nrt.raddr) != IPV6_ADDR_UNICAST) { ++ ipv6_addr_copy(&nrt.raddr, &pinfo->prefix); ++ nrt.pfix_len = pinfo->prefix_len; ++ if (pinfo->router_address) ++ nrt.glob_addr = 1; ++ else ++ nrt.glob_addr = 0; ++ if (update) ++ ha = &pinfo->prefix; ++ DEBUG(DBG_DATADUMP, "Address of the received " ++ "prefix info option: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&nrt.raddr)); ++ DEBUG(DBG_DATADUMP, "the length of the prefix is %d", ++ nrt.pfix_len); ++ } ++ } ++ } ++ if (ndopts->nd_opts_rai) { ++ nrt.interval = ntohl(*(__u32 *)(ndopts->nd_opts_rai+4)); ++ DEBUG(DBG_DATADUMP, ++ "received router interval option with interval : %d ", ++ nrt.interval / HZ); ++ ++ if (nrt.interval > MAX_RADV_INTERVAL) { ++ nrt.interval = 0; ++ DEBUG(DBG_DATADUMP, "but we are using: %d, " ++ "because interval>MAX_RADV_INTERVAL", ++ nrt.interval / HZ); ++ } ++ } ++ ++ res = mipv6_router_event(&nrt); ++ ++ if (ha && lladdr) { ++ mipv6_mn_ha_nd_update(__dev_get_by_index(ifi), ha, lladdr); ++ } ++ return res; ++} ++ ++int __init mipv6_initialize_mdetect(void) ++{ ++ ++ DEBUG_FUNC(); ++ ++ spin_lock_init(&router_lock); ++ spin_lock_init(&ho_lock); ++ init_timer(&coa_timer); ++ init_timer(&r_timer); ++ r_timer.expires = jiffies + HZ; ++ add_timer(&r_timer); ++ ++ /* Actual HO, also deletes old routes after the addition of new ones ++ in ndisc */ ++ MIPV6_SETCALL(mipv6_change_router, mipv6_change_router); ++ ++ MIPV6_SETCALL(mipv6_ra_rcv, mipv6_mn_ra_rcv); ++ ++ return 0; ++} ++ ++int __exit mipv6_shutdown_mdetect() ++{ ++ ++ DEBUG_FUNC(); ++ ++ MIPV6_RESETCALL(mipv6_ra_rcv); ++ MIPV6_RESETCALL(mipv6_change_router); ++ spin_lock_bh(&router_lock); ++ spin_lock(&ho_lock); ++ del_timer(&coa_timer); ++ del_timer(&r_timer); ++ /* Free the memory allocated by router list */ ++ list_free(&curr_router); ++ if (_ho) ++ kfree(_ho); ++ spin_unlock(&ho_lock); ++ spin_unlock_bh(&router_lock); ++ return 0; ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mdetect.h linux-2.4.25/net/ipv6/mobile_ip6/mdetect.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/mdetect.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/mdetect.h 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,37 @@ ++/* ++ * MIPL Mobile IPv6 Movement detection module header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _MDETECT_H ++#define _MDETECT_H ++ ++struct handoff { ++ int home_address; /* Is the coa a home address */ ++ int ifindex; ++ int plen; ++ struct in6_addr *coa; ++ struct in6_addr rtr_addr; /* Prefix or rtr address if coa is home address */ ++}; ++ ++int mipv6_initialize_mdetect(void); ++ ++int mipv6_shutdown_mdetect(void); ++ ++int mipv6_get_care_of_address(struct in6_addr *homeaddr, struct in6_addr *coa); ++ ++int mipv6_mdet_del_if(int ifindex); ++ ++int mipv6_mdet_finalize_ho(const struct in6_addr *coa, const int ifindex); ++ ++void mipv6_mdet_retrigger_ho(void); ++ ++void mipv6_mdet_set_curr_rtr_reachable(int reachable); ++ ++#endif /* _MDETECT_H */ +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp.c linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,342 @@ ++/** ++ * Generic icmp routines ++ * ++ * Authors: ++ * Jaakko Laine , ++ * Ville Nuorvala ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "debug.h" ++#include "bcache.h" ++#include "mipv6_icmp.h" ++#include "config.h" ++ ++struct mipv6_icmpv6_msg { ++ struct icmp6hdr icmph; ++ __u8 *data; ++ struct in6_addr *daddr; ++ int len; ++ __u32 csum; ++}; ++ ++#define MIPV6_ICMP_HOP_LIMIT 64 ++ ++static struct socket *mipv6_icmpv6_socket = NULL; ++static __u16 identifier = 0; ++ ++int mipv6_icmpv6_no_rcv(struct sk_buff *skb) ++{ ++ return 0; ++} ++ ++static int mipv6_icmpv6_xmit_holder = -1; ++ ++static int mipv6_icmpv6_xmit_lock_bh(void) ++{ ++ if (!spin_trylock(&mipv6_icmpv6_socket->sk->lock.slock)) { ++ if (mipv6_icmpv6_xmit_holder == smp_processor_id()) ++ return -EAGAIN; ++ spin_lock(&mipv6_icmpv6_socket->sk->lock.slock); ++ } ++ mipv6_icmpv6_xmit_holder = smp_processor_id(); ++ return 0; ++} ++ ++static __inline__ int mipv6_icmpv6_xmit_lock(void) ++{ ++ int ret; ++ local_bh_disable(); ++ ret = mipv6_icmpv6_xmit_lock_bh(); ++ if (ret) ++ local_bh_enable(); ++ return ret; ++} ++ ++static void mipv6_icmpv6_xmit_unlock_bh(void) ++{ ++ mipv6_icmpv6_xmit_holder = -1; ++ spin_unlock(&mipv6_icmpv6_socket->sk->lock.slock); ++} ++ ++static __inline__ void mipv6_icmpv6_xmit_unlock(void) ++{ ++ mipv6_icmpv6_xmit_unlock_bh(); ++ local_bh_enable(); ++} ++ ++ ++/** ++ * mipv6_icmpv6_dest_unreach - Destination Unreachable ICMP error message handler ++ * @skb: buffer containing ICMP error message ++ * ++ * Special Mobile IPv6 ICMP handling. If Correspondent Node receives ++ * persistent ICMP Destination Unreachable messages for a destination ++ * in its Binding Cache, the binding should be deleted. See draft ++ * section 8.8. ++ **/ ++static int mipv6_icmpv6_rcv_dest_unreach(struct sk_buff *skb) ++{ ++ struct icmp6hdr *icmph = (struct icmp6hdr *) skb->h.raw; ++ struct ipv6hdr *ipv6h = (struct ipv6hdr *) (icmph + 1); ++ int left = (skb->tail - skb->h.raw) - sizeof(*icmph)- sizeof(ipv6h); ++ struct ipv6_opt_hdr *eh; ++ struct rt2_hdr *rt2h = NULL; ++ struct in6_addr *daddr = &ipv6h->daddr; ++ struct in6_addr *saddr = &ipv6h->saddr; ++ int hdrlen, nexthdr = ipv6h->nexthdr; ++ struct mipv6_bce bce; ++ DEBUG_FUNC(); ++ ++ eh = (struct ipv6_opt_hdr *) (ipv6h + 1); ++ ++ while (left > 0) { ++ if (nexthdr != NEXTHDR_HOP && nexthdr != NEXTHDR_DEST && ++ nexthdr != NEXTHDR_ROUTING) ++ return 0; ++ ++ hdrlen = ipv6_optlen(eh); ++ if (hdrlen > left) ++ return 0; ++ ++ if (nexthdr == NEXTHDR_ROUTING) { ++ struct ipv6_rt_hdr *rth = (struct ipv6_rt_hdr *) eh; ++ ++ if (rth->type == IPV6_SRCRT_TYPE_2) { ++ if (hdrlen != sizeof(struct rt2_hdr)) ++ return 0; ++ ++ rt2h = (struct rt2_hdr *) rth; ++ ++ if (rt2h->rt_hdr.segments_left > 0) ++ daddr = &rt2h->addr; ++ break; ++ } ++ } ++ /* check for home address option in case this node is a MN */ ++ if (nexthdr == NEXTHDR_DEST) { ++ __u8 *raw = (__u8 *) eh; ++ __u16 i = 2; ++ while (1) { ++ struct mipv6_dstopt_homeaddr *hao; ++ ++ if (i + sizeof (*hao) > hdrlen) ++ break; ++ ++ hao = (struct mipv6_dstopt_homeaddr *) &raw[i]; ++ ++ if (hao->type == MIPV6_TLV_HOMEADDR && ++ hao->length == sizeof(struct in6_addr)) { ++ saddr = &hao->addr; ++ break; ++ } ++ if (hao->type) ++ i += hao->length + 2; ++ else ++ i++; ++ } ++ ++ } ++ nexthdr = eh->nexthdr; ++ eh = (struct ipv6_opt_hdr *) ((u8 *) eh + hdrlen); ++ left -= hdrlen; ++ } ++ if (rt2h == NULL) return 0; ++ ++ if (mipv6_bcache_get(daddr, saddr, &bce) == 0 && !(bce.flags&HOME_REGISTRATION)) { ++ /* A primitive algorithm for detecting persistent ICMP destination unreachable messages */ ++ if (bce.destunr_count && ++ time_after(jiffies, ++ bce.last_destunr + MIPV6_DEST_UNR_IVAL*HZ)) ++ bce.destunr_count = 0; ++ ++ bce.destunr_count++; ++ ++ mipv6_bcache_icmp_err(daddr, saddr, bce.destunr_count); ++ ++ if (bce.destunr_count > MIPV6_MAX_DESTUNREACH && mipv6_bcache_delete(daddr, saddr, CACHE_ENTRY) == 0) { ++ DEBUG(DBG_INFO, "Deleted bcache entry " ++ "%x:%x:%x:%x:%x:%x:%x:%x " ++ "%x:%x:%x:%x:%x:%x:%x:%x (reason: " ++ "%d dest unreachables) ", ++ NIPV6ADDR(daddr), NIPV6ADDR(saddr), bce.destunr_count); ++ } ++ } ++ return 0; ++} ++ ++static int mipv6_icmpv6_getfrag(const void *data, struct in6_addr *saddr, ++ char *buff, unsigned int offset, ++ unsigned int len) ++{ ++ struct mipv6_icmpv6_msg *msg = (struct mipv6_icmpv6_msg *) data; ++ struct icmp6hdr *icmph; ++ __u32 csum; ++ ++ if (offset) { ++ msg->csum = csum_partial_copy_nocheck(msg->data + offset - ++ sizeof(*icmph), buff, ++ len, msg->csum); ++ return 0; ++ } ++ ++ csum = csum_partial_copy_nocheck((__u8 *) &msg->icmph, buff, ++ sizeof(*icmph), msg->csum); ++ ++ csum = csum_partial_copy_nocheck(msg->data, buff + sizeof(*icmph), ++ len - sizeof(*icmph), csum); ++ ++ icmph = (struct icmp6hdr *) buff; ++ ++ icmph->icmp6_cksum = csum_ipv6_magic(saddr, msg->daddr, msg->len, ++ IPPROTO_ICMPV6, csum); ++ return 0; ++} ++ ++/** ++ * mipv6_icmpv6_send - generic icmpv6 message send ++ * @daddr: destination address ++ * @saddr: source address ++ * @type: icmp type ++ * @code: icmp code ++ * @id: packet identifier. If null, uses internal counter to get new id ++ * @data: packet data ++ * @datalen: length of data in bytes ++ */ ++void mipv6_icmpv6_send(struct in6_addr *daddr, struct in6_addr *saddr, int type, ++ int code, __u16 *id, __u16 flags, void *data, int datalen) ++{ ++ struct sock *sk = mipv6_icmpv6_socket->sk; ++ struct flowi fl; ++ struct mipv6_icmpv6_msg msg; ++ ++ DEBUG_FUNC(); ++ ++ fl.proto = IPPROTO_ICMPV6; ++ fl.fl6_dst = daddr; ++ fl.fl6_src = saddr; ++ fl.fl6_flowlabel = 0; ++ fl.uli_u.icmpt.type = type; ++ fl.uli_u.icmpt.code = code; ++ ++ msg.icmph.icmp6_type = type; ++ msg.icmph.icmp6_code = code; ++ msg.icmph.icmp6_cksum = 0; ++ ++ if (id) ++ msg.icmph.icmp6_identifier = htons(*id); ++ else ++ msg.icmph.icmp6_identifier = htons(identifier++); ++ ++ msg.icmph.icmp6_sequence = htons(flags); ++ msg.data = data; ++ msg.csum = 0; ++ msg.len = datalen + sizeof(struct icmp6hdr); ++ msg.daddr = daddr; ++ ++ if (mipv6_icmpv6_xmit_lock()) ++ return; ++ ++ ip6_build_xmit(sk, mipv6_icmpv6_getfrag, &msg, &fl, msg.len, NULL, -1, ++ MSG_DONTWAIT); ++ ++ ICMP6_INC_STATS_BH(Icmp6OutMsgs); ++ mipv6_icmpv6_xmit_unlock(); ++} ++ ++/** ++ * icmp6_rcv - ICMPv6 receive and multiplex ++ * @skb: buffer containing ICMP message ++ * ++ * Generic ICMPv6 receive function to multiplex messages to approriate ++ * handlers. Only used for ICMP messages with special handling in ++ * Mobile IPv6. ++ **/ ++static void icmp6_rcv(struct sk_buff *skb) ++{ ++ struct icmp6hdr *hdr; ++ ++ if (skb_is_nonlinear(skb) && ++ skb_linearize(skb, GFP_ATOMIC) != 0) { ++ kfree_skb(skb); ++ return; ++ } ++ __skb_push(skb, skb->data-skb->h.raw); ++ ++ hdr = (struct icmp6hdr *) skb->h.raw; ++ ++ switch (hdr->icmp6_type) { ++ case ICMPV6_DEST_UNREACH: ++ mipv6_icmpv6_rcv_dest_unreach(skb); ++ break; ++ ++ case ICMPV6_PARAMPROB: ++ mip6_fn.icmpv6_paramprob_rcv(skb); ++ break; ++ ++ case MIPV6_DHAAD_REPLY: ++ mip6_fn.icmpv6_dhaad_rep_rcv(skb); ++ break; ++ ++ case MIPV6_PREFIX_ADV: ++ mip6_fn.icmpv6_pfxadv_rcv(skb); ++ break; ++ ++ case MIPV6_DHAAD_REQUEST: ++ mip6_fn.icmpv6_dhaad_req_rcv(skb); ++ break; ++ ++ case MIPV6_PREFIX_SOLICIT: ++ mip6_fn.icmpv6_pfxsol_rcv(skb); ++ break; ++ } ++} ++ ++int mipv6_icmpv6_init(void) ++{ ++ struct sock *sk; ++ int err; ++ ++ if ((mipv6_icmpv6_socket = sock_alloc()) == NULL) { ++ DEBUG(DBG_ERROR, "Cannot allocate mipv6_icmpv6_socket"); ++ return -1; ++ } ++ mipv6_icmpv6_socket->type = SOCK_RAW; ++ ++ if ((err = sock_create(PF_INET6, SOCK_RAW, IPPROTO_ICMP, ++ &mipv6_icmpv6_socket)) < 0) { ++ DEBUG(DBG_ERROR, "Cannot initialize mipv6_icmpv6_socket"); ++ sock_release(mipv6_icmpv6_socket); ++ mipv6_icmpv6_socket = NULL; /* For safety */ ++ return err; ++ } ++ sk = mipv6_icmpv6_socket->sk; ++ sk->allocation = GFP_ATOMIC; ++ sk->prot->unhash(sk); ++ ++ /* Register our ICMP handler */ ++ MIPV6_SETCALL(mipv6_icmp_rcv, icmp6_rcv); ++ return 0; ++} ++ ++void mipv6_icmpv6_exit(void) ++{ ++ MIPV6_RESETCALL(mipv6_icmp_rcv); ++ if (mipv6_icmpv6_socket) ++ sock_release(mipv6_icmpv6_socket); ++ mipv6_icmpv6_socket = NULL; /* For safety */ ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp.h linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp.h 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,43 @@ ++/* ++ * MIPL Mobile IPv6 ICMP send and receive prototypes ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _MIPV6_ICMP ++#define _MIPV6_ICMP ++ ++#include ++#include ++ ++void mipv6_icmpv6_send(struct in6_addr *daddr, struct in6_addr *saddr, ++ int type, int code, __u16 *id, __u16 flags, ++ void *data, int datalen); ++ ++void mipv6_icmpv6_send_dhaad_req(struct in6_addr *home_addr, int plen, __u16 dhaad_id); ++ ++void mipv6_icmpv6_send_dhaad_rep(int ifindex, __u16 id, struct in6_addr *daddr); ++/* No handling */ ++int mipv6_icmpv6_no_rcv(struct sk_buff *skb); ++ ++/* Receive DHAAD Reply message */ ++int mipv6_icmpv6_rcv_dhaad_rep(struct sk_buff *skb); ++/* Receive Parameter Problem message */ ++int mipv6_icmpv6_rcv_paramprob(struct sk_buff *skb); ++/* Receive prefix advertisements */ ++int mipv6_icmpv6_rcv_pfx_adv(struct sk_buff *skb); ++ ++/* Receive DHAAD Request message */ ++int mipv6_icmpv6_rcv_dhaad_req(struct sk_buff *skb); ++/* Receive prefix solicitations */ ++int mipv6_icmpv6_rcv_pfx_sol(struct sk_buff *skb); ++ ++int mipv6_icmpv6_init(void); ++void mipv6_icmpv6_exit(void); ++ ++#endif +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp_ha.c linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp_ha.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp_ha.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp_ha.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,158 @@ ++/* ++ * Home Agent specific ICMP routines ++ * ++ * Authors: ++ * Antti Tuominen ++ * Jaakko Laine ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "halist.h" ++#include "debug.h" ++#include "mipv6_icmp.h" ++//#include "prefix.h" ++ ++/* Is this the easiest way of checking on ++ * which interface an anycast address is ? ++ */ ++static int find_ac_dev(struct in6_addr *addr) ++{ ++ int ifindex = 0; ++ struct net_device *dev; ++ read_lock(&dev_base_lock); ++ for (dev=dev_base; dev; dev=dev->next) { ++ if (ipv6_chk_acast_addr(dev, addr)) { ++ ifindex = dev->ifindex; ++ break; ++ } ++ } ++ read_unlock(&dev_base_lock); ++ return ifindex; ++} ++ ++/** ++ * mipv6_icmpv6_send_dhaad_rep - Reply to DHAAD Request ++ * @ifindex: index of interface request was received from ++ * @id: request's identification number ++ * @daddr: requester's IPv6 address ++ * ++ * When Home Agent receives Dynamic Home Agent Address Discovery ++ * request, it replies with a list of home agents available on the ++ * home link. ++ */ ++void mipv6_icmpv6_send_dhaad_rep(int ifindex, __u16 id, struct in6_addr *daddr) ++{ ++ __u8 *data = NULL; ++ struct in6_addr home, *ha_addrs = NULL; ++ int addr_count, max_addrs, size = 0; ++ ++ if (daddr == NULL) ++ return; ++ ++ if (mipv6_ha_get_addr(ifindex, &home) < 0) { ++ DEBUG(DBG_INFO, "Not Home Agent in this interface"); ++ return; ++ } ++ ++ /* We send all available HA addresses, not exceeding a maximum ++ * number we can fit in a packet with minimum IPv6 MTU (to ++ * avoid fragmentation). ++ */ ++ max_addrs = 76; ++ addr_count = mipv6_ha_get_pref_list(ifindex, &ha_addrs, max_addrs); ++ ++ if (addr_count < 0) return; ++ ++ if (addr_count != 0 && ha_addrs == NULL) { ++ DEBUG(DBG_ERROR, "addr_count = %d but return no addresses", ++ addr_count); ++ return; ++ } ++ data = (u8 *)ha_addrs; ++ ++ size = addr_count * sizeof(struct in6_addr); ++ ++ mipv6_icmpv6_send(daddr, &home, MIPV6_DHAAD_REPLY, ++ 0, &id, 0, data, size); ++ if (ha_addrs) { ++ data = NULL; ++ kfree(ha_addrs); ++ } ++} ++ ++/** ++ * mipv6_icmpv6_dhaad_req - Home Agent Address Discovery Request ICMP handler ++ * @skb: buffer containing ICMP information message ++ * ++ * Special Mobile IPv6 ICMP message. Handles Dynamic Home Agent ++ * Address Discovery Request messages. ++ **/ ++int mipv6_icmpv6_rcv_dhaad_req(struct sk_buff *skb) ++{ ++ struct icmp6hdr *phdr = (struct icmp6hdr *) skb->h.raw; ++ struct in6_addr *saddr = &skb->nh.ipv6h->saddr; ++ struct in6_addr *daddr = &skb->nh.ipv6h->daddr; ++ __u16 identifier; ++ int ifindex = 0; ++ ++ DEBUG_FUNC(); ++ ++ /* Invalid packet checks. */ ++ if (phdr->icmp6_code != 0) ++ return 0; ++ ++ identifier = ntohs(phdr->icmp6_identifier); ++ ++ /* ++ * Make sure we have the right ifindex (if the ++ * req came through another interface. ++ */ ++ ifindex = find_ac_dev(daddr); ++ if (ifindex == 0) { ++ DEBUG(DBG_WARNING, "received dhaad request to anycast address %x:%x:%x:%x:%x:%x:%x:%x" ++ " on which prefix we are not HA", ++ NIPV6ADDR(daddr)); ++ return 0; ++ } ++ ++ /* ++ * send reply with list ++ */ ++ mipv6_icmpv6_send_dhaad_rep(ifindex, identifier, saddr); ++ return 1; ++} ++#if 0 ++/** ++ * mipv6_icmpv6_handle_pfx_sol - handle prefix solicitations ++ * @skb: sk_buff including the icmp6 message ++ */ ++int mipv6_icmpv6_rcv_pfx_sol(struct sk_buff *skb) ++{ ++ struct in6_addr *saddr = &skb->nh.ipv6h->saddr; ++ struct in6_addr *daddr = &skb->nh.ipv6h->daddr; ++ struct inet6_ifaddr *ifp; ++ ++ DEBUG_FUNC(); ++ ++ if (!(ifp = ipv6_get_ifaddr(daddr, NULL))) ++ return -1; ++ ++ in6_ifa_put(ifp); ++ mipv6_pfx_cancel_send(saddr, -1); ++ ++ return 0; ++} ++#endif +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp_mn.c linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp_mn.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp_mn.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp_mn.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,273 @@ ++/* ++ * Mobile Node specific ICMP routines ++ * ++ * Authors: ++ * Antti Tuominen ++ * Jaakko Laine ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "mn.h" ++#include "bul.h" ++#include "mdetect.h" ++#include "debug.h" ++#include "mipv6_icmp.h" ++#include "util.h" ++//#include "prefix.h" ++ ++#define INFINITY 0xffffffff ++ ++/** ++ * mipv6_icmpv6_paramprob - Parameter Problem ICMP error message handler ++ * @skb: buffer containing ICMP error message ++ * ++ * Special Mobile IPv6 ICMP handling. If Mobile Node receives ICMP ++ * Parameter Problem message when using a Home Address Option, ++ * offending node should be logged and error message dropped. If ++ * error is received because of a Binding Update, offending node ++ * should be recorded in Binding Update List and no more Binding ++ * Updates should be sent to this destination. See RFC 3775 section ++ * 10.15. ++ **/ ++int mipv6_icmpv6_rcv_paramprob(struct sk_buff *skb) ++{ ++ struct icmp6hdr *phdr = (struct icmp6hdr *) skb->h.raw; ++ struct in6_addr *saddr = skb ? &skb->nh.ipv6h->saddr : NULL; ++ struct in6_addr *daddr = skb ? &skb->nh.ipv6h->daddr : NULL; ++ struct ipv6hdr *hdr = (struct ipv6hdr *) (phdr + 1); ++ int ulen = (skb->tail - (unsigned char *) (phdr + 1)); ++ ++ int errptr; ++ __u8 *off_octet; ++ ++ DEBUG_FUNC(); ++ ++ /* We only handle code 1 & 2 messages. */ ++ if (phdr->icmp6_code != ICMPV6_UNK_NEXTHDR && ++ phdr->icmp6_code != ICMPV6_UNK_OPTION) ++ return 0; ++ ++ /* Find offending octet in the original packet. */ ++ errptr = ntohl(phdr->icmp6_pointer); ++ ++ /* There is not enough of the original packet left to figure ++ * out what went wrong. Bail out. */ ++ if (ulen <= errptr) ++ return 0; ++ ++ off_octet = ((__u8 *) hdr + errptr); ++ DEBUG(DBG_INFO, "Parameter problem: offending octet %d [0x%2x]", ++ errptr, *off_octet); ++ ++ /* If CN did not understand Mobility Header, set BUL entry to ++ * ACK_ERROR so no further BUs are sumbitted to this CN. */ ++ if (phdr->icmp6_code == ICMPV6_UNK_NEXTHDR && ++ *off_octet == IPPROTO_MOBILITY) { ++ struct bul_inval_args args; ++ args.all_rr_states = 1; ++ args.cn = saddr; ++ args.mn = daddr; ++ write_lock(&bul_lock); ++ mipv6_bul_iterate(mn_bul_invalidate, &args); ++ write_unlock(&bul_lock); ++ } ++ ++ /* If CN did not understand Home Address Option, we log an ++ * error and discard the error message. */ ++ if (phdr->icmp6_code == ICMPV6_UNK_OPTION && ++ *off_octet == MIPV6_TLV_HOMEADDR) { ++ DEBUG(DBG_WARNING, "Correspondent node does not " ++ "implement Home Address Option receipt."); ++ return 1; ++ } ++ return 0; ++} ++ ++/** ++ * mipv6_mn_dhaad_send_req - Send DHAAD Request to home network ++ * @home_addr: address to do DHAAD for ++ * @plen: prefix length for @home_addr ++ * ++ * Send Dynamic Home Agent Address Discovery Request to the Home ++ * Agents anycast address in the nodes home network. ++ **/ ++void ++mipv6_icmpv6_send_dhaad_req(struct in6_addr *home_addr, int plen, __u16 dhaad_id) ++{ ++ struct in6_addr ha_anycast; ++ struct in6_addr careofaddr; ++ ++ if (mipv6_get_care_of_address(home_addr, &careofaddr) < 0) { ++ DEBUG(DBG_WARNING, "Could not get node's Care-of Address"); ++ return; ++ } ++ ++ if (mipv6_ha_anycast(&ha_anycast, home_addr, plen) < 0) { ++ DEBUG(DBG_WARNING, ++ "Could not get Home Agent Anycast address for home address %x:%x.%x:%x:%x:%x:%x:%x/%d", ++ NIPV6ADDR(home_addr), plen); ++ return; ++ } ++ ++ mipv6_icmpv6_send(&ha_anycast, &careofaddr, MIPV6_DHAAD_REQUEST, 0, ++ &dhaad_id, 0, NULL, 0); ++ ++} ++ ++/** ++ * mipv6_icmpv6_dhaad_rep - Home Agent Address Discovery Reply ICMP handler ++ * @skb: buffer containing ICMP information message ++ * ++ * Special Mobile IPv6 ICMP message. Handles Dynamic Home Agent ++ * Address Discovery Reply messages. ++ **/ ++int mipv6_icmpv6_rcv_dhaad_rep(struct sk_buff *skb) ++{ ++ struct icmp6hdr *phdr = (struct icmp6hdr *) skb->h.raw; ++ struct in6_addr *address; ++ struct in6_addr *saddr = &skb->nh.ipv6h->saddr; ++ __u16 identifier; ++ int ulen = (skb->tail - (unsigned char *) ((__u32 *) phdr + 2)); ++ int i; ++ struct in6_addr home_addr, coa; ++ struct in6_addr *first_ha = NULL; ++ struct mn_info *minfo; ++ int n_addr = ulen / sizeof(struct in6_addr); ++ ++ DEBUG_FUNC(); ++ ++ /* Invalid packet checks. */ ++ if (ulen % sizeof(struct in6_addr) != 0) ++ return 0; ++ ++ if (phdr->icmp6_code != 0) ++ return 0; ++ ++ identifier = ntohs(phdr->icmp6_identifier); ++ if (ulen > 0) { ++ address = (struct in6_addr *) ((__u32 *) phdr + 2); ++ } else { ++ address = saddr; ++ n_addr = 1; ++ } ++ ++ /* receive list of home agent addresses ++ * add to home agents list ++ */ ++ DEBUG(DBG_INFO, "DHAAD: got %d home agents", n_addr); ++ ++ first_ha = address; ++ ++ /* lookup H@ with identifier */ ++ read_lock(&mn_info_lock); ++ minfo = mipv6_mninfo_get_by_id(identifier); ++ if (!minfo) { ++ read_unlock(&mn_info_lock); ++ DEBUG(DBG_INFO, "no mninfo with id %d", ++ identifier); ++ return 0; ++ } ++ spin_lock(&minfo->lock); ++ ++ /* Logic: ++ * 1. if old HA on list, prefer it ++ * 2. otherwise first HA on list prefered ++ */ ++ for (i = 0; i < n_addr; i++) { ++ DEBUG(DBG_INFO, "HA[%d] %x:%x:%x:%x:%x:%x:%x:%x", ++ i, NIPV6ADDR(address)); ++ if (ipv6_addr_cmp(&minfo->ha, address) == 0) { ++ spin_unlock(&minfo->lock); ++ read_unlock(&mn_info_lock); ++ return 0; ++ } ++ address++; ++ } ++ ipv6_addr_copy(&minfo->ha, first_ha); ++ spin_unlock(&minfo->lock); ++ ipv6_addr_copy(&home_addr, &minfo->home_addr); ++ read_unlock(&mn_info_lock); ++ ++ mipv6_get_care_of_address(&home_addr, &coa); ++ init_home_registration(&home_addr, &coa); ++ ++ return 1; ++} ++#if 0 ++/** ++ * mipv6_icmpv6_handle_pfx_adv - handle prefix advertisements ++ * @skb: sk_buff including the icmp6 message ++ */ ++int mipv6_icmpv6_rcv_pfx_adv(struct sk_buff *skb) ++{ ++ struct icmp6hdr *hdr = (struct icmp6hdr *) skb->h.raw; ++ struct in6_addr *saddr = &skb->nh.ipv6h->saddr; ++ struct in6_addr *daddr = &skb->nh.ipv6h->daddr; ++ __u8 *opt = (__u8 *) (hdr + 1); ++ int optlen = (skb->tail - opt); ++ unsigned long min_expire = INFINITY; ++ struct inet6_skb_parm *parm = (struct inet6_skb_parm *) skb->cb; ++ ++ DEBUG_FUNC(); ++ ++ while (optlen > 0) { ++ int len = opt[1] << 3; ++ if (len == 0) ++ goto set_timer; ++ ++ if (opt[0] == ND_OPT_PREFIX_INFO) { ++ int ifindex; ++ unsigned long expire; ++ struct prefix_info *pinfo = ++ (struct prefix_info *) opt; ++ struct net_device *dev; ++ struct mn_info *mninfo; ++ ++ read_lock(&mn_info_lock); ++ mninfo = mipv6_mninfo_get_by_ha(saddr); ++ if (mninfo == NULL) { ++ ifindex = 0; ++ } else { ++ spin_lock(&mninfo->lock); ++ ifindex = mninfo->ifindex; ++ spin_unlock(&mninfo->lock); ++ mninfo = NULL; ++ } ++ read_unlock(&mn_info_lock); ++ ++ if (!(dev = dev_get_by_index(ifindex))) { ++ DEBUG(DBG_WARNING, "Cannot find device by index %d", parm->iif); ++ goto nextopt; ++ } ++ ++ expire = ntohl(pinfo->valid); ++ expire = expire == 0 ? INFINITY : expire; ++ ++ min_expire = expire < min_expire ? expire : min_expire; ++ ++ dev_put(dev); ++ } ++ ++nextopt: ++ optlen -= len; ++ opt += len; ++ } ++ ++set_timer: ++ ++ mipv6_pfx_add_home(parm->iif, saddr, daddr, min_expire); ++ return 0; ++} ++#endif +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mn.c linux-2.4.25/net/ipv6/mobile_ip6/mn.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/mn.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/mn.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,1521 @@ ++/* ++ * Mobile-node functionality ++ * ++ * Authors: ++ * Sami Kivisaari ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "util.h" ++#include "mdetect.h" ++#include "bul.h" ++#include "mobhdr.h" ++#include "debug.h" ++#include "mn.h" ++#include "mipv6_icmp.h" ++#include "multiaccess_ctl.h" ++//#include "prefix.h" ++#include "tunnel_mn.h" ++#include "stats.h" ++#include "config.h" ++ ++#define MIPV6_BUL_SIZE 128 ++ ++static LIST_HEAD(mn_info_list); ++ ++/* Lock for list of MN infos */ ++rwlock_t mn_info_lock = RW_LOCK_UNLOCKED; ++ ++static spinlock_t ifrh_lock = SPIN_LOCK_UNLOCKED; ++ ++struct ifr_holder { ++ struct list_head list; ++ struct in6_ifreq ifr; ++ int old_ifi; ++ struct handoff *ho; ++}; ++ ++LIST_HEAD(ifrh_list); ++ ++static struct tq_struct mv_home_addr_task; ++ ++/* Determines whether manually configured home addresses are preferred as ++ * source addresses over dynamically configured ones ++ */ ++int mipv6_use_preconfigured_hoaddr = 1; ++ ++/* Determines whether home addresses, which are at home are preferred as ++ * source addresses over other home addresses ++ */ ++int mipv6_use_topol_corr_hoaddr = 0; ++ ++static spinlock_t icmpv6_id_lock = SPIN_LOCK_UNLOCKED; ++static __u16 icmpv6_id = 0; ++ ++static inline __u16 mipv6_get_dhaad_id(void) ++{ ++ __u16 ret; ++ spin_lock_bh(&icmpv6_id_lock); ++ ret = ++icmpv6_id; ++ spin_unlock_bh(&icmpv6_id_lock); ++ return ret; ++} ++ ++/** ++ * mipv6_mninfo_get_by_home - Returns mn_info for a home address ++ * @haddr: home address of MN ++ * ++ * Returns mn_info on success %NULL otherwise. Caller MUST hold ++ * @mn_info_lock (read or write). ++ **/ ++struct mn_info *mipv6_mninfo_get_by_home(struct in6_addr *haddr) ++{ ++ struct list_head *lh; ++ struct mn_info *minfo; ++ ++ DEBUG_FUNC(); ++ ++ if (!haddr) ++ return NULL; ++ ++ list_for_each(lh, &mn_info_list) { ++ minfo = list_entry(lh, struct mn_info, list); ++ spin_lock(&minfo->lock); ++ if (!ipv6_addr_cmp(&minfo->home_addr, haddr)) { ++ spin_unlock(&minfo->lock); ++ return minfo; ++ } ++ spin_unlock(&minfo->lock); ++ } ++ return NULL; ++} ++ ++/** ++ * mipv6_mninfo_get_by_ha - Lookup mn_info with Home Agent address ++ * @home_agent: Home Agent address ++ * ++ * Searches for a mn_info entry with @ha set to @home_agent. You MUST ++ * hold @mn_info_lock when calling this function. Returns pointer to ++ * mn_info entry or %NULL on failure. ++ **/ ++struct mn_info *mipv6_mninfo_get_by_ha(struct in6_addr *home_agent) ++{ ++ struct list_head *lh; ++ struct mn_info *minfo; ++ ++ if (!home_agent) ++ return NULL; ++ ++ list_for_each(lh, &mn_info_list) { ++ minfo = list_entry(lh, struct mn_info, list); ++ spin_lock(&minfo->lock); ++ if (!ipv6_addr_cmp(&minfo->ha, home_agent)) { ++ spin_unlock(&minfo->lock); ++ return minfo; ++ } ++ spin_unlock(&minfo->lock); ++ } ++ return NULL; ++} ++ ++/** ++ * mipv6_mninfo_get_by_id - Lookup mn_info with id ++ * @id: DHAAD identifier ++ * ++ * Searches for a mn_info entry with @dhaad_id set to @id. You MUST ++ * hold @mn_info_lock when calling this function. Returns pointer to ++ * mn_info entry or %NULL on failure. ++ **/ ++struct mn_info *mipv6_mninfo_get_by_id(unsigned short id) ++{ ++ struct list_head *lh; ++ struct mn_info *minfo = 0; ++ ++ list_for_each(lh, &mn_info_list) { ++ minfo = list_entry(lh, struct mn_info, list); ++ spin_lock(&minfo->lock); ++ if (minfo->dhaad_id == id) { ++ spin_unlock(&minfo->lock); ++ return minfo; ++ } ++ spin_unlock(&minfo->lock); ++ } ++ return NULL; ++} ++ ++/** ++ * mipv6_mninfo_add - Adds a new home info for MN ++ * @ifindex: Interface for home address ++ * @home_addr: Home address of MN, must be set ++ * @plen: prefix length of the home address, must be set ++ * @isathome : home address at home ++ * @lifetime: lifetime of the home address, 0 is infinite ++ * @ha: home agent for the home address ++ * @ha_plen: prefix length of home agent's address, can be zero ++ * @ha_lifetime: Lifetime of the home address, 0 is infinite ++ * ++ * The function adds a new home info entry for MN, allowing it to ++ * register the home address with the home agent. Starts home ++ * registration process. If @ha is %ADDRANY, DHAAD is performed to ++ * find a home agent. Returns 0 on success, a negative value ++ * otherwise. Caller MUST NOT hold @mn_info_lock or ++ * @addrconf_hash_lock. ++ **/ ++void mipv6_mninfo_add(int ifindex, struct in6_addr *home_addr, int plen, ++ int isathome, unsigned long lifetime, struct in6_addr *ha, ++ int ha_plen, unsigned long ha_lifetime, int man_conf) ++{ ++ struct mn_info *minfo; ++ struct in6_addr coa; ++ ++ DEBUG_FUNC(); ++ ++ write_lock_bh(&mn_info_lock); ++ if ((minfo = mipv6_mninfo_get_by_home(home_addr)) != NULL){ ++ DEBUG(1, "MN info already exists"); ++ write_unlock_bh(&mn_info_lock); ++ return; ++ } ++ minfo = kmalloc(sizeof(struct mn_info), GFP_ATOMIC); ++ if (!minfo) { ++ write_unlock_bh(&mn_info_lock); ++ return; ++ } ++ memset(minfo, 0, sizeof(struct mn_info)); ++ spin_lock_init(&minfo->lock); ++ ++ ++ ipv6_addr_copy(&minfo->home_addr, home_addr); ++ ++ if (ha) ++ ipv6_addr_copy(&minfo->ha, ha); ++ if (ha_plen < 128 && ha_plen > 0) ++ minfo->home_plen = ha_plen; ++ else minfo->home_plen = 64; ++ ++ minfo->ifindex_user = ifindex; /* Ifindex for tunnel interface */ ++ minfo->ifindex = ifindex; /* Interface on which home address is currently conf'd */ ++ /* TODO: we should get home address lifetime from somewhere */ ++ /* minfo->home_addr_expires = jiffies + lifetime * HZ; */ ++ ++ /* manual configuration flag cannot be unset by dynamic updates ++ * from prefix advertisements ++ */ ++ if (!minfo->man_conf) minfo->man_conf = man_conf; ++ minfo->is_at_home = isathome; ++ ++ list_add(&minfo->list, &mn_info_list); ++ write_unlock_bh(&mn_info_lock); ++ ++ if (mipv6_get_care_of_address(home_addr, &coa) == 0) ++ init_home_registration(home_addr, &coa); ++} ++ ++/** ++ * mipv6_mninfo_del - Delete home info for MN ++ * @home_addr : Home address or prefix ++ * @del_dyn_only : Delete only dynamically created home entries ++ * ++ * Deletes every mn_info entry that matches the first plen bits of ++ * @home_addr. Returns number of deleted entries on success and a ++ * negative value otherwise. Caller MUST NOT hold @mn_info_lock. ++ **/ ++int mipv6_mninfo_del(struct in6_addr *home_addr, int del_dyn_only) ++{ ++ struct list_head *lh, *next; ++ struct mn_info *minfo; ++ int ret = -1; ++ if (!home_addr) ++ return -1; ++ ++ write_lock(&mn_info_lock); ++ ++ list_for_each_safe(lh, next, &mn_info_list) { ++ minfo = list_entry(lh, struct mn_info, list); ++ if (ipv6_addr_cmp(&minfo->home_addr, home_addr) == 0 ++ && ((!minfo->man_conf && del_dyn_only) || !del_dyn_only)){ ++ list_del(&minfo->list); ++ kfree(minfo); ++ ret++; ++ } ++ } ++ write_unlock(&mn_info_lock); ++ return ret; ++} ++ ++void mipv6_mn_set_home(int ifindex, struct in6_addr *homeaddr, int plen, ++ struct in6_addr *homeagent, int ha_plen) ++{ ++ mipv6_mninfo_add(ifindex, homeaddr, plen, 0, 0, ++ homeagent, ha_plen, 0, 1); ++} ++ ++static int skip_dad(struct in6_addr *addr) ++{ ++ struct mn_info *minfo; ++ int ret = 0; ++ ++ if (addr == NULL) { ++ DEBUG(DBG_CRITICAL, "Null argument"); ++ return 0; ++ } ++ read_lock_bh(&mn_info_lock); ++ if ((minfo = mipv6_mninfo_get_by_home(addr)) != NULL) { ++ if ((minfo->is_at_home != MN_NOT_AT_HOME) && (minfo->has_home_reg)) ++ ret = 1; ++ DEBUG(DBG_INFO, "minfo->is_at_home = %d, minfo->has_home_reg = %d", ++ minfo->is_at_home, minfo->has_home_reg); ++ } ++ read_unlock_bh(&mn_info_lock); ++ ++ return ret; ++} ++/** ++ * mipv6_mn_is_home_addr - Determines if addr is node's home address ++ * @addr: IPv6 address ++ * ++ * Returns 1 if addr is node's home address. Otherwise returns zero. ++ **/ ++int mipv6_mn_is_home_addr(struct in6_addr *addr) ++{ ++ int ret = 0; ++ ++ if (addr == NULL) { ++ DEBUG(DBG_CRITICAL, "Null argument"); ++ return -1; ++ } ++ read_lock_bh(&mn_info_lock); ++ if (mipv6_mninfo_get_by_home(addr)) ++ ret = 1; ++ read_unlock_bh(&mn_info_lock); ++ ++ return (ret); ++} ++ ++/** ++ * mipv6_mn_is_at_home - determine if node is home for a home address ++ * @home_addr : home address of MN ++ * ++ * Returns 1 if home address in question is in the home network, 0 ++ * otherwise. Caller MUST NOT not hold @mn_info_lock. ++ **/ ++int mipv6_mn_is_at_home(struct in6_addr *home_addr) ++{ ++ struct mn_info *minfo; ++ int ret = 0; ++ read_lock_bh(&mn_info_lock); ++ if ((minfo = mipv6_mninfo_get_by_home(home_addr)) != NULL) { ++ spin_lock(&minfo->lock); ++ ret = (minfo->is_at_home == MN_AT_HOME); ++ spin_unlock(&minfo->lock); ++ } ++ read_unlock_bh(&mn_info_lock); ++ return ret; ++} ++void mipv6_mn_set_home_reg(struct in6_addr *home_addr, int has_home_reg) ++{ ++ struct mn_info *minfo; ++ read_lock_bh(&mn_info_lock); ++ ++ if ((minfo = mipv6_mninfo_get_by_home(home_addr)) != NULL) { ++ spin_lock(&minfo->lock); ++ minfo->has_home_reg = has_home_reg; ++ spin_unlock(&minfo->lock); ++ } ++ read_unlock_bh(&mn_info_lock); ++} ++ ++static int mn_inet6addr_event( ++ struct notifier_block *nb, unsigned long event, void *ptr) ++{ ++ struct inet6_ifaddr *ifp = (struct inet6_ifaddr *)ptr; ++ ++ switch (event) { ++ case NETDEV_UP: ++ /* Is address a valid coa ?*/ ++ if (!(ifp->flags & IFA_F_TENTATIVE)) ++ mipv6_mdet_finalize_ho(&ifp->addr, ++ ifp->idev->dev->ifindex); ++ else if(skip_dad(&ifp->addr)) ++ ifp->flags &= ~IFA_F_TENTATIVE; ++ break; ++ case NETDEV_DOWN: ++#if 0 ++ /* This is useless with manually configured home ++ addresses, which will not expire ++ */ ++ mipv6_mninfo_del(&ifp->addr, 0); ++#endif ++ break; ++ ++ } ++ ++ return NOTIFY_DONE; ++} ++ ++struct notifier_block mipv6_mn_inet6addr_notifier = { ++ mn_inet6addr_event, ++ NULL, ++ 0 /* check if using zero is ok */ ++}; ++ ++static void mipv6_get_saddr_hook(struct in6_addr *homeaddr) ++{ ++ int found = 0, reiter = 0; ++ struct list_head *lh; ++ struct mn_info *minfo = NULL; ++ struct in6_addr coa; ++ ++ read_lock_bh(&mn_info_lock); ++restart: ++ list_for_each(lh, &mn_info_list) { ++ minfo = list_entry(lh, struct mn_info, list); ++ if ((ipv6_addr_scope(homeaddr) != ipv6_addr_scope(&minfo->home_addr)) ++ || ipv6_chk_addr(&minfo->home_addr, NULL) == 0) ++ continue; ++ ++ spin_lock(&minfo->lock); ++ if (minfo->is_at_home == MN_AT_HOME || minfo->has_home_reg) { ++ if ((mipv6_use_topol_corr_hoaddr && ++ minfo->is_at_home == MN_AT_HOME) || ++ (mipv6_use_preconfigured_hoaddr && ++ minfo->man_conf) || ++ (!(mipv6_use_preconfigured_hoaddr || ++ mipv6_use_topol_corr_hoaddr) || reiter)) { ++ spin_unlock(&minfo->lock); ++ ipv6_addr_copy(homeaddr, &minfo->home_addr); ++ found = 1; ++ break; ++ } ++ } ++ spin_unlock(&minfo->lock); ++ } ++ if (!found && !reiter) { ++ reiter = 1; ++ goto restart; ++ } ++ ++ if (!found && minfo && ++ !mipv6_get_care_of_address(&minfo->home_addr, &coa)) { ++ ipv6_addr_copy(homeaddr, &coa); ++ } ++ read_unlock_bh(&mn_info_lock); ++ ++ DEBUG(DBG_DATADUMP, "Source address selection: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(homeaddr)); ++ return; ++} ++ ++static void mv_home_addr(void *arg) ++{ ++ mm_segment_t oldfs; ++ int err = 0, new_if = 0; ++ struct list_head *lh, *next; ++ struct ifr_holder *ifrh; ++ LIST_HEAD(list); ++ ++ DEBUG(DBG_INFO, "mipv6 move home address task"); ++ ++ spin_lock_bh(&ifrh_lock); ++ list_splice_init(&ifrh_list, &list); ++ spin_unlock_bh(&ifrh_lock); ++ ++ oldfs = get_fs(); set_fs(KERNEL_DS); ++ list_for_each_safe(lh, next, &list) { ++ ifrh = list_entry(lh, struct ifr_holder, list); ++ if (ifrh->old_ifi) { ++ new_if = ifrh->ifr.ifr6_ifindex; ++ ifrh->ifr.ifr6_ifindex = ifrh->old_ifi; ++ err = addrconf_del_ifaddr(&ifrh->ifr); ++ ifrh->ifr.ifr6_ifindex = new_if; ++ if (err < 0) ++ DEBUG(DBG_WARNING, "removal of home address %x:%x:%x:%x:%x:%x:%x:%x from" ++ " old interface %d failed with status %d", ++ NIPV6ADDR(&ifrh->ifr.ifr6_addr), ifrh->old_ifi, err); ++ } ++ if(!err) { ++ err = addrconf_add_ifaddr(&ifrh->ifr); ++ } ++ if (ifrh->ho) { ++ DEBUG(DBG_INFO, "Calling mobile_node moved after moving home address to new if"); ++ mipv6_mobile_node_moved(ifrh->ho); ++ } ++ list_del(&ifrh->list); ++ kfree(ifrh); ++ } ++ set_fs(oldfs); ++ ++ if (err < 0) ++ DEBUG(DBG_WARNING, "adding of home address to a new interface %d failed %d", new_if, err); ++ else { ++ DEBUG(DBG_WARNING, "adding of home address to a new interface OK"); ++ } ++} ++ ++struct dhaad_halist { ++ struct list_head list; ++ struct in6_addr addr; ++ int retry; ++}; ++ ++/* clear all has from candidate list. do this when a new dhaad reply ++ * is received. */ ++int mipv6_mn_flush_ha_candidate(struct list_head *ha) ++{ ++ struct list_head *p, *tmp; ++ struct dhaad_halist *e; ++ ++ list_for_each_safe(p, tmp, ha) { ++ e = list_entry(p, struct dhaad_halist, list); ++ list_del(p); ++ kfree(e); ++ e = NULL; ++ } ++ return 0; ++} ++ ++/* add new ha to candidates. only done when dhaad reply is received. */ ++int mipv6_mn_add_ha_candidate(struct list_head *ha, struct in6_addr *addr) ++{ ++ struct dhaad_halist *e; ++ ++ e = kmalloc(sizeof(*e), GFP_ATOMIC); ++ memset(e, 0, sizeof(*e)); ++ ipv6_addr_copy(&e->addr, addr); ++ ++ list_add_tail(&e->list, ha); ++ return 0; ++} ++ ++#define MAX_RETRIES_PER_HA 3 ++ ++/* get next ha candidate. this is done when dhaad reply has been ++ * received and we want to register with the best available ha. */ ++int mipv6_mn_get_ha_candidate(struct list_head *ha, struct in6_addr *addr) ++{ ++ struct list_head *p; ++ ++ list_for_each(p, ha) { ++ struct dhaad_halist *e; ++ e = list_entry(p, typeof(*e), list); ++ if (e->retry >= 0 && e->retry < MAX_RETRIES_PER_HA) { ++ ipv6_addr_copy(addr, &e->addr); ++ return 0; ++ } ++ } ++ return -1; ++} ++ ++/* change candidate status. if registration with ha fails, we ++ * increase retry for ha candidate. if retry is >= 3 we set it to -1 ++ * (failed), do get_ha_candidate() again */ ++int mipv6_mn_try_ha_candidate(struct list_head *ha, struct in6_addr *addr) ++{ ++ struct list_head *p; ++ ++ list_for_each(p, ha) { ++ struct dhaad_halist *e; ++ e = list_entry(p, typeof(*e), list); ++ if (ipv6_addr_cmp(addr, &e->addr) == 0) { ++ if (e->retry >= MAX_RETRIES_PER_HA) e->retry = -1; ++ else if (e->retry >= 0) e->retry++; ++ return 0; ++ } ++ } ++ return -1; ++} ++ ++/** ++ * mipv6_mn_get_bulifetime - Get lifetime for a binding update ++ * @home_addr: home address for BU ++ * @coa: care-of address for BU ++ * @flags: flags used for BU ++ * ++ * Returns maximum lifetime for BUs determined by the lifetime of ++ * care-of address and the lifetime of home address. ++ **/ ++__u32 mipv6_mn_get_bulifetime(struct in6_addr *home_addr, struct in6_addr *coa, ++ __u8 flags) ++{ ++ struct inet6_ifaddr *ifp_hoa, *ifp_coa; ++ __u32 lifetime = (flags & MIPV6_BU_F_HOME ? ++ HA_BU_DEF_LIFETIME : CN_BU_DEF_LIFETIME); ++ ++ ifp_hoa = ipv6_get_ifaddr(home_addr, NULL); ++ if(!ifp_hoa) { ++ DEBUG(DBG_INFO, "home address missing"); ++ return 0; ++ } ++ if (!(ifp_hoa->flags & IFA_F_PERMANENT)){ ++ if (ifp_hoa->valid_lft) ++ lifetime = min_t(__u32, lifetime, ifp_hoa->valid_lft); ++ else ++ DEBUG(DBG_ERROR, "Zero lifetime for home address"); ++ } ++ in6_ifa_put(ifp_hoa); ++ ++ ifp_coa = ipv6_get_ifaddr(coa, NULL); ++ if (!ifp_coa) { ++ DEBUG(DBG_INFO, "care-of address missing"); ++ return 0; ++ } ++ if (!(ifp_coa->flags & IFA_F_PERMANENT)) { ++ if(ifp_coa->valid_lft) ++ lifetime = min_t(__u32, lifetime, ifp_coa->valid_lft); ++ else ++ DEBUG(DBG_ERROR, ++ "Zero lifetime for care-of address"); ++ } ++ in6_ifa_put(ifp_coa); ++ ++ DEBUG(DBG_INFO, "Lifetime for binding is %ld", lifetime); ++ return lifetime; ++} ++ ++static int ++mipv6_mn_tnl_rcv_send_bu_hook(struct ip6_tnl *t, struct sk_buff *skb) ++{ ++ struct ipv6hdr *inner; ++ struct ipv6hdr *outer = skb->nh.ipv6h; ++ struct mn_info *minfo = NULL; ++ __u32 lifetime; ++ __u8 user_flags = 0; ++ ++ DEBUG_FUNC(); ++ ++ if (!is_mip6_tnl(t)) ++ return IP6_TNL_ACCEPT; ++ ++ if (!mip6node_cnf.accept_ret_rout) { ++ DEBUG(DBG_INFO, "Return routability administratively disabled" ++ " not doing route optimization"); ++ return IP6_TNL_ACCEPT; ++ } ++ if (!pskb_may_pull(skb, skb->h.raw-skb->data+sizeof(*inner))) ++ return IP6_TNL_DROP; ++ ++ inner = (struct ipv6hdr *)skb->h.raw; ++ ++ read_lock(&mn_info_lock); ++ minfo = mipv6_mninfo_get_by_home(&inner->daddr); ++ ++ if (!minfo) { ++ DEBUG(DBG_WARNING, "MN info missing"); ++ read_unlock(&mn_info_lock); ++ return IP6_TNL_ACCEPT; ++ } ++ DEBUG(DBG_DATADUMP, "MIPV6 MN: Received a tunneled IPv6 packet" ++ " to %x:%x:%x:%x:%x:%x:%x:%x," ++ " from %x:%x:%x:%x:%x:%x:%x:%x with\n tunnel header" ++ "daddr: %x:%x:%x:%x:%x:%x:%x:%x," ++ "saddr: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&inner->daddr), NIPV6ADDR(&inner->saddr), ++ NIPV6ADDR(&outer->daddr), NIPV6ADDR(&outer->saddr)); ++ ++ spin_lock(&minfo->lock); ++ ++ /* We don't send bus in response to all tunneled packets */ ++ ++ if (!ipv6_addr_cmp(&minfo->ha, &inner->saddr)) { ++ spin_unlock(&minfo->lock); ++ read_unlock(&mn_info_lock); ++ DEBUG(DBG_ERROR, "HA BUG: Received a tunneled packet " ++ "originally sent by home agent, not sending BU"); ++ return IP6_TNL_ACCEPT; ++ } ++ spin_unlock(&minfo->lock); ++ read_unlock(&mn_info_lock); ++ ++ DEBUG(DBG_DATADUMP, "Sending BU to correspondent node"); ++ ++ user_flags |= mip6node_cnf.bu_cn_ack ? MIPV6_BU_F_ACK : 0; ++ ++ if (inner->nexthdr != IPPROTO_DSTOPTS && ++ inner->nexthdr != IPPROTO_MOBILITY) { ++ struct in6_addr coa; ++ /* Don't start RR when receiving ICMP error messages */ ++ if (inner->nexthdr == IPPROTO_ICMPV6) { ++ int ptr = (u8*)(inner+1) - skb->data; ++ u8 type; ++ ++ if (skb_copy_bits(skb, ++ ptr+offsetof(struct icmp6hdr, ++ icmp6_type), ++ &type, 1) ++ || !(type & ICMPV6_INFOMSG_MASK)) { ++ return IP6_TNL_ACCEPT; ++ } ++ } ++ lifetime = mipv6_mn_get_bulifetime(&inner->daddr, ++ &outer->daddr, 0); ++ if (lifetime && ++ !mipv6_get_care_of_address(&inner->daddr, &coa)) { ++ write_lock(&bul_lock); ++ mipv6_send_bu(&inner->daddr, &inner->saddr, &coa, ++ INITIAL_BINDACK_TIMEOUT, ++ MAX_BINDACK_TIMEOUT, 1, ++ user_flags, ++ lifetime, NULL); ++ write_unlock(&bul_lock); ++ } ++ } ++ DEBUG(DBG_DATADUMP, "setting rcv_tunnel flag in skb"); ++ skb->security |= MIPV6_RCV_TUNNEL; ++ return IP6_TNL_ACCEPT; ++} ++ ++static struct ip6_tnl_hook_ops mipv6_mn_tnl_rcv_send_bu_ops = { ++ {NULL, NULL}, ++ IP6_TNL_PRE_DECAP, ++ IP6_TNL_PRI_FIRST, ++ mipv6_mn_tnl_rcv_send_bu_hook ++}; ++ ++static int ++mipv6_mn_tnl_xmit_stats_hook(struct ip6_tnl *t, struct sk_buff *skb) ++{ ++ DEBUG_FUNC(); ++ if (is_mip6_tnl(t)) ++ MIPV6_INC_STATS(n_encapsulations); ++ return IP6_TNL_ACCEPT; ++} ++ ++static struct ip6_tnl_hook_ops mipv6_mn_tnl_xmit_stats_ops = { ++ {NULL, NULL}, ++ IP6_TNL_PRE_ENCAP, ++ IP6_TNL_PRI_LAST, ++ mipv6_mn_tnl_xmit_stats_hook ++}; ++ ++static int ++mipv6_mn_tnl_rcv_stats_hook(struct ip6_tnl *t, struct sk_buff *skb) ++{ ++ DEBUG_FUNC(); ++ if (is_mip6_tnl(t)) ++ MIPV6_INC_STATS(n_decapsulations); ++ return IP6_TNL_ACCEPT; ++} ++ ++static struct ip6_tnl_hook_ops mipv6_mn_tnl_rcv_stats_ops = { ++ {NULL, NULL}, ++ IP6_TNL_PRE_DECAP, ++ IP6_TNL_PRI_LAST, ++ mipv6_mn_tnl_rcv_stats_hook ++}; ++ ++static void mn_check_tunneled_packet(struct sk_buff *skb) ++{ ++ DEBUG_FUNC(); ++ /* If tunnel flag was set */ ++ if (skb->security & MIPV6_RCV_TUNNEL) { ++ struct in6_addr coa; ++ __u32 lifetime; ++ __u8 user_flags = 0; ++ int ptr = (u8*)(skb->nh.ipv6h+1) - skb->data; ++ int len = skb->len - ptr; ++ __u8 nexthdr = skb->nh.ipv6h->nexthdr; ++ ++ if (len < 0) ++ return; ++ ++ ptr = ipv6_skip_exthdr(skb, ptr, &nexthdr, len); ++ if (ptr < 0) ++ return; ++ ++ if (!mip6node_cnf.accept_ret_rout) { ++ DEBUG(DBG_INFO, "Return routability administratively disabled"); ++ return; ++ } ++ if (nexthdr == IPPROTO_MOBILITY) ++ return; ++ ++ /* Don't start RR when receiving ICMP error messages */ ++ if (nexthdr == IPPROTO_ICMPV6) { ++ u8 type; ++ ++ if (skb_copy_bits(skb, ++ ptr+offsetof(struct icmp6hdr, ++ icmp6_type), ++ &type, 1) ++ || !(type & ICMPV6_INFOMSG_MASK)) { ++ return; ++ } ++ } ++ user_flags |= mip6node_cnf.bu_cn_ack ? MIPV6_BU_F_ACK : 0; ++ mipv6_get_care_of_address(&skb->nh.ipv6h->daddr, &coa); ++ lifetime = mipv6_mn_get_bulifetime(&skb->nh.ipv6h->daddr, ++ &coa, 0); ++ ++ DEBUG(DBG_WARNING, "packet to address %x:%x:%x:%x:%x:%x:%x:%x" ++ "was tunneled. Sending BU to CN" ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&skb->nh.ipv6h->daddr), ++ NIPV6ADDR(&skb->nh.ipv6h->saddr)); ++ /* This should work also with home address option */ ++ ++ write_lock(&bul_lock); ++ mipv6_send_bu(&skb->nh.ipv6h->daddr, &skb->nh.ipv6h->saddr, ++ &coa, INITIAL_BINDACK_TIMEOUT, ++ MAX_BINDACK_TIMEOUT, 1, user_flags, ++ lifetime, NULL); ++ write_unlock(&bul_lock); ++ } ++} ++ ++static int sched_mv_home_addr_task(struct in6_addr *haddr, int plen_new, ++ int newif, int oldif, struct handoff *ho) ++{ ++ int alloc_size; ++ struct ifr_holder *ifrh; ++ ++ alloc_size = sizeof(*ifrh) + (ho ? sizeof(*ho): 0); ++ if ((ifrh = kmalloc(alloc_size, GFP_ATOMIC)) == NULL) { ++ DEBUG(DBG_ERROR, "Out of memory"); ++ return -1; ++ } ++ if (ho) { ++ ifrh->ho = (struct handoff *)((struct ifr_holder *)(ifrh + 1)); ++ memcpy(ifrh->ho, ho, sizeof(*ho)); ++ } else ++ ifrh->ho = NULL; ++ ++ /* must queue task to avoid deadlock with rtnl */ ++ ifrh->ifr.ifr6_ifindex = newif; ++ ifrh->ifr.ifr6_prefixlen = plen_new; ++ ipv6_addr_copy(&ifrh->ifr.ifr6_addr, haddr); ++ ifrh->old_ifi = oldif; ++ ++ spin_lock_bh(&ifrh_lock); ++ list_add_tail(&ifrh->list, &ifrh_list); ++ spin_unlock_bh(&ifrh_lock); ++ ++ schedule_task(&mv_home_addr_task); ++ ++ return 0; ++} ++ ++static void send_ret_home_ns(struct in6_addr *ha_addr, ++ struct in6_addr *home_addr, ++ int ifindex) ++{ ++ struct in6_addr nil; ++ struct in6_addr mcaddr; ++ struct net_device *dev = dev_get_by_index(ifindex); ++ if (!dev) ++ return; ++ memset(&nil, 0, sizeof(nil)); ++ addrconf_addr_solict_mult(home_addr, &mcaddr); ++ ndisc_send_ns(dev, NULL, home_addr, &mcaddr, &nil); ++ dev_put(dev); ++} ++ ++static inline int ha_is_reachable(int ifindex, struct in6_addr *ha) ++{ ++ struct net_device *dev; ++ int reachable = 0; ++ ++ dev = dev_get_by_index(ifindex); ++ if (dev) { ++ struct neighbour *neigh; ++ if ((neigh = ndisc_get_neigh(dev, ha)) != NULL) { ++ read_lock_bh(&neigh->lock); ++ if (neigh->nud_state&NUD_VALID) ++ reachable = 1; ++ read_unlock_bh(&neigh->lock); ++ neigh_release(neigh); ++ } ++ dev_put(dev); ++ } ++ return reachable; ++} ++ ++static int mn_ha_handoff(struct handoff *ho) ++{ ++ struct list_head *lh; ++ struct mn_info *minfo; ++ struct in6_addr *coa= ho->coa; ++ int wait_mv_home = 0; ++ ++ read_lock_bh(&mn_info_lock); ++ list_for_each(lh, &mn_info_list) { ++ __u8 has_home_reg; ++ int ifindex; ++ struct in6_addr ha; ++ __u8 athome; ++ __u32 lifetime; ++ struct mipv6_bul_entry *entry = NULL; ++ ++ minfo = list_entry(lh, struct mn_info, list); ++ spin_lock(&minfo->lock); ++ has_home_reg = minfo->has_home_reg; ++ ifindex = minfo->ifindex; ++ ipv6_addr_copy(&ha, &minfo->ha); ++ ++ if (mipv6_prefix_compare(&ho->rtr_addr, &minfo->home_addr, ++ ho->plen)) { ++ if (minfo->has_home_reg) ++ athome = minfo->is_at_home = MN_RETURNING_HOME; ++ else ++ athome = minfo->is_at_home = MN_AT_HOME; ++ coa = &minfo->home_addr; ++ ++ spin_unlock(&minfo->lock); ++#if 0 ++ /* Cancel prefix solicitation, rtr is our HA */ ++ mipv6_pfx_cancel_send(&ho->rtr_addr, ifindex); ++#endif ++ minfo->ifindex = ho->ifindex; ++ ++ if (minfo->has_home_reg && ++ !ha_is_reachable(ho->ifindex, &minfo->ha)) { ++ send_ret_home_ns(&minfo->ha, ++ &minfo->home_addr, ++ ho->ifindex); ++ mipv6_mdet_set_curr_rtr_reachable(0); ++ wait_mv_home++; ++ } ++ if (ifindex != ho->ifindex){ ++ wait_mv_home++; ++ DEBUG(DBG_INFO, ++ "Moving home address back to " ++ "the home interface"); ++ sched_mv_home_addr_task(&minfo->home_addr, ++ 128, ++ ho->ifindex, ++ ifindex, ho); ++ } ++ if (!has_home_reg || wait_mv_home) ++ continue; ++ ++ lifetime = 0; ++ ++ } else { ++ athome = minfo->is_at_home = MN_NOT_AT_HOME; ++ if (minfo->ifindex_user != minfo->ifindex) { ++ DEBUG(DBG_INFO, "Scheduling home address move to virtual interface"); ++ sched_mv_home_addr_task(&minfo->home_addr, ++ 128, ++ minfo->ifindex_user, ++ minfo->ifindex, ho); /* Is minfo->ifindex correct */ ++ ++ wait_mv_home++; ++ } ++ minfo->ifindex = minfo->ifindex_user; ++ spin_unlock(&minfo->lock); ++ if (wait_mv_home) ++ continue; ++ if (!has_home_reg && ++ init_home_registration(&minfo->home_addr, ++ ho->coa)) { ++ continue; ++ } ++ lifetime = mipv6_mn_get_bulifetime(&minfo->home_addr, ++ ho->coa, ++ MIPV6_BU_F_HOME); ++ ++ } ++ write_lock(&bul_lock); ++ if (!(entry = mipv6_bul_get(&ha, &minfo->home_addr)) || ++ !(entry->flags & MIPV6_BU_F_HOME)) { ++ DEBUG(DBG_ERROR, ++ "Unable to find home registration for " ++ "home address: %x:%x:%x:%x:%x:%x:%x:%x!\n", ++ NIPV6ADDR(&minfo->home_addr)); ++ write_unlock(&bul_lock); ++ continue; ++ } ++ DEBUG(DBG_INFO, "Sending home de ? %d registration for " ++ "home address: %x:%x:%x:%x:%x:%x:%x:%x\n" ++ "to home agent %x:%x:%x:%x:%x:%x:%x:%x, " ++ "with lifetime %ld", ++ (athome != MN_NOT_AT_HOME), ++ NIPV6ADDR(&entry->home_addr), ++ NIPV6ADDR(&entry->cn_addr), lifetime); ++ mipv6_send_bu(&entry->home_addr, &entry->cn_addr, ++ coa, INITIAL_BINDACK_TIMEOUT, ++ MAX_BINDACK_TIMEOUT, 1, entry->flags, ++ lifetime, NULL); ++ write_unlock(&bul_lock); ++ ++ } ++ read_unlock_bh(&mn_info_lock); ++ return wait_mv_home; ++} ++/** ++ * mn_cn_handoff - called for every bul entry to send BU to CN ++ * @rawentry: bul entry ++ * @args: handoff event ++ * @sortkey: ++ * ++ * Since MN can have many home addresses and home networks, every BUL ++ * entry needs to be checked ++ **/ ++int mn_cn_handoff(void *rawentry, void *args, unsigned long *sortkey) ++{ ++ struct mipv6_bul_entry *entry = (struct mipv6_bul_entry *)rawentry; ++ struct in6_addr *coa = (struct in6_addr *)args; ++ ++ DEBUG_FUNC(); ++ ++ /* Home registrations already handled by mn_ha_handoff */ ++ if (entry->flags & MIPV6_BU_F_HOME) ++ return ITERATOR_CONT; ++ ++ /* BUL is locked by mipv6_mobile_node_moved which calls us ++ through mipv6_bul_iterate */ ++ ++ if (mipv6_prefix_compare(coa, ++ &entry->home_addr, ++ 64)) { ++ mipv6_send_bu(&entry->home_addr, &entry->cn_addr, ++ &entry->home_addr, INITIAL_BINDACK_TIMEOUT, ++ MAX_BINDACK_TIMEOUT, 1, entry->flags, 0, ++ NULL); ++ } else { ++ u32 lifetime = mipv6_mn_get_bulifetime(&entry->home_addr, ++ coa, ++ entry->flags); ++ mipv6_send_bu(&entry->home_addr, &entry->cn_addr, ++ coa, INITIAL_BINDACK_TIMEOUT, ++ MAX_BINDACK_TIMEOUT, 1, entry->flags, ++ lifetime, NULL); ++ } ++ return ITERATOR_CONT; ++} ++ ++ ++int mn_bul_invalidate(void *rawentry, void *args, unsigned long *sortkey) ++{ ++ struct mipv6_bul_entry *bul = (struct mipv6_bul_entry *)rawentry; ++ struct bul_inval_args *arg = (struct bul_inval_args *)args; ++ ++ DEBUG_FUNC(); ++ ++ if (!ipv6_addr_cmp(arg->cn, &bul->cn_addr) && ++ (!ipv6_addr_cmp(arg->mn, &bul->home_addr) || ++ !ipv6_addr_cmp(arg->mn, &bul->coa))) { ++ if (arg->all_rr_states || !bul->rr || ++ (bul->rr->rr_state != RR_INIT && ++ bul->rr->rr_state != RR_DONE)) { ++ bul->state = ACK_ERROR; ++ bul->callback = bul_entry_expired; ++ bul->callback_time = jiffies + ++ DUMB_CN_BU_LIFETIME * HZ; ++ bul->expire = bul->callback_time; ++ DEBUG(DBG_INFO, "BUL entry set to ACK_ERROR"); ++ mipv6_bul_reschedule(bul); ++ } ++ } ++ return ITERATOR_CONT; ++} ++/** ++ * init_home_registration - start Home Registration process ++ * @home_addr: home address ++ * @coa: care-of address ++ * ++ * Checks whether we have a Home Agent address for this home address. ++ * If not starts Dynamic Home Agent Address Discovery. Otherwise ++ * tries to register with home agent if not already registered. ++ * Returns 1, if home registration process is started and 0 otherwise ++ **/ ++int init_home_registration(struct in6_addr *home_addr, struct in6_addr *coa) ++{ ++ struct mn_info *hinfo; ++ struct in6_addr ha; ++ __u8 man_conf; ++ int ifindex; ++ __u32 lifetime; ++ __u8 user_flags = 0, flags; ++ ++ DEBUG_FUNC(); ++ ++ read_lock_bh(&mn_info_lock); ++ if ((hinfo = mipv6_mninfo_get_by_home(home_addr)) == NULL) { ++ DEBUG(DBG_ERROR, "No mn_info found for address: " ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(home_addr)); ++ read_unlock_bh(&mn_info_lock); ++ return -ENOENT; ++ } ++ spin_lock(&hinfo->lock); ++ if (mipv6_prefix_compare(&hinfo->home_addr, coa, hinfo->home_plen)) { ++ spin_unlock(&hinfo->lock); ++ read_unlock_bh(&mn_info_lock); ++ DEBUG(DBG_INFO, "Adding home address, MN at home"); ++ return 1; ++ } ++ if (ipv6_addr_any(&hinfo->ha)) { ++ int dhaad_id = mipv6_get_dhaad_id(); ++ hinfo->dhaad_id = dhaad_id; ++ spin_unlock(&hinfo->lock); ++ mipv6_icmpv6_send_dhaad_req(home_addr, hinfo->home_plen, dhaad_id); ++ read_unlock_bh(&mn_info_lock); ++ DEBUG(DBG_INFO, ++ "Home Agent address not set, initiating DHAAD"); ++ return 1; ++ } ++ ipv6_addr_copy(&ha, &hinfo->ha); ++ man_conf = hinfo->man_conf; ++ ifindex = hinfo->ifindex; ++ spin_unlock(&hinfo->lock); ++ read_unlock_bh(&mn_info_lock); ++#if 0 ++ if (man_conf) ++ mipv6_pfx_add_ha(&ha, coa, ifindex); ++#endif ++ if (mipv6_bul_exists(&ha, home_addr)) { ++ DEBUG(DBG_INFO, "BU already sent to HA"); ++ return 0; ++ } ++ /* user flags received through sysctl */ ++ user_flags |= mip6node_cnf.bu_lladdr ? MIPV6_BU_F_LLADDR : 0; ++ user_flags |= mip6node_cnf.bu_keymgm ? MIPV6_BU_F_KEYMGM : 0; ++ ++ flags = MIPV6_BU_F_HOME | MIPV6_BU_F_ACK | user_flags; ++ ++ lifetime = mipv6_mn_get_bulifetime(home_addr, coa, flags); ++ ++ DEBUG(DBG_INFO, "Sending initial home registration for " ++ "home address: %x:%x:%x:%x:%x:%x:%x:%x\n" ++ "to home agent %x:%x:%x:%x:%x:%x:%x:%x, " ++ "with lifetime %ld, prefixlength %d", ++ NIPV6ADDR(home_addr), NIPV6ADDR(&ha), lifetime, 0); ++ ++ write_lock_bh(&bul_lock); ++ mipv6_send_bu(home_addr, &ha, coa, INITIAL_BINDACK_DAD_TIMEOUT, ++ MAX_BINDACK_TIMEOUT, 1, flags, lifetime, NULL); ++ write_unlock_bh(&bul_lock); ++ ++ return 1; ++} ++ ++/** ++ * mipv6_mobile_node_moved - Send BUs to all HAs and CNs ++ * @ho: handoff structure contains the new and previous routers ++ * ++ * Event for handoff. Sends BUs everyone on Binding Update List. ++ **/ ++int mipv6_mobile_node_moved(struct handoff *ho) ++{ ++#if 0 ++ int bu_to_prev_router = 1; ++#endif ++ int dummy; ++ ++ DEBUG_FUNC(); ++ ++ ma_ctl_upd_iface(ho->ifindex, ++ MA_IFACE_CURRENT | MA_IFACE_HAS_ROUTER, &dummy); ++ ++ /* First send BU to HA, then to all other nodes that are on BU list */ ++ if (mn_ha_handoff(ho) != 0) ++ return 0; /* Wait for move home address task */ ++#if 0 ++ /* Add current care-of address to mn_info list, if current router acts ++ as a HA.*/ ++ ++ if (ho->home_address && bu_to_prev_router) ++ mipv6_mninfo_add(ho->coa, ho->plen, ++ MN_AT_HOME, 0, &ho->rtr_addr, ++ ho->plen, ROUTER_BU_DEF_LIFETIME, ++ 0); ++ ++#endif ++ return 0; ++} ++ ++/** ++ * mipv6_mn_send_home_na - send NA when returning home ++ * @haddr: home address to advertise ++ * ++ * After returning home, MN must advertise all its valid addresses in ++ * home link to all nodes. ++ **/ ++void mipv6_mn_send_home_na(struct in6_addr *haddr) ++{ ++ struct net_device *dev = NULL; ++ struct in6_addr mc_allnodes; ++ struct mn_info *hinfo = NULL; ++ ++ read_lock(&mn_info_lock); ++ hinfo = mipv6_mninfo_get_by_home(haddr); ++ if (!hinfo) { ++ read_unlock(&mn_info_lock); ++ return; ++ } ++ spin_lock(&hinfo->lock); ++ hinfo->is_at_home = MN_AT_HOME; ++ dev = dev_get_by_index(hinfo->ifindex); ++ spin_unlock(&hinfo->lock); ++ read_unlock(&mn_info_lock); ++ if (dev == NULL) { ++ DEBUG(DBG_ERROR, "Send home_na: device not found."); ++ return; ++ } ++ ++ ipv6_addr_all_nodes(&mc_allnodes); ++ ndisc_send_na(dev, NULL, &mc_allnodes, haddr, 0, 0, 1, 1); ++ dev_put(dev); ++} ++ ++static int mn_use_hao(struct in6_addr *daddr, struct in6_addr *saddr) ++{ ++ struct mipv6_bul_entry *entry; ++ struct mn_info *minfo = NULL; ++ int add_ha = 0; ++ ++ read_lock_bh(&mn_info_lock); ++ minfo = mipv6_mninfo_get_by_home(saddr); ++ if (minfo && minfo->is_at_home != MN_AT_HOME) { ++ read_lock_bh(&bul_lock); ++ if ((entry = mipv6_bul_get(daddr, saddr)) == NULL) { ++ read_unlock_bh(&bul_lock); ++ read_unlock_bh(&mn_info_lock); ++ return add_ha; ++ } ++ add_ha = (entry->state != ACK_ERROR && ++ (!entry->rr || entry->rr->rr_state == RR_DONE || ++ entry->flags & MIPV6_BU_F_HOME)); ++ read_unlock_bh(&bul_lock); ++ } ++ read_unlock_bh(&mn_info_lock); ++ return add_ha; ++} ++ ++static int ++mn_dev_event(struct notifier_block *nb, unsigned long event, void *ptr) ++{ ++ struct net_device *dev = ptr; ++ struct list_head *lh; ++ struct mn_info *minfo; ++ int newif = 0; ++ ++ /* here are probably the events we need to worry about */ ++ switch (event) { ++ case NETDEV_UP: ++ DEBUG(DBG_DATADUMP, "New netdevice %s registered.", dev->name); ++ if (dev->type != ARPHRD_LOOPBACK && !dev_is_mip6_tnl(dev)) ++ ma_ctl_add_iface(dev->ifindex); ++ ++ break; ++ case NETDEV_GOING_DOWN: ++ DEBUG(DBG_DATADUMP, "Netdevice %s disappeared.", dev->name); ++ /* ++ * Go through mn_info list and move all home addresses on the ++ * netdev going down to a new device. This will make it ++ * practically impossible for the home address to return home, ++ * but allow MN to retain its connections using the address. ++ */ ++ ++ read_lock_bh(&mn_info_lock); ++ list_for_each(lh, &mn_info_list) { ++ minfo = list_entry(lh, struct mn_info, list); ++ spin_lock(&minfo->lock); ++ if (minfo->ifindex == dev->ifindex) { ++ if (sched_mv_home_addr_task(&minfo->home_addr, 128, ++ minfo->ifindex_user, ++ 0, NULL) < 0) { ++ minfo->ifindex = 0; ++ spin_unlock(&minfo->lock); ++ read_unlock_bh(&mn_info_lock); ++ return NOTIFY_DONE; ++ } else { ++ minfo->ifindex = minfo->ifindex_user; ++ if (minfo->is_at_home) { ++ minfo->is_at_home = 0; ++ ++ } ++ newif = minfo->ifindex_user; ++ } ++ } ++ spin_unlock(&minfo->lock); ++ } ++ ++ read_unlock_bh(&mn_info_lock); ++ } ++ ma_ctl_upd_iface(dev->ifindex, MA_IFACE_NOT_PRESENT, &newif); ++ mipv6_mdet_del_if(dev->ifindex); ++ ++ return NOTIFY_DONE; ++} ++ ++struct notifier_block mipv6_mn_dev_notifier = { ++ mn_dev_event, ++ NULL, ++ 0 /* check if using zero is ok */ ++}; ++ ++static void deprecate_addr(struct mn_info *minfo) ++{ ++ /* ++ * Lookup address from IPv6 address list and set deprecated flag ++ */ ++ ++} ++ ++/* ++ * Required because we can only modify addresses after the packet is ++ * constructed. We otherwise mess with higher level protocol ++ * pseudoheaders. With strict protocol layering life would be SO much ++ * easier! ++ */ ++static unsigned int modify_xmit_addrs(unsigned int hooknum, ++ struct sk_buff **pskb, ++ const struct net_device *in, ++ const struct net_device *out, ++ int (*okfn) (struct sk_buff *)) ++{ ++ struct sk_buff *skb = *pskb; ++ ++ DEBUG_FUNC(); ++ ++ if (skb) { ++ struct ipv6hdr *hdr = skb->nh.ipv6h; ++ struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb; ++ struct mipv6_bul_entry *bule; ++ struct in6_addr *daddr; ++ ++ if (!ipv6_addr_any(&opt->hoa)) ++ daddr = &opt->hoa; ++ else ++ daddr = &hdr->daddr; ++ ++ /* We don't consult bul when sending a BU to avoid deadlock, since ++ * BUL is already locked. ++ */ ++ ++ ++ if (opt->mipv6_flags & MIPV6_SND_HAO && ++ !(opt->mipv6_flags & MIPV6_SND_BU)) { ++ write_lock(&bul_lock); ++ bule = mipv6_bul_get(daddr, &hdr->saddr); ++ if (!bule) { ++ write_unlock(&bul_lock); ++ return NF_ACCEPT; ++ } ++ if (!bule->rr || bule->rr->rr_state == RR_DONE || ++ bule->flags & MIPV6_BU_F_HOME) { ++ DEBUG(DBG_DATADUMP, ++ "Replace source address with CoA and reroute"); ++ ipv6_addr_copy(&hdr->saddr, &bule->coa); ++ skb->nfcache |= NFC_ALTERED; ++ } ++ write_unlock(&bul_lock); ++ } else if (opt->mipv6_flags & MIPV6_SND_HAO) { ++ mipv6_get_care_of_address(&hdr->saddr, &hdr->saddr); ++ skb->nfcache |= NFC_ALTERED; ++ } ++ } ++ return NF_ACCEPT; ++} ++ ++/* We set a netfilter hook so that we can modify outgoing packet's ++ * source addresses ++ */ ++struct nf_hook_ops addr_modify_hook_ops = { ++ {NULL, NULL}, /* List head, no predecessor, no successor */ ++ modify_xmit_addrs, ++ PF_INET6, ++ NF_IP6_LOCAL_OUT, ++ NF_IP6_PRI_FIRST /* Should be of EXTREMELY high priority since we ++ * do not want to mess with IPSec (possibly ++ * implemented as packet filter) ++ */ ++}; ++ ++#define MN_INFO_LEN 77 ++ ++static int mn_proc_info(char *buffer, char **start, off_t offset, ++ int length) ++{ ++ struct list_head *p; ++ struct mn_info *minfo; ++ int len = 0, skip = 0; ++ ++ DEBUG_FUNC(); ++ ++ read_lock_bh(&mn_info_lock); ++ list_for_each(p, &mn_info_list) { ++ if (len < offset / MN_INFO_LEN) { ++ skip++; ++ continue; ++ } ++ if (len >= length) ++ break; ++ minfo = list_entry(p, struct mn_info, list); ++ spin_lock(&minfo->lock); ++ len += sprintf(buffer + len, "%02d %08x%08x%08x%08x %02x " ++ "%08x%08x%08x%08x %d %d\n", ++ minfo->ifindex, ++ ntohl(minfo->home_addr.s6_addr32[0]), ++ ntohl(minfo->home_addr.s6_addr32[1]), ++ ntohl(minfo->home_addr.s6_addr32[2]), ++ ntohl(minfo->home_addr.s6_addr32[3]), ++ minfo->home_plen, ++ ntohl(minfo->ha.s6_addr32[0]), ++ ntohl(minfo->ha.s6_addr32[1]), ++ ntohl(minfo->ha.s6_addr32[2]), ++ ntohl(minfo->ha.s6_addr32[3]), ++ minfo->is_at_home, minfo->has_home_reg); ++ spin_unlock(&minfo->lock); ++ } ++ read_unlock_bh(&mn_info_lock); ++ ++ *start = buffer; ++ if (offset) ++ *start += offset % MN_INFO_LEN; ++ ++ len -= offset % MN_INFO_LEN; ++ ++ if (len > length) ++ len = length; ++ if (len < 0) ++ len = 0; ++ ++ return len; ++} ++ ++int mipv6_mn_ha_nd_update(struct net_device *dev, ++ struct in6_addr *ha, u8 *lladdr) ++{ ++ int valid = 0; ++ struct neighbour *neigh; ++ if ((neigh = ndisc_get_neigh(dev, ha))) { ++ read_lock(&neigh->lock); ++ valid = neigh->nud_state & NUD_VALID; ++ read_unlock(&neigh->lock); ++ if (!valid && lladdr) ++ neigh_update(neigh, lladdr, NUD_REACHABLE, 0, 1); ++ neigh_release(neigh); ++ } ++ return valid; ++} ++ ++int mipv6_mn_ha_probe(struct inet6_ifaddr *ifp, u8 *lladdr) ++{ ++ struct mn_info *minfo; ++ ++ if (!(minfo = mipv6_mninfo_get_by_home(&ifp->addr)) || ++ ipv6_addr_any(&minfo->ha)) ++ return 0; ++ ++ if (mipv6_mn_ha_nd_update(ifp->idev->dev, &minfo->ha, lladdr)) ++ mipv6_mdet_retrigger_ho(); ++ return 1; ++} ++ ++int __init mipv6_mn_init(void) ++{ ++ struct net_device *dev; ++ ++ DEBUG_FUNC(); ++ ++ if (mipv6_add_tnl_to_ha()) ++ return -ENODEV; ++ ++ mipv6_bul_init(MIPV6_BUL_SIZE); ++ mip6_fn.mn_use_hao = mn_use_hao; ++ mip6_fn.mn_check_tunneled_packet = mn_check_tunneled_packet; ++ INIT_TQUEUE(&mv_home_addr_task, mv_home_addr, NULL); ++ ++ ma_ctl_init(); ++ for (dev = dev_base; dev; dev = dev->next) { ++ if (dev->flags & IFF_UP && ++ dev->type != ARPHRD_LOOPBACK && !dev_is_mip6_tnl(dev)) { ++ ma_ctl_add_iface(dev->ifindex); ++ } ++ } ++ DEBUG(DBG_INFO, "Multiaccess support initialized"); ++ ++ register_netdevice_notifier(&mipv6_mn_dev_notifier); ++ register_inet6addr_notifier(&mipv6_mn_inet6addr_notifier); ++ ++ ip6ip6_tnl_register_hook(&mipv6_mn_tnl_rcv_send_bu_ops); ++ ip6ip6_tnl_register_hook(&mipv6_mn_tnl_xmit_stats_ops); ++ ip6ip6_tnl_register_hook(&mipv6_mn_tnl_rcv_stats_ops); ++ ++ MIPV6_SETCALL(mipv6_set_home, mipv6_mn_set_home); ++ ++ mipv6_initialize_mdetect(); ++ ++ /* COA to home transformation hook */ ++ MIPV6_SETCALL(mipv6_get_home_address, mipv6_get_saddr_hook); ++ MIPV6_SETCALL(mipv6_mn_ha_probe, mipv6_mn_ha_probe); ++ MIPV6_SETCALL(mipv6_is_home_addr, mipv6_mn_is_home_addr); ++ proc_net_create("mip6_mninfo", 0, mn_proc_info); ++ /* Set packet modification hook (source addresses) */ ++ nf_register_hook(&addr_modify_hook_ops); ++ ++ return 0; ++} ++ ++void __exit mipv6_mn_exit(void) ++{ ++ struct list_head *lh, *tmp; ++ struct mn_info *minfo; ++ DEBUG_FUNC(); ++ ++ mip6_fn.mn_use_hao = NULL; ++ mip6_fn.mn_check_tunneled_packet = NULL; ++ ++ MIPV6_RESETCALL(mipv6_set_home); ++ MIPV6_RESETCALL(mipv6_get_home_address); ++ MIPV6_RESETCALL(mipv6_mn_ha_probe); ++ MIPV6_RESETCALL(mipv6_is_home_addr); ++ nf_unregister_hook(&addr_modify_hook_ops); ++ proc_net_remove("mip6_mninfo"); ++ mipv6_shutdown_mdetect(); ++ ip6ip6_tnl_unregister_hook(&mipv6_mn_tnl_rcv_stats_ops); ++ ip6ip6_tnl_unregister_hook(&mipv6_mn_tnl_xmit_stats_ops); ++ ip6ip6_tnl_unregister_hook(&mipv6_mn_tnl_rcv_send_bu_ops); ++ ma_ctl_clean(); ++ ++ unregister_inet6addr_notifier(&mipv6_mn_inet6addr_notifier); ++ unregister_netdevice_notifier(&mipv6_mn_dev_notifier); ++ write_lock_bh(&mn_info_lock); ++ ++ list_for_each_safe(lh, tmp, &mn_info_list) { ++ minfo = list_entry(lh, struct mn_info, list); ++ if (minfo->is_at_home == MN_NOT_AT_HOME) ++ deprecate_addr(minfo); ++ list_del(&minfo->list); ++ kfree(minfo); ++ } ++ write_unlock_bh(&mn_info_lock); ++ mipv6_bul_exit(); ++ flush_scheduled_tasks(); ++ mipv6_del_tnl_to_ha(); ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mn.h linux-2.4.25/net/ipv6/mobile_ip6/mn.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/mn.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/mn.h 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,96 @@ ++/* ++ * MIPL Mobile IPv6 Mobile Node header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _MN_H ++#define _MN_H ++ ++#include ++ ++/* constants for sending of BUs*/ ++#define HA_BU_DEF_LIFETIME 10000 ++#define CN_BU_DEF_LIFETIME 420 /* Max lifetime for RR bindings from RFC 3775 */ ++#define DUMB_CN_BU_LIFETIME 600 /* BUL entry lifetime in case of dumb CN */ ++#define ROUTER_BU_DEF_LIFETIME 30 /* For packet forwarding from previous coa */ ++#define ERROR_DEF_LIFETIME DUMB_CN_BU_LIFETIME ++ ++extern rwlock_t mn_info_lock; ++ ++#define MN_NOT_AT_HOME 0 ++#define MN_RETURNING_HOME 1 ++#define MN_AT_HOME 2 ++ ++/* ++ * Mobile Node information record ++ */ ++struct mn_info { ++ struct in6_addr home_addr; ++ struct in6_addr ha; ++ __u8 home_plen; ++ __u8 is_at_home; ++ __u8 has_home_reg; ++ __u8 man_conf; ++ int ifindex; ++ int ifindex_user; ++ unsigned long home_addr_expires; ++ unsigned short dhaad_id; ++ struct list_head list; ++ spinlock_t lock; ++}; ++ ++/* prototypes for interface functions */ ++int mipv6_mn_init(void); ++void mipv6_mn_exit(void); ++ ++struct handoff; ++ ++/* Interface to movement detection */ ++int mipv6_mobile_node_moved(struct handoff *ho); ++ ++void mipv6_mn_send_home_na(struct in6_addr *haddr); ++/* Init home reg. with coa */ ++int init_home_registration(struct in6_addr *home_addr, struct in6_addr *coa); ++ ++/* mn_info functions that require locking by caller */ ++struct mn_info *mipv6_mninfo_get_by_home(struct in6_addr *haddr); ++ ++struct mn_info *mipv6_mninfo_get_by_ha(struct in6_addr *home_agent); ++ ++struct mn_info *mipv6_mninfo_get_by_id(unsigned short id); ++ ++/* "safe" mn_info functions */ ++void mipv6_mninfo_add(int ifindex, struct in6_addr *home_addr, int plen, ++ int isathome, unsigned long lifetime, struct in6_addr *ha, ++ int ha_plen, unsigned long ha_lifetime, int man_conf); ++ ++int mipv6_mninfo_del(struct in6_addr *home_addr, int del_dyn_only); ++ ++void mipv6_mn_set_home_reg(struct in6_addr *home_addr, int has_home_reg); ++ ++int mipv6_mn_is_at_home(struct in6_addr *addr); ++ ++int mipv6_mn_is_home_addr(struct in6_addr *addr); ++ ++__u32 mipv6_mn_get_bulifetime(struct in6_addr *home_addr, ++ struct in6_addr *coa, __u8 flags); ++int mn_cn_handoff(void *rawentry, void *args, unsigned long *sortkey); ++ ++int mipv6_mn_ha_nd_update(struct net_device *dev, ++ struct in6_addr *ha, u8 *lladdr); ++ ++struct bul_inval_args { ++ int all_rr_states; ++ struct in6_addr *cn; ++ struct in6_addr *mn; ++}; ++ ++int mn_bul_invalidate(void *rawentry, void *args, unsigned long *sortkey); ++ ++#endif /* _MN_H */ +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mobhdr.h linux-2.4.25/net/ipv6/mobile_ip6/mobhdr.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/mobhdr.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/mobhdr.h 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,101 @@ ++/* ++ * MIPL Mobile IPv6 Mobility Header send and receive ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _MOBHDR_H ++#define _MOBHDR_H ++ ++#include ++ ++/* RR states for mipv6_send_bu() */ ++#define RR_INIT 0x00 ++#define RR_WAITH 0x01 ++#define RR_WAITC 0x02 ++#define RR_WAITHC 0x13 ++#define RR_DONE 0x10 ++ ++#define MH_UNKNOWN_CN 1 ++#define MH_AUTH_FAILED 2 ++#define MH_SEQUENCE_MISMATCH 3 ++ ++struct mipv6_bul_entry; ++struct sk_buff; ++ ++int mipv6_mh_common_init(void); ++void mipv6_mh_common_exit(void); ++int mipv6_mh_mn_init(void); ++void mipv6_mh_mn_exit(void); ++ ++struct mipv6_mh_opt { ++ struct mipv6_mo_alt_coa *alt_coa; ++ struct mipv6_mo_nonce_indices *nonce_indices; ++ struct mipv6_mo_bauth_data *auth_data; ++ struct mipv6_mo_br_advice *br_advice; ++ int freelen; ++ int totlen; ++ u8 *next_free; ++ u8 data[0]; ++}; ++ ++struct mobopt { ++ struct mipv6_mo_alt_coa *alt_coa; ++ struct mipv6_mo_nonce_indices *nonce_indices; ++ struct mipv6_mo_bauth_data *auth_data; ++ struct mipv6_mo_br_advice *br_advice; ++}; ++ ++struct mipv6_mh_opt *alloc_mh_opts(int totlen); ++int append_mh_opt(struct mipv6_mh_opt *ops, u8 type, u8 len, void *data); ++int parse_mo_tlv(void *mos, int len, struct mobopt *opts); ++int mipv6_add_pad(u8 *data, int n); ++ ++struct mipv6_auth_parm { ++ struct in6_addr *coa; ++ struct in6_addr *cn_addr; ++ __u8 *k_bu; ++}; ++ ++int send_mh(struct in6_addr *daddr, struct in6_addr *saddr, ++ u8 msg_type, u8 msg_len, u8 *msg, ++ struct in6_addr *hao_addr, struct in6_addr *rth_addr, ++ struct mipv6_mh_opt *ops, struct mipv6_auth_parm *parm); ++ ++int mipv6_mh_register(int type, int (*func)(struct sk_buff *, ++ struct in6_addr *, struct in6_addr *, ++ struct in6_addr *, struct in6_addr *, struct mipv6_mh *)); ++ ++void mipv6_mh_unregister(int type); ++ ++int mipv6_send_brr(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct mipv6_mh_opt *ops); ++ ++int mipv6_send_bu(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct in6_addr *coa, __u32 initdelay, ++ __u32 maxackdelay, __u8 exp, __u8 flags, ++ __u32 lifetime, struct mipv6_mh_opt *ops); ++ ++int mipv6_send_be(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct in6_addr *home, __u8 status); ++ ++int mipv6_send_ba(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct in6_addr *auth_coa, struct in6_addr *rep_coa, ++ u8 status, u16 sequence, u32 lifetime, u8 *k_bu); ++ ++/* Binding Authentication Data Option routines */ ++#define MAX_HASH_LENGTH 20 ++#define MIPV6_RR_MAC_LENGTH 12 ++ ++int mipv6_auth_build(struct in6_addr *cn_addr, struct in6_addr *coa, ++ __u8 *opt, __u8 *aud_data, __u8 *k_bu); ++ ++int mipv6_auth_check(struct in6_addr *cn_addr, struct in6_addr *coa, ++ __u8 *opt, __u8 optlen, struct mipv6_mo_bauth_data *aud, ++ __u8 *k_bu); ++#endif /* _MOBHDR_H */ +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mobhdr_common.c linux-2.4.25/net/ipv6/mobile_ip6/mobhdr_common.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/mobhdr_common.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/mobhdr_common.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,1210 @@ ++/* ++ * Mobile IPv6 Mobility Header Common Functions ++ * ++ * Authors: ++ * Antti Tuominen ++ * ++ * $Id: s.mh_recv.c 1.159 02/10/16 15:01:29+03:00 antti@traci.mipl.mediapoli.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. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "stats.h" ++#include "debug.h" ++#include "mobhdr.h" ++#include "bcache.h" ++ ++#include "rr_crypto.h" ++#include "exthdrs.h" ++#include "config.h" ++ ++#define MIPV6_MH_MAX MIPV6_MH_BE ++struct mh_proto { ++ int (*func) (struct sk_buff *, ++ struct in6_addr *, struct in6_addr *, ++ struct in6_addr *, struct in6_addr *, ++ struct mipv6_mh *); ++}; ++ ++static struct mh_proto mh_rcv[MIPV6_MH_MAX]; ++ ++int mipv6_mh_register(int type, int (*func)(struct sk_buff *, ++ struct in6_addr *, struct in6_addr *, ++ struct in6_addr *, struct in6_addr *, struct mipv6_mh *)) ++{ ++ if (mh_rcv[type].func != NULL) ++ return -1; ++ ++ mh_rcv[type].func = func; ++ ++ return 0; ++} ++ ++void mipv6_mh_unregister(int type) ++{ ++ if (type < 0 || type > MIPV6_MH_MAX) ++ return; ++ ++ mh_rcv[type].func = NULL; ++} ++ ++struct socket *mipv6_mh_socket = NULL; ++ ++/* TODO: Fix fragmentation */ ++static int dstopts_getfrag( ++ const void *data, struct in6_addr *addr, ++ char *buff, unsigned int offset, unsigned int len) ++{ ++ memcpy(buff, data + offset, len); ++ return 0; ++} ++ ++struct mipv6_mh_opt *alloc_mh_opts(int totlen) ++{ ++ struct mipv6_mh_opt *ops; ++ ++ ops = kmalloc(sizeof(*ops) + totlen, GFP_ATOMIC); ++ if (ops == NULL) ++ return NULL; ++ ++ memset(ops, 0, sizeof(*ops)); ++ ops->next_free = ops->data; ++ ops->freelen = totlen; ++ ++ return ops; ++} ++ ++int append_mh_opt(struct mipv6_mh_opt *ops, u8 type, u8 len, void *data) ++{ ++ struct mipv6_mo *mo; ++ ++ if (ops->next_free == NULL) { ++ DEBUG(DBG_ERROR, "No free room for option"); ++ return -ENOMEM; ++ } ++ if (ops->freelen < len + 2) { ++ DEBUG(DBG_ERROR, "No free room for option"); ++ return -ENOMEM; ++ } ++ else { ++ ops->freelen -= (len + 2); ++ ops->totlen += (len + 2); ++ } ++ ++ mo = (struct mipv6_mo *)ops->next_free; ++ mo->type = type; ++ mo->length = len; ++ ++ switch (type) { ++ case MIPV6_OPT_ALTERNATE_COA: ++ ops->alt_coa = (struct mipv6_mo_alt_coa *)mo; ++ ipv6_addr_copy(&ops->alt_coa->addr, (struct in6_addr *)data); ++ break; ++ case MIPV6_OPT_NONCE_INDICES: ++ DEBUG(DBG_INFO, "Added nonce indices pointer"); ++ ops->nonce_indices = (struct mipv6_mo_nonce_indices *)mo; ++ ops->nonce_indices->home_nonce_i = *(__u16 *)data; ++ ops->nonce_indices->careof_nonce_i = *((__u16 *)data + 1); ++ break; ++ case MIPV6_OPT_AUTH_DATA: ++ DEBUG(DBG_INFO, "Added opt auth_data pointer"); ++ ops->auth_data = (struct mipv6_mo_bauth_data *)mo; ++ break; ++ case MIPV6_OPT_BIND_REFRESH_ADVICE: ++ ops->br_advice = (struct mipv6_mo_br_advice *)mo; ++ ops->br_advice->refresh_interval = htons(*(u16 *)data); ++ break; ++ default: ++ DEBUG(DBG_ERROR, "Unknow option type"); ++ break; ++ } ++ ++ if (ops->freelen == 0) ++ ops->next_free = NULL; ++ else ++ ops->next_free += (len + 2); ++ ++ return 0; ++} ++ ++/* ++ * Calculates required padding with xn + y requirement with offset ++ */ ++static inline int optpad(int xn, int y, int offset) ++{ ++ return ((y - offset) & (xn - 1)); ++} ++ ++static int option_pad(int type, int offset) ++{ ++ if (type == MIPV6_OPT_ALTERNATE_COA) ++ return optpad(8, 6, offset); /* 8n + 6 */ ++ if (type == MIPV6_OPT_BIND_REFRESH_ADVICE || ++ type == MIPV6_OPT_NONCE_INDICES) ++ return optpad(2, 0, offset); /* 2n */ ++ return 0; ++} ++ ++/* ++ * Add Pad1 or PadN option to data ++ */ ++int mipv6_add_pad(u8 *data, int n) ++{ ++ struct mipv6_mo_padn *padn; ++ ++ if (n <= 0) return 0; ++ if (n == 1) { ++ *data = MIPV6_OPT_PAD1; ++ return 1; ++ } ++ padn = (struct mipv6_mo_padn *)data; ++ padn->type = MIPV6_OPT_PADN; ++ padn->length = n - 2; ++ memset(padn->data, 0, n - 2); ++ return n; ++} ++ ++/* ++ * Write options to mobility header buffer ++ */ ++static int prepare_mh_opts(u8 *optdata, int off, struct mipv6_mh_opt *ops) ++{ ++ u8 *nextopt = optdata; ++ int offset = off, pad = 0; ++ ++ if (ops == NULL) { ++ nextopt = NULL; ++ return -1; ++ } ++ ++ if (ops->alt_coa) { ++ pad = option_pad(MIPV6_OPT_ALTERNATE_COA, offset); ++ nextopt += mipv6_add_pad(nextopt, pad); ++ memcpy(nextopt, ops->alt_coa, sizeof(struct mipv6_mo_alt_coa)); ++ nextopt += sizeof(struct mipv6_mo_alt_coa); ++ offset += pad + sizeof(struct mipv6_mo_alt_coa); ++ } ++ ++ if (ops->br_advice) { ++ pad = option_pad(MIPV6_OPT_BIND_REFRESH_ADVICE, offset); ++ nextopt += mipv6_add_pad(nextopt, pad); ++ memcpy(nextopt, ops->br_advice, sizeof(struct mipv6_mo_br_advice)); ++ nextopt += sizeof(struct mipv6_mo_br_advice); ++ offset += pad + sizeof(struct mipv6_mo_br_advice); ++ } ++ ++ if (ops->nonce_indices) { ++ pad = option_pad(MIPV6_OPT_NONCE_INDICES, offset); ++ nextopt += mipv6_add_pad(nextopt, pad); ++ memcpy(nextopt, ops->nonce_indices, sizeof(struct mipv6_mo_nonce_indices)); ++ nextopt += sizeof(struct mipv6_mo_nonce_indices); ++ offset += pad + sizeof(struct mipv6_mo_nonce_indices); ++ } ++ ++ if (ops->auth_data) { ++ /* This option should always be the last. Header ++ * length must be a multiple of 8 octects, so we pad ++ * if necessary. */ ++ pad = optpad(8, 0, offset + ops->auth_data->length + 2); ++ nextopt += mipv6_add_pad(nextopt, pad); ++ memcpy(nextopt, ops->auth_data, ops->auth_data->length + 2); ++ nextopt += ops->auth_data->length + 2; ++ } ++ nextopt = NULL; ++ ++ return 0; ++} ++ ++static int calculate_mh_opts(struct mipv6_mh_opt *ops, int mh_len) ++{ ++ int offset = mh_len; ++ ++ if (ops == NULL) ++ return 0; ++ ++ if (ops->alt_coa) ++ offset += sizeof(struct mipv6_mo_alt_coa) ++ + option_pad(MIPV6_OPT_ALTERNATE_COA, offset); ++ ++ if (ops->br_advice) ++ offset += sizeof(struct mipv6_mo_br_advice) ++ + option_pad(MIPV6_OPT_BIND_REFRESH_ADVICE, offset); ++ ++ if (ops->nonce_indices) ++ offset += sizeof(struct mipv6_mo_nonce_indices) ++ + option_pad(MIPV6_OPT_NONCE_INDICES, offset); ++ ++ if (ops->auth_data) /* no alignment */ ++ offset += ops->auth_data->length + 2; ++ ++ return offset - mh_len; ++} ++ ++/* ++ * ++ * Mobility Header Message send functions ++ * ++ */ ++ ++/** ++ * send_mh - builds and sends a MH msg ++ * ++ * @daddr: destination address for packet ++ * @saddr: source address for packet ++ * @msg_type: type of MH ++ * @msg_len: message length ++ * @msg: MH type specific data ++ * @hao_addr: home address for home address option ++ * @rth_addr: routing header address ++ * @ops: mobility options ++ * @parm: auth data ++ * ++ * Builds MH, appends the type specific msg data to the header and ++ * sends the packet with a home address option, if a home address was ++ * given. Returns 0, if everything succeeded and a negative error code ++ * otherwise. ++ **/ ++int send_mh(struct in6_addr *daddr, ++ struct in6_addr *saddr, ++ u8 msg_type, u8 msg_len, u8 *msg, ++ struct in6_addr *hao_addr, ++ struct in6_addr *rth_addr, ++ struct mipv6_mh_opt *ops, ++ struct mipv6_auth_parm *parm) ++{ ++ struct flowi fl; ++ struct mipv6_mh *mh; ++ struct sock *sk = mipv6_mh_socket->sk; ++ struct ipv6_txoptions *txopt = NULL; ++ int tot_len = sizeof(struct mipv6_mh) + msg_len; ++ int padded_len = 0, txopt_len = 0; ++ ++ DEBUG_FUNC(); ++ /* Add length of options */ ++ tot_len += calculate_mh_opts(ops, tot_len); ++ /* Needs to be a multiple of 8 octets */ ++ padded_len = tot_len + optpad(8, 0, tot_len); ++ ++ mh = sock_kmalloc(sk, padded_len, GFP_ATOMIC); ++ if (!mh) { ++ DEBUG(DBG_ERROR, "memory allocation failed"); ++ return -ENOMEM; ++ } ++ ++ memset(&fl, 0, sizeof(fl)); ++ fl.proto = IPPROTO_MOBILITY; ++ fl.fl6_dst = daddr; ++ fl.fl6_src = saddr; ++ fl.fl6_flowlabel = 0; ++ fl.oif = sk->bound_dev_if; ++ ++ if (hao_addr || rth_addr) { ++ __u8 *opt_ptr; ++ ++ if (hao_addr) ++ txopt_len += sizeof(struct mipv6_dstopt_homeaddr) + 6; ++ if (rth_addr) ++ txopt_len += sizeof(struct rt2_hdr); ++ ++ txopt_len += sizeof(*txopt); ++ txopt = sock_kmalloc(sk, txopt_len, GFP_ATOMIC); ++ if (txopt == NULL) { ++ DEBUG(DBG_ERROR, "No socket space left"); ++ sock_kfree_s(sk, mh, padded_len); ++ return -ENOMEM; ++ } ++ memset(txopt, 0, txopt_len); ++ txopt->tot_len = txopt_len; ++ opt_ptr = (__u8 *) (txopt + 1); ++ if (hao_addr) { ++ int holen = sizeof(struct mipv6_dstopt_homeaddr) + 6; ++ txopt->dst1opt = (struct ipv6_opt_hdr *) opt_ptr; ++ txopt->opt_flen += holen; ++ opt_ptr += holen; ++ mipv6_append_dst1opts(txopt->dst1opt, saddr, ++ NULL, holen); ++ txopt->mipv6_flags = MIPV6_SND_HAO | MIPV6_SND_BU; ++ } ++ if (rth_addr) { ++ int rtlen = sizeof(struct rt2_hdr); ++ txopt->srcrt2 = (struct ipv6_rt_hdr *) opt_ptr; ++ txopt->opt_nflen += rtlen; ++ opt_ptr += rtlen; ++ mipv6_append_rt2hdr(txopt->srcrt2, rth_addr); ++ } ++ } ++ ++ /* Fill in the fields of MH */ ++ mh->payload = NEXTHDR_NONE; ++ mh->length = (padded_len >> 3) - 1; /* Units of 8 octets - 1 */ ++ mh->type = msg_type; ++ mh->reserved = 0; ++ mh->checksum = 0; ++ ++ memcpy(mh->data, msg, msg_len); ++ prepare_mh_opts(mh->data + msg_len, msg_len + sizeof(*mh), ops); ++ /* If BAD is present, this is already done. */ ++ mipv6_add_pad((u8 *)mh + tot_len, padded_len - tot_len); ++ ++ if (parm && parm->k_bu && ops && ops->auth_data) { ++ /* Calculate the position of the authorization data before adding checksum*/ ++ mipv6_auth_build(parm->cn_addr, parm->coa, (__u8 *)mh, ++ (__u8 *)mh + padded_len - MIPV6_RR_MAC_LENGTH, parm->k_bu); ++ } ++ /* Calculate the MH checksum */ ++ mh->checksum = csum_ipv6_magic(fl.fl6_src, fl.fl6_dst, ++ padded_len, IPPROTO_MOBILITY, ++ csum_partial((char *)mh, padded_len, 0)); ++ ip6_build_xmit(sk, dstopts_getfrag, mh, &fl, padded_len, txopt, 255, ++ MSG_DONTWAIT); ++ /* dst cache must be cleared so RR messages can be routed through ++ different interfaces */ ++ sk_dst_reset(sk); ++ ++ if (txopt_len) ++ sock_kfree_s(sk, txopt, txopt_len); ++ sock_kfree_s(sk, mh, padded_len); ++ return 0; ++} ++ ++/** ++ * mipv6_send_brr - send a Binding Refresh Request ++ * @saddr: source address for BRR ++ * @daddr: destination address for BRR ++ * @ops: mobility options ++ * ++ * Sends a binding request. On a mobile node, use the mobile node's ++ * home address for @saddr. Returns 0 on success, negative on ++ * failure. ++ **/ ++int mipv6_send_brr(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct mipv6_mh_opt *ops) ++{ ++ struct mipv6_mh_brr br; ++ ++ memset(&br, 0, sizeof(br)); ++ /* We don't need to explicitly add a RH to brr, since it will be ++ * included automatically, if a BCE exists ++ */ ++ MIPV6_INC_STATS(n_brr_sent); ++ return send_mh(daddr, saddr, MIPV6_MH_BRR, sizeof(br), (u8 *)&br, ++ NULL, NULL, ops, NULL); ++} ++ ++/** ++ * mipv6_send_ba - send a Binding Acknowledgement ++ * @saddr: source address for BA ++ * @daddr: destination address for BA ++ * @reply_coa: destination care-of address of MN ++ * @auth_coa: care-of address of MN used for authentication ++ * @status: status field value ++ * @sequence: sequence number from BU ++ * @lifetime: granted lifetime for binding in seconds ++ * @ops: mobility options ++ * ++ * Send a binding acknowledgement. On a mobile node, use the mobile ++ * node's home address for saddr. Returns 0 on success, non-zero on ++ * failure. ++ **/ ++int mipv6_send_ba(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct in6_addr *auth_coa, struct in6_addr *rep_coa, ++ u8 status, u16 sequence, u32 lifetime, u8 *k_bu) ++{ ++ struct mipv6_mh_ba ba; ++ struct mipv6_auth_parm parm; ++ struct mipv6_mh_opt *ops = NULL; ++ int ops_len = 0, ret = 0; ++ struct mipv6_bce bc_entry; ++ int coming_home = 0; ++ int bypass_tnl = 0; ++ ++ memset(&ba, 0, sizeof(ba)); ++ ++ ba.status = status; ++ ba.sequence = htons(sequence); ++ ba.lifetime = htons(lifetime >> 2); ++ ++ DEBUG(DBG_INFO, "sending a status %d BA %s authenticator to MN \n" ++ "%x:%x:%x:%x:%x:%x:%x:%x at care of address \n" ++ "%x:%x:%x:%x:%x:%x:%x:%x : with lifetime %d and \n" ++ " sequence number %d", ++ status, k_bu ? "with" : "without", ++ NIPV6ADDR(daddr), NIPV6ADDR(auth_coa), lifetime, sequence); ++ ++ memset(&parm, 0, sizeof(parm)); ++ parm.coa = auth_coa; ++ parm.cn_addr = saddr; ++ ++ if (k_bu) { ++ ops_len += sizeof(struct mipv6_mo_bauth_data) + ++ MIPV6_RR_MAC_LENGTH; ++ parm.k_bu = k_bu; ++ } ++ ++ if (mip6node_cnf.binding_refresh_advice) { ++ ops_len += sizeof(struct mipv6_mo_br_advice); ++ } ++ if (ops_len) { ++ ops = alloc_mh_opts(ops_len); ++ if (ops == NULL) { ++ DEBUG(DBG_WARNING, "Out of memory"); ++ return -ENOMEM; ++ } ++ if (mip6node_cnf.binding_refresh_advice > 0) { ++ if (append_mh_opt(ops, MIPV6_OPT_BIND_REFRESH_ADVICE, 2, ++ &mip6node_cnf.binding_refresh_advice) < 0) { ++ DEBUG(DBG_WARNING, "Adding BRA failed"); ++ if (ops) ++ kfree(ops); ++ return -ENOMEM; ++ } ++ } ++ if (k_bu) { ++ if (append_mh_opt(ops, MIPV6_OPT_AUTH_DATA, ++ MIPV6_RR_MAC_LENGTH, NULL) < 0) { ++ DEBUG(DBG_WARNING, "Adding BAD failed"); ++ if (ops) ++ kfree(ops); ++ return -ENOMEM; ++ } ++ } ++ } ++ coming_home = !ipv6_addr_cmp(rep_coa, daddr); ++ ++ bypass_tnl = (coming_home && ++ !mipv6_bcache_get(daddr, saddr, &bc_entry) && ++ bc_entry.flags&MIPV6_BU_F_HOME && ++ status >= 128); ++ ++ if (bypass_tnl && mip6_fn.bce_tnl_rt_del) ++ mip6_fn.bce_tnl_rt_del(&bc_entry.coa, ++ &bc_entry.our_addr, ++ &bc_entry.home_addr); ++ ++ if (coming_home) ++ ret = send_mh(daddr, saddr, MIPV6_MH_BA, sizeof(ba), (u8 *)&ba, ++ NULL, NULL, ops, &parm); ++ else ++ ret = send_mh(daddr, saddr, MIPV6_MH_BA, sizeof(ba), (u8 *)&ba, ++ NULL, rep_coa, ops, &parm); ++ ++ if (bypass_tnl && mip6_fn.bce_tnl_rt_add) ++ mip6_fn.bce_tnl_rt_add(&bc_entry.coa, ++ &bc_entry.our_addr, ++ &bc_entry.home_addr); ++ ++ if (ret == 0) { ++ if (status < 128) { ++ MIPV6_INC_STATS(n_ba_sent); ++ } else { ++ MIPV6_INC_STATS(n_ban_sent); ++ } ++ } ++ ++ if (ops) ++ kfree(ops); ++ ++ return 0; ++} ++ ++/** ++ * mipv6_send_be - send a Binding Error message ++ * @saddr: source address for BE ++ * @daddr: destination address for BE ++ * @home: Home Address in offending packet (if any) ++ * ++ * Sends a binding error. On a mobile node, use the mobile node's ++ * home address for @saddr. Returns 0 on success, negative on ++ * failure. ++ **/ ++int mipv6_send_be(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct in6_addr *home, __u8 status) ++{ ++ struct mipv6_mh_be be; ++ int ret = 0; ++ struct mipv6_bce bc_entry; ++ int bypass_tnl = 0; ++ ++ if (ipv6_addr_is_multicast(daddr)) ++ return -EINVAL; ++ ++ memset(&be, 0, sizeof(be)); ++ be.status = status; ++ if (home) ++ ipv6_addr_copy(&be.home_addr, home); ++ ++ if (mipv6_bcache_get(daddr, saddr, &bc_entry) == 0 && ++ bc_entry.flags&MIPV6_BU_F_HOME) ++ bypass_tnl = 1; ++ ++ if (bypass_tnl && mip6_fn.bce_tnl_rt_del) ++ mip6_fn.bce_tnl_rt_del(&bc_entry.coa, ++ &bc_entry.our_addr, ++ &bc_entry.home_addr); ++ ++ ret = send_mh(daddr, saddr, MIPV6_MH_BE, sizeof(be), (u8 *)&be, ++ NULL, NULL, NULL, NULL); ++ ++ if (bypass_tnl && mip6_fn.bce_tnl_rt_add) ++ mip6_fn.bce_tnl_rt_add(&bc_entry.coa, ++ &bc_entry.our_addr, ++ &bc_entry.home_addr); ++ ++ if (ret == 0) ++ MIPV6_INC_STATS(n_be_sent); ++ ++ return ret; ++} ++ ++/** ++ * mipv6_send_addr_test - send a HoT or CoT message ++ * @saddr: source address ++ * @daddr: destination address ++ * @msg_type: HoT or CoT message ++ * @init: HoTI or CoTI message ++ * ++ * Send a reply to HoTI or CoTI message. ++ **/ ++static int mipv6_send_addr_test(struct in6_addr *saddr, ++ struct in6_addr *daddr, ++ int msg_type, ++ struct mipv6_mh_addr_ti *init) ++{ ++ u_int8_t *kgen_token = NULL; ++ struct mipv6_mh_addr_test addr_test; ++ struct mipv6_rr_nonce *nonce; ++ struct mipv6_mh_opt *ops = NULL; ++ int ret = 0; ++ ++ DEBUG_FUNC(); ++ ++ if ((nonce = mipv6_rr_get_new_nonce())== NULL) { ++ DEBUG(DBG_WARNING, "Nonce creation failed"); ++ return 0; ++ } ++ if (mipv6_rr_cookie_create(daddr, &kgen_token, nonce->index)) { ++ DEBUG(DBG_WARNING, "No cookie"); ++ return 0; ++ } ++ ++ addr_test.nonce_index = nonce->index; ++ memcpy(addr_test.init_cookie, init->init_cookie, ++ MIPV6_RR_COOKIE_LENGTH); ++ memcpy(addr_test.kgen_token, kgen_token, ++ MIPV6_RR_COOKIE_LENGTH); ++ ++ /* No options defined */ ++ ret = send_mh(daddr, saddr, msg_type, sizeof(addr_test), ++ (u8 *)&addr_test, NULL, NULL, ops, NULL); ++ ++ if (ret == 0) { ++ if (msg_type == MIPV6_MH_HOT) { ++ MIPV6_INC_STATS(n_hot_sent); ++ } else { ++ MIPV6_INC_STATS(n_cot_sent); ++ } ++ } ++ ++ return 0; ++} ++ ++static void bc_cache_add(int ifindex, struct in6_addr *daddr, ++ struct in6_addr *haddr, struct in6_addr *coa, ++ struct in6_addr *rep_coa, __u32 lifetime, ++ __u16 sequence, __u8 flags, __u8 *k_bu) ++{ ++ __u8 ba_status = SUCCESS; ++ ++ if (lifetime > MAX_RR_BINDING_LIFE) ++ lifetime = MAX_RR_BINDING_LIFE; ++ ++ if (mipv6_bcache_add(ifindex, daddr, haddr, coa, lifetime, ++ sequence, flags, CACHE_ENTRY) != 0) { ++ DEBUG(DBG_ERROR, "binding failed."); ++ ba_status = INSUFFICIENT_RESOURCES; ++ } ++ ++ if (flags & MIPV6_BU_F_ACK) { ++ DEBUG(DBG_INFO, "sending ack (code=%d)", ba_status); ++ mipv6_send_ba(daddr, haddr, coa, rep_coa, ba_status, sequence, ++ lifetime, k_bu); ++ } ++} ++ ++static void bc_cn_home_add(int ifindex, struct in6_addr *daddr, ++ struct in6_addr *haddr, struct in6_addr *coa, ++ struct in6_addr *rep_coa, __u32 lifetime, ++ __u16 sequence, __u8 flags, __u8 *k_bu) ++{ ++ mipv6_send_ba(daddr, haddr, coa, rep_coa, ++ HOME_REGISTRATION_NOT_SUPPORTED, ++ sequence, lifetime, k_bu); ++} ++ ++static void bc_cache_delete(struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u16 sequence, __u8 flags, ++ __u8 *k_bu) ++{ ++ __u8 status = SUCCESS; ++ ++ /* Cached Care-of Address Deregistration */ ++ if (mipv6_bcache_exists(haddr, daddr) == CACHE_ENTRY) { ++ mipv6_bcache_delete(haddr, daddr, CACHE_ENTRY); ++ } else { ++ DEBUG(DBG_INFO, "entry is not in cache"); ++ status = REASON_UNSPECIFIED; ++ } ++ if (flags & MIPV6_BU_F_ACK) { ++ mipv6_send_ba(daddr, haddr, coa, rep_coa, status, sequence, ++ 0, k_bu); ++ } ++} ++ ++static void bc_cn_home_delete(struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u16 sequence, __u8 flags, ++ __u8 *k_bu) ++{ ++} ++ ++/** ++ * parse_mo_tlv - Parse TLV-encoded Mobility Options ++ * @mos: pointer to Mobility Options ++ * @len: total length of options ++ * @opts: structure to store option pointers ++ * ++ * Parses Mobility Options passed in @mos. Stores pointers in @opts ++ * to all valid mobility options found in @mos. Unknown options and ++ * padding (%MIPV6_OPT_PAD1 and %MIPV6_OPT_PADN) is ignored and ++ * skipped. ++ **/ ++int parse_mo_tlv(void *mos, int len, struct mobopt *opts) ++{ ++ struct mipv6_mo *curr = (struct mipv6_mo *)mos; ++ int left = len; ++ ++ while (left > 0) { ++ int optlen = 0; ++ if (curr->type == MIPV6_OPT_PAD1) ++ optlen = 1; ++ else ++ optlen = 2 + curr->length; ++ ++ if (optlen > left) ++ goto bad; ++ ++ switch (curr->type) { ++ case MIPV6_OPT_PAD1: ++ DEBUG(DBG_DATADUMP, "MIPV6_OPT_PAD1 at %x", curr); ++ break; ++ case MIPV6_OPT_PADN: ++ DEBUG(DBG_DATADUMP, "MIPV6_OPT_PADN at %x", curr); ++ break; ++ case MIPV6_OPT_ALTERNATE_COA: ++ DEBUG(DBG_DATADUMP, "MIPV6_OPT_ACOA at %x", curr); ++ opts->alt_coa = (struct mipv6_mo_alt_coa *)curr; ++ break; ++ case MIPV6_OPT_NONCE_INDICES: ++ DEBUG(DBG_DATADUMP, "MIPV6_OPT_NONCE_INDICES at %x", curr); ++ opts->nonce_indices = ++ (struct mipv6_mo_nonce_indices *)curr; ++ break; ++ case MIPV6_OPT_AUTH_DATA: ++ DEBUG(DBG_DATADUMP, "MIPV6_OPT_AUTH_DATA at %x", curr); ++ opts->auth_data = (struct mipv6_mo_bauth_data *)curr; ++ break; ++ case MIPV6_OPT_BIND_REFRESH_ADVICE: ++ DEBUG(DBG_DATADUMP, "MIPV6_OPT_BIND_REFRESH_ADVICE at %x", curr); ++ opts->br_advice = (struct mipv6_mo_br_advice *)curr; ++ break; ++ default: ++ DEBUG(DBG_INFO, "MO Unknown option type %d at %x, ignoring.", ++ curr->type, curr); ++ /* unknown mobility option, ignore and skip */ ++ } ++ ++ (u8 *)curr += optlen; ++ left -= optlen; ++ } ++ ++ if (left == 0) ++ return 0; ++ bad: ++ return -1; ++} ++ ++/* ++ * ++ * Mobility Header Message handlers ++ * ++ */ ++ ++static int mipv6_handle_mh_testinit(struct sk_buff *skb, ++ struct in6_addr *cn, ++ struct in6_addr *lcoa, ++ struct in6_addr *saddr, ++ struct in6_addr *fcoa, ++ struct mipv6_mh *mh) ++{ ++ struct mipv6_mh_addr_ti *ti = (struct mipv6_mh_addr_ti *)mh->data; ++ int msg_len = (mh->length+1) << 3; ++ int opt_len; ++ DEBUG_FUNC(); ++ ++ if (msg_len > skb->len) ++ return -1; ++ ++ opt_len = msg_len - sizeof(*mh) - sizeof(*ti); ++ ++ if (opt_len < 0) { ++ __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw; ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, pos, skb->dev); ++ ++ DEBUG(DBG_INFO, "Mobility Header length less than H/C TestInit"); ++ return -1; ++ } ++ if (!mip6node_cnf.accept_ret_rout) { ++ DEBUG(DBG_INFO, "Return routability administratively disabled"); ++ return -1; ++ } ++ if (lcoa || fcoa) { ++ DEBUG(DBG_INFO, "H/C TestInit has HAO or RTH2, dropped."); ++ return -1; ++ } ++ ++ if (mh->type == MIPV6_MH_HOTI) { ++ MIPV6_INC_STATS(n_hoti_rcvd); ++ return mipv6_send_addr_test(cn, saddr, MIPV6_MH_HOT, ti); ++ } else if (mh->type == MIPV6_MH_COTI) { ++ MIPV6_INC_STATS(n_coti_rcvd); ++ return mipv6_send_addr_test(cn, saddr, MIPV6_MH_COT, ti); ++ } else ++ return -1; /* Impossible to get here */ ++} ++ ++/** ++ * mipv6_handle_mh_bu - Binding Update handler ++ * @src: care-of address of sender ++ * @dst: our address ++ * @haddr: home address of sender ++ * @mh: pointer to the beginning of the Mobility Header ++ * ++ * Handles Binding Update. Packet and offset to option are passed. ++ * Returns 0 on success, otherwise negative. ++ **/ ++static int mipv6_handle_mh_bu(struct sk_buff *skb, ++ struct in6_addr *dst, ++ struct in6_addr *unused, ++ struct in6_addr *haddr, ++ struct in6_addr *coaddr, ++ struct mipv6_mh *mh) ++{ ++ struct mipv6_mh_bu *bu = (struct mipv6_mh_bu *)mh->data; ++ int msg_len = (mh->length+1) << 3; ++ int opt_len; ++ int auth = 0; ++ int dereg; /* Is this deregistration? */ ++ int addr_type; ++ ++ struct mipv6_bce bc_entry; ++ struct in6_addr *coa, *reply_coa; ++ __u8 *key_bu = NULL; /* RR BU authentication key */ ++ __u8 flags = bu->flags; ++ __u16 sequence; ++ __u32 lifetime; ++ __u16 nonce_ind = (__u16) -1; ++ ++ if (msg_len > skb->len) ++ return -1; ++ ++ opt_len = msg_len - sizeof(*mh) - sizeof(*bu); ++ ++ if (opt_len < 0) { ++ __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw; ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, pos, skb->dev); ++ ++ DEBUG(DBG_INFO, "Mobility Header length less than BU"); ++ MIPV6_INC_STATS(n_bu_drop.invalid); ++ return -1; ++ } ++ ++ addr_type = ipv6_addr_type(haddr); ++ if (addr_type&IPV6_ADDR_LINKLOCAL || !(addr_type&IPV6_ADDR_UNICAST)) ++ return -EINVAL; ++ ++ /* If HAO not present, CoA == HAddr */ ++ if (coaddr == NULL) ++ coa = haddr; ++ else { ++ coa = coaddr; ++ addr_type = ipv6_addr_type(coa); ++ if (addr_type&IPV6_ADDR_LINKLOCAL || ++ !(addr_type&IPV6_ADDR_UNICAST)) ++ return -EINVAL; ++ } ++ reply_coa = coa; ++ ++ sequence = ntohs(bu->sequence); ++ if (bu->lifetime == 0xffff) ++ lifetime = 0xffffffff; ++ else ++ lifetime = ntohs(bu->lifetime) << 2; ++ ++ dereg = (ipv6_addr_cmp(haddr, coa) == 0 || lifetime == 0); ++ ++ if (opt_len > 0) { ++ struct mobopt opts; ++ memset(&opts, 0, sizeof(opts)); ++ if (parse_mo_tlv(bu + 1, opt_len, &opts) < 0) { ++ MIPV6_INC_STATS(n_bu_drop.invalid); ++ return -1; ++ } ++ /* ++ * MIPV6_OPT_AUTH_DATA, MIPV6_OPT_NONCE_INDICES, ++ * MIPV6_OPT_ALT_COA ++ */ ++ if (opts.alt_coa) { ++ coa = &opts.alt_coa->addr; ++ dereg = (ipv6_addr_cmp(haddr, coa) == 0 || lifetime == 0); ++ } ++ addr_type = ipv6_addr_type(coa); ++ if (addr_type&IPV6_ADDR_LINKLOCAL || ++ !(addr_type&IPV6_ADDR_UNICAST)) ++ return -EINVAL; ++ ++ if (flags & MIPV6_BU_F_HOME) { ++ if (opts.nonce_indices) ++ return -1; ++ } else { ++ u8 ba_status = 0; ++ u8 *h_ckie = NULL, *c_ckie = NULL; /* Home and care-of cookies */ ++ ++ /* BUs to CN MUST include authorization data and nonce indices options */ ++ if (!opts.auth_data || !opts.nonce_indices) { ++ DEBUG(DBG_WARNING, ++ "Route optimization BU without authorization material, aborting processing"); ++ return MH_AUTH_FAILED; ++ } ++ if (mipv6_rr_cookie_create( ++ haddr, &h_ckie, opts.nonce_indices->home_nonce_i) < 0) { ++ DEBUG(DBG_WARNING, ++ "mipv6_rr_cookie_create failed for home cookie"); ++ ba_status = EXPIRED_HOME_NONCE_INDEX; ++ } ++ nonce_ind = opts.nonce_indices->home_nonce_i; ++ /* Don't create the care-of cookie, if MN deregisters */ ++ if (!dereg && mipv6_rr_cookie_create( ++ coa, &c_ckie, ++ opts.nonce_indices->careof_nonce_i) < 0) { ++ DEBUG(DBG_WARNING, ++ "mipv6_rr_cookie_create failed for coa cookie"); ++ if (ba_status == 0) ++ ba_status = EXPIRED_CAREOF_NONCE_INDEX; ++ else ++ ba_status = EXPIRED_NONCES; ++ } ++ if (ba_status == 0) { ++ if (dereg) ++ key_bu = mipv6_rr_key_calc(h_ckie, NULL); ++ else ++ key_bu = mipv6_rr_key_calc(h_ckie, c_ckie); ++ mh->checksum = 0;/* TODO: Don't mangle the packet */ ++ if (key_bu && mipv6_auth_check( ++ dst, coa, (__u8 *)mh, msg_len + sizeof(*mh), opts.auth_data, key_bu) == 0) { ++ DEBUG(DBG_INFO, "mipv6_auth_check OK for BU"); ++ auth = 1; ++ } else { ++ DEBUG(DBG_WARNING, ++ "BU Authentication failed"); ++ } ++ } ++ if (h_ckie) ++ kfree(h_ckie); ++ if (c_ckie) ++ kfree(c_ckie); ++ if (ba_status != 0) { ++ MIPV6_INC_STATS(n_bu_drop.auth); ++ mipv6_send_ba(dst, haddr, coa, ++ reply_coa, ba_status, ++ sequence, 0, NULL); ++ goto out; ++ } ++ } ++ ++ } ++ /* Require authorization option for RO, home reg is protected by IPsec */ ++ if (!(flags & MIPV6_BU_F_HOME) && !auth) { ++ MIPV6_INC_STATS(n_bu_drop.auth); ++ if (key_bu) ++ kfree(key_bu); ++ return MH_AUTH_FAILED; ++ } ++ ++ if (mipv6_bcache_get(haddr, dst, &bc_entry) == 0) { ++ if ((bc_entry.flags&MIPV6_BU_F_HOME) != ++ (flags&MIPV6_BU_F_HOME)) { ++ DEBUG(DBG_INFO, ++ "Registration type change. Sending BA REG_TYPE_CHANGE_FORBIDDEN"); ++ mipv6_send_ba(dst, haddr, coa, reply_coa, ++ REG_TYPE_CHANGE_FORBIDDEN, ++ sequence, lifetime, key_bu); ++ goto out; ++ } ++ if (!MIPV6_SEQ_GT(sequence, bc_entry.seq)) { ++ DEBUG(DBG_INFO, ++ "Sequence number mismatch. Sending BA SEQUENCE_NUMBER_OUT_OF_WINDOW"); ++ mipv6_send_ba(dst, haddr, coa, reply_coa, ++ SEQUENCE_NUMBER_OUT_OF_WINDOW, ++ bc_entry.seq, lifetime, key_bu); ++ goto out; ++ } ++ } ++ ++ if (!dereg) { ++ int ifindex; ++ struct rt6_info *rt; ++ ++ /* Avoid looping binding cache entries */ ++ if (mipv6_bcache_get(coa, dst, &bc_entry) == 0) { ++ DEBUG(DBG_WARNING, "Looped BU, dropping the packet"); ++ goto out; ++ } ++ DEBUG(DBG_INFO, "calling bu_add."); ++ if ((rt = rt6_lookup(haddr, dst, 0, 0)) != NULL) { ++ ifindex = rt->rt6i_dev->ifindex; ++ dst_release(&rt->u.dst); ++ } else { ++ /* ++ * Can't process the BU since the right interface is ++ * not found. ++ */ ++ DEBUG(DBG_WARNING, "No route entry found for handling " ++ "a BU request, (using 0 as index)"); ++ ifindex = 0; ++ } ++ if (flags & MIPV6_BU_F_HOME) ++ mip6_fn.bce_home_add(ifindex, dst, haddr, coa, ++ reply_coa, lifetime, sequence, ++ flags, key_bu); ++ else ++ mip6_fn.bce_cache_add(ifindex, dst, haddr, coa, ++ reply_coa, lifetime, sequence, ++ flags, key_bu); ++ } else { ++ DEBUG(DBG_INFO, "calling BCE delete."); ++ ++ if (flags & MIPV6_BU_F_HOME) ++ mip6_fn.bce_home_del(dst, haddr, coa, reply_coa, ++ sequence, flags, key_bu); ++ else { ++ mipv6_rr_invalidate_nonce(nonce_ind); ++ mip6_fn.bce_cache_del(dst, haddr, coa, reply_coa, ++ sequence, flags, key_bu); ++ } ++ } ++ out: ++ MIPV6_INC_STATS(n_bu_rcvd); ++ if (key_bu) ++ kfree(key_bu); ++ return 0; ++} ++ ++static int mipv6_mh_rcv(struct sk_buff *skb) ++{ ++ struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb; ++ struct mipv6_mh *mh; ++ struct in6_addr *lhome, *fhome, *lcoa = NULL, *fcoa = NULL; ++ int ret = 0; ++ ++ fhome = &skb->nh.ipv6h->saddr; ++ lhome = &skb->nh.ipv6h->daddr; ++ ++ if (opt->hao != 0) { ++ struct mipv6_dstopt_homeaddr *hao; ++ hao = (struct mipv6_dstopt_homeaddr *)(skb->nh.raw + opt->hao); ++ fcoa = &hao->addr; ++ } ++ ++ if (opt->srcrt2 != 0) { ++ struct rt2_hdr *rt2; ++ rt2 = (struct rt2_hdr *)((u8 *)skb->nh.raw + opt->srcrt2); ++ lcoa = &rt2->addr; ++ } ++ ++ /* Verify checksum is correct */ ++ if (skb->ip_summed == CHECKSUM_HW) { ++ skb->ip_summed = CHECKSUM_UNNECESSARY; ++ if (csum_ipv6_magic(fhome, lhome, skb->len, IPPROTO_MOBILITY, ++ skb->csum)) { ++ if (net_ratelimit()) ++ printk(KERN_WARNING "MIPv6 MH hw checksum failed\n"); ++ skb->ip_summed = CHECKSUM_NONE; ++ } ++ } ++ if (skb->ip_summed == CHECKSUM_NONE) { ++ if (csum_ipv6_magic(fhome, lhome, skb->len, IPPROTO_MOBILITY, ++ skb_checksum(skb, 0, skb->len, 0))) { ++ if (net_ratelimit()) ++ printk(KERN_WARNING "MIPv6 MH checksum failed\n"); ++ goto bad; ++ } ++ } ++ ++ if (!pskb_may_pull(skb, skb->h.raw-skb->data+sizeof(*mh)) || ++ !pskb_may_pull(skb, ++ skb->h.raw-skb->data+((skb->h.raw[1]+1)<<3))) { ++ DEBUG(DBG_INFO, "MIPv6 MH invalid length"); ++ kfree_skb(skb); ++ return 0; ++ } ++ ++ mh = (struct mipv6_mh *) skb->h.raw; ++ ++ /* Verify there are no more headers after the MH */ ++ if (mh->payload != NEXTHDR_NONE) { ++ __u32 pos = (__u32)&mh->payload - (__u32)skb->nh.raw; ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, pos, skb->dev); ++ ++ DEBUG(DBG_INFO, "MIPv6 MH error"); ++ goto bad; ++ } ++ ++ if (mh->type > MIPV6_MH_MAX) { ++ /* send binding error */ ++ printk("Invalid mobility header type (%d)\n", mh->type); ++ mipv6_send_be(lhome, fcoa ? fcoa : fhome, ++ fcoa ? fhome : NULL, ++ MIPV6_BE_UNKNOWN_MH_TYPE); ++ goto bad; ++ } ++ if (mh_rcv[mh->type].func != NULL) { ++ ret = mh_rcv[mh->type].func(skb, lhome, lcoa, fhome, fcoa, mh); ++ } else { ++ DEBUG(DBG_INFO, "No handler for MH Type %d", mh->type); ++ goto bad; ++ } ++ ++ kfree_skb(skb); ++ return 0; ++ ++bad: ++ MIPV6_INC_STATS(n_mh_in_error); ++ kfree_skb(skb); ++ return 0; ++ ++} ++ ++#if LINUX_VERSION_CODE >= 0x2052a ++struct inet6_protocol mipv6_mh_protocol = ++{ ++ mipv6_mh_rcv, /* handler */ ++ NULL /* error control */ ++}; ++#else ++struct inet6_protocol mipv6_mh_protocol = ++{ ++ mipv6_mh_rcv, /* handler */ ++ NULL, /* error control */ ++ NULL, /* next */ ++ IPPROTO_MOBILITY, /* protocol ID */ ++ 0, /* copy */ ++ NULL, /* data */ ++ "MIPv6 MH" /* name */ ++}; ++#endif ++ ++/* ++ * ++ * Code module init/exit functions ++ * ++ */ ++ ++int __init mipv6_mh_common_init(void) ++{ ++ struct sock *sk; ++ int err; ++ ++ mip6_fn.bce_home_add = bc_cn_home_add; ++ mip6_fn.bce_cache_add = bc_cache_add; ++ mip6_fn.bce_home_del = bc_cn_home_delete; ++ mip6_fn.bce_cache_del = bc_cache_delete; ++ ++ mipv6_mh_socket = sock_alloc(); ++ if (mipv6_mh_socket == NULL) { ++ printk(KERN_ERR ++ "Failed to create the MIP6 MH control socket.\n"); ++ return -1; ++ } ++ mipv6_mh_socket->type = SOCK_RAW; ++ ++ if ((err = sock_create(PF_INET6, SOCK_RAW, IPPROTO_MOBILITY, ++ &mipv6_mh_socket)) < 0) { ++ printk(KERN_ERR ++ "Failed to initialize the MIP6 MH control socket (err %d).\n", ++ err); ++ sock_release(mipv6_mh_socket); ++ mipv6_mh_socket = NULL; /* for safety */ ++ return err; ++ } ++ ++ sk = mipv6_mh_socket->sk; ++ sk->allocation = GFP_ATOMIC; ++ sk->sndbuf = 64 * 1024 + sizeof(struct sk_buff); ++ sk->prot->unhash(sk); ++ ++ memset(&mh_rcv, 0, sizeof(mh_rcv)); ++ mh_rcv[MIPV6_MH_HOTI].func = mipv6_handle_mh_testinit; ++ mh_rcv[MIPV6_MH_COTI].func = mipv6_handle_mh_testinit; ++ mh_rcv[MIPV6_MH_BU].func = mipv6_handle_mh_bu; ++ ++#if LINUX_VERSION_CODE >= 0x2052a ++ if (inet6_add_protocol(&mipv6_mh_protocol, IPPROTO_MOBILITY) < 0) { ++ printk(KERN_ERR "Failed to register MOBILITY protocol\n"); ++ sock_release(mipv6_mh_socket); ++ mipv6_mh_socket = NULL; ++ return -EAGAIN; ++ } ++#else ++ inet6_add_protocol(&mipv6_mh_protocol); ++#endif ++ /* To disable the use of dst_cache, ++ * which slows down the sending of BUs ?? ++ */ ++ sk->dst_cache=NULL; ++ ++ return 0; ++} ++ ++void __exit mipv6_mh_common_exit(void) ++{ ++ if (mipv6_mh_socket) sock_release(mipv6_mh_socket); ++ mipv6_mh_socket = NULL; /* For safety. */ ++ ++#if LINUX_VERSION_CODE >= 0x2052a ++ inet6_del_protocol(&mipv6_mh_protocol, IPPROTO_MOBILITY); ++#else ++ inet6_del_protocol(&mipv6_mh_protocol); ++#endif ++ memset(&mh_rcv, 0, sizeof(mh_rcv)); ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mobhdr_mn.c linux-2.4.25/net/ipv6/mobile_ip6/mobhdr_mn.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/mobhdr_mn.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/mobhdr_mn.c 2004-06-26 11:29:32.000000000 +0100 +@@ -0,0 +1,1155 @@ ++/* ++ * Mobile IPv6 Mobility Header Functions for Mobile Node ++ * ++ * Authors: ++ * Antti Tuominen ++ * Niklas Kämpe ++ * Henrik Petander ++ * ++ * $Id:$ ++ * ++ * 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. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mobhdr.h" ++#include "mn.h" ++#include "bul.h" ++#include "rr_crypto.h" ++#include "debug.h" ++#include "util.h" ++#include "stats.h" ++ ++int rr_configured = 1; ++ ++/* Return value of mipv6_rr_state() */ ++#define NO_RR 0 ++#define DO_RR 1 ++#define RR_FOR_COA 2 ++#define INPROGRESS_RR 3 ++ ++/** ++ * send_bu_msg - sends a Binding Update ++ * @bulentry : BUL entry with the information for building a BU ++ * ++ * Function builds a BU msg based on the contents of a bul entry. ++ * Does not change the bul entry. ++ **/ ++static int send_bu_msg(struct mipv6_bul_entry *binding) ++{ ++ int auth = 0; /* Use auth */ ++ int ret = 0; ++ struct mipv6_auth_parm parm; ++ struct mipv6_mh_bu bu; ++ ++ if (!binding) { ++ DEBUG(DBG_ERROR, "called with a null bul entry"); ++ return -1; ++ } ++ ++ memset(&parm, 0, sizeof(parm)); ++ if (mipv6_prefix_compare(&binding->coa, &binding->home_addr, 64)) ++ parm.coa = &binding->home_addr; ++ else ++ parm.coa = &binding->coa; ++ parm.cn_addr = &binding->cn_addr; ++ ++ if (binding->rr && binding->rr->kbu) { ++ DEBUG(DBG_INFO, "Binding with key"); ++ auth = 1; ++ parm.k_bu = binding->rr->kbu; ++ } ++ memset(&bu, 0, sizeof(bu)); ++ bu.flags = binding->flags; ++ bu.sequence = htons(binding->seq); ++ bu.lifetime = htons(binding->lifetime >> 2); ++ bu.reserved = 0; ++ ++ ret = send_mh(&binding->cn_addr, &binding->home_addr, ++ MIPV6_MH_BU, sizeof(bu), (u8 *)&bu, ++ &binding->home_addr, NULL, ++ binding->ops, &parm); ++ ++ if (ret == 0) ++ MIPV6_INC_STATS(n_bu_sent); ++ ++ return ret; ++} ++ ++/** ++ * mipv6_send_addr_test_init - send a HoTI or CoTI message ++ * @saddr: source address for H/CoTI ++ * @daddr: destination address for H/CoTI ++ * @msg_type: Identifies whether HoTI or CoTI ++ * @init_cookie: the HoTi or CoTi init cookie ++ * ++ * The message will be retransmitted till we get a HoT or CoT message, since ++ * our caller (mipv6_RR_start) has entered this message in the BUL with ++ * exponential backoff retramission set. ++ */ ++static int mipv6_send_addr_test_init(struct in6_addr *saddr, ++ struct in6_addr *daddr, ++ u8 msg_type, ++ u8 *init_cookie) ++{ ++ struct mipv6_mh_addr_ti ti; ++ struct mipv6_mh_opt *ops = NULL; ++ int ret = 0; ++ ++ /* Set reserved and copy the cookie from address test init msg */ ++ ti.reserved = 0; ++ mipv6_rr_mn_cookie_create(init_cookie); ++ memcpy(ti.init_cookie, init_cookie, MIPV6_RR_COOKIE_LENGTH); ++ ++ ret = send_mh(daddr, saddr, msg_type, sizeof(ti), (u8 *)&ti, ++ NULL, NULL, ops, NULL); ++ if (ret == 0) { ++ if (msg_type == MIPV6_MH_HOTI) { ++ MIPV6_INC_STATS(n_hoti_sent); ++ } else { ++ MIPV6_INC_STATS(n_coti_sent); ++ } ++ } ++ ++ return ret; ++} ++ ++/* ++ * ++ * Callback handlers for binding update list ++ * ++ */ ++ ++/* Return value 0 means keep entry, non-zero means discard entry. */ ++ ++/* Callback for BUs not requiring acknowledgement ++ */ ++int bul_entry_expired(struct mipv6_bul_entry *bulentry) ++{ ++ /* Lifetime expired, delete entry. */ ++ DEBUG(DBG_INFO, "bul entry 0x%p lifetime expired, deleting entry", ++ bulentry); ++ return 1; ++} ++ ++/* Callback for BUs requiring acknowledgement with exponential resending ++ * scheme */ ++static int bul_resend_exp(struct mipv6_bul_entry *bulentry) ++{ ++ unsigned long now = jiffies; ++ ++ DEBUG(DBG_INFO, "(0x%x) resending bu", (int) bulentry); ++ ++ ++ /* If sending a de-registration, do not care about the ++ * lifetime value, as de-registrations are normally sent with ++ * a zero lifetime value. If the entry is a home entry get the ++ * current lifetime. ++ */ ++ ++ if (bulentry->lifetime != 0) { ++ bulentry->lifetime = mipv6_mn_get_bulifetime( ++ &bulentry->home_addr, &bulentry->coa, bulentry->flags); ++ ++ bulentry->expire = now + bulentry->lifetime * HZ; ++ } else { ++ bulentry->expire = now + HOME_RESEND_EXPIRE * HZ; ++ } ++ if (bulentry->rr) { ++ /* Redo RR, if cookies have expired */ ++ if (time_after(jiffies, bulentry->rr->home_time + MAX_TOKEN_LIFE * HZ)) ++ bulentry->rr->rr_state |= RR_WAITH; ++ if (time_after(jiffies, bulentry->rr->careof_time + MAX_NONCE_LIFE * HZ)) ++ bulentry->rr->rr_state |= RR_WAITC; ++ ++ if (bulentry->rr->rr_state & RR_WAITH) { ++ /* Resend HoTI directly */ ++ mipv6_send_addr_test_init(&bulentry->home_addr, ++ &bulentry->cn_addr, MIPV6_MH_HOTI, ++ bulentry->rr->hot_cookie); ++ } ++ if (bulentry->rr->rr_state & RR_WAITC) { ++ /* Resend CoTI directly */ ++ mipv6_send_addr_test_init(&bulentry->coa, ++ &bulentry->cn_addr, MIPV6_MH_COTI, ++ bulentry->rr->cot_cookie); ++ } ++ goto out; ++ } ++ ++ bulentry->seq++; ++ ++ if (send_bu_msg(bulentry) < 0) ++ DEBUG(DBG_ERROR, "Resending of BU failed"); ++ ++out: ++ /* Schedule next retransmission */ ++ if (bulentry->delay < bulentry->maxdelay) { ++ bulentry->delay = 2 * bulentry->delay; ++ if (bulentry->delay > bulentry->maxdelay) { ++ /* can happen if maxdelay is not power(mindelay, 2) */ ++ bulentry->delay = bulentry->maxdelay; ++ } ++ } else if (bulentry->flags & MIPV6_BU_F_HOME) { ++ /* Home registration - continue sending BU at maxdelay rate */ ++ DEBUG(DBG_INFO, "Sending BU to HA after max ack wait time " ++ "reached(0x%x)", (int) bulentry); ++ bulentry->delay = bulentry->maxdelay; ++ } else if (!(bulentry->flags & MIPV6_BU_F_HOME)) { ++ /* Failed to get BA from a CN */ ++ bulentry->callback_time = now; ++ return -1; ++ } ++ ++ bulentry->callback_time = now + bulentry->delay * HZ; ++ return 0; ++} ++ ++ ++ ++/* Callback for sending a registration refresh BU ++ */ ++static int bul_refresh(struct mipv6_bul_entry *bulentry) ++{ ++ unsigned long now = jiffies; ++ ++ /* Refresh interval passed, send new BU */ ++ DEBUG(DBG_INFO, "bul entry 0x%x refresh interval passed, sending new BU", (int) bulentry); ++ if (bulentry->lifetime == 0) ++ return 0; ++ ++ /* Set new maximum lifetime and expiration time */ ++ bulentry->lifetime = mipv6_mn_get_bulifetime(&bulentry->home_addr, ++ &bulentry->coa, ++ bulentry->flags); ++ bulentry->expire = now + bulentry->lifetime * HZ; ++ bulentry->seq++; ++ /* Send update */ ++ if (send_bu_msg(bulentry) < 0) ++ DEBUG(DBG_ERROR, "Resending of BU failed"); ++ ++ if (time_after_eq(now, bulentry->expire)) { ++ /* Sanity check */ ++ DEBUG(DBG_ERROR, "bul entry expire time in history - setting expire to %u secs", ERROR_DEF_LIFETIME); ++ bulentry->lifetime = ERROR_DEF_LIFETIME; ++ bulentry->expire = now + ERROR_DEF_LIFETIME*HZ; ++ } ++ ++ /* Set up retransmission */ ++ bulentry->state = RESEND_EXP; ++ bulentry->callback = bul_resend_exp; ++ bulentry->callback_time = now + INITIAL_BINDACK_TIMEOUT*HZ; ++ bulentry->delay = INITIAL_BINDACK_TIMEOUT; ++ bulentry->maxdelay = MAX_BINDACK_TIMEOUT; ++ ++ return 0; ++} ++ ++static int mipv6_send_RR_bu(struct mipv6_bul_entry *bulentry) ++{ ++ int ret; ++ int ops_len = 0; ++ u16 nonces[2]; ++ ++ DEBUG(DBG_INFO, "Sending BU to CN %x:%x:%x:%x:%x:%x:%x:%x " ++ "for home address %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&bulentry->cn_addr), NIPV6ADDR(&bulentry->home_addr)); ++ nonces[0] = bulentry->rr->home_nonce_index; ++ nonces[1] = bulentry->rr->careof_nonce_index; ++ ops_len = sizeof(struct mipv6_mo_bauth_data) + MIPV6_RR_MAC_LENGTH + ++ sizeof(struct mipv6_mo_nonce_indices); ++ if (bulentry->ops) { ++ DEBUG(DBG_WARNING, "Bul entry had existing mobility options, freeing them"); ++ kfree(bulentry->ops); ++ } ++ bulentry->ops = alloc_mh_opts(ops_len); ++ ++ if (!bulentry->ops) ++ return -ENOMEM; ++ if (append_mh_opt(bulentry->ops, MIPV6_OPT_NONCE_INDICES, ++ sizeof(struct mipv6_mo_nonce_indices) - 2, nonces) < 0) ++ return -ENOMEM; ++ ++ if (append_mh_opt(bulentry->ops, MIPV6_OPT_AUTH_DATA, ++ MIPV6_RR_MAC_LENGTH, NULL) < 0) ++ return -ENOMEM; ++ /* RR procedure is over, send a BU */ ++ if (!(bulentry->flags & MIPV6_BU_F_ACK)) { ++ DEBUG(DBG_INFO, "Setting bul callback to bul_entry_expired"); ++ bulentry->state = ACK_OK; ++ bulentry->callback = bul_entry_expired; ++ bulentry->callback_time = jiffies + HZ * bulentry->lifetime; ++ bulentry->expire = jiffies + HZ * bulentry->lifetime; ++ } ++ else { ++ bulentry->callback_time = jiffies + HZ; ++ bulentry->expire = jiffies + HZ * bulentry->lifetime; ++ } ++ ++ ret = send_bu_msg(bulentry); ++ mipv6_bul_reschedule(bulentry); ++ return ret; ++} ++ ++static int mipv6_rr_state(struct mipv6_bul_entry *bul, struct in6_addr *saddr, ++ struct in6_addr *coa, __u8 flags) ++{ ++ if (!rr_configured) ++ return NO_RR; ++ if (flags & MIPV6_BU_F_HOME) { ++ /* We don't need RR, this is a Home Registration */ ++ return NO_RR; ++ } ++ if (!bul || !bul->rr) { ++ /* First time BU to CN, need RR */ ++ return DO_RR; ++ } ++ ++ switch (bul->rr->rr_state) { ++ case RR_INIT: ++ /* Need RR if first BU to CN */ ++ return DO_RR; ++ case RR_DONE: ++ /* If MN moves to a new coa, do RR for it */ ++ if (!ipv6_addr_cmp(&bul->coa, coa)) ++ return NO_RR; ++ else ++ return DO_RR; ++ default: ++ /* ++ * We are in the middle of RR, the HoTI and CoTI have been ++ * sent. But we haven't got HoT and CoT from the CN, so ++ * don't do anything more at this time. ++ */ ++ return INPROGRESS_RR; ++ } ++} ++ ++/** ++ * mipv6_RR_start - Start Return Routability procedure ++ * @home_addr: home address ++ * @cn_addr: correspondent address ++ * @coa: care-of address ++ * @entry: binding update list entry (if any) ++ * @initdelay: initial ack timeout ++ * @maxackdelay: maximum ack timeout ++ * @flags: flags ++ * @lifetime: lifetime of binding ++ * @ops: mobility options ++ * ++ * Caller must hold @bul_lock (write). ++ **/ ++static int mipv6_RR_start(struct in6_addr *home_addr, struct in6_addr *cn_addr, ++ struct in6_addr *coa, struct mipv6_bul_entry *entry, ++ __u32 initdelay, __u32 maxackdelay, __u8 flags, ++ __u32 lifetime, struct mipv6_mh_opt *ops) ++{ ++ int ret = -1; ++ struct mipv6_bul_entry *bulentry = entry; ++ struct mipv6_rr_info *rr = NULL; ++ int seq = 0; ++ DEBUG_FUNC(); ++ ++ /* Do RR procedure only for care-of address after handoff, ++ if home cookie is still valid */ ++ if (bulentry && bulentry->rr) { ++ if (time_before(jiffies, bulentry->rr->home_time + MAX_NONCE_LIFE * HZ) && ++ lifetime && !(ipv6_addr_cmp(home_addr, coa) == 0)) { ++ mipv6_rr_mn_cookie_create(bulentry->rr->cot_cookie); ++ DEBUG(DBG_INFO, "Bul entry and rr info exist, only doing RR for CoA"); ++ ipv6_addr_copy(&bulentry->coa, coa); ++ bulentry->rr->rr_state |= RR_WAITC; ++ } else if (!lifetime) { /* Send only HoTi when returning home */ ++ mipv6_rr_mn_cookie_create(bulentry->rr->hot_cookie); ++ DEBUG(DBG_INFO, "Bul entry and rr info exist, only doing RR for HoA"); ++ ipv6_addr_copy(&bulentry->coa, coa); /* Home address as CoA */ ++ bulentry->rr->rr_state |= RR_WAITH; ++ } ++ } else { ++ DEBUG(DBG_INFO, "Doing RR for both HoA and CoA"); ++ rr = kmalloc(sizeof(*rr), GFP_ATOMIC); ++ memset(rr, 0, sizeof(*rr)); ++ rr->rr_state = RR_WAITHC; ++ } ++ if (bulentry) { ++ if (bulentry->state == ACK_ERROR) ++ goto out; ++ seq = bulentry->seq + 1; ++ } else ++ seq = 0; ++ /* Save the info in the BUL to retransmit the BU after RR is done */ ++ /* Caller must hold bul_lock (write) since we don't */ ++ ++ if ((bulentry = mipv6_bul_add(cn_addr, home_addr, coa, ++ min_t(__u32, lifetime, MAX_RR_BINDING_LIFE), ++ seq, flags, bul_resend_exp, initdelay, ++ RESEND_EXP, initdelay, ++ maxackdelay, ops, ++ rr)) == NULL) { ++ DEBUG(DBG_INFO, "couldn't update BUL for HoTi"); ++ goto out; ++ } ++ ++ rr = bulentry->rr; ++ if (rr->rr_state&RR_WAITH) ++ mipv6_send_addr_test_init(home_addr, cn_addr, MIPV6_MH_HOTI, ++ rr->hot_cookie); ++ if (ipv6_addr_cmp(home_addr, coa) && lifetime) ++ mipv6_send_addr_test_init(coa, cn_addr, MIPV6_MH_COTI, rr->cot_cookie); ++ else { ++ bulentry->rr->rr_state &= ~RR_WAITC; ++ } ++ ret = 0; ++out: ++ return ret; ++} ++ ++/* ++ * Status codes for mipv6_ba_rcvd() ++ */ ++#define STATUS_UPDATE 0 ++#define STATUS_REMOVE 1 ++ ++/** ++ * mipv6_ba_rcvd - Update BUL for this Binding Acknowledgement ++ * @ifindex: interface BA came from ++ * @cnaddr: sender IPv6 address ++ * @home_addr: home address ++ * @sequence: sequence number ++ * @lifetime: lifetime granted by Home Agent in seconds ++ * @refresh: recommended resend interval ++ * @status: %STATUS_UPDATE (ack) or %STATUS_REMOVE (nack) ++ * ++ * This function must be called to notify the module of the receipt of ++ * a binding acknowledgement so that it can cease retransmitting the ++ * option. The caller must have validated the acknowledgement before calling ++ * this function. 'status' can be either STATUS_UPDATE in which case the ++ * binding acknowledgement is assumed to be valid and the corresponding ++ * binding update list entry is updated, or STATUS_REMOVE in which case ++ * the corresponding binding update list entry is removed (this can be ++ * used upon receiving a negative acknowledgement). ++ * Returns 0 if a matching binding update has been sent or non-zero if ++ * not. ++ */ ++static int mipv6_ba_rcvd(int ifindex, struct in6_addr *cnaddr, ++ struct in6_addr *home_addr, ++ u16 sequence, u32 lifetime, ++ u32 refresh, int status) ++{ ++ struct mipv6_bul_entry *bulentry; ++ unsigned long now = jiffies; ++ struct in6_addr coa; ++ ++ DEBUG(DBG_INFO, "BA received with sequence number 0x%x, status: %d", ++ (int) sequence, status); ++ ++ /* Find corresponding entry in binding update list. */ ++ write_lock(&bul_lock); ++ if ((bulentry = mipv6_bul_get(cnaddr, home_addr)) == NULL) { ++ DEBUG(DBG_INFO, "- discarded, no entry in bul matches BA source address"); ++ write_unlock(&bul_lock); ++ return -1; ++ } ++ ++ ipv6_addr_copy(&coa, &bulentry->coa); ++ if (status == SEQUENCE_NUMBER_OUT_OF_WINDOW) { ++ __u32 lifetime = mipv6_mn_get_bulifetime(&bulentry->home_addr, ++ &bulentry->coa, ++ bulentry->flags); ++ bulentry->seq = sequence; ++ ++ mipv6_send_bu(&bulentry->home_addr, &bulentry->cn_addr, ++ &bulentry->coa, INITIAL_BINDACK_TIMEOUT, ++ MAX_BINDACK_TIMEOUT, 1, bulentry->flags, ++ lifetime, NULL); ++ write_unlock(&bul_lock); ++ return 0; ++ } else if (status >= REASON_UNSPECIFIED) { ++ int err; ++ int at_home = MN_NOT_AT_HOME; ++ DEBUG(DBG_WARNING, "- NACK - BA status: %d, deleting bul entry", status); ++ if (bulentry->flags & MIPV6_BU_F_HOME) { ++ struct mn_info *minfo; ++ read_lock(&mn_info_lock); ++ minfo = mipv6_mninfo_get_by_home(home_addr); ++ if (minfo) { ++ spin_lock(&minfo->lock); ++ if (minfo->is_at_home != MN_NOT_AT_HOME) ++ minfo->is_at_home = MN_AT_HOME; ++ at_home = minfo->is_at_home; ++ minfo->has_home_reg = 0; ++ spin_unlock(&minfo->lock); ++ } ++ read_unlock(&mn_info_lock); ++ DEBUG(DBG_ERROR, "Home registration failed: BA status: %d, deleting bul entry", status); ++ } ++ write_unlock(&bul_lock); ++ err = mipv6_bul_delete(cnaddr, home_addr); ++ if (at_home == MN_AT_HOME) { ++ mipv6_mn_send_home_na(home_addr); ++ write_lock_bh(&bul_lock); ++ mipv6_bul_iterate(mn_cn_handoff, &coa); ++ write_unlock_bh(&bul_lock); ++ } ++ return err; ++ } ++ bulentry->state = ACK_OK; ++ ++ if (bulentry->flags & MIPV6_BU_F_HOME && lifetime > 0) { ++ /* For home registrations: schedule a refresh binding update. ++ * Use the refresh interval given by home agent or 80% ++ * of lifetime, whichever is less. ++ * ++ * Adjust binding lifetime if 'granted' lifetime ++ * (lifetime value in received binding acknowledgement) ++ * is shorter than 'requested' lifetime (lifetime ++ * value sent in corresponding binding update). ++ * max((L_remain - (L_update - L_ack)), 0) ++ */ ++ if (lifetime * HZ < (bulentry->expire - bulentry->lastsend)) { ++ bulentry->expire = ++ max_t(__u32, bulentry->expire - ++ ((bulentry->expire - bulentry->lastsend) - ++ lifetime * HZ), jiffies + ++ ERROR_DEF_LIFETIME * HZ); ++ } ++ if (refresh > lifetime || refresh == 0) ++ refresh = 4 * lifetime / 5; ++ DEBUG(DBG_INFO, "setting callback for expiration of" ++ " a Home Registration: lifetime:%d, refresh:%d", ++ lifetime, refresh); ++ bulentry->callback = bul_refresh; ++ bulentry->callback_time = now + refresh * HZ; ++ bulentry->expire = now + lifetime * HZ; ++ bulentry->lifetime = lifetime; ++ if (time_after_eq(jiffies, bulentry->expire)) { ++ /* Sanity check */ ++ DEBUG(DBG_ERROR, "bul entry expire time in history - setting expire to %u secs", ++ ERROR_DEF_LIFETIME); ++ bulentry->expire = jiffies + ERROR_DEF_LIFETIME * HZ; ++ } ++ mipv6_mn_set_home_reg(home_addr, 1); ++ mipv6_bul_iterate(mn_cn_handoff, &coa); ++ } else if ((bulentry->flags & MIPV6_BU_F_HOME) && bulentry->lifetime == 0) { ++ write_unlock(&bul_lock); ++ DEBUG(DBG_INFO, "Got BA for deregistration BU"); ++ mipv6_mn_set_home_reg(home_addr, 0); ++ mipv6_bul_delete(cnaddr, home_addr); ++ mipv6_mn_send_home_na(home_addr); ++ ++ write_lock_bh(&bul_lock); ++ mipv6_bul_iterate(mn_cn_handoff, &coa); ++ write_unlock_bh(&bul_lock); ++ return 0; ++ } ++ ++ mipv6_bul_reschedule(bulentry); ++ write_unlock(&bul_lock); ++ ++ return 0; ++} ++ ++static int mipv6_handle_mh_HC_test(struct sk_buff *skb, ++ struct in6_addr *saddr, ++ struct in6_addr *fcoa, ++ struct in6_addr *cn, ++ struct in6_addr *lcoa, ++ struct mipv6_mh *mh) ++{ ++ int ret = 0; ++ int msg_len = (mh->length+1) << 3; ++ int opt_len; ++ ++ struct mipv6_mh_addr_test *tm = (struct mipv6_mh_addr_test *)mh->data; ++ struct mipv6_bul_entry *bulentry; ++ ++ DEBUG_FUNC(); ++ ++ if (msg_len > skb->len) ++ return -1; ++ ++ opt_len = msg_len - sizeof(*mh) - sizeof(*tm); ++ ++ if (opt_len < 0) { ++ __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw; ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, pos, skb->dev); ++ ++ DEBUG(DBG_INFO, "Mobility Header length less than H/C Test"); ++ return -1; ++ } ++ if (fcoa || lcoa) { ++ DEBUG(DBG_INFO, "H/C Test has HAO or RTH2, dropped."); ++ return -1; ++ } ++ write_lock(&bul_lock); ++ ++ /* We need to get the home address, since CoT only has the CoA*/ ++ if (mh->type == MIPV6_MH_COT) { ++ if ((bulentry = mipv6_bul_get_by_ccookie(cn, tm->init_cookie)) == NULL) { ++ DEBUG(DBG_ERROR, "has no BUL or RR state for " ++ "source:%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(cn)); ++ write_unlock(&bul_lock); ++ return -1; ++ } ++ } else { /* HoT has the home address */ ++ if (((bulentry = mipv6_bul_get(cn, saddr)) == NULL) || !bulentry->rr) { ++ DEBUG(DBG_ERROR, "has no BUL or RR state for " ++ "source:%x:%x:%x:%x:%x:%x:%x:%x " ++ "dest:%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(cn), NIPV6ADDR(saddr)); ++ write_unlock(&bul_lock); ++ return -1; ++ } ++ } ++ ++ switch (mh->type) { ++ case MIPV6_MH_HOT: ++ if ((bulentry->rr->rr_state & RR_WAITH) == 0) { ++ DEBUG(DBG_ERROR, "Not waiting for a Home Test message"); ++ goto out; ++ } ++ /* ++ * Make sure no home cookies have been received yet. ++ * TODO: Check not being put in at this time since subsequent ++ * BU's after this time will have home cookie stored. ++ */ ++ ++ /* Check if the cookie received is the right one */ ++ if (!mipv6_equal_cookies(tm->init_cookie, ++ bulentry->rr->hot_cookie)) { ++ /* Invalid cookie, might be an old cookie */ ++ DEBUG(DBG_WARNING, "Received HoT cookie does not match stored cookie"); ++ goto out; ++ } ++ DEBUG(DBG_INFO, "Got Care-of Test message"); ++ bulentry->rr->rr_state &= ~RR_WAITH; ++ memcpy(bulentry->rr->home_cookie, tm->kgen_token, MIPV6_COOKIE_LEN); ++ bulentry->rr->home_nonce_index = tm->nonce_index; ++ bulentry->rr->home_time = jiffies; ++ ret = 1; ++ break; ++ ++ case MIPV6_MH_COT: ++ if ((bulentry->rr->rr_state & RR_WAITC) == 0) { ++ DEBUG(DBG_ERROR, "Not waiting for a Home Test message"); ++ goto out; ++ } ++ /* ++ * Make sure no home cookies have been received yet. ++ * TODO: Check not being put in at this time since subsequent ++ * BU's at this time will have careof cookie stored. ++ */ ++ ++ /* Check if the cookie received is the right one */ ++ if (!mipv6_equal_cookies(tm->init_cookie, ++ bulentry->rr->cot_cookie)) { ++ DEBUG(DBG_INFO, "Received CoT cookie does not match stored cookie"); ++ goto out; ++ } ++ bulentry->rr->rr_state &= ~RR_WAITC; ++ memcpy(bulentry->rr->careof_cookie, tm->kgen_token, MIPV6_COOKIE_LEN); ++ bulentry->rr->careof_nonce_index = tm->nonce_index; ++ bulentry->rr->careof_time = jiffies; ++ ret = 1; ++ break; ++ default: ++ /* Impossible to get here */ ++ break; ++ } ++out: ++ if (bulentry->rr->rr_state == RR_DONE) { ++ if (bulentry->rr->kbu) /* First free any old keys */ ++ kfree(bulentry->rr->kbu); ++ /* Store the session key to be used in BU's */ ++ if (ipv6_addr_cmp(&bulentry->coa, &bulentry->home_addr) && bulentry->lifetime) ++ bulentry->rr->kbu = mipv6_rr_key_calc(bulentry->rr->home_cookie, ++ bulentry->rr->careof_cookie); ++ else ++ bulentry->rr->kbu = mipv6_rr_key_calc(bulentry->rr->home_cookie, ++ NULL); ++ /* RR procedure is over, send a BU */ ++ mipv6_send_RR_bu(bulentry); ++ } ++ write_unlock(&bul_lock); ++ return ret; ++} ++ ++/** ++ * mipv6_handle_mh_brr - Binding Refresh Request handler ++ * @home: home address ++ * @coa: care-of address ++ * @cn: source of this packet ++ * @mh: pointer to the beginning of the Mobility Header ++ * ++ * Handles Binding Refresh Request. Packet and offset to option are ++ * passed. Returns 0 on success, otherwise negative. ++ **/ ++static int mipv6_handle_mh_brr(struct sk_buff *skb, ++ struct in6_addr *home, ++ struct in6_addr *unused1, ++ struct in6_addr *cn, ++ struct in6_addr *unused2, ++ struct mipv6_mh *mh) ++{ ++ struct mipv6_mh_brr *brr = (struct mipv6_mh_brr *)mh->data; ++ struct mipv6_bul_entry *binding; ++ int msg_len = (mh->length+1) << 3; ++ int opt_len; ++ ++ if (msg_len > skb->len) ++ return -1; ++ ++ opt_len = msg_len - sizeof(*mh) - sizeof(*brr); ++ ++ if (opt_len < 0) { ++ __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw; ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, pos, skb->dev); ++ ++ DEBUG(DBG_WARNING, "Mobility Header length less than BRR"); ++ MIPV6_INC_STATS(n_brr_drop.invalid); ++ return -1; ++ } ++ ++ /* check we know src, else drop */ ++ write_lock(&bul_lock); ++ if ((binding = mipv6_bul_get(cn, home)) == NULL) { ++ MIPV6_INC_STATS(n_brr_drop.misc); ++ write_unlock(&bul_lock); ++ return MH_UNKNOWN_CN; ++ } ++ ++ MIPV6_INC_STATS(n_brr_rcvd); ++ ++ if (opt_len > 0) { ++ struct mobopt opts; ++ memset(&opts, 0, sizeof(opts)); ++ if (parse_mo_tlv(brr + 1, opt_len, &opts) < 0) { ++ write_unlock(&bul_lock); ++ return -1; ++ } ++ /* ++ * MIPV6_OPT_AUTH_DATA ++ */ ++ } ++ ++ /* must hold bul_lock (write) */ ++ mipv6_RR_start(home, cn, &binding->coa, binding, binding->delay, ++ binding->maxdelay, binding->flags, ++ binding->lifetime, binding->ops); ++ ++ write_unlock(&bul_lock); ++ /* MAY also decide to delete binding and send zero lifetime BU ++ with alt-coa set to home address */ ++ ++ return 0; ++} ++ ++/** ++ * mipv6_handle_mh_ba - Binding Acknowledgement handler ++ * @src: source of this packet ++ * @coa: care-of address ++ * @home: home address ++ * @mh: pointer to the beginning of the Mobility Header ++ * ++ **/ ++static int mipv6_handle_mh_ba(struct sk_buff *skb, ++ struct in6_addr *home, ++ struct in6_addr *coa, ++ struct in6_addr *src, ++ struct in6_addr *unused, ++ struct mipv6_mh *mh) ++{ ++ struct mipv6_mh_ba *ba = (struct mipv6_mh_ba *)mh->data; ++ struct mipv6_bul_entry *binding = NULL; ++ struct mobopt opts; ++ int msg_len = (mh->length+1) << 3; ++ int opt_len; ++ ++ int auth = 1, req_auth = 1, refresh = -1, ifindex = 0; ++ u32 lifetime, sequence; ++ ++ if (msg_len > skb->len) ++ return -1; ++ ++ opt_len = msg_len - sizeof(*mh) - sizeof(*ba); ++ ++ if (opt_len < 0) { ++ __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw; ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, pos, skb->dev); ++ ++ DEBUG(DBG_WARNING, "Mobility Header length less than BA"); ++ MIPV6_INC_STATS(n_ba_drop.invalid); ++ return -1; ++ } ++ ++ lifetime = ntohs(ba->lifetime) << 2; ++ sequence = ntohs(ba->sequence); ++ ++ if (opt_len > 0) { ++ memset(&opts, 0, sizeof(opts)); ++ if (parse_mo_tlv(ba + 1, opt_len, &opts) < 0) ++ return -1; ++ /* ++ * MIPV6_OPT_AUTH_DATA, MIPV6_OPT_BR_ADVICE ++ */ ++ if (opts.br_advice) ++ refresh = ntohs(opts.br_advice->refresh_interval); ++ } ++ ++ if (ba->status >= EXPIRED_HOME_NONCE_INDEX && ++ ba->status <= EXPIRED_NONCES) ++ req_auth = 0; ++ ++ write_lock(&bul_lock); ++ binding = mipv6_bul_get(src, home); ++ if (!binding) { ++ DEBUG(DBG_INFO, "No binding, BA dropped."); ++ write_unlock(&bul_lock); ++ return -1; ++ } ++ ++ if (opts.auth_data && binding->rr && ++ (mipv6_auth_check(src, coa, (__u8 *)mh, msg_len, ++ opts.auth_data, binding->rr->kbu) == 0)) ++ auth = 1; ++ ++ if (req_auth && binding->rr && !auth) { ++ DEBUG(DBG_INFO, "BA Authentication failed."); ++ MIPV6_INC_STATS(n_ba_drop.auth); ++ write_unlock(&bul_lock); ++ return MH_AUTH_FAILED; ++ } ++ ++ if (ba->status == SEQUENCE_NUMBER_OUT_OF_WINDOW) { ++ DEBUG(DBG_INFO, ++ "Sequence number out of window, setting seq to %d", ++ sequence); ++ } else if (binding->seq != sequence) { ++ DEBUG(DBG_INFO, "BU/BA Sequence Number mismatch %d != %d", ++ binding->seq, sequence); ++ MIPV6_INC_STATS(n_ba_drop.invalid); ++ write_unlock(&bul_lock); ++ return MH_SEQUENCE_MISMATCH; ++ } ++ if (ba->status == EXPIRED_HOME_NONCE_INDEX || ba->status == EXPIRED_NONCES) { ++ if (binding->rr) { ++ /* Need to resend home test init to CN */ ++ binding->rr->rr_state |= RR_WAITH; ++ mipv6_send_addr_test_init(&binding->home_addr, ++ &binding->cn_addr, ++ MIPV6_MH_HOTI, ++ binding->rr->hot_cookie); ++ MIPV6_INC_STATS(n_ban_rcvd); ++ } else { ++ DEBUG(DBG_WARNING, "Got BA with status EXPIRED_HOME_NONCE_INDEX" ++ "for non-RR BU"); ++ MIPV6_INC_STATS(n_ba_drop.invalid); ++ } ++ write_unlock(&bul_lock); ++ return 0; ++ } ++ if (ba->status == EXPIRED_CAREOF_NONCE_INDEX || ba->status == EXPIRED_NONCES) { ++ if (binding->rr) { ++ /* Need to resend care-of test init to CN */ ++ binding->rr->rr_state |= RR_WAITC; ++ mipv6_send_addr_test_init(&binding->coa, ++ &binding->cn_addr, ++ MIPV6_MH_COTI, ++ binding->rr->cot_cookie); ++ MIPV6_INC_STATS(n_ban_rcvd); ++ } else { ++ DEBUG(DBG_WARNING, "Got BA with status EXPIRED_HOME_CAREOF_INDEX" ++ "for non-RR BU"); ++ MIPV6_INC_STATS(n_ba_drop.invalid); ++ } ++ write_unlock(&bul_lock); ++ return 0; ++ } ++ write_unlock(&bul_lock); ++ ++ if (ba->status >= REASON_UNSPECIFIED) { ++ DEBUG(DBG_INFO, "Binding Ack status : %d indicates error", ba->status); ++ mipv6_ba_rcvd(ifindex, src, home, sequence, lifetime, ++ refresh, ba->status); ++ MIPV6_INC_STATS(n_ban_rcvd); ++ return 0; ++ } ++ MIPV6_INC_STATS(n_ba_rcvd); ++ if (mipv6_ba_rcvd(ifindex, src, home, ntohs(ba->sequence), lifetime, ++ refresh, ba->status)) { ++ DEBUG(DBG_WARNING, "mipv6_ba_rcvd failed"); ++ } ++ ++ return 0; ++} ++ ++/** ++ * mipv6_handle_mh_be - Binding Error handler ++ * @cn: source of this packet ++ * @coa: care-of address ++ * @home: home address ++ * @mh: pointer to the beginning of the Mobility Header ++ * ++ **/ ++ ++static int mipv6_handle_mh_be(struct sk_buff *skb, ++ struct in6_addr *home, ++ struct in6_addr *coa, ++ struct in6_addr *cn, ++ struct in6_addr *unused, ++ struct mipv6_mh *mh) ++{ ++ struct mipv6_mh_be *be = (struct mipv6_mh_be *)mh->data; ++ int msg_len = (mh->length+1) << 3; ++ int opt_len; ++ struct in6_addr *hoa; ++ struct bul_inval_args args; ++ ++ DEBUG_FUNC(); ++ ++ if (msg_len > skb->len) ++ return -1; ++ ++ opt_len = msg_len - sizeof(*mh) - sizeof(*be); ++ ++ if (opt_len < 0) { ++ __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw; ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, pos, skb->dev); ++ ++ DEBUG(DBG_WARNING, "Mobility Header length less than BE"); ++ MIPV6_INC_STATS(n_be_drop.invalid); ++ return -1; ++ } ++ ++ ++ if (!ipv6_addr_any(&be->home_addr)) ++ hoa = &be->home_addr; ++ else ++ hoa = home; ++ ++ MIPV6_INC_STATS(n_be_rcvd); ++ ++ args.all_rr_states = 0; ++ args.cn = cn; ++ args.mn = hoa; ++ ++ switch (be->status) { ++ case 1: /* Home Address Option used without a binding */ ++ /* Get ULP information about CN-MN communication. If ++ nothing in progress, MUST delete. Otherwise MAY ++ ignore. */ ++ args.all_rr_states = 1; ++ case 2: /* Received unknown MH type */ ++ /* If not expecting ack, SHOULD ignore. If MH ++ extension in use, stop it. If not, stop RO for ++ this CN. */ ++ write_lock(&bul_lock); ++ mipv6_bul_iterate(mn_bul_invalidate, &args); ++ write_unlock(&bul_lock); ++ break; ++ } ++ ++ return 0; ++} ++ ++/* ++ * mipv6_bu_rate_limit() : Takes a bulentry, a COA and 'flags' to check ++ * whether BU being sent is for Home Registration or not. ++ * ++ * If the number of BU's sent is fewer than MAX_FAST_UPDATES, this BU ++ * is allowed to be sent at the MAX_UPDATE_RATE. ++ * If the number of BU's sent is greater than or equal to MAX_FAST_UPDATES, ++ * this BU is allowed to be sent at the SLOW_UPDATE_RATE. ++ * ++ * Assumption : This function is not re-entrant. and the caller holds the ++ * bulentry lock (by calling mipv6_bul_get()) to stop races with other ++ * CPU's executing this same function. ++ * ++ * Side-Effects. Either of the following could on success : ++ * 1. Sets consecutive_sends to 1 if the entry is a Home agent ++ * registration or the COA has changed. ++ * 2. Increments consecutive_sends if the number of BU's sent so ++ * far is less than MAX_FAST_UPDATES, and this BU is being sent ++ * atleast MAX_UPDATE_RATE after previous one. ++ * ++ * Return Value : 0 on Success, -1 on Failure ++ */ ++static int mipv6_bu_rate_limit(struct mipv6_bul_entry *bulentry, ++ struct in6_addr *coa, __u8 flags) ++{ ++ if ((flags & MIPV6_BU_F_HOME) || ipv6_addr_cmp(&bulentry->coa, coa)) { ++ /* Home Agent Registration or different COA - restart from 1 */ ++ bulentry->consecutive_sends = 1; ++ return 0; ++ } ++ ++ if (bulentry->consecutive_sends < MAX_FAST_UPDATES) { ++ /* First MAX_FAST_UPDATES can be sent at MAX_UPDATE_RATE */ ++ if (jiffies - bulentry->lastsend < MAX_UPDATE_RATE * HZ) { ++ return -1; ++ } ++ bulentry->consecutive_sends ++; ++ } else { ++ /* Remaining updates SHOULD be sent at SLOW_UPDATE_RATE */ ++ if (jiffies - bulentry->lastsend < SLOW_UPDATE_RATE * HZ) { ++ return -1; ++ } ++ /* Don't inc 'consecutive_sends' to avoid overflow to zero */ ++ } ++ /* OK to send a BU */ ++ return 0; ++} ++ ++/** ++ * mipv6_send_bu - send a Binding Update ++ * @saddr: source address for BU ++ * @daddr: destination address for BU ++ * @coa: care-of address for MN ++ * @initdelay: initial BA wait timeout ++ * @maxackdelay: maximum BA wait timeout ++ * @exp: exponention back off ++ * @flags: flags for BU ++ * @lifetime: granted lifetime for binding ++ * @ops: mobility options ++ * ++ * Send a binding update. 'flags' may contain any of %MIPV6_BU_F_ACK, ++ * %MIPV6_BU_F_HOME, %MIPV6_BU_F_ROUTER bitwise ORed. If ++ * %MIPV6_BU_F_ACK is included retransmission will be attempted until ++ * the update has been acknowledged. Retransmission is done if no ++ * acknowledgement is received within @initdelay seconds. @exp ++ * specifies whether to use exponential backoff (@exp != 0) or linear ++ * backoff (@exp == 0). For exponential backoff the time to wait for ++ * an acknowledgement is doubled on each retransmission until a delay ++ * of @maxackdelay, after which retransmission is no longer attempted. ++ * For linear backoff the delay is kept constant and @maxackdelay ++ * specifies the maximum number of retransmissions instead. If ++ * sub-options are present ops must contain all sub-options to be ++ * added. On a mobile node, use the mobile node's home address for ++ * @saddr. Returns 0 on success, non-zero on failure. ++ * ++ * Caller may not hold @bul_lock. ++ **/ ++int mipv6_send_bu(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct in6_addr *coa, u32 initdelay, ++ u32 maxackdelay, u8 exp, u8 flags, u32 lifetime, ++ struct mipv6_mh_opt *ops) ++{ ++ int ret; ++ __u8 state; ++ __u16 seq = 0; ++ int (*callback)(struct mipv6_bul_entry *); ++ __u32 callback_time; ++ struct mipv6_bul_entry *bulentry; ++ ++ /* First a sanity check: don't send BU to local addresses */ ++ if(ipv6_chk_addr(daddr, NULL)) { ++ DEBUG(DBG_ERROR, "BUG: Trying to send BU to local address"); ++ return -1; ++ } ++ DEBUG(DBG_INFO, "Sending BU to CN %x:%x:%x:%x:%x:%x:%x:%x " ++ "for home address %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(daddr), NIPV6ADDR(saddr)); ++ ++ if ((bulentry = mipv6_bul_get(daddr, saddr)) != NULL) { ++ if (bulentry->state == ACK_ERROR) { ++ /* ++ * Don't send any more BU's to nodes which don't ++ * understanding one. ++ */ ++ DEBUG(DBG_INFO, "Not sending BU to node which doesn't" ++ " understand one"); ++ return -1; ++ } ++ if (mipv6_bu_rate_limit(bulentry, coa, flags) < 0) { ++ DEBUG(DBG_DATADUMP, "Limiting BU sent."); ++ return 0; ++ } ++ } ++ ++ switch (mipv6_rr_state(bulentry, saddr, coa, flags)) { ++ case INPROGRESS_RR: ++ /* We are already doing RR, don't do BU at this time, it is ++ * done automatically later */ ++ DEBUG(DBG_INFO, "RR in progress not sending BU"); ++ return 0; ++ ++ case DO_RR: ++ /* Just do RR and return, BU is done automatically later */ ++ DEBUG(DBG_INFO, "starting RR" ); ++ mipv6_RR_start(saddr, daddr, coa, bulentry, initdelay, ++ maxackdelay, flags, lifetime, ops); ++ return 0; ++ ++ case NO_RR: ++ DEBUG(DBG_DATADUMP, "No RR necessary" ); ++ default: ++ break; ++ } ++ ++ if (bulentry) ++ seq = bulentry->seq + 1; ++ ++ /* Add to binding update list */ ++ ++ if (flags & MIPV6_BU_F_ACK) { ++ DEBUG(DBG_INFO, "Setting bul callback to bul_resend_exp"); ++ /* Send using exponential backoff */ ++ state = RESEND_EXP; ++ callback = bul_resend_exp; ++ callback_time = initdelay; ++ } else { ++ DEBUG(DBG_INFO, "Setting bul callback to bul_entry_expired"); ++ /* No acknowledgement/resending required */ ++ state = ACK_OK; /* pretend we got an ack */ ++ callback = bul_entry_expired; ++ callback_time = lifetime; ++ } ++ ++ /* BU only for the home address */ ++ /* We must hold bul_lock (write) while calling add */ ++ if ((bulentry = mipv6_bul_add(daddr, saddr, coa, lifetime, seq, ++ flags, callback, callback_time, ++ state, initdelay, maxackdelay, ops, ++ NULL)) == NULL) { ++ DEBUG(DBG_INFO, "couldn't update BUL"); ++ return 0; ++ } ++ ret = send_bu_msg(bulentry); ++ ++ return ret; ++} ++ ++int __init mipv6_mh_mn_init(void) ++{ ++ mipv6_mh_register(MIPV6_MH_HOT, mipv6_handle_mh_HC_test); ++ mipv6_mh_register(MIPV6_MH_COT, mipv6_handle_mh_HC_test); ++ mipv6_mh_register(MIPV6_MH_BA, mipv6_handle_mh_ba); ++ mipv6_mh_register(MIPV6_MH_BRR, mipv6_handle_mh_brr); ++ mipv6_mh_register(MIPV6_MH_BE, mipv6_handle_mh_be); ++ ++ return 0; ++} ++ ++void __exit mipv6_mh_mn_exit(void) ++{ ++ mipv6_mh_unregister(MIPV6_MH_HOT); ++ mipv6_mh_unregister(MIPV6_MH_COT); ++ mipv6_mh_unregister(MIPV6_MH_BA); ++ mipv6_mh_unregister(MIPV6_MH_BRR); ++ mipv6_mh_unregister(MIPV6_MH_BE); ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/module_cn.c linux-2.4.25/net/ipv6/mobile_ip6/module_cn.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/module_cn.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/module_cn.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,167 @@ ++/* ++ * Mobile IPv6 Common Module ++ * ++ * Authors: ++ * Sami Kivisaari ++ * Antti Tuominen ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++ ++#ifdef CONFIG_SYSCTL ++#include ++#endif /* CONFIG_SYSCTL */ ++ ++#include ++ ++#include "bcache.h" ++#include "mipv6_icmp.h" ++#include "stats.h" ++#include "mobhdr.h" ++#include "exthdrs.h" ++ ++int mipv6_debug = 1; ++ ++#if defined(MODULE) && LINUX_VERSION_CODE > 0x20115 ++MODULE_AUTHOR("MIPL Team"); ++MODULE_DESCRIPTION("Mobile IPv6"); ++MODULE_LICENSE("GPL"); ++MODULE_PARM(mipv6_debug, "i"); ++#endif ++ ++#include "config.h" ++ ++struct mip6_func mip6_fn; ++struct mip6_conf mip6node_cnf = { ++ capabilities: CAP_CN, ++ accept_ret_rout: 1, ++ max_rtr_reachable_time: 0, ++ eager_cell_switching: 0, ++ max_num_tunnels: 0, ++ min_num_tunnels: 0, ++ binding_refresh_advice: 0, ++ bu_lladdr: 0, ++ bu_keymgm: 0, ++ bu_cn_ack: 0 ++}; ++ ++#define MIPV6_BCACHE_SIZE 128 ++ ++/********************************************************************** ++ * ++ * MIPv6 CN Module Init / Cleanup ++ * ++ **********************************************************************/ ++ ++#ifdef CONFIG_SYSCTL ++/* Sysctl table */ ++ctl_table mipv6_mobility_table[] = { ++ {NET_IPV6_MOBILITY_DEBUG, "debuglevel", ++ &mipv6_debug, sizeof(int), 0644, NULL, ++ &proc_dointvec}, ++ {NET_IPV6_MOBILITY_RETROUT, "accept_return_routability", ++ &mip6node_cnf.accept_ret_rout, sizeof(int), 0644, NULL, ++ &proc_dointvec}, ++ {0} ++}; ++ctl_table mipv6_table[] = { ++ {NET_IPV6_MOBILITY, "mobility", NULL, 0, 0555, mipv6_mobility_table}, ++ {0} ++}; ++ ++static struct ctl_table_header *mipv6_sysctl_header; ++static struct ctl_table mipv6_net_table[]; ++static struct ctl_table mipv6_root_table[]; ++ ++ctl_table mipv6_net_table[] = { ++ {NET_IPV6, "ipv6", NULL, 0, 0555, mipv6_table}, ++ {0} ++}; ++ ++ctl_table mipv6_root_table[] = { ++ {CTL_NET, "net", NULL, 0, 0555, mipv6_net_table}, ++ {0} ++}; ++#endif /* CONFIG_SYSCTL */ ++ ++extern void mipv6_rr_init(void); ++ ++/* Initialize the module */ ++static int __init mip6_init(void) ++{ ++ int err = 0; ++ ++ printk(KERN_INFO "MIPL Mobile IPv6 for Linux Correspondent Node %s (%s)\n", ++ MIPLVERSION, MIPV6VERSION); ++ ++#ifdef CONFIG_IPV6_MOBILITY_DEBUG ++ printk(KERN_INFO "Debug-level: %d\n", mipv6_debug); ++#endif ++ ++ if ((err = mipv6_bcache_init(MIPV6_BCACHE_SIZE)) < 0) ++ goto bcache_fail; ++ ++ if ((err = mipv6_icmpv6_init()) < 0) ++ goto icmp_fail; ++ ++ if ((err = mipv6_stats_init()) < 0) ++ goto stats_fail; ++ mipv6_rr_init(); ++ ++#ifdef CONFIG_SYSCTL ++ mipv6_sysctl_header = register_sysctl_table(mipv6_root_table, 0); ++#endif ++ ++ if ((err = mipv6_mh_common_init()) < 0) ++ goto mh_fail; ++ ++ MIPV6_SETCALL(mipv6_modify_txoptions, mipv6_modify_txoptions); ++ ++ MIPV6_SETCALL(mipv6_handle_homeaddr, mipv6_handle_homeaddr); ++ MIPV6_SETCALL(mipv6_icmp_swap_addrs, mipv6_icmp_swap_addrs); ++ ++ return 0; ++ ++mh_fail: ++#ifdef CONFIG_SYSCTL ++ unregister_sysctl_table(mipv6_sysctl_header); ++#endif ++ mipv6_stats_exit(); ++stats_fail: ++ mipv6_icmpv6_exit(); ++icmp_fail: ++ mipv6_bcache_exit(); ++bcache_fail: ++ return err; ++} ++module_init(mip6_init); ++ ++#ifdef MODULE ++/* Cleanup module */ ++static void __exit mip6_exit(void) ++{ ++ printk(KERN_INFO "mip6_base.o exiting.\n"); ++#ifdef CONFIG_SYSCTL ++ unregister_sysctl_table(mipv6_sysctl_header); ++#endif ++ ++ /* Invalidate all custom kernel hooks. No need to do this ++ separately for all hooks. */ ++ mipv6_invalidate_calls(); ++ ++ mipv6_mh_common_exit(); ++ mipv6_stats_exit(); ++ mipv6_icmpv6_exit(); ++ mipv6_bcache_exit(); ++} ++module_exit(mip6_exit); ++#endif /* MODULE */ +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/module_ha.c linux-2.4.25/net/ipv6/mobile_ip6/module_ha.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/module_ha.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/module_ha.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,264 @@ ++/* ++ * Mobile IPv6 Home Agent Module ++ * ++ * Authors: ++ * Sami Kivisaari ++ * Antti Tuominen ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++ ++#ifdef CONFIG_SYSCTL ++#include ++#endif /* CONFIG_SYSCTL */ ++ ++#include ++#include ++ ++#include "mobhdr.h" ++#include "tunnel_ha.h" ++#include "ha.h" ++#include "halist.h" ++#include "mipv6_icmp.h" ++//#include "prefix.h" ++#include "bcache.h" ++#include "debug.h" ++ ++int mipv6_use_auth = 0; ++ ++#if defined(MODULE) && LINUX_VERSION_CODE > 0x20115 ++MODULE_AUTHOR("MIPL Team"); ++MODULE_DESCRIPTION("Mobile IPv6 Home Agent"); ++MODULE_LICENSE("GPL"); ++#endif ++ ++#include "config.h" ++ ++#define MIPV6_HALIST_SIZE 128 ++struct ha_info_opt { ++ u8 type; ++ u8 len; ++ u16 res; ++ u16 pref; ++ u16 ltime; ++}; ++/* ++ * Called from ndisc.c's router_discovery. ++ */ ++static int mipv6_ha_ra_rcv(struct sk_buff *skb, struct ndisc_options *ndopts) ++{ ++ unsigned int ha_info_pref = 0, ha_info_lifetime; ++ int ifi = ((struct inet6_skb_parm *)skb->cb)->iif; ++ struct ra_msg *ra = (struct ra_msg *) skb->h.raw; ++ struct in6_addr *saddr = &skb->nh.ipv6h->saddr; ++ struct in6_addr ll_addr; ++ struct hal { ++ struct in6_addr prefix; ++ int plen; ++ struct hal *next; ++ }; ++ ++ DEBUG_FUNC(); ++ ++ ha_info_lifetime = ntohs(ra->icmph.icmp6_rt_lifetime); ++ ipv6_addr_copy(&ll_addr, saddr); ++ ++ if (ndopts->nd_opts_hai) { ++ struct ha_info_opt *hai = (struct ha_info_opt *)ndopts->nd_opts_hai; ++ ha_info_pref = ntohs(hai->pref); ++ ha_info_lifetime = ntohs(hai->ltime); ++ DEBUG(DBG_DATADUMP, ++ "received home agent info with preference : %d and lifetime : %d", ++ ha_info_pref, ha_info_lifetime); ++ } ++ if (ndopts->nd_opts_pi) { ++ struct nd_opt_hdr *p; ++ for (p = ndopts->nd_opts_pi; ++ p; ++ p = ndisc_next_option(p, ndopts->nd_opts_pi_end)) { ++ struct prefix_info *pinfo; ++ ++ pinfo = (struct prefix_info *) p; ++ ++ if (pinfo->router_address) { ++ DEBUG(DBG_DATADUMP, "Adding router address to " ++ "ha queue \n"); ++ /* If RA has H bit set and Prefix Info ++ * Option R bit set, queue this ++ * address to be added to Home Agents ++ * List. ++ */ ++ if (ipv6_addr_type(&pinfo->prefix) & ++ IPV6_ADDR_LINKLOCAL) ++ continue; ++ if (!ra->icmph.icmp6_home_agent || !ha_info_lifetime) { ++ mipv6_halist_delete(&pinfo->prefix); ++ continue; ++ } else { ++ ++ mipv6_halist_add(ifi, &pinfo->prefix, ++ pinfo->prefix_len, &ll_addr, ++ ha_info_pref, ha_info_lifetime); ++ } ++ ++ } ++ ++ } ++ } ++ return MIPV6_ADD_RTR; ++} ++ ++/********************************************************************** ++ * ++ * MIPv6 Module Init / Cleanup ++ * ++ **********************************************************************/ ++ ++#ifdef CONFIG_SYSCTL ++/* Sysctl table */ ++extern int ++mipv6_max_tnls_sysctl(ctl_table *, int, struct file *, void *, size_t *); ++ ++extern int ++mipv6_min_tnls_sysctl(ctl_table *, int, struct file *, void *, size_t *); ++ ++int max_adv = ~(u16)0; ++int min_zero = 0; ++ctl_table mipv6_mobility_table[] = { ++ {NET_IPV6_MOBILITY_BINDING_REFRESH, "binding_refresh_advice", ++ &mip6node_cnf.binding_refresh_advice, sizeof(int), 0644, NULL, ++ &proc_dointvec_minmax, &sysctl_intvec, 0, &min_zero, &max_adv}, ++ ++ {NET_IPV6_MOBILITY_MAX_TNLS, "max_tnls", &mipv6_max_tnls, sizeof(int), ++ 0644, NULL, &mipv6_max_tnls_sysctl}, ++ {NET_IPV6_MOBILITY_MIN_TNLS, "min_tnls", &mipv6_min_tnls, sizeof(int), ++ 0644, NULL, &mipv6_min_tnls_sysctl}, ++ {0} ++}; ++ctl_table mipv6_table[] = { ++ {NET_IPV6_MOBILITY, "mobility", NULL, 0, 0555, mipv6_mobility_table}, ++ {0} ++}; ++ ++static struct ctl_table_header *mipv6_sysctl_header; ++static struct ctl_table mipv6_net_table[]; ++static struct ctl_table mipv6_root_table[]; ++ ++ctl_table mipv6_net_table[] = { ++ {NET_IPV6, "ipv6", NULL, 0, 0555, mipv6_table}, ++ {0} ++}; ++ ++ctl_table mipv6_root_table[] = { ++ {CTL_NET, "net", NULL, 0, 0555, mipv6_net_table}, ++ {0} ++}; ++#endif /* CONFIG_SYSCTL */ ++ ++extern void mipv6_check_dad(struct in6_addr *haddr); ++extern void mipv6_dad_init(void); ++extern void mipv6_dad_exit(void); ++extern int mipv6_forward(struct sk_buff *); ++ ++/* Initialize the module */ ++static int __init mip6_ha_init(void) ++{ ++ int err = 0; ++ ++ printk(KERN_INFO "MIPL Mobile IPv6 for Linux Home Agent %s (%s)\n", ++ MIPLVERSION, MIPV6VERSION); ++ mip6node_cnf.capabilities = CAP_CN | CAP_HA; ++ ++ mip6_fn.icmpv6_dhaad_rep_rcv = mipv6_icmpv6_no_rcv; ++ mip6_fn.icmpv6_dhaad_req_rcv = mipv6_icmpv6_rcv_dhaad_req; ++ mip6_fn.icmpv6_pfxadv_rcv = mipv6_icmpv6_no_rcv; ++ mip6_fn.icmpv6_pfxsol_rcv = mipv6_icmpv6_no_rcv; ++ mip6_fn.icmpv6_paramprob_rcv = mipv6_icmpv6_no_rcv; ++ ++#ifdef CONFIG_IPV6_MOBILITY_DEBUG ++ printk(KERN_INFO "Debug-level: %d\n", mipv6_debug); ++#endif ++ ++#ifdef CONFIG_SYSCTL ++ mipv6_sysctl_header = register_sysctl_table(mipv6_root_table, 0); ++#endif ++ mipv6_initialize_tunnel(); ++ ++ if ((err = mipv6_ha_init()) < 0) ++ goto ha_fail; ++ ++ MIPV6_SETCALL(mipv6_ra_rcv, mipv6_ha_ra_rcv); ++ MIPV6_SETCALL(mipv6_forward, mipv6_forward); ++ mipv6_dad_init(); ++ MIPV6_SETCALL(mipv6_check_dad, mipv6_check_dad); ++ ++ if ((err = mipv6_halist_init(MIPV6_HALIST_SIZE)) < 0) ++ goto halist_fail; ++ ++// mipv6_initialize_pfx_icmpv6(); ++ ++ return 0; ++ ++halist_fail: ++ mipv6_dad_exit(); ++ mipv6_ha_exit(); ++ha_fail: ++ mipv6_shutdown_tunnel(); ++ ++ mip6_fn.icmpv6_dhaad_rep_rcv = NULL; ++ mip6_fn.icmpv6_dhaad_req_rcv = NULL; ++ mip6_fn.icmpv6_pfxadv_rcv = NULL; ++ mip6_fn.icmpv6_pfxsol_rcv = NULL; ++ mip6_fn.icmpv6_paramprob_rcv = NULL; ++ ++ MIPV6_RESETCALL(mipv6_ra_rcv); ++ MIPV6_RESETCALL(mipv6_forward); ++ MIPV6_RESETCALL(mipv6_check_dad); ++ ++#ifdef CONFIG_SYSCTL ++ unregister_sysctl_table(mipv6_sysctl_header); ++#endif ++ return err; ++} ++module_init(mip6_ha_init); ++ ++#ifdef MODULE ++/* Cleanup module */ ++static void __exit mip6_ha_exit(void) ++{ ++ printk(KERN_INFO "mip6_ha.o exiting.\n"); ++ mip6node_cnf.capabilities &= ~(int)CAP_HA; ++ ++ mipv6_bcache_cleanup(HOME_REGISTRATION); ++ ++ MIPV6_RESETCALL(mipv6_ra_rcv); ++ MIPV6_RESETCALL(mipv6_forward); ++ MIPV6_RESETCALL(mipv6_check_dad); ++ ++ mipv6_halist_exit(); ++// mipv6_shutdown_pfx_icmpv6(); ++ ++ mip6_fn.icmpv6_dhaad_rep_rcv = NULL; ++ mip6_fn.icmpv6_dhaad_req_rcv = NULL; ++ mip6_fn.icmpv6_pfxadv_rcv = NULL; ++ mip6_fn.icmpv6_pfxsol_rcv = NULL; ++ mip6_fn.icmpv6_paramprob_rcv = NULL; ++ ++ mipv6_dad_exit(); ++ mipv6_ha_exit(); ++ mipv6_shutdown_tunnel(); ++#ifdef CONFIG_SYSCTL ++ unregister_sysctl_table(mipv6_sysctl_header); ++#endif ++} ++module_exit(mip6_ha_exit); ++#endif /* MODULE */ +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/module_mn.c linux-2.4.25/net/ipv6/mobile_ip6/module_mn.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/module_mn.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/module_mn.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,188 @@ ++/* ++ * Mobile IPv6 Mobile Node Module ++ * ++ * Authors: ++ * Sami Kivisaari ++ * Antti Tuominen ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++ ++#ifdef CONFIG_SYSCTL ++#include ++#endif /* CONFIG_SYSCTL */ ++ ++#include ++ ++extern int mipv6_debug; ++int mipv6_use_auth = 0; ++ ++#if defined(MODULE) && LINUX_VERSION_CODE > 0x20115 ++MODULE_AUTHOR("MIPL Team"); ++MODULE_DESCRIPTION("Mobile IPv6 Mobile Node"); ++MODULE_LICENSE("GPL"); ++MODULE_PARM(mipv6_debug, "i"); ++#endif ++ ++#include "config.h" ++ ++#include "mobhdr.h" ++#include "mn.h" ++#include "mipv6_icmp.h" ++//#include "prefix.h" ++ ++/* TODO: These will go as soon as we get rid of the last two ioctls */ ++extern int mipv6_ioctl_mn_init(void); ++extern void mipv6_ioctl_mn_exit(void); ++ ++/********************************************************************** ++ * ++ * MIPv6 Module Init / Cleanup ++ * ++ **********************************************************************/ ++ ++#ifdef CONFIG_SYSCTL ++/* Sysctl table */ ++ ++extern int max_rtr_reach_time; ++extern int eager_cell_switching; ++ ++static int max_reach = 1000; ++static int min_reach = 1; ++static int max_one = 1; ++static int min_zero = 0; ++ ++extern int ++mipv6_mdetect_mech_sysctl(ctl_table *, int, struct file *, void *, size_t *); ++ ++extern int ++mipv6_router_reach_sysctl(ctl_table *, int, struct file *, void *, size_t *); ++ ++ctl_table mipv6_mobility_table[] = { ++ {NET_IPV6_MOBILITY_BU_F_LLADDR, "bu_flag_lladdr", ++ &mip6node_cnf.bu_lladdr, sizeof(int), 0644, NULL, ++ &proc_dointvec_minmax, &sysctl_intvec, 0, &min_zero, &max_one}, ++ {NET_IPV6_MOBILITY_BU_F_KEYMGM, "bu_flag_keymgm", ++ &mip6node_cnf.bu_keymgm, sizeof(int), 0644, NULL, ++ &proc_dointvec_minmax, &sysctl_intvec, 0, &min_zero, &max_one}, ++ {NET_IPV6_MOBILITY_BU_F_CN_ACK, "bu_flag_cn_ack", ++ &mip6node_cnf.bu_cn_ack, sizeof(int), 0644, NULL, ++ &proc_dointvec_minmax, &sysctl_intvec, 0, &min_zero, &max_one}, ++ ++ {NET_IPV6_MOBILITY_ROUTER_REACH, "max_router_reachable_time", ++ &max_rtr_reach_time, sizeof(int), 0644, NULL, ++ &proc_dointvec_minmax, &sysctl_intvec, 0, &min_reach, &max_reach}, ++ ++ {NET_IPV6_MOBILITY_MDETECT_MECHANISM, "eager_cell_switching", ++ &eager_cell_switching, sizeof(int), 0644, NULL, ++ &proc_dointvec_minmax, &sysctl_intvec, 0, &min_zero, &max_one}, ++ ++ {0} ++}; ++ctl_table mipv6_table[] = { ++ {NET_IPV6_MOBILITY, "mobility", NULL, 0, 0555, mipv6_mobility_table}, ++ {0} ++}; ++ ++static struct ctl_table_header *mipv6_sysctl_header; ++static struct ctl_table mipv6_net_table[]; ++static struct ctl_table mipv6_root_table[]; ++ ++ctl_table mipv6_net_table[] = { ++ {NET_IPV6, "ipv6", NULL, 0, 0555, mipv6_table}, ++ {0} ++}; ++ ++ctl_table mipv6_root_table[] = { ++ {CTL_NET, "net", NULL, 0, 0555, mipv6_net_table}, ++ {0} ++}; ++#endif /* CONFIG_SYSCTL */ ++ ++/* Initialize the module */ ++static int __init mip6_mn_init(void) ++{ ++ int err = 0; ++ ++ printk(KERN_INFO "MIPL Mobile IPv6 for Linux Mobile Node %s (%s)\n", ++ MIPLVERSION, MIPV6VERSION); ++ mip6node_cnf.capabilities = CAP_CN | CAP_MN; ++ ++#ifdef CONFIG_IPV6_MOBILITY_DEBUG ++ printk(KERN_INFO "Debug-level: %d\n", mipv6_debug); ++#endif ++ ++#ifdef CONFIG_SYSCTL ++ mipv6_sysctl_header = register_sysctl_table(mipv6_root_table, 0); ++#endif ++ if ((err = mipv6_mn_init()) < 0) ++ goto mn_fail; ++ ++ mipv6_mh_mn_init(); ++ ++ mip6_fn.icmpv6_dhaad_rep_rcv = mipv6_icmpv6_rcv_dhaad_rep; ++ mip6_fn.icmpv6_dhaad_req_rcv = mipv6_icmpv6_no_rcv; ++ mip6_fn.icmpv6_pfxadv_rcv = mipv6_icmpv6_no_rcv; ++ mip6_fn.icmpv6_pfxsol_rcv = mipv6_icmpv6_no_rcv; ++ mip6_fn.icmpv6_paramprob_rcv = mipv6_icmpv6_rcv_paramprob; ++ ++// mipv6_initialize_pfx_icmpv6(); ++ ++ if ((err = mipv6_ioctl_mn_init()) < 0) ++ goto ioctl_fail; ++ ++ return 0; ++ ++ioctl_fail: ++// mipv6_shutdown_pfx_icmpv6(); ++ ++ mip6_fn.icmpv6_dhaad_rep_rcv = NULL; ++ mip6_fn.icmpv6_dhaad_req_rcv = NULL; ++ mip6_fn.icmpv6_pfxadv_rcv = NULL; ++ mip6_fn.icmpv6_pfxsol_rcv = NULL; ++ mip6_fn.icmpv6_paramprob_rcv = NULL; ++ ++ mipv6_mh_mn_exit(); ++ mipv6_mn_exit(); ++mn_fail: ++#ifdef CONFIG_SYSCTL ++ unregister_sysctl_table(mipv6_sysctl_header); ++#endif ++ return err; ++} ++module_init(mip6_mn_init); ++ ++#ifdef MODULE ++/* Cleanup module */ ++static void __exit mip6_mn_exit(void) ++{ ++ printk(KERN_INFO "mip6_mn.o exiting.\n"); ++ mip6node_cnf.capabilities &= ~(int)CAP_MN; ++ ++ mipv6_ioctl_mn_exit(); ++// mipv6_shutdown_pfx_icmpv6(); ++ ++ mip6_fn.icmpv6_dhaad_rep_rcv = NULL; ++ mip6_fn.icmpv6_dhaad_req_rcv = NULL; ++ mip6_fn.icmpv6_pfxadv_rcv = NULL; ++ mip6_fn.icmpv6_pfxsol_rcv = NULL; ++ mip6_fn.icmpv6_paramprob_rcv = NULL; ++ ++ mipv6_mn_exit(); ++ ++/* common cleanup */ ++#ifdef CONFIG_SYSCTL ++ unregister_sysctl_table(mipv6_sysctl_header); ++#endif ++} ++module_exit(mip6_mn_exit); ++#endif /* MODULE */ +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/multiaccess_ctl.c linux-2.4.25/net/ipv6/mobile_ip6/multiaccess_ctl.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/multiaccess_ctl.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/multiaccess_ctl.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,287 @@ ++/* ++ * 2001 (c) Oy L M Ericsson Ab ++ * ++ * Author: NomadicLab / Ericsson Research ++ * ++ * $Id$ ++ * ++ */ ++ ++/* ++ * Vertical hand-off information manager ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "multiaccess_ctl.h" ++#include "debug.h" ++ ++/* ++ * Local variables ++ */ ++static LIST_HEAD(if_list); ++ ++/* Internal interface information list */ ++struct ma_if_info { ++ struct list_head list; ++ int interface_id; ++ int preference; ++ __u8 status; ++}; ++ ++/** ++ * ma_ctl_get_preference - get preference value for interface ++ * @ifi: interface index ++ * ++ * Returns integer value preference for given interface. ++ **/ ++int ma_ctl_get_preference(int ifi) ++{ ++ struct list_head *lh; ++ struct ma_if_info *info; ++ int pref = 0; ++ ++ list_for_each(lh, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ if (info->interface_id == ifi) { ++ pref = info->preference; ++ return pref; ++ } ++ } ++ return -1; ++} ++/** ++ * ma_ctl_get_preference - get preference value for interface ++ * @ifi: interface index ++ * ++ * Returns integer value interface index for interface with highest preference. ++ **/ ++int ma_ctl_get_preferred_if(void) ++{ ++ struct list_head *lh; ++ struct ma_if_info *info, *pref_if = NULL; ++ ++ list_for_each(lh, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ if (!pref_if || (info->preference > pref_if->preference)) { ++ pref_if = info; ++ } ++ } ++ if (pref_if) return pref_if->interface_id; ++ return 0; ++} ++/** ++ * ma_ctl_set_preference - set preference for interface ++ * @arg: ioctl args ++ * ++ * Sets preference of an existing interface (called by ioctl). ++ **/ ++void ma_ctl_set_preference(unsigned long arg) ++{ ++ struct list_head *lh; ++ struct ma_if_info *info; ++ struct ma_if_uinfo uinfo; ++ ++ memset(&uinfo, 0, sizeof(struct ma_if_uinfo)); ++ if (copy_from_user(&uinfo, (struct ma_if_uinfo *)arg, ++ sizeof(struct ma_if_uinfo)) < 0) { ++ DEBUG(DBG_WARNING, "copy_from_user failed"); ++ return; ++ } ++ ++ /* check if the interface exists */ ++ list_for_each(lh, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ if (info->interface_id == uinfo.interface_id) { ++ info->preference = uinfo.preference; ++ return; ++ } ++ } ++} ++ ++/** ++ * ma_ctl_add_iface - add new interface to list ++ * @if_index: interface index ++ * ++ * Adds new interface entry to preference list. Preference is set to ++ * the same value as @if_index. Entry @status is set to ++ * %MA_IFACE_NOT_USED. ++ **/ ++void ma_ctl_add_iface(int if_index) ++{ ++ struct list_head *lh; ++ struct ma_if_info *info; ++ ++ DEBUG_FUNC(); ++ ++ /* check if the interface already exists */ ++ list_for_each(lh, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ if (info->interface_id == if_index) { ++ info->status = MA_IFACE_NOT_USED; ++ info->preference = if_index; ++ return; ++ } ++ } ++ ++ info = kmalloc(sizeof(struct ma_if_info), GFP_ATOMIC); ++ if (info == NULL) { ++ DEBUG(DBG_ERROR, "Out of memory"); ++ return; ++ } ++ memset(info, 0, sizeof(struct ma_if_info)); ++ info->interface_id = if_index; ++ info->preference = if_index; ++ info->status = MA_IFACE_NOT_USED; ++ list_add(&info->list, &if_list); ++} ++ ++/** ++ * ma_ctl_del_iface - remove entry from the list ++ * @if_index: interface index ++ * ++ * Removes entry for interface @if_index from preference list. ++ **/ ++int ma_ctl_del_iface(int if_index) ++{ ++ struct list_head *lh, *next; ++ struct ma_if_info *info; ++ ++ DEBUG_FUNC(); ++ ++ /* if the iface exists, change availability to 0 */ ++ list_for_each_safe(lh, next, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ if (info->interface_id == if_index) { ++ list_del(&info->list); ++ kfree(info); ++ return 0; ++ } ++ } ++ ++ return -1; ++} ++ ++/** ++ * ma_ctl_upd_iface - update entry (and list) ++ * @if_index: interface to update ++ * @status: new status for interface ++ * @change_if_index: new interface ++ * ++ * Updates @if_index entry on preference list. Entry status is set to ++ * @status. If new @status is %MA_IFACE_CURRENT, updates list to have ++ * only one current device. If @status is %MA_IFACE_NOT_PRESENT, ++ * entry is deleted and further if entry had %MA_IFACE_CURRENT set, ++ * new current device is looked up and returned in @change_if_index. ++ * New preferred interface is also returned if current device changes ++ * to %MA_IFACE_NOT_USED. Returns 0 on success, otherwise negative. ++ **/ ++int ma_ctl_upd_iface(int if_index, int status, int *change_if_index) ++{ ++ struct list_head *lh, *tmp; ++ struct ma_if_info *info, *pref = NULL; ++ int found = 0; ++ ++ DEBUG_FUNC(); ++ ++ *change_if_index = 0; ++ ++ /* check if the interface exists */ ++ list_for_each_safe(lh, tmp, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ if (status == MA_IFACE_NOT_PRESENT) { ++ if (info->interface_id == if_index) { ++ list_del_init(&info->list); ++ kfree(info); ++ found = 1; ++ break; ++ } ++ } else if (status == MA_IFACE_CURRENT) { ++ if (info->interface_id == if_index) { ++ info->status |= MA_IFACE_CURRENT; ++ found = 1; ++ } else { ++ info->status |= MA_IFACE_NOT_USED; ++ } ++ } else if (status == MA_IFACE_NOT_USED) { ++ if (info->interface_id == if_index) { ++ if (info->status | MA_IFACE_CURRENT) { ++ found = 1; ++ } ++ info->status &= !MA_IFACE_CURRENT; ++ info->status |= MA_IFACE_NOT_USED; ++ info->status &= !MA_IFACE_HAS_ROUTER; ++ } ++ break; ++ } else if (status == MA_IFACE_HAS_ROUTER) { ++ if (info->interface_id == if_index) { ++ info->status |= MA_IFACE_HAS_ROUTER; ++ } ++ return 0; ++ } ++ } ++ ++ if (status & (MA_IFACE_NOT_USED|MA_IFACE_NOT_PRESENT) && found) { ++ /* select new interface */ ++ list_for_each(lh, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ if (pref == NULL || ((info->preference > pref->preference) && ++ info->status & MA_IFACE_HAS_ROUTER)) ++ pref = info; ++ } ++ if (pref) { ++ *change_if_index = pref->interface_id; ++ pref->status |= MA_IFACE_CURRENT; ++ } else { ++ *change_if_index = -1; ++ } ++ return 0; ++ } ++ ++ if (found) return 0; ++ ++ return -1; ++} ++ ++static int if_proc_info(char *buffer, char **start, off_t offset, ++ int length) ++{ ++ struct list_head *lh; ++ struct ma_if_info *info; ++ int len = 0; ++ ++ list_for_each(lh, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ len += sprintf(buffer + len, "%02d %010d %1d %1d\n", ++ info->interface_id, info->preference, ++ !!(info->status & MA_IFACE_HAS_ROUTER), ++ !!(info->status & MA_IFACE_CURRENT)); ++ } ++ ++ *start = buffer + offset; ++ ++ len -= offset; ++ ++ if (len > length) len = length; ++ ++ return len; ++ ++} ++ ++void ma_ctl_init(void) ++{ ++ proc_net_create("mip6_iface", 0, if_proc_info); ++} ++ ++void ma_ctl_clean(void) ++{ ++ proc_net_remove("mip6_iface"); ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/multiaccess_ctl.h linux-2.4.25/net/ipv6/mobile_ip6/multiaccess_ctl.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/multiaccess_ctl.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/multiaccess_ctl.h 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,77 @@ ++/* ++ * 2001 (c) Oy L M Ericsson Ab ++ * ++ * Author: NomadicLab / Ericsson Research ++ * ++ * $Id$ ++ * ++ */ ++ ++#ifndef _MULTIACCESS_CTL_H ++#define _MULTIACCESS_CTL_H ++ ++/* status */ ++#define MA_IFACE_NOT_PRESENT 0x01 ++#define MA_IFACE_NOT_USED 0x02 ++#define MA_IFACE_HAS_ROUTER 0x04 ++#define MA_IFACE_CURRENT 0x10 ++ ++struct ma_if_uinfo { ++ int interface_id; ++ int preference; ++ __u8 status; ++}; ++/* ++ * @ma_ctl_get_preferred_id: returns most preferred interface id ++ */ ++int ma_ctl_get_preferred_if(void); ++ ++/* @ma_ctl_get_preference: returns preference for an interface ++ * @name: name of the interface (dev->name) ++ */ ++int ma_ctl_get_preference(int ifi); ++ ++/* ++ * Public function: ma_ctl_set_preference ++ * Description: Set preference of an existing interface (called by ioctl) ++ * Returns: ++ */ ++void ma_ctl_set_preference(unsigned long); ++ ++/* ++ * Public function: ma_ctl_add_iface ++ * Description: Inform control module to insert a new interface ++ * Returns: 0 if success, any other number means an error ++ */ ++void ma_ctl_add_iface(int); ++ ++/* ++ * Public function: ma_ctl_del_iface ++ * Description: Inform control module to remove an obsolete interface ++ * Returns: 0 if success, any other number means an error ++ */ ++int ma_ctl_del_iface(int); ++ ++/* ++ * Public function: ma_ctl_upd_iface ++ * Description: Inform control module of status change. ++ * Returns: 0 if success, any other number means an error ++ */ ++int ma_ctl_upd_iface(int, int, int *); ++ ++/* ++ * Public function: ma_ctl_init ++ * Description: XXX ++ * Returns: XXX ++ */ ++void ma_ctl_init(void); ++ ++/* ++ * Public function: ma_ctl_clean ++ * Description: XXX ++ * Returns: - ++ */ ++void ma_ctl_clean(void); ++ ++ ++#endif +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/ndisc_ha.c linux-2.4.25/net/ipv6/mobile_ip6/ndisc_ha.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/ndisc_ha.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/ndisc_ha.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,596 @@ ++/* ++ * Mobile IPv6 Duplicate Address Detection Functions ++ * ++ * Authors: ++ * Krishna Kumar ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "debug.h" ++#include "bcache.h" ++#include "ha.h" /* mipv6_generate_ll_addr */ ++ ++/* ++ * Binding Updates from MN are cached in this structure till DAD is performed. ++ * This structure is used to retrieve a pending Binding Update for the HA to ++ * reply to after performing DAD. The first cell is different from the rest as ++ * follows : ++ * 1. The first cell is used to chain the remaining cells. ++ * 2. The timeout of the first cell is used to delete expired entries ++ * in the list of cells, while the timeout of the other cells are ++ * used for timing out a NS request so as to reply to a BU. ++ * 3. The only elements of the first cell that are used are : ++ * next, prev, and callback_timer. ++ * ++ * TODO : Don't we need to do pneigh_lookup on the Link Local address ? ++ */ ++struct mipv6_dad_cell { ++ /* Information needed for DAD management */ ++ struct mipv6_dad_cell *next; /* Next element on the DAD list */ ++ struct mipv6_dad_cell *prev; /* Prev element on the DAD list */ ++ __u16 probes; /* Number of times to probe for addr */ ++ __u16 flags; /* Entry flags - see below */ ++ struct timer_list callback_timer; /* timeout for entry */ ++ ++ /* Information needed for performing DAD */ ++ struct inet6_ifaddr *ifp; ++ int ifindex; ++ struct in6_addr daddr; ++ struct in6_addr haddr; /* home address */ ++ struct in6_addr ll_haddr; /* Link Local value of haddr */ ++ struct in6_addr coa; ++ struct in6_addr rep_coa; ++ __u32 ba_lifetime; ++ __u16 sequence; ++ __u8 bu_flags; ++}; ++ ++/* Values for the 'flags' field in the mipv6_dad_cell */ ++#define DAD_INIT_ENTRY 0 ++#define DAD_DUPLICATE_ADDRESS 1 ++#define DAD_UNIQUE_ADDRESS 2 ++ ++/* Head of the pending DAD list */ ++static struct mipv6_dad_cell dad_cell_head; ++ ++/* Lock to access the pending DAD list */ ++static rwlock_t dad_lock = RW_LOCK_UNLOCKED; ++ ++/* Timer routine which deletes 'expired' entries in the DAD list */ ++static void mipv6_dad_delete_old_entries(unsigned long unused) ++{ ++ struct mipv6_dad_cell *curr, *next; ++ unsigned long next_time = 0; ++ ++ write_lock(&dad_lock); ++ curr = dad_cell_head.next; ++ while (curr != &dad_cell_head) { ++ next = curr->next; ++ if (curr->flags != DAD_INIT_ENTRY) { ++ if (curr->callback_timer.expires <= jiffies) { ++ /* Entry has expired, free it up. */ ++ curr->next->prev = curr->prev; ++ curr->prev->next = curr->next; ++ in6_ifa_put(curr->ifp); ++ kfree(curr); ++ } else if (next_time < ++ curr->callback_timer.expires) { ++ next_time = curr->callback_timer.expires; ++ } ++ } ++ curr = next; ++ } ++ write_unlock(&dad_lock); ++ if (next_time) { ++ /* ++ * Start another timer if more cells need to be removed at ++ * a later stage. ++ */ ++ dad_cell_head.callback_timer.expires = next_time; ++ add_timer(&dad_cell_head.callback_timer); ++ } ++} ++ ++/* ++ * Queue a timeout routine to clean up 'expired' DAD entries. ++ */ ++static void mipv6_start_dad_head_timer(struct mipv6_dad_cell *cell) ++{ ++ unsigned long expire = jiffies + ++ cell->ifp->idev->nd_parms->retrans_time * 10; ++ ++ if (!timer_pending(&dad_cell_head.callback_timer) || ++ expire < dad_cell_head.callback_timer.expires) { ++ /* ++ * Add timer if none pending, or mod the timer if new ++ * cell needs to be expired before existing timer runs. ++ * ++ * We let the cell remain as long as possible, so that ++ * new BU's as part of retransmissions don't have to go ++ * through DAD before replying. ++ */ ++ dad_cell_head.callback_timer.expires = expire; ++ ++ /* ++ * Keep the cell around for atleast some time to handle ++ * retransmissions or BU's due to fast MN movement. This ++ * is needed otherwise a previous timeout can delete all ++ * expired entries including this new one. ++ */ ++ cell->callback_timer.expires = jiffies + ++ cell->ifp->idev->nd_parms->retrans_time * 5; ++ if (!timer_pending(&dad_cell_head.callback_timer)) { ++ add_timer(&dad_cell_head.callback_timer); ++ } else { ++ mod_timer(&dad_cell_head.callback_timer, expire); ++ } ++ } ++} ++ ++ ++/* Join solicited node MC address */ ++static inline void mipv6_join_sol_mc_addr(struct in6_addr *addr, ++ struct net_device *dev) ++{ ++ struct in6_addr maddr; ++ ++ /* Join solicited node MC address */ ++ addrconf_addr_solict_mult(addr, &maddr); ++ ipv6_dev_mc_inc(dev, &maddr); ++} ++ ++/* Leave solicited node MC address */ ++static inline void mipv6_leave_sol_mc_addr(struct in6_addr *addr, ++ struct net_device *dev) ++{ ++ struct in6_addr maddr; ++ ++ addrconf_addr_solict_mult(addr, &maddr); ++ ipv6_dev_mc_dec(dev, &maddr); ++} ++ ++/* Send a NS */ ++static inline void mipv6_dad_send_ns(struct inet6_ifaddr *ifp, ++ struct in6_addr *haddr) ++{ ++ struct in6_addr unspec; ++ struct in6_addr mcaddr; ++ ++ ipv6_addr_set(&unspec, 0, 0, 0, 0); ++ addrconf_addr_solict_mult(haddr, &mcaddr); ++ ++ /* addr is 'unspec' since we treat this address as transient */ ++ ndisc_send_ns(ifp->idev->dev, NULL, haddr, &mcaddr, &unspec); ++} ++ ++/* ++ * Search for a home address in the list of pending DAD's. Called from ++ * Neighbor Advertisement ++ * Return values : ++ * -1 : No DAD entry found for this advertisement, or entry already ++ * finished processing. ++ * 0 : Entry found waiting for DAD to finish. ++ */ ++static int dad_search_haddr(struct in6_addr *ll_haddr, ++ struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u16 * seq, struct inet6_ifaddr **ifp) ++{ ++ struct mipv6_dad_cell *cell; ++ ++ read_lock(&dad_lock); ++ cell = dad_cell_head.next; ++ while (cell != &dad_cell_head && ++ ipv6_addr_cmp(&cell->ll_haddr, ll_haddr) && ++ ipv6_addr_cmp(&cell->haddr, ll_haddr)) { ++ cell = cell->next; ++ } ++ if (cell == &dad_cell_head || cell->flags != DAD_INIT_ENTRY) { ++ /* Not found element, or element already finished processing */ ++ if (cell != &dad_cell_head) { ++ /* ++ * Set the state to DUPLICATE, even if it was UNIQUE ++ * earlier. It is not needed to setup timer via ++ * mipv6_start_dad_head_timer since this must have ++ * already been done. ++ */ ++ cell->flags = DAD_DUPLICATE_ADDRESS; ++ } ++ read_unlock(&dad_lock); ++ return -1; ++ } ++ ++ /* ++ * The NA found an unprocessed entry in the DAD list. Expire this ++ * entry since another node advertised this address. Caller should ++ * reject BU (DAD failed). ++ */ ++ ipv6_addr_copy(daddr, &cell->daddr); ++ ipv6_addr_copy(haddr, &cell->haddr); ++ ipv6_addr_copy(coa, &cell->coa); ++ ipv6_addr_copy(rep_coa, &cell->rep_coa); ++ *seq = cell->sequence; ++ *ifp = cell->ifp; ++ ++ if (del_timer(&cell->callback_timer) == 0) { ++ /* Timer already deleted, race with Timeout Handler */ ++ /* No action needed */ ++ } ++ ++ cell->flags = DAD_DUPLICATE_ADDRESS; ++ ++ /* Now leave this address to avoid future processing of NA's */ ++ mipv6_leave_sol_mc_addr(&cell->ll_haddr, cell->ifp->idev->dev); ++ /* Leave also global address, if link local address was in use */ ++ if (ipv6_addr_cmp(&cell->ll_haddr, &cell->haddr)) ++ mipv6_leave_sol_mc_addr(&cell->haddr, cell->ifp->idev->dev); ++ /* Start dad_head timer to remove this entry */ ++ mipv6_start_dad_head_timer(cell); ++ ++ read_unlock(&dad_lock); ++ ++ return 0; ++} ++ ++/* ENTRY routine called via Neighbor Advertisement */ ++void mipv6_check_dad(struct in6_addr *ll_haddr) ++{ ++ struct in6_addr daddr, haddr, coa, rep_coa; ++ struct inet6_ifaddr *ifp; ++ __u16 seq; ++ ++ if (dad_search_haddr(ll_haddr, &daddr, &haddr, &coa, &rep_coa, &seq, ++ &ifp) < 0) { ++ /* ++ * Didn't find entry, or no action needed (the action has ++ * already been performed). ++ */ ++ return; ++ } ++ ++ /* ++ * A DAD cell was present, meaning that there is a pending BU ++ * request for 'haddr' - reject the BU. ++ */ ++ mipv6_bu_finish(ifp, 0, DUPLICATE_ADDR_DETECT_FAIL, ++ &daddr, &haddr, &coa, &rep_coa, 0, seq, 0, NULL); ++ return; ++} ++ ++/* ++ * Check if the passed 'cell' is in the list of pending DAD's. Called from ++ * the Timeout Handler. ++ * ++ * Assumes that the caller is holding the dad_lock in reader mode. ++ */ ++static int dad_search_cell(struct mipv6_dad_cell *cell) ++{ ++ struct mipv6_dad_cell *tmp; ++ ++ tmp = dad_cell_head.next; ++ while (tmp != &dad_cell_head && tmp != cell) { ++ tmp = tmp->next; ++ } ++ if (tmp == cell) { ++ if (cell->flags == DAD_INIT_ENTRY) { ++ /* Found valid entry */ ++ if (--cell->probes == 0) { ++ /* ++ * Retransmission's are over - return success. ++ */ ++ cell->flags = DAD_UNIQUE_ADDRESS; ++ ++ /* ++ * Leave this address to avoid future ++ * processing of NA's. ++ */ ++ mipv6_leave_sol_mc_addr(&cell->ll_haddr, ++ cell->ifp->idev-> ++ dev); ++ if (ipv6_addr_cmp(&cell->ll_haddr, &cell->haddr)) ++ mipv6_leave_sol_mc_addr(&cell->haddr, ++ cell->ifp->idev->dev); ++ /* start timeout to delete this cell. */ ++ mipv6_start_dad_head_timer(cell); ++ return 0; ++ } ++ /* ++ * Retransmission not finished, send another NS and ++ * return failure. ++ */ ++ mipv6_dad_send_ns(cell->ifp, &cell->ll_haddr); ++ if (ipv6_addr_cmp(&cell->ll_haddr, &cell->haddr)) ++ mipv6_leave_sol_mc_addr(&cell->haddr, ++ cell->ifp->idev->dev); ++ cell->callback_timer.expires = jiffies + ++ cell->ifp->idev->nd_parms->retrans_time; ++ add_timer(&cell->callback_timer); ++ } else { ++ /* ++ * This means that an NA was received before the ++ * timeout and when the state changed from ++ * DAD_INIT_ENTRY, the BU got failed as a result. ++ * There is nothing to be done. ++ */ ++ } ++ } ++ return -1; ++} ++ ++/* ENTRY routine called via Timeout */ ++static void mipv6_dad_timeout(unsigned long arg) ++{ ++ __u8 ba_status = SUCCESS; ++ struct in6_addr daddr; ++ struct in6_addr haddr; ++ struct in6_addr coa; ++ struct in6_addr rep_coa; ++ struct inet6_ifaddr *ifp; ++ int ifindex; ++ __u32 ba_lifetime; ++ __u16 sequence; ++ __u8 flags; ++ struct mipv6_dad_cell *cell = (struct mipv6_dad_cell *) arg; ++ ++ /* ++ * If entry is not in the list, we have already sent BU Failure ++ * after getting a NA. ++ */ ++ read_lock(&dad_lock); ++ if (dad_search_cell(cell) < 0) { ++ /* ++ * 'cell' is no longer valid (may not be in the list or ++ * is already processed, due to NA processing), or NS ++ * retransmissions are not yet over. ++ */ ++ read_unlock(&dad_lock); ++ return; ++ } ++ ++ /* This is the final Timeout. Send Bind Ack Success */ ++ ++ ifp = cell->ifp; ++ ifindex = cell->ifindex; ++ ba_lifetime = cell->ba_lifetime; ++ sequence = cell->sequence; ++ flags = cell->bu_flags; ++ ++ ipv6_addr_copy(&daddr, &cell->daddr); ++ ipv6_addr_copy(&haddr, &cell->haddr); ++ ipv6_addr_copy(&coa, &cell->coa); ++ ipv6_addr_copy(&rep_coa, &cell->rep_coa); ++ read_unlock(&dad_lock); ++ ++ /* Send BU Acknowledgement Success */ ++ mipv6_bu_finish(ifp, ifindex, ba_status, ++ &daddr, &haddr, &coa, &rep_coa, ++ ba_lifetime, sequence, flags, NULL); ++ return; ++} ++ ++/* ++ * Check if original home address exists in our DAD pending list, if so return ++ * the cell. ++ * ++ * Assumes that the caller is holding the dad_lock in writer mode. ++ */ ++static struct mipv6_dad_cell *mipv6_dad_get_cell(struct in6_addr *haddr) ++{ ++ struct mipv6_dad_cell *cell; ++ ++ cell = dad_cell_head.next; ++ while (cell != &dad_cell_head ++ && ipv6_addr_cmp(&cell->haddr, haddr)) { ++ cell = cell->next; ++ } ++ if (cell == &dad_cell_head) { ++ /* Not found element */ ++ return NULL; ++ } ++ return cell; ++} ++ ++/* ++ * Save all parameters needed for doing a Bind Ack in the mipv6_dad_cell ++ * structure. ++ */ ++static void mipv6_dad_save_cell(struct mipv6_dad_cell *cell, ++ struct inet6_ifaddr *ifp, int ifindex, ++ struct in6_addr *daddr, ++ struct in6_addr *haddr, ++ struct in6_addr *coa, ++ struct in6_addr *rep_coa, ++ __u32 ba_lifetime, ++ __u16 sequence, __u8 flags) ++{ ++ in6_ifa_hold(ifp); ++ cell->ifp = ifp; ++ cell->ifindex = ifindex; ++ ++ ipv6_addr_copy(&cell->daddr, daddr); ++ ipv6_addr_copy(&cell->haddr, haddr); ++ ipv6_addr_copy(&cell->coa, coa); ++ ipv6_addr_copy(&cell->rep_coa, rep_coa); ++ ++ /* Convert cell->ll_haddr to Link Local address */ ++ if (flags & MIPV6_BU_F_LLADDR) ++ mipv6_generate_ll_addr(&cell->ll_haddr, haddr); ++ else ++ ipv6_addr_copy(&cell->ll_haddr, haddr); ++ ++ cell->ba_lifetime = ba_lifetime; ++ cell->sequence = sequence; ++ cell->bu_flags = flags; ++} ++ ++/* ++ * Top level DAD routine for performing DAD. ++ * ++ * Return values ++ * 0 : Don't need to do DAD. ++ * 1 : Need to do DAD. ++ * -n : Error, where 'n' is the reason for the error. ++ * ++ * Assumption : DAD process has been optimized by using cached values upto ++ * some time. However sometimes this can cause problems. Eg. when the first ++ * BU was received, DAD might have failed. Before the second BU arrived, ++ * the node using MN's home address might have stopped using it, but still ++ * we will return DAD_DUPLICATE_ADDRESS based on the first DAD's result. Or ++ * this can go the other way around. However, it is a very small possibility ++ * and thus optimization is turned on by default. It is possible to change ++ * this feature (needs a little code-rewriting in this routine), but ++ * currently DAD result is being cached for performance reasons. ++ */ ++int mipv6_dad_start(struct inet6_ifaddr *ifp, int ifindex, ++ struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u32 ba_lifetime, __u16 sequence, __u8 flags) ++{ ++ int found; ++ struct mipv6_dad_cell *cell; ++ struct mipv6_bce bc_entry; ++ ++ if (ifp->idev->cnf.dad_transmits == 0) { ++ /* DAD is not configured on the HA, return SUCCESS */ ++ return 0; ++ } ++ ++ if (mipv6_bcache_get(haddr, daddr, &bc_entry) == 0) { ++ /* ++ * We already have an entry in our cache - don't need to ++ * do DAD as we are already defending this home address. ++ */ ++ return 0; ++ } ++ ++ write_lock(&dad_lock); ++ if ((cell = mipv6_dad_get_cell(haddr)) != NULL) { ++ /* ++ * An existing entry for BU was found in our cache due ++ * to retransmission of the BU or a new COA registration. ++ */ ++ switch (cell->flags) { ++ case DAD_INIT_ENTRY: ++ /* Old entry is waiting for DAD to complete */ ++ break; ++ case DAD_UNIQUE_ADDRESS: ++ /* DAD is finished successfully - return success. */ ++ write_unlock(&dad_lock); ++ return 0; ++ case DAD_DUPLICATE_ADDRESS: ++ /* ++ * DAD is finished and we got a NA while doing BU - ++ * return failure. ++ */ ++ write_unlock(&dad_lock); ++ return -DUPLICATE_ADDR_DETECT_FAIL; ++ default: ++ /* Unknown state - should never happen */ ++ DEBUG(DBG_WARNING, ++ "cell entry in unknown state : %d", ++ cell->flags); ++ write_unlock(&dad_lock); ++ return -REASON_UNSPECIFIED; ++ } ++ found = 1; ++ } else { ++ if ((cell = (struct mipv6_dad_cell *) ++ kmalloc(sizeof(struct mipv6_dad_cell), GFP_ATOMIC)) ++ == NULL) { ++ return -INSUFFICIENT_RESOURCES; ++ } ++ found = 0; ++ } ++ ++ mipv6_dad_save_cell(cell, ifp, ifindex, daddr, haddr, coa, rep_coa, ++ ba_lifetime, sequence, flags); ++ ++ if (!found) { ++ cell->flags = DAD_INIT_ENTRY; ++ cell->probes = ifp->idev->cnf.dad_transmits; ++ ++ /* Insert element on dad_cell_head list */ ++ dad_cell_head.prev->next = cell; ++ cell->next = &dad_cell_head; ++ cell->prev = dad_cell_head.prev; ++ dad_cell_head.prev = cell; ++ write_unlock(&dad_lock); ++ if (flags & MIPV6_BU_F_LLADDR) { ++ /* join the solicited node MC of the global homeaddr.*/ ++ mipv6_join_sol_mc_addr(&cell->haddr, ifp->idev->dev); ++ /* Send a NS */ ++ mipv6_dad_send_ns(ifp, &cell->haddr); ++ } ++ /* join the solicited node MC of the homeaddr. */ ++ mipv6_join_sol_mc_addr(&cell->ll_haddr, ifp->idev->dev); ++ ++ /* Send a NS */ ++ mipv6_dad_send_ns(ifp, &cell->ll_haddr); ++ ++ /* Initialize timer for this cell to timeout the NS. */ ++ init_timer(&cell->callback_timer); ++ cell->callback_timer.data = (unsigned long) cell; ++ cell->callback_timer.function = mipv6_dad_timeout; ++ cell->callback_timer.expires = jiffies + ++ ifp->idev->nd_parms->retrans_time; ++ add_timer(&cell->callback_timer); ++ } else { ++ write_unlock(&dad_lock); ++ } ++ return 1; ++} ++ ++void __init mipv6_dad_init(void) ++{ ++ dad_cell_head.next = dad_cell_head.prev = &dad_cell_head; ++ init_timer(&dad_cell_head.callback_timer); ++ dad_cell_head.callback_timer.data = 0; ++ dad_cell_head.callback_timer.function = ++ mipv6_dad_delete_old_entries; ++} ++ ++void __exit mipv6_dad_exit(void) ++{ ++ struct mipv6_dad_cell *curr, *next; ++ ++ write_lock_bh(&dad_lock); ++ del_timer(&dad_cell_head.callback_timer); ++ ++ curr = dad_cell_head.next; ++ while (curr != &dad_cell_head) { ++ next = curr->next; ++ del_timer(&curr->callback_timer); ++ if (curr->flags == DAD_INIT_ENTRY) { ++ /* ++ * We were in DAD_INIT state and listening to the ++ * solicited node MC address - need to stop that. ++ */ ++ mipv6_leave_sol_mc_addr(&curr->ll_haddr, ++ curr->ifp->idev->dev); ++ if (ipv6_addr_cmp(&curr->ll_haddr, &curr->haddr)) ++ mipv6_leave_sol_mc_addr(&curr->haddr, ++ curr->ifp->idev->dev); ++ } ++ in6_ifa_put(curr->ifp); ++ kfree(curr); ++ curr = next; ++ } ++ dad_cell_head.next = dad_cell_head.prev = &dad_cell_head; ++ write_unlock_bh(&dad_lock); ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/prefix.c linux-2.4.25/net/ipv6/mobile_ip6/prefix.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/prefix.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/prefix.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,217 @@ ++/** ++ * Prefix solicitation and advertisement ++ * ++ * Authors: ++ * Jaakko Laine ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mipv6_icmp.h" ++#include "debug.h" ++#include "sortedlist.h" ++#include "prefix.h" ++#include "config.h" ++ ++#define INFINITY 0xffffffff ++ ++struct timer_list pfx_timer; ++ ++struct list_head pfx_list; ++rwlock_t pfx_list_lock = RW_LOCK_UNLOCKED; ++ ++int compare_pfx_list_entry(const void *data1, const void *data2, ++ int datalen) ++{ ++ struct pfx_list_entry *e1 = (struct pfx_list_entry *) data1; ++ struct pfx_list_entry *e2 = (struct pfx_list_entry *) data2; ++ ++ return ((ipv6_addr_cmp(&e1->daddr, &e2->daddr) == 0) ++ && (e2->ifindex == -1 || e1->ifindex == e2->ifindex)); ++} ++ ++/** ++ * mipv6_pfx_cancel_send - cancel pending items to daddr from saddr ++ * @daddr: Destination address ++ * @ifindex: pending items on this interface will be canceled ++ * ++ * if ifindex == -1, all items to daddr will be removed ++ */ ++void mipv6_pfx_cancel_send(struct in6_addr *daddr, int ifindex) ++{ ++ unsigned long tmp; ++ struct pfx_list_entry entry; ++ ++ DEBUG_FUNC(); ++ ++ /* We'll just be comparing these parts... */ ++ memcpy(&entry.daddr, daddr, sizeof(struct in6_addr)); ++ entry.ifindex = ifindex; ++ ++ write_lock_bh(&pfx_list_lock); ++ ++ while (mipv6_slist_del_item(&pfx_list, &entry, ++ compare_pfx_list_entry) == 0) ++ ; ++ ++ if ((tmp = mipv6_slist_get_first_key(&pfx_list))) ++ mod_timer(&pfx_timer, tmp); ++ ++ write_unlock_bh(&pfx_list_lock); ++} ++ ++/** ++ * mipv6_pfx_add_ha - add a new HA to send prefix solicitations to ++ * @daddr: address of HA ++ * @saddr: our address to use as source address ++ * @ifindex: interface index ++ */ ++void mipv6_pfx_add_ha(struct in6_addr *daddr, struct in6_addr *saddr, ++ int ifindex) ++{ ++ unsigned long tmp; ++ struct pfx_list_entry entry; ++ ++ DEBUG_FUNC(); ++ ++ memcpy(&entry.daddr, daddr, sizeof(struct in6_addr)); ++ memcpy(&entry.saddr, saddr, sizeof(struct in6_addr)); ++ entry.retries = 0; ++ entry.ifindex = ifindex; ++ ++ write_lock_bh(&pfx_list_lock); ++ if (mipv6_slist_modify(&pfx_list, &entry, sizeof(struct pfx_list_entry), ++ jiffies + INITIAL_SOLICIT_TIMER * HZ, ++ compare_pfx_list_entry)) ++ DEBUG(DBG_WARNING, "Cannot add new HA to pfx list"); ++ ++ if ((tmp = mipv6_slist_get_first_key(&pfx_list))) ++ mod_timer(&pfx_timer, tmp); ++ write_unlock_bh(&pfx_list_lock); ++} ++ ++int mipv6_pfx_add_home(int ifindex, struct in6_addr *saddr, ++ struct in6_addr *daddr, unsigned long min_expire) ++{ ++ unsigned long tmp; ++ ++ write_lock(&pfx_list_lock); ++ ++ if (min_expire != INFINITY) { ++ unsigned long expire; ++ struct pfx_list_entry entry; ++ ++ memcpy(&entry.daddr, saddr, sizeof(struct in6_addr)); ++ memcpy(&entry.saddr, daddr, sizeof(struct in6_addr)); ++ entry.retries = 0; ++ entry.ifindex = ifindex; ++ ++ /* This is against the RFC 3775, but we need to set ++ * a minimum interval for a prefix solicitation. ++ * Otherwise a prefix solicitation storm will ++ * result if valid lifetime of the prefix is ++ * smaller than MAX_PFX_ADV_DELAY ++ */ ++ min_expire -= MAX_PFX_ADV_DELAY; ++ min_expire = min_expire < MIN_PFX_SOL_DELAY ? MIN_PFX_SOL_DELAY : min_expire; ++ ++ expire = jiffies + min_expire * HZ; ++ ++ if (mipv6_slist_modify(&pfx_list, &entry, ++ sizeof(struct pfx_list_entry), ++ expire, ++ compare_pfx_list_entry) != 0) ++ DEBUG(DBG_WARNING, "Cannot add new entry to pfx_list"); ++ } ++ ++ if ((tmp = mipv6_slist_get_first_key(&pfx_list))) ++ mod_timer(&pfx_timer, tmp); ++ ++ write_unlock(&pfx_list_lock); ++ ++ return 0; ++} ++ ++/** ++ * set_ha_pfx_list - manipulate pfx_list for HA when timer goes off ++ * @entry: pfx_list_entry that is due ++ */ ++static void set_ha_pfx_list(struct pfx_list_entry *entry) ++{ ++} ++ ++/** ++ * set_mn_pfx_list - manipulate pfx_list for MN when timer goes off ++ * @entry: pfx_list_entry that is due ++ */ ++static void set_mn_pfx_list(struct pfx_list_entry *entry) ++{ ++} ++ ++/** ++ * pfx_timer_handler - general timer handler ++ * @dummy: dummy ++ * ++ * calls set_ha_pfx_list and set_mn_pfx_list to do the thing when ++ * a timer goes off ++ */ ++static void pfx_timer_handler(unsigned long dummy) ++{ ++ unsigned long tmp; ++ struct pfx_list_entry *entry; ++ ++ DEBUG_FUNC(); ++ ++ write_lock(&pfx_list_lock); ++ if (!(entry = mipv6_slist_get_first(&pfx_list))) ++ goto out; ++ ++ if (mip6node_cnf.capabilities & CAP_HA) ++ set_ha_pfx_list(entry); ++ if (mip6node_cnf.capabilities & CAP_MN) ++ set_mn_pfx_list(entry); ++ if ((tmp = mipv6_slist_get_first_key(&pfx_list))) ++ mod_timer(&pfx_timer, tmp); ++ ++ out: ++ write_unlock(&pfx_list_lock); ++} ++ ++int mipv6_initialize_pfx_icmpv6(void) ++{ ++ INIT_LIST_HEAD(&pfx_list); ++ ++ init_timer(&pfx_timer); ++ pfx_timer.function = pfx_timer_handler; ++ ++ return 0; ++} ++ ++void mipv6_shutdown_pfx_icmpv6(void) ++{ ++ struct prefix_info *tmp; ++ ++ if (timer_pending(&pfx_timer)) ++ del_timer(&pfx_timer); ++ ++ write_lock_bh(&pfx_list_lock); ++ while ((tmp = mipv6_slist_del_first(&pfx_list))) ++ kfree(tmp); ++ write_unlock_bh(&pfx_list_lock); ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/prefix.h linux-2.4.25/net/ipv6/mobile_ip6/prefix.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/prefix.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/prefix.h 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,57 @@ ++/* ++ * MIPL Mobile IPv6 Prefix solicitation and advertisement ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _PREFIX_H ++#define _PREFIX_H ++ ++#include ++ ++struct pfx_list_entry { ++ struct in6_addr daddr; ++ struct in6_addr saddr; ++ int retries; ++ int ifindex; ++}; ++ ++extern struct list_head pfx_list; ++extern rwlock_t pfx_list_lock; ++extern struct timer_list pfx_timer; ++ ++int compare_pfx_list_entry(const void *data1, const void *data2, ++ int datalen); ++ ++/** ++ * mipv6_pfx_cancel_send - cancel pending pfx_advs/sols to daddr ++ * @daddr: destination address ++ * @ifindex: pending items on this interface will be canceled ++ * ++ * if ifindex == -1, all items to daddr will be removed ++ */ ++void mipv6_pfx_cancel_send(struct in6_addr *daddr, int ifindex); ++ ++/** ++ * mipv6_pfx_add_ha - add a new HA to send prefix solicitations to ++ * @daddr: address of HA ++ * @saddr: our address to use as source address ++ * @ifindex: interface index ++ */ ++void mipv6_pfx_add_ha(struct in6_addr *daddr, struct in6_addr *saddr, ++ int ifindex); ++ ++void mipv6_pfxs_modified(struct prefix_info *pinfo, int ifindex); ++ ++int mipv6_pfx_add_home(int ifindex, struct in6_addr *daddr, ++ struct in6_addr *saddr, unsigned long min_expire); ++ ++int mipv6_initialize_pfx_icmpv6(void); ++void mipv6_shutdown_pfx_icmpv6(void); ++ ++#endif +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/prefix_ha.c linux-2.4.25/net/ipv6/mobile_ip6/prefix_ha.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/prefix_ha.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/prefix_ha.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,122 @@ ++/** ++ * Prefix advertisement for Home Agent ++ * ++ * Authors: ++ * Jaakko Laine ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mipv6_icmp.h" ++#include "debug.h" ++#include "sortedlist.h" ++#include "util.h" ++#include "bcache.h" ++#include "config.h" ++#include "prefix.h" ++ ++/** ++ * pfx_adv_iterator - modify pfx_list entries according to new prefix info ++ * @data: MN's home registration bcache_entry ++ * @args: new prefix info ++ * @sortkey: ignored ++ */ ++static int pfx_adv_iterator(void *data, void *args, unsigned long sortkey) ++{ ++ struct mipv6_bce *bc_entry = (struct mipv6_bce *) data; ++ struct prefix_info *pinfo = (struct prefix_info *) args; ++ ++ if (mipv6_prefix_compare(&bc_entry->coa, &pinfo->prefix, ++ pinfo->prefix_len) == 0) { ++ struct pfx_list_entry pfx_entry; ++ ++ memcpy(&pfx_entry.daddr, &bc_entry->coa, ++ sizeof(struct in6_addr)); ++ memcpy(&pfx_entry.daddr, &bc_entry->our_addr, ++ sizeof(struct in6_addr)); ++ pfx_entry.retries = 0; ++ pfx_entry.ifindex = bc_entry->ifindex; ++ ++ mipv6_slist_modify(&pfx_list, &pfx_entry, ++ sizeof(struct pfx_list_entry), ++ jiffies + ++ net_random() % (MAX_PFX_ADV_DELAY * HZ), ++ compare_pfx_list_entry); ++ } ++ ++ return 0; ++} ++ ++struct homereg_iterator_args { ++ struct list_head *head; ++ int count; ++}; ++ ++static int homereg_iterator(void *data, void *args, unsigned long *sortkey) ++{ ++ struct mipv6_bce *entry = (struct mipv6_bce *) data; ++ struct homereg_iterator_args *state = ++ (struct homereg_iterator_args *) args; ++ ++ if (entry->type == HOME_REGISTRATION) { ++ mipv6_slist_add(state->head, entry, ++ sizeof(struct mipv6_bce), ++ state->count); ++ state->count++; ++ } ++ return 0; ++} ++ ++static int mipv6_bcache_get_homeregs(struct list_head *head) ++{ ++ struct homereg_iterator_args args; ++ ++ DEBUG_FUNC(); ++ ++ args.count = 0; ++ args.head = head; ++ ++ mipv6_bcache_iterate(homereg_iterator, &args); ++ return args.count; ++} ++ ++/** ++ * mipv6_prefix_added - prefix was added to interface, act accordingly ++ * @pinfo: prefix_info that was added ++ * @ifindex: interface index ++ */ ++void mipv6_pfxs_modified(struct prefix_info *pinfo, int ifindex) ++{ ++ int count; ++ unsigned long tmp; ++ struct list_head home_regs; ++ ++ DEBUG_FUNC(); ++ ++ INIT_LIST_HEAD(&home_regs); ++ ++ if (!(count = mipv6_bcache_get_homeregs(&home_regs))) ++ return; ++ ++ write_lock_bh(&pfx_list_lock); ++ mipv6_slist_for_each(&home_regs, pinfo, pfx_adv_iterator); ++ if ((tmp = mipv6_slist_get_first_key(&pfx_list))) ++ mod_timer(&pfx_timer, tmp); ++ write_unlock_bh(&pfx_list_lock); ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/rr_crypto.c linux-2.4.25/net/ipv6/mobile_ip6/rr_crypto.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/rr_crypto.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/rr_crypto.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,255 @@ ++/* ++ * rr_cookie.c - Mobile IPv6 return routability crypto ++ * Author : Henrik Petander ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ * ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "debug.h" ++#include "hmac.h" ++#include "rr_crypto.h" ++ ++#define DBG_RR 5 ++ ++u8 k_CN[HMAC_SHA1_KEY_SIZE]; // secret key of CN ++ ++u16 curr_index = 0; ++ ++struct nonce_timestamp nonce_table[MAX_NONCES]; ++spinlock_t nonce_lock = SPIN_LOCK_UNLOCKED; ++void update_nonces(void); ++ ++/** nonce_is_fresh - whether the nonce was generated recently ++ * ++ * @non_ts : table entry containing the nonce and a timestamp ++ * @interval : if nonce was generated within interval seconds it is fresh ++ * ++ * Returns 1 if the nonce is fresh, 0 otherwise. ++ */ ++static int nonce_is_fresh(struct nonce_timestamp *non_ts, unsigned long interval) ++{ ++ if (time_before(jiffies, non_ts->timestamp + interval * HZ) && !non_ts->invalid) ++ return 1; ++ return 0; ++} ++void mipv6_rr_invalidate_nonce(u16 nonce_ind) ++{ ++ spin_lock_bh(&nonce_lock); ++ if (nonce_ind > MAX_NONCES) { ++ spin_unlock_bh(&nonce_lock); ++ return; ++ } ++ nonce_table[nonce_ind].invalid = 1; ++ spin_unlock_bh(&nonce_lock); ++} ++/* Returns a pointer to a new nonce */ ++struct mipv6_rr_nonce * mipv6_rr_get_new_nonce(void) ++{ ++ struct mipv6_rr_nonce *nce = kmalloc(sizeof(*nce), GFP_ATOMIC); ++ ++ if (!nce) ++ return NULL; ++ // Lock nonces here ++ spin_lock_bh(&nonce_lock); ++ // If nonce is not fresh create new one ++ if (!nonce_is_fresh(&nonce_table[curr_index], MIPV6_RR_NONCE_LIFETIME)) { ++ // increment the last nonce pointer and create new nonce ++ curr_index++; ++ // Wrap around ++ if (curr_index == MAX_NONCES) ++ curr_index = 0; ++ // Get random data to fill the nonce data ++ get_random_bytes(nonce_table[curr_index].nonce.data, MIPV6_RR_NONCE_DATA_LENGTH); ++ // Fill the index field ++ nonce_table[curr_index].nonce.index = curr_index; ++ nonce_table[curr_index].invalid = 0; ++ nonce_table[curr_index].timestamp = jiffies; ++ } ++ spin_unlock_bh(&nonce_lock); ++ memcpy(nce, &nonce_table[curr_index].nonce, sizeof(*nce)); ++ // Unlock nonces ++ return nce; ++} ++/** mipv6_rr_nonce_get_by_index - returns a nonce for index ++ * @nonce_ind : index of the nonce ++ * ++ * Returns a nonce or NULL if the nonce index was invalid or the nonce ++ * for the index was not fresh. ++ */ ++struct mipv6_rr_nonce * mipv6_rr_nonce_get_by_index(u16 nonce_ind) ++{ ++ struct mipv6_rr_nonce *nce = NULL; ++ ++ spin_lock_bh(&nonce_lock); ++ if (nonce_ind >= MAX_NONCES) { ++ DEBUG(DBG_WARNING, "Nonce index field from BU invalid"); ++ ++ /* Here a double of the nonce_lifetime is used for freshness ++ * verification, since the nonces ++ * are not created in response to every initiator packet ++ */ ++ } else if (nonce_is_fresh(&nonce_table[nonce_ind], 2 * MIPV6_RR_NONCE_LIFETIME)) { ++ nce = kmalloc(sizeof(*nce), GFP_ATOMIC); ++ memcpy(nce, &nonce_table[nonce_ind].nonce, sizeof(*nce)); ++ } ++ spin_unlock_bh(&nonce_lock); ++ ++ return nce; ++} ++ ++/* Fills rr test init cookies with random bytes */ ++void mipv6_rr_mn_cookie_create(u8 *cookie) ++{ ++ get_random_bytes(cookie, MIPV6_RR_COOKIE_LENGTH); ++} ++ ++/** mipv6_rr_cookie_create - builds a home or care-of cookie ++ * ++ * @addr : the home or care-of address from HoTI or CoTI ++ * @ckie : memory where the cookie is copied to ++ * @nce : pointer to a nonce used for the calculation, nce is freed during the function ++ * ++ */ ++int mipv6_rr_cookie_create(struct in6_addr *addr, u8 **ckie, ++ u16 nonce_index) ++{ ++ struct ah_processing ah_proc; ++ u8 digest[HMAC_SHA1_HASH_LEN]; ++ struct mipv6_rr_nonce *nce; ++ ++ if ((nce = mipv6_rr_nonce_get_by_index(nonce_index))== NULL) ++ return -1; ++ ++ if (*ckie == NULL && (*ckie = kmalloc(MIPV6_RR_COOKIE_LENGTH, ++ GFP_ATOMIC)) == NULL) { ++ kfree(nce); ++ return -1; ++ } ++ /* Calculate the full hmac-sha1 digest from address and nonce using the secret key of cn */ ++ ++ if (ah_hmac_sha1_init(&ah_proc, k_CN, HMAC_SHA1_KEY_SIZE) < 0) { ++ DEBUG(DBG_ERROR, "Hmac sha1 initialization failed"); ++ kfree(nce); ++ return -1; ++ } ++ ++ ah_hmac_sha1_loop(&ah_proc, addr, sizeof(*addr)); ++ ah_hmac_sha1_loop(&ah_proc, nce->data, MIPV6_RR_NONCE_DATA_LENGTH); ++ ah_hmac_sha1_result(&ah_proc, digest); ++ ++ ++ /* clean up nonce */ ++ kfree(nce); ++ ++ /* Copy first 64 bits of hash target to the cookie */ ++ memcpy(*ckie, digest, MIPV6_RR_COOKIE_LENGTH); ++ return 0; ++} ++ ++/** mipv6_rr_key_calc - creates BU authentication key ++ * ++ * @hoc : Home Cookie ++ * @coc : Care-of Cookie ++ * ++ * Returns BU authentication key of length HMAC_SHA1_KEY_SIZE or NULL in error cases, ++ * caller needs to free the key. ++ */ ++u8 *mipv6_rr_key_calc(u8 *hoc, u8 *coc) ++{ ++ ++ u8 *key_bu = kmalloc(HMAC_SHA1_KEY_SIZE, GFP_ATOMIC); ++ SHA1_CTX c; ++ ++ if (!key_bu) { ++ DEBUG(DBG_CRITICAL, "Memory allocation failed, could nort create BU authentication key"); ++ return NULL; ++ } ++ ++ /* Calculate the key from home and care-of cookies ++ * Kbu = sha1(home_cookie | care-of cookie) ++ * or KBu = sha1(home_cookie), if MN deregisters ++ */ ++ sha1_init(&c); ++ sha1_compute(&c, hoc, MIPV6_RR_COOKIE_LENGTH); ++ if (coc) ++ sha1_compute(&c, coc, MIPV6_RR_COOKIE_LENGTH); ++ sha1_final(&c, key_bu); ++ DEBUG(DBG_RR, "Home and Care-of cookies used for calculating key "); ++ debug_print_buffer(DBG_RR, hoc, MIPV6_RR_COOKIE_LENGTH); ++ if (coc) ++ debug_print_buffer(DBG_RR, coc, MIPV6_RR_COOKIE_LENGTH); ++ ++ return key_bu; ++} ++ ++void mipv6_rr_init(void) ++{ ++ get_random_bytes(k_CN, HMAC_SHA1_KEY_SIZE); ++ memset(nonce_table, 0, MAX_NONCES * sizeof(struct nonce_timestamp)); ++} ++ ++#ifdef TEST_MIPV6_RR_CRYPTO ++void mipv6_test_rr(void) ++{ ++ struct mipv6_rr_nonce *nonce; ++ struct in6_addr a1, a2; ++ int ind1, ind2; ++ u8 *ckie1 = NULL, *ckie2 = NULL; ++ u8 *key_mn = NULL, *key_cn = NULL; ++ mipv6_init_rr(); ++ ++ nonce = mipv6_rr_get_new_nonce(); ++ if (!nonce) { ++ printk("mipv6_rr_get_new_nonce() failed, at 1! \n"); ++ return; ++ } ++ mipv6_rr_cookie_create(&a1, &ckie1, nonce->index); ++ ind1 = nonce->index; ++ kfree(nonce); ++ ++ nonce = mipv6_rr_get_new_nonce(); ++ if (!nonce) { ++ printk("mipv6_rr_get_new_nonce() failed, at 2! \n"); ++ return; ++ } ++ ++ mipv6_rr_cookie_create(&a2, &ckie2, nonce->index); ++ ind2 = nonce->index; ++ key_mn = mipv6_rr_key_calc(ckie1, ckie2); ++ ++ /* Create home and coa cookies based on indices */ ++ mipv6_rr_cookie_create(&a1, &ckie1, ind1); ++ mipv6_rr_cookie_create(&a2, &ckie2, ind2); ++ key_cn = mipv6_rr_key_calc(ckie1, ckie2); ++ if (!key_cn || !key_mn) { ++ printk("creation of secret key failed!\n"); ++ return; ++ } ++ if(memcmp(key_cn, key_mn, HMAC_SHA1_KEY_SIZE)) ++ printk("mipv6_rr_key_calc produced different keys for MN and CN \n"); ++ else ++ printk("mipv6_rr_crypto test OK\n"); ++ kfree(nonce); ++ kfree(key_cn); ++ kfree(key_mn); ++} ++#endif +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/rr_crypto.h linux-2.4.25/net/ipv6/mobile_ip6/rr_crypto.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/rr_crypto.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/rr_crypto.h 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,72 @@ ++/* ++ * MIPL Mobile IPv6 Return routability crypto prototypes ++ * ++ * $Id:$ ++ * ++ * 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. ++ */ ++ ++#ifndef _RR_CRYPTO ++#define _RR_CRYPTO ++ ++#include ++ ++/* Macros and data structures */ ++ ++#define MIPV6_RR_NONCE_LIFETIME 60 ++#define MIPV6_RR_NONCE_DATA_LENGTH 8 ++#define MIPV6_RR_COOKIE_LENGTH 8 ++#define COOKIE_SIZE 8 ++#define MAX_NONCES 4 ++#define HMAC_SHA1_KEY_SIZE 20 ++ ++struct mipv6_rr_nonce { ++ u_int16_t index; ++ u_int8_t data[MIPV6_RR_NONCE_DATA_LENGTH]; ++}; ++ ++struct nonce_timestamp { ++ struct mipv6_rr_nonce nonce; ++ unsigned long timestamp; ++ u_int8_t invalid; ++}; ++ ++/* Function definitions */ ++ ++/* Return 1 if equal, 0 if not */ ++static __inline__ int mipv6_equal_cookies(u8 *c1, u8 *c2) ++{ ++ return (memcmp(c1, c2, MIPV6_RR_COOKIE_LENGTH) == 0); ++} ++ ++/* Function declarations */ ++ ++/* Create cookie for HoTi and CoTi */ ++extern void mipv6_rr_mn_cookie_create(u8 *cookie); ++ ++/* Create cookie for HoT and CoT */ ++extern int mipv6_rr_cookie_create(struct in6_addr *addr, u8 **ckie, u16 nonce_index); ++ ++/* Calculate return routability key from home and care-of cookies, key length is ++ * HMAC_SHA1_KEY_SIZE ++ */ ++extern u_int8_t *mipv6_rr_key_calc(u8 *hoc, u8 *coc); ++ ++extern struct mipv6_rr_nonce *mipv6_rr_get_new_nonce(void); ++ ++/* For avoiding replay attacks when MN deregisters */ ++extern void mipv6_rr_invalidate_nonce(u16 nonce_index); ++/* ++ * initializes the return routability crypto ++ */ ++ ++void mipv6_rr_init(void); ++ ++#ifdef TEST_MIPV6_RR_CRYPTO ++void mipv6_test_rr(void); ++#endif /* TEST_MIPV6_RR_CRYPTO */ ++ ++#endif /* RR_CRYPTO */ +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/sortedlist.c linux-2.4.25/net/ipv6/mobile_ip6/sortedlist.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/sortedlist.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/sortedlist.c 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,349 @@ ++/** ++ * Sorted list - linked list with sortkey. ++ * ++ * Authors: ++ * Jaakko Laine ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++struct mipv6_sorted_list_entry { ++ struct list_head list; ++ void *data; ++ int datalen; ++ unsigned long sortkey; ++}; ++ ++/** ++ * compare - compares two arbitrary data items ++ * @data1: first data item ++ * @data2: second data item ++ * @datalen: length of data items in bits ++ * ++ * datalen is in bits! ++ */ ++int mipv6_bitwise_compare(const void *data1, const void *data2, int datalen) ++{ ++ int n = datalen; ++ __u8 * ptr1 = (__u8 *)data1; ++ __u8 * ptr2 = (__u8 *)data2; ++ ++ for (; n>=0; n-=8, ptr1++, ptr2++) { ++ if (n >= 8) { ++ if (*ptr1 != *ptr2) ++ return 0; ++ } else { ++ if ((*ptr1 ^ *ptr2) & ((~0) << (8 - n))) ++ return 0; ++ } ++ } ++ ++ return 1; ++} ++ ++/** ++ * mipv6_slist_add - add an entry to sorted list ++ * @head: list_head of the sorted list ++ * @data: item to store ++ * @datalen: length of data (in bytes) ++ * @key: sortkey of item ++ * ++ * Allocates memory for entry and data ++ */ ++int mipv6_slist_add(struct list_head *head, void *data, int datalen, ++ unsigned long sortkey) ++{ ++ struct list_head *pos; ++ struct mipv6_sorted_list_entry *entry, *tmp, *next; ++ ++ entry = kmalloc(sizeof(struct mipv6_sorted_list_entry), GFP_ATOMIC); ++ ++ if (!entry) ++ return -1; ++ ++ entry->data = kmalloc(datalen, GFP_ATOMIC); ++ ++ if (!entry->data) { ++ kfree(entry); ++ return -1; ++ } ++ ++ memcpy(entry->data, data, datalen); ++ entry->datalen = datalen; ++ entry->sortkey = sortkey; ++ ++ if ((pos = head->next) == head) { ++ list_add(&entry->list, head); ++ return 0; ++ } ++ ++ tmp = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (entry->sortkey < tmp->sortkey) { ++ list_add(&entry->list, head); ++ return 0; ++ } ++ ++ for (; pos != head; pos = pos->next) { ++ tmp = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (pos->next == head) { ++ list_add(&entry->list, &tmp->list); ++ return 0; ++ } ++ next = list_entry(pos->next, struct mipv6_sorted_list_entry, list); ++ if (entry->sortkey >= tmp->sortkey && entry->sortkey < next->sortkey) { ++ list_add(&entry->list, &tmp->list); ++ return 0; ++ } ++ } ++ ++ /* never reached */ ++ return -1; ++} ++ ++/** ++ * mipv6_slist_get_first - get the first data item in the list ++ * @head: list_head of the sorted list ++ * ++ * Returns the actual data item, not copy, so don't kfree it ++ */ ++void *mipv6_slist_get_first(struct list_head *head) ++{ ++ struct mipv6_sorted_list_entry *entry; ++ ++ if (list_empty(head)) ++ return NULL; ++ ++ entry = list_entry(head->next, struct mipv6_sorted_list_entry, list); ++ return entry->data; ++} ++ ++/** ++ * mipv6_slist_del_first - delete (and get) the first item in list ++ * @head: list_head of the sorted list ++ * ++ * Remember to kfree the item ++ */ ++void *mipv6_slist_del_first(struct list_head *head) ++{ ++ void *tmp; ++ struct mipv6_sorted_list_entry *entry; ++ ++ if (list_empty(head)) ++ return NULL; ++ ++ entry = list_entry(head->next, struct mipv6_sorted_list_entry, list); ++ tmp = entry->data; ++ ++ list_del(head->next); ++ kfree(entry); ++ ++ return tmp; ++} ++ ++/** ++ * mipv6_slist_del_item - delete entry ++ * @head: list_head of the sorted list ++ * @data: item to delete ++ * @compare: function used for comparing the data items ++ * ++ * compare function needs to have prototype ++ * int (*compare)(const void *data1, const void *data2, int datalen) ++ */ ++int mipv6_slist_del_item(struct list_head *head, void *data, ++ int (*compare)(const void *data1, const void *data2, ++ int datalen)) ++{ ++ struct list_head *pos; ++ struct mipv6_sorted_list_entry *entry; ++ ++ for(pos = head->next; pos != head; pos = pos->next) { ++ entry = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (compare(data, entry->data, entry->datalen)) { ++ list_del(pos); ++ kfree(entry->data); ++ kfree(entry); ++ return 0; ++ } ++ } ++ ++ return -1; ++} ++ ++/** ++ * mipv6_slist_get_first_key - get sortkey of the first item ++ * @head: list_head of the sorted list ++ */ ++unsigned long mipv6_slist_get_first_key(struct list_head *head) ++{ ++ struct mipv6_sorted_list_entry *entry; ++ ++ if (list_empty(head)) ++ return 0; ++ ++ entry = list_entry(head->next, struct mipv6_sorted_list_entry, list); ++ return entry->sortkey; ++} ++ ++/** ++ * mipv6_slist_get_key - get sortkey of the data item ++ * @head: list_head of the sorted list ++ * @data: the item to search for ++ * @compare: function used for comparing the data items ++ * ++ * compare function needs to have prototype ++ * int (*compare)(const void *data1, const void *data2, int datalen) ++ */ ++unsigned long mipv6_slist_get_key(struct list_head *head, void *data, ++ int (*compare)(const void *data1, ++ const void *data2, ++ int datalen)) ++{ ++ struct list_head *pos; ++ struct mipv6_sorted_list_entry *entry; ++ ++ for(pos = head->next; pos != head; pos = pos->next) { ++ entry = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (compare(data, entry->data, entry->datalen)) ++ return entry->sortkey; ++ } ++ ++ return 0; ++} ++ ++/** ++ * mipv6_slist_get_data - get the data item identified by sortkey ++ * @head: list_head of the sorted list ++ * @key: sortkey of the item ++ * ++ * Returns the actual data item, not copy, so don't kfree it ++ */ ++void *mipv6_slist_get_data(struct list_head *head, unsigned long sortkey) ++{ ++ struct list_head *pos; ++ struct mipv6_sorted_list_entry *entry; ++ ++ list_for_each(pos, head) { ++ entry = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (entry->sortkey == sortkey) ++ return entry->data; ++ } ++ ++ return NULL; ++} ++ ++/** ++ * reorder_entry - move an entry to a new position according to sortkey ++ * @head: list_head of the sorted list ++ * @entry_pos: current place of the entry ++ * @key: new sortkey ++ */ ++static void reorder_entry(struct list_head *head, struct list_head *entry_pos, ++ unsigned long sortkey) ++{ ++ struct list_head *pos; ++ struct mipv6_sorted_list_entry *entry; ++ ++ list_del(entry_pos); ++ ++ for (pos = head->next; pos != head; pos = pos->next) { ++ entry = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (sortkey >= entry->sortkey) { ++ list_add(entry_pos, &entry->list); ++ return; ++ } ++ } ++ ++ list_add(entry_pos, head); ++} ++ ++/** ++ * mipv6_slist_modify - modify data item ++ * @head: list_head of the sorted list ++ * @data: item, whose sortkey is to be modified ++ * @datalen: datalen in bytes ++ * @new_key: new sortkey ++ * @compare: function used for comparing the data items ++ * ++ * Compies the new data on top of the old one, if compare function returns ++ * true. If there's no matching entry, new one will be created. ++ * Compare function needs to have prototype ++ * int (*compare)(const void *data1, const void *data2, int datalen) ++ */ ++int mipv6_slist_modify(struct list_head *head, void *data, int datalen, ++ unsigned long new_key, ++ int (*compare)(const void *data1, const void *data2, ++ int datalen)) ++{ ++ struct list_head *pos; ++ struct mipv6_sorted_list_entry *entry; ++ ++ for (pos = head->next; pos != head; pos = pos->next) { ++ entry = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (compare(data, entry->data, datalen)) { ++ memcpy(entry->data, data, datalen); ++ entry->sortkey = new_key; ++ reorder_entry(head, &entry->list, new_key); ++ return 0; ++ } ++ } ++ ++ return mipv6_slist_add(head, data, datalen, new_key); ++} ++ ++/** ++ * mipv6_slist_push_first - move the first entry to place indicated by new_key ++ * @head: list_head of the sorted list ++ * @new_key: new sortkey ++ */ ++int mipv6_slist_push_first(struct list_head *head, unsigned long new_key) ++{ ++ struct mipv6_sorted_list_entry *entry; ++ ++ if (list_empty(head)) ++ return -1; ++ ++ entry = list_entry(head->next, struct mipv6_sorted_list_entry, list); ++ entry->sortkey = new_key; ++ ++ reorder_entry(head, head->next, new_key); ++ return 0; ++} ++ ++/** ++ * mipv6_slist_for_each - apply func to every item in list ++ * @head: list_head of the sorted list ++ * @args: args to pass to func ++ * @func: function to use ++ * ++ * function must be of type ++ * int (*func)(void *data, void *args, unsigned long sortkey) ++ * List iteration will stop once func has been applied to every item ++ * or when func returns true ++ */ ++int mipv6_slist_for_each(struct list_head *head, void *args, ++ int (*func)(void *data, void *args, ++ unsigned long sortkey)) ++{ ++ struct list_head *pos; ++ struct mipv6_sorted_list_entry *entry; ++ ++ list_for_each(pos, head) { ++ entry = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (func(entry->data, args, entry->sortkey)) ++ break; ++ } ++ ++ return 0; ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/sortedlist.h linux-2.4.25/net/ipv6/mobile_ip6/sortedlist.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/sortedlist.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/sortedlist.h 2004-06-26 11:29:31.000000000 +0100 +@@ -0,0 +1,133 @@ ++/* ++ * Sorted list - linked list with sortkey ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++/** ++ * compare - compares two arbitrary data items ++ * @data1: first data item ++ * @data2: second data item ++ * @datalen: length of data items in bits ++ * ++ * datalen is in bits! ++ */ ++int mipv6_bitwise_compare(const void *data1, const void *data2, int datalen); ++ ++/** ++ * mipv6_slist_add - add an entry to sorted list ++ * @head: list_head of the sorted list ++ * @data: item to store ++ * @datalen: length of data (in bytes) ++ * @key: sortkey of item ++ * ++ * Allocates memory for entry and data ++ */ ++int mipv6_slist_add(struct list_head *head, void *data, int datalen, ++ unsigned long sortkey); ++ ++/** ++ * mipv6_slist_get_first - get the first data item in the list ++ * @head: list_head of the sorted list ++ * ++ * Returns the actual data item, not copy, so don't kfree it ++ */ ++void *mipv6_slist_get_first(struct list_head *head); ++ ++/** ++ * mipv6_slist_del_first - delete (and get) the first item in list ++ * @head: list_head of the sorted list ++ * ++ * Remember to kfree the item ++ */ ++void *mipv6_slist_del_first(struct list_head *head); ++ ++/** ++ * mipv6_slist_del_item - delete entry ++ * @head: list_head of the sorted list ++ * @data: item to delete ++ * @compare: function used for comparing the data items ++ * ++ * compare function needs to have prototype ++ * int (*compare)(const void *data1, const void *data2, int datalen) where ++ * datalen is in bits ++ */ ++int mipv6_slist_del_item(struct list_head *head, void *data, ++ int (*compare)(const void *data1, const void *data2, ++ int datalen)); ++ ++/** ++ * mipv6_slist_get_first_key - get sortkey of the first item ++ * @head: list_head of the sorted list ++ */ ++unsigned long mipv6_slist_get_first_key(struct list_head *head); ++ ++/** ++ * mipv6_slist_get_key - get sortkey of the data item ++ * @head: list_head of the sorted list ++ * @data: the item to search for ++ * @compare: function used for comparing the data items ++ * ++ * compare function needs to have prototype ++ * int (*compare)(const void *data1, const void *data2, int datalen) where ++ * datalen is in bits ++ */ ++unsigned long mipv6_slist_get_key(struct list_head *head, void *data, ++ int (*compare)(const void *data1, ++ const void *data2, ++ int datalen)); ++ ++/** ++ * mipv6_slist_get_data - get the data item identified by sortkey ++ * @head: list_head of the sorted list ++ * @key: sortkey of the item ++ * ++ * Returns the actual data item, not copy, so don't kfree it ++ */ ++void *mipv6_slist_get_data(struct list_head *head, unsigned long sortkey); ++ ++/** ++ * mipv6_slist_modify - modify data item ++ * @head: list_head of the sorted list ++ * @data: item, whose sortkey is to be modified ++ * @datalen: datalen in bytes ++ * @new_key: new sortkey ++ * @compare: function used for comparing the data items ++ * ++ * Compies the new data on top of the old one, if compare function returns ++ * non-negative. If there's no matching entry, new one will be created. ++ * Compare function needs to have prototype ++ * int (*compare)(const void *data1, const void *data2, int datalen) where ++ * datalen is in bits. ++ */ ++int mipv6_slist_modify(struct list_head *head, void *data, int datalen, ++ unsigned long new_key, ++ int (*compare)(const void *data1, const void *data2, ++ int datalen)); ++ ++/** ++ * mipv6_slist_push_first - move the first entry to place indicated by new_key ++ * @head: list_head of the sorted list ++ * @new_key: new sortkey ++ */ ++int mipv6_slist_push_first(struct list_head *head, unsigned long new_key); ++ ++/** ++ * mipv6_slist_for_each - apply func to every item in list ++ * @head: list_head of the sorted list ++ * @args: args to pass to func ++ * @func: function to use ++ * ++ * function must be of type ++ * int (*func)(void *data, void *args, unsigned long sortkey) ++ * List iteration will stop once func has been applied to every item ++ * or when func returns true ++ */ ++int mipv6_slist_for_each(struct list_head *head, void *args, ++ int (*func)(void *data, void *args, ++ unsigned long sortkey)); +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/stats.c linux-2.4.25/net/ipv6/mobile_ip6/stats.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/stats.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/stats.c 2004-06-26 11:29:32.000000000 +0100 +@@ -0,0 +1,90 @@ ++/* ++ * Statistics module ++ * ++ * Authors: ++ * Sami Kivisaari ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ * Changes: ++ * Krishna Kumar, ++ * Venkata Jagana : SMP locking fix ++ */ ++ ++#include ++#include ++#include "stats.h" ++ ++struct mipv6_statistics mipv6_stats; ++ ++static int proc_info_dump( ++ char *buffer, char **start, ++ off_t offset, int length) ++{ ++ struct inf { ++ char *name; ++ int *value; ++ } int_stats[] = { ++ {"NEncapsulations", &mipv6_stats.n_encapsulations}, ++ {"NDecapsulations", &mipv6_stats.n_decapsulations}, ++ {"NBindRefreshRqsRcvd", &mipv6_stats.n_brr_rcvd}, ++ {"NHomeTestInitsRcvd", &mipv6_stats.n_hoti_rcvd}, ++ {"NCareofTestInitsRcvd", &mipv6_stats.n_coti_rcvd}, ++ {"NHomeTestRcvd", &mipv6_stats.n_hot_rcvd}, ++ {"NCareofTestRcvd", &mipv6_stats.n_cot_rcvd}, ++ {"NBindUpdatesRcvd", &mipv6_stats.n_bu_rcvd}, ++ {"NBindAcksRcvd", &mipv6_stats.n_ba_rcvd}, ++ {"NBindNAcksRcvd", &mipv6_stats.n_ban_rcvd}, ++ {"NBindErrorsRcvd", &mipv6_stats.n_be_rcvd}, ++ {"NBindRefreshRqsSent", &mipv6_stats.n_brr_sent}, ++ {"NHomeTestInitsSent", &mipv6_stats.n_hoti_sent}, ++ {"NCareofTestInitsSent", &mipv6_stats.n_coti_sent}, ++ {"NHomeTestSent", &mipv6_stats.n_hot_sent}, ++ {"NCareofTestSent", &mipv6_stats.n_cot_sent}, ++ {"NBindUpdatesSent", &mipv6_stats.n_bu_sent}, ++ {"NBindAcksSent", &mipv6_stats.n_ba_sent}, ++ {"NBindNAcksSent", &mipv6_stats.n_ban_sent}, ++ {"NBindErrorsSent", &mipv6_stats.n_be_sent}, ++ {"NBindUpdatesDropAuth", &mipv6_stats.n_bu_drop.auth}, ++ {"NBindUpdatesDropInvalid", &mipv6_stats.n_bu_drop.invalid}, ++ {"NBindUpdatesDropMisc", &mipv6_stats.n_bu_drop.misc}, ++ {"NBindAcksDropAuth", &mipv6_stats.n_bu_drop.auth}, ++ {"NBindAcksDropInvalid", &mipv6_stats.n_bu_drop.invalid}, ++ {"NBindAcksDropMisc", &mipv6_stats.n_bu_drop.misc}, ++ {"NBindRqsDropAuth", &mipv6_stats.n_bu_drop.auth}, ++ {"NBindRqsDropInvalid", &mipv6_stats.n_bu_drop.invalid}, ++ {"NBindRqsDropMisc", &mipv6_stats.n_bu_drop.misc} ++ }; ++ ++ int i; ++ int len = 0; ++ for(i=0; i length) len = length; ++ ++ return len; ++} ++ ++int mipv6_stats_init(void) ++{ ++ memset(&mipv6_stats, 0, sizeof(struct mipv6_statistics)); ++ proc_net_create("mip6_stat", 0, proc_info_dump); ++ return 0; ++} ++ ++void mipv6_stats_exit(void) ++{ ++ proc_net_remove("mip6_stat"); ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/stats.h linux-2.4.25/net/ipv6/mobile_ip6/stats.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/stats.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/stats.h 2004-06-26 11:29:32.000000000 +0100 +@@ -0,0 +1,71 @@ ++/* ++ * MIPL Mobile IPv6 Statistics header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _STATS_H ++#define _STATS_H ++ ++struct mipv6_drop { ++ __u32 auth; ++ __u32 invalid; ++ __u32 misc; ++}; ++ ++struct mipv6_statistics { ++ int n_encapsulations; ++ int n_decapsulations; ++ int n_mh_in_msg; ++ int n_mh_in_error; ++ int n_mh_out_msg; ++ int n_mh_out_error; ++ ++ int n_brr_rcvd; ++ int n_hoti_rcvd; ++ int n_coti_rcvd; ++ int n_hot_rcvd; ++ int n_cot_rcvd; ++ int n_bu_rcvd; ++ int n_ba_rcvd; ++ int n_ban_rcvd; ++ int n_be_rcvd; ++ ++ int n_brr_sent; ++ int n_hoti_sent; ++ int n_coti_sent; ++ int n_hot_sent; ++ int n_cot_sent; ++ int n_bu_sent; ++ int n_ba_sent; ++ int n_ban_sent; ++ int n_be_sent; ++ ++ int n_ha_rcvd; ++ int n_ha_sent; ++ ++ struct mipv6_drop n_bu_drop; ++ struct mipv6_drop n_ba_drop; ++ struct mipv6_drop n_brr_drop; ++ struct mipv6_drop n_be_drop; ++ struct mipv6_drop n_ha_drop; ++}; ++ ++extern struct mipv6_statistics mipv6_stats; ++ ++#ifdef CONFIG_SMP ++/* atomic_t is max 24 bits long */ ++#define MIPV6_INC_STATS(X) atomic_inc((atomic_t *)&mipv6_stats.X); ++#else ++#define MIPV6_INC_STATS(X) mipv6_stats.X++; ++#endif ++ ++int mipv6_stats_init(void); ++void mipv6_stats_exit(void); ++ ++#endif +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel.h linux-2.4.25/net/ipv6/mobile_ip6/tunnel.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/tunnel.h 2004-06-26 11:29:32.000000000 +0100 +@@ -0,0 +1,35 @@ ++/* ++ * MIPL Mobile IPv6 IP6-IP6 tunneling header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _TUNNEL_H ++#define _TUNNEL_H ++ ++#include ++#include ++#include ++ ++static __inline__ int is_mip6_tnl(struct ip6_tnl *t) ++{ ++ return (t != NULL && ++ t->parms.flags & IP6_TNL_F_KERNEL_DEV && ++ t->parms.flags & IP6_TNL_F_MIP6_DEV); ++ ++} ++ ++static __inline__ int dev_is_mip6_tnl(struct net_device *dev) ++{ ++ struct ip6_tnl *t = (struct ip6_tnl *)dev->priv; ++ return (dev->type == ARPHRD_TUNNEL6 && is_mip6_tnl(t)); ++} ++ ++ ++#endif ++ +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_ha.c linux-2.4.25/net/ipv6/mobile_ip6/tunnel_ha.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_ha.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/tunnel_ha.c 2004-06-26 11:29:32.000000000 +0100 +@@ -0,0 +1,264 @@ ++/* ++ * IPv6-IPv6 tunneling module ++ * ++ * Authors: ++ * Sami Kivisaari ++ * Ville Nuorvala ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef CONFIG_SYSCTL ++#include ++#endif /* CONFIG_SYSCTL */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "tunnel.h" ++#include "debug.h" ++#include "stats.h" ++#include "config.h" ++ ++#define MIPV6_TNL_MAX IP6_TNL_MAX ++#define MIPV6_TNL_MIN 1 ++ ++int mipv6_max_tnls = 3; ++int mipv6_min_tnls = 1; ++ ++DECLARE_MUTEX(tnl_sem); ++ ++int mipv6_max_tnls_sysctl(ctl_table *ctl, int write, struct file *filp, ++ void *buffer, size_t *lenp) ++{ ++ int err; ++ ++ DEBUG_FUNC(); ++ ++ down(&tnl_sem); ++ if (write) { ++ int diff; ++ int old_max_tnls = mipv6_max_tnls; ++ err = proc_dointvec(ctl, write, filp, buffer, lenp); ++ if (err < 0) ++ goto out; ++ if (mipv6_max_tnls < mipv6_min_tnls || ++ mipv6_max_tnls > MIPV6_TNL_MAX) { ++ mipv6_max_tnls = old_max_tnls; ++ goto out; ++ } ++ if (mipv6_max_tnls < old_max_tnls) { ++ diff = old_max_tnls - mipv6_max_tnls; ++ ip6ip6_tnl_dec_max_kdev_count(diff); ++ } else if (mipv6_max_tnls > old_max_tnls) { ++ diff = mipv6_max_tnls - old_max_tnls; ++ ip6ip6_tnl_inc_max_kdev_count(diff); ++ } ++ } else { ++ err = proc_dointvec(ctl, write, filp, buffer, lenp); ++ } ++out: ++ up(&tnl_sem); ++ return err; ++} ++ ++int mipv6_min_tnls_sysctl(ctl_table *ctl, int write, struct file *filp, ++ void *buffer, size_t *lenp) ++{ ++ int err; ++ ++ DEBUG_FUNC(); ++ ++ down(&tnl_sem); ++ if (write) { ++ int diff; ++ int old_min_tnls = mipv6_min_tnls; ++ err = proc_dointvec(ctl, write, filp, buffer, lenp); ++ if (err < 0) ++ goto out; ++ if (mipv6_min_tnls > mipv6_max_tnls || ++ mipv6_min_tnls < MIPV6_TNL_MIN) { ++ mipv6_min_tnls = old_min_tnls; ++ goto out; ++ } ++ if (mipv6_min_tnls < old_min_tnls) { ++ diff = old_min_tnls - mipv6_min_tnls; ++ ip6ip6_tnl_dec_min_kdev_count(diff); ++ } else if (mipv6_min_tnls > old_min_tnls) { ++ diff = mipv6_min_tnls - old_min_tnls; ++ ip6ip6_tnl_inc_min_kdev_count(diff); ++ } ++ } else { ++ err = proc_dointvec(ctl, write, filp, buffer, lenp); ++ } ++out: ++ up(&tnl_sem); ++ return err; ++} ++ ++static __inline__ int mipv6_tnl_add(struct in6_addr *remote, ++ struct in6_addr *local) ++{ ++ struct ip6_tnl_parm p; ++ int ret; ++ ++ DEBUG_FUNC(); ++ ++ memset(&p, 0, sizeof(p)); ++ p.proto = IPPROTO_IPV6; ++ ipv6_addr_copy(&p.laddr, local); ++ ipv6_addr_copy(&p.raddr, remote); ++ p.hop_limit = 255; ++ p.flags = (IP6_TNL_F_KERNEL_DEV | IP6_TNL_F_MIP6_DEV | ++ IP6_TNL_F_IGN_ENCAP_LIMIT); ++ ++ ret = ip6ip6_kernel_tnl_add(&p); ++ if (ret > 0) { ++ DEBUG(DBG_INFO, "added tunnel from: " ++ "%x:%x:%x:%x:%x:%x:%x:%x to: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(local), NIPV6ADDR(remote)); ++ } else { ++ DEBUG(DBG_WARNING, "unable to add tunnel from: " ++ "%x:%x:%x:%x:%x:%x:%x:%x to: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(local), NIPV6ADDR(remote)); ++ } ++ return ret; ++} ++ ++static __inline__ int mipv6_tnl_del(struct in6_addr *remote, ++ struct in6_addr *local) ++{ ++ struct ip6_tnl *t = ip6ip6_tnl_lookup(remote, local); ++ ++ DEBUG_FUNC(); ++ ++ if (t != NULL && (t->parms.flags & IP6_TNL_F_MIP6_DEV)) { ++ DEBUG(DBG_INFO, "deleting tunnel from: " ++ "%x:%x:%x:%x:%x:%x:%x:%x to: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(local), NIPV6ADDR(remote)); ++ ++ return ip6ip6_kernel_tnl_del(t); ++ } ++ return 0; ++} ++ ++static int add_route_to_mn(struct in6_addr *coa, struct in6_addr *ha_addr, ++ struct in6_addr *home_addr) ++{ ++ struct in6_rtmsg rtmsg; ++ int err; ++ struct ip6_tnl *t = ip6ip6_tnl_lookup(coa, ha_addr); ++ ++ if (!is_mip6_tnl(t)) { ++ DEBUG(DBG_CRITICAL,"Tunnel missing"); ++ return -ENODEV; ++ } ++ ++ DEBUG(DBG_INFO, "adding route to: %x:%x:%x:%x:%x:%x:%x:%x via " ++ "tunnel device", NIPV6ADDR(home_addr)); ++ ++ memset(&rtmsg, 0, sizeof(rtmsg)); ++ ipv6_addr_copy(&rtmsg.rtmsg_dst, home_addr); ++ rtmsg.rtmsg_dst_len = 128; ++ rtmsg.rtmsg_type = RTMSG_NEWROUTE; ++ rtmsg.rtmsg_flags = RTF_UP | RTF_NONEXTHOP | RTF_HOST | RTF_MOBILENODE; ++ rtmsg.rtmsg_ifindex = t->dev->ifindex; ++ rtmsg.rtmsg_metric = IP6_RT_PRIO_MIPV6; ++ if ((err = ip6_route_add(&rtmsg, NULL)) == -EEXIST) { ++ err = 0; ++ } ++ return err; ++} ++ ++static void del_route_to_mn(struct in6_addr *coa, struct in6_addr *ha_addr, ++ struct in6_addr *home_addr) ++{ ++ struct ip6_tnl *t = ip6ip6_tnl_lookup(coa, ha_addr); ++ ++ DEBUG_FUNC(); ++ ++ if (is_mip6_tnl(t)) { ++ struct in6_rtmsg rtmsg; ++ ++ DEBUG(DBG_INFO, "deleting route to: %x:%x:%x:%x:%x:%x:%x:%x " ++ " via tunnel device", NIPV6ADDR(home_addr)); ++ ++ memset(&rtmsg, 0, sizeof(rtmsg)); ++ ipv6_addr_copy(&rtmsg.rtmsg_dst, home_addr); ++ rtmsg.rtmsg_dst_len = 128; ++ rtmsg.rtmsg_ifindex = t->dev->ifindex; ++ rtmsg.rtmsg_metric = IP6_RT_PRIO_MIPV6; ++ ip6_route_del(&rtmsg, NULL); ++ } ++} ++ ++ ++int mipv6_add_tnl_to_mn(struct in6_addr *coa, ++ struct in6_addr *ha_addr, ++ struct in6_addr *home_addr) ++{ ++ int ret; ++ ++ DEBUG_FUNC(); ++ ++ ret = mipv6_tnl_add(coa, ha_addr); ++ ++ if (ret > 0) { ++ int err = add_route_to_mn(coa, ha_addr, home_addr); ++ if (err) { ++ if (err != -ENODEV) { ++ mipv6_tnl_del(coa, ha_addr); ++ } ++ return err; ++ } ++ } ++ return ret; ++} ++ ++int mipv6_del_tnl_to_mn(struct in6_addr *coa, ++ struct in6_addr *ha_addr, ++ struct in6_addr *home_addr) ++{ ++ DEBUG_FUNC(); ++ del_route_to_mn(coa, ha_addr, home_addr); ++ return mipv6_tnl_del(coa, ha_addr); ++} ++ ++__init void mipv6_initialize_tunnel(void) ++{ ++ down(&tnl_sem); ++ ip6ip6_tnl_inc_max_kdev_count(mipv6_max_tnls); ++ ip6ip6_tnl_inc_min_kdev_count(mipv6_min_tnls); ++ up(&tnl_sem); ++ mip6_fn.bce_tnl_rt_add = add_route_to_mn; ++ mip6_fn.bce_tnl_rt_del = del_route_to_mn; ++} ++ ++__exit void mipv6_shutdown_tunnel(void) ++{ ++ mip6_fn.bce_tnl_rt_del = NULL; ++ mip6_fn.bce_tnl_rt_add = NULL; ++ down(&tnl_sem); ++ ip6ip6_tnl_dec_min_kdev_count(mipv6_min_tnls); ++ ip6ip6_tnl_dec_max_kdev_count(mipv6_max_tnls); ++ up(&tnl_sem); ++} ++ +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_ha.h linux-2.4.25/net/ipv6/mobile_ip6/tunnel_ha.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_ha.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/tunnel_ha.h 2004-06-26 11:29:32.000000000 +0100 +@@ -0,0 +1,20 @@ ++#ifndef _TUNNEL_HA_H ++#define _TUNNEL_HA_H ++ ++#include "tunnel.h" ++ ++extern int mipv6_max_tnls; ++extern int mipv6_min_tnls; ++ ++extern void mipv6_initialize_tunnel(void); ++extern void mipv6_shutdown_tunnel(void); ++ ++extern int mipv6_add_tnl_to_mn(struct in6_addr *coa, ++ struct in6_addr *ha_addr, ++ struct in6_addr *home_addr); ++ ++extern int mipv6_del_tnl_to_mn(struct in6_addr *coa, ++ struct in6_addr *ha_addr, ++ struct in6_addr *home_addr); ++ ++#endif +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_mn.c linux-2.4.25/net/ipv6/mobile_ip6/tunnel_mn.c +--- linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_mn.c 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/tunnel_mn.c 2004-06-26 11:29:32.000000000 +0100 +@@ -0,0 +1,160 @@ ++/* ++ * IPv6-IPv6 tunneling module ++ * ++ * Authors: ++ * Sami Kivisaari ++ * Ville Nuorvala ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef CONFIG_SYSCTL ++#include ++#endif /* CONFIG_SYSCTL */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "tunnel.h" ++#include "debug.h" ++#include "stats.h" ++ ++static struct net_device *mn_ha_tdev; ++ ++static spinlock_t mn_ha_lock = SPIN_LOCK_UNLOCKED; ++ ++static __inline__ int add_reverse_route(struct in6_addr *ha_addr, ++ struct in6_addr *home_addr, ++ struct net_device *tdev) ++{ ++ struct in6_rtmsg rtmsg; ++ int err; ++ ++ DEBUG_FUNC(); ++ ++ memset(&rtmsg, 0, sizeof(rtmsg)); ++ rtmsg.rtmsg_type = RTMSG_NEWROUTE; ++ ipv6_addr_copy(&rtmsg.rtmsg_src, home_addr); ++ rtmsg.rtmsg_src_len = 128; ++ rtmsg.rtmsg_flags = RTF_UP | RTF_DEFAULT; ++ rtmsg.rtmsg_ifindex = tdev->ifindex; ++ rtmsg.rtmsg_metric = IP6_RT_PRIO_MIPV6; ++ if ((err = ip6_route_add(&rtmsg, NULL)) == -EEXIST) { ++ return 0; ++ } ++ return err; ++} ++ ++static __inline__ void del_reverse_route(struct in6_addr *ha_addr, ++ struct in6_addr *home_addr, ++ struct net_device *tdev) ++{ ++ struct in6_rtmsg rtmsg; ++ ++ DEBUG(DBG_INFO, "removing reverse route via tunnel device"); ++ ++ memset(&rtmsg, 0, sizeof(rtmsg)); ++ ipv6_addr_copy(&rtmsg.rtmsg_src, home_addr); ++ rtmsg.rtmsg_src_len = 128; ++ rtmsg.rtmsg_ifindex = tdev->ifindex; ++ rtmsg.rtmsg_metric = IP6_RT_PRIO_MIPV6; ++ ip6_route_del(&rtmsg, NULL); ++} ++ ++int mipv6_add_tnl_to_ha(void) ++{ ++ struct ip6_tnl_parm p; ++ struct ip6_tnl *t; ++ int err; ++ ++ DEBUG_FUNC(); ++ ++ memset(&p, 0, sizeof(p)); ++ p.proto = IPPROTO_IPV6; ++ p.hop_limit = 255; ++ p.flags = (IP6_TNL_F_KERNEL_DEV | IP6_TNL_F_MIP6_DEV | ++ IP6_TNL_F_IGN_ENCAP_LIMIT); ++ strcpy(p.name, "mip6mnha1"); ++ ++ rtnl_lock(); ++ if ((err = ip6ip6_tnl_create(&p, &t))) { ++ rtnl_unlock(); ++ return err; ++ } ++ spin_lock_bh(&mn_ha_lock); ++ ++ if (!mn_ha_tdev) { ++ mn_ha_tdev = t->dev; ++ dev_hold(mn_ha_tdev); ++ } ++ spin_unlock_bh(&mn_ha_lock); ++ dev_open(t->dev); ++ rtnl_unlock(); ++ return 0; ++} ++ ++int mipv6_mv_tnl_to_ha(struct in6_addr *ha_addr, ++ struct in6_addr *coa, ++ struct in6_addr *home_addr) ++{ ++ int err = -ENODEV; ++ ++ DEBUG_FUNC(); ++ ++ spin_lock_bh(&mn_ha_lock); ++ if (mn_ha_tdev) { ++ struct ip6_tnl_parm p; ++ memset(&p, 0, sizeof(p)); ++ p.proto = IPPROTO_IPV6; ++ ipv6_addr_copy(&p.laddr, coa); ++ ipv6_addr_copy(&p.raddr, ha_addr); ++ p.hop_limit = 255; ++ p.flags = (IP6_TNL_F_KERNEL_DEV | IP6_TNL_F_MIP6_DEV | ++ IP6_TNL_F_IGN_ENCAP_LIMIT); ++ ++ ip6ip6_tnl_change((struct ip6_tnl *) mn_ha_tdev->priv, &p); ++ if (ipv6_addr_cmp(coa, home_addr)) { ++ err = add_reverse_route(ha_addr, home_addr, ++ mn_ha_tdev); ++ } else { ++ del_reverse_route(ha_addr, home_addr, mn_ha_tdev); ++ err = 0; ++ } ++ } ++ spin_unlock_bh(&mn_ha_lock); ++ return err; ++} ++ ++void mipv6_del_tnl_to_ha(void) ++{ ++ struct net_device *dev; ++ ++ DEBUG_FUNC(); ++ ++ rtnl_lock(); ++ spin_lock_bh(&mn_ha_lock); ++ dev = mn_ha_tdev; ++ mn_ha_tdev = NULL; ++ spin_unlock_bh(&mn_ha_lock); ++ dev_put(dev); ++ unregister_netdevice(dev); ++ rtnl_unlock(); ++} +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_mn.h linux-2.4.25/net/ipv6/mobile_ip6/tunnel_mn.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_mn.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/tunnel_mn.h 2004-06-26 11:29:32.000000000 +0100 +@@ -0,0 +1,14 @@ ++#ifndef _TUNNEL_MN_H ++#define _TUNNEL_MN_H ++ ++#include "tunnel.h" ++ ++extern int mipv6_add_tnl_to_ha(void); ++ ++extern int mipv6_mv_tnl_to_ha(struct in6_addr *ha_addr, ++ struct in6_addr *coa, ++ struct in6_addr *home_addr); ++ ++extern int mipv6_del_tnl_to_ha(void); ++ ++#endif +diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/util.h linux-2.4.25/net/ipv6/mobile_ip6/util.h +--- linux-2.4.25.old/net/ipv6/mobile_ip6/util.h 1970-01-01 01:00:00.000000000 +0100 ++++ linux-2.4.25/net/ipv6/mobile_ip6/util.h 2004-06-26 11:29:32.000000000 +0100 +@@ -0,0 +1,91 @@ ++/* ++ * MIPL Mobile IPv6 Utility functions ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _UTIL_H ++#define _UTIL_H ++ ++#include ++#include ++ ++/** ++ * mipv6_prefix_compare - Compare two IPv6 prefixes ++ * @addr: IPv6 address ++ * @prefix: IPv6 address ++ * @nprefix: number of bits to compare ++ * ++ * Perform prefix comparison bitwise for the @nprefix first bits ++ * Returns 1, if the prefixes are the same, 0 otherwise ++ **/ ++static inline int mipv6_prefix_compare(const struct in6_addr *addr, ++ const struct in6_addr *prefix, ++ const unsigned int pfix_len) ++{ ++ int i; ++ unsigned int nprefix = pfix_len; ++ ++ if (nprefix > 128) ++ return 0; ++ ++ for (i = 0; nprefix > 0; nprefix -= 32, i++) { ++ if (nprefix >= 32) { ++ if (addr->s6_addr32[i] != prefix->s6_addr32[i]) ++ return 0; ++ } else { ++ if (((addr->s6_addr32[i] ^ prefix->s6_addr32[i]) & ++ ((~0) << (32 - nprefix))) != 0) ++ return 0; ++ return 1; ++ } ++ } ++ ++ return 1; ++} ++ ++/** ++ * homeagent_anycast - Compute Home Agent anycast address ++ * @ac_addr: append home agent anycast suffix to passed prefix ++ * @prefix: prefix ha anycast address is generated from ++ * @plen: length of prefix in bits ++ * ++ * Calculate corresponding Home Agent Anycast Address (RFC2526) in a ++ * given subnet. ++ */ ++static inline int ++mipv6_ha_anycast(struct in6_addr *ac_addr, struct in6_addr *prefix, int plen) ++{ ++ if (plen <= 0 || plen > 120) { ++ /* error, interface id should be minimum 8 bits */ ++ return -1; ++ } ++ ipv6_addr_copy(ac_addr, prefix); ++ ++ if (plen < 32) ++ ac_addr->s6_addr32[0] |= htonl((u32)(~0) >> plen); ++ if (plen < 64) ++ ac_addr->s6_addr32[1] |= htonl((u32)(~0) >> (plen > 32 ? plen % 32 : 0)); ++ if (plen < 92) ++ ac_addr->s6_addr32[2] |= htonl((u32)(~0) >> (plen > 64 ? plen % 32 : 0)); ++ if (plen <= 120) ++ ac_addr->s6_addr32[3] |= htonl((u32)(~0) >> (plen > 92 ? plen % 32 : 0)); ++ ++ /* RFC2526: for interface identifiers in EUI-64 ++ * format, the universal/local bit in the interface ++ * identifier MUST be set to 0. */ ++ if (plen == 64) { ++ ac_addr->s6_addr32[2] &= (int)htonl(0xfdffffff); ++ } ++ /* Mobile IPv6 Home-Agents anycast id (0x7e) */ ++ ac_addr->s6_addr32[3] &= (int)htonl(0xfffffffe); ++ ++ return 0; ++} ++ ++#endif /* _UTIL_H */ +diff -uprN linux-2.4.25.old/net/ipv6/ndisc.c linux-2.4.25/net/ipv6/ndisc.c +--- linux-2.4.25.old/net/ipv6/ndisc.c 2003-11-28 18:26:21.000000000 +0000 ++++ linux-2.4.25/net/ipv6/ndisc.c 2004-06-26 11:29:32.000000000 +0100 +@@ -23,6 +23,7 @@ + * and moved to net/core. + * Pekka Savola : RFC2461 validation + * YOSHIFUJI Hideaki @USAGI : Verify ND options properly ++ * Ville Nuorvala : RFC2461 fixes to proxy ND + */ + + /* Set to 3 to get tracing... */ +@@ -70,6 +71,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -187,6 +189,8 @@ struct ndisc_options *ndisc_parse_option + case ND_OPT_TARGET_LL_ADDR: + case ND_OPT_MTU: + case ND_OPT_REDIRECT_HDR: ++ case ND_OPT_RTR_ADV_INTERVAL: ++ case ND_OPT_HOME_AGENT_INFO: + if (ndopts->nd_opt_array[nd_opt->nd_opt_type]) { + ND_PRINTK2((KERN_WARNING + "ndisc_parse_options(): duplicated ND6 option found: type=%d\n", +@@ -372,8 +376,8 @@ ndisc_build_ll_hdr(struct sk_buff *skb, + */ + + void ndisc_send_na(struct net_device *dev, struct neighbour *neigh, +- struct in6_addr *daddr, struct in6_addr *solicited_addr, +- int router, int solicited, int override, int inc_opt) ++ struct in6_addr *daddr, struct in6_addr *solicited_addr, ++ int router, int solicited, int override, int inc_opt) + { + static struct in6_addr tmpaddr; + struct inet6_ifaddr *ifp; +@@ -766,7 +770,8 @@ void ndisc_recv_ns(struct sk_buff *skb) + int addr_type = ipv6_addr_type(saddr); + + if (in6_dev && in6_dev->cnf.forwarding && +- (addr_type & IPV6_ADDR_UNICAST) && ++ (addr_type & IPV6_ADDR_UNICAST || ++ addr_type == IPV6_ADDR_ANY) && + pneigh_lookup(&nd_tbl, &msg->target, dev, 0)) { + int inc = ipv6_addr_type(daddr)&IPV6_ADDR_MULTICAST; + +@@ -778,13 +783,21 @@ void ndisc_recv_ns(struct sk_buff *skb) + nd_tbl.stats.rcv_probes_mcast++; + else + nd_tbl.stats.rcv_probes_ucast++; +- +- neigh = neigh_event_ns(&nd_tbl, lladdr, saddr, dev); + +- if (neigh) { +- ndisc_send_na(dev, neigh, saddr, &msg->target, +- 0, 1, 0, 1); +- neigh_release(neigh); ++ if (addr_type & IPV6_ADDR_UNICAST) { ++ neigh = neigh_event_ns(&nd_tbl, lladdr, saddr, dev); ++ ++ if (neigh) { ++ ndisc_send_na(dev, neigh, saddr, &msg->target, ++ 0, 1, 0, 1); ++ neigh_release(neigh); ++ } ++ } else { ++ /* the proxy should also protect against DAD */ ++ struct in6_addr maddr; ++ ipv6_addr_all_nodes(&maddr); ++ ndisc_send_na(dev, NULL, &maddr, &msg->target, ++ 0, 0, 0, 1); + } + } else { + struct sk_buff *n = skb_clone(skb, GFP_ATOMIC); +@@ -849,6 +862,9 @@ void ndisc_recv_na(struct sk_buff *skb) + if (ifp->flags & IFA_F_TENTATIVE) { + addrconf_dad_failure(ifp); + return; ++ } else if (ndisc_mip_mn_ha_probe(ifp, lladdr)) { ++ in6_ifa_put(ifp); ++ return; + } + /* What should we make now? The advertisement + is invalid, but ndisc specs say nothing +@@ -887,6 +903,7 @@ void ndisc_recv_na(struct sk_buff *skb) + msg->icmph.icmp6_override, 1); + neigh_release(neigh); + } ++ ndisc_check_mipv6_dad(&msg->target); + } + + static void ndisc_router_discovery(struct sk_buff *skb) +@@ -894,6 +911,7 @@ static void ndisc_router_discovery(struc + struct ra_msg *ra_msg = (struct ra_msg *) skb->h.raw; + struct neighbour *neigh; + struct inet6_dev *in6_dev; ++ int change_rtr; + struct rt6_info *rt; + int lifetime; + struct ndisc_options ndopts; +@@ -923,10 +941,6 @@ static void ndisc_router_discovery(struc + ND_PRINTK1("RA: can't find in6 device\n"); + return; + } +- if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_ra) { +- in6_dev_put(in6_dev); +- return; +- } + + if (!ndisc_parse_options(opt, optlen, &ndopts)) { + in6_dev_put(in6_dev); +@@ -935,7 +949,12 @@ static void ndisc_router_discovery(struc + "ICMP6 RA: invalid ND option, ignored.\n"); + return; + } ++ change_rtr = ndisc_mipv6_ra_rcv(skb, &ndopts); + ++ if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_ra) { ++ in6_dev_put(in6_dev); ++ return; ++ } + if (in6_dev->if_flags & IF_RS_SENT) { + /* + * flag that an RA was received after an RS was sent +@@ -963,8 +982,7 @@ static void ndisc_router_discovery(struc + ip6_del_rt(rt, NULL); + rt = NULL; + } +- +- if (rt == NULL && lifetime) { ++ if (rt == NULL && lifetime && change_rtr) { + ND_PRINTK2("ndisc_rdisc: adding default router\n"); + + rt = rt6_add_dflt_router(&skb->nh.ipv6h->saddr, skb->dev); +@@ -1087,6 +1105,8 @@ out: + if (rt) + dst_release(&rt->u.dst); + in6_dev_put(in6_dev); ++ ++ ndisc_mipv6_change_router(change_rtr); + } + + static void ndisc_redirect_rcv(struct sk_buff *skb) +diff -uprN linux-2.4.25.old/net/ipv6/raw.c linux-2.4.25/net/ipv6/raw.c +--- linux-2.4.25.old/net/ipv6/raw.c 2003-11-28 18:26:21.000000000 +0000 ++++ linux-2.4.25/net/ipv6/raw.c 2004-06-26 11:29:32.000000000 +0100 +@@ -43,6 +43,7 @@ + #include + #include + #include ++#include + + #include + +@@ -636,6 +637,7 @@ static int rawv6_sendmsg(struct sock *sk + hdr.daddr = daddr; + else + hdr.daddr = NULL; ++ hdr.daddr = mipv6_get_fake_hdr_daddr(hdr.daddr, daddr); + + err = ip6_build_xmit(sk, rawv6_frag_cksum, &hdr, &fl, len, + opt, hlimit, msg->msg_flags); +diff -uprN linux-2.4.25.old/net/ipv6/route.c linux-2.4.25/net/ipv6/route.c +--- linux-2.4.25.old/net/ipv6/route.c 2004-02-18 13:36:32.000000000 +0000 ++++ linux-2.4.25/net/ipv6/route.c 2004-06-26 11:29:32.000000000 +0100 +@@ -49,6 +49,7 @@ + #include + #include + #include ++#include + + #include + +@@ -363,12 +364,8 @@ static struct rt6_info *rt6_cow(struct r + rt->u.dst.flags |= DST_HOST; + + #ifdef CONFIG_IPV6_SUBTREES +- if (rt->rt6i_src.plen && saddr) { +- ipv6_addr_copy(&rt->rt6i_src.addr, saddr); +- rt->rt6i_src.plen = 128; +- } ++ rt->rt6i_src.plen = ort->rt6i_src.plen; + #endif +- + rt->rt6i_nexthop = ndisc_get_neigh(rt->rt6i_dev, &rt->rt6i_gateway); + + dst_hold(&rt->u.dst); +@@ -511,14 +508,19 @@ struct dst_entry * ip6_route_output(stru + struct rt6_info *rt; + int strict; + int attempts = 3; ++ struct in6_addr *saddr; + ++ if (ipv6_chk_addr(fl->nl_u.ip6_u.daddr, NULL)) ++ saddr = NULL; ++ else ++ saddr = fl->nl_u.ip6_u.saddr; ++ + strict = ipv6_addr_type(fl->nl_u.ip6_u.daddr) & (IPV6_ADDR_MULTICAST|IPV6_ADDR_LINKLOCAL); + + relookup: + read_lock_bh(&rt6_lock); + +- fn = fib6_lookup(&ip6_routing_table, fl->nl_u.ip6_u.daddr, +- fl->nl_u.ip6_u.saddr); ++ fn = fib6_lookup(&ip6_routing_table, fl->nl_u.ip6_u.daddr, saddr); + + restart: + rt = fn->leaf; +@@ -663,25 +665,6 @@ out: + return (atomic_read(&ip6_dst_ops.entries) > ip6_rt_max_size); + } + +-/* Clean host part of a prefix. Not necessary in radix tree, +- but results in cleaner routing tables. +- +- Remove it only when all the things will work! +- */ +- +-static void ipv6_addr_prefix(struct in6_addr *pfx, +- const struct in6_addr *addr, int plen) +-{ +- int b = plen&0x7; +- int o = plen>>3; +- +- memcpy(pfx->s6_addr, addr, o); +- if (o < 16) +- memset(pfx->s6_addr + o, 0, 16 - o); +- if (b != 0) +- pfx->s6_addr[o] = addr->s6_addr[o]&(0xff00 >> b); +-} +- + static int ipv6_get_mtu(struct net_device *dev) + { + int mtu = IPV6_MIN_MTU; +@@ -810,7 +793,7 @@ int ip6_route_add(struct in6_rtmsg *rtms + if (!(gwa_type&IPV6_ADDR_UNICAST)) + goto out; + +- grt = rt6_lookup(gw_addr, NULL, rtmsg->rtmsg_ifindex, 1); ++ grt = rt6_lookup(gw_addr, &rtmsg->rtmsg_src, rtmsg->rtmsg_ifindex, 1); + + err = -EHOSTUNREACH; + if (grt == NULL) +@@ -848,7 +831,15 @@ int ip6_route_add(struct in6_rtmsg *rtms + goto out; + } + } +- ++#ifdef USE_IPV6_MOBILITY ++ /* If destination is mobile node, add special skb->dst->input ++ * function for proxy ND. ++ */ ++ if (rtmsg->rtmsg_flags & RTF_MOBILENODE) { ++ rt->u.dst.input = ip6_mipv6_forward; ++ } ++#endif /* CONFIG_IPV6_MOBILITY */ ++ + if (ipv6_addr_is_multicast(&rt->rt6i_dst.addr)) + rt->rt6i_hoplimit = IPV6_DEFAULT_MCASTHOPS; + else +@@ -936,7 +927,7 @@ void rt6_redirect(struct in6_addr *dest, + struct rt6_info *rt, *nrt; + + /* Locate old route to this destination. */ +- rt = rt6_lookup(dest, NULL, neigh->dev->ifindex, 1); ++ rt = rt6_lookup(dest, saddr, neigh->dev->ifindex, 1); + + if (rt == NULL) + return; +@@ -1003,6 +994,9 @@ source_ok: + nrt = ip6_rt_copy(rt); + if (nrt == NULL) + goto out; ++#ifdef CONFIG_IPV6_SUBTREES ++ nrt->rt6i_src.plen = rt->rt6i_src.plen; ++#endif + + nrt->rt6i_flags = RTF_GATEWAY|RTF_UP|RTF_DYNAMIC|RTF_CACHE; + if (on_link) +@@ -1104,6 +1098,9 @@ void rt6_pmtu_discovery(struct in6_addr + nrt = ip6_rt_copy(rt); + if (nrt == NULL) + goto out; ++#ifdef CONFIG_IPV6_SUBTREES ++ nrt->rt6i_src.plen = rt->rt6i_src.plen; ++#endif + ipv6_addr_copy(&nrt->rt6i_dst.addr, daddr); + nrt->rt6i_dst.plen = 128; + nrt->u.dst.flags |= DST_HOST; +diff -uprN linux-2.4.25.old/net/ipv6/tcp_ipv6.c linux-2.4.25/net/ipv6/tcp_ipv6.c +--- linux-2.4.25.old/net/ipv6/tcp_ipv6.c 2003-11-28 18:26:21.000000000 +0000 ++++ linux-2.4.25/net/ipv6/tcp_ipv6.c 2004-06-26 11:29:32.000000000 +0100 +@@ -50,6 +50,7 @@ + #include + #include + #include ++#include + + #include + +@@ -557,6 +558,7 @@ static int tcp_v6_connect(struct sock *s + struct flowi fl; + struct dst_entry *dst; + int addr_type; ++ int reroute = 0; + int err; + + if (addr_len < SIN6_LEN_RFC2133) +@@ -660,7 +662,7 @@ static int tcp_v6_connect(struct sock *s + + fl.proto = IPPROTO_TCP; + fl.fl6_dst = &np->daddr; +- fl.fl6_src = saddr; ++ fl.fl6_src = saddr; + fl.oif = sk->bound_dev_if; + fl.uli_u.ports.dport = usin->sin6_port; + fl.uli_u.ports.sport = sk->sport; +@@ -669,31 +671,46 @@ static int tcp_v6_connect(struct sock *s + struct rt0_hdr *rt0 = (struct rt0_hdr *) np->opt->srcrt; + fl.nl_u.ip6_u.daddr = rt0->addr; + } +- + dst = ip6_route_output(sk, &fl); +- ++#ifdef CONFIG_IPV6_SUBTREES ++ reroute = (saddr == NULL); ++#endif + if ((err = dst->error) != 0) { + dst_release(dst); + goto failure; + } +- +- ip6_dst_store(sk, dst, NULL); +- sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM; +- ++ if (!reroute) { ++ ip6_dst_store(sk, dst, NULL, NULL); ++ sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM; ++ } + if (saddr == NULL) { + err = ipv6_get_saddr(dst, &np->daddr, &saddr_buf); ++ ++ if (reroute) ++ dst_release(dst); + if (err) + goto failure; + + saddr = &saddr_buf; ++ ipv6_addr_copy(&np->rcv_saddr, saddr); ++#ifdef CONFIG_IPV6_SUBTREES ++ fl.fl6_src = saddr; ++ dst = ip6_route_output(sk, &fl); ++ ++ if ((err = dst->error) != 0) { ++ dst_release(dst); ++ goto failure; ++ } ++ ip6_dst_store(sk, dst, NULL, NULL); ++ sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM; ++#endif + } + + /* set the source address */ +- ipv6_addr_copy(&np->rcv_saddr, saddr); + ipv6_addr_copy(&np->saddr, saddr); + sk->rcv_saddr= LOOPBACK4_IPV6; + +- tp->ext_header_len = 0; ++ tp->ext_header_len = tcp_v6_get_mipv6_header_len(); + if (np->opt) + tp->ext_header_len = np->opt->opt_flen+np->opt->opt_nflen; + tp->mss_clamp = IPV6_MIN_MTU - sizeof(struct tcphdr) - sizeof(struct ipv6hdr); +@@ -1338,7 +1355,7 @@ static struct sock * tcp_v6_syn_recv_soc + #endif + MOD_INC_USE_COUNT; + +- ip6_dst_store(newsk, dst, NULL); ++ ip6_dst_store(newsk, dst, NULL, NULL); + sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM; + + newtp = &(newsk->tp_pinfo.af_tcp); +@@ -1383,7 +1400,7 @@ static struct sock * tcp_v6_syn_recv_soc + sock_kfree_s(sk, opt, opt->tot_len); + } + +- newtp->ext_header_len = 0; ++ newtp->ext_header_len = tcp_v6_get_mipv6_header_len(); + if (np->opt) + newtp->ext_header_len = np->opt->opt_nflen + np->opt->opt_flen; + +@@ -1710,7 +1727,7 @@ static int tcp_v6_rebuild_header(struct + return err; + } + +- ip6_dst_store(sk, dst, NULL); ++ ip6_dst_store(sk, dst, NULL, NULL); + sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM; + } + +@@ -1749,7 +1766,7 @@ static int tcp_v6_xmit(struct sk_buff *s + return -sk->err_soft; + } + +- ip6_dst_store(sk, dst, NULL); ++ ip6_dst_store(sk, dst, NULL, NULL); + } + + skb->dst = dst_clone(dst); +diff -uprN linux-2.4.25.old/net/ipv6/udp.c linux-2.4.25/net/ipv6/udp.c +--- linux-2.4.25.old/net/ipv6/udp.c 2004-02-18 13:36:32.000000000 +0000 ++++ linux-2.4.25/net/ipv6/udp.c 2004-06-26 11:29:32.000000000 +0100 +@@ -48,6 +48,7 @@ + #include + #include + #include ++#include + + #include + +@@ -232,6 +233,7 @@ int udpv6_connect(struct sock *sk, struc + struct ip6_flowlabel *flowlabel = NULL; + int addr_type; + int err; ++ int reroute = 0; + + if (usin->sin6_family == AF_INET) { + if (__ipv6_only_sock(sk)) +@@ -331,7 +333,7 @@ ipv4_connected: + + fl.proto = IPPROTO_UDP; + fl.fl6_dst = &np->daddr; +- fl.fl6_src = &saddr; ++ fl.fl6_src = NULL; + fl.oif = sk->bound_dev_if; + fl.uli_u.ports.dport = sk->dport; + fl.uli_u.ports.sport = sk->sport; +@@ -348,29 +350,44 @@ ipv4_connected: + struct rt0_hdr *rt0 = (struct rt0_hdr *) np->opt->srcrt; + fl.fl6_dst = rt0->addr; + } +- + dst = ip6_route_output(sk, &fl); +- + if ((err = dst->error) != 0) { + dst_release(dst); + fl6_sock_release(flowlabel); +- return err; +- } +- +- ip6_dst_store(sk, dst, fl.fl6_dst); +- ++ return err; ++ } ++#ifdef CONFIG_IPV6_SUBTREES ++ reroute = (fl.fl6_src == NULL); ++#endif + /* get the source adddress used in the apropriate device */ + + err = ipv6_get_saddr(dst, daddr, &saddr); + ++ if (reroute) ++ dst_release(dst); ++ + if (err == 0) { +- if(ipv6_addr_any(&np->saddr)) ++#ifdef CONFIG_IPV6_SUBTREES ++ if (reroute) { ++ fl.fl6_src = &saddr; ++ dst = ip6_route_output(sk, &fl); ++ if ((err = dst->error) != 0) { ++ dst_release(dst); ++ fl6_sock_release(flowlabel); ++ return err; ++ } ++ } ++#endif ++ if(ipv6_addr_any(&np->saddr)) { + ipv6_addr_copy(&np->saddr, &saddr); +- ++ fl.fl6_src = &np->saddr; ++ } + if(ipv6_addr_any(&np->rcv_saddr)) { + ipv6_addr_copy(&np->rcv_saddr, &saddr); + sk->rcv_saddr = LOOPBACK4_IPV6; + } ++ ip6_dst_store(sk, dst, fl.fl6_dst, ++ fl.fl6_src == &np->saddr ? fl.fl6_src : NULL); + sk->state = TCP_ESTABLISHED; + } + fl6_sock_release(flowlabel); +@@ -894,6 +911,7 @@ static int udpv6_sendmsg(struct sock *sk + opt = fl6_merge_options(&opt_space, flowlabel, opt); + if (opt && opt->srcrt) + udh.daddr = daddr; ++ udh.daddr = mipv6_get_fake_hdr_daddr(udh.daddr, daddr); + + udh.uh.source = sk->sport; + udh.uh.len = len < 0x10000 ? htons(len) : 0; +diff -uprN linux-2.4.25.old/net/netsyms.c linux-2.4.25/net/netsyms.c +--- linux-2.4.25.old/net/netsyms.c 2003-11-28 18:26:21.000000000 +0000 ++++ linux-2.4.25/net/netsyms.c 2004-06-26 11:29:32.000000000 +0100 +@@ -190,6 +190,7 @@ EXPORT_SYMBOL(neigh_sysctl_register); + #endif + EXPORT_SYMBOL(pneigh_lookup); + EXPORT_SYMBOL(pneigh_enqueue); ++EXPORT_SYMBOL(pneigh_delete); + EXPORT_SYMBOL(neigh_destroy); + EXPORT_SYMBOL(neigh_parms_alloc); + EXPORT_SYMBOL(neigh_parms_release); diff --git a/recipes/linux/files/mipv6-1.1-v2.4.27.patch b/recipes/linux/files/mipv6-1.1-v2.4.27.patch new file mode 100644 index 0000000000..b8fb071d28 --- /dev/null +++ b/recipes/linux/files/mipv6-1.1-v2.4.27.patch @@ -0,0 +1,19736 @@ + +# +# Patch managed by http://www.holgerschurig.de/patcher.html +# + +--- linux-2.4.27/Documentation/Configure.help~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/Documentation/Configure.help +@@ -6308,6 +6308,57 @@ + + It is safe to say N here for now. + ++IPv6: IPv6 over IPv6 Tunneling (EXPERIMENTAL) ++CONFIG_IPV6_TUNNEL ++ Experimental IP6-IP6 tunneling. You must select this, if you want ++ to use CONFIG_IPV6_MOBILITY. More information in MIPL Mobile IPv6 ++ instructions. ++ ++ If you don't want IP6-IP6 tunnels and Mobile IPv6, say N. ++ ++IPv6: Mobility Support (EXPERIMENTAL) ++CONFIG_IPV6_MOBILITY ++ This is experimental support for the upcoming specification of ++ Mobile IPv6. Mobile IPv6 allows nodes to seamlessly move between ++ networks without changing their IP addresses, thus allowing them to ++ maintain upper layer connections (e.g. TCP). Selecting this option ++ allows your computer to act as a Correspondent Node (CN). A MIPv6 ++ Mobile Node will be able to communicate with the CN and use route ++ optimization. ++ ++ For more information and configuration details, see ++ http://www.mipl.mediapoli.com/. ++ ++ If unsure, say N. ++ ++MIPv6: Mobile Node Support ++CONFIG_IPV6_MOBILITY_MN ++ If you want your computer to be a MIPv6 Mobile Node (MN), select ++ this option. You must configure MN using the userspace tools ++ available at http://www.mipl.mediapoli.com/download/mipv6-tools/. ++ ++ If your computer is stationary, or you are unsure if you need this, ++ say N. Note that you will need a properly configured MIPv6 Home ++ Agent to use any Mobile Nodes. ++ ++MIPv6: Home Agent Support ++CONFIG_IPV6_MOBILITY_HA ++ If you want your router to serve as a MIPv6 Home Agent (HA), select ++ this option. You must configure HA using the userspace tools ++ available at http://www.mipl.mediapoli.com/download/mipv6-tools/. ++ ++ If your computer is not a router, or you are unsure if you need ++ this, say N. ++ ++MIPv6: Debug messages ++CONFIG_IPV6_MOBILITY_DEBUG ++ MIPL Mobile IPv6 can produce a lot of debugging messages. There are ++ eight debug levels (0 through 7) and the level is controlled via ++ /proc/sys/net/ipv6/mobility/debuglevel. Since MIPL is still ++ experimental, you might want to say Y here. ++ ++ Be sure to say Y and record debug messages when submitting a bug ++ report. + The SCTP Protocol (EXPERIMENTAL) + CONFIG_IP_SCTP + Stream Control Transmission Protocol +--- linux-2.4.27/Documentation/DocBook/Makefile~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/Documentation/DocBook/Makefile +@@ -2,7 +2,7 @@ + kernel-api.sgml parportbook.sgml kernel-hacking.sgml \ + kernel-locking.sgml via-audio.sgml mousedrivers.sgml sis900.sgml \ + deviceiobook.sgml procfs-guide.sgml tulip-user.sgml \ +- journal-api.sgml libata.sgml ++ journal-api.sgml libata.sgml mip6-func.sgml + + PS := $(patsubst %.sgml, %.ps, $(BOOKS)) + PDF := $(patsubst %.sgml, %.pdf, $(BOOKS)) +@@ -96,6 +96,9 @@ + procfs-guide.sgml: procfs-guide.tmpl procfs_example.sgml + $(TOPDIR)/scripts/docgen < procfs-guide.tmpl >$@ + ++mip6-func.sgml: mip6-func.tmpl ++ $(TOPDIR)/scripts/docgen <$< >$@ ++ + APISOURCES := $(TOPDIR)/drivers/media/video/videodev.c \ + $(TOPDIR)/arch/i386/kernel/irq.c \ + $(TOPDIR)/arch/i386/kernel/mca.c \ +--- /dev/null ++++ linux-2.4.27/Documentation/DocBook/mip6-func.tmpl +@@ -0,0 +1,756 @@ ++ ++ ++ ++ MIPL Mobile IPv6 Function Reference Guide ++ ++ ++ ++ MIPL Mobile IPv6 for Linux Team ++ ++ Helsinki University of Technology ++ Telecommunications Software and Multimedia Lab ++
++ PO BOX 9201 ++ FIN-02015 HUT ++ Finland ++ mipl@list.mipl.mediapoli.com ++
++
++
++
++ ++ ++ 2000-2001 ++ Helsinki University of Technology ++ ++ ++ ++ ++ Copyright (c) 2001, 2002 MIPL Mobile IPv6 for Linux Team. ++ ++ ++ Permission is granted to copy, distribute and/or modify this ++ document under the terms of the GNU Free Documentation License, ++ Version 1.1 published by the Free Software Foundation; with the ++ Invariant Sections being "Introduction", with the Front-Cover ++ Texts being "MIPL Mobile IPv6 Function Reference Guide", "MIPL ++ Mobile IPv6 for Linux Team" and "Helsinki University of ++ Technology". A copy of the license is included in . ++ ++ ++ ++
++ ++ ++ ++ ++ Introduction ++ ++ ++ MIPL Mobile IPv6 for Linux is an implementation of Mobility ++ Support in IPv6 IETF mobile-ip working groups Internet-Draft ++ (draft-ietf-mobileip-ipv6). This implementation has been ++ developed in the Telecommunications Software and Multimedia ++ Laboratory at Helsinki University of Technology. ++ ++ ++ ++ MIPL is fully open source, licensed under the GNU General ++ Public License. Latest source for MIPL can be downloaded from ++ the MIPL website at: ++ ++ ++ http://www.mipl.mediapoli.com/. ++ ++ ++ Developers and users interested in MIPL can subscribe to the ++ MIPL mailing list by sending e-mail to ++ majordomo@list.mipl.mediapoli.com with ++ ++ ++ subscribe mipl ++ ++ ++ in the body of the message. ++ ++ ++ ++ This document is a reference guide to MIPL functions. Intended ++ audience is developers wishing to contribute to the project. ++ Hopefully this document will make it easier and quicker to ++ understand and adopt the inner workings of MIPL Mobile IPv6. ++ ++ ++ ++ MIPL Mobile IPv6 for Linux Team members (past and present): ++ ++ ++ ++
++ Sami Kivisaari Sami.Kivisaari@hut.fi ++
++
++ ++
++ Niklas Kampe Niklas.Kampe@hut.fi ++
++
++ ++
++ Juha Mynttinen Juha.Mynttinen@hut.fi ++
++
++ ++
++ Toni Nykanen Toni.Nykanen@iki.fi ++
++
++ ++
++ Henrik Petander Henrik.Petander@hut.fi ++
++
++ ++
++ Antti Tuominen ajtuomin@tml.hut.fi ++
++
++
++ ++ ++ ++
++ Marko Myllynen ++
++
++ ++
++ Ville Nuorvala vnuorval@tcs.hut.fi ++
++
++ ++
++ Jaakko Laine Jaakko.Laine@hut.fi ++
++
++
++
++ ++
++ ++ ++ Common functions for all entities ++ ++ Low-level functions ++ ++ These functions implement memory allocation used by others. ++ Hashlist functions implement a linked list with hash lookup, ++ which is used with Binding Update List, Binding Cache, Home ++ Agents List etc. ++ ++!Inet/ipv6/mobile_ip6/mempool.h ++!Inet/ipv6/mobile_ip6/hashlist.h ++ ++ ++ Debug functions ++ ++ Debug and utility functions. These functions are available if ++ CONFIG_IPV6_MOBILITY_DEBUG is set. ++ Otherwise macros expand to no operation. ++ ++!Inet/ipv6/mobile_ip6/debug.h ++!Inet/ipv6/mobile_ip6/mipv6.c ++ ++ ++ Extension Header functions ++ ++ These functions create and handle extension headers that are ++ specific to MIPv6. ++ ++!Inet/ipv6/mobile_ip6/exthdrs.c ++ ++ ++ Mobility Header functions ++ ++ MIPv6 specifies a new protocol called Mobility Header. ++ Mobility Header has several message types. Messages may also ++ carry Mobility Options. These functions are used to create and ++ handle Mobility Headers and Mobility Options. ++ ++!Inet/ipv6/mobile_ip6/sendopts.c ++!Inet/ipv6/mobile_ip6/mh_recv.c ++!Inet/ipv6/mobile_ip6/auth_subopt.c ++ ++ ++ Binding Cache ++ ++ All Mobile IPv6 entities have a binding cache. These functions ++ provide easy manipulation of the binding cache. ++ ++!Inet/ipv6/mobile_ip6/bcache.c ++ ++ ++ Security ++ ++ ++ These functions are common authentication functions and ++ implement Draft 13 style IPSec AH support for Binding Updates. ++ ++!Inet/ipv6/mobile_ip6/ah_algo.c ++!Inet/ipv6/mobile_ip6/sadb.c ++!Inet/ipv6/mobile_ip6/ah.c ++ ++ ++ Utility functions ++ ++ ++ These functions are general utility functions commonly used by ++ all entities. ++ ++!Inet/ipv6/mobile_ip6/util.c ++ ++ ++ ++ ++ ++ Mobile Node functions ++ General functions ++ ++ ++!Inet/ipv6/mobile_ip6/mn.c ++ ++ ++ Binding Update List ++ ++ Mobile Node keeps track of sent binding updates in Binding ++ Update List. ++ ++!Inet/ipv6/mobile_ip6/bul.c ++ ++ ++ Movement detection ++ ++ ++ These functions are used by the mobile node for movement ++ detection. ++ ++!Inet/ipv6/mobile_ip6/mdetect.c ++ ++ ++ ++ ++ Home Agent functions ++ General functions ++ ++ ++!Inet/ipv6/mobile_ip6/ha.c ++ ++ ++ Duplicate Address Detection functions ++ ++ Home Agent does Duplicate Address Detection for Mobile Nodes' ++ addresses. These functions implement MIPv6 specific DAD ++ functionality. ++ ++!Inet/ipv6/mobile_ip6/dad.c ++ ++ ++ ++ ++ GNU Free Documentation License ++ ++ ++ Version 1.1, March 2000 ++ ++ ++ ++ Copyright (C) 2000 Free Software Foundation, Inc. ++ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ Everyone is permitted to copy and distribute verbatim copies ++ of this license document, but changing it is not allowed. ++ ++ ++ 0. PREAMBLE ++ ++ ++ The purpose of this License is to make a manual, textbook, or ++ other written document "free" in the sense of freedom: to ++ assure everyone the effective freedom to copy and redistribute ++ it, with or without modifying it, either commercially or ++ noncommercially. Secondarily, this License preserves for the ++ author and publisher a way to get credit for their work, while ++ not being considered responsible for modifications made by ++ others. ++ ++ ++ ++ This License is a kind of "copyleft", which means that ++ derivative works of the document must themselves be free in the ++ same sense. It complements the GNU General Public License, ++ which is a copyleft license designed for free software. ++ ++ ++ ++ We have designed this License in order to use it for manuals ++ for free software, because free software needs free ++ documentation: a free program should come with manuals ++ providing the same freedoms that the software does. But this ++ License is not limited to software manuals; it can be used for ++ any textual work, regardless of subject matter or whether it is ++ published as a printed book. We recommend this License ++ principally for works whose purpose is instruction or ++ reference. ++ ++ ++ ++ 1. APPLICABILITY AND DEFINITIONS ++ ++ ++ This License applies to any manual or other work that contains ++ a notice placed by the copyright holder saying it can be ++ distributed under the terms of this License. The "Document", ++ below, refers to any such manual or work. Any member of the ++ public is a licensee, and is addressed as "you". ++ ++ ++ ++ A "Modified Version" of the Document means any work containing ++ the Document or a portion of it, either copied verbatim, or ++ with modifications and/or translated into another language. ++ ++ ++ ++ A "Secondary Section" is a named appendix or a front-matter ++ section of the Document that deals exclusively with the ++ relationship of the publishers or authors of the Document to ++ the Document's overall subject (or to related matters) and ++ contains nothing that could fall directly within that overall ++ subject. (For example, if the Document is in part a textbook of ++ mathematics, a Secondary Section may not explain any ++ mathematics.) The relationship could be a matter of historical ++ connection with the subject or with related matters, or of ++ legal, commercial, philosophical, ethical or political position ++ regarding them. ++ ++ ++ ++ The "Invariant Sections" are certain Secondary Sections whose ++ titles are designated, as being those of Invariant Sections, in ++ the notice that says that the Document is released under this ++ License. ++ ++ ++ ++ The "Cover Texts" are certain short passages of text that are ++ listed, as Front-Cover Texts or Back-Cover Texts, in the notice ++ that says that the Document is released under this License. ++ ++ ++ ++ A "Transparent" copy of the Document means a machine-readable ++ copy, represented in a format whose specification is available ++ to the general public, whose contents can be viewed and edited ++ directly and straightforwardly with generic text editors or ++ (for images composed of pixels) generic paint programs or (for ++ drawings) some widely available drawing editor, and that is ++ suitable for input to text formatters or for automatic ++ translation to a variety of formats suitable for input to text ++ formatters. A copy made in an otherwise Transparent file format ++ whose markup has been designed to thwart or discourage ++ subsequent modification by readers is not Transparent. A copy ++ that is not "Transparent" is called "Opaque". ++ ++ ++ ++ Examples of suitable formats for Transparent copies include ++ plain ASCII without markup, Texinfo input format, LaTeX input ++ format, SGML or XML using a publicly available DTD, and ++ standard-conforming simple HTML designed for human ++ modification. Opaque formats include PostScript, PDF, ++ proprietary formats that can be read and edited only by ++ proprietary word processors, SGML or XML for which the DTD ++ and/or processing tools are not generally available, and the ++ machine-generated HTML produced by some word processors for ++ output purposes only. ++ ++ ++ ++ The "Title Page" means, for a printed book, the title page ++ itself, plus such following pages as are needed to hold, ++ legibly, the material this License requires to appear in the ++ title page. For works in formats which do not have any title ++ page as such, "Title Page" means the text near the most ++ prominent appearance of the work's title, preceding the ++ beginning of the body of the text. ++ ++ ++ ++ 2. VERBATIM COPYING ++ ++ ++ You may copy and distribute the Document in any medium, either ++ commercially or noncommercially, provided that this License, ++ the copyright notices, and the license notice saying this ++ License applies to the Document are reproduced in all copies, ++ and that you add no other conditions whatsoever to those of ++ this License. You may not use technical measures to obstruct or ++ control the reading or further copying of the copies you make ++ or distribute. However, you may accept compensation in exchange ++ for copies. If you distribute a large enough number of copies ++ you must also follow the conditions in section 3. ++ ++ ++ ++ You may also lend copies, under the same conditions stated ++ above, and you may publicly display copies. ++ ++ ++ ++ 3. COPYING IN QUANTITY ++ ++ ++ If you publish printed copies of the Document numbering more ++ than 100, and the Document's license notice requires Cover ++ Texts, you must enclose the copies in covers that carry, ++ clearly and legibly, all these Cover Texts: Front-Cover Texts ++ on the front cover, and Back-Cover Texts on the back ++ cover. Both covers must also clearly and legibly identify you ++ as the publisher of these copies. The front cover must present ++ the full title with all words of the title equally prominent ++ and visible. You may add other material on the covers in ++ addition. Copying with changes limited to the covers, as long ++ as they preserve the title of the Document and satisfy these ++ conditions, can be treated as verbatim copying in other ++ respects. ++ ++ ++ ++ If the required texts for either cover are too voluminous to ++ fit legibly, you should put the first ones listed (as many as ++ fit reasonably) on the actual cover, and continue the rest onto ++ adjacent pages. ++ ++ ++ ++ If you publish or distribute Opaque copies of the Document ++ numbering more than 100, you must either include a ++ machine-readable Transparent copy along with each Opaque copy, ++ or state in or with each Opaque copy a publicly-accessible ++ computer-network location containing a complete Transparent ++ copy of the Document, free of added material, which the general ++ network-using public has access to download anonymously at no ++ charge using public-standard network protocols. If you use the ++ latter option, you must take reasonably prudent steps, when you ++ begin distribution of Opaque copies in quantity, to ensure that ++ this Transparent copy will remain thus accessible at the stated ++ location until at least one year after the last time you ++ distribute an Opaque copy (directly or through your agents or ++ retailers) of that edition to the public. ++ ++ ++ ++ It is requested, but not required, that you contact the authors ++ of the Document well before redistributing any large number of ++ copies, to give them a chance to provide you with an updated ++ version of the Document. ++ ++ ++ ++ 4. MODIFICATIONS ++ ++ ++ You may copy and distribute a Modified Version of the Document ++ under the conditions of sections 2 and 3 above, provided that ++ you release the Modified Version under precisely this License, ++ with the Modified Version filling the role of the Document, ++ thus licensing distribution and modification of the Modified ++ Version to whoever possesses a copy of it. In addition, you ++ must do these things in the Modified Version: ++ ++ ++ ++ ++ ++ ++ A. Use in the Title Page (and on the covers, if any) a title ++ distinct from that of the Document, and from those of previous ++ versions (which should, if there were any, be listed in the ++ History section of the Document). You may use the same title ++ as a previous version if the original publisher of that ++ version gives permission. ++ ++ ++ ++ ++ B. List on the Title Page, as authors, one or more persons ++ or entities responsible for authorship of the modifications in ++ the Modified Version, together with at least five of the ++ principal authors of the Document (all of its principal ++ authors, if it has less than five). ++ ++ ++ ++ ++ C. State on the Title page the name of the publisher of the ++ Modified Version, as the publisher. ++ ++ ++ ++ ++ D. Preserve all the copyright notices of the Document. ++ ++ ++ ++ ++ E. Add an appropriate copyright notice for your ++ modifications adjacent to the other copyright notices. ++ ++ ++ ++ ++ F. Include, immediately after the copyright notices, a ++ license notice giving the public permission to use the ++ Modified Version under the terms of this License, in the form ++ shown in the Addendum below. ++ ++ ++ ++ ++ G. Preserve in that license notice the full lists of ++ Invariant Sections and required Cover Texts given in the ++ Document's license notice. ++ ++ ++ ++ ++ H. Include an unaltered copy of this License. ++ ++ ++ ++ ++ I. Preserve the section entitled "History", and its title, ++ and add to it an item stating at least the title, year, new ++ authors, and publisher of the Modified Version as given on the ++ Title Page. If there is no section entitled "History" in the ++ Document, create one stating the title, year, authors, and ++ publisher of the Document as given on its Title Page, then add ++ an item describing the Modified Version as stated in the ++ previous sentence. ++ ++ ++ ++ ++ J. Preserve the network location, if any, given in the ++ Document for public access to a Transparent copy of the ++ Document, and likewise the network locations given in the ++ Document for previous versions it was based on. These may be ++ placed in the "History" section. You may omit a network ++ location for a work that was published at least four years ++ before the Document itself, or if the original publisher of ++ the version it refers to gives permission. ++ ++ ++ ++ ++ K. In any section entitled "Acknowledgements" or ++ "Dedications", preserve the section's title, and preserve in ++ the section all the substance and tone of each of the ++ contributor acknowledgements and/or dedications given therein. ++ ++ ++ ++ ++ L. Preserve all the Invariant Sections of the Document, ++ unaltered in their text and in their titles. Section numbers ++ or the equivalent are not considered part of the section ++ titles. ++ ++ ++ ++ ++ M. Delete any section entitled "Endorsements". Such a ++ section may not be included in the Modified Version. ++ ++ ++ ++ ++ N. Do not retitle any existing section as "Endorsements" or ++ to conflict in title with any Invariant Section. ++ ++ ++ ++ ++ ++ ++ If the Modified Version includes new front-matter sections or ++ appendices that qualify as Secondary Sections and contain no ++ material copied from the Document, you may at your option ++ designate some or all of these sections as invariant. To do ++ this, add their titles to the list of Invariant Sections in the ++ Modified Version's license notice. These titles must be ++ distinct from any other section titles. ++ ++ ++ ++ You may add a section entitled "Endorsements", provided it ++ contains nothing but endorsements of your Modified Version by ++ various parties--for example, statements of peer review or that ++ the text has been approved by an organization as the ++ authoritative definition of a standard. ++ ++ ++ ++ You may add a passage of up to five words as a Front-Cover ++ Text, and a passage of up to 25 words as a Back-Cover Text, to ++ the end of the list of Cover Texts in the Modified ++ Version. Only one passage of Front-Cover Text and one of ++ Back-Cover Text may be added by (or through arrangements made ++ by) any one entity. If the Document already includes a cover ++ text for the same cover, previously added by you or by ++ arrangement made by the same entity you are acting on behalf ++ of, you may not add another; but you may replace the old one, ++ on explicit permission from the previous publisher that added ++ the old one. ++ ++ ++ ++ The author(s) and publisher(s) of the Document do not by this ++ License give permission to use their names for publicity for or ++ to assert or imply endorsement of any Modified Version. ++ ++ ++ ++ 5. COMBINING DOCUMENTS ++ ++ ++ You may combine the Document with other documents released ++ under this License, under the terms defined in section 4 above ++ for modified versions, provided that you include in the ++ combination all of the Invariant Sections of all of the ++ original documents, unmodified, and list them all as Invariant ++ Sections of your combined work in its license notice. ++ ++ ++ ++ The combined work need only contain one copy of this License, ++ and multiple identical Invariant Sections may be replaced with ++ a single copy. If there are multiple Invariant Sections with ++ the same name but different contents, make the title of each ++ such section unique by adding at the end of it, in parentheses, ++ the name of the original author or publisher of that section if ++ known, or else a unique number. Make the same adjustment to the ++ section titles in the list of Invariant Sections in the license ++ notice of the combined work. ++ ++ ++ ++ In the combination, you must combine any sections entitled ++ "History" in the various original documents, forming one ++ section entitled "History"; likewise combine any sections ++ entitled "Acknowledgements", and any sections entitled ++ "Dedications". You must delete all sections entitled ++ "Endorsements." ++ ++ ++ ++ 6. COLLECTIONS OF DOCUMENTS ++ ++ ++ You may make a collection consisting of the Document and other ++ documents released under this License, and replace the ++ individual copies of this License in the various documents with ++ a single copy that is included in the collection, provided that ++ you follow the rules of this License for verbatim copying of ++ each of the documents in all other respects. ++ ++ ++ ++ You may extract a single document from such a collection, and ++ distribute it individually under this License, provided you ++ insert a copy of this License into the extracted document, and ++ follow this License in all other respects regarding verbatim ++ copying of that document. ++ ++ ++ ++ 7. AGGREGATION WITH INDEPENDENT WORKS ++ ++ ++ A compilation of the Document or its derivatives with other ++ separate and independent documents or works, in or on a volume ++ of a storage or distribution medium, does not as a whole count ++ as a Modified Version of the Document, provided no compilation ++ copyright is claimed for the compilation. Such a compilation is ++ called an "aggregate", and this License does not apply to the ++ other self-contained works thus compiled with the Document, on ++ account of their being thus compiled, if they are not ++ themselves derivative works of the Document. ++ ++ ++ ++ If the Cover Text requirement of section 3 is applicable to ++ these copies of the Document, then if the Document is less than ++ one quarter of the entire aggregate, the Document's Cover Texts ++ may be placed on covers that surround only the Document within ++ the aggregate. Otherwise they must appear on covers around the ++ whole aggregate. ++ ++ ++ ++ 8. TRANSLATION ++ ++ ++ Translation is considered a kind of modification, so you may ++ distribute translations of the Document under the terms of ++ section 4. Replacing Invariant Sections with translations ++ requires special permission from their copyright holders, but ++ you may include translations of some or all Invariant Sections ++ in addition to the original versions of these Invariant ++ Sections. You may include a translation of this License ++ provided that you also include the original English version of ++ this License. In case of a disagreement between the translation ++ and the original English version of this License, the original ++ English version will prevail. ++ ++ ++ ++ 9. TERMINATION ++ ++ ++ You may not copy, modify, sublicense, or distribute the ++ Document except as expressly provided for under this ++ License. Any other attempt to copy, modify, sublicense or ++ distribute the Document is void, and will automatically ++ terminate your rights under this License. However, parties who ++ have received copies, or rights, from you under this License ++ will not have their licenses terminated so long as such parties ++ remain in full compliance. ++ ++ ++ ++ 10. FUTURE REVISIONS OF THIS LICENSE ++ ++ ++ The Free Software Foundation may publish new, revised versions ++ of the GNU Free Documentation License from time to time. Such ++ new versions will be similar in spirit to the present version, ++ but may differ in detail to address new problems or ++ concerns. See http://www.gnu.org/copyleft/. ++ ++ ++ ++ Each version of the License is given a distinguishing version ++ number. If the Document specifies that a particular numbered ++ version of this License "or any later version" applies to it, ++ you have the option of following the terms and conditions ++ either of that specified version or of any later version that ++ has been published (not as a draft) by the Free Software ++ Foundation. If the Document does not specify a version number ++ of this License, you may choose any version ever published (not ++ as a draft) by the Free Software Foundation. ++ ++ ++ ++ ++
+--- linux-2.4.27/include/linux/icmpv6.h~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/include/linux/icmpv6.h +@@ -40,14 +40,16 @@ + struct icmpv6_nd_ra { + __u8 hop_limit; + #if defined(__LITTLE_ENDIAN_BITFIELD) +- __u8 reserved:6, ++ __u8 reserved:5, ++ home_agent:1, + other:1, + managed:1; + + #elif defined(__BIG_ENDIAN_BITFIELD) + __u8 managed:1, + other:1, +- reserved:6; ++ home_agent:1, ++ reserved:5; + #else + #error "Please fix " + #endif +@@ -70,6 +72,7 @@ + #define icmp6_addrconf_managed icmp6_dataun.u_nd_ra.managed + #define icmp6_addrconf_other icmp6_dataun.u_nd_ra.other + #define icmp6_rt_lifetime icmp6_dataun.u_nd_ra.rt_lifetime ++#define icmp6_home_agent icmp6_dataun.u_nd_ra.home_agent + }; + + +--- linux-2.4.27/include/linux/if_arp.h~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/include/linux/if_arp.h +@@ -59,7 +59,7 @@ + #define ARPHRD_RAWHDLC 518 /* Raw HDLC */ + + #define ARPHRD_TUNNEL 768 /* IPIP tunnel */ +-#define ARPHRD_TUNNEL6 769 /* IPIP6 tunnel */ ++#define ARPHRD_TUNNEL6 769 /* IP6IP6 tunnel */ + #define ARPHRD_FRAD 770 /* Frame Relay Access Device */ + #define ARPHRD_SKIP 771 /* SKIP vif */ + #define ARPHRD_LOOPBACK 772 /* Loopback device */ +--- linux-2.4.27/include/linux/in6.h~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/include/linux/in6.h +@@ -142,6 +142,11 @@ + #define IPV6_TLV_JUMBO 194 + + /* ++ * Mobile IPv6 TLV options. ++ */ ++#define MIPV6_TLV_HOMEADDR 201 ++ ++/* + * IPV6 socket options + */ + +--- linux-2.4.27/include/linux/ipv6.h~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/include/linux/ipv6.h +@@ -29,6 +29,7 @@ + + #define IPV6_SRCRT_STRICT 0x01 /* this hop must be a neighbor */ + #define IPV6_SRCRT_TYPE_0 0 /* IPv6 type 0 Routing Header */ ++#define IPV6_SRCRT_TYPE_2 2 /* type 2 for Mobile IPv6 */ + + /* + * routing header +@@ -71,6 +72,19 @@ + struct in6_addr addr[0]; + + #define rt0_type rt_hdr.type ++ ++}; ++ ++/* ++ * routing header type 2 ++ */ ++ ++struct rt2_hdr { ++ struct ipv6_rt_hdr rt_hdr; ++ __u32 reserved; ++ struct in6_addr addr; ++ ++#define rt2_type rt_hdr.type; + }; + + /* +@@ -156,12 +170,16 @@ + struct inet6_skb_parm + { + int iif; ++ __u8 mipv6_flags; + __u16 ra; + __u16 hop; + __u16 auth; + __u16 dst0; + __u16 srcrt; ++ __u16 srcrt2; ++ __u16 hao; + __u16 dst1; ++ struct in6_addr hoa; + }; + + #endif +--- linux-2.4.27/include/linux/ipv6_route.h~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/include/linux/ipv6_route.h +@@ -33,6 +33,7 @@ + #define RTF_CACHE 0x01000000 /* cache entry */ + #define RTF_FLOW 0x02000000 /* flow significant route */ + #define RTF_POLICY 0x04000000 /* policy route */ ++#define RTF_MOBILENODE 0x10000000 /* for routing to Mobile Node */ + + #define RTF_LOCAL 0x80000000 + +--- /dev/null ++++ linux-2.4.27/include/linux/ipv6_tunnel.h +@@ -0,0 +1,34 @@ ++/* ++ * $Id$ ++ */ ++ ++#ifndef _IPV6_TUNNEL_H ++#define _IPV6_TUNNEL_H ++ ++#define IPV6_TLV_TNL_ENCAP_LIMIT 4 ++#define IPV6_DEFAULT_TNL_ENCAP_LIMIT 4 ++ ++/* don't add encapsulation limit if one isn't present in inner packet */ ++#define IP6_TNL_F_IGN_ENCAP_LIMIT 0x1 ++/* copy the traffic class field from the inner packet */ ++#define IP6_TNL_F_USE_ORIG_TCLASS 0x2 ++/* copy the flowlabel from the inner packet */ ++#define IP6_TNL_F_USE_ORIG_FLOWLABEL 0x4 ++/* created and maintained from within the kernel */ ++#define IP6_TNL_F_KERNEL_DEV 0x8 ++/* being used for Mobile IPv6 */ ++#define IP6_TNL_F_MIP6_DEV 0x10 ++ ++struct ip6_tnl_parm { ++ char name[IFNAMSIZ]; /* name of tunnel device */ ++ int link; /* ifindex of underlying L2 interface */ ++ __u8 proto; /* tunnel protocol */ ++ __u8 encap_limit; /* encapsulation limit for tunnel */ ++ __u8 hop_limit; /* hop limit for tunnel */ ++ __u32 flowinfo; /* traffic class and flowlabel for tunnel */ ++ __u32 flags; /* tunnel flags */ ++ struct in6_addr laddr; /* local tunnel end-point address */ ++ struct in6_addr raddr; /* remote tunnel end-point address */ ++}; ++ ++#endif +--- linux-2.4.27/include/linux/rtnetlink.h~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/include/linux/rtnetlink.h +@@ -315,6 +315,7 @@ + IFA_BROADCAST, + IFA_ANYCAST, + IFA_CACHEINFO, ++ IFA_HOMEAGENT, + __IFA_MAX + }; + +@@ -324,6 +325,7 @@ + + #define IFA_F_SECONDARY 0x01 + ++#define IFA_F_HOMEADDR 0x10 + #define IFA_F_DEPRECATED 0x20 + #define IFA_F_TENTATIVE 0x40 + #define IFA_F_PERMANENT 0x80 +--- linux-2.4.27/include/linux/skbuff.h~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/include/linux/skbuff.h +@@ -177,7 +177,7 @@ + * want to keep them across layers you have to do a skb_clone() + * first. This is owned by whoever has the skb queued ATM. + */ +- char cb[48]; ++ char cb[64]; + + unsigned int len; /* Length of actual data */ + unsigned int data_len; +--- linux-2.4.27/include/linux/sysctl.h~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/include/linux/sysctl.h +@@ -404,6 +404,23 @@ + NET_IPV6_ICMP=19, + NET_IPV6_BINDV6ONLY=20, + NET_IPV6_MLD_MAX_MSF=25, ++ NET_IPV6_MOBILITY=26 ++}; ++ ++/* /proc/sys/net/ipv6/mobility */ ++enum { ++ NET_IPV6_MOBILITY_DEBUG=1, ++ NET_IPV6_MOBILITY_TUNNEL_SITELOCAL=2, ++ NET_IPV6_MOBILITY_ROUTER_SOLICITATION_MAX_SENDTIME=3, ++ NET_IPV6_MOBILITY_ROUTER_REACH=4, ++ NET_IPV6_MOBILITY_MDETECT_MECHANISM=5, ++ NET_IPV6_MOBILITY_RETROUT=6, ++ NET_IPV6_MOBILITY_MAX_TNLS=7, ++ NET_IPV6_MOBILITY_MIN_TNLS=8, ++ NET_IPV6_MOBILITY_BINDING_REFRESH=9, ++ NET_IPV6_MOBILITY_BU_F_LLADDR=10, ++ NET_IPV6_MOBILITY_BU_F_KEYMGM=11, ++ NET_IPV6_MOBILITY_BU_F_CN_ACK=12 + }; + + enum { +--- linux-2.4.27/include/net/addrconf.h~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/include/net/addrconf.h +@@ -16,9 +16,11 @@ + #if defined(__BIG_ENDIAN_BITFIELD) + __u8 onlink : 1, + autoconf : 1, +- reserved : 6; ++ router_address : 1, ++ reserved : 5; + #elif defined(__LITTLE_ENDIAN_BITFIELD) +- __u8 reserved : 6, ++ __u8 reserved : 5, ++ router_address : 1, + autoconf : 1, + onlink : 1; + #else +@@ -55,6 +57,7 @@ + struct net_device *dev); + extern struct inet6_ifaddr * ipv6_get_ifaddr(struct in6_addr *addr, + struct net_device *dev); ++extern void ipv6_del_addr(struct inet6_ifaddr *ifp); + extern int ipv6_get_saddr(struct dst_entry *dst, + struct in6_addr *daddr, + struct in6_addr *saddr); +@@ -85,7 +88,9 @@ + extern void ipv6_mc_down(struct inet6_dev *idev); + extern void ipv6_mc_init_dev(struct inet6_dev *idev); + extern void ipv6_mc_destroy_dev(struct inet6_dev *idev); ++extern void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags); + extern void addrconf_dad_failure(struct inet6_ifaddr *ifp); ++extern void addrconf_dad_completed(struct inet6_ifaddr *ifp); + + extern int ipv6_chk_mcast_addr(struct net_device *dev, struct in6_addr *group, + struct in6_addr *src_addr); +@@ -116,6 +121,9 @@ + extern int register_inet6addr_notifier(struct notifier_block *nb); + extern int unregister_inet6addr_notifier(struct notifier_block *nb); + ++extern int ipv6_generate_eui64(u8 *eui, struct net_device *dev); ++extern int ipv6_inherit_eui64(u8 *eui, struct inet6_dev *idev); ++ + static inline struct inet6_dev * + __in6_dev_get(struct net_device *dev) + { +--- linux-2.4.27/include/net/ip6_route.h~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/include/net/ip6_route.h +@@ -2,6 +2,7 @@ + #define _NET_IP6_ROUTE_H + + #define IP6_RT_PRIO_FW 16 ++#define IP6_RT_PRIO_MIPV6 64 + #define IP6_RT_PRIO_USER 1024 + #define IP6_RT_PRIO_ADDRCONF 256 + #define IP6_RT_PRIO_KERN 512 +@@ -40,6 +41,9 @@ + + extern int ip6_route_add(struct in6_rtmsg *rtmsg, + struct nlmsghdr *); ++ ++extern int ip6_route_del(struct in6_rtmsg *rtmsg, ++ struct nlmsghdr *); + extern int ip6_del_rt(struct rt6_info *, + struct nlmsghdr *); + +@@ -99,7 +103,8 @@ + */ + + static inline void ip6_dst_store(struct sock *sk, struct dst_entry *dst, +- struct in6_addr *daddr) ++ struct in6_addr *daddr, ++ struct in6_addr *saddr) + { + struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6; + struct rt6_info *rt = (struct rt6_info *) dst; +@@ -107,6 +112,9 @@ + write_lock(&sk->dst_lock); + __sk_dst_set(sk, dst); + np->daddr_cache = daddr; ++#ifdef CONFIG_IPV6_SUBTREES ++ np->saddr_cache = saddr; ++#endif + np->dst_cookie = rt->rt6i_node ? rt->rt6i_node->fn_sernum : 0; + write_unlock(&sk->dst_lock); + } +--- linux-2.4.27/include/net/ipv6.h~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/include/net/ipv6.h +@@ -37,6 +37,7 @@ + #define NEXTHDR_ICMP 58 /* ICMP for IPv6. */ + #define NEXTHDR_NONE 59 /* No next header */ + #define NEXTHDR_DEST 60 /* Destination options header. */ ++#define NEXTHDR_MH 135 /* Mobility header, RFC 3775 */ + + #define NEXTHDR_MAX 255 + +@@ -146,9 +147,12 @@ + __u16 opt_flen; /* after fragment hdr */ + __u16 opt_nflen; /* before fragment hdr */ + ++ __u8 mipv6_flags; /* flags set by MIPv6 */ ++ + struct ipv6_opt_hdr *hopopt; + struct ipv6_opt_hdr *dst0opt; +- struct ipv6_rt_hdr *srcrt; /* Routing Header */ ++ struct ipv6_rt_hdr *srcrt; /* Routing Header Type 0 */ ++ struct ipv6_rt_hdr *srcrt2; /* Routing Header Type 2 */ + struct ipv6_opt_hdr *auth; + struct ipv6_opt_hdr *dst1opt; + +@@ -257,6 +261,38 @@ + a->s6_addr32[2] | a->s6_addr32[3] ) == 0); + } + ++static inline void ipv6_addr_prefix(struct in6_addr *pfx, ++ const struct in6_addr *addr, int plen) ++{ ++ /* caller must guarantee 0 <= plen <= 128 */ ++ int o = plen >> 3, ++ b = plen & 0x7; ++ ++ memcpy(pfx->s6_addr, addr, o); ++ if (b != 0) { ++ pfx->s6_addr[o] = addr->s6_addr[o] & (0xff00 >> b); ++ o++; ++ } ++ if (o < 16) ++ memset(pfx->s6_addr + o, 0, 16 - o); ++} ++ ++static inline int ipv6_prefix_cmp(const struct in6_addr *p1, ++ const struct in6_addr *p2, int plen) ++{ ++ int b = plen&0x7; ++ int o = plen>>3; ++ int res = 0; ++ ++ if (o > 0) ++ res = memcmp(&p1->s6_addr[0], &p2->s6_addr[0], o); ++ if (res == 0 && b > 0) { ++ __u8 m = (0xff00 >> b) & 0xff; ++ res = (p1->s6_addr[o] & m) - (p2->s6_addr[o] & m); ++ } ++ return res; ++} ++ + /* + * Prototypes exported by ipv6 + */ +--- /dev/null ++++ linux-2.4.27/include/net/ipv6_tunnel.h +@@ -0,0 +1,92 @@ ++/* ++ * $Id$ ++ */ ++ ++#ifndef _NET_IPV6_TUNNEL_H ++#define _NET_IPV6_TUNNEL_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++/* capable of sending packets */ ++#define IP6_TNL_F_CAP_XMIT 0x10000 ++/* capable of receiving packets */ ++#define IP6_TNL_F_CAP_RCV 0x20000 ++ ++#define IP6_TNL_MAX 128 ++ ++/* IPv6 tunnel */ ++ ++struct ip6_tnl { ++ struct ip6_tnl *next; /* next tunnel in list */ ++ struct net_device *dev; /* virtual device associated with tunnel */ ++ struct net_device_stats stat; /* statistics for tunnel device */ ++ int recursion; /* depth of hard_start_xmit recursion */ ++ struct ip6_tnl_parm parms; /* tunnel configuration paramters */ ++ struct flowi fl; /* flowi template for xmit */ ++ atomic_t refcnt; /* nr of identical tunnels used by kernel */ ++ struct socket *sock; ++}; ++ ++#define IP6_TNL_PRE_ENCAP 0 ++#define IP6_TNL_PRE_DECAP 1 ++#define IP6_TNL_MAXHOOKS 2 ++ ++#define IP6_TNL_DROP 0 ++#define IP6_TNL_ACCEPT 1 ++ ++typedef int ip6_tnl_hookfn(struct ip6_tnl *t, struct sk_buff *skb); ++ ++struct ip6_tnl_hook_ops { ++ struct list_head list; ++ unsigned int hooknum; ++ int priority; ++ ip6_tnl_hookfn *hook; ++}; ++ ++enum ip6_tnl_hook_priorities { ++ IP6_TNL_PRI_FIRST = INT_MIN, ++ IP6_TNL_PRI_LAST = INT_MAX ++}; ++ ++/* Tunnel encapsulation limit destination sub-option */ ++ ++struct ipv6_tlv_tnl_enc_lim { ++ __u8 type; /* type-code for option */ ++ __u8 length; /* option length */ ++ __u8 encap_limit; /* tunnel encapsulation limit */ ++} __attribute__ ((packed)); ++ ++#ifdef __KERNEL__ ++extern int ip6ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt); ++ ++extern struct ip6_tnl *ip6ip6_tnl_lookup(struct in6_addr *remote, ++ struct in6_addr *local); ++ ++void ip6ip6_tnl_change(struct ip6_tnl *t, struct ip6_tnl_parm *p); ++ ++extern int ip6ip6_kernel_tnl_add(struct ip6_tnl_parm *p); ++ ++extern int ip6ip6_kernel_tnl_del(struct ip6_tnl *t); ++ ++extern unsigned int ip6ip6_tnl_inc_max_kdev_count(unsigned int n); ++ ++extern unsigned int ip6ip6_tnl_dec_max_kdev_count(unsigned int n); ++ ++extern unsigned int ip6ip6_tnl_inc_min_kdev_count(unsigned int n); ++ ++extern unsigned int ip6ip6_tnl_dec_min_kdev_count(unsigned int n); ++ ++extern void ip6ip6_tnl_register_hook(struct ip6_tnl_hook_ops *reg); ++ ++extern void ip6ip6_tnl_unregister_hook(struct ip6_tnl_hook_ops *reg); ++ ++#ifdef CONFIG_IPV6_TUNNEL ++extern int __init ip6_tunnel_init(void); ++extern void ip6_tunnel_cleanup(void); ++#endif ++#endif ++#endif +--- /dev/null ++++ linux-2.4.27/include/net/mipglue.h +@@ -0,0 +1,266 @@ ++/* ++ * Glue for Mobility support integration to IPv6 ++ * ++ * Authors: ++ * Antti Tuominen ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#ifndef _NET_MIPGLUE_H ++#define _NET_MIPGLUE_H ++ ++#ifndef USE_IPV6_MOBILITY ++#if defined(CONFIG_IPV6_MOBILITY) || defined(CONFIG_IPV6_MOBILITY_MODULE) ++#define USE_IPV6_MOBILITY ++#endif ++#endif ++ ++/* symbols to indicate whether destination options received should take ++ * effect or not (see exthdrs.c, procrcv.c) ++ */ ++#define MIPV6_DSTOPTS_ACCEPT 1 ++#define MIPV6_DSTOPTS_DISCARD 0 ++ ++#define MIPV6_IGN_RTR 0 ++#define MIPV6_ADD_RTR 1 ++#define MIPV6_CHG_RTR 2 ++ ++/* MIPV6: Approximate maximum for mobile IPv6 options and headers */ ++#define MIPV6_HEADERS 48 ++ ++#ifdef __KERNEL__ ++#include ++#include ++#include ++ ++struct sk_buff; ++struct ndisc_options; ++struct sock; ++struct ipv6_txoptions; ++struct flowi; ++struct dst_entry; ++struct in6_addr; ++struct inet6_ifaddr; ++ ++#ifdef USE_IPV6_MOBILITY ++ ++/* calls a procedure from mipv6-module */ ++#define MIPV6_CALLPROC(X) if(mipv6_functions.X) mipv6_functions.X ++ ++/* calls a function from mipv6-module, default-value if function not defined ++ */ ++#define MIPV6_CALLFUNC(X,Y) (!mipv6_functions.X)?(Y):mipv6_functions.X ++ ++/* sets a handler-function to process a call */ ++#define MIPV6_SETCALL(X,Y) if(mipv6_functions.X) printk("mipv6: Warning, function assigned twice!\n"); \ ++ mipv6_functions.X = Y ++#define MIPV6_RESETCALL(X) mipv6_functions.X = NULL ++ ++/* pointers to mipv6 callable functions */ ++struct mipv6_callable_functions { ++ void (*mipv6_initialize_dstopt_rcv) (struct sk_buff *skb); ++ int (*mipv6_finalize_dstopt_rcv) (int process); ++ int (*mipv6_handle_homeaddr) (struct sk_buff *skb, int optoff); ++ int (*mipv6_ra_rcv) (struct sk_buff *skb, ++ struct ndisc_options *ndopts); ++ void (*mipv6_icmp_rcv) (struct sk_buff *skb); ++ struct ipv6_txoptions * (*mipv6_modify_txoptions) ( ++ struct sock *sk, ++ struct sk_buff *skb, ++ struct ipv6_txoptions *opt, ++ struct flowi *fl, ++ struct dst_entry **dst); ++ void (*mipv6_set_home) (int ifindex, struct in6_addr *homeaddr, ++ int plen, struct in6_addr *homeagent, ++ int plen2); ++ void (*mipv6_get_home_address) (struct in6_addr *home_addr); ++ void (*mipv6_get_care_of_address)(struct in6_addr *homeaddr, ++ struct in6_addr *coa); ++ int (*mipv6_is_home_addr)(struct in6_addr *addr); ++ void (*mipv6_change_router)(void); ++ void (*mipv6_check_dad)(struct in6_addr *home_addr); ++ void (*mipv6_icmp_swap_addrs)(struct sk_buff *skb); ++ int (*mipv6_forward)(struct sk_buff *skb); ++ int (*mipv6_mn_ha_probe)(struct inet6_ifaddr *ifp, u8 *lladdr); ++}; ++ ++extern struct mipv6_callable_functions mipv6_functions; ++ ++extern void mipv6_invalidate_calls(void); ++ ++extern int mipv6_handle_dstopt(struct sk_buff *skb, int optoff); ++ ++static inline int ++ndisc_mip_mn_ha_probe(struct inet6_ifaddr *ifp, u8 *lladdr) ++{ ++ return MIPV6_CALLFUNC(mipv6_mn_ha_probe, 0)(ifp, lladdr); ++} ++ ++/* Must only be called for HA, no checks here */ ++static inline int ip6_mipv6_forward(struct sk_buff *skb) ++{ ++ return MIPV6_CALLFUNC(mipv6_forward, 0)(skb); ++} ++ ++/* ++ * Avoid adding new default routers if the old one is still in use ++ */ ++ ++static inline int ndisc_mipv6_ra_rcv(struct sk_buff *skb, ++ struct ndisc_options *ndopts) ++{ ++ return MIPV6_CALLFUNC(mipv6_ra_rcv, MIPV6_ADD_RTR)(skb, ndopts); ++} ++ ++static inline int ipv6_chk_mip_home_addr(struct in6_addr *addr) ++{ ++ return MIPV6_CALLFUNC(mipv6_is_home_addr, 0)(addr); ++} ++ ++static inline void ndisc_mipv6_change_router(int change_rtr) ++{ ++ if (change_rtr == MIPV6_CHG_RTR) ++ MIPV6_CALLPROC(mipv6_change_router)(); ++} ++ ++static inline void ndisc_check_mipv6_dad(struct in6_addr *target) ++{ ++ MIPV6_CALLPROC(mipv6_check_dad)(target); ++} ++ ++static inline void icmpv6_swap_mipv6_addrs(struct sk_buff *skb) ++{ ++ MIPV6_CALLPROC(mipv6_icmp_swap_addrs)(skb); ++} ++ ++static inline void mipv6_icmp_rcv(struct sk_buff *skb) ++{ ++ MIPV6_CALLPROC(mipv6_icmp_rcv)(skb); ++} ++ ++static inline int tcp_v6_get_mipv6_header_len(void) ++{ ++ return MIPV6_HEADERS; ++} ++ ++static inline struct in6_addr * ++mipv6_get_fake_hdr_daddr(struct in6_addr *hdaddr, struct in6_addr *daddr) ++{ ++ return daddr; ++} ++ ++static inline void ++addrconf_set_mipv6_mn_home(int ifindex, struct in6_addr *homeaddr, int plen, ++ struct in6_addr *homeagent, int plen2) ++{ ++ MIPV6_CALLPROC(mipv6_set_home)(ifindex, homeaddr, plen, homeagent, plen2); ++} ++ ++static inline void addrconf_get_mipv6_home_address(struct in6_addr *saddr) ++{ ++ MIPV6_CALLPROC(mipv6_get_home_address)(saddr); ++} ++ ++static inline struct ipv6_txoptions * ++ip6_add_mipv6_txoptions(struct sock *sk, struct sk_buff *skb, ++ struct ipv6_txoptions *opt, struct flowi *fl, ++ struct dst_entry **dst) ++{ ++ return MIPV6_CALLFUNC(mipv6_modify_txoptions, opt)(sk, skb, opt, fl, dst); ++ ++} ++ ++static inline void ++ip6_mark_mipv6_packet(struct ipv6_txoptions *txopt, struct sk_buff *skb) ++{ ++ struct inet6_skb_parm *opt; ++ if (txopt) { ++ opt = (struct inet6_skb_parm *)skb->cb; ++ opt->mipv6_flags = txopt->mipv6_flags; ++ } ++} ++ ++static inline void ++ip6_free_mipv6_txoptions(struct ipv6_txoptions *opt, ++ struct ipv6_txoptions *orig_opt) ++{ ++ if (opt && opt != orig_opt) ++ kfree(opt); ++} ++ ++#else /* USE_IPV6_MOBILITY */ ++ ++#define mipv6_handle_dstopt ip6_tlvopt_unknown ++ ++static inline int ++ndisc_mip_mn_ha_probe(struct inet6_ifaddr *ifp, u8 *lladdr) ++{ ++ return 0; ++} ++ ++static inline int ip6_mipv6_forward(struct sk_buff *skb) ++{ ++ return 0; ++} ++ ++static inline int ndisc_mipv6_ra_rcv(struct sk_buff *skb, ++ struct ndisc_options *ndopts) ++{ ++ return MIPV6_ADD_RTR; ++} ++ ++static inline int ipv6_chk_mip_home_addr(struct in6_addr *addr) ++{ ++ return 0; ++} ++ ++static inline void ndisc_mipv6_change_router(int change_rtr) {} ++ ++static inline void ndisc_check_mipv6_dad(struct in6_addr *target) {} ++ ++static inline void icmpv6_swap_mipv6_addrs(struct sk_buff *skb) {} ++ ++static inline void mipv6_icmp_rcv(struct sk_buff *skb) {} ++ ++static inline int tcp_v6_get_mipv6_header_len(void) ++{ ++ return 0; ++} ++ ++static inline struct in6_addr * ++mipv6_get_fake_hdr_daddr(struct in6_addr *hdaddr, struct in6_addr *daddr) ++{ ++ return hdaddr; ++} ++ ++static inline void ++addrconf_set_mipv6_mn_home(int ifindex, struct in6_addr *homeaddr, int plen, ++ struct in6_addr *homeagent, int plen2) {} ++ ++static inline void addrconf_get_mipv6_home_address(struct in6_addr *saddr) {} ++ ++static inline struct ipv6_txoptions * ++ip6_add_mipv6_txoptions(struct sock *sk, struct sk_buff *skb, ++ struct ipv6_txoptions *opt, struct flowi *fl, ++ struct dst_entry **dst) ++{ ++ return opt; ++} ++ ++static inline void ++ip6_mark_mipv6_packet(struct ipv6_txoptions *txopt, struct sk_buff *skb) {} ++ ++static inline void ++ip6_free_mipv6_txoptions(struct ipv6_txoptions *opt, ++ struct ipv6_txoptions *orig_opt) {} ++ ++#endif /* USE_IPV6_MOBILITY */ ++#endif /* __KERNEL__ */ ++#endif /* _NET_MIPGLUE_H */ +--- /dev/null ++++ linux-2.4.27/include/net/mipv6.h +@@ -0,0 +1,258 @@ ++/* ++ * Mobile IPv6 header-file ++ * ++ * Authors: ++ * Sami Kivisaari ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#ifndef _NET_MIPV6_H ++#define _NET_MIPV6_H ++ ++#include ++#include ++#include ++ ++/* ++ * ++ * Mobile IPv6 Protocol constants ++ * ++ */ ++#define DHAAD_RETRIES 4 /* transmissions */ ++#define INITIAL_BINDACK_TIMEOUT 1 /* seconds */ ++#define INITIAL_DHAAD_TIMEOUT 3 /* seconds */ ++#define INITIAL_SOLICIT_TIMER 3 /* seconds */ ++#define MAX_BINDACK_TIMEOUT 32 /* seconds */ ++#define MAX_NONCE_LIFE 240 /* seconds */ ++#define MAX_TOKEN_LIFE 210 /* seconds */ ++#define MAX_RR_BINDING_LIFE 420 /* seconds */ ++#define MAX_UPDATE_RATE 3 /* 1/s (min delay=1s) */ ++#define PREFIX_ADV_RETRIES 3 /* transmissions */ ++#define PREFIX_ADV_TIMEOUT 3 /* seconds */ ++ ++#define MAX_FAST_UPDATES 5 /* transmissions */ ++#define MAX_PFX_ADV_DELAY 1000 /* seconds */ ++#define SLOW_UPDATE_RATE 10 /* 1/10s (max delay=10s)*/ ++#define INITIAL_BINDACK_DAD_TIMEOUT 2 /* seconds */ ++ ++/* ++ * ++ * Mobile IPv6 (RFC 3775) Protocol configuration variable defaults ++ * ++ */ ++#define DefHomeRtrAdvInterval 1000 /* seconds */ ++#define DefMaxMobPfxAdvInterval 86400 /* seconds */ ++#define DefMinDelayBetweenRAs 3 /* seconds (min 0.03) */ ++#define DefMinMobPfxAdvInterval 600 /* seconds */ ++#define DefInitialBindackTimeoutFirstReg 1.5 /* seconds */ ++ ++/* This is not actually specified in the draft, but is needed to avoid ++ * prefix solicitation storm when valid lifetime of a prefix is smaller ++ * than MAX_PFX_ADV_DELAY ++ */ ++#define MIN_PFX_SOL_DELAY 5 /* seconds */ ++ ++/* Mobile IPv6 ICMP types */ ++/* ++ * Official numbers from RFC 3775 ++ */ ++#define MIPV6_DHAAD_REQUEST 144 ++#define MIPV6_DHAAD_REPLY 145 ++#define MIPV6_PREFIX_SOLICIT 146 ++#define MIPV6_PREFIX_ADV 147 ++ ++/* Binding update flag codes */ ++#define MIPV6_BU_F_ACK 0x80 ++#define MIPV6_BU_F_HOME 0x40 ++#define MIPV6_BU_F_LLADDR 0x20 ++#define MIPV6_BU_F_KEYMGM 0x10 ++ ++/* Binding ackknowledgment flag codes */ ++#define MIPV6_BA_F_KEYMGM 0x80 ++ ++/* Binding error status */ ++#define MIPV6_BE_HAO_WO_BINDING 1 ++#define MIPV6_BE_UNKNOWN_MH_TYPE 2 ++ ++/* Mobility Header */ ++struct mipv6_mh ++{ ++ __u8 payload; /* Payload Protocol */ ++ __u8 length; /* MH Length */ ++ __u8 type; /* MH Type */ ++ __u8 reserved; /* Reserved */ ++ __u16 checksum; /* Checksum */ ++ __u8 data[0]; /* Message specific data */ ++} __attribute__ ((packed)); ++ ++/* Mobility Header type */ ++#define IPPROTO_MOBILITY 135 /* RFC 3775*/ ++/* Mobility Header Message Types */ ++ ++#define MIPV6_MH_BRR 0 ++#define MIPV6_MH_HOTI 1 ++#define MIPV6_MH_COTI 2 ++#define MIPV6_MH_HOT 3 ++#define MIPV6_MH_COT 4 ++#define MIPV6_MH_BU 5 ++#define MIPV6_MH_BA 6 ++#define MIPV6_MH_BE 7 ++ ++/* ++ * Status codes for Binding Acknowledgements ++ */ ++#define SUCCESS 0 ++#define REASON_UNSPECIFIED 128 ++#define ADMINISTRATIVELY_PROHIBITED 129 ++#define INSUFFICIENT_RESOURCES 130 ++#define HOME_REGISTRATION_NOT_SUPPORTED 131 ++#define NOT_HOME_SUBNET 132 ++#define NOT_HA_FOR_MN 133 ++#define DUPLICATE_ADDR_DETECT_FAIL 134 ++#define SEQUENCE_NUMBER_OUT_OF_WINDOW 135 ++#define EXPIRED_HOME_NONCE_INDEX 136 ++#define EXPIRED_CAREOF_NONCE_INDEX 137 ++#define EXPIRED_NONCES 138 ++#define REG_TYPE_CHANGE_FORBIDDEN 139 ++/* ++ * Values for mipv6_flags in struct inet6_skb_parm ++ */ ++ ++#define MIPV6_RCV_TUNNEL 0x1 ++#define MIPV6_SND_HAO 0x2 ++#define MIPV6_SND_BU 0x4 ++ ++/* ++ * Mobility Header Message structures ++ */ ++ ++struct mipv6_mh_brr ++{ ++ __u16 reserved; ++ /* Mobility options */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mh_bu ++{ ++ __u16 sequence; /* sequence number of BU */ ++ __u8 flags; /* flags */ ++ __u8 reserved; /* reserved bits */ ++ __u16 lifetime; /* lifetime of BU */ ++ /* Mobility options */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mh_ba ++{ ++ __u8 status; /* statuscode */ ++ __u8 reserved; /* reserved bits */ ++ __u16 sequence; /* sequence number of BA */ ++ __u16 lifetime; /* lifetime in CN's bcache */ ++ /* Mobility options */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mh_be ++{ ++ __u8 status; ++ __u8 reserved; ++ struct in6_addr home_addr; ++ /* Mobility options */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mh_addr_ti ++{ ++ __u16 reserved; /* Reserved */ ++ u_int8_t init_cookie[8]; /* HoT/CoT Init Cookie */ ++ /* Mobility options */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mh_addr_test ++{ ++ __u16 nonce_index; /* Home/Care-of Nonce Index */ ++ u_int8_t init_cookie[8]; /* HoT/CoT Init Cookie */ ++ u_int8_t kgen_token[8]; /* Home/Care-of key generation token */ ++ /* Mobility options */ ++} __attribute__ ((packed)); ++ ++/* ++ * Mobility Options for various MH types. ++ */ ++#define MIPV6_OPT_PAD1 0x00 ++#define MIPV6_OPT_PADN 0x01 ++#define MIPV6_OPT_BIND_REFRESH_ADVICE 0x02 ++#define MIPV6_OPT_ALTERNATE_COA 0x03 ++#define MIPV6_OPT_NONCE_INDICES 0x04 ++#define MIPV6_OPT_AUTH_DATA 0x05 ++ ++#define MIPV6_SEQ_GT(x,y) \ ++ ((short int)(((__u16)(x)) - ((__u16)(y))) > 0) ++ ++/* ++ * Mobility Option structures ++ */ ++ ++struct mipv6_mo ++{ ++ __u8 type; ++ __u8 length; ++ __u8 value[0]; /* type specific data */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mo_pad1 ++{ ++ __u8 type; ++} __attribute__ ((packed)); ++ ++struct mipv6_mo_padn ++{ ++ __u8 type; ++ __u8 length; ++ __u8 data[0]; ++} __attribute__ ((packed)); ++ ++struct mipv6_mo_alt_coa ++{ ++ __u8 type; ++ __u8 length; ++ struct in6_addr addr; /* alternate care-of-address */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mo_nonce_indices ++{ ++ __u8 type; ++ __u8 length; ++ __u16 home_nonce_i; /* Home Nonce Index */ ++ __u16 careof_nonce_i; /* Careof Nonce Index */ ++} __attribute__ ((packed)); ++ ++struct mipv6_mo_bauth_data ++{ ++ __u8 type; ++ __u8 length; ++ __u8 data[0]; ++} __attribute__ ((packed)); ++ ++struct mipv6_mo_br_advice ++{ ++ __u8 type; ++ __u8 length; ++ __u16 refresh_interval; /* Refresh Interval */ ++} __attribute__ ((packed)); ++ ++/* ++ * Home Address Destination Option structure ++ */ ++struct mipv6_dstopt_homeaddr ++{ ++ __u8 type; /* type-code for option */ ++ __u8 length; /* option length */ ++ struct in6_addr addr; /* home address */ ++} __attribute__ ((packed)); ++ ++#endif /* _NET_MIPV6_H */ +--- linux-2.4.27/include/net/ndisc.h~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/include/net/ndisc.h +@@ -21,6 +21,10 @@ + #define ND_OPT_REDIRECT_HDR 4 + #define ND_OPT_MTU 5 + ++/* Mobile IPv6 specific ndisc options */ ++#define ND_OPT_RTR_ADV_INTERVAL 7 ++#define ND_OPT_HOME_AGENT_INFO 8 ++ + #define MAX_RTR_SOLICITATION_DELAY HZ + + #define ND_REACHABLE_TIME (30*HZ) +@@ -57,7 +61,7 @@ + } __attribute__((__packed__)); + + struct ndisc_options { +- struct nd_opt_hdr *nd_opt_array[7]; ++ struct nd_opt_hdr *nd_opt_array[10]; + struct nd_opt_hdr *nd_opt_piend; + }; + +@@ -67,6 +71,8 @@ + #define nd_opts_pi_end nd_opt_piend + #define nd_opts_rh nd_opt_array[ND_OPT_REDIRECT_HDR] + #define nd_opts_mtu nd_opt_array[ND_OPT_MTU] ++#define nd_opts_rai nd_opt_array[ND_OPT_RTR_ADV_INTERVAL] ++#define nd_opts_hai nd_opt_array[ND_OPT_HOME_AGENT_INFO] + + extern struct nd_opt_hdr *ndisc_next_option(struct nd_opt_hdr *cur, struct nd_opt_hdr *end); + extern struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len, struct ndisc_options *ndopts); +@@ -83,6 +89,15 @@ + struct in6_addr *daddr, + struct in6_addr *saddr); + ++extern void ndisc_send_na(struct net_device *dev, ++ struct neighbour *neigh, ++ struct in6_addr *daddr, ++ struct in6_addr *solicited_addr, ++ int router, ++ int solicited, ++ int override, ++ int inc_opt); ++ + extern void ndisc_send_rs(struct net_device *dev, + struct in6_addr *saddr, + struct in6_addr *daddr); +--- linux-2.4.27/include/net/sock.h~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/include/net/sock.h +@@ -149,7 +149,9 @@ + struct in6_addr rcv_saddr; + struct in6_addr daddr; + struct in6_addr *daddr_cache; +- ++#if defined(CONFIG_IPV6_SUBTREES) ++ struct in6_addr *saddr_cache; ++#endif + __u32 flow_label; + __u32 frag_size; + int hop_limit; +--- linux-2.4.27/net/Makefile~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/Makefile +@@ -7,7 +7,7 @@ + + O_TARGET := network.o + +-mod-subdirs := ipv4/netfilter ipv6/netfilter ipx irda bluetooth atm netlink sched core sctp 802 ++mod-subdirs := ipv4/netfilter ipv6/netfilter ipx irda bluetooth atm netlink sched core sctp 802 ipv6 + export-objs := netsyms.o + + subdir-y := core ethernet +@@ -25,6 +25,7 @@ + ifneq ($(CONFIG_IPV6),n) + ifneq ($(CONFIG_IPV6),) + subdir-$(CONFIG_NETFILTER) += ipv6/netfilter ++subdir-$(CONFIG_IPV6_MOBILITY) += ipv6/mobile_ip6 + endif + endif + +--- linux-2.4.27/net/core/neighbour.c~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/core/neighbour.c +@@ -386,7 +386,7 @@ + if (!creat) + return NULL; + +- n = kmalloc(sizeof(*n) + key_len, GFP_KERNEL); ++ n = kmalloc(sizeof(*n) + key_len, GFP_ATOMIC); + if (n == NULL) + return NULL; + +--- linux-2.4.27/net/ipv6/Config.in~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/ipv6/Config.in +@@ -1,10 +1,16 @@ + # + # IPv6 configuration + # +- ++bool ' IPv6: routing by source address (EXPERIMENTAL)' CONFIG_IPV6_SUBTREES + #bool ' IPv6: flow policy support' CONFIG_RT6_POLICY + #bool ' IPv6: firewall support' CONFIG_IPV6_FIREWALL + ++if [ "$CONFIG_IPV6" != "n" ]; then ++ dep_tristate ' IPv6: IPv6 over IPv6 Tunneling (EXPERIMENTAL)' CONFIG_IPV6_TUNNEL $CONFIG_IPV6 ++fi ++ ++source net/ipv6/mobile_ip6/Config.in ++ + if [ "$CONFIG_NETFILTER" != "n" ]; then + source net/ipv6/netfilter/Config.in + fi +--- linux-2.4.27/net/ipv6/Makefile~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/ipv6/Makefile +@@ -6,18 +6,28 @@ + # unless it's something special (ie not a .c file). + # + ++export-objs := ipv6_syms.o ipv6_tunnel.o + +-O_TARGET := ipv6.o ++#list-multi := ipv6.o ipv6_tunnel.o + +-obj-y := af_inet6.o anycast.o ip6_output.o ip6_input.o addrconf.o sit.o \ +- route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o raw.o \ +- protocol.o icmp.o mcast.o reassembly.o tcp_ipv6.o \ +- exthdrs.o sysctl_net_ipv6.o datagram.o proc.o \ +- ip6_flowlabel.o ipv6_syms.o ++ipv6-objs := af_inet6.o anycast.o ip6_output.o ip6_input.o addrconf.o \ ++ sit.o route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o \ ++ raw.o protocol.o icmp.o mcast.o reassembly.o tcp_ipv6.o \ ++ exthdrs.o sysctl_net_ipv6.o datagram.o proc.o \ ++ ip6_flowlabel.o ipv6_syms.o + +-export-objs := ipv6_syms.o ++ifneq ($(CONFIG_IPV6_MOBILITY),n) ++ifneq ($(CONFIG_IPV6_MOBILITY),) ++ipv6-objs += mipglue.o ++endif ++endif ++ ++obj-$(CONFIG_IPV6) += ipv6.o ++obj-$(CONFIG_IPV6_TUNNEL) += ipv6_tunnel.o ++ ++ipv6.o: $(ipv6-objs) ++ $(LD) -r -o $@ $(ipv6-objs) + +-obj-m := $(O_TARGET) + + #obj-$(CONFIG_IPV6_FIREWALL) += ip6_fw.o + +--- linux-2.4.27/net/ipv6/addrconf.c~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/ipv6/addrconf.c +@@ -68,6 +68,8 @@ + + #include + ++#include ++ + #define IPV6_MAX_ADDRESSES 16 + + /* Set to 3 to get tracing... */ +@@ -103,9 +105,9 @@ + + static int addrconf_ifdown(struct net_device *dev, int how); + +-static void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags); ++void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags); + static void addrconf_dad_timer(unsigned long data); +-static void addrconf_dad_completed(struct inet6_ifaddr *ifp); ++void addrconf_dad_completed(struct inet6_ifaddr *ifp); + static void addrconf_rs_timer(unsigned long data); + static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifa); + +@@ -330,38 +332,6 @@ + return idev; + } + +-void ipv6_addr_prefix(struct in6_addr *prefix, +- struct in6_addr *addr, int prefix_len) +-{ +- unsigned long mask; +- int ncopy, nbits; +- +- memset(prefix, 0, sizeof(*prefix)); +- +- if (prefix_len <= 0) +- return; +- if (prefix_len > 128) +- prefix_len = 128; +- +- ncopy = prefix_len / 32; +- switch (ncopy) { +- case 4: prefix->s6_addr32[3] = addr->s6_addr32[3]; +- case 3: prefix->s6_addr32[2] = addr->s6_addr32[2]; +- case 2: prefix->s6_addr32[1] = addr->s6_addr32[1]; +- case 1: prefix->s6_addr32[0] = addr->s6_addr32[0]; +- case 0: break; +- } +- nbits = prefix_len % 32; +- if (nbits == 0) +- return; +- +- mask = ~((1 << (32 - nbits)) - 1); +- mask = htonl(mask); +- +- prefix->s6_addr32[ncopy] = addr->s6_addr32[ncopy] & mask; +-} +- +- + static void dev_forward_change(struct inet6_dev *idev) + { + struct net_device *dev; +@@ -513,7 +483,7 @@ + + /* This function wants to get referenced ifp and releases it before return */ + +-static void ipv6_del_addr(struct inet6_ifaddr *ifp) ++void ipv6_del_addr(struct inet6_ifaddr *ifp) + { + struct inet6_ifaddr *ifa, **ifap; + struct inet6_dev *idev = ifp->idev; +@@ -662,6 +632,12 @@ + if (match) + in6_ifa_put(match); + ++ /* The home address is always used as source address in ++ * MIPL mobile IPv6 ++ */ ++ if (scope != IFA_HOST && scope != IFA_LINK) ++ addrconf_get_mipv6_home_address(saddr); ++ + return err; + } + +@@ -815,7 +791,7 @@ + } + + +-static int ipv6_generate_eui64(u8 *eui, struct net_device *dev) ++int ipv6_generate_eui64(u8 *eui, struct net_device *dev) + { + switch (dev->type) { + case ARPHRD_ETHER: +@@ -840,7 +816,7 @@ + return -1; + } + +-static int ipv6_inherit_eui64(u8 *eui, struct inet6_dev *idev) ++int ipv6_inherit_eui64(u8 *eui, struct inet6_dev *idev) + { + int err = -1; + struct inet6_ifaddr *ifp; +@@ -1407,6 +1383,24 @@ + sit_route_add(dev); + } + ++/** ++ * addrconf_ipv6_tunnel_config - configure IPv6 tunnel device ++ * @dev: tunnel device ++ **/ ++ ++static void addrconf_ipv6_tunnel_config(struct net_device *dev) ++{ ++ struct inet6_dev *idev; ++ ++ ASSERT_RTNL(); ++ ++ /* Assign inet6_dev structure to tunnel device */ ++ if ((idev = ipv6_find_idev(dev)) == NULL) { ++ printk(KERN_DEBUG "init ipv6 tunnel: add_dev failed\n"); ++ return; ++ } ++} ++ + + int addrconf_notify(struct notifier_block *this, unsigned long event, + void * data) +@@ -1421,6 +1415,10 @@ + addrconf_sit_config(dev); + break; + ++ case ARPHRD_TUNNEL6: ++ addrconf_ipv6_tunnel_config(dev); ++ break; ++ + case ARPHRD_LOOPBACK: + init_loopback(dev); + break; +@@ -1602,7 +1600,7 @@ + /* + * Duplicate Address Detection + */ +-static void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags) ++void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags) + { + struct net_device *dev; + unsigned long rand_num; +@@ -1667,7 +1665,7 @@ + in6_ifa_put(ifp); + } + +-static void addrconf_dad_completed(struct inet6_ifaddr *ifp) ++void addrconf_dad_completed(struct inet6_ifaddr *ifp) + { + struct net_device * dev = ifp->idev->dev; + +@@ -1676,7 +1674,7 @@ + */ + + ipv6_ifa_notify(RTM_NEWADDR, ifp); +- ++ notifier_call_chain(&inet6addr_chain,NETDEV_UP,ifp); + /* If added prefix is link local and forwarding is off, + start sending router solicitations. + */ +@@ -1877,8 +1875,20 @@ + if (rta[IFA_LOCAL-1]) { + if (pfx && memcmp(pfx, RTA_DATA(rta[IFA_LOCAL-1]), sizeof(*pfx))) + return -EINVAL; ++ if (ifm->ifa_flags & IFA_F_HOMEADDR && !rta[IFA_HOMEAGENT-1]) ++ return -EINVAL; + pfx = RTA_DATA(rta[IFA_LOCAL-1]); + } ++ if (rta[IFA_HOMEAGENT-1]) { ++ struct in6_addr *ha; ++ if (pfx == NULL || !(ifm->ifa_flags & IFA_F_HOMEADDR)) ++ return -EINVAL; ++ if (RTA_PAYLOAD(rta[IFA_HOMEAGENT-1]) < sizeof(*ha)) ++ return -EINVAL; ++ ha = RTA_DATA(rta[IFA_HOMEAGENT-1]); ++ addrconf_set_mipv6_mn_home(ifm->ifa_index, pfx, ifm->ifa_prefixlen, ++ ha, ifm->ifa_prefixlen); ++ } + if (pfx == NULL) + return -EINVAL; + +--- linux-2.4.27/net/ipv6/af_inet6.c~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/ipv6/af_inet6.c +@@ -58,6 +58,9 @@ + #include + #include + #include ++#ifdef CONFIG_IPV6_TUNNEL ++#include ++#endif + + #include + #include +@@ -646,6 +649,11 @@ + err = ndisc_init(&inet6_family_ops); + if (err) + goto ndisc_fail; ++#ifdef CONFIG_IPV6_TUNNEL ++ err = ip6_tunnel_init(); ++ if (err) ++ goto ip6_tunnel_fail; ++#endif + err = igmp6_init(&inet6_family_ops); + if (err) + goto igmp_fail; +@@ -698,6 +706,10 @@ + #endif + igmp_fail: + ndisc_cleanup(); ++#ifdef CONFIG_IPV6_TUNNEL ++ ip6_tunnel_cleanup(); ++ip6_tunnel_fail: ++#endif + ndisc_fail: + icmpv6_cleanup(); + icmp_fail: +@@ -730,6 +742,9 @@ + ip6_route_cleanup(); + ipv6_packet_cleanup(); + igmp6_cleanup(); ++#ifdef CONFIG_IPV6_TUNNEL ++ ip6_tunnel_cleanup(); ++#endif + ndisc_cleanup(); + icmpv6_cleanup(); + #ifdef CONFIG_SYSCTL +--- linux-2.4.27/net/ipv6/exthdrs.c~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/ipv6/exthdrs.c +@@ -41,6 +41,9 @@ + #include + #include + ++#include ++#include ++ + #include + + /* +@@ -160,7 +163,8 @@ + *****************************/ + + struct tlvtype_proc tlvprocdestopt_lst[] = { +- /* No destination options are defined now */ ++ /* Mobility Support destination options */ ++ {MIPV6_TLV_HOMEADDR, mipv6_handle_dstopt}, + {-1, NULL} + }; + +@@ -210,6 +214,7 @@ + + struct ipv6_rt_hdr *hdr; + struct rt0_hdr *rthdr; ++ struct rt2_hdr *rt2hdr; + + if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8) || + !pskb_may_pull(skb, (skb->h.raw-skb->data)+((skb->h.raw[1]+1)<<3))) { +@@ -225,17 +230,25 @@ + kfree_skb(skb); + return -1; + } +- ++ /* Silently discard invalid packets containing RTH type 2 */ ++ if (hdr->type == IPV6_SRCRT_TYPE_2 && ++ (hdr->hdrlen != 2 || hdr->segments_left != 1)) { ++ kfree_skb(skb); ++ return -1; ++ } + looped_back: + if (hdr->segments_left == 0) { +- opt->srcrt = skb->h.raw - skb->nh.raw; ++ if (hdr->type == IPV6_SRCRT_TYPE_0) ++ opt->srcrt = skb->h.raw - skb->nh.raw; ++ else if (hdr->type == IPV6_SRCRT_TYPE_2) ++ opt->srcrt2 = skb->h.raw - skb->nh.raw; + skb->h.raw += (hdr->hdrlen + 1) << 3; + opt->dst0 = opt->dst1; + opt->dst1 = 0; + return (&hdr->nexthdr) - skb->nh.raw; + } + +- if (hdr->type != IPV6_SRCRT_TYPE_0) { ++ if (hdr->type != IPV6_SRCRT_TYPE_0 && hdr->type != IPV6_SRCRT_TYPE_2) { + icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, (&hdr->type) - skb->nh.raw); + return -1; + } +@@ -275,9 +288,20 @@ + + i = n - --hdr->segments_left; + +- rthdr = (struct rt0_hdr *) hdr; +- addr = rthdr->addr; +- addr += i - 1; ++ if (hdr->type == IPV6_SRCRT_TYPE_0) { ++ rthdr = (struct rt0_hdr *) hdr; ++ addr = rthdr->addr; ++ addr += i - 1; ++ } else { ++ /* check that address is this node's home address */ ++ rt2hdr = (struct rt2_hdr *) hdr; ++ addr = &rt2hdr->addr; ++ if (!ipv6_chk_addr(addr, NULL) || ++ !ipv6_chk_mip_home_addr(addr)) { ++ kfree_skb(skb); ++ return -1; ++ } ++ } + + addr_type = ipv6_addr_type(addr); + +@@ -330,6 +354,10 @@ + temporary (or permanent) backdoor. + If listening socket set IPV6_RTHDR to 2, then we invert header. + --ANK (980729) ++ ++ By the Mobile IPv6 specification Type 2 routing header MUST NOT be ++ inverted. ++ --AJT (20020917) + */ + + struct ipv6_txoptions * +@@ -352,6 +380,18 @@ + struct ipv6_txoptions *opt; + int hdrlen = ipv6_optlen(hdr); + ++ if (hdr->type == IPV6_SRCRT_TYPE_2) { ++ opt = sock_kmalloc(sk, sizeof(*opt) + hdrlen, GFP_ATOMIC); ++ if (opt == NULL) ++ return NULL; ++ memset(opt, 0, sizeof(*opt)); ++ opt->tot_len = sizeof(*opt) + hdrlen; ++ opt->srcrt = (void*)(opt+1); ++ opt->opt_nflen = hdrlen; ++ memcpy(opt->srcrt, hdr, sizeof(struct rt2_hdr)); ++ return opt; ++ } ++ + if (hdr->segments_left || + hdr->type != IPV6_SRCRT_TYPE_0 || + hdr->hdrlen & 0x01) +@@ -622,8 +662,18 @@ + if (opt) { + if (opt->dst0opt) + prev_hdr = ipv6_build_exthdr(skb, prev_hdr, NEXTHDR_DEST, opt->dst0opt); +- if (opt->srcrt) +- prev_hdr = ipv6_build_rthdr(skb, prev_hdr, opt->srcrt, daddr); ++ if (opt->srcrt) { ++ if (opt->srcrt2) { ++ struct in6_addr *rt2_hop = &((struct rt2_hdr *)opt->srcrt2)->addr; ++ prev_hdr = ipv6_build_rthdr(skb, prev_hdr, opt->srcrt, rt2_hop); ++ } else ++ prev_hdr = ipv6_build_rthdr(skb, prev_hdr, opt->srcrt, daddr); ++ } ++ if (opt->srcrt2) { ++ struct inet6_skb_parm *parm = (struct inet6_skb_parm *)skb->cb; ++ ipv6_addr_copy(&parm->hoa, daddr); ++ prev_hdr = ipv6_build_rthdr(skb, prev_hdr, opt->srcrt2, daddr); ++ } + } + return prev_hdr; + } +@@ -684,6 +734,11 @@ + u8 *proto, + struct in6_addr **daddr) + { ++ if (opt->srcrt2) { ++ struct inet6_skb_parm *parm = (struct inet6_skb_parm *)skb->cb; ++ ipv6_addr_copy(&parm->hoa, *daddr); ++ ipv6_push_rthdr(skb, proto, opt->srcrt2, daddr); ++ } + if (opt->srcrt) + ipv6_push_rthdr(skb, proto, opt->srcrt, daddr); + if (opt->dst0opt) +@@ -719,6 +774,8 @@ + *((char**)&opt2->auth) += dif; + if (opt2->srcrt) + *((char**)&opt2->srcrt) += dif; ++ if (opt2->srcrt2) ++ *((char**)&opt2->srcrt2) += dif; + } + return opt2; + } +--- linux-2.4.27/net/ipv6/icmp.c~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/ipv6/icmp.c +@@ -61,6 +61,8 @@ + #include + #include + ++#include ++ + #include + #include + +@@ -364,6 +366,8 @@ + + msg.len = len; + ++ icmpv6_swap_mipv6_addrs(skb); ++ + ip6_build_xmit(sk, icmpv6_getfrag, &msg, &fl, len, NULL, -1, + MSG_DONTWAIT); + if (type >= ICMPV6_DEST_UNREACH && type <= ICMPV6_PARAMPROB) +@@ -562,13 +566,13 @@ + rt6_pmtu_discovery(&orig_hdr->daddr, &orig_hdr->saddr, dev, + ntohl(hdr->icmp6_mtu)); + +- /* +- * Drop through to notify +- */ ++ icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu); ++ break; + + case ICMPV6_DEST_UNREACH: +- case ICMPV6_TIME_EXCEED: + case ICMPV6_PARAMPROB: ++ mipv6_icmp_rcv(skb); ++ case ICMPV6_TIME_EXCEED: + icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu); + break; + +@@ -598,6 +602,13 @@ + case ICMPV6_MLD2_REPORT: + break; + ++ case MIPV6_DHAAD_REQUEST: ++ case MIPV6_DHAAD_REPLY: ++ case MIPV6_PREFIX_SOLICIT: ++ case MIPV6_PREFIX_ADV: ++ mipv6_icmp_rcv(skb); ++ break; ++ + default: + if (net_ratelimit()) + printk(KERN_DEBUG "icmpv6: msg of unkown type\n"); +--- linux-2.4.27/net/ipv6/ip6_fib.c~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/ipv6/ip6_fib.c +@@ -18,6 +18,7 @@ + * Yuji SEKIYA @USAGI: Support default route on router node; + * remove ip6_null_entry from the top of + * routing table. ++ * Ville Nuorvala: Fixes to source address based routing + */ + #include + #include +@@ -40,7 +41,6 @@ + #include + + #define RT6_DEBUG 2 +-#undef CONFIG_IPV6_SUBTREES + + #if RT6_DEBUG >= 3 + #define RT6_TRACE(x...) printk(KERN_DEBUG x) +@@ -500,6 +500,8 @@ + mod_timer(&ip6_fib_timer, jiffies + ip6_rt_gc_interval); + } + ++static struct rt6_info * fib6_find_prefix(struct fib6_node *fn); ++ + /* + * Add routing information to the routing tree. + * / +@@ -508,17 +510,19 @@ + + int fib6_add(struct fib6_node *root, struct rt6_info *rt, struct nlmsghdr *nlh) + { +- struct fib6_node *fn; ++ struct fib6_node *fn = root; + int err = -ENOMEM; + +- fn = fib6_add_1(root, &rt->rt6i_dst.addr, sizeof(struct in6_addr), +- rt->rt6i_dst.plen, (u8*) &rt->rt6i_dst - (u8*) rt); ++#ifdef CONFIG_IPV6_SUBTREES ++ struct fib6_node *pn = NULL; + ++ fn = fib6_add_1(root, &rt->rt6i_src.addr, sizeof(struct in6_addr), ++ rt->rt6i_src.plen, (u8*) &rt->rt6i_src - (u8*) rt); ++ + if (fn == NULL) + goto out; + +-#ifdef CONFIG_IPV6_SUBTREES +- if (rt->rt6i_src.plen) { ++ if (rt->rt6i_dst.plen) { + struct fib6_node *sn; + + if (fn->subtree == NULL) { +@@ -546,9 +550,9 @@ + + /* Now add the first leaf node to new subtree */ + +- sn = fib6_add_1(sfn, &rt->rt6i_src.addr, +- sizeof(struct in6_addr), rt->rt6i_src.plen, +- (u8*) &rt->rt6i_src - (u8*) rt); ++ sn = fib6_add_1(sfn, &rt->rt6i_dst.addr, ++ sizeof(struct in6_addr), rt->rt6i_dst.plen, ++ (u8*) &rt->rt6i_dst - (u8*) rt); + + if (sn == NULL) { + /* If it is failed, discard just allocated +@@ -562,21 +566,30 @@ + /* Now link new subtree to main tree */ + sfn->parent = fn; + fn->subtree = sfn; +- if (fn->leaf == NULL) { +- fn->leaf = rt; +- atomic_inc(&rt->rt6i_ref); +- } + } else { +- sn = fib6_add_1(fn->subtree, &rt->rt6i_src.addr, +- sizeof(struct in6_addr), rt->rt6i_src.plen, +- (u8*) &rt->rt6i_src - (u8*) rt); ++ sn = fib6_add_1(fn->subtree, &rt->rt6i_dst.addr, ++ sizeof(struct in6_addr), rt->rt6i_dst.plen, ++ (u8*) &rt->rt6i_dst - (u8*) rt); + + if (sn == NULL) + goto st_failure; + } + ++ /* fib6_add_1 might have cleared the old leaf pointer */ ++ if (fn->leaf == NULL) { ++ fn->leaf = rt; ++ atomic_inc(&rt->rt6i_ref); ++ } ++ ++ pn = fn; + fn = sn; + } ++#else ++ fn = fib6_add_1(root, &rt->rt6i_dst.addr, sizeof(struct in6_addr), ++ rt->rt6i_dst.plen, (u8*) &rt->rt6i_dst - (u8*) rt); ++ ++ if (fn == NULL) ++ goto out; + #endif + + err = fib6_add_rt2node(fn, rt, nlh); +@@ -588,8 +601,30 @@ + } + + out: +- if (err) ++ if (err) { ++#ifdef CONFIG_IPV6_SUBTREES ++ ++ /* If fib6_add_1 has cleared the old leaf pointer in the ++ super-tree leaf node, we have to find a new one for it. ++ ++ This situation will never arise in the sub-tree since ++ the node will at least have the route that caused ++ fib6_add_rt2node to fail. ++ */ ++ ++ if (pn && !(pn->fn_flags & RTN_RTINFO)) { ++ pn->leaf = fib6_find_prefix(pn); ++#if RT6_DEBUG >= 2 ++ if (!pn->leaf) { ++ BUG_TRAP(pn->leaf); ++ pn->leaf = &ip6_null_entry; ++ } ++#endif ++ atomic_inc(&pn->leaf->rt6i_ref); ++ } ++#endif + dst_free(&rt->u.dst); ++ } + return err; + + #ifdef CONFIG_IPV6_SUBTREES +@@ -597,8 +632,8 @@ + is orphan. If it is, shoot it. + */ + st_failure: +- if (fn && !(fn->fn_flags&RTN_RTINFO|RTN_ROOT)) +- fib_repair_tree(fn); ++ if (fn && !(fn->fn_flags & (RTN_RTINFO | RTN_ROOT))) ++ fib6_repair_tree(fn); + dst_free(&rt->u.dst); + return err; + #endif +@@ -641,22 +676,28 @@ + break; + } + +- while ((fn->fn_flags & RTN_ROOT) == 0) { ++ for (;;) { + #ifdef CONFIG_IPV6_SUBTREES + if (fn->subtree) { +- struct fib6_node *st; +- struct lookup_args *narg; +- +- narg = args + 1; +- +- if (narg->addr) { +- st = fib6_lookup_1(fn->subtree, narg); ++ struct rt6key *key; + +- if (st && !(st->fn_flags & RTN_ROOT)) +- return st; ++ key = (struct rt6key *) ((u8 *) fn->leaf + ++ args->offset); ++ ++ if (addr_match(&key->addr, args->addr, key->plen)) { ++ struct fib6_node *st; ++ struct lookup_args *narg = args + 1; ++ if (!ipv6_addr_any(narg->addr)) { ++ st = fib6_lookup_1(fn->subtree, narg); ++ ++ if (st && !(st->fn_flags & RTN_ROOT)) ++ return st; ++ } + } + } + #endif ++ if (fn->fn_flags & RTN_ROOT) ++ break; + + if (fn->fn_flags & RTN_RTINFO) { + struct rt6key *key; +@@ -680,13 +721,22 @@ + struct lookup_args args[2]; + struct rt6_info *rt = NULL; + struct fib6_node *fn; ++#ifdef CONFIG_IPV6_SUBTREES ++ struct in6_addr saddr_buf; ++#endif + ++#ifdef CONFIG_IPV6_SUBTREES ++ if (saddr == NULL) { ++ memset(&saddr_buf, 0, sizeof(struct in6_addr)); ++ saddr = &saddr_buf; ++ } ++ args[0].offset = (u8*) &rt->rt6i_src - (u8*) rt; ++ args[0].addr = saddr; ++ args[1].offset = (u8*) &rt->rt6i_dst - (u8*) rt; ++ args[1].addr = daddr; ++#else + args[0].offset = (u8*) &rt->rt6i_dst - (u8*) rt; + args[0].addr = daddr; +- +-#ifdef CONFIG_IPV6_SUBTREES +- args[1].offset = (u8*) &rt->rt6i_src - (u8*) rt; +- args[1].addr = saddr; + #endif + + fn = fib6_lookup_1(root, args); +@@ -739,19 +789,25 @@ + { + struct rt6_info *rt = NULL; + struct fib6_node *fn; +- +- fn = fib6_locate_1(root, daddr, dst_len, +- (u8*) &rt->rt6i_dst - (u8*) rt); +- + #ifdef CONFIG_IPV6_SUBTREES +- if (src_len) { +- BUG_TRAP(saddr!=NULL); +- if (fn == NULL) +- fn = fn->subtree; ++ struct in6_addr saddr_buf; ++ ++ if (saddr == NULL) { ++ memset(&saddr_buf, 0, sizeof(struct in6_addr)); ++ saddr = &saddr_buf; ++ } ++ fn = fib6_locate_1(root, saddr, src_len, ++ (u8*) &rt->rt6i_src - (u8*) rt); ++ if (dst_len) { + if (fn) +- fn = fib6_locate_1(fn, saddr, src_len, +- (u8*) &rt->rt6i_src - (u8*) rt); ++ fn = fib6_locate_1(fn->subtree, daddr, dst_len, ++ (u8*) &rt->rt6i_dst - (u8*) rt); ++ else ++ return NULL; + } ++#else ++ fn = fib6_locate_1(root, daddr, dst_len, ++ (u8*) &rt->rt6i_dst - (u8*) rt); + #endif + + if (fn && fn->fn_flags&RTN_RTINFO) +@@ -939,7 +995,7 @@ + } + fn = fn->parent; + } +- /* No more references are possiible at this point. */ ++ /* No more references are possible at this point. */ + if (atomic_read(&rt->rt6i_ref) != 1) BUG(); + } + +--- linux-2.4.27/net/ipv6/ip6_input.c~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/ipv6/ip6_input.c +@@ -40,13 +40,42 @@ + #include + #include + ++static inline int ip6_proxy_chk(struct sk_buff *skb) ++{ ++ struct ipv6hdr *hdr = skb->nh.ipv6h; ++ ++ if (ipv6_addr_type(&hdr->daddr)&IPV6_ADDR_UNICAST && ++ pneigh_lookup(&nd_tbl, &hdr->daddr, skb->dev, 0)) { ++ u8 nexthdr = hdr->nexthdr; ++ int offset; ++ struct icmp6hdr msg; + ++ if (ipv6_ext_hdr(nexthdr)) { ++ offset = ipv6_skip_exthdr(skb, sizeof(*hdr), &nexthdr, ++ skb->len - sizeof(*hdr)); ++ if (offset < 0) ++ return 0; ++ } else ++ offset = sizeof(*hdr); ++ ++ /* capture unicast NUD probes on behalf of the proxied node */ + ++ if (nexthdr == IPPROTO_ICMPV6 && ++ !skb_copy_bits(skb, offset, &msg, sizeof(msg)) && ++ msg.icmp6_type == NDISC_NEIGHBOUR_SOLICITATION) { ++ return 1; ++ } ++ } ++ return 0; ++} ++ + static inline int ip6_rcv_finish( struct sk_buff *skb) + { +- if (skb->dst == NULL) +- ip6_route_input(skb); +- ++ if (skb->dst == NULL) { ++ if (ip6_proxy_chk(skb)) ++ return ip6_input(skb); ++ ip6_route_input(skb); ++ } + return skb->dst->input(skb); + } + +--- linux-2.4.27/net/ipv6/ip6_output.c~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/ipv6/ip6_output.c +@@ -50,6 +50,8 @@ + #include + #include + ++#include ++ + static __inline__ void ipv6_select_ident(struct sk_buff *skb, struct frag_hdr *fhdr) + { + static u32 ipv6_fragmentation_id = 1; +@@ -194,7 +196,14 @@ + u8 proto = fl->proto; + int seg_len = skb->len; + int hlimit; ++ int retval; ++ struct ipv6_txoptions *orig_opt = opt; ++ ++ opt = ip6_add_mipv6_txoptions(sk, skb, orig_opt, fl, &dst); + ++ if(orig_opt && !opt) ++ return -ENOMEM; ++ + if (opt) { + int head_room; + +@@ -209,8 +218,11 @@ + struct sk_buff *skb2 = skb_realloc_headroom(skb, head_room); + kfree_skb(skb); + skb = skb2; +- if (skb == NULL) ++ if (skb == NULL) { ++ ip6_free_mipv6_txoptions(opt, orig_opt); ++ + return -ENOBUFS; ++ } + if (sk) + skb_set_owner_w(skb, sk); + } +@@ -242,7 +254,10 @@ + + if (skb->len <= dst->pmtu) { + IP6_INC_STATS(Ip6OutRequests); +- return NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, ip6_maybe_reroute); ++ ip6_mark_mipv6_packet(opt, skb); ++ retval = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, ip6_maybe_reroute); ++ ip6_free_mipv6_txoptions(opt, orig_opt); ++ return retval; + } + + if (net_ratelimit()) +@@ -250,6 +265,9 @@ + skb->dev = dst->dev; + icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, dst->pmtu, skb->dev); + kfree_skb(skb); ++ ++ ip6_free_mipv6_txoptions(opt, orig_opt); ++ + return -EMSGSIZE; + } + +@@ -473,6 +491,7 @@ + + IP6_INC_STATS(Ip6FragCreates); + IP6_INC_STATS(Ip6OutRequests); ++ ip6_mark_mipv6_packet(opt, skb); + err = NF_HOOK(PF_INET6,NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, ip6_maybe_reroute); + if (err) { + kfree_skb(last_skb); +@@ -499,6 +518,7 @@ + IP6_INC_STATS(Ip6FragCreates); + IP6_INC_STATS(Ip6FragOKs); + IP6_INC_STATS(Ip6OutRequests); ++ ip6_mark_mipv6_packet(opt, last_skb); + return NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, last_skb, NULL,dst->dev, ip6_maybe_reroute); + } + +@@ -509,26 +529,43 @@ + struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6; + struct in6_addr *final_dst = NULL; + struct dst_entry *dst; ++ struct rt6_info *rt; + int err = 0; + unsigned int pktlength, jumbolen, mtu; + struct in6_addr saddr; ++ struct ipv6_txoptions *orig_opt = opt; ++#ifdef CONFIG_IPV6_SUBTREES ++ struct dst_entry *org_dst; ++#endif ++ ++ opt = ip6_add_mipv6_txoptions(sk, NULL, orig_opt, fl, NULL); ++ ++ if(orig_opt && !opt) ++ return -ENOMEM; + + if (opt && opt->srcrt) { + struct rt0_hdr *rt0 = (struct rt0_hdr *) opt->srcrt; + final_dst = fl->fl6_dst; + fl->fl6_dst = rt0->addr; +- } ++ } else if (opt && opt->srcrt2) { ++ struct rt2_hdr *rt2 = (struct rt2_hdr *) opt->srcrt2; ++ final_dst = fl->fl6_dst; ++ fl->fl6_dst = &rt2->addr; ++ } + + if (!fl->oif && ipv6_addr_is_multicast(fl->nl_u.ip6_u.daddr)) + fl->oif = np->mcast_oif; + + dst = __sk_dst_check(sk, np->dst_cookie); ++#ifdef CONFIG_IPV6_SUBTREES ++ org_dst = dst; ++#endif + if (dst) { +- struct rt6_info *rt = (struct rt6_info*)dst; ++ rt = (struct rt6_info*)dst; + + /* Yes, checking route validity in not connected + case is not very simple. Take into account, +- that we do not support routing by source, TOS, ++ that we do not support routing by TOS, + and MSG_DONTROUTE --ANK (980726) + + 1. If route was host route, check that +@@ -548,6 +585,13 @@ + ipv6_addr_cmp(fl->fl6_dst, &rt->rt6i_dst.addr)) + && (np->daddr_cache == NULL || + ipv6_addr_cmp(fl->fl6_dst, np->daddr_cache))) ++#ifdef CONFIG_IPV6_SUBTREES ++ || (fl->fl6_src != NULL ++ && (rt->rt6i_src.plen != 128 || ++ ipv6_addr_cmp(fl->fl6_src, &rt->rt6i_src.addr)) ++ && (np->saddr_cache == NULL || ++ ipv6_addr_cmp(fl->fl6_src, np->saddr_cache))) ++#endif + || (fl->oif && fl->oif != dst->dev->ifindex)) { + dst = NULL; + } else +@@ -560,21 +604,42 @@ + if (dst->error) { + IP6_INC_STATS(Ip6OutNoRoutes); + dst_release(dst); ++ ip6_free_mipv6_txoptions(opt, orig_opt); + return -ENETUNREACH; + } + + if (fl->fl6_src == NULL) { + err = ipv6_get_saddr(dst, fl->fl6_dst, &saddr); +- + if (err) { + #if IP6_DEBUG >= 2 + printk(KERN_DEBUG "ip6_build_xmit: " + "no available source address\n"); + #endif ++ ++#ifdef CONFIG_IPV6_SUBTREES ++ if (dst != org_dst) { ++ dst_release(dst); ++ dst = org_dst; ++ } ++#endif + goto out; + } + fl->fl6_src = &saddr; + } ++#ifdef CONFIG_IPV6_SUBTREES ++ rt = (struct rt6_info*)dst; ++ if (dst != org_dst || rt->rt6i_src.plen != 128 || ++ ipv6_addr_cmp(fl->fl6_src, &rt->rt6i_src.addr)) { ++ dst_release(dst); ++ dst = ip6_route_output(sk, fl); ++ if (dst->error) { ++ IP6_INC_STATS(Ip6OutNoRoutes); ++ dst_release(dst); ++ ip6_free_mipv6_txoptions(opt, orig_opt); ++ return -ENETUNREACH; ++ } ++ } ++#endif + pktlength = length; + + if (hlimit < 0) { +@@ -667,6 +732,7 @@ + + if (!err) { + IP6_INC_STATS(Ip6OutRequests); ++ ip6_mark_mipv6_packet(opt, skb); + err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, ip6_maybe_reroute); + } else { + err = -EFAULT; +@@ -688,9 +754,14 @@ + * cleanup + */ + out: +- ip6_dst_store(sk, dst, fl->nl_u.ip6_u.daddr == &np->daddr ? &np->daddr : NULL); ++ ip6_dst_store(sk, dst, ++ fl->nl_u.ip6_u.daddr == &np->daddr ? &np->daddr : NULL, ++ fl->nl_u.ip6_u.saddr == &np->saddr ? &np->saddr : NULL); + if (err > 0) + err = np->recverr ? net_xmit_errno(err) : 0; ++ ++ ip6_free_mipv6_txoptions(opt, orig_opt); ++ + return err; + } + +@@ -769,6 +840,15 @@ + return -ETIMEDOUT; + } + ++ /* The proxying router can't forward traffic sent to a link-local ++ address, so signal the sender and discard the packet. This ++ behavior is required by the MIPv6 specification. */ ++ ++ if (ipv6_addr_type(&hdr->daddr) & IPV6_ADDR_LINKLOCAL && ++ skb->dev && pneigh_lookup(&nd_tbl, &hdr->daddr, skb->dev, 0)) { ++ dst_link_failure(skb); ++ goto drop; ++ } + /* IPv6 specs say nothing about it, but it is clear that we cannot + send redirects to source routed frames. + */ +--- linux-2.4.27/net/ipv6/ipv6_syms.c~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/ipv6/ipv6_syms.c +@@ -6,6 +6,8 @@ + #include + #include + #include ++#include ++#include + + EXPORT_SYMBOL(ipv6_addr_type); + EXPORT_SYMBOL(icmpv6_send); +@@ -35,3 +37,47 @@ + EXPORT_SYMBOL(in6_dev_finish_destroy); + EXPORT_SYMBOL(ipv6_skip_exthdr); + ++#if defined(CONFIG_IPV6_TUNNEL_MODULE) || defined(CONFIG_IPV6_MOBILITY_MODULE) ++EXPORT_SYMBOL(ip6_build_xmit); ++EXPORT_SYMBOL(rt6_lookup); ++EXPORT_SYMBOL(ipv6_ext_hdr); ++#endif ++#ifdef CONFIG_IPV6_MOBILITY_MODULE ++EXPORT_SYMBOL(mipv6_functions); ++EXPORT_SYMBOL(mipv6_invalidate_calls); ++#if defined(CONFIG_IPV6_MOBILITY_HA_MODULE) || defined(CONFIG_IPV6_MOBILITY_MN_MODULE) ++EXPORT_SYMBOL(ip6_route_add); ++EXPORT_SYMBOL(ip6_route_del); ++EXPORT_SYMBOL(ipv6_get_lladdr); ++EXPORT_SYMBOL(ipv6_get_ifaddr); ++EXPORT_SYMBOL(nd_tbl); ++EXPORT_SYMBOL(ndisc_send_ns); ++EXPORT_SYMBOL(ndisc_send_na); ++EXPORT_SYMBOL(ndisc_next_option); ++EXPORT_SYMBOL(inet6_ifa_finish_destroy); ++#endif ++#ifdef CONFIG_IPV6_MOBILITY_HA_MODULE ++EXPORT_SYMBOL(ipv6_dev_ac_dec); ++EXPORT_SYMBOL(ipv6_dev_ac_inc); ++EXPORT_SYMBOL(ipv6_dev_mc_dec); ++EXPORT_SYMBOL(ipv6_dev_mc_inc); ++EXPORT_SYMBOL(ip6_forward); ++EXPORT_SYMBOL(ip6_input); ++EXPORT_SYMBOL(ipv6_chk_acast_addr); ++#endif ++#ifdef CONFIG_IPV6_MOBILITY_MN_MODULE ++#endif ++EXPORT_SYMBOL(addrconf_add_ifaddr); ++EXPORT_SYMBOL(addrconf_del_ifaddr); ++EXPORT_SYMBOL(addrconf_dad_start); ++EXPORT_SYMBOL(ip6_del_rt); ++EXPORT_SYMBOL(ip6_routing_table); ++EXPORT_SYMBOL(rt6_get_dflt_router); ++EXPORT_SYMBOL(rt6_purge_dflt_routers); ++EXPORT_SYMBOL(rt6_lock); ++EXPORT_SYMBOL(ndisc_send_rs); ++EXPORT_SYMBOL(fib6_clean_tree); ++EXPORT_SYMBOL(ipv6_del_addr); ++EXPORT_SYMBOL(ipv6_generate_eui64); ++EXPORT_SYMBOL(ipv6_inherit_eui64); ++#endif +--- /dev/null ++++ linux-2.4.27/net/ipv6/ipv6_tunnel.c +@@ -0,0 +1,1604 @@ ++/* ++ * IPv6 over IPv6 tunnel device ++ * Linux INET6 implementation ++ * ++ * Authors: ++ * Ville Nuorvala ++ * ++ * $Id$ ++ * ++ * Based on: ++ * linux/net/ipv6/sit.c ++ * ++ * RFC 2473 ++ * ++ * 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. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++MODULE_AUTHOR("Ville Nuorvala"); ++MODULE_DESCRIPTION("IPv6-in-IPv6 tunnel"); ++MODULE_LICENSE("GPL"); ++ ++#define IPV6_TLV_TEL_DST_SIZE 8 ++ ++#ifdef IP6_TNL_DEBUG ++#define IP6_TNL_TRACE(x...) printk(KERN_DEBUG "%s:" x "\n", __FUNCTION__) ++#else ++#define IP6_TNL_TRACE(x...) do {;} while(0) ++#endif ++ ++#define IPV6_TCLASS_MASK (IPV6_FLOWINFO_MASK & ~IPV6_FLOWLABEL_MASK) ++ ++#define HASH_SIZE 32 ++ ++#define HASH(addr) (((addr)->s6_addr32[0] ^ (addr)->s6_addr32[1] ^ \ ++ (addr)->s6_addr32[2] ^ (addr)->s6_addr32[3]) & \ ++ (HASH_SIZE - 1)) ++ ++static int ip6ip6_fb_tnl_dev_init(struct net_device *dev); ++static int ip6ip6_tnl_dev_init(struct net_device *dev); ++ ++/* the IPv6 IPv6 tunnel fallback device */ ++static struct net_device ip6ip6_fb_tnl_dev = { ++ name: "ip6tnl0", ++ init: ip6ip6_fb_tnl_dev_init ++}; ++ ++/* the IPv6 IPv6 fallback tunnel */ ++static struct ip6_tnl ip6ip6_fb_tnl = { ++ dev: &ip6ip6_fb_tnl_dev, ++ parms:{name: "ip6tnl0", proto: IPPROTO_IPV6} ++}; ++ ++/* lists for storing tunnels in use */ ++static struct ip6_tnl *tnls_r_l[HASH_SIZE]; ++static struct ip6_tnl *tnls_wc[1]; ++static struct ip6_tnl **tnls[2] = { tnls_wc, tnls_r_l }; ++ ++/* list for unused cached kernel tunnels */ ++static struct ip6_tnl *tnls_kernel[1]; ++/* maximum number of cached kernel tunnels */ ++static unsigned int max_kdev_count = 0; ++/* minimum number of cached kernel tunnels */ ++static unsigned int min_kdev_count = 0; ++/* current number of cached kernel tunnels */ ++static unsigned int kdev_count = 0; ++ ++/* lists for tunnel hook functions */ ++static struct list_head hooks[IP6_TNL_MAXHOOKS]; ++ ++/* locks for the different lists */ ++static rwlock_t ip6ip6_lock = RW_LOCK_UNLOCKED; ++static rwlock_t ip6ip6_kernel_lock = RW_LOCK_UNLOCKED; ++static rwlock_t ip6ip6_hook_lock = RW_LOCK_UNLOCKED; ++ ++/* flag indicating if the module is being removed */ ++static int shutdown = 0; ++ ++/** ++ * ip6ip6_tnl_lookup - fetch tunnel matching the end-point addresses ++ * @remote: the address of the tunnel exit-point ++ * @local: the address of the tunnel entry-point ++ * ++ * Return: ++ * tunnel matching given end-points if found, ++ * else fallback tunnel if its device is up, ++ * else %NULL ++ **/ ++ ++struct ip6_tnl * ++ip6ip6_tnl_lookup(struct in6_addr *remote, struct in6_addr *local) ++{ ++ unsigned h0 = HASH(remote); ++ unsigned h1 = HASH(local); ++ struct ip6_tnl *t; ++ ++ for (t = tnls_r_l[h0 ^ h1]; t; t = t->next) { ++ if (!ipv6_addr_cmp(local, &t->parms.laddr) && ++ !ipv6_addr_cmp(remote, &t->parms.raddr) && ++ (t->dev->flags & IFF_UP)) ++ return t; ++ } ++ if ((t = tnls_wc[0]) != NULL && (t->dev->flags & IFF_UP)) ++ return t; ++ ++ return NULL; ++} ++ ++/** ++ * ip6ip6_bucket - get head of list matching given tunnel parameters ++ * @p: parameters containing tunnel end-points ++ * ++ * Description: ++ * ip6ip6_bucket() returns the head of the list matching the ++ * &struct in6_addr entries laddr and raddr in @p. ++ * ++ * Return: head of IPv6 tunnel list ++ **/ ++ ++static struct ip6_tnl ** ++ip6ip6_bucket(struct ip6_tnl_parm *p) ++{ ++ struct in6_addr *remote = &p->raddr; ++ struct in6_addr *local = &p->laddr; ++ unsigned h = 0; ++ int prio = 0; ++ ++ if (!ipv6_addr_any(remote) || !ipv6_addr_any(local)) { ++ prio = 1; ++ h = HASH(remote) ^ HASH(local); ++ } ++ return &tnls[prio][h]; ++} ++ ++/** ++ * ip6ip6_kernel_tnl_link - add new kernel tunnel to cache ++ * @t: kernel tunnel ++ * ++ * Note: ++ * %IP6_TNL_F_KERNEL_DEV is assumed to be raised in t->parms.flags. ++ * See the comments on ip6ip6_kernel_tnl_add() for more information. ++ **/ ++ ++static inline void ++ip6ip6_kernel_tnl_link(struct ip6_tnl *t) ++{ ++ write_lock_bh(&ip6ip6_kernel_lock); ++ t->next = tnls_kernel[0]; ++ tnls_kernel[0] = t; ++ kdev_count++; ++ write_unlock_bh(&ip6ip6_kernel_lock); ++} ++ ++/** ++ * ip6ip6_kernel_tnl_unlink - remove first kernel tunnel from cache ++ * ++ * Return: first free kernel tunnel ++ * ++ * Note: ++ * See the comments on ip6ip6_kernel_tnl_add() for more information. ++ **/ ++ ++static inline struct ip6_tnl * ++ip6ip6_kernel_tnl_unlink(void) ++{ ++ struct ip6_tnl *t; ++ ++ write_lock_bh(&ip6ip6_kernel_lock); ++ if ((t = tnls_kernel[0]) != NULL) { ++ tnls_kernel[0] = t->next; ++ kdev_count--; ++ } ++ write_unlock_bh(&ip6ip6_kernel_lock); ++ return t; ++} ++ ++/** ++ * ip6ip6_tnl_link - add tunnel to hash table ++ * @t: tunnel to be added ++ **/ ++ ++static void ++ip6ip6_tnl_link(struct ip6_tnl *t) ++{ ++ struct ip6_tnl **tp = ip6ip6_bucket(&t->parms); ++ ++ write_lock_bh(&ip6ip6_lock); ++ t->next = *tp; ++ *tp = t; ++ write_unlock_bh(&ip6ip6_lock); ++} ++ ++/** ++ * ip6ip6_tnl_unlink - remove tunnel from hash table ++ * @t: tunnel to be removed ++ **/ ++ ++static void ++ip6ip6_tnl_unlink(struct ip6_tnl *t) ++{ ++ struct ip6_tnl **tp; ++ ++ write_lock_bh(&ip6ip6_lock); ++ for (tp = ip6ip6_bucket(&t->parms); *tp; tp = &(*tp)->next) { ++ if (t == *tp) { ++ *tp = t->next; ++ break; ++ } ++ } ++ write_unlock_bh(&ip6ip6_lock); ++} ++ ++/** ++ * ip6ip6_tnl_create() - create a new tunnel ++ * @p: tunnel parameters ++ * @pt: pointer to new tunnel ++ * ++ * Description: ++ * Create tunnel matching given parameters. New kernel managed devices are ++ * not put in the normal hash structure, but are instead cached for later ++ * use. ++ * ++ * Return: ++ * 0 on success ++ **/ ++ ++ ++static int __ip6ip6_tnl_create(struct ip6_tnl_parm *p, ++ struct ip6_tnl **pt, ++ int kernel_list) ++{ ++ struct net_device *dev; ++ int err = -ENOBUFS; ++ struct ip6_tnl *t; ++ ++ MOD_INC_USE_COUNT; ++ dev = kmalloc(sizeof (*dev) + sizeof (*t), GFP_KERNEL); ++ if (!dev) { ++ MOD_DEC_USE_COUNT; ++ return err; ++ } ++ memset(dev, 0, sizeof (*dev) + sizeof (*t)); ++ dev->priv = (void *) (dev + 1); ++ t = (struct ip6_tnl *) dev->priv; ++ t->dev = dev; ++ dev->init = ip6ip6_tnl_dev_init; ++ dev->features |= NETIF_F_DYNALLOC; ++ if (kernel_list) { ++ memcpy(t->parms.name, p->name, IFNAMSIZ - 1); ++ t->parms.proto = IPPROTO_IPV6; ++ t->parms.flags = IP6_TNL_F_KERNEL_DEV; ++ } else { ++ memcpy(&t->parms, p, sizeof (*p)); ++ } ++ t->parms.name[IFNAMSIZ - 1] = '\0'; ++ strcpy(dev->name, t->parms.name); ++ if (!dev->name[0]) { ++ int i; ++ for (i = 0; i < IP6_TNL_MAX; i++) { ++ sprintf(dev->name, "ip6tnl%d", i); ++ if (__dev_get_by_name(dev->name) == NULL) ++ break; ++ } ++ ++ if (i == IP6_TNL_MAX) { ++ goto failed; ++ } ++ memcpy(t->parms.name, dev->name, IFNAMSIZ); ++ } ++ if ((err = register_netdevice(dev)) < 0) { ++ goto failed; ++ } ++ dev_hold(dev); ++ if (kernel_list) { ++ ip6ip6_kernel_tnl_link(t); ++ } else { ++ ip6ip6_tnl_link(t); ++ } ++ *pt = t; ++ return 0; ++failed: ++ kfree(dev); ++ MOD_DEC_USE_COUNT; ++ return err; ++} ++ ++ ++int ip6ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt) ++{ ++ return __ip6ip6_tnl_create(p, pt, 0); ++} ++ ++ ++static void manage_kernel_tnls(void *foo); ++ ++static struct tq_struct manager_task = { ++ routine:manage_kernel_tnls, ++ data:NULL ++}; ++ ++/** ++ * manage_kernel_tnls() - create and destroy kernel tunnels ++ * ++ * Description: ++ * manage_kernel_tnls() creates new kernel devices if there ++ * are less than $min_kdev_count of them and deletes old ones if ++ * there are less than $max_kdev_count of them in the cache ++ * ++ * Note: ++ * Schedules itself to be run later in process context if called from ++ * interrupt. Therefore only works synchronously when called from process ++ * context. ++ **/ ++ ++static void ++manage_kernel_tnls(void *foo) ++{ ++ struct ip6_tnl *t = NULL; ++ struct ip6_tnl_parm parm; ++ ++ /* We can't do this processing in interrupt ++ context so schedule it for later */ ++ if (in_interrupt()) { ++ read_lock(&ip6ip6_kernel_lock); ++ if (!shutdown && ++ (kdev_count < min_kdev_count || ++ kdev_count > max_kdev_count)) { ++ schedule_task(&manager_task); ++ } ++ read_unlock(&ip6ip6_kernel_lock); ++ return; ++ } ++ ++ rtnl_lock(); ++ read_lock_bh(&ip6ip6_kernel_lock); ++ memset(&parm, 0, sizeof (parm)); ++ parm.flags = IP6_TNL_F_KERNEL_DEV; ++ /* Create tunnels until there are at least min_kdev_count */ ++ while (kdev_count < min_kdev_count) { ++ read_unlock_bh(&ip6ip6_kernel_lock); ++ if (!__ip6ip6_tnl_create(&parm, &t, 1)) { ++ dev_open(t->dev); ++ } else { ++ goto err; ++ } ++ read_lock_bh(&ip6ip6_kernel_lock); ++ } ++ ++ /* Destroy tunnels until there are at most max_kdev_count */ ++ while (kdev_count > max_kdev_count) { ++ read_unlock_bh(&ip6ip6_kernel_lock); ++ if ((t = ip6ip6_kernel_tnl_unlink()) != NULL) { ++ unregister_netdevice(t->dev); ++ } else { ++ goto err; ++ } ++ read_lock_bh(&ip6ip6_kernel_lock); ++ } ++ read_unlock_bh(&ip6ip6_kernel_lock); ++err: ++ rtnl_unlock(); ++} ++ ++/** ++ * ip6ip6_tnl_inc_max_kdev_count() - increase max kernel dev cache size ++ * @n: size increase ++ * Description: ++ * Increase the upper limit for the number of kernel devices allowed in the ++ * cache at any on time. ++ **/ ++ ++unsigned int ++ip6ip6_tnl_inc_max_kdev_count(unsigned int n) ++{ ++ write_lock_bh(&ip6ip6_kernel_lock); ++ max_kdev_count += n; ++ write_unlock_bh(&ip6ip6_kernel_lock); ++ manage_kernel_tnls(NULL); ++ return max_kdev_count; ++} ++ ++/** ++ * ip6ip6_tnl_dec_max_kdev_count() - decrease max kernel dev cache size ++ * @n: size decrement ++ * Description: ++ * Decrease the upper limit for the number of kernel devices allowed in the ++ * cache at any on time. ++ **/ ++ ++unsigned int ++ip6ip6_tnl_dec_max_kdev_count(unsigned int n) ++{ ++ write_lock_bh(&ip6ip6_kernel_lock); ++ max_kdev_count -= min(max_kdev_count, n); ++ if (max_kdev_count < min_kdev_count) ++ min_kdev_count = max_kdev_count; ++ write_unlock_bh(&ip6ip6_kernel_lock); ++ manage_kernel_tnls(NULL); ++ return max_kdev_count; ++} ++ ++/** ++ * ip6ip6_tnl_inc_min_kdev_count() - increase min kernel dev cache size ++ * @n: size increase ++ * Description: ++ * Increase the lower limit for the number of kernel devices allowed in the ++ * cache at any on time. ++ **/ ++ ++unsigned int ++ip6ip6_tnl_inc_min_kdev_count(unsigned int n) ++{ ++ write_lock_bh(&ip6ip6_kernel_lock); ++ min_kdev_count += n; ++ if (min_kdev_count > max_kdev_count) ++ max_kdev_count = min_kdev_count; ++ write_unlock_bh(&ip6ip6_kernel_lock); ++ manage_kernel_tnls(NULL); ++ return min_kdev_count; ++} ++ ++/** ++ * ip6ip6_tnl_dec_min_kdev_count() - decrease min kernel dev cache size ++ * @n: size decrement ++ * Description: ++ * Decrease the lower limit for the number of kernel devices allowed in the ++ * cache at any on time. ++ **/ ++ ++unsigned int ++ip6ip6_tnl_dec_min_kdev_count(unsigned int n) ++{ ++ write_lock_bh(&ip6ip6_kernel_lock); ++ min_kdev_count -= min(min_kdev_count, n); ++ write_unlock_bh(&ip6ip6_kernel_lock); ++ manage_kernel_tnls(NULL); ++ return min_kdev_count; ++} ++ ++/** ++ * ip6ip6_tnl_locate - find or create tunnel matching given parameters ++ * @p: tunnel parameters ++ * @create: != 0 if allowed to create new tunnel if no match found ++ * ++ * Description: ++ * ip6ip6_tnl_locate() first tries to locate an existing tunnel ++ * based on @parms. If this is unsuccessful, but @create is set a new ++ * tunnel device is created and registered for use. ++ * ++ * Return: ++ * 0 if tunnel located or created, ++ * -EINVAL if parameters incorrect, ++ * -ENODEV if no matching tunnel available ++ **/ ++ ++int ip6ip6_tnl_locate(struct ip6_tnl_parm *p, struct ip6_tnl **pt, int create) ++{ ++ struct in6_addr *remote = &p->raddr; ++ struct in6_addr *local = &p->laddr; ++ struct ip6_tnl *t; ++ ++ if (p->proto != IPPROTO_IPV6) ++ return -EINVAL; ++ ++ for (t = *ip6ip6_bucket(p); t; t = t->next) { ++ if (!ipv6_addr_cmp(local, &t->parms.laddr) && ++ !ipv6_addr_cmp(remote, &t->parms.raddr)) { ++ *pt = t; ++ return (create ? -EEXIST : 0); ++ } ++ } ++ return ip6ip6_tnl_create(p, pt); ++} ++ ++/** ++ * ip6ip6_tnl_dev_destructor - tunnel device destructor ++ * @dev: the device to be destroyed ++ **/ ++ ++static void ++ip6ip6_tnl_dev_destructor(struct net_device *dev) ++{ ++ if (dev != &ip6ip6_fb_tnl_dev) { ++ MOD_DEC_USE_COUNT; ++ } ++} ++ ++/** ++ * ip6ip6_tnl_dev_uninit - tunnel device uninitializer ++ * @dev: the device to be destroyed ++ * ++ * Description: ++ * ip6ip6_tnl_dev_uninit() removes tunnel from its list ++ **/ ++ ++static void ++ip6ip6_tnl_dev_uninit(struct net_device *dev) ++{ ++ struct ip6_tnl *t = (struct ip6_tnl *) dev->priv; ++ ++ if (dev == &ip6ip6_fb_tnl_dev) { ++ write_lock_bh(&ip6ip6_lock); ++ tnls_wc[0] = NULL; ++ write_unlock_bh(&ip6ip6_lock); ++ } else { ++ ip6ip6_tnl_unlink(t); ++ } ++ sock_release(t->sock); ++ dev_put(dev); ++} ++ ++/** ++ * parse_tvl_tnl_enc_lim - handle encapsulation limit option ++ * @skb: received socket buffer ++ * ++ * Return: ++ * 0 if none was found, ++ * else index to encapsulation limit ++ **/ ++ ++static __u16 ++parse_tlv_tnl_enc_lim(struct sk_buff *skb, __u8 * raw) ++{ ++ struct ipv6hdr *ipv6h = (struct ipv6hdr *) raw; ++ __u8 nexthdr = ipv6h->nexthdr; ++ __u16 off = sizeof (*ipv6h); ++ ++ while (ipv6_ext_hdr(nexthdr) && nexthdr != NEXTHDR_NONE) { ++ __u16 optlen = 0; ++ struct ipv6_opt_hdr *hdr; ++ if (raw + off + sizeof (*hdr) > skb->data && ++ !pskb_may_pull(skb, raw - skb->data + off + sizeof (*hdr))) ++ break; ++ ++ hdr = (struct ipv6_opt_hdr *) (raw + off); ++ if (nexthdr == NEXTHDR_FRAGMENT) { ++ struct frag_hdr *frag_hdr = (struct frag_hdr *) hdr; ++ if (frag_hdr->frag_off) ++ break; ++ optlen = 8; ++ } else if (nexthdr == NEXTHDR_AUTH) { ++ optlen = (hdr->hdrlen + 2) << 2; ++ } else { ++ optlen = ipv6_optlen(hdr); ++ } ++ if (nexthdr == NEXTHDR_DEST) { ++ __u16 i = off + 2; ++ while (1) { ++ struct ipv6_tlv_tnl_enc_lim *tel; ++ ++ /* No more room for encapsulation limit */ ++ if (i + sizeof (*tel) > off + optlen) ++ break; ++ ++ tel = (struct ipv6_tlv_tnl_enc_lim *) &raw[i]; ++ /* return index of option if found and valid */ ++ if (tel->type == IPV6_TLV_TNL_ENCAP_LIMIT && ++ tel->length == 1) ++ return i; ++ /* else jump to next option */ ++ if (tel->type) ++ i += tel->length + 2; ++ else ++ i++; ++ } ++ } ++ nexthdr = hdr->nexthdr; ++ off += optlen; ++ } ++ return 0; ++} ++ ++/** ++ * ip6ip6_err - tunnel error handler ++ * ++ * Description: ++ * ip6ip6_err() should handle errors in the tunnel according ++ * to the specifications in RFC 2473. ++ **/ ++ ++void ip6ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ++ int type, int code, int offset, __u32 info) ++{ ++ struct ipv6hdr *ipv6h = (struct ipv6hdr *) skb->data; ++ struct ip6_tnl *t; ++ int rel_msg = 0; ++ int rel_type = ICMPV6_DEST_UNREACH; ++ int rel_code = ICMPV6_ADDR_UNREACH; ++ __u32 rel_info = 0; ++ __u16 len; ++ ++ /* If the packet doesn't contain the original IPv6 header we are ++ in trouble since we might need the source address for furter ++ processing of the error. */ ++ ++ read_lock(&ip6ip6_lock); ++ if ((t = ip6ip6_tnl_lookup(&ipv6h->daddr, &ipv6h->saddr)) == NULL) ++ goto out; ++ ++ switch (type) { ++ __u32 teli; ++ struct ipv6_tlv_tnl_enc_lim *tel; ++ __u32 mtu; ++ case ICMPV6_DEST_UNREACH: ++ if (net_ratelimit()) ++ printk(KERN_WARNING ++ "%s: Path to destination invalid " ++ "or inactive!\n", t->parms.name); ++ rel_msg = 1; ++ break; ++ case ICMPV6_TIME_EXCEED: ++ if (code == ICMPV6_EXC_HOPLIMIT) { ++ if (net_ratelimit()) ++ printk(KERN_WARNING ++ "%s: Too small hop limit or " ++ "routing loop in tunnel!\n", ++ t->parms.name); ++ rel_msg = 1; ++ } ++ break; ++ case ICMPV6_PARAMPROB: ++ /* ignore if parameter problem not caused by a tunnel ++ encapsulation limit sub-option */ ++ if (code != ICMPV6_HDR_FIELD) { ++ break; ++ } ++ teli = parse_tlv_tnl_enc_lim(skb, skb->data); ++ ++ if (teli && teli == ntohl(info) - 2) { ++ tel = (struct ipv6_tlv_tnl_enc_lim *) &skb->data[teli]; ++ if (tel->encap_limit == 0) { ++ if (net_ratelimit()) ++ printk(KERN_WARNING ++ "%s: Too small encapsulation " ++ "limit or routing loop in " ++ "tunnel!\n", t->parms.name); ++ rel_msg = 1; ++ } ++ } ++ break; ++ case ICMPV6_PKT_TOOBIG: ++ mtu = ntohl(info) - offset; ++ if (mtu < IPV6_MIN_MTU) ++ mtu = IPV6_MIN_MTU; ++ t->dev->mtu = mtu; ++ ++ if ((len = sizeof (*ipv6h) + ipv6h->payload_len) > mtu) { ++ rel_type = ICMPV6_PKT_TOOBIG; ++ rel_code = 0; ++ rel_info = mtu; ++ rel_msg = 1; ++ } ++ break; ++ } ++ if (rel_msg && pskb_may_pull(skb, offset + sizeof (*ipv6h))) { ++ struct rt6_info *rt; ++ struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC); ++ if (!skb2) ++ goto out; ++ ++ dst_release(skb2->dst); ++ skb2->dst = NULL; ++ skb_pull(skb2, offset); ++ skb2->nh.raw = skb2->data; ++ ++ /* Try to guess incoming interface */ ++ rt = rt6_lookup(&skb2->nh.ipv6h->saddr, NULL, 0, 0); ++ ++ if (rt && rt->rt6i_dev) ++ skb2->dev = rt->rt6i_dev; ++ ++ icmpv6_send(skb2, rel_type, rel_code, rel_info, skb2->dev); ++ ++ if (rt) ++ dst_release(&rt->u.dst); ++ ++ kfree_skb(skb2); ++ } ++out: ++ read_unlock(&ip6ip6_lock); ++} ++ ++/** ++ * call_hooks - call ipv6 tunnel hooks ++ * @hooknum: hook number, either %IP6_TNL_PRE_ENCAP, or ++ * %IP6_TNL_PRE_DECAP ++ * @t: the current tunnel ++ * @skb: the tunneled packet ++ * ++ * Description: ++ * Pass packet to all the hook functions until %IP6_TNL_DROP ++ * ++ * Return: ++ * %IP6_TNL_ACCEPT or %IP6_TNL_DROP ++ **/ ++ ++static inline int ++call_hooks(unsigned int hooknum, struct ip6_tnl *t, struct sk_buff *skb) ++{ ++ struct ip6_tnl_hook_ops *h; ++ int accept = IP6_TNL_ACCEPT; ++ ++ if (hooknum < IP6_TNL_MAXHOOKS) { ++ struct list_head *i; ++ read_lock(&ip6ip6_hook_lock); ++ for (i = hooks[hooknum].next; i != &hooks[hooknum]; i = i->next) { ++ h = (struct ip6_tnl_hook_ops *) i; ++ ++ if (h->hook) { ++ accept = h->hook(t, skb); ++ ++ if (accept != IP6_TNL_ACCEPT) ++ break; ++ } ++ } ++ read_unlock(&ip6ip6_hook_lock); ++ } ++ return accept; ++} ++ ++/** ++ * ip6ip6_rcv - decapsulate IPv6 packet and retransmit it locally ++ * @skb: received socket buffer ++ * ++ * Return: 0 ++ **/ ++ ++int ip6ip6_rcv(struct sk_buff *skb) ++{ ++ struct ipv6hdr *ipv6h; ++ struct ip6_tnl *t; ++ ++ if (!pskb_may_pull(skb, sizeof (*ipv6h))) ++ goto discard; ++ ++ ipv6h = skb->nh.ipv6h; ++ ++ read_lock(&ip6ip6_lock); ++ ++ if ((t = ip6ip6_tnl_lookup(&ipv6h->saddr, &ipv6h->daddr)) != NULL) { ++ if (!(t->parms.flags & IP6_TNL_F_CAP_RCV) || ++ call_hooks(IP6_TNL_PRE_DECAP, t, skb) != IP6_TNL_ACCEPT) { ++ t->stat.rx_dropped++; ++ read_unlock(&ip6ip6_lock); ++ goto discard; ++ } ++ skb->mac.raw = skb->nh.raw; ++ skb->nh.raw = skb->data; ++ skb->protocol = htons(ETH_P_IPV6); ++ skb->pkt_type = PACKET_HOST; ++ memset(skb->cb, 0, sizeof(struct inet6_skb_parm)); ++ skb->dev = t->dev; ++ dst_release(skb->dst); ++ skb->dst = NULL; ++ t->stat.rx_packets++; ++ t->stat.rx_bytes += skb->len; ++ netif_rx(skb); ++ read_unlock(&ip6ip6_lock); ++ return 0; ++ } ++ read_unlock(&ip6ip6_lock); ++ icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH, 0, skb->dev); ++discard: ++ kfree_skb(skb); ++ return 0; ++} ++ ++static inline struct ipv6_txoptions *create_tel(__u8 encap_limit) ++{ ++ struct ipv6_tlv_tnl_enc_lim *tel; ++ struct ipv6_txoptions *opt; ++ __u8 *raw; ++ ++ int opt_len = sizeof(*opt) + IPV6_TLV_TEL_DST_SIZE; ++ ++ if (!(opt = kmalloc(opt_len, GFP_ATOMIC))) { ++ return NULL; ++ } ++ memset(opt, 0, opt_len); ++ opt->tot_len = opt_len; ++ opt->dst0opt = (struct ipv6_opt_hdr *) (opt + 1); ++ opt->opt_nflen = 8; ++ ++ tel = (struct ipv6_tlv_tnl_enc_lim *) (opt->dst0opt + 1); ++ tel->type = IPV6_TLV_TNL_ENCAP_LIMIT; ++ tel->length = 1; ++ tel->encap_limit = encap_limit; ++ ++ raw = (__u8 *) opt->dst0opt; ++ raw[5] = IPV6_TLV_PADN; ++ raw[6] = 1; ++ ++ return opt; ++} ++ ++static int ++ip6ip6_getfrag(const void *data, struct in6_addr *addr, ++ char *buff, unsigned int offset, unsigned int len) ++{ ++ memcpy(buff, data + offset, len); ++ return 0; ++} ++ ++/** ++ * ip6ip6_tnl_addr_conflict - compare packet addresses to tunnel's own ++ * @t: the outgoing tunnel device ++ * @hdr: IPv6 header from the incoming packet ++ * ++ * Description: ++ * Avoid trivial tunneling loop by checking that tunnel exit-point ++ * doesn't match source of incoming packet. ++ * ++ * Return: ++ * 1 if conflict, ++ * 0 else ++ **/ ++ ++static inline int ++ip6ip6_tnl_addr_conflict(struct ip6_tnl *t, struct ipv6hdr *hdr) ++{ ++ return !ipv6_addr_cmp(&t->parms.raddr, &hdr->saddr); ++} ++ ++/** ++ * ip6ip6_tnl_xmit - encapsulate packet and send ++ * @skb: the outgoing socket buffer ++ * @dev: the outgoing tunnel device ++ * ++ * Description: ++ * Build new header and do some sanity checks on the packet before sending ++ * it to ip6_build_xmit(). ++ * ++ * Return: ++ * 0 ++ **/ ++ ++int ip6ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev) ++{ ++ struct ip6_tnl *t = (struct ip6_tnl *) dev->priv; ++ struct net_device_stats *stats = &t->stat; ++ struct ipv6hdr *ipv6h = skb->nh.ipv6h; ++ struct ipv6_txoptions *opt = NULL; ++ int encap_limit = -1; ++ __u16 offset; ++ struct flowi fl; ++ int err = 0; ++ struct dst_entry *dst; ++ struct sock *sk = t->sock->sk; ++ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6; ++ int mtu; ++ ++ if (t->recursion++) { ++ stats->collisions++; ++ goto tx_err; ++ } ++ if (skb->protocol != htons(ETH_P_IPV6) || ++ !(t->parms.flags & IP6_TNL_F_CAP_XMIT) || ++ ip6ip6_tnl_addr_conflict(t, ipv6h)) { ++ goto tx_err; ++ } ++ if ((offset = parse_tlv_tnl_enc_lim(skb, skb->nh.raw)) > 0) { ++ struct ipv6_tlv_tnl_enc_lim *tel; ++ tel = (struct ipv6_tlv_tnl_enc_lim *) &skb->nh.raw[offset]; ++ if (tel->encap_limit == 0) { ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, offset + 2, skb->dev); ++ goto tx_err; ++ } ++ encap_limit = tel->encap_limit - 1; ++ } else if (!(t->parms.flags & IP6_TNL_F_IGN_ENCAP_LIMIT)) { ++ encap_limit = t->parms.encap_limit; ++ } ++ if (call_hooks(IP6_TNL_PRE_ENCAP, t, skb) != IP6_TNL_ACCEPT) ++ goto discard; ++ memcpy(&fl, &t->fl, sizeof (fl)); ++ ++ if ((t->parms.flags & IP6_TNL_F_USE_ORIG_TCLASS)) ++ fl.fl6_flowlabel |= (*(__u32 *) ipv6h & IPV6_TCLASS_MASK); ++ if ((t->parms.flags & IP6_TNL_F_USE_ORIG_FLOWLABEL)) ++ fl.fl6_flowlabel |= (*(__u32 *) ipv6h & IPV6_FLOWLABEL_MASK); ++ ++ if (encap_limit >= 0 && (opt = create_tel(encap_limit)) == NULL) ++ goto tx_err; ++ ++ dst = __sk_dst_check(sk, np->dst_cookie); ++ ++ if (dst) { ++ if (np->daddr_cache == NULL || ++ ipv6_addr_cmp(fl.fl6_dst, np->daddr_cache) || ++#ifdef CONFIG_IPV6_SUBTREES ++ np->saddr_cache == NULL || ++ ipv6_addr_cmp(fl.fl6_src, np->saddr_cache) || ++#endif ++ (fl.oif && fl.oif != dst->dev->ifindex)) { ++ dst = NULL; ++ } else { ++ dst_hold(dst); ++ } ++ } ++ if (dst == NULL) { ++ dst = ip6_route_output(sk, &fl); ++ if (dst->error) { ++ stats->tx_carrier_errors++; ++ dst_link_failure(skb); ++ goto tx_err_dst_release; ++ } ++ /* local routing loop */ ++ if (dst->dev == dev) { ++ stats->collisions++; ++ if (net_ratelimit()) ++ printk(KERN_WARNING ++ "%s: Local routing loop detected!\n", ++ t->parms.name); ++ goto tx_err_dst_release; ++ } ++ } ++ mtu = dst->pmtu - sizeof (*ipv6h); ++ if (opt) { ++ mtu -= (opt->opt_nflen + opt->opt_flen); ++ } ++ if (mtu < IPV6_MIN_MTU) ++ mtu = IPV6_MIN_MTU; ++ if (skb->dst && mtu < skb->dst->pmtu) { ++ struct rt6_info *rt = (struct rt6_info *) skb->dst; ++ rt->rt6i_flags |= RTF_MODIFIED; ++ rt->u.dst.pmtu = mtu; ++ } ++ if (skb->len > mtu) { ++ icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, dev); ++ goto tx_err_dst_release; ++ } ++ ip6_dst_store(sk, dst, &np->daddr, &np->saddr); ++ err = ip6_build_xmit(sk, ip6ip6_getfrag, (void *) skb->nh.raw, ++ &fl, skb->len, opt, t->parms.hop_limit, ++ MSG_DONTWAIT); ++ ++ if (err == NET_XMIT_SUCCESS || err == NET_XMIT_CN) { ++ stats->tx_bytes += skb->len; ++ stats->tx_packets++; ++ } else { ++ stats->tx_errors++; ++ stats->tx_aborted_errors++; ++ } ++ if (opt) ++ kfree(opt); ++ kfree_skb(skb); ++ t->recursion--; ++ return 0; ++tx_err_dst_release: ++ dst_release(dst); ++ if (opt) ++ kfree(opt); ++tx_err: ++ stats->tx_errors++; ++discard: ++ stats->tx_dropped++; ++ kfree_skb(skb); ++ t->recursion--; ++ return 0; ++} ++ ++static void ip6_tnl_set_cap(struct ip6_tnl *t) ++{ ++ struct ip6_tnl_parm *p = &t->parms; ++ struct in6_addr *laddr = &p->laddr; ++ struct in6_addr *raddr = &p->raddr; ++ int ltype = ipv6_addr_type(laddr); ++ int rtype = ipv6_addr_type(raddr); ++ ++ p->flags &= ~(IP6_TNL_F_CAP_XMIT|IP6_TNL_F_CAP_RCV); ++ ++ if (ltype != IPV6_ADDR_ANY && rtype != IPV6_ADDR_ANY && ++ ((ltype|rtype) & ++ (IPV6_ADDR_UNICAST| ++ IPV6_ADDR_LOOPBACK|IPV6_ADDR_LINKLOCAL| ++ IPV6_ADDR_MAPPED|IPV6_ADDR_RESERVED)) == IPV6_ADDR_UNICAST) { ++ struct net_device *ldev = NULL; ++ int l_ok = 1; ++ int r_ok = 1; ++ ++ if (p->link) ++ ldev = dev_get_by_index(p->link); ++ ++ if ((ltype&IPV6_ADDR_UNICAST) && !ipv6_chk_addr(laddr, ldev)) ++ l_ok = 0; ++ ++ if ((rtype&IPV6_ADDR_UNICAST) && ipv6_chk_addr(raddr, NULL)) ++ r_ok = 0; ++ ++ if (l_ok && r_ok) { ++ if (ltype&IPV6_ADDR_UNICAST) ++ p->flags |= IP6_TNL_F_CAP_XMIT; ++ if (rtype&IPV6_ADDR_UNICAST) ++ p->flags |= IP6_TNL_F_CAP_RCV; ++ } ++ if (ldev) ++ dev_put(ldev); ++ } ++} ++ ++static void ip6ip6_tnl_link_config(struct ip6_tnl *t) ++{ ++ struct net_device *dev = t->dev; ++ struct ip6_tnl_parm *p = &t->parms; ++ struct flowi *fl = &t->fl; ++ ++ /* Set up flowi template */ ++ fl->fl6_src = &p->laddr; ++ fl->fl6_dst = &p->raddr; ++ fl->oif = p->link; ++ fl->fl6_flowlabel = 0; ++ ++ if (!(p->flags&IP6_TNL_F_USE_ORIG_TCLASS)) ++ fl->fl6_flowlabel |= IPV6_TCLASS_MASK & htonl(p->flowinfo); ++ if (!(p->flags&IP6_TNL_F_USE_ORIG_FLOWLABEL)) ++ fl->fl6_flowlabel |= IPV6_FLOWLABEL_MASK & htonl(p->flowinfo); ++ ++ ip6_tnl_set_cap(t); ++ ++ if (p->flags&IP6_TNL_F_CAP_XMIT && p->flags&IP6_TNL_F_CAP_RCV) ++ dev->flags |= IFF_POINTOPOINT; ++ else ++ dev->flags &= ~IFF_POINTOPOINT; ++ ++ if (p->flags & IP6_TNL_F_CAP_XMIT) { ++ struct rt6_info *rt = rt6_lookup(&p->raddr, &p->laddr, ++ p->link, 0); ++ ++ if (rt == NULL) ++ return; ++ ++ if (rt->rt6i_dev) { ++ dev->iflink = rt->rt6i_dev->ifindex; ++ ++ dev->hard_header_len = rt->rt6i_dev->hard_header_len + ++ sizeof (struct ipv6hdr); ++ ++ dev->mtu = rt->rt6i_dev->mtu - sizeof (struct ipv6hdr); ++ ++ if (dev->mtu < IPV6_MIN_MTU) ++ dev->mtu = IPV6_MIN_MTU; ++ } ++ dst_release(&rt->u.dst); ++ } ++} ++ ++/** ++ * __ip6ip6_tnl_change - update the tunnel parameters ++ * @t: tunnel to be changed ++ * @p: tunnel configuration parameters ++ * ++ * Description: ++ * __ip6ip6_tnl_change() updates the tunnel parameters ++ **/ ++ ++static void ++__ip6ip6_tnl_change(struct ip6_tnl *t, struct ip6_tnl_parm *p) ++{ ++ ipv6_addr_copy(&t->parms.laddr, &p->laddr); ++ ipv6_addr_copy(&t->parms.raddr, &p->raddr); ++ t->parms.flags = p->flags; ++ t->parms.hop_limit = p->hop_limit; ++ t->parms.encap_limit = p->encap_limit; ++ t->parms.flowinfo = p->flowinfo; ++ ip6ip6_tnl_link_config(t); ++} ++ ++void ip6ip6_tnl_change(struct ip6_tnl *t, struct ip6_tnl_parm *p) ++{ ++ ip6ip6_tnl_unlink(t); ++ __ip6ip6_tnl_change(t, p); ++ ip6ip6_tnl_link(t); ++} ++ ++/** ++ * ip6ip6_kernel_tnl_add - configure and add kernel tunnel to hash ++ * @p: kernel tunnel configuration parameters ++ * ++ * Description: ++ * ip6ip6_kernel_tnl_add() fetches an unused kernel tunnel configures ++ * it according to @p and places it among the active tunnels. ++ * ++ * Return: ++ * number of references to tunnel on success, ++ * %-EEXIST if there is already a device matching description ++ * %-EINVAL if p->flags doesn't have %IP6_TNL_F_KERNEL_DEV raised, ++ * %-ENODEV if there are no unused kernel tunnels available ++ * ++ * Note: ++ * The code for creating, opening, closing and destroying network devices ++ * must be called from process context, while the Mobile IP code, which ++ * needs the tunnel devices, unfortunately runs in interrupt context. ++ * ++ * The devices must be created and opened in advance, then placed in a ++ * list where the kernel can fetch and ready them for use at a later time. ++ * ++ **/ ++ ++int ++ip6ip6_kernel_tnl_add(struct ip6_tnl_parm *p) ++{ ++ struct ip6_tnl *t; ++ ++ if (!(p->flags & IP6_TNL_F_KERNEL_DEV)) ++ return -EINVAL; ++ if ((t = ip6ip6_tnl_lookup(&p->raddr, &p->laddr)) != NULL && ++ t != &ip6ip6_fb_tnl) { ++ /* Handle duplicate tunnels by incrementing ++ reference count */ ++ atomic_inc(&t->refcnt); ++ goto out; ++ } ++ if ((t = ip6ip6_kernel_tnl_unlink()) == NULL) ++ return -ENODEV; ++ __ip6ip6_tnl_change(t, p); ++ ++ atomic_inc(&t->refcnt); ++ ++ ip6ip6_tnl_link(t); ++ ++ manage_kernel_tnls(NULL); ++out: ++ return atomic_read(&t->refcnt); ++} ++ ++/** ++ * ip6ip6_kernel_tnl_del - delete no longer needed kernel tunnel ++ * @t: kernel tunnel to be removed from hash ++ * ++ * Description: ++ * ip6ip6_kernel_tnl_del() removes and deconfigures the tunnel @t ++ * and places it among the unused kernel devices. ++ * ++ * Return: ++ * number of references on success, ++ * %-EINVAL if p->flags doesn't have %IP6_TNL_F_KERNEL_DEV raised, ++ * ++ * Note: ++ * See the comments on ip6ip6_kernel_tnl_add() for more information. ++ **/ ++ ++int ++ip6ip6_kernel_tnl_del(struct ip6_tnl *t) ++{ ++ if (!t) ++ return -ENODEV; ++ ++ if (!(t->parms.flags & IP6_TNL_F_KERNEL_DEV)) ++ return -EINVAL; ++ ++ if (atomic_dec_and_test(&t->refcnt)) { ++ struct ip6_tnl_parm p; ++ ip6ip6_tnl_unlink(t); ++ memset(&p, 0, sizeof (p)); ++ p.flags = IP6_TNL_F_KERNEL_DEV; ++ ++ __ip6ip6_tnl_change(t, &p); ++ ++ ip6ip6_kernel_tnl_link(t); ++ ++ manage_kernel_tnls(NULL); ++ } ++ return atomic_read(&t->refcnt); ++} ++ ++/** ++ * ip6ip6_tnl_ioctl - configure ipv6 tunnels from userspace ++ * @dev: virtual device associated with tunnel ++ * @ifr: parameters passed from userspace ++ * @cmd: command to be performed ++ * ++ * Description: ++ * ip6ip6_tnl_ioctl() is used for managing IPv6 tunnels ++ * from userspace. ++ * ++ * The possible commands are the following: ++ * %SIOCGETTUNNEL: get tunnel parameters for device ++ * %SIOCADDTUNNEL: add tunnel matching given tunnel parameters ++ * %SIOCCHGTUNNEL: change tunnel parameters to those given ++ * %SIOCDELTUNNEL: delete tunnel ++ * ++ * The fallback device "ip6tnl0", created during module ++ * initialization, can be used for creating other tunnel devices. ++ * ++ * Return: ++ * 0 on success, ++ * %-EFAULT if unable to copy data to or from userspace, ++ * %-EPERM if current process hasn't %CAP_NET_ADMIN set or attempting ++ * to configure kernel devices from userspace, ++ * %-EINVAL if passed tunnel parameters are invalid, ++ * %-EEXIST if changing a tunnel's parameters would cause a conflict ++ * %-ENODEV if attempting to change or delete a nonexisting device ++ * ++ * Note: ++ * See the comments on ip6ip6_kernel_tnl_add() for more information ++ * about kernel tunnels. ++ * **/ ++ ++static int ++ip6ip6_tnl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) ++{ ++ int err = 0; ++ int create; ++ struct ip6_tnl_parm p; ++ struct ip6_tnl *t = NULL; ++ ++ MOD_INC_USE_COUNT; ++ ++ switch (cmd) { ++ case SIOCGETTUNNEL: ++ if (dev == &ip6ip6_fb_tnl_dev) { ++ if (copy_from_user(&p, ++ ifr->ifr_ifru.ifru_data, ++ sizeof (p))) { ++ err = -EFAULT; ++ break; ++ } ++ if ((err = ip6ip6_tnl_locate(&p, &t, 0)) == -ENODEV) ++ t = (struct ip6_tnl *) dev->priv; ++ else if (err) ++ break; ++ } else ++ t = (struct ip6_tnl *) dev->priv; ++ ++ memcpy(&p, &t->parms, sizeof (p)); ++ if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof (p))) { ++ err = -EFAULT; ++ } ++ break; ++ case SIOCADDTUNNEL: ++ case SIOCCHGTUNNEL: ++ err = -EPERM; ++ create = (cmd == SIOCADDTUNNEL); ++ if (!capable(CAP_NET_ADMIN)) ++ break; ++ if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof (p))) { ++ err = -EFAULT; ++ break; ++ } ++ if (p.flags & IP6_TNL_F_KERNEL_DEV) { ++ break; ++ } ++ if (!create && dev != &ip6ip6_fb_tnl_dev) { ++ t = (struct ip6_tnl *) dev->priv; ++ } ++ if (!t && (err = ip6ip6_tnl_locate(&p, &t, create))) { ++ break; ++ } ++ if (cmd == SIOCCHGTUNNEL) { ++ if (t->dev != dev) { ++ err = -EEXIST; ++ break; ++ } ++ if (t->parms.flags & IP6_TNL_F_KERNEL_DEV) { ++ err = -EPERM; ++ break; ++ } ++ ip6ip6_tnl_change(t, &p); ++ netdev_state_change(dev); ++ } ++ if (copy_to_user(ifr->ifr_ifru.ifru_data, ++ &t->parms, sizeof (p))) { ++ err = -EFAULT; ++ } else { ++ err = 0; ++ } ++ break; ++ case SIOCDELTUNNEL: ++ err = -EPERM; ++ if (!capable(CAP_NET_ADMIN)) ++ break; ++ ++ if (dev == &ip6ip6_fb_tnl_dev) { ++ if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, ++ sizeof (p))) { ++ err = -EFAULT; ++ break; ++ } ++ err = ip6ip6_tnl_locate(&p, &t, 0); ++ if (err) ++ break; ++ if (t == &ip6ip6_fb_tnl) { ++ err = -EPERM; ++ break; ++ } ++ } else { ++ t = (struct ip6_tnl *) dev->priv; ++ } ++ if (t->parms.flags & IP6_TNL_F_KERNEL_DEV) ++ err = -EPERM; ++ else ++ err = unregister_netdevice(t->dev); ++ break; ++ default: ++ err = -EINVAL; ++ } ++ MOD_DEC_USE_COUNT; ++ return err; ++} ++ ++/** ++ * ip6ip6_tnl_get_stats - return the stats for tunnel device ++ * @dev: virtual device associated with tunnel ++ * ++ * Return: stats for device ++ **/ ++ ++static struct net_device_stats * ++ip6ip6_tnl_get_stats(struct net_device *dev) ++{ ++ return &(((struct ip6_tnl *) dev->priv)->stat); ++} ++ ++/** ++ * ip6ip6_tnl_change_mtu - change mtu manually for tunnel device ++ * @dev: virtual device associated with tunnel ++ * @new_mtu: the new mtu ++ * ++ * Return: ++ * 0 on success, ++ * %-EINVAL if mtu too small ++ **/ ++ ++static int ++ip6ip6_tnl_change_mtu(struct net_device *dev, int new_mtu) ++{ ++ if (new_mtu < IPV6_MIN_MTU) { ++ return -EINVAL; ++ } ++ dev->mtu = new_mtu; ++ return 0; ++} ++ ++/** ++ * ip6ip6_tnl_dev_init_gen - general initializer for all tunnel devices ++ * @dev: virtual device associated with tunnel ++ * ++ * Description: ++ * Set function pointers and initialize the &struct flowi template used ++ * by the tunnel. ++ **/ ++ ++static int ++ip6ip6_tnl_dev_init_gen(struct net_device *dev) ++{ ++ struct ip6_tnl *t = (struct ip6_tnl *) dev->priv; ++ struct flowi *fl = &t->fl; ++ int err; ++ struct sock *sk; ++ ++ if ((err = sock_create(PF_INET6, SOCK_RAW, IPPROTO_IPV6, &t->sock))) { ++ printk(KERN_ERR ++ "Failed to create IPv6 tunnel socket (err %d).\n", err); ++ return err; ++ } ++ t->sock->inode->i_uid = 0; ++ t->sock->inode->i_gid = 0; ++ ++ sk = t->sock->sk; ++ sk->allocation = GFP_ATOMIC; ++ sk->net_pinfo.af_inet6.hop_limit = 254; ++ sk->net_pinfo.af_inet6.mc_loop = 0; ++ sk->prot->unhash(sk); ++ ++ memset(fl, 0, sizeof (*fl)); ++ fl->proto = IPPROTO_IPV6; ++ ++ dev->destructor = ip6ip6_tnl_dev_destructor; ++ dev->uninit = ip6ip6_tnl_dev_uninit; ++ dev->hard_start_xmit = ip6ip6_tnl_xmit; ++ dev->get_stats = ip6ip6_tnl_get_stats; ++ dev->do_ioctl = ip6ip6_tnl_ioctl; ++ dev->change_mtu = ip6ip6_tnl_change_mtu; ++ ++ dev->type = ARPHRD_TUNNEL6; ++ dev->hard_header_len = LL_MAX_HEADER + sizeof (struct ipv6hdr); ++ dev->mtu = ETH_DATA_LEN - sizeof (struct ipv6hdr); ++ dev->flags |= IFF_NOARP; ++ dev->iflink = 0; ++ /* Hmm... MAX_ADDR_LEN is 8, so the ipv6 addresses can't be ++ copied to dev->dev_addr and dev->broadcast, like the ipv4 ++ addresses were in ipip.c, ip_gre.c and sit.c. */ ++ dev->addr_len = 0; ++ return 0; ++} ++ ++/** ++ * ip6ip6_tnl_dev_init - initializer for all non fallback tunnel devices ++ * @dev: virtual device associated with tunnel ++ **/ ++ ++static int ++ip6ip6_tnl_dev_init(struct net_device *dev) ++{ ++ struct ip6_tnl *t = (struct ip6_tnl *) dev->priv; ++ ip6ip6_tnl_dev_init_gen(dev); ++ ip6ip6_tnl_link_config(t); ++ return 0; ++} ++ ++#ifdef MODULE ++ ++/** ++ * ip6ip6_fb_tnl_open - function called when fallback device opened ++ * @dev: fallback device ++ * ++ * Return: 0 ++ **/ ++ ++static int ++ip6ip6_fb_tnl_open(struct net_device *dev) ++{ ++ MOD_INC_USE_COUNT; ++ return 0; ++} ++ ++/** ++ * ip6ip6_fb_tnl_close - function called when fallback device closed ++ * @dev: fallback device ++ * ++ * Return: 0 ++ **/ ++ ++static int ++ip6ip6_fb_tnl_close(struct net_device *dev) ++{ ++ MOD_DEC_USE_COUNT; ++ return 0; ++} ++#endif ++ ++/** ++ * ip6ip6_fb_tnl_dev_init - initializer for fallback tunnel device ++ * @dev: fallback device ++ * ++ * Return: 0 ++ **/ ++ ++int __init ++ip6ip6_fb_tnl_dev_init(struct net_device *dev) ++{ ++ ip6ip6_tnl_dev_init_gen(dev); ++#ifdef MODULE ++ dev->open = ip6ip6_fb_tnl_open; ++ dev->stop = ip6ip6_fb_tnl_close; ++#endif ++ dev_hold(dev); ++ tnls_wc[0] = &ip6ip6_fb_tnl; ++ return 0; ++} ++ ++/** ++ * ip6ip6_tnl_register_hook - add hook for processing of tunneled packets ++ * @reg: hook function and its parameters ++ * ++ * Description: ++ * Add a netfilter like hook function for special handling of tunneled ++ * packets. The hook functions are called before encapsulation ++ * (%IP6_TNL_PRE_ENCAP) and before decapsulation ++ * (%IP6_TNL_PRE_DECAP). The possible return values by the hook ++ * functions are %IP6_TNL_DROP, %IP6_TNL_ACCEPT and ++ * %IP6_TNL_STOLEN (in case the hook function took care of the packet ++ * and it doesn't have to be processed any further). ++ **/ ++ ++void ++ip6ip6_tnl_register_hook(struct ip6_tnl_hook_ops *reg) ++{ ++ if (reg->hooknum < IP6_TNL_MAXHOOKS) { ++ struct list_head *i; ++ ++ write_lock_bh(&ip6ip6_hook_lock); ++ for (i = hooks[reg->hooknum].next; ++ i != &hooks[reg->hooknum]; i = i->next) { ++ if (reg->priority < ++ ((struct ip6_tnl_hook_ops *) i)->priority) { ++ break; ++ } ++ } ++ list_add(®->list, i->prev); ++ write_unlock_bh(&ip6ip6_hook_lock); ++ } ++} ++ ++/** ++ * ip6ip6_tnl_unregister_hook - remove tunnel hook ++ * @reg: hook function and its parameters ++ **/ ++ ++void ++ip6ip6_tnl_unregister_hook(struct ip6_tnl_hook_ops *reg) ++{ ++ if (reg->hooknum < IP6_TNL_MAXHOOKS) { ++ write_lock_bh(&ip6ip6_hook_lock); ++ list_del(®->list); ++ write_unlock_bh(&ip6ip6_hook_lock); ++ } ++} ++ ++ ++/* the IPv6 over IPv6 protocol structure */ ++static struct inet6_protocol ip6ip6_protocol = { ++ ip6ip6_rcv, /* IPv6 handler */ ++ ip6ip6_err, /* IPv6 error control */ ++ NULL, /* next */ ++ IPPROTO_IPV6, /* protocol ID */ ++ 0, /* copy */ ++ NULL, /* data */ ++ "IPv6 over IPv6" /* name */ ++}; ++ ++/** ++ * ip6_tunnel_init - register protocol and reserve needed resources ++ * ++ * Return: 0 on success ++ **/ ++ ++int __init ip6_tunnel_init(void) ++{ ++ int i, err; ++ ++ ip6ip6_fb_tnl_dev.priv = (void *) &ip6ip6_fb_tnl; ++ ++ for (i = 0; i < IP6_TNL_MAXHOOKS; i++) { ++ INIT_LIST_HEAD(&hooks[i]); ++ } ++ if ((err = register_netdev(&ip6ip6_fb_tnl_dev))) ++ return err; ++ ++ inet6_add_protocol(&ip6ip6_protocol); ++ return 0; ++} ++ ++/** ++ * ip6_tunnel_cleanup - free resources and unregister protocol ++ **/ ++ ++void ip6_tunnel_cleanup(void) ++{ ++ write_lock_bh(&ip6ip6_kernel_lock); ++ shutdown = 1; ++ write_unlock_bh(&ip6ip6_kernel_lock); ++ flush_scheduled_tasks(); ++ manage_kernel_tnls(NULL); ++ inet6_del_protocol(&ip6ip6_protocol); ++ unregister_netdev(&ip6ip6_fb_tnl_dev); ++} ++ ++#ifdef MODULE ++module_init(ip6_tunnel_init); ++module_exit(ip6_tunnel_cleanup); ++#endif ++ ++#if defined(CONFIG_IPV6_MOBILITY_HA_MODULE) || defined(CONFIG_IPV6_MOBILITY_MN_MODULE) ++EXPORT_SYMBOL(ip6ip6_tnl_register_hook); ++EXPORT_SYMBOL(ip6ip6_tnl_unregister_hook); ++#endif ++#ifdef CONFIG_IPV6_MOBILITY_HA_MODULE ++EXPORT_SYMBOL(ip6ip6_tnl_dec_max_kdev_count); ++EXPORT_SYMBOL(ip6ip6_tnl_inc_max_kdev_count); ++EXPORT_SYMBOL(ip6ip6_tnl_dec_min_kdev_count); ++EXPORT_SYMBOL(ip6ip6_tnl_inc_min_kdev_count); ++EXPORT_SYMBOL(ip6ip6_kernel_tnl_add); ++EXPORT_SYMBOL(ip6ip6_kernel_tnl_del); ++EXPORT_SYMBOL(ip6ip6_tnl_lookup); ++#endif ++#ifdef CONFIG_IPV6_MOBILITY_MN_MODULE ++EXPORT_SYMBOL(ip6ip6_tnl_create); ++EXPORT_SYMBOL(ip6ip6_tnl_change); ++#endif ++ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mipglue.c +@@ -0,0 +1,63 @@ ++/* ++ * Glue for Mobility support integration to IPv6 ++ * ++ * Authors: ++ * Antti Tuominen ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++extern int ip6_tlvopt_unknown(struct sk_buff *skb, int optoff); ++ ++/* Initialize all zero */ ++struct mipv6_callable_functions mipv6_functions = { NULL }; ++ ++/* Sets mipv6_functions struct to zero to invalidate all successive ++ * calls to mipv6 functions. Used on module unload. */ ++ ++void mipv6_invalidate_calls(void) ++{ ++ memset(&mipv6_functions, 0, sizeof(mipv6_functions)); ++} ++ ++ ++/* Selects correct handler for tlv encoded destination option. Called ++ * by ip6_parse_tlv. Checks if mipv6 calls are valid before calling. */ ++ ++int mipv6_handle_dstopt(struct sk_buff *skb, int optoff) ++{ ++ int ret; ++ ++ switch (skb->nh.raw[optoff]) { ++ case MIPV6_TLV_HOMEADDR: ++ ret = MIPV6_CALLFUNC(mipv6_handle_homeaddr, 0)(skb, optoff); ++ break; ++ default: ++ /* Should never happen */ ++ printk(KERN_ERR __FILE__ ": Invalid destination option code (%d)\n", ++ skb->nh.raw[optoff]); ++ ret = 1; ++ break; ++ } ++ ++ /* If mipv6 handlers are not valid, pass the packet to ++ * ip6_tlvopt_unknown() for correct handling. */ ++ if (!ret) ++ return ip6_tlvopt_unknown(skb, optoff); ++ ++ return ret; ++} ++ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/Config.in +@@ -0,0 +1,12 @@ ++# ++# Mobile IPv6 Configuration ++# ++dep_tristate ' IPv6: Mobility Support (Correspondent Node)' CONFIG_IPV6_MOBILITY $CONFIG_IPV6 ++if [ "$CONFIG_IPV6_IPV6_TUNNEL" != "n" ]; then ++ dep_tristate ' MIPv6: Mobile Node Support' CONFIG_IPV6_MOBILITY_MN $CONFIG_IPV6_MOBILITY ++ ++ dep_tristate ' MIPv6: Home Agent Support' CONFIG_IPV6_MOBILITY_HA $CONFIG_IPV6_MOBILITY ++fi ++if [ "$CONFIG_IPV6_MOBILITY" != "n" ]; then ++ bool ' MIPv6: Debug messages' CONFIG_IPV6_MOBILITY_DEBUG ++fi +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/Makefile +@@ -0,0 +1,35 @@ ++# ++# Makefile for the MIPL Mobile IPv6 for Linux. ++# ++# Note! Dependencies are done automagically by 'make dep', which also ++# removes any old dependencies. DON'T put your own dependencies here ++# unless it's something special (ie not a .c file). ++# ++ ++ ++O_TARGET := mip6_base.o ++ ++list-multi := mip6_ha.o mip6_mn.o ++ ++obj-y := hashlist.o bcache.o mobhdr_common.o stats.o exthdrs.o \ ++ rr_crypto.o hmac.o auth_opt.o mipv6_icmp.o module_cn.o ++ ++obj-m := $(O_TARGET) ++ ++mip6_ha-objs := halist.o mipv6_icmp_ha.o tunnel_ha.o \ ++ ndisc_ha.o ha.o module_ha.o ++ ++mip6_mn-objs := mipv6_icmp_mn.o ioctl_mn.o tunnel_mn.o \ ++ mdetect.o bul.o multiaccess_ctl.o mobhdr_mn.o mn.o \ ++ module_mn.o ++ ++obj-$(CONFIG_IPV6_MOBILITY_HA) += mip6_ha.o ++obj-$(CONFIG_IPV6_MOBILITY_MN) += mip6_mn.o ++ ++include $(TOPDIR)/Rules.make ++ ++mip6_ha.o: $(mip6_ha-objs) ++ $(LD) -r -o $@ $(mip6_ha-objs) ++ ++mip6_mn.o: $(mip6_mn-objs) ++ $(LD) -r -o $@ $(mip6_mn-objs) +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/README +@@ -0,0 +1,15 @@ ++MIPL Mobile IPv6 for Linux ++ ++More information at http://www.mipl.mediapoli.com/. ++ ++To join MIPL Mobile IPv6 for Linux mailing lists go to: ++ ++ http://www.mipl.mediapoli.com/cgi-bin/mailman/listinfo ++ ++Or send mail with subject "subscribe" for the general list to: ++ ++ mipl-request@list.mipl.mediapoli.com ++ ++or for the developer list to: ++ ++ mipl-devel-request@list.mail.mediapoli.com +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/auth_opt.c +@@ -0,0 +1,121 @@ ++/* ++ * MIPv6 Binding Authentication Data Option functions ++ * ++ * Authors: ++ * Henrik Petander ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++ ++#include "debug.h" ++#include "hmac.h" ++#include "mobhdr.h" ++ ++#define DBG_KEY 5 ++ ++int mipv6_auth_build(struct in6_addr *cn_addr, struct in6_addr *coa, ++ __u8 *mh, __u8 *aud_data, __u8 *k_bu) ++{ ++ /* First look up the peer from sadb based on his address */ ++ struct ah_processing ahp; ++ ++ /* Don't add any other options or this system is screwed */ ++ ++ __u8 buf[MAX_HASH_LENGTH]; ++ ++ ++ if (!k_bu) { ++ DEBUG(DBG_ERROR, "k_bu missing, aborting"); ++ return -1; ++ } ++ DEBUG(DBG_KEY, "Key for building authenticator:"); ++ debug_print_buffer(DBG_KEY, k_bu, HMAC_SHA1_KEY_SIZE); ++ ++ if (ah_hmac_sha1_init(&ahp, k_bu, HMAC_SHA1_KEY_SIZE) < 0) { ++ DEBUG(DBG_ERROR, "Failed to initialize hmac sha1"); ++ return -1; ++ } ++ ++ DEBUG(DBG_KEY, "coa: "); ++ debug_print_buffer(DBG_KEY, coa, 16); ++ DEBUG(DBG_KEY, "cn_addr: "); ++ debug_print_buffer(DBG_KEY, cn_addr, 16); ++ DEBUG(DBG_KEY, "MH contents: "); ++ debug_print_buffer(DBG_KEY, mh, aud_data - mh); ++ ++ /* First the common part */ ++ ah_hmac_sha1_loop(&ahp, coa, sizeof(struct in6_addr)); ++ ah_hmac_sha1_loop(&ahp, cn_addr, sizeof(struct in6_addr)); ++ ah_hmac_sha1_loop(&ahp, mh, aud_data - mh); ++ ah_hmac_sha1_result(&ahp, buf); ++ ++ memcpy(aud_data, buf, MIPV6_RR_MAC_LENGTH); ++ ++ return 0; ++} ++ ++int mipv6_auth_check(struct in6_addr *cn_addr, struct in6_addr *coa, ++ __u8 *opt, __u8 optlen, ++ struct mipv6_mo_bauth_data *aud, __u8 *k_bu) ++{ ++ int ret = -1; ++ struct ah_processing ahp; ++ __u8 htarget[MAX_HASH_LENGTH]; ++ ++ /* Look up peer by home address */ ++ if (!k_bu) { ++ DEBUG(DBG_ERROR, "k_bu missing, aborting"); ++ return -1; ++ } ++ ++ DEBUG(DBG_KEY, "Key for checking authenticator:"); ++ debug_print_buffer(DBG_KEY, k_bu, HMAC_SHA1_KEY_SIZE); ++ ++ if (!aud || !coa) { ++ DEBUG(DBG_INFO, "%s is NULL", aud ? "coa" : "aud"); ++ goto out; ++ } ++ ++ if (aud->length != MIPV6_RR_MAC_LENGTH) { ++ DEBUG(DBG_ERROR, ++ ": Incorrect authentication option length %d", aud->length); ++ goto out; ++ } ++ ++ if (ah_hmac_sha1_init(&ahp, k_bu, HMAC_SHA1_KEY_SIZE) < 0) { ++ DEBUG(DBG_ERROR, ++ "internal error in initialization of authentication algorithm"); ++ goto out; ++ } ++ DEBUG(DBG_KEY, "coa: "); ++ debug_print_buffer(DBG_KEY, coa, 16); ++ DEBUG(DBG_KEY, "cn_addr: "); ++ debug_print_buffer(DBG_KEY, cn_addr, 16); ++ DEBUG(DBG_KEY, "MH contents: "); ++ debug_print_buffer(DBG_KEY, opt, (u8*) aud->data - opt); ++ ++ ah_hmac_sha1_loop(&ahp, coa, sizeof(struct in6_addr)); ++ ah_hmac_sha1_loop(&ahp, cn_addr, sizeof(struct in6_addr)); ++ ++ /* ++ * Process MH + options till the start of the authenticator in ++ * Auth. data option ++ */ ++ ah_hmac_sha1_loop(&ahp, opt, (u8 *)aud->data - opt); ++ ah_hmac_sha1_result(&ahp, htarget); ++ if (memcmp(htarget, aud->data, MIPV6_RR_MAC_LENGTH) == 0) ++ ret = 0; ++ ++ DEBUG(DBG_ERROR, "returning %d", ret); ++out: ++ return ret; ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/bcache.c +@@ -0,0 +1,746 @@ ++/* ++ * Binding Cache ++ * ++ * Authors: ++ * Juha Mynttinen ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++/* ++ * Changes: ++ * ++ * Nanno Langstraat : Timer code cleaned up, active socket ++ * test rewritten ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "bcache.h" ++#include "hashlist.h" ++#include "debug.h" ++#include "mobhdr.h" ++#include "tunnel.h" ++#include "config.h" ++ ++#define TIMERDELAY HZ/10 ++ ++struct mipv6_bcache { ++ struct hashlist *entries; ++ __u32 size; ++ struct timer_list callback_timer; ++}; ++ ++struct in6_addr_pair { ++ struct in6_addr *a1; ++ struct in6_addr *a2; ++}; ++ ++static rwlock_t bcache_lock = RW_LOCK_UNLOCKED; ++ ++static struct mipv6_bcache bcache; ++ ++static int bcache_proc_info(char *buffer, char **start, off_t offset, ++ int length); ++ ++#define MIPV6_BCACHE_HASHSIZE 32 ++ ++/* Moment of transmission of a BR, in seconds before bcache entry expiry */ ++#define BCACHE_BR_SEND_LEAD 3 ++ ++#define MIPV6_MAX_BRR 3 /* Send 3 BRRs before deleting BC entry */ ++#define MIPV6_BRR_RATE HZ /* Send BRRs once per second */ ++ ++/* ++ * Internal functions. ++ */ ++ ++struct cache_entry_iterator_args { ++ struct mipv6_bce **entry; ++}; ++ ++static int find_first_cache_entry_iterator(void *data, void *args, ++ unsigned long *lifetime) ++{ ++ struct mipv6_bce *entry = ++ (struct mipv6_bce *) data; ++ struct cache_entry_iterator_args *state = ++ (struct cache_entry_iterator_args *) args; ++ ++ ASSERT(entry != NULL); ++ ++ if (entry->type == CACHE_ENTRY) { ++ *(state->entry) = entry; ++ return ITERATOR_STOP; /* stop iteration */ ++ } else { ++ return ITERATOR_CONT; /* continue iteration */ ++ } ++} ++ ++ ++/* ++ * Get memory for a new bcache entry. If bcache is full, a cache ++ * entry may be deleted to get space for a home registration, but not ++ * vice versa. ++ */ ++static struct mipv6_bce *mipv6_bce_alloc(__u8 type) ++{ ++ struct mipv6_bce *entry; ++ struct cache_entry_iterator_args args; ++ ++ DEBUG_FUNC(); ++ ++ entry = (struct mipv6_bce *) ++ hashlist_alloc(bcache.entries, SLAB_ATOMIC); ++ ++ /* Cache replacement policy: always replace the CACHE_ENTRY ++ closest to expiration. Type HOME_REGISTRATION entry may ++ never be deleted before expiration. */ ++ if (entry == NULL) { ++ /* cache full, try to delete a CACHE_ENTRY */ ++ args.entry = &entry; ++ hashlist_iterate(bcache.entries, &args, ++ find_first_cache_entry_iterator); ++ if (entry == NULL) ++ return NULL; ++ hashlist_delete(bcache.entries, ++ (struct hashlist_entry *)entry); ++ entry = (struct mipv6_bce *) ++ hashlist_alloc(bcache.entries, SLAB_ATOMIC); ++ } ++ return entry; ++} ++ ++/* ++ * Frees entry's memory allocated with mipv6_bce_alloc ++ */ ++static void mipv6_bce_free(struct mipv6_bce *entry) ++{ ++ hashlist_free(bcache.entries, (void *) entry); ++} ++ ++/* ++ * Removes all expired entries ++ */ ++static void expire(void) ++{ ++ struct mipv6_bce *entry; ++ struct br_addrs { ++ struct in6_addr daddr; ++ struct in6_addr saddr; ++ struct br_addrs *next; ++ }; ++ struct br_addrs *br_info = NULL; ++ ++ DEBUG_FUNC(); ++ ++ write_lock(&bcache_lock); ++ ++ while ((entry = (struct mipv6_bce *) ++ hashlist_get_first(bcache.entries)) != NULL) { ++ struct rt6_info *rt; ++ if (time_after_eq(jiffies, entry->callback_time)) { ++ ++ DEBUG(DBG_INFO, "an entry expired"); ++ ++ if (entry->type & HOME_REGISTRATION) { ++ mip6_fn.proxy_del(&entry->home_addr, entry); ++ } ++ hashlist_delete(bcache.entries, (void *)entry); ++ mipv6_bce_free(entry); ++ entry = NULL; ++ } else if (entry->br_callback_time != 0 && ++ time_after_eq(jiffies, entry->br_callback_time) && ++ entry->br_count < MIPV6_MAX_BRR && ++ (rt = rt6_lookup(&entry->home_addr, &entry->our_addr, 0, 0)) != NULL){ ++ /* Do we have a destination cache entry for the home address */ ++ if (rt->rt6i_flags & RTF_CACHE) { ++ struct br_addrs *tmp; ++ tmp = br_info; ++ DEBUG(DBG_INFO, ++ "bcache entry recently used. Sending BR."); ++ /* queue for sending */ ++ br_info = kmalloc(sizeof(struct br_addrs), ++ GFP_ATOMIC); ++ if (br_info) { ++ ipv6_addr_copy(&br_info->saddr, ++ &entry->our_addr); ++ ipv6_addr_copy(&br_info->daddr, ++ &entry->home_addr); ++ br_info->next = tmp; ++ entry->last_br = jiffies; ++ entry->br_callback_time = jiffies + MIPV6_BRR_RATE; ++ entry->br_count++; ++ } else { ++ br_info = tmp; ++ DEBUG(DBG_ERROR, "Out of memory"); ++ } ++ ++ } else ++ entry->br_callback_time = 0; ++ dst_release(&rt->u.dst); ++ } else { ++ entry->br_callback_time = 0; ++ break; ++ } ++ } ++ write_unlock(&bcache_lock); ++ ++ while (br_info) { ++ struct br_addrs *tmp = br_info->next; ++ if (mipv6_send_brr(&br_info->saddr, &br_info->daddr, NULL) < 0) ++ DEBUG(DBG_WARNING, ++ "BR send for %x:%x:%x:%x:%x:%x:%x:%x failed", ++ NIPV6ADDR(&br_info->daddr)); ++ kfree(br_info); ++ br_info = tmp; ++ } ++} ++ ++static void set_timer(void) ++{ ++ struct mipv6_bce *entry; ++ unsigned long callback_time; ++ ++ DEBUG_FUNC(); ++ ++ entry = (struct mipv6_bce *) ++ hashlist_get_first(bcache.entries); ++ if (entry != NULL) { ++ if (entry->br_callback_time > 0 && ++ time_after(entry->br_callback_time, jiffies)) ++ callback_time = entry->br_callback_time; ++ else if (time_after(entry->callback_time, jiffies)) ++ callback_time = entry->callback_time; ++ else { ++ DEBUG(DBG_WARNING, ++ "bcache timer attempted to schedule" ++ " for a historical jiffies count!"); ++ callback_time = jiffies + TIMERDELAY; ++ } ++ ++ DEBUG(DBG_INFO, "setting timer to now"); ++ mod_timer(&bcache.callback_timer, callback_time); ++ } else { ++ del_timer(&bcache.callback_timer); ++ DEBUG(DBG_INFO, "BC empty, not setting a new timer"); ++ } ++} ++ ++/* ++ * The function that is scheduled to do the callback functions. May be ++ * modified e.g to allow Binding Requests, now only calls expire() and ++ * schedules a new timer. ++ */ ++static void timer_handler(unsigned long dummy) ++{ ++ expire(); ++ write_lock(&bcache_lock); ++ set_timer(); ++ write_unlock(&bcache_lock); ++} ++ ++/* ++ * Interface functions visible to other modules ++ */ ++ ++/** ++ * mipv6_bcache_add - add Binding Cache entry ++ * @ifindex: interface index ++ * @our_addr: own address ++ * @home_addr_org: MN's home address ++ * @coa: MN's care-of address ++ * @lifetime: lifetime for this binding ++ * @prefix: prefix length ++ * @seq: sequence number ++ * @flags: flags received in BU ++ * @type: type of entry ++ * ++ * Adds an entry for this @home_addr_org in the Binding Cache. If entry ++ * already exists, old entry is updated. @type may be %CACHE_ENTRY or ++ * %HOME_REGISTRATION. ++ **/ ++int mipv6_bcache_add(int ifindex, ++ struct in6_addr *our_addr, ++ struct in6_addr *home_addr, ++ struct in6_addr *coa, ++ __u32 lifetime, __u16 seq, __u8 flags, __u8 type) ++{ ++ struct mipv6_bce *entry; ++ int update = 0; ++ int create_tunnel = 0; ++ unsigned long now = jiffies; ++ struct in6_addr_pair hashkey; ++ int ret = -1; ++ ++ DEBUG_FUNC(); ++ ++ hashkey.a1 = home_addr; ++ hashkey.a2 = our_addr; ++ ++ write_lock(&bcache_lock); ++ ++ if (type == HOME_REGISTRATION && !(mip6node_cnf.capabilities&CAP_HA)) ++ return 0; ++ ++ if (unlikely(bcache.entries == NULL)) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ ++ if ((entry = (struct mipv6_bce *) ++ hashlist_get(bcache.entries, &hashkey)) != NULL) { ++ /* if an entry for this home_addr exists (with smaller ++ * seq than the new seq), update it by removing it ++ * first ++ */ ++ if (!MIPV6_SEQ_GT(seq, entry->seq)) { ++ DEBUG(DBG_INFO, "smaller seq than existing, not updating"); ++ goto out; ++ } ++ DEBUG(DBG_INFO, "updating an existing entry"); ++ update = 1; ++ ++ /* H-flag is already checked in BU handler. */ ++ /* XXX: Should we care about the other flags?*/ ++ if (flags != entry->flags) { ++ DEBUG(DBG_INFO, "entry/BU flag mismatch"); ++ } ++ ++ if (type == HOME_REGISTRATION) { ++ create_tunnel = (ipv6_addr_cmp(&entry->coa, coa) || ++ entry->ifindex != ifindex); ++ } ++ } else { ++ /* no entry for this home_addr, try to create a new entry */ ++ DEBUG(DBG_INFO, "creating a new entry"); ++ update = 0; ++ ++ entry = mipv6_bce_alloc(type); ++ if (entry == NULL) { ++ DEBUG(DBG_INFO, "cache full, entry not added"); ++ goto err; ++ } ++ ++ create_tunnel = (type == HOME_REGISTRATION); ++ } ++ ++ if (create_tunnel) { ++ if (update) ++ mip6_fn.proxy_del(&entry->home_addr, entry); ++ if (mip6_fn.proxy_create(flags, ifindex, coa, our_addr, home_addr) < 0) { ++ goto err_proxy; ++ } ++ } ++ ++ ipv6_addr_copy(&(entry->our_addr), our_addr); ++ ipv6_addr_copy(&(entry->home_addr), home_addr); ++ ipv6_addr_copy(&(entry->coa), coa); ++ entry->ifindex = ifindex; ++ entry->seq = seq; ++ entry->type = type; ++ entry->flags = flags; ++ ++ entry->last_br = 0; ++ entry->destunr_count = 0; ++ entry->callback_time = now + lifetime * HZ; ++ if (entry->type & HOME_REGISTRATION) ++ entry->br_callback_time = 0; ++ else ++ entry->br_callback_time = now + ++ (lifetime - BCACHE_BR_SEND_LEAD) * HZ; ++ ++ if (update) { ++ DEBUG(DBG_INFO, "updating entry : %x", entry); ++ hashlist_reposition(bcache.entries, (void *)entry, ++ entry->callback_time); ++ } else { ++ DEBUG(DBG_INFO, "adding entry: %x", entry); ++ if ((hashlist_add(bcache.entries, ++ &hashkey, ++ entry->callback_time, entry)) < 0) { ++ ++ DEBUG(DBG_ERROR, "Hash add failed"); ++ goto err_hashlist; ++ } ++ } ++ ++ set_timer(); ++ ++out: ++ write_unlock(&bcache_lock); ++ return 0; ++ ++err_hashlist: ++ if (create_tunnel) { ++ mip6_fn.proxy_del(home_addr, entry); ++ } ++err_proxy: ++ if (update) { ++ hashlist_delete(bcache.entries, (void *)entry); ++ } ++ mipv6_bce_free(entry); ++err: ++ write_unlock(&bcache_lock); ++ return ret; ++} ++ ++int mipv6_bcache_icmp_err(struct in6_addr *home_addr, ++ struct in6_addr *our_addr, ++ int destunr_count) ++{ ++ struct mipv6_bce *entry; ++ struct in6_addr_pair hashkey; ++ ++ int ret = -ENOENT; ++ ++ DEBUG_FUNC(); ++ ++ hashkey.a1 = home_addr; ++ hashkey.a2 = our_addr; ++ ++ write_lock(&bcache_lock); ++ if (unlikely(bcache.entries == NULL)) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ ++ if ((entry = (struct mipv6_bce *) ++ hashlist_get(bcache.entries, &hashkey)) != NULL) { ++ entry->last_destunr = jiffies; ++ entry->destunr_count = destunr_count; ++ ret = 0; ++ } ++err: ++ write_unlock(&bcache_lock); ++ return ret; ++} ++ ++ ++/** ++ * mipv6_bcache_delete - delete Binding Cache entry ++ * @home_addr: MN's home address ++ * @our_addr: our address ++ * @type: type of entry ++ * ++ * Deletes an entry associated with @home_addr from Binding Cache. ++ * Valid values for @type are %CACHE_ENTRY, %HOME_REGISTRATION and ++ * %ANY_ENTRY. %ANY_ENTRY deletes any type of entry. ++ **/ ++int mipv6_bcache_delete(struct in6_addr *home_addr, ++ struct in6_addr *our_addr, __u8 type) ++{ ++ struct mipv6_bce *entry; ++ struct in6_addr_pair hashkey; ++ int err = 0; ++ ++ DEBUG_FUNC(); ++ ++ if (home_addr == NULL || our_addr == NULL) { ++ DEBUG(DBG_INFO, "error in arguments"); ++ return -EINVAL; ++ } ++ ++ hashkey.a1 = home_addr; ++ hashkey.a2 = our_addr; ++ ++ write_lock(&bcache_lock); ++ ++ if (unlikely(bcache.entries == NULL) || ++ (entry = (struct mipv6_bce *) ++ hashlist_get(bcache.entries, &hashkey)) == NULL || ++ !(entry->type & type)) { ++ DEBUG(DBG_INFO, "No matching entry found"); ++ err = -ENOENT; ++ goto out; ++ } ++ ++ hashlist_delete(bcache.entries, (void *) entry); ++ mipv6_bce_free(entry); ++ ++ set_timer(); ++out: ++ write_unlock(&bcache_lock); ++ return err; ++} ++ ++/** ++ * mipv6_bcache_exists - check if entry exists ++ * @home_addr: home address to check ++ * @our_addr: our address ++ * ++ * Determines if a binding exists for @home_addr. Returns type of the ++ * entry or negative if entry does not exist. ++ **/ ++int mipv6_bcache_exists(struct in6_addr *home_addr, ++ struct in6_addr *our_addr) ++{ ++ struct mipv6_bce *entry; ++ struct in6_addr_pair hashkey; ++ int type = -ENOENT; ++ ++ DEBUG_FUNC(); ++ ++ if (home_addr == NULL || our_addr == NULL) ++ return -EINVAL; ++ ++ hashkey.a1 = home_addr; ++ hashkey.a2 = our_addr; ++ ++ read_lock(&bcache_lock); ++ if (likely(bcache.entries != NULL) && ++ (entry = (struct mipv6_bce *) ++ hashlist_get(bcache.entries, &hashkey)) != NULL) { ++ type = entry->type; ++ } ++ read_unlock(&bcache_lock); ++ ++ return type; ++} ++ ++/** ++ * mipv6_bcache_get - get entry from Binding Cache ++ * @home_addr: home address to search ++ * @our_addr: our address ++ * @entry: pointer to buffer ++ * ++ * Gets a copy of Binding Cache entry for @home_addr. If entry ++ * exists entry is copied to @entry and zero is returned. ++ * Otherwise returns negative. ++ **/ ++int mipv6_bcache_get(struct in6_addr *home_addr, ++ struct in6_addr *our_addr, ++ struct mipv6_bce *entry) ++{ ++ struct mipv6_bce *entry2; ++ struct in6_addr_pair hashkey; ++ int ret = -ENOENT; ++ ++ DEBUG_FUNC(); ++ ++ if (home_addr == NULL || our_addr == NULL || entry == NULL) ++ return -EINVAL; ++ ++ hashkey.a1 = home_addr; ++ hashkey.a2 = our_addr; ++ ++ read_lock_bh(&bcache_lock); ++ ++ entry2 = (struct mipv6_bce *) ++ hashlist_get(bcache.entries, &hashkey); ++ if (entry2 != NULL) { ++ memcpy(entry, entry2, sizeof(struct mipv6_bce)); ++ ret = 0; ++ } ++ read_unlock_bh(&bcache_lock); ++ return ret; ++} ++ ++int mipv6_bcache_iterate(hashlist_iterator_t func, void *args) ++{ ++ int ret; ++ ++ read_lock_bh(&bcache_lock); ++ ret = hashlist_iterate(bcache.entries, args, func); ++ read_unlock_bh(&bcache_lock); ++ ++ return ret; ++} ++ ++/* ++ * Proc-filesystem functions ++ */ ++ ++#define BC_INFO_LEN 80 ++ ++struct procinfo_iterator_args { ++ char *buffer; ++ int offset; ++ int length; ++ int skip; ++ int len; ++}; ++ ++static int procinfo_iterator(void *data, void *args, unsigned long *pref) ++{ ++ struct procinfo_iterator_args *arg = ++ (struct procinfo_iterator_args *) args; ++ struct mipv6_bce *entry = ++ (struct mipv6_bce *) data; ++ ++ ASSERT(entry != NULL); ++ ++ if (arg->skip < arg->offset / BC_INFO_LEN) { ++ arg->skip++; ++ return ITERATOR_CONT; ++ } ++ ++ if (arg->len >= arg->length) ++ return ITERATOR_CONT; ++ ++ /* HoA CoA CallbackInSecs Type */ ++ arg->len += sprintf(arg->buffer + arg->len, ++ "%08x%08x%08x%08x %08x%08x%08x%08x %010lu %02d\n", ++ ntohl(entry->home_addr.s6_addr32[0]), ++ ntohl(entry->home_addr.s6_addr32[1]), ++ ntohl(entry->home_addr.s6_addr32[2]), ++ ntohl(entry->home_addr.s6_addr32[3]), ++ ntohl(entry->coa.s6_addr32[0]), ++ ntohl(entry->coa.s6_addr32[1]), ++ ntohl(entry->coa.s6_addr32[2]), ++ ntohl(entry->coa.s6_addr32[3]), ++ ((entry->callback_time) - jiffies) / HZ, ++ (int) entry->type); ++ ++ return ITERATOR_CONT; ++} ++ ++ /* ++ * Callback function for proc filesystem. ++ */ ++static int bcache_proc_info(char *buffer, char **start, off_t offset, ++ int length) ++{ ++ struct procinfo_iterator_args args; ++ ++ DEBUG_FUNC(); ++ ++ args.buffer = buffer; ++ args.offset = offset; ++ args.length = length; ++ args.skip = 0; ++ args.len = 0; ++ ++ read_lock_bh(&bcache_lock); ++ hashlist_iterate(bcache.entries, &args, procinfo_iterator); ++ read_unlock_bh(&bcache_lock); ++ ++ *start = buffer; ++ if (offset) ++ *start += offset % BC_INFO_LEN; ++ ++ args.len -= offset % BC_INFO_LEN; ++ ++ if (args.len > length) ++ args.len = length; ++ if (args.len < 0) ++ args.len = 0; ++ ++ return args.len; ++} ++ ++static int bcache_compare(void *data, void *hashkey) ++{ ++ struct in6_addr_pair *p = (struct in6_addr_pair *) hashkey; ++ struct mipv6_bce *e = (struct mipv6_bce *) data; ++ ++ if (ipv6_addr_cmp(&e->home_addr, p->a1) == 0 ++ && ipv6_addr_cmp(&e->our_addr, p->a2) == 0) ++ return 0; ++ else ++ return -1; ++} ++ ++static __u32 bcache_hash(void *hashkey) ++{ ++ struct in6_addr_pair *p = (struct in6_addr_pair *) hashkey; ++ ++ return p->a1->s6_addr32[0] ^ p->a1->s6_addr32[1] ^ ++ p->a2->s6_addr32[2] ^ p->a2->s6_addr32[3]; ++} ++ ++/* ++ * Initialization and shutdown functions ++ */ ++ ++int __init mipv6_bcache_init(__u32 size) ++{ ++ if (size < 1) { ++ DEBUG(DBG_ERROR, "Binding cache size must be at least 1"); ++ return -EINVAL; ++ } ++ bcache.entries = hashlist_create(MIPV6_BCACHE_HASHSIZE, size, ++ sizeof(struct mipv6_bce), ++ "mip6_bcache", NULL, NULL, ++ bcache_compare, bcache_hash); ++ ++ if (bcache.entries == NULL) { ++ DEBUG(DBG_ERROR, "Failed to initialize hashlist"); ++ return -ENOMEM; ++ } ++ ++ init_timer(&bcache.callback_timer); ++ bcache.callback_timer.data = 0; ++ bcache.callback_timer.function = timer_handler; ++ bcache.size = size; ++ ++ proc_net_create("mip6_bcache", 0, bcache_proc_info); ++ ++ DEBUG(DBG_INFO, "Binding cache initialized"); ++ return 0; ++} ++ ++static int ++bce_cleanup_iterator(void *rawentry, void *args, unsigned long *sortkey) ++{ ++ int type = (int) args; ++ struct mipv6_bce *entry = (struct mipv6_bce *) rawentry; ++ if (entry->type == type) { ++ if (entry->type & HOME_REGISTRATION) { ++ if (unlikely(mip6_fn.proxy_del == NULL)) ++ DEBUG(DBG_ERROR, "proxy_del unitialized"); ++ else ++ mip6_fn.proxy_del(&entry->home_addr, entry); ++ } ++ return ITERATOR_DELETE_ENTRY; ++ } ++ return ITERATOR_CONT; ++ ++} ++ ++void mipv6_bcache_cleanup(int type) ++{ ++ write_lock_bh(&bcache_lock); ++ hashlist_iterate(bcache.entries,(void *) type, bce_cleanup_iterator); ++ write_unlock_bh(&bcache_lock); ++} ++ ++int __exit mipv6_bcache_exit(void) ++{ ++ struct hashlist *entries; ++ ++ DEBUG_FUNC(); ++ ++ proc_net_remove("mip6_bcache"); ++ ++ write_lock_bh(&bcache_lock); ++ DEBUG(DBG_INFO, "Stopping the bcache timer"); ++ del_timer(&bcache.callback_timer); ++ hashlist_iterate(bcache.entries,(void *)CACHE_ENTRY, ++ bce_cleanup_iterator); ++ ++ entries = bcache.entries; ++ bcache.entries = NULL; ++ write_unlock_bh(&bcache_lock); ++ ++ hashlist_destroy(entries); ++ return 0; ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/bcache.h +@@ -0,0 +1,72 @@ ++/* ++ * MIPL Mobile IPv6 Binding Cache header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _BCACHE_H ++#define _BCACHE_H ++ ++#include ++#include ++#include "hashlist.h" ++ ++#define CACHE_ENTRY 1 /* this and HOME_REGISTRATION are the entry types */ ++#define HOME_REGISTRATION 2 ++#define ANY_ENTRY 3 ++ ++#define MIPV6_MAX_DESTUNREACH 5 /* Delete CN BCEs after 5 destination unreachables */ ++#define MIPV6_DEST_UNR_IVAL 10 /* What is the max interval of destination ++ unreacahable error messages for them to be persistent*/ ++ ++struct mipv6_bce { ++ struct hashlist_entry e; ++ int ifindex; /* Interface identifier */ ++ struct in6_addr our_addr; /* our address (as seen by the MN) */ ++ struct in6_addr home_addr; /* MN home address */ ++ struct in6_addr coa; /* MN care-of address */ ++ unsigned long callback_time; /* time of expiration (in jiffies) */ ++ unsigned long br_callback_time; /* time for sending a BR (in jiffies) */ ++ int (*callback_function)(struct mipv6_bce *entry); ++ __u8 type; /* home registration */ ++ __u8 router; /* mn is router */ ++ __u8 flags; /* flags received in BU */ ++ __u16 seq; /* sequence number */ ++ unsigned long last_br; /* time when last BR sent */ ++ unsigned long last_destunr; /* time when last ICMP destination unreachable received */ ++ int br_count; /* How many BRRs have sent */ ++ int destunr_count; /* Number of destination unreachables received */ ++}; ++ ++int mipv6_bcache_add(int ifindex, struct in6_addr *our_addr, ++ struct in6_addr *home_addr, struct in6_addr *coa, ++ __u32 lifetime, __u16 seq, __u8 flags, __u8 type); ++ ++int mipv6_bcache_icmp_err(struct in6_addr *home_addr, ++ struct in6_addr *our_addr, ++ int destunr_count); ++ ++int mipv6_bcache_delete(struct in6_addr *home_addr, struct in6_addr *our_addr, ++ __u8 type); ++ ++int mipv6_bcache_exists(struct in6_addr *home_addr, ++ struct in6_addr *our_addr); ++ ++int mipv6_bcache_get(struct in6_addr *home_addr, ++ struct in6_addr *our_addr, ++ struct mipv6_bce *entry); ++ ++int mipv6_bcache_iterate(int (*func)(void *, void *, unsigned long *), void *args); ++ ++void mipv6_bcache_cleanup(int type); ++ ++int mipv6_bcache_init(__u32 size); ++ ++int mipv6_bcache_exit(void); ++ ++#endif /* _BCACHE_H */ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/bul.c +@@ -0,0 +1,634 @@ ++/* ++ * Binding update list ++ * ++ * Authors: ++ * Juha Mynttinen ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++/* ++ * Changes: ++ * ++ * Nanno Langstraat : Timer code cleaned up ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "bul.h" ++#include "debug.h" ++#include "hashlist.h" ++#include "tunnel_mn.h" ++#include "mobhdr.h" ++ ++#define MIPV6_BUL_HASHSIZE 32 ++ ++rwlock_t bul_lock = RW_LOCK_UNLOCKED; ++ ++struct mipv6_bul { ++ struct hashlist *entries; ++ struct timer_list callback_timer; ++}; ++ ++static struct mipv6_bul bul; ++ ++struct in6_addr_pair { ++ struct in6_addr *a1; ++ struct in6_addr *a2; ++}; ++ ++/********************************************************************** ++ * ++ * Private functions ++ * ++ **********************************************************************/ ++ ++static int bul_compare(void *data, void *hashkey) ++{ ++ struct in6_addr_pair *p = (struct in6_addr_pair *)hashkey; ++ struct mipv6_bul_entry *e = (struct mipv6_bul_entry *)data; ++ ++ if (ipv6_addr_cmp(&e->cn_addr, p->a1) == 0 ++ && ipv6_addr_cmp(&e->home_addr, p->a2) == 0) ++ return 0; ++ else ++ return -1; ++} ++ ++struct test_keys { ++ struct in6_addr *addr; ++ u8 *cookie; ++}; ++ ++static int bul_compare_cookie(void *data, void *keys) ++{ ++ struct test_keys *p = (struct test_keys *)keys; ++ struct mipv6_bul_entry *e = (struct mipv6_bul_entry *)data; ++ ++ if (ipv6_addr_cmp(&e->cn_addr, p->addr) == 0 && e->rr ++ && memcmp(&e->rr->cot_cookie, p->cookie, 8) == 0) ++ return 0; ++ else ++ return -1; ++} ++ ++static u32 bul_hash(void *hashkey) ++{ ++ struct in6_addr_pair *p = (struct in6_addr_pair *)hashkey; ++ ++ return p->a1->s6_addr32[0] ^ ++ p->a1->s6_addr32[1] ^ ++ p->a1->s6_addr32[2] ^ ++ p->a1->s6_addr32[3]; ++} ++ ++static int bul_proc_info(char *buffer, char **start, off_t offset, ++ int length); ++ ++static struct mipv6_bul_entry *mipv6_bul_get_entry(void) ++{ ++ DEBUG_FUNC(); ++ return ((struct mipv6_bul_entry *) ++ hashlist_alloc(bul.entries, SLAB_ATOMIC)); ++} ++ ++static void mipv6_bul_entry_free(struct mipv6_bul_entry *entry) ++{ ++ DEBUG_FUNC(); ++ ++ if (entry->rr) { ++ if (entry->rr->kbu) ++ kfree(entry->rr->kbu); ++ kfree(entry->rr); ++ } ++ if (entry->ops) ++ kfree(entry->ops); ++ hashlist_free(bul.entries, (void *)entry); ++} ++ ++static __inline__ int del_bul_entry_tnl(struct mipv6_bul_entry *entry) ++{ ++ if (entry->flags & MIPV6_BU_F_HOME) { ++ return mipv6_mv_tnl_to_ha(&entry->cn_addr, ++ &entry->coa, ++ &entry->home_addr); ++ } ++ return 0; ++} ++ ++static void timer_update(void) ++{ ++ struct mipv6_bul_entry *entry; ++ ++ DEBUG_FUNC(); ++ ++ entry = hashlist_get_first(bul.entries); ++ ++ while (entry && time_after_eq(jiffies, entry->callback_time)) { ++ if (time_after_eq(jiffies, entry->expire) || ++ entry->callback(entry) != 0) { ++ /* ++ * Either the entry has expired, or the callback ++ * indicated that it should be deleted. ++ */ ++ hashlist_delete(bul.entries, (void *)entry); ++ ++ del_bul_entry_tnl(entry); ++ mipv6_bul_entry_free(entry); ++ DEBUG(DBG_INFO, "Entry deleted (was expired) from " ++ "binding update list"); ++ } else { ++ /* move entry to its right place in the hashlist */ ++ DEBUG(DBG_INFO, "Rescheduling"); ++ hashlist_reposition(bul.entries, (void *)entry, ++ entry->callback_time); ++ } ++ entry = (struct mipv6_bul_entry *) ++ hashlist_get_first(bul.entries); ++ } ++ ++ if (entry == NULL) { ++ DEBUG(DBG_INFO, "bul empty, not setting a new timer"); ++ del_timer(&bul.callback_timer); ++ } else { ++ mod_timer(&bul.callback_timer, entry->callback_time); ++ } ++} ++ ++static void timer_handler(unsigned long dummy) ++{ ++ DEBUG_FUNC(); ++ ++ write_lock(&bul_lock); ++ timer_update(); ++ write_unlock(&bul_lock); ++} ++ ++/********************************************************************** ++ * ++ * Public interface functions ++ * ++ **********************************************************************/ ++ ++/** ++ * mipv6_bul_iterate - apply interator function to all entries ++ * @func: function to apply ++ * @args: extra arguments for iterator ++ * ++ * Applies @func for each entry in Binding Update List. Extra ++ * arguments given in @args are also passed to the iterator function. ++ * Caller must hold @bul_lock. ++ **/ ++int mipv6_bul_iterate(hashlist_iterator_t func, void *args) ++{ ++ DEBUG_FUNC(); ++ ++ return hashlist_iterate(bul.entries, args, func); ++} ++ ++/** ++ * mipv6_bul_exists - check if Binding Update List entry exists ++ * @cn: address to check ++ * ++ * Checks if Binding Update List has an entry for @cn. Returns true ++ * if entry exists, false otherwise. Caller may not hold @bul_lock. ++ **/ ++int mipv6_bul_exists(struct in6_addr *cn, struct in6_addr *haddr) ++{ ++ int exists; ++ struct in6_addr_pair hashkey; ++ ++ DEBUG_FUNC(); ++ ++ hashkey.a1 = cn; ++ hashkey.a2 = haddr; ++ ++ read_lock_bh(&bul_lock); ++ ++ if (unlikely(bul.entries == NULL)) ++ exists = 0; ++ else ++ exists = (hashlist_get(bul.entries, &hashkey) != NULL); ++ ++ read_unlock_bh(&bul_lock); ++ return exists; ++} ++ ++/** ++ * mipv6_bul_get - get Binding Update List entry ++ * @cn_addr: CN address to search ++ * @home_addr: home address to search ++ * ++ * Returns Binding Update List entry for @cn_addr if it exists. ++ * Otherwise returns %NULL. Caller must hold @bul_lock. ++ **/ ++struct mipv6_bul_entry *mipv6_bul_get(struct in6_addr *cn_addr, ++ struct in6_addr *home_addr) ++{ ++ struct mipv6_bul_entry *entry; ++ struct in6_addr_pair hashkey; ++ ++ DEBUG_FUNC(); ++ ++ if (unlikely(bul.entries == NULL)) { ++ return NULL; ++ } ++ hashkey.a1 = cn_addr; ++ hashkey.a2 = home_addr; ++ ++ entry = (struct mipv6_bul_entry *) ++ hashlist_get(bul.entries, &hashkey); ++ ++ return entry; ++} ++ ++struct mipv6_bul_entry *mipv6_bul_get_by_ccookie( ++ struct in6_addr *cn_addr, u8 *cookie) ++{ ++ struct test_keys key; ++ ++ DEBUG_FUNC(); ++ ++ if (unlikely(bul.entries == NULL)) ++ return NULL; ++ key.addr = cn_addr; ++ key.cookie = cookie; ++ ++ return (struct mipv6_bul_entry *) ++ hashlist_get_ex(bul.entries, &key, ++ bul_compare_cookie); ++} ++ ++/** ++ * mipv6_bul_reschedule - reschedule Binding Update List entry ++ * @entry: entry to reschedule ++ * ++ * Reschedules a Binding Update List entry. Must be called after ++ * modifying entry lifetime. Caller must hold @bul_lock (write). ++ **/ ++void mipv6_bul_reschedule(struct mipv6_bul_entry *entry) ++{ ++ DEBUG_FUNC(); ++ ++ hashlist_reposition(bul.entries, ++ (void *)entry, ++ entry->callback_time); ++ timer_update(); ++} ++ ++/** ++ * mipv6_bul_add - add binding update to Binding Update List ++ * @cn_addr: IPv6 address where BU was sent ++ * @home_addr: Home address for this binding ++ * @coa: Care-of address for this binding ++ * @lifetime: expiration time of the binding in seconds ++ * @seq: sequence number of the BU ++ * @flags: %MIPV6_BU_F_* flags ++ * @callback: callback function called on expiration ++ * @callback_time: expiration time for callback ++ * @state: binding send state ++ * @delay: retransmission delay ++ * @maxdelay: retransmission maximum delay ++ * @ops: Mobility header options for BU ++ * @rr: Return routability information ++ * ++ * Adds a binding update sent to @cn_addr for @home_addr to the ++ * Binding Update List. If entry already exists, it is updated. ++ * Entry is set to expire in @lifetime seconds. Entry has a callback ++ * function @callback that is called at @callback_time. Entry @state ++ * controls resending of this binding update and it can be set to ++ * %ACK_OK, %RESEND_EXP or %ACK_ERROR. Returns a pointer to the newly ++ * created or updated entry. Caller must hold @bul_lock (write). ++ **/ ++struct mipv6_bul_entry *mipv6_bul_add( ++ struct in6_addr *cn_addr, struct in6_addr *home_addr, ++ struct in6_addr *coa, ++ __u32 lifetime, __u16 seq, __u8 flags, ++ int (*callback)(struct mipv6_bul_entry *entry), ++ __u32 callback_time, ++ __u8 state, __u32 delay, __u32 maxdelay, ++ struct mipv6_mh_opt *ops, ++ struct mipv6_rr_info *rr) ++{ ++ struct mipv6_bul_entry *entry; ++ int update = 0; ++ struct in6_addr_pair hashkey; ++ ++ DEBUG_FUNC(); ++ ++ if (unlikely(bul.entries == NULL)) ++ return NULL; ++ ++ if (cn_addr == NULL || home_addr == NULL || coa == NULL || ++ lifetime < 0 || callback == NULL || callback_time < 0 || ++ (state != ACK_OK && state != RESEND_EXP && state != ACK_ERROR) || ++ delay < 0 || maxdelay < 0) { ++ DEBUG(DBG_ERROR, "invalid arguments"); ++ return NULL; ++ } ++ DEBUG(DBG_INFO, "cn_addr: %x:%x:%x:%x:%x:%x:%x:%x, " ++ "home_addr: %x:%x:%x:%x:%x:%x:%x:%x" ++ "coaddr: %x:%x:%x:%x:%x:%x:%x:%x", NIPV6ADDR(cn_addr), ++ NIPV6ADDR(home_addr), NIPV6ADDR(coa)); ++ hashkey.a1 = cn_addr; ++ hashkey.a2 = home_addr; ++ ++ /* ++ * decide whether to add a new entry or update existing, also ++ * check if there's room for a new entry when adding a new ++ * entry (latter is handled by mipv6_bul_get_entry() ++ */ ++ if ((entry = (struct mipv6_bul_entry *) ++ hashlist_get(bul.entries, &hashkey)) != NULL) { ++ /* if an entry for this cn_addr exists (with smaller ++ * seq than the new entry's seq), update it */ ++ ++ if (MIPV6_SEQ_GT(seq, entry->seq)) { ++ DEBUG(DBG_INFO, "updating an existing entry"); ++ update = 1; ++ } else { ++ DEBUG(DBG_INFO, "smaller seq than existing, not updating"); ++ return NULL; ++ } ++ } else { ++ entry = mipv6_bul_get_entry(); ++ if (entry == NULL) { ++ DEBUG(DBG_WARNING, "binding update list full, can't add!!!"); ++ return NULL; ++ } ++ memset(entry, 0, sizeof(*entry)); ++ /* First BU send happens here, save count in the entry */ ++ entry->consecutive_sends = 1; ++ } ++ ++ if (!update) { ++ ipv6_addr_copy(&(entry->cn_addr), cn_addr); ++ ipv6_addr_copy(&(entry->home_addr), home_addr); ++ entry->ops = ops; ++ } ++ /* Add Return Routability info to bul entry */ ++ if (rr) { ++ if(entry->rr) ++ kfree(entry->rr); ++ entry->rr = rr; ++ } ++ ++ ipv6_addr_copy(&(entry->coa), coa); ++ entry->lifetime = lifetime; ++ if (lifetime) ++ entry->expire = jiffies + lifetime * HZ; ++ else if (flags & MIPV6_BU_F_ACK) ++ entry->expire = jiffies + HOME_RESEND_EXPIRE * HZ; ++ entry->seq = seq; ++ entry->flags = flags; ++ entry->lastsend = jiffies; /* current time = last use of the entry */ ++ entry->state = state; ++ entry->delay = delay; ++ entry->maxdelay = maxdelay; ++ entry->callback_time = jiffies + callback_time * HZ; ++ entry->callback = callback; ++ ++ if (flags & MIPV6_BU_F_HOME && ++ mipv6_mv_tnl_to_ha(cn_addr, coa, home_addr)) { ++ DEBUG(DBG_ERROR, "reconfiguration of the tunnel failed"); ++ } ++ if (update) { ++ DEBUG(DBG_INFO, "updating entry: %x", entry); ++ hashlist_reposition(bul.entries, (void *)entry, ++ entry->callback_time); ++ } else { ++ DEBUG(DBG_INFO, "adding entry: %x", entry); ++ ++ hashkey.a1 = &entry->cn_addr; ++ hashkey.a2 = &entry->home_addr; ++ ++ if ((hashlist_add(bul.entries, &hashkey, ++ entry->callback_time, ++ entry)) < 0) { ++ DEBUG(DBG_ERROR, "Hash add failed"); ++ mipv6_bul_entry_free(entry); ++ return NULL; ++ } ++ } ++ timer_update(); ++ ++ return entry; ++} ++ ++/** ++ * mipv6_bul_delete - delete Binding Update List entry ++ * @cn_addr: address for entry to delete ++ * ++ * Deletes the entry for @cn_addr from the Binding Update List. ++ * Returns zero if entry was deleted succesfully, otherwise returns ++ * negative. Caller may not hold @bul_lock. ++ **/ ++int mipv6_bul_delete(struct in6_addr *cn_addr, struct in6_addr *home_addr) ++{ ++ struct mipv6_bul_entry *entry; ++ struct in6_addr_pair hashkey; ++ ++ DEBUG_FUNC(); ++ ++ hashkey.a1 = cn_addr; ++ hashkey.a2 = home_addr; ++ ++ write_lock(&bul_lock); ++ ++ if (unlikely(bul.entries == NULL) || ++ (entry = (struct mipv6_bul_entry *) ++ hashlist_get(bul.entries, &hashkey)) == NULL) { ++ write_unlock(&bul_lock); ++ DEBUG(DBG_INFO, "No such entry"); ++ return -ENOENT; ++ } ++ ++ hashlist_delete(bul.entries, (void *)entry); ++ ++ del_bul_entry_tnl(entry); ++ ++ mipv6_bul_entry_free(entry); ++ timer_update(); ++ write_unlock(&bul_lock); ++ ++ DEBUG(DBG_INFO, "Binding update list entry deleted"); ++ ++ return 0; ++} ++ ++/********************************************************************** ++ * ++ * Proc interface functions ++ * ++ **********************************************************************/ ++ ++#define BUL_INFO_LEN 152 ++ ++struct procinfo_iterator_args { ++ char *buffer; ++ int offset; ++ int length; ++ int skip; ++ int len; ++}; ++ ++static int procinfo_iterator(void *data, void *args, ++ unsigned long *sortkey) ++{ ++ struct procinfo_iterator_args *arg = ++ (struct procinfo_iterator_args *)args; ++ struct mipv6_bul_entry *entry = ++ (struct mipv6_bul_entry *)data; ++ unsigned long callback_seconds; ++ ++ DEBUG_FUNC(); ++ ++ if (entry == NULL) return ITERATOR_ERR; ++ ++ if (time_after(jiffies, entry->callback_time)) ++ callback_seconds = 0; ++ else ++ callback_seconds = (entry->callback_time - jiffies) / HZ; ++ ++ if (arg->skip < arg->offset / BUL_INFO_LEN) { ++ arg->skip++; ++ return ITERATOR_CONT; ++ } ++ ++ if (arg->len >= arg->length) ++ return ITERATOR_CONT; ++ ++ /* CN HoA CoA ExpInSecs SeqNum State Delay MaxDelay CallbackInSecs */ ++ arg->len += sprintf(arg->buffer + arg->len, ++ "%08x%08x%08x%08x %08x%08x%08x%08x %08x%08x%08x%08x\n" ++ "%010lu %05d %02d %010d %010d %010lu\n", ++ ntohl(entry->cn_addr.s6_addr32[0]), ++ ntohl(entry->cn_addr.s6_addr32[1]), ++ ntohl(entry->cn_addr.s6_addr32[2]), ++ ntohl(entry->cn_addr.s6_addr32[3]), ++ ntohl(entry->home_addr.s6_addr32[0]), ++ ntohl(entry->home_addr.s6_addr32[1]), ++ ntohl(entry->home_addr.s6_addr32[2]), ++ ntohl(entry->home_addr.s6_addr32[3]), ++ ntohl(entry->coa.s6_addr32[0]), ++ ntohl(entry->coa.s6_addr32[1]), ++ ntohl(entry->coa.s6_addr32[2]), ++ ntohl(entry->coa.s6_addr32[3]), ++ (entry->expire - jiffies) / HZ, ++ entry->seq, entry->state, entry->delay, ++ entry->maxdelay, callback_seconds); ++ ++ return ITERATOR_CONT; ++} ++ ++ ++/* ++ * Callback function for proc filesystem. ++ */ ++static int bul_proc_info(char *buffer, char **start, off_t offset, ++ int length) ++{ ++ struct procinfo_iterator_args args; ++ ++ DEBUG_FUNC(); ++ ++ args.buffer = buffer; ++ args.offset = offset; ++ args.length = length; ++ args.skip = 0; ++ args.len = 0; ++ ++ read_lock_bh(&bul_lock); ++ hashlist_iterate(bul.entries, &args, procinfo_iterator); ++ read_unlock_bh(&bul_lock); ++ ++ *start = buffer; ++ if (offset) ++ *start += offset % BUL_INFO_LEN; ++ ++ args.len -= offset % BUL_INFO_LEN; ++ ++ if (args.len > length) ++ args.len = length; ++ if (args.len < 0) ++ args.len = 0; ++ ++ return args.len; ++} ++ ++/********************************************************************** ++ * ++ * Code module init/fini functions ++ * ++ **********************************************************************/ ++ ++int __init mipv6_bul_init(__u32 size) ++{ ++ DEBUG_FUNC(); ++ ++ if (size < 1) { ++ DEBUG(DBG_CRITICAL, ++ "Binding update list size must be at least 1"); ++ return -EINVAL; ++ } ++ bul.entries = hashlist_create(MIPV6_BUL_HASHSIZE, size, ++ sizeof(struct mipv6_bul_entry), ++ "mip6_bul", NULL, NULL, ++ bul_compare, bul_hash); ++ ++ if (bul.entries == NULL) { ++ DEBUG(DBG_CRITICAL, "Couldn't allocate memory for " ++ "hashlist when creating a binding update list"); ++ return -ENOMEM; ++ } ++ init_timer(&bul.callback_timer); ++ bul.callback_timer.data = 0; ++ bul.callback_timer.function = timer_handler; ++ proc_net_create("mip6_bul", 0, bul_proc_info); ++ DEBUG(DBG_INFO, "Binding update list initialized"); ++ return 0; ++} ++ ++void __exit mipv6_bul_exit() ++{ ++ struct mipv6_bul_entry *entry; ++ struct hashlist *entries; ++ ++ DEBUG_FUNC(); ++ ++ proc_net_remove("mip6_bul"); ++ ++ write_lock_bh(&bul_lock); ++ ++ DEBUG(DBG_INFO, "Stopping the bul timer"); ++ del_timer(&bul.callback_timer); ++ ++ while ((entry = (struct mipv6_bul_entry *) ++ hashlist_get_first(bul.entries)) != NULL) { ++ hashlist_delete(bul.entries, (void *)entry); ++ ++ del_bul_entry_tnl(entry); ++ ++ mipv6_bul_entry_free(entry); ++ } ++ entries = bul.entries; ++ bul.entries = NULL; ++ write_unlock_bh(&bul_lock); ++ ++ hashlist_destroy(entries); ++ ++ DEBUG(DBG_INFO, "binding update list destroyed"); ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/bul.h +@@ -0,0 +1,91 @@ ++/* ++ * MIPL Mobile IPv6 Binding Update List header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _BUL_H ++#define _BUL_H ++ ++#include "hashlist.h" ++ ++#define ACK_OK 0x01 ++#define RESEND_EXP 0x02 ++#define ACK_ERROR 0x04 ++ ++#define HOME_RESEND_EXPIRE 3600 ++#define MIPV6_COOKIE_LEN 8 ++struct mipv6_rr_info { ++ /* RR information */ ++ u16 rr_state; /* State of the RR */ ++ u16 rr_flags; /* Flags for the RR */ ++ u8 hot_cookie[MIPV6_COOKIE_LEN]; /* HoT Cookie */ ++ u8 cot_cookie[MIPV6_COOKIE_LEN]; /* CoT Cookie */ ++ u8 home_cookie[MIPV6_COOKIE_LEN]; /* Home Cookie */ ++ u8 careof_cookie[MIPV6_COOKIE_LEN]; /* Careof Cookie */ ++ u32 lastsend_hoti; /* When HoTI was last sent (jiffies) */ ++ u32 lastsend_coti; /* When CoTI was last sent (jiffies) */ ++ u32 home_time; /* when Care-of cookie was received */ ++ u32 careof_time; /* when Home cookie was received */ ++ int home_nonce_index; /* Home cookie nonce index */ ++ int careof_nonce_index; /* Care-of cookie nonce index */ ++ u8 *kbu; /* Binding authentication key */ ++}; ++struct mipv6_bul_entry { ++ struct hashlist_entry e; ++ struct in6_addr cn_addr; /* CN to which BU was sent */ ++ struct in6_addr home_addr; /* home address of this binding */ ++ struct in6_addr coa; /* care-of address of the sent BU */ ++ ++ unsigned long expire; /* entry's expiration time (jiffies) */ ++ __u32 lifetime; /* lifetime sent in this BU */ ++ __u32 lastsend; /* last time when BU sent (jiffies) */ ++ __u32 consecutive_sends; /* Number of consecutive BU's sent */ ++ __u16 seq; /* sequence number of the latest BU */ ++ __u8 flags; /* BU send flags */ ++ __u8 state; /* resend state */ ++ __u32 initdelay; /* initial ack wait */ ++ __u32 delay; /* current ack wait */ ++ __u32 maxdelay; /* maximum ack wait */ ++ ++ struct mipv6_rr_info *rr; ++ struct mipv6_mh_opt *ops; /* saved option values */ ++ ++ unsigned long callback_time; ++ int (*callback)(struct mipv6_bul_entry *entry); ++}; ++ ++extern rwlock_t bul_lock; ++ ++int mipv6_bul_init(__u32 size); ++ ++void mipv6_bul_exit(void); ++ ++struct mipv6_bul_entry *mipv6_bul_add( ++ struct in6_addr *cn_addr, struct in6_addr *home_addr, ++ struct in6_addr *coa, __u32 lifetime, __u16 seq, __u8 flags, ++ int (*callback)(struct mipv6_bul_entry *entry), __u32 callback_time, ++ __u8 state, __u32 delay, __u32 maxdelay, struct mipv6_mh_opt *ops, ++ struct mipv6_rr_info *rr); ++ ++int mipv6_bul_delete(struct in6_addr *cn_addr, struct in6_addr *home_addr); ++ ++int mipv6_bul_exists(struct in6_addr *cnaddr, struct in6_addr *home_addr); ++ ++struct mipv6_bul_entry *mipv6_bul_get(struct in6_addr *cnaddr, ++ struct in6_addr *home_addr); ++struct mipv6_bul_entry *mipv6_bul_get_by_ccookie(struct in6_addr *cn_addr, ++ u8 *cookie); ++ ++int bul_entry_expired(struct mipv6_bul_entry *bulentry); ++ ++void mipv6_bul_reschedule(struct mipv6_bul_entry *entry); ++ ++int mipv6_bul_iterate(int (*func)(void *, void *, unsigned long *), void *args); ++ ++#endif /* BUL_H */ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/config.h +@@ -0,0 +1,72 @@ ++/* ++ * Configuration parameters ++ * ++ * $Id$ ++ */ ++ ++#define MIPV6VERSION "D24" ++#define MIPLVERSION "v1.0" ++ ++#define CAP_CN 0x01 ++#define CAP_HA 0x02 ++#define CAP_MN 0x04 ++ ++struct mip6_conf { ++ int capabilities; ++ int debug_level; ++ int accept_ret_rout; ++ int max_rtr_reachable_time; ++ int eager_cell_switching; ++ int max_num_tunnels; ++ int min_num_tunnels; ++ int binding_refresh_advice; ++ int bu_lladdr; ++ int bu_keymgm; ++ int bu_cn_ack; ++}; ++ ++extern struct mip6_conf mip6node_cnf; ++ ++struct mipv6_bce; ++ ++struct mip6_func { ++ void (*bce_home_add) (int ifindex, struct in6_addr *daddr, ++ struct in6_addr *haddr, struct in6_addr *coa, ++ struct in6_addr *rep_coa, __u32 lifetime, ++ __u16 sequence, __u8 flags, __u8 *k_bu); ++ void (*bce_cache_add) (int ifindex, struct in6_addr *daddr, ++ struct in6_addr *haddr, struct in6_addr *coa, ++ struct in6_addr *rep_coa, __u32 lifetime, ++ __u16 sequence, __u8 flags, __u8 *k_bu); ++ void (*bce_home_del) (struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u16 sequence, __u8 flags, ++ __u8 *k_bu); ++ void (*bce_cache_del) (struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u16 sequence, __u8 flags, ++ __u8 *k_bu); ++ ++ int (*bce_tnl_rt_add) (struct in6_addr *coa, ++ struct in6_addr *ha_addr, ++ struct in6_addr *home_addr); ++ ++ void (*bce_tnl_rt_del) (struct in6_addr *coa, ++ struct in6_addr *ha_addr, ++ struct in6_addr *home_addr); ++ ++ void (*proxy_del) (struct in6_addr *home_addr, struct mipv6_bce *entry); ++ int (*proxy_create) (int flags, int ifindex, struct in6_addr *coa, ++ struct in6_addr *our_addr, struct in6_addr *home_addr); ++ ++ int (*icmpv6_dhaad_rep_rcv) (struct sk_buff *skb); ++ int (*icmpv6_dhaad_req_rcv) (struct sk_buff *skb); ++ int (*icmpv6_pfxadv_rcv) (struct sk_buff *skb); ++ int (*icmpv6_pfxsol_rcv) (struct sk_buff *skb); ++ int (*icmpv6_paramprob_rcv) (struct sk_buff *skb); ++ ++ int (*mn_use_hao) (struct in6_addr *daddr, struct in6_addr *saddr); ++ void (*mn_check_tunneled_packet) (struct sk_buff *skb); ++}; ++ ++extern struct mip6_func mip6_fn; +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/debug.h +@@ -0,0 +1,112 @@ ++/* ++ * MIPL Mobile IPv6 Debugging macros and functions ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _DEBUG_H ++#define _DEBUG_H ++ ++#include ++ ++/* priorities for different debug conditions */ ++ ++#define DBG_CRITICAL 0 /* unrecoverable error */ ++#define DBG_ERROR 1 /* error (recoverable) */ ++#define DBG_WARNING 2 /* unusual situation but not a real error */ ++#define DBG_INFO 3 /* generally useful information */ ++#define DBG_EXTRA 4 /* extra information */ ++#define DBG_FUNC_ENTRY 6 /* use to indicate function entry and exit */ ++#define DBG_DATADUMP 7 /* packet dumps, etc. lots of flood */ ++ ++/** ++ * NIPV6ADDR - macro for IPv6 addresses ++ * @addr: Network byte order IPv6 address ++ * ++ * Macro for printing IPv6 addresses. Used in conjunction with ++ * printk() or derivatives (such as DEBUG macro). ++ **/ ++#define NIPV6ADDR(addr) \ ++ ntohs(((u16 *)addr)[0]), \ ++ ntohs(((u16 *)addr)[1]), \ ++ ntohs(((u16 *)addr)[2]), \ ++ ntohs(((u16 *)addr)[3]), \ ++ ntohs(((u16 *)addr)[4]), \ ++ ntohs(((u16 *)addr)[5]), \ ++ ntohs(((u16 *)addr)[6]), \ ++ ntohs(((u16 *)addr)[7]) ++ ++#ifdef CONFIG_IPV6_MOBILITY_DEBUG ++extern int mipv6_debug; ++ ++/** ++ * debug_print - print debug message ++ * @debug_level: message priority ++ * @fname: calling function's name ++ * @fmt: printf-style formatting string ++ * ++ * Prints a debug message to system log if @debug_level is less or ++ * equal to @mipv6_debug. Should always be called using DEBUG() ++ * macro, not directly. ++ **/ ++static void debug_print(int debug_level, const char *fname, const char* fmt, ...) ++{ ++ char s[1024]; ++ va_list args; ++ ++ if (mipv6_debug < debug_level) ++ return; ++ ++ va_start(args, fmt); ++ vsprintf(s, fmt, args); ++ printk("mip6[%s]: %s\n", fname, s); ++ va_end(args); ++} ++ ++/** ++ * debug_print_buffer - print arbitrary buffer to system log ++ * @debug_level: message priority ++ * @data: pointer to buffer ++ * @len: number of bytes to print ++ * ++ * Prints @len bytes from buffer @data to system log. @debug_level ++ * tells on which debug level message gets printed. For ++ * debug_print_buffer() priority %DBG_DATADUMP should be used. ++ **/ ++#define debug_print_buffer(debug_level,data,len) { \ ++ if (mipv6_debug >= debug_level) { \ ++ int i; \ ++ for (i=0; i ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include "debug.h" ++#include "stats.h" ++#include "mobhdr.h" ++#include "bcache.h" ++#include "config.h" ++ ++/** ++ * mipv6_append_home_addr - Add Home Address Option ++ * @opt: buffer for Home Address Option ++ * @offset: offset from beginning of @opt ++ * @addr: address for HAO ++ * ++ * Adds a Home Address Option to a packet. Option is stored in ++ * @offset from beginning of @opt. The option is created but the ++ * original source address in IPv6 header is left intact. The source ++ * address will be changed from home address to CoA after the checksum ++ * has been calculated in getfrag. Padding is done automatically, and ++ * @opt must have allocated space for both actual option and pad. ++ * Returns offset from @opt to end of options. ++ **/ ++int mipv6_append_home_addr(__u8 *opt, int offset, struct in6_addr *addr) ++{ ++ int pad; ++ struct mipv6_dstopt_homeaddr *ho; ++ ++ DEBUG(DBG_DATADUMP, "HAO: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(addr)); ++ ++ pad = (6 - offset) & 7; ++ mipv6_add_pad(opt + offset, pad); ++ ++ ho = (struct mipv6_dstopt_homeaddr *)(opt + offset + pad); ++ ho->type = MIPV6_TLV_HOMEADDR; ++ ho->length = sizeof(*ho) - 2; ++ ipv6_addr_copy(&ho->addr, addr); ++ ++ return offset + pad + sizeof(*ho); ++} ++static inline int check_hao_validity(struct mipv6_dstopt_homeaddr *haopt, ++ u8 *dst1, ++ struct in6_addr *saddr, ++ struct in6_addr *daddr) ++{ ++ int addr_type = ipv6_addr_type(&haopt->addr); ++ struct mipv6_bce bc_entry; ++ ++ if (addr_type & IPV6_ADDR_LINKLOCAL || ++ !(addr_type & IPV6_ADDR_UNICAST)) { ++ DEBUG(DBG_INFO, "HAO with link local or non-unicast HoA, " ++ "not sending BE to " ++ "home address " ++ "%x:%x:%x:%x:%x:%x:%x:%x ", ++ "care-of address %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&haopt->addr), ++ NIPV6ADDR(saddr)); ++ return -EINVAL; ++ } else if (dst1[0] != IPPROTO_MOBILITY && ++ (mipv6_bcache_get(&haopt->addr, ++ daddr, &bc_entry) != 0 || ++ ipv6_addr_cmp(saddr, &bc_entry.coa))) { ++ DEBUG(DBG_INFO, "HAO without binding or incorrect CoA, " ++ "sending BE code 1: " ++ "home address %x:%x:%x:%x:%x:%x:%x:%x", ++ "to care-of address %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&haopt->addr), ++ NIPV6ADDR(saddr)); ++ return -ENOENT; ++ } ++ return 0; ++} ++/** ++ * mipv6_handle_homeaddr - Home Address Destination Option handler ++ * @skb: packet buffer ++ * @optoff: offset to where option begins ++ * ++ * Handles Home Address Option in IPv6 Destination Option header. ++ * Packet and offset to option are passed. If HAO is used without ++ * binding, sends a Binding Error code 1. When sending BE, notify bit ++ * is cleared to prevent IPv6 error handling from sending ICMP ++ * Parameter Problem. Returns 1 on success, otherwise zero. ++ **/ ++int mipv6_handle_homeaddr(struct sk_buff *skb, int optoff) ++{ ++ struct in6_addr *saddr = &skb->nh.ipv6h->saddr; ++ struct in6_addr coaddr; ++ struct inet6_skb_parm *opt = (struct inet6_skb_parm *) skb->cb; ++ struct mipv6_dstopt_homeaddr *haopt = ++ (struct mipv6_dstopt_homeaddr *) &skb->nh.raw[optoff]; ++ u8 *dst1; ++ int err; ++ ++ DEBUG_FUNC(); ++ ++ if (haopt->length != sizeof(*haopt) - 2) { ++ DEBUG(DBG_WARNING, "HAO has invalid length"); ++ MIPV6_INC_STATS(n_ha_drop.invalid); ++ return 0; ++ } ++ dst1 = (u8 *)skb->h.raw; ++ err = check_hao_validity(haopt, dst1, saddr, &skb->nh.ipv6h->daddr); ++ ++ if (err) { ++ haopt->type &= ~(0x80); /* clear notify bit */ ++ if (err == -ENOENT) ++ mipv6_send_be(&skb->nh.ipv6h->daddr, saddr, ++ &haopt->addr, MIPV6_BE_HAO_WO_BINDING); ++ MIPV6_INC_STATS(n_ha_drop.misc); ++ return 0; ++ } ++ ipv6_addr_copy(&coaddr, saddr); ++ ipv6_addr_copy(saddr, &haopt->addr); ++ ipv6_addr_copy(&haopt->addr, &coaddr); ++ opt->hao = optoff; ++ if (mip6_fn.mn_check_tunneled_packet != NULL) ++ mip6_fn.mn_check_tunneled_packet(skb); ++ ++ MIPV6_INC_STATS(n_ha_rcvd); ++ return 1; ++} ++ ++/** ++ * mipv6_icmp_swap_addrs - Switch HAO and src and RT2 and dest for ICMP errors ++ * @skb: packet buffer ++ * ++ * Reset the source address and the Home Address option in skb before ++ * appending it to an ICMP error message, so original packet appears ++ * in the error message rather than mangled. ++ **/ ++void mipv6_icmp_swap_addrs(struct sk_buff *skb) ++{ ++ struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb; ++ struct in6_addr tmp; ++ struct in6_addr *hoa; ++ DEBUG_FUNC(); ++ if (opt->srcrt2) { ++ struct rt2_hdr *rt2; ++ rt2 = (struct rt2_hdr *)(skb->nh.raw + opt->srcrt2); ++ hoa = &rt2->addr; ++ ++ ipv6_addr_copy(&tmp, hoa); ++ ipv6_addr_copy(hoa, &skb->nh.ipv6h->daddr); ++ ipv6_addr_copy(&skb->nh.ipv6h->daddr, &tmp); ++ rt2->rt_hdr.segments_left++; ++ skb->nh.ipv6h->hop_limit++; ++ } ++ if (opt->hao) { ++ struct mipv6_dstopt_homeaddr *hao; ++ hao = (struct mipv6_dstopt_homeaddr *)(skb->nh.raw + opt->hao); ++ hoa = &hao->addr; ++ ++ ipv6_addr_copy(&tmp, hoa); ++ ipv6_addr_copy(hoa, &skb->nh.ipv6h->saddr); ++ ipv6_addr_copy(&skb->nh.ipv6h->saddr, &tmp); ++ } ++} ++ ++/** ++ * mipv6_append_rt2hdr - Add Type 2 Routing Header ++ * @rt: buffer for new routing header ++ * @addr: intermediate hop address ++ * ++ * Adds a Routing Header Type 2 in a packet. Stores newly created ++ * routing header in buffer @rt. Type 2 RT only carries one address, ++ * so there is no need to process old routing header. @rt must have ++ * allocated space for 24 bytes. ++ **/ ++void mipv6_append_rt2hdr(struct ipv6_rt_hdr *rt, struct in6_addr *addr) ++{ ++ struct rt2_hdr *rt2 = (struct rt2_hdr *)rt; ++ ++ DEBUG(DBG_DATADUMP, "RT2: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(addr)); ++ ++ if (ipv6_addr_type(addr) == IPV6_ADDR_MULTICAST) { ++ DEBUG(DBG_ERROR, "destination address not unicast"); ++ return; ++ } ++ ++ memset(rt2, 0, sizeof(*rt2)); ++ rt2->rt_hdr.type = 2; ++ rt2->rt_hdr.hdrlen = 2; ++ rt2->rt_hdr.segments_left = 1; ++ ipv6_addr_copy(&rt2->addr, addr); ++} ++ ++/** ++ * mipv6_append_dst1opts - Add Destination Option (1) Headers ++ * @dst1opt: buffer for new destination options ++ * @saddr: address for Home Address Option ++ * @old_dst1opt: old destination options ++ * @len: length of options ++ * ++ * Adds Destination Option (1) Header to a packet. New options are ++ * stored in @dst1opt. If old destination options exist, they are ++ * copied from @old_dst1opt. Only Home Address Option is destination ++ * option. @dstopt must have allocated space for @len bytes. @len ++ * includes Destination Option Header (2 bytes), Home Address Option ++ * (18 bytes) and possible HAO pad (8n+6). ++ **/ ++/* ++ * ISSUE: Home Address Destination Option should really be added to a ++ * new destination option header specified in Mobile IPv6 spec which ++ * should be placed after routing header(s), but before fragmentation ++ * header. Putting HAO in DO1 works for now, but support for the new ++ * placement should be added to the IPv6 stack. ++ */ ++void ++mipv6_append_dst1opts(struct ipv6_opt_hdr *dst1opt, struct in6_addr *saddr, ++ struct ipv6_opt_hdr *old_dst1opt, int len) ++{ ++ int offset; ++ ++ if (old_dst1opt) { ++ memcpy(dst1opt, old_dst1opt, ipv6_optlen(old_dst1opt)); ++ offset = ipv6_optlen(old_dst1opt); ++ } else { ++ offset = sizeof (*dst1opt); ++ } ++ dst1opt->hdrlen = (len >> 3) - 1; ++ mipv6_append_home_addr((__u8 *) dst1opt, offset, saddr); ++} ++ ++/** ++ * mipv6_modify_txoptions - Modify outgoing packets ++ * @sk: socket ++ * @skb: packet buffer for outgoing packet ++ * @old_opt: transmit options ++ * @fl: packet flow structure ++ * @dst: pointer to destination cache entry ++ * ++ * Adds Home Address Option (for MN packets, when not at home) and ++ * Routing Header Type 2 (for CN packets when sending to an MN) to ++ * data packets. Old extension headers are copied from @old_opt (if ++ * any). Extension headers are _explicitly_ added for packets with ++ * Mobility Header. Returns the new header structure, or old if no ++ * changes. ++ **/ ++struct ipv6_txoptions * ++mipv6_modify_txoptions(struct sock *sk, struct sk_buff *skb, ++ struct ipv6_txoptions *old_opt, struct flowi *fl, ++ struct dst_entry **dst) ++{ ++ struct ipv6_opt_hdr *old_hopopt = NULL; ++ struct ipv6_opt_hdr *old_dst1opt = NULL; ++ struct ipv6_rt_hdr *old_srcrt = NULL; ++ ++ int srcrtlen = 0, dst1len = 0; ++ int tot_len, use_hao = 0; ++ struct ipv6_txoptions *opt; ++ struct mipv6_bce bc_entry; ++ struct in6_addr tmpaddr, *saddr, *daddr, coaddr; ++ __u8 *opt_ptr; ++ ++ DEBUG_FUNC(); ++ ++ if (fl->proto == IPPROTO_MOBILITY) return old_opt; ++ /* ++ * we have to be prepared to the fact that saddr might not be present, ++ * if that is the case, we acquire saddr just as kernel does. ++ */ ++ saddr = fl ? fl->fl6_src : NULL; ++ daddr = fl ? fl->fl6_dst : NULL; ++ ++ if (daddr == NULL) ++ return old_opt; ++ if (saddr == NULL) { ++ int err = ipv6_get_saddr(NULL, daddr, &tmpaddr); ++ if (err) ++ return old_opt; ++ else ++ saddr = &tmpaddr; ++ } ++ ++ DEBUG(DBG_DATADUMP, ++ "dest. address of packet: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(daddr)); ++ DEBUG(DBG_DATADUMP, " and src. address: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(saddr)); ++ ++ if (old_opt) { ++ old_hopopt = old_opt->hopopt; ++ old_dst1opt = old_opt->dst1opt; ++ old_srcrt = old_opt->srcrt; ++ } ++ ++ if (mip6_fn.mn_use_hao != NULL) ++ use_hao = mip6_fn.mn_use_hao(daddr, saddr); ++ ++ if (use_hao) { ++ if (old_dst1opt) ++ dst1len = ipv6_optlen(old_dst1opt); ++ dst1len += sizeof(struct mipv6_dstopt_homeaddr) + ++ ((6 - dst1len) & 7); /* padding */ ++ } ++ ++ if (mipv6_bcache_get(daddr, saddr, &bc_entry) == 0) ++ srcrtlen = sizeof(struct rt2_hdr); ++ ++ if ((tot_len = srcrtlen + dst1len) == 0) { ++ return old_opt; ++ } ++ ++ tot_len += sizeof(*opt); ++ ++ if (!(opt = kmalloc(tot_len, GFP_ATOMIC))) { ++ return NULL; ++ } ++ memset(opt, 0, tot_len); ++ opt->tot_len = tot_len; ++ opt_ptr = (__u8 *) (opt + 1); ++ ++ if (old_srcrt) { ++ opt->srcrt = old_srcrt; ++ opt->opt_nflen += ipv6_optlen(old_srcrt); ++ } ++ ++ if (srcrtlen) { ++ DEBUG(DBG_DATADUMP, "Binding exists. Adding routing header"); ++ ++ opt->srcrt2 = (struct ipv6_rt_hdr *) opt_ptr; ++ opt->opt_nflen += srcrtlen; ++ opt_ptr += srcrtlen; ++ ++ /* ++ * Append care-of-address to routing header (original ++ * destination address is home address, the first ++ * source route segment gets put to the destination ++ * address and the home address gets to the last ++ * segment of source route (just as it should)) ++ */ ++ ++ ipv6_addr_copy(&coaddr, &bc_entry.coa); ++ ++ mipv6_append_rt2hdr(opt->srcrt2, &coaddr); ++ ++ /* ++ * reroute output (we have to do this in case of TCP ++ * segment) unless a routing header of type 0 is also added ++ */ ++ if (dst && !opt->srcrt) { ++ struct in6_addr *tmp = fl->fl6_dst; ++ fl->fl6_dst = &coaddr; ++ ++ dst_release(*dst); ++ *dst = ip6_route_output(sk, fl); ++ if (skb) ++ skb->dst = *dst; ++ fl->fl6_dst = tmp; ++ ++ DEBUG(DBG_DATADUMP, "Rerouted outgoing packet"); ++ } ++ } ++ ++ /* Only home address option is inserted to first dst opt header */ ++ if (dst1len) { ++ opt->dst1opt = (struct ipv6_opt_hdr *) opt_ptr; ++ opt->opt_flen += dst1len; ++ opt_ptr += dst1len; ++ mipv6_append_dst1opts(opt->dst1opt, saddr, ++ old_dst1opt, dst1len); ++ opt->mipv6_flags = MIPV6_SND_HAO; ++ } else if (old_dst1opt) { ++ opt->dst1opt = old_dst1opt; ++ opt->opt_flen += ipv6_optlen(old_dst1opt); ++ } ++ if (old_hopopt) { ++ opt->hopopt = old_hopopt; ++ opt->opt_nflen += ipv6_optlen(old_hopopt); ++ } ++ ++ return opt; ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/exthdrs.h +@@ -0,0 +1,47 @@ ++/* ++ * MIPL Mobile IPv6 Extension Headers header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _MIPV6_EXTHDRS_H ++#define _MIPV6_EXTHDRS_H ++ ++struct in6_addr; ++struct sk_buff; ++struct ipv6_rt_hdr; ++struct ipv6_opt_hdr; ++struct ipv6_txoptions; ++struct flowi; ++struct dst_entry; ++/* ++ * Home Address Destination Option function prototypes ++ */ ++int mipv6_append_home_addr(__u8 *opt, int offset, struct in6_addr *addr); ++ ++int mipv6_handle_homeaddr(struct sk_buff *skb, int optoff); ++ ++void mipv6_icmp_swap_addrs(struct sk_buff *skb); ++ ++/* ++ * Creates a routing header of type 2. ++ */ ++void mipv6_append_rt2hdr(struct ipv6_rt_hdr *srcrt, struct in6_addr *addr); ++ ++/* Function to add the first destination option header, which may ++ * include a home address option. ++ */ ++void mipv6_append_dst1opts(struct ipv6_opt_hdr *dst1opt, struct in6_addr *saddr, ++ struct ipv6_opt_hdr *old_dst1opt, int len); ++ ++struct ipv6_txoptions *mipv6_modify_txoptions( ++ struct sock *sk, struct sk_buff *skb, ++ struct ipv6_txoptions *old_opt, struct flowi *fl, ++ struct dst_entry **dst); ++ ++#endif /* _MIPV6_EXTHDRS_H */ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/ha.c +@@ -0,0 +1,553 @@ ++/* ++ * Home-agent functionality ++ * ++ * Authors: ++ * Sami Kivisaari ++ * Henrik Petander ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ * Changes: Venkata Jagana, ++ * Krishna Kumar : Statistics fix ++ * Masahide Nakamura : Use of mipv6_forward ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#ifdef CONFIG_SYSCTL ++#include ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "tunnel_ha.h" ++#include "bcache.h" ++#include "stats.h" ++#include "debug.h" ++#include "util.h" ++#include "ha.h" ++#include "config.h" ++#include "mobhdr.h" ++ ++static int mipv6_ha_tunnel_sitelocal = 0; ++ ++#ifdef CONFIG_SYSCTL ++ ++static struct ctl_table_header *mipv6_ha_sysctl_header; ++ ++static struct mipv6_ha_sysctl_table ++{ ++ struct ctl_table_header *sysctl_header; ++ ctl_table mipv6_vars[3]; ++ ctl_table mipv6_mobility_table[2]; ++ ctl_table mipv6_proto_table[2]; ++ ctl_table mipv6_root_table[2]; ++} mipv6_ha_sysctl = { ++ NULL, ++ ++ {{NET_IPV6_MOBILITY_TUNNEL_SITELOCAL, "tunnel_sitelocal", ++ &mipv6_ha_tunnel_sitelocal, sizeof(int), 0644, NULL, ++ &proc_dointvec}, ++ {0}}, ++ ++ {{NET_IPV6_MOBILITY, "mobility", NULL, 0, 0555, ++ mipv6_ha_sysctl.mipv6_vars}, {0}}, ++ {{NET_IPV6, "ipv6", NULL, 0, 0555, ++ mipv6_ha_sysctl.mipv6_mobility_table}, {0}}, ++ {{CTL_NET, "net", NULL, 0, 0555, ++ mipv6_ha_sysctl.mipv6_proto_table}, {0}} ++}; ++ ++#endif /* CONFIG_SYSCTL */ ++ ++ ++/* this is defined in kernel IPv6 module (sockglue.c) */ ++extern struct packet_type ipv6_packet_type; ++ ++/* mipv6_forward: Intercept NS packets destined to home address of MN */ ++int mipv6_forward(struct sk_buff *skb) ++{ ++ struct ipv6hdr *ipv6h; ++ struct in6_addr *daddr, *saddr; ++ __u8 nexthdr; ++ int nhoff; ++ ++ if (skb == NULL) return 0; ++ ++ ipv6h = skb->nh.ipv6h; ++ daddr = &ipv6h->daddr; ++ saddr = &ipv6h->saddr; ++ ++ nexthdr = ipv6h->nexthdr; ++ nhoff = sizeof(*ipv6h); ++ ++ if (ipv6_ext_hdr(nexthdr)) ++ nhoff = ipv6_skip_exthdr(skb, nhoff, &nexthdr, ++ skb->len - sizeof(*ipv6h)); ++ ++ /* Do not to forward Neighbor Solicitation to Home Address of MN */ ++ if (nexthdr == IPPROTO_ICMPV6) { ++ struct icmp6hdr *icmp6h; ++ int dest_type; ++ ++ if (nhoff < 0 || !pskb_may_pull(skb, nhoff + ++ sizeof(struct icmp6hdr))) { ++ kfree_skb(skb); ++ return 0; ++ } ++ ++ dest_type = ipv6_addr_type(daddr); ++ icmp6h = (struct icmp6hdr *)&skb->nh.raw[nhoff]; ++ ++ /* Intercepts NS to HoA of MN */ ++ ++ if ((icmp6h->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION) || ++ ((dest_type & IPV6_ADDR_MULTICAST) && ++ (icmp6h->icmp6_type == NDISC_ROUTER_ADVERTISEMENT))) { ++ ip6_input(skb); ++ } else { ++ ip6_forward(skb); ++ } ++ } else { ++ ip6_forward(skb); ++ } ++ return 0; ++} ++ ++ ++/** ++ * mipv6_proxy_nd_rem - stop acting as a proxy for @home_address ++ * @home_addr: address to remove ++ * @ha_addr: home agent's address on home link ++ * @linklocal: link-local compatibility bit ++ * ++ * When Home Agent acts as a proxy for an address it must leave the ++ * solicited node multicast group for that address and stop responding ++ * to neighbour solicitations. ++ **/ ++static int mipv6_proxy_nd_rem(struct in6_addr *home_addr, ++ int ifindex, int linklocal) ++{ ++ /* When MN returns home HA leaves the solicited mcast groups ++ * for MNs home addresses ++ */ ++ int err; ++ struct net_device *dev; ++ ++ DEBUG_FUNC(); ++ ++ if ((dev = dev_get_by_index(ifindex)) == NULL) { ++ DEBUG(DBG_ERROR, "couldn't get dev"); ++ return -ENODEV; ++ } ++#if 1 /* TEST */ ++ /* Remove link-local entry */ ++ if (linklocal) { ++ struct in6_addr ll_addr; ++ mipv6_generate_ll_addr(&ll_addr, home_addr); ++ if ((err = pneigh_delete(&nd_tbl, &ll_addr, dev)) < 0) { ++ DEBUG(DBG_INFO, ++ "peigh_delete failed for " ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&ll_addr)); ++ } ++ } ++#endif ++ /* Remove global (or site-local) entry */ ++ if ((err = pneigh_delete(&nd_tbl, home_addr, dev)) < 0) { ++ DEBUG(DBG_INFO, ++ "peigh_delete failed for " ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(home_addr)); ++ } ++ dev_put(dev); ++ return err; ++} ++ ++/** ++ * mipv6_proxy_nd - join multicast group for this address ++ * @home_addr: address to defend ++ * @ha_addr: home agent's address on home link ++ * @linklocal: link-local compatibility bit ++ * ++ * While Mobile Node is away from home, Home Agent acts as a proxy for ++ * @home_address. HA responds to neighbour solicitations for @home_address ++ * thus getting all packets destined to home address of MN. ++ **/ ++static int mipv6_proxy_nd(struct in6_addr *home_addr, ++ int ifindex, int linklocal) ++{ ++ /* The HA sends a proxy ndisc_na message to all hosts on MN's ++ * home subnet by sending a neighbor advertisement with the ++ * home address or all addresses of the mobile node if the ++ * prefix is not 0. The addresses are formed by combining the ++ * suffix or the host part of the address with each subnet ++ * prefix that exists in the home subnet ++ */ ++ ++ /* Since no previous entry for MN exists a proxy_nd advertisement ++ * is sent to all nodes link local multicast address ++ */ ++ int err = -1; ++ ++ struct net_device *dev; ++ struct in6_addr na_saddr; ++ struct in6_addr ll_addr; ++ struct pneigh_entry *ll_pneigh; ++ struct in6_addr mcdest; ++ int send_ll_na = 0; ++ int inc_opt = 1; ++ int solicited = 0; ++ int override = 1; ++ ++ DEBUG_FUNC(); ++ ++ if ((dev = dev_get_by_index(ifindex)) == NULL) { ++ DEBUG(DBG_ERROR, "couldn't get dev"); ++ return -ENODEV; ++ } ++ ++ if (!pneigh_lookup(&nd_tbl, home_addr, dev, 1)) { ++ DEBUG(DBG_INFO, ++ "peigh_lookup failed for " ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(home_addr)); ++ goto free_dev; ++ } ++#if 1 /* TEST */ ++ if (linklocal) { ++ mipv6_generate_ll_addr(&ll_addr, home_addr); ++ ++ if ((ll_pneigh = pneigh_lookup(&nd_tbl, &ll_addr, ++ dev, 1)) == NULL) { ++ DEBUG(DBG_INFO, ++ "peigh_lookup failed for " ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&ll_addr)); ++ pneigh_delete(&nd_tbl, home_addr, dev); ++ goto free_dev; ++ } else { ++ send_ll_na = 1; ++ } ++ } else { ++ ll_pneigh = NULL; ++ } ++#endif ++ /* Proxy neighbor advertisement of MN's home address ++ * to all nodes solicited multicast address ++ */ ++ if (!ipv6_get_lladdr(dev, &na_saddr)) { ++ ipv6_addr_all_nodes(&mcdest); ++ ndisc_send_na(dev, NULL, &mcdest, home_addr, 0, ++ solicited, override, inc_opt); ++#if 1 /* TEST */ ++ if (send_ll_na) { ++ ndisc_send_na(dev, NULL, &mcdest, &ll_addr, ++ 0, solicited, override, inc_opt); ++ } ++#endif ++ err = 0; ++ } else { ++ DEBUG(DBG_ERROR, "failed to get link local address for sending proxy NA"); ++ } ++free_dev: ++ dev_put(dev); ++ return err; ++ ++} ++ ++struct inet6_ifaddr *is_on_link_ipv6_address(struct in6_addr *mn_haddr, ++ struct in6_addr *ha_addr) ++{ ++ struct inet6_ifaddr *ifp; ++ struct inet6_dev *in6_dev; ++ struct inet6_ifaddr *oifp = NULL; ++ ++ if ((ifp = ipv6_get_ifaddr(ha_addr, 0)) == NULL) ++ return NULL; ++ ++ if ((in6_dev = ifp->idev) != NULL) { ++ in6_dev_hold(in6_dev); ++ oifp = in6_dev->addr_list; ++ while (oifp != NULL) { ++ spin_lock(&oifp->lock); ++ if (mipv6_prefix_compare(&oifp->addr, mn_haddr, ++ oifp->prefix_len) && ++ !(oifp->flags & IFA_F_TENTATIVE)) { ++ spin_unlock(&oifp->lock); ++ DEBUG(DBG_INFO, "Home Addr Opt: on-link"); ++ in6_ifa_hold(oifp); ++ break; ++ } ++ spin_unlock(&oifp->lock); ++ oifp = oifp->if_next; ++ } ++ in6_dev_put(in6_dev); ++ } ++ in6_ifa_put(ifp); ++/* DEBUG(DBG_WARNING, "Home Addr Opt NOT on-link"); */ ++ return oifp; ++ ++} ++ ++/* ++ * Lifetime checks. ifp->valid_lft >= ifp->prefered_lft always (see addrconf.c) ++ * Returned value is in seconds. ++ */ ++ ++static __u32 get_min_lifetime(struct inet6_ifaddr *ifp, __u32 lifetime) ++{ ++ __u32 rem_lifetime = 0; ++ unsigned long now = jiffies; ++ ++ if (ifp->valid_lft == 0) { ++ rem_lifetime = lifetime; ++ } else { ++ __u32 valid_lft_left = ++ ifp->valid_lft - ((now - ifp->tstamp) / HZ); ++ rem_lifetime = ++ min_t(unsigned long, valid_lft_left, lifetime); ++ } ++ ++ return rem_lifetime; ++} ++ ++#define MAX_LIFETIME 1000 ++ ++/** ++ * mipv6_lifetime_check - check maximum lifetime is not exceeded ++ * @lifetime: lifetime to check ++ * ++ * Checks @lifetime does not exceed %MAX_LIFETIME. Returns @lifetime ++ * if not exceeded, otherwise returns %MAX_LIFETIME. ++ **/ ++static int mipv6_lifetime_check(int lifetime) ++{ ++ return (lifetime > MAX_LIFETIME) ? MAX_LIFETIME : lifetime; ++} ++ ++/* Generic routine handling finish of BU processing */ ++void mipv6_bu_finish(struct inet6_ifaddr *ifp, int ifindex, __u8 ba_status, ++ struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u32 ba_lifetime, __u16 sequence, __u8 flags, __u8 *k_bu) ++{ ++ int err; ++ ++ if (ba_status >= REASON_UNSPECIFIED) { ++ /* DAD failed */ ++ goto out; ++ } ++ ++ ba_lifetime = get_min_lifetime(ifp, ba_lifetime); ++ ba_lifetime = mipv6_lifetime_check(ba_lifetime); ++ ++ if ((err = mipv6_bcache_add(ifindex, daddr, haddr, coa, ++ ba_lifetime, sequence, flags, ++ HOME_REGISTRATION)) != 0 ) { ++ DEBUG(DBG_WARNING, "home reg failed."); ++ ++ if (err == -ENOMEDIUM) ++ return; ++ ++ ba_status = INSUFFICIENT_RESOURCES; ++ } else { ++ DEBUG(DBG_INFO, "home reg succeeded."); ++ } ++ ++ DEBUG(DBG_DATADUMP, "home_addr: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(haddr)); ++ DEBUG(DBG_DATADUMP, "coa: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(coa)); ++ DEBUG(DBG_DATADUMP, "lifet:%d, seq:%d", ba_lifetime, sequence); ++out: ++ mipv6_send_ba(daddr, haddr, coa, rep_coa, ba_status, sequence, ++ ba_lifetime, k_bu); ++} ++ ++static int ha_proxy_create(int flags, int ifindex, struct in6_addr *coa, ++ struct in6_addr *our_addr, struct in6_addr *home_addr) ++{ ++ int ret; ++ ++ if ((ret = mipv6_add_tnl_to_mn(coa, our_addr, home_addr)) <= 0) { ++ if (ret != -ENOMEDIUM) { ++ DEBUG(DBG_ERROR, "unable to configure tunnel to MN!"); ++ } ++ return -1; ++ } ++ if (mipv6_proxy_nd(home_addr, ifindex, ++ flags & MIPV6_BU_F_LLADDR) != 0) { ++ DEBUG(DBG_ERROR, "mipv6_proxy_nd failed!"); ++ mipv6_del_tnl_to_mn(coa, our_addr, home_addr); ++ return -2; ++ } ++ return 0; ++} ++ ++static void ha_proxy_del(struct in6_addr *home_addr, struct mipv6_bce *entry) ++{ ++ if (mipv6_proxy_nd_rem(&entry->home_addr, entry->ifindex, ++ entry->flags & MIPV6_BU_F_LLADDR) == 0) { ++ DEBUG(DBG_INFO, "proxy_nd succ"); ++ } else { ++ DEBUG(DBG_INFO, "proxy_nd fail"); ++ } ++ mipv6_del_tnl_to_mn(&entry->coa, &entry->our_addr, home_addr); ++} ++ ++static void bc_home_add(int ifindex, ++ struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u32 lifetime, __u16 sequence, __u8 flags, ++ __u8 *k_bu) ++{ ++ struct inet6_ifaddr *ifp = NULL; ++ __u8 ba_status = SUCCESS; ++ ++ DEBUG_FUNC(); ++ ++ ifp = is_on_link_ipv6_address(haddr, daddr); ++ ++ if (ifp == NULL) { ++ ba_status = NOT_HOME_SUBNET; ++ } else if (((ipv6_addr_type(haddr) & IPV6_ADDR_SITELOCAL) || ++ (ipv6_addr_type(coa) & IPV6_ADDR_SITELOCAL)) ++ && !mipv6_ha_tunnel_sitelocal) { ++ /* Site-local home or care-of addresses are not ++ accepted by default */ ++ ba_status = ADMINISTRATIVELY_PROHIBITED; ++ } else { ++ int ret; ++ ++ ifindex = ifp->idev->dev->ifindex; ++ ++ if ((ret = mipv6_dad_start(ifp, ifindex, daddr, ++ haddr, coa, rep_coa, lifetime, ++ sequence, flags)) < 0) { ++ /* An error occurred */ ++ ba_status = -ret; ++ } else if (ret) { ++ /* DAD is needed to be performed. */ ++ in6_ifa_put(ifp); ++ return; ++ } ++ } ++ ++ mipv6_bu_finish(ifp, ifindex, ba_status, daddr, haddr, coa, ++ rep_coa, lifetime, sequence, flags, k_bu); ++ if (ifp) ++ in6_ifa_put(ifp); ++} ++ ++static void bc_home_delete(struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u16 sequence, __u8 flags, __u8 *k_bu) ++{ ++ __u8 status = SUCCESS; ++ struct mipv6_bce bce; ++ ++ /* Primary Care-of Address Deregistration */ ++ if (mipv6_bcache_get(haddr, daddr, &bce) < 0) { ++ DEBUG(DBG_INFO, "entry is not in cache"); ++ status = NOT_HA_FOR_MN; ++ } else { ++ ha_proxy_del(&bce.home_addr, &bce); ++ mipv6_bcache_delete(haddr, daddr, HOME_REGISTRATION); ++ } ++ mipv6_send_ba(daddr, haddr, coa, rep_coa, status, sequence, 0, k_bu); ++} ++ ++extern int mipv6_ra_rcv_ptr(struct sk_buff *skb, struct icmp6hdr *msg); ++ ++ ++static int ++mipv6_ha_tnl_xmit_stats_hook(struct ip6_tnl *t, struct sk_buff *skb) ++{ ++ DEBUG_FUNC(); ++ if (is_mip6_tnl(t)) ++ MIPV6_INC_STATS(n_encapsulations); ++ return IP6_TNL_ACCEPT; ++} ++ ++static struct ip6_tnl_hook_ops mipv6_ha_tnl_xmit_stats_ops = { ++ {NULL, NULL}, ++ IP6_TNL_PRE_ENCAP, ++ IP6_TNL_PRI_LAST, ++ mipv6_ha_tnl_xmit_stats_hook ++}; ++ ++static int ++mipv6_ha_tnl_rcv_stats_hook(struct ip6_tnl *t, struct sk_buff *skb) ++{ ++ DEBUG_FUNC(); ++ if (is_mip6_tnl(t)) ++ MIPV6_INC_STATS(n_decapsulations); ++ return IP6_TNL_ACCEPT; ++} ++ ++static struct ip6_tnl_hook_ops mipv6_ha_tnl_rcv_stats_ops = { ++ {NULL, NULL}, ++ IP6_TNL_PRE_DECAP, ++ IP6_TNL_PRI_LAST, ++ mipv6_ha_tnl_rcv_stats_hook ++}; ++ ++static struct mip6_func old; ++ ++int __init mipv6_ha_init(void) ++{ ++ DEBUG_FUNC(); ++ ++#ifdef CONFIG_SYSCTL ++ if (!(mipv6_ha_sysctl_header = ++ register_sysctl_table(mipv6_ha_sysctl.mipv6_root_table, 0))) ++ printk(KERN_ERR "Failed to register sysctl handlers!"); ++#endif ++ memcpy(&old, &mip6_fn, sizeof(struct mip6_func)); ++ mip6_fn.bce_home_add = bc_home_add; ++ mip6_fn.bce_home_del = bc_home_delete; ++ mip6_fn.proxy_del = ha_proxy_del; ++ mip6_fn.proxy_create = ha_proxy_create; ++ /* register packet interception hooks */ ++ ip6ip6_tnl_register_hook(&mipv6_ha_tnl_xmit_stats_ops); ++ ip6ip6_tnl_register_hook(&mipv6_ha_tnl_rcv_stats_ops); ++ return 0; ++} ++ ++void __exit mipv6_ha_exit(void) ++{ ++ DEBUG_FUNC(); ++ ++#ifdef CONFIG_SYSCTL ++ unregister_sysctl_table(mipv6_ha_sysctl_header); ++#endif ++ ++ /* remove packet interception hooks */ ++ ip6ip6_tnl_unregister_hook(&mipv6_ha_tnl_rcv_stats_ops); ++ ip6ip6_tnl_unregister_hook(&mipv6_ha_tnl_xmit_stats_ops); ++ ++ mip6_fn.bce_home_add = old.bce_home_add; ++ mip6_fn.bce_home_del = old.bce_home_del; ++ mip6_fn.proxy_del = old.proxy_del; ++ mip6_fn.proxy_create = old.proxy_create; ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/ha.h +@@ -0,0 +1,39 @@ ++/* ++ * MIPL Mobile IPv6 Home Agent header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _HA_H ++#define _HA_H ++ ++int mipv6_ha_init(void); ++void mipv6_ha_exit(void); ++ ++int mipv6_dad_start(struct inet6_ifaddr *ifp, int ifindex, ++ struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u32 ba_lifetime, __u16 sequence, __u8 flags); ++ ++void mipv6_bu_finish(struct inet6_ifaddr *ifp, int ifindex, ++ __u8 ba_status, struct in6_addr *daddr, ++ struct in6_addr *haddr, struct in6_addr *coa, ++ struct in6_addr *rep_coa, __u32 ba_lifetime, ++ __u16 sequence, __u8 flags, __u8 *k_bu); ++ ++ ++static __inline__ void mipv6_generate_ll_addr(struct in6_addr *ll_addr, ++ struct in6_addr *addr) ++{ ++ ll_addr->s6_addr32[0] = htonl(0xfe800000); ++ ll_addr->s6_addr32[1] = 0; ++ ll_addr->s6_addr32[2] = addr->s6_addr32[2]; ++ ll_addr->s6_addr32[3] = addr->s6_addr32[3]; ++} ++ ++#endif +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/halist.c +@@ -0,0 +1,507 @@ ++/* ++ * Home Agents List ++ * ++ * Authors: ++ * Antti Tuominen ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#define PREF_BASE 0xffff /* MAX value for u16 field in RA */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "hashlist.h" ++#include "util.h" ++#include "debug.h" ++ ++struct mipv6_halist { ++ struct hashlist *entries; ++ struct timer_list expire_timer; ++}; ++ ++static rwlock_t home_agents_lock = RW_LOCK_UNLOCKED; ++ ++static struct mipv6_halist home_agents; ++ ++struct mipv6_halist_entry { ++ struct hashlist_entry e; ++ int ifindex; /* Link identifier */ ++ struct in6_addr link_local_addr; /* HA's link-local address */ ++ struct in6_addr global_addr; /* HA's Global address */ ++ int plen; ++ long preference; /* The preference for this HA */ ++ unsigned long expire; /* expiration time (jiffies) */ ++}; ++ ++static inline void mipv6_ha_ac_add(struct in6_addr *ll_addr, int ifindex, ++ struct in6_addr *glob_addr, int plen) ++{ ++ struct net_device *dev; ++ ++ if ((dev = __dev_get_by_index(ifindex)) && ipv6_chk_addr(ll_addr, dev)) { ++ struct in6_addr addr; ++ mipv6_ha_anycast(&addr, glob_addr, plen); ++ ipv6_dev_ac_inc(dev, &addr); ++ } ++} ++ ++static inline void mipv6_ha_ac_del(struct in6_addr *ll_addr, int ifindex, ++ struct in6_addr *glob_addr, int plen) ++{ ++ struct net_device *dev; ++ ++ if ((dev = __dev_get_by_index(ifindex)) && ipv6_chk_addr(ll_addr, dev)) { ++ struct in6_addr addr; ++ mipv6_ha_anycast(&addr, glob_addr, plen); ++ ipv6_dev_ac_dec(dev, &addr); ++ } ++} ++ ++struct preflist_iterator_args { ++ int count; ++ int requested; ++ int ifindex; ++ struct in6_addr *list; ++}; ++ ++static int preflist_iterator(void *data, void *args, ++ unsigned long *pref) ++{ ++ struct preflist_iterator_args *state = ++ (struct preflist_iterator_args *)args; ++ struct mipv6_halist_entry *entry = ++ (struct mipv6_halist_entry *)data; ++ struct in6_addr *newaddr = ++ (struct in6_addr *)state->list + state->count; ++ ++ if (state->count >= state->requested) ++ return ITERATOR_STOP; ++ ++ if (time_after(jiffies, entry->expire)) { ++ if (!ipv6_addr_any(&entry->link_local_addr)) { ++ mipv6_ha_ac_del(&entry->link_local_addr, ++ entry->ifindex, ++ &entry->global_addr, entry->plen); ++ } ++ DEBUG(DBG_INFO, "preflist_iterator: Deleting entry with address %x:%x:%x:%x:%x:%x:%x:%x to list", NIPV6ADDR(&entry->global_addr)); ++ return ITERATOR_DELETE_ENTRY; ++ } ++ if (state->ifindex != entry->ifindex) ++ return ITERATOR_CONT; ++ ++ ipv6_addr_copy(newaddr, &entry->global_addr); ++ DEBUG(DBG_INFO, "preflist_iterator: adding new entry with address %x:%x:%x:%x:%x:%x:%x:%x to list", NIPV6ADDR(&entry->global_addr)); ++ state->count++; ++ ++ return ITERATOR_CONT; ++} ++ ++static int gc_iterator(void *data, void *args, ++ unsigned long *pref) ++{ ++ struct mipv6_halist_entry *entry = ++ (struct mipv6_halist_entry *)data; ++ ++ int *type = (int *)args; ++ ++ if (*type == 1 || time_after(jiffies, entry->expire)) { ++ if (!ipv6_addr_any(&entry->link_local_addr)) { ++ mipv6_ha_ac_del(&entry->link_local_addr, ++ entry->ifindex, ++ &entry->global_addr, entry->plen); ++ } ++ return ITERATOR_DELETE_ENTRY; ++ } ++ ++ return ITERATOR_CONT; ++} ++ ++static int mipv6_halist_gc(int type) ++{ ++ DEBUG_FUNC(); ++ hashlist_iterate(home_agents.entries, &type, gc_iterator); ++ return 0; ++} ++ ++static void mipv6_halist_expire(unsigned long dummy) ++{ ++ DEBUG_FUNC(); ++ ++ write_lock(&home_agents_lock); ++ mipv6_halist_gc(0); ++ write_unlock(&home_agents_lock); ++} ++ ++ ++static struct mipv6_halist_entry *mipv6_halist_new_entry(void) ++{ ++ struct mipv6_halist_entry *entry; ++ ++ DEBUG_FUNC(); ++ ++ entry = hashlist_alloc(home_agents.entries, SLAB_ATOMIC); ++ ++ return entry; ++} ++ ++ ++ ++/** ++ * mipv6_halist_add - Add new home agent to the Home Agents List ++ * @ifindex: interface identifier ++ * @glob_addr: home agent's global address ++ * @ll_addr: home agent's link-local address ++ * @pref: relative preference for this home agent ++ * @lifetime: lifetime for the entry ++ * ++ * Adds new home agent to the Home Agents List. The list is interface ++ * specific and @ifindex tells through which interface the home agent ++ * was heard. Returns zero on success and negative on failure. ++ **/ ++ ++int mipv6_halist_add(int ifindex, struct in6_addr *glob_addr, int plen, ++ struct in6_addr *ll_addr, unsigned int pref, __u32 lifetime) ++{ ++ int update = 0, ret = 0; ++ unsigned int mpref; ++ struct mipv6_halist_entry *entry = NULL; ++ ++ DEBUG_FUNC(); ++ ++ write_lock(&home_agents_lock); ++ ++ if (glob_addr == NULL || lifetime <= 0) { ++ DEBUG(DBG_WARNING, "invalid arguments"); ++ ret = -EINVAL; ++ goto out; ++ } ++ mpref = PREF_BASE - pref; ++ if ((entry = (struct mipv6_halist_entry *) ++ hashlist_get(home_agents.entries, glob_addr)) != NULL) { ++ if (entry->ifindex == ifindex) { ++ DEBUG(DBG_DATADUMP, "updating old entry with address %x:%x:%x:%x:%x:%x:%x:%x", NIPV6ADDR(glob_addr)); ++ update = 1; ++ } else { ++ DEBUG(DBG_INFO, "halist_add : adding new entry with address %x:%x:%x:%x:%x:%x:%x:%x", NIPV6ADDR(glob_addr)); ++ update = 0; ++ } ++ } ++ if (update) { ++ entry->expire = jiffies + lifetime * HZ; ++ if (entry->preference != mpref) { ++ entry->preference = mpref; ++ ret = hashlist_reposition(home_agents.entries, ++ (void *)entry, mpref); ++ } ++ } else { ++ entry = mipv6_halist_new_entry(); ++ if (entry == NULL) { ++ DEBUG(DBG_INFO, "list full"); ++ ret = -ENOMEM; ++ goto out; ++ } ++ entry->ifindex = ifindex; ++ if (ll_addr) { ++ ipv6_addr_copy(&entry->link_local_addr, ll_addr); ++ mipv6_ha_ac_add(ll_addr, ifindex, glob_addr, plen); ++ } else ++ ipv6_addr_set(&entry->link_local_addr, 0, 0, 0, 0); ++ ++ ipv6_addr_copy(&entry->global_addr, glob_addr); ++ entry->plen = plen; ++ entry->preference = mpref; ++ entry->expire = jiffies + lifetime * HZ; ++ ret = hashlist_add(home_agents.entries, glob_addr, mpref, ++ entry); ++ } ++out: ++ write_unlock(&home_agents_lock); ++ return ret; ++} ++ ++/** ++ * mipv6_halist_delete - delete home agent from Home Agents List ++ * @glob_addr: home agent's global address ++ * ++ * Deletes entry for home agent @glob_addr from the Home Agent List. ++ **/ ++int mipv6_halist_delete(struct in6_addr *glob_addr) ++{ ++ struct hashlist_entry *e; ++ struct mipv6_halist_entry *entry; ++ DEBUG_FUNC(); ++ ++ if (glob_addr == NULL) { ++ DEBUG(DBG_WARNING, "invalid glob addr"); ++ return -EINVAL; ++ } ++ write_lock(&home_agents_lock); ++ if ((e = hashlist_get(home_agents.entries, glob_addr)) == NULL) { ++ write_unlock(&home_agents_lock); ++ return -ENOENT; ++ } ++ hashlist_delete(home_agents.entries, e); ++ entry = (struct mipv6_halist_entry *)e; ++ if (!ipv6_addr_any(&entry->link_local_addr)) { ++ mipv6_ha_ac_del(&entry->link_local_addr, entry->ifindex, ++ &entry->global_addr, entry->plen); ++ } ++ hashlist_free(home_agents.entries, e); ++ write_unlock(&home_agents_lock); ++ return 0; ++} ++ ++/** ++ * mipv6_ha_get_pref_list - Get list of preferred home agents ++ * @ifindex: interface identifier ++ * @addrs: pointer to a buffer to store the list ++ * @max: maximum number of home agents to return ++ * ++ * Creates a list of @max preferred (or all known if less than @max) ++ * home agents. Home Agents List is interface specific so you must ++ * supply @ifindex. Stores list in addrs and returns number of home ++ * agents stored. On failure, returns a negative value. ++ **/ ++int mipv6_ha_get_pref_list(int ifindex, struct in6_addr **addrs, int max) ++{ ++ struct preflist_iterator_args args; ++ ++ DEBUG_FUNC(); ++ if (max <= 0) { ++ *addrs = NULL; ++ return 0; ++ } ++ ++ args.count = 0; ++ args.requested = max; ++ args.ifindex = ifindex; ++ args.list = kmalloc(max * sizeof(struct in6_addr), GFP_ATOMIC); ++ ++ if (args.list == NULL) return -ENOMEM; ++ ++ read_lock(&home_agents_lock); ++ hashlist_iterate(home_agents.entries, &args, preflist_iterator); ++ read_unlock(&home_agents_lock); ++ ++ if (args.count >= 0) { ++ *addrs = args.list; ++ } else { ++ kfree(args.list); ++ *addrs = NULL; ++ } ++ ++ return args.count; ++} ++ ++struct getaddr_iterator_args { ++ struct net_device *dev; ++ struct in6_addr *addr; ++}; ++ ++static int getaddr_iterator(void *data, void *args, ++ unsigned long *pref) ++{ ++ struct mipv6_halist_entry *entry = ++ (struct mipv6_halist_entry *)data; ++ struct getaddr_iterator_args *state = ++ (struct getaddr_iterator_args *)args; ++ ++ if (entry->ifindex != state->dev->ifindex) ++ return ITERATOR_CONT; ++ ++ if (ipv6_chk_addr(&entry->global_addr, state->dev)) { ++ ipv6_addr_copy(state->addr, &entry->global_addr); ++ return ITERATOR_STOP; ++ } ++ return ITERATOR_CONT; ++} ++ ++/* ++ * Get Home Agent Address for given interface. If node is not serving ++ * as a HA for this interface returns negative error value. ++ */ ++int mipv6_ha_get_addr(int ifindex, struct in6_addr *addr) ++{ ++ struct getaddr_iterator_args args; ++ struct net_device *dev; ++ ++ if (ifindex <= 0) ++ return -EINVAL; ++ ++ if ((dev = dev_get_by_index(ifindex)) == NULL) ++ return -ENODEV; ++ ++ memset(addr, 0, sizeof(struct in6_addr)); ++ args.dev = dev; ++ args.addr = addr; ++ read_lock(&home_agents_lock); ++ hashlist_iterate(home_agents.entries, &args, getaddr_iterator); ++ read_unlock(&home_agents_lock); ++ dev_put(dev); ++ ++ if (ipv6_addr_any(addr)) ++ return -ENOENT; ++ ++ return 0; ++} ++ ++#define HALIST_INFO_LEN 81 ++ ++struct procinfo_iterator_args { ++ char *buffer; ++ int offset; ++ int length; ++ int skip; ++ int len; ++}; ++ ++static int procinfo_iterator(void *data, void *args, ++ unsigned long *pref) ++{ ++ struct procinfo_iterator_args *arg = ++ (struct procinfo_iterator_args *)args; ++ struct mipv6_halist_entry *entry = ++ (struct mipv6_halist_entry *)data; ++ unsigned long int expire; ++ ++ DEBUG_FUNC(); ++ ++ if (entry == NULL) return ITERATOR_ERR; ++ ++ if (time_after(jiffies, entry->expire)) { ++ if (!ipv6_addr_any(&entry->link_local_addr)) { ++ mipv6_ha_ac_del(&entry->link_local_addr, ++ entry->ifindex, ++ &entry->global_addr, entry->plen); ++ } ++ return ITERATOR_DELETE_ENTRY; ++ } ++ if (arg->skip < arg->offset / HALIST_INFO_LEN) { ++ arg->skip++; ++ return ITERATOR_CONT; ++ } ++ ++ if (arg->len >= arg->length) ++ return ITERATOR_CONT; ++ ++ expire = (entry->expire - jiffies) / HZ; ++ ++ arg->len += sprintf(arg->buffer + arg->len, ++ "%02d %08x%08x%08x%08x %08x%08x%08x%08x %05ld %05ld\n", ++ entry->ifindex, ++ ntohl(entry->global_addr.s6_addr32[0]), ++ ntohl(entry->global_addr.s6_addr32[1]), ++ ntohl(entry->global_addr.s6_addr32[2]), ++ ntohl(entry->global_addr.s6_addr32[3]), ++ ntohl(entry->link_local_addr.s6_addr32[0]), ++ ntohl(entry->link_local_addr.s6_addr32[1]), ++ ntohl(entry->link_local_addr.s6_addr32[2]), ++ ntohl(entry->link_local_addr.s6_addr32[3]), ++ -(entry->preference - PREF_BASE), expire); ++ ++ return ITERATOR_CONT; ++} ++ ++static int halist_proc_info(char *buffer, char **start, off_t offset, ++ int length) ++{ ++ struct procinfo_iterator_args args; ++ ++ DEBUG_FUNC(); ++ ++ args.buffer = buffer; ++ args.offset = offset; ++ args.length = length; ++ args.skip = 0; ++ args.len = 0; ++ ++ read_lock_bh(&home_agents_lock); ++ hashlist_iterate(home_agents.entries, &args, procinfo_iterator); ++ read_unlock_bh(&home_agents_lock); ++ ++ *start = buffer; ++ if (offset) ++ *start += offset % HALIST_INFO_LEN; ++ ++ args.len -= offset % HALIST_INFO_LEN; ++ ++ if (args.len > length) ++ args.len = length; ++ if (args.len < 0) ++ args.len = 0; ++ ++ return args.len; ++} ++ ++static int halist_compare(void *data, void *hashkey) ++{ ++ struct mipv6_halist_entry *e = (struct mipv6_halist_entry *)data; ++ struct in6_addr *key = (struct in6_addr *)hashkey; ++ ++ return ipv6_addr_cmp(&e->global_addr, key); ++} ++ ++static __u32 halist_hash(void *hashkey) ++{ ++ struct in6_addr *key = (struct in6_addr *)hashkey; ++ __u32 hash; ++ ++ hash = key->s6_addr32[0] ^ ++ key->s6_addr32[1] ^ ++ key->s6_addr32[2] ^ ++ key->s6_addr32[3]; ++ ++ return hash; ++} ++ ++int __init mipv6_halist_init(__u32 size) ++{ ++ DEBUG_FUNC(); ++ ++ if (size <= 0) { ++ DEBUG(DBG_ERROR, "size must be at least 1"); ++ return -EINVAL; ++ } ++ init_timer(&home_agents.expire_timer); ++ home_agents.expire_timer.data = 0; ++ home_agents.expire_timer.function = mipv6_halist_expire; ++ home_agents_lock = RW_LOCK_UNLOCKED; ++ ++ home_agents.entries = hashlist_create(16, size, sizeof(struct mipv6_halist_entry), ++ "mip6_halist", NULL, NULL, ++ halist_compare, halist_hash); ++ ++ if (home_agents.entries == NULL) { ++ DEBUG(DBG_ERROR, "Failed to initialize hashlist"); ++ return -ENOMEM; ++ } ++ ++ proc_net_create("mip6_home_agents", 0, halist_proc_info); ++ DEBUG(DBG_INFO, "Home Agents List initialized"); ++ return 0; ++} ++ ++void __exit mipv6_halist_exit(void) ++{ ++ DEBUG_FUNC(); ++ proc_net_remove("mip6_home_agents"); ++ write_lock_bh(&home_agents_lock); ++ DEBUG(DBG_INFO, "Stopping the halist timer"); ++ del_timer(&home_agents.expire_timer); ++ mipv6_halist_gc(1); ++ write_unlock_bh(&home_agents_lock); ++ hashlist_destroy(home_agents.entries); ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/halist.h +@@ -0,0 +1,28 @@ ++/* ++ * MIPL Mobile IPv6 Home Agents List header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _HALIST_H ++#define _HALIST_H ++ ++int mipv6_halist_init(__u32 size); ++ ++void mipv6_halist_exit(void); ++ ++int mipv6_halist_add(int ifindex, struct in6_addr *glob_addr, int plen, ++ struct in6_addr *ll_addr, unsigned int pref, __u32 lifetime); ++ ++int mipv6_halist_delete(struct in6_addr *glob_addr); ++ ++int mipv6_ha_get_pref_list(int ifindex, struct in6_addr **addrs, int max); ++ ++int mipv6_ha_get_addr(int ifindex, struct in6_addr *addr); ++ ++#endif /* _HALIST_H */ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/hashlist.c +@@ -0,0 +1,351 @@ ++/* ++ * Generic hashtable with chaining. Supports secodary sort order ++ * with doubly linked-list. ++ * ++ * Authors: ++ * Sami Kivisaari ++ * Antti Tuominen ++ * ++ * $Id: s.hashlist.c 1.21 02/10/07 19:31:52+03:00 antti@traci.mipl.mediapoli.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. ++ */ ++ ++#include ++#include "hashlist.h" ++#include "debug.h" ++ ++struct hashlist { ++ int count; /* entry count */ ++ int maxcount; /* max entries */ ++ __u32 bucketnum; /* hash buckets */ ++ ++ kmem_cache_t *kmem; ++ ++ struct list_head *hashtable; ++ struct list_head sortedlist; ++ ++ int (*compare)(void *data, void *hashkey); ++ __u32 (*hash_function)(void *hashkey); ++}; ++ ++/** ++ * hashlist_create - Create new hashlist ++ * @bucketnum: number of hash buckets ++ * @maxentries: maximum number of entries (0 = no limit) ++ * @size: entry size in bytes ++ * @name: name for kmem_cache_t ++ * @ctor: kmem_cache_t constructor ++ * @dtor: kmem_cache_t destructor ++ * @compare: compare function for key ++ * @hash_function: hash function ++ * ++ * Creates a hashlist structure with @max_entries entries of @size ++ * bytes. User must supply @hash_function and @compare function for ++ * the hashlist. User can also supply @ctor and @dtor for kmem_cache. ++ **/ ++struct hashlist *hashlist_create(int bucketnum, int max_entries, size_t size, ++ char *name, ++ void (*ctor)(void *, kmem_cache_t *, unsigned long), ++ void (*dtor)(void *, kmem_cache_t *, unsigned long), ++ int (*compare)(void *data, void *hashkey), ++ __u32 (*hash_function)(void *hashkey)) ++{ ++ int i; ++ struct hashlist *hl; ++ ++ if (!compare || !hash_function) ++ goto hlfailed; ++ ++ hl = kmalloc(sizeof(struct hashlist), GFP_ATOMIC); ++ if (!hl) goto hlfailed; ++ ++ hl->kmem = kmem_cache_create(name, size, 0, 0, ctor, dtor); ++ if (!hl->kmem) goto poolfailed; ++ ++ hl->hashtable = kmalloc( ++ sizeof(struct list_head) * bucketnum, GFP_ATOMIC); ++ if (!hl->hashtable) goto hashfailed; ++ ++ for (i = 0; i < bucketnum; i++) ++ INIT_LIST_HEAD(&hl->hashtable[i]); ++ ++ INIT_LIST_HEAD(&hl->sortedlist); ++ ++ hl->maxcount = max_entries; ++ hl->count = 0; ++ hl->bucketnum = bucketnum; ++ hl->compare = compare; ++ hl->hash_function = hash_function; ++ ++ return hl; ++ ++hashfailed: ++ kmem_cache_destroy(hl->kmem); ++ hl->kmem = NULL; ++ ++poolfailed: ++ kfree(hl); ++ ++hlfailed: ++ DEBUG(DBG_ERROR, "could not create hashlist"); ++ ++ return NULL; ++} ++ ++/** ++ * hashlist_destroy - Destroy hashlist ++ * @hashlist: hashlist to destroy ++ * ++ * Frees all memory allocated for a hashlist. ++ **/ ++void hashlist_destroy(struct hashlist *hashlist) ++{ ++ DEBUG_FUNC(); ++ ++ if (hashlist == NULL) return; ++ ++ if (hashlist->hashtable) { ++ kfree(hashlist->hashtable); ++ hashlist->hashtable = NULL; ++ } ++ ++ if (hashlist->kmem) { ++ kmem_cache_destroy(hashlist->kmem); ++ hashlist->kmem = NULL; ++ } ++ ++ kfree(hashlist); ++ ++ return; ++} ++ ++/* ++ * Insert a chain of entries to hashlist into correct order. The ++ * entries are assumed to have valid hashkeys. We use time_after_eq ++ * for comparing, since it handles wrap-around correctly, and the ++ * sortkey is usually jiffies. ++ */ ++static void sorted_insert(struct list_head *lh, struct hashlist_entry *he) ++{ ++ struct list_head *p; ++ struct hashlist_entry *hlp = NULL; ++ unsigned long sortkey = he->sortkey; ++ ++ if (list_empty(lh)) { ++ list_add(&he->sorted, lh); ++ return; ++ } ++ ++ list_for_each(p, lh) { ++ hlp = list_entry(p, typeof(*hlp), sorted); ++ if (time_after_eq(hlp->sortkey, sortkey)) { ++ list_add(&he->sorted, hlp->sorted.prev); ++ return; ++ } ++ } ++ list_add(&he->sorted, &hlp->sorted); ++} ++ ++/** ++ * hashlist_iterate - Apply function for all elements in a hash list ++ * @hashlist: pointer to hashlist ++ * @args: data to pass to the function ++ * @func: pointer to a function ++ * ++ * Apply arbitrary function @func to all elements in a hash list. ++ * @func must be a pointer to a function with the following prototype: ++ * int func(void *entry, void *arg, struct in6_addr *hashkey, unsigned ++ * long *sortkey). Function must return %ITERATOR_STOP, ++ * %ITERATOR_CONT or %ITERATOR_DELETE_ENTRY. %ITERATOR_STOP stops ++ * iterator and returns last return value from the function. ++ * %ITERATOR_CONT continues with iteration. %ITERATOR_DELETE_ENTRY ++ * deletes current entry from the hashlist. If function changes ++ * hashlist element's sortkey, iterator automatically schedules ++ * element to be reinserted after all elements have been processed. ++ */ ++int hashlist_iterate( ++ struct hashlist *hashlist, void *args, ++ hashlist_iterator_t func) ++{ ++ int res = ITERATOR_CONT; ++ unsigned long skey; ++ struct list_head *p, *n, repos; ++ struct hashlist_entry *he; ++ ++ DEBUG_FUNC(); ++ INIT_LIST_HEAD(&repos); ++ ++ list_for_each_safe(p, n, &hashlist->sortedlist) { ++ he = list_entry(p, typeof(*he), sorted); ++ if (res == ITERATOR_STOP) ++ break; ++ skey = he->sortkey; ++ res = func(he, args, &he->sortkey); ++ if (res == ITERATOR_DELETE_ENTRY) { ++ hashlist_delete(hashlist, he); ++ hashlist_free(hashlist, he); ++ } else if (skey != he->sortkey) { ++ /* iterator changed the sortkey, schedule for ++ * repositioning */ ++ list_move(&he->sorted, &repos); ++ } ++ } ++ list_for_each_safe(p, n, &repos) { ++ he = list_entry(p, typeof(*he), sorted); ++ sorted_insert(&hashlist->sortedlist, he); ++ } ++ return res; ++} ++ ++/** ++ * hashlist_alloc - Allocate memory for a hashlist entry ++ * @hashlist: hashlist for allocated entry ++ * @size: size of entry in bytes ++ * ++ * Allocates @size bytes memory from @hashlist->kmem. ++ **/ ++void *hashlist_alloc(struct hashlist *hashlist, int type) ++{ ++ if (hashlist == NULL) return NULL; ++ return kmem_cache_alloc(hashlist->kmem, type); ++} ++ ++/** ++ * hashlist_free - Free hashlist entry ++ * @hashlist: hashlist where @he is ++ * @he: entry to free ++ * ++ * Frees an allocated hashlist entry. ++ **/ ++void hashlist_free(struct hashlist *hashlist, struct hashlist_entry *he) ++{ ++ kmem_cache_free(hashlist->kmem, he); ++} ++ ++/** ++ * hashlist_add - Add element to hashlist ++ * @hashlist: pointer to hashlist ++ * @hashkey: hashkey for the element ++ * @sortkey: key for sorting ++ * @data: element data ++ * ++ * Add element to hashlist. Hashlist is also sorted in a linked list ++ * by @sortkey. ++ */ ++int hashlist_add(struct hashlist *hashlist, void *hashkey, ++ unsigned long sortkey, void *entry) ++{ ++ struct hashlist_entry *he = (struct hashlist_entry *)entry; ++ unsigned int hash; ++ ++ if (hashlist->count >= hashlist->maxcount) ++ return -1; ++ ++ hashlist->count++; ++ ++ /* link the entry to sorted order */ ++ he->sortkey = sortkey; ++ sorted_insert(&hashlist->sortedlist, he); ++ ++ /* hash the entry */ ++ hash = hashlist->hash_function(hashkey) % hashlist->bucketnum; ++ list_add(&he->hashlist, &hashlist->hashtable[hash]); ++ ++ return 0; ++} ++ ++/** ++ * hashlist_get_ex - Get element from hashlist ++ * @hashlist: hashlist ++ * @hashkey: hashkey of the desired entry ++ * ++ * Lookup entry with @hashkey from the hash table using @compare ++ * function for entry comparison. Returns entry on success, otherwise ++ * %NULL. ++ **/ ++struct hashlist_entry *hashlist_get_ex( ++ struct hashlist *hashlist, void *hashkey, ++ int (*compare)(void *data, void *hashkey)) ++{ ++ struct list_head *p, *bkt; ++ __u32 hash; ++ ++ hash = hashlist->hash_function(hashkey) % hashlist->bucketnum; ++ bkt = &hashlist->hashtable[hash]; ++ ++ /* scan the entries within the same hashbucket */ ++ list_for_each(p, bkt) { ++ struct hashlist_entry *he = list_entry(p, typeof(*he), ++ hashlist); ++ if (compare(he, hashkey) == 0) ++ return he; ++ } ++ ++ return NULL; ++} ++ ++/** ++ * hashlist_get - Get element from hashlist ++ * @hashlist: hashlist ++ * @hashkey: hashkey of the desired entry ++ * ++ * Lookup entry with @hashkey from the hash table. Returns entry on ++ * success, otherwise %NULL. ++ **/ ++struct hashlist_entry *hashlist_get(struct hashlist *hashlist, void *hashkey) ++{ ++ return hashlist_get_ex(hashlist, hashkey, hashlist->compare); ++} ++ ++/** ++ * hashlist_reposition - set entry to new position in the list ++ * @hashlist: hashlist ++ * @he: entry to reposition ++ * @sortkey: new sortkey of the entry ++ * ++ * If secondary order sortkey changes, entry must be repositioned in ++ * the sorted list. ++ **/ ++int hashlist_reposition(struct hashlist *hashlist, struct hashlist_entry *he, ++ unsigned long sortkey) ++{ ++ list_del(&he->sorted); ++ he->sortkey = sortkey; ++ sorted_insert(&hashlist->sortedlist, he); ++ ++ return 0; ++} ++ ++/** ++ * hashlist_delete - Delete entry from hashlist ++ * @hashlist: hashlist where entry is ++ * @he: entry to delete ++ * ++ * Deletes an entry from the hashlist and sorted list. ++ **/ ++void hashlist_delete(struct hashlist *hashlist, ++ struct hashlist_entry *he) ++{ ++ list_del_init(&he->hashlist); ++ list_del_init(&he->sorted); ++ ++ hashlist->count--; ++} ++ ++/** ++ * hashlist_get_first - Get first item from sorted list ++ * @hashlist: pointer to hashlist ++ * ++ * Returns first item in the secondary sort order. ++ **/ ++void * hashlist_get_first(struct hashlist *hashlist) ++{ ++ if (list_empty(&hashlist->sortedlist)) ++ return NULL; ++ ++ return list_entry(hashlist->sortedlist.next, struct hashlist_entry, sorted); ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/hashlist.h +@@ -0,0 +1,63 @@ ++/* ++ * MIPL Mobile IPv6 Hashlist header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _HASHLIST_H ++#define _HASHLIST_H ++ ++#define ITERATOR_ERR -1 ++#define ITERATOR_CONT 0 ++#define ITERATOR_STOP 1 ++#define ITERATOR_DELETE_ENTRY 2 ++ ++struct kmem_cache_t; ++ ++struct hashlist_entry { ++ unsigned long sortkey; ++ struct list_head sorted; ++ struct list_head hashlist; ++}; ++ ++struct hashlist * hashlist_create( ++ int bucketnum, int max_entries, size_t size, char *name, ++ void (*ctor)(void *, kmem_cache_t *, unsigned long), ++ void (*dtor)(void *, kmem_cache_t *, unsigned long), ++ int (*compare)(void *data, void *hashkey), ++ __u32 (*hash_function)(void *hashkey)); ++ ++void hashlist_destroy(struct hashlist *hashlist); ++ ++void *hashlist_alloc(struct hashlist *hashlist, int type); ++ ++void hashlist_free(struct hashlist *hashlist, struct hashlist_entry *he); ++ ++struct hashlist_entry *hashlist_get(struct hashlist *hashlist, void *hashkey); ++ ++struct hashlist_entry *hashlist_get_ex( ++ struct hashlist *hashlist, void *hashkey, ++ int (*compare)(void *data, void *hashkey)); ++ ++int hashlist_add(struct hashlist *hashlist, void *hashkey, ++ unsigned long sortkey, void *data); ++ ++void hashlist_delete(struct hashlist *hashlist, struct hashlist_entry *he); ++ ++/* iterator function */ ++typedef int (*hashlist_iterator_t)(void *, void *, unsigned long *); ++ ++int hashlist_iterate(struct hashlist *hashlist, void *args, ++ hashlist_iterator_t func); ++ ++void * hashlist_get_first(struct hashlist *hashlist); ++ ++int hashlist_reposition(struct hashlist *hashlist, struct hashlist_entry *he, ++ unsigned long sortkey); ++ ++#endif +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/hmac.c +@@ -0,0 +1,658 @@ ++/* Authentication algorithms ++ * ++ * Authors: ++ * Alexis Olivereau ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ * Changes: ++ * Henrik Petander : Cleaned up unused parts ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "hmac.h" ++#define LROLL(x, s) (((x) << (s)) | ((x) >> (32 - (s)))) ++ ++/* MD5 */ ++#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) ++#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) ++#define H(x, y, z) ((x) ^ (y) ^ (z)) ++#define I(x, y, z) ((y) ^ ((x) | ~(z))) ++ ++#define FF(a, b, c, d, m, s, t) { \ ++ (a) += F ((b), (c), (d)) + (m) + (t); \ ++ (a) = LROLL((a), (s)); \ ++ (a) += (b); \ ++ } ++#define GG(a, b, c, d, m, s, t) { \ ++ (a) += G ((b), (c), (d)) + (m) + (t); \ ++ (a) = LROLL((a), (s)); \ ++ (a) += (b); \ ++ } ++#define HH(a, b, c, d, m, s, t) { \ ++ (a) += H ((b), (c), (d)) + (m) + (t); \ ++ (a) = LROLL((a), (s)); \ ++ (a) += (b); \ ++ } ++#define II(a, b, c, d, m, s, t) { \ ++ (a) += I ((b), (c), (d)) + (m) + (t); \ ++ (a) = LROLL((a), (s)); \ ++ (a) += (b); \ ++ } ++ ++#define s11 7 ++#define s12 12 ++#define s13 17 ++#define s14 22 ++#define s21 5 ++#define s22 9 ++#define s23 14 ++#define s24 20 ++#define s31 4 ++#define s32 11 ++#define s33 16 ++#define s34 23 ++#define s41 6 ++#define s42 10 ++#define s43 15 ++#define s44 21 ++ ++/* SHA-1 */ ++#define f(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) ++#define g(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) ++#define h(x, y, z) ((x) ^ (y) ^ (z)) ++ ++#define K1 0x5a827999 ++#define K2 0x6ed9eba1 ++#define K3 0x8f1bbcdc ++#define K4 0xca62c1d6 ++ ++int ah_hmac_md5_init(struct ah_processing *ahp, u_int8_t *key, u_int32_t key_len) ++{ ++ int i; ++ int key_up4; ++ uint32_t ipad = 0x36363636; ++ uint8_t extkey[64]; ++ ++ ahp->key_auth = key; ++ ahp->key_auth_len = key_len; ++ ahp->context = (void *) kmalloc(sizeof(MD5_CTX), GFP_ATOMIC); ++ if (ahp->context == NULL) ++ return -1; ++ md5_init((MD5_CTX *) ahp->context); ++ if ((64 * sizeof(uint8_t)) < ahp->key_auth_len) { ++ printk("buffer overflow!"); ++ return -1; ++ } ++ memcpy(extkey, ahp->key_auth, ahp->key_auth_len); ++ if (ahp->key_auth_len % 4) { ++ memset(extkey + ahp->key_auth_len, 0, ++ 4 - (ahp->key_auth_len % 4)); ++ } ++ key_up4 = ((ahp->key_auth_len + 0x3) & 0xFFFFFFFC) / 4; ++ ++ for (i = 0; i < key_up4; i++) ++ ((uint32_t *) extkey)[i] = ((uint32_t *) extkey)[i] ^ ipad; ++ for (i = key_up4; i < 16; i++) ++ ((uint32_t *) extkey)[i] = ipad; ++ ++ md5_compute((MD5_CTX *) ahp->context, extkey, 64); ++ return 0; ++} ++ ++void ah_hmac_md5_loop(struct ah_processing *ahp, void *str, uint32_t len) ++{ ++ md5_compute((MD5_CTX *) ahp->context, str, len); ++} ++ ++void ah_hmac_md5_result(struct ah_processing *ahp, char *digest) ++{ ++ uint8_t inner[HMAC_MD5_HASH_LEN]; ++ int i; ++ int key_up4; ++ uint32_t opad = 0x5c5c5c5c; ++ uint8_t extkey[64]; ++ ++ md5_final((MD5_CTX *) ahp->context, inner); ++ md5_init((MD5_CTX *) ahp->context); ++ ++ memcpy(extkey, ahp->key_auth, ahp->key_auth_len); ++ if (ahp->key_auth_len % 4) { ++ memset(extkey + ahp->key_auth_len, 0, ++ 4 - (ahp->key_auth_len % 4)); ++ } ++ key_up4 = ((ahp->key_auth_len + 0x3) & 0xFFFFFFFC) / 4; ++ ++ for (i = 0; i < key_up4; i++) ++ ((uint32_t *) extkey)[i] = ((uint32_t *) extkey)[i] ^ opad; ++ for (i = key_up4; i < 16; i++) ++ ((uint32_t *) extkey)[i] = opad; ++ ++ md5_compute((MD5_CTX *) ahp->context, extkey, 64); ++ md5_compute((MD5_CTX *) ahp->context, inner, HMAC_MD5_HASH_LEN); ++ ++ md5_final((MD5_CTX *) ahp->context, digest); ++ ++ kfree(ahp->context); ++} ++ ++int ah_hmac_sha1_init(struct ah_processing *ahp, u_int8_t *key, u_int32_t key_len) ++{ ++ int i; ++ int key_up4; ++ uint32_t ipad = 0x36363636; ++ uint8_t extkey[64]; ++ ++ ahp->key_auth = key; ++ ahp->key_auth_len = key_len; ++ ++ ahp->context = (void *) kmalloc(sizeof(SHA1_CTX), GFP_ATOMIC); ++ //if (ahp->context == NULL) ++ // return -1; ++ ++ sha1_init((SHA1_CTX *) ahp->context); ++ ++ memcpy(extkey, ahp->key_auth, ahp->key_auth_len); ++ if (ahp->key_auth_len % 4) { ++ memset(extkey + ahp->key_auth_len, 0, ++ 4 - (ahp->key_auth_len % 4)); ++ } ++ key_up4 = ((ahp->key_auth_len + 0x3) & 0xFFFFFFFC) / 4; ++ ++ for (i = 0; i < key_up4; i++) ++ ((uint32_t *) extkey)[i] = ((uint32_t *) extkey)[i] ^ ipad; ++ for (i = key_up4; i < 16; i++) ++ ((uint32_t *) extkey)[i] = ipad; ++ ++ sha1_compute((SHA1_CTX *) ahp->context, extkey, 64); ++ return 0; ++} ++ ++void ah_hmac_sha1_loop(struct ah_processing *ahp, void *str, uint32_t len) ++{ ++ if (!ahp) ++ return; ++ sha1_compute((SHA1_CTX *) ahp->context, str, len); ++} ++ ++void ah_hmac_sha1_result(struct ah_processing *ahp, char *digest) ++{ ++ uint8_t inner[HMAC_SHA1_HASH_LEN]; ++ int i; ++ int key_up4; ++ uint32_t opad = 0x5c5c5c5c; ++ uint8_t extkey[64]; ++ ++ if (!ahp) ++ return; ++ sha1_final((SHA1_CTX *) ahp->context, inner); ++ sha1_init((SHA1_CTX *) ahp->context); ++ ++ memcpy(extkey, ahp->key_auth, ahp->key_auth_len); ++ if (ahp->key_auth_len % 4) { ++ memset(extkey + ahp->key_auth_len, 0, ++ 4 - (ahp->key_auth_len % 4)); ++ } ++ key_up4 = ((ahp->key_auth_len + 0x3) & 0xFFFFFFFC) / 4; ++ ++ for (i = 0; i < key_up4; i++) ++ ((uint32_t *) extkey)[i] = ((uint32_t *) extkey)[i] ^ opad; ++ for (i = key_up4; i < 16; i++) ++ ((uint32_t *) extkey)[i] = opad; ++ ++ sha1_compute((SHA1_CTX *) ahp->context, extkey, 64); ++ sha1_compute((SHA1_CTX *) ahp->context, inner, ++ HMAC_SHA1_HASH_LEN); ++ ++ sha1_final((SHA1_CTX *) ahp->context, digest); ++ ++ kfree(ahp->context); ++} ++ ++void md5_init(MD5_CTX * ctx) ++{ ++ ctx->A = 0x67452301; ++ ctx->B = 0xefcdab89; ++ ctx->C = 0x98badcfe; ++ ctx->D = 0x10325476; ++ ctx->buf_cur = ctx->buf; ++ ctx->bitlen[0] = ctx->bitlen[1] = 0; ++ memset(ctx->buf, 0, 64); ++} ++ ++void md5_over_block(MD5_CTX * ctx, uint8_t * data) ++{ ++ uint32_t M[16]; ++ uint32_t a = ctx->A; ++ uint32_t b = ctx->B; ++ uint32_t c = ctx->C; ++ uint32_t d = ctx->D; ++ ++ create_M_blocks(M, data); ++ ++ /* Round 1 */ ++ FF(a, b, c, d, M[0], s11, 0xd76aa478); /* 1 */ ++ FF(d, a, b, c, M[1], s12, 0xe8c7b756); /* 2 */ ++ FF(c, d, a, b, M[2], s13, 0x242070db); /* 3 */ ++ FF(b, c, d, a, M[3], s14, 0xc1bdceee); /* 4 */ ++ FF(a, b, c, d, M[4], s11, 0xf57c0faf); /* 5 */ ++ FF(d, a, b, c, M[5], s12, 0x4787c62a); /* 6 */ ++ FF(c, d, a, b, M[6], s13, 0xa8304613); /* 7 */ ++ FF(b, c, d, a, M[7], s14, 0xfd469501); /* 8 */ ++ FF(a, b, c, d, M[8], s11, 0x698098d8); /* 9 */ ++ FF(d, a, b, c, M[9], s12, 0x8b44f7af); /* 10 */ ++ FF(c, d, a, b, M[10], s13, 0xffff5bb1); /* 11 */ ++ FF(b, c, d, a, M[11], s14, 0x895cd7be); /* 12 */ ++ FF(a, b, c, d, M[12], s11, 0x6b901122); /* 13 */ ++ FF(d, a, b, c, M[13], s12, 0xfd987193); /* 14 */ ++ FF(c, d, a, b, M[14], s13, 0xa679438e); /* 15 */ ++ FF(b, c, d, a, M[15], s14, 0x49b40821); /* 16 */ ++ ++ /* Round 2 */ ++ GG(a, b, c, d, M[1], s21, 0xf61e2562); /* 17 */ ++ GG(d, a, b, c, M[6], s22, 0xc040b340); /* 18 */ ++ GG(c, d, a, b, M[11], s23, 0x265e5a51); /* 19 */ ++ GG(b, c, d, a, M[0], s24, 0xe9b6c7aa); /* 20 */ ++ GG(a, b, c, d, M[5], s21, 0xd62f105d); /* 21 */ ++ GG(d, a, b, c, M[10], s22, 0x02441453); /* 22 */ ++ GG(c, d, a, b, M[15], s23, 0xd8a1e681); /* 23 */ ++ GG(b, c, d, a, M[4], s24, 0xe7d3fbc8); /* 24 */ ++ GG(a, b, c, d, M[9], s21, 0x21e1cde6); /* 25 */ ++ GG(d, a, b, c, M[14], s22, 0xc33707d6); /* 26 */ ++ GG(c, d, a, b, M[3], s23, 0xf4d50d87); /* 27 */ ++ GG(b, c, d, a, M[8], s24, 0x455a14ed); /* 28 */ ++ GG(a, b, c, d, M[13], s21, 0xa9e3e905); /* 29 */ ++ GG(d, a, b, c, M[2], s22, 0xfcefa3f8); /* 30 */ ++ GG(c, d, a, b, M[7], s23, 0x676f02d9); /* 31 */ ++ GG(b, c, d, a, M[12], s24, 0x8d2a4c8a); /* 32 */ ++ ++ /* Round 3 */ ++ HH(a, b, c, d, M[5], s31, 0xfffa3942); /* 33 */ ++ HH(d, a, b, c, M[8], s32, 0x8771f681); /* 34 */ ++ HH(c, d, a, b, M[11], s33, 0x6d9d6122); /* 35 */ ++ HH(b, c, d, a, M[14], s34, 0xfde5380c); /* 36 */ ++ HH(a, b, c, d, M[1], s31, 0xa4beea44); /* 37 */ ++ HH(d, a, b, c, M[4], s32, 0x4bdecfa9); /* 38 */ ++ HH(c, d, a, b, M[7], s33, 0xf6bb4b60); /* 39 */ ++ HH(b, c, d, a, M[10], s34, 0xbebfbc70); /* 40 */ ++ HH(a, b, c, d, M[13], s31, 0x289b7ec6); /* 41 */ ++ HH(d, a, b, c, M[0], s32, 0xeaa127fa); /* 42 */ ++ HH(c, d, a, b, M[3], s33, 0xd4ef3085); /* 43 */ ++ HH(b, c, d, a, M[6], s34, 0x4881d05); /* 44 */ ++ HH(a, b, c, d, M[9], s31, 0xd9d4d039); /* 45 */ ++ HH(d, a, b, c, M[12], s32, 0xe6db99e5); /* 46 */ ++ HH(c, d, a, b, M[15], s33, 0x1fa27cf8); /* 47 */ ++ HH(b, c, d, a, M[2], s34, 0xc4ac5665); /* 48 */ ++ ++ /* Round 4 */ ++ II(a, b, c, d, M[0], s41, 0xf4292244); /* 49 */ ++ II(d, a, b, c, M[7], s42, 0x432aff97); /* 50 */ ++ II(c, d, a, b, M[14], s43, 0xab9423a7); /* 51 */ ++ II(b, c, d, a, M[5], s44, 0xfc93a039); /* 52 */ ++ II(a, b, c, d, M[12], s41, 0x655b59c3); /* 53 */ ++ II(d, a, b, c, M[3], s42, 0x8f0ccc92); /* 54 */ ++ II(c, d, a, b, M[10], s43, 0xffeff47d); /* 55 */ ++ II(b, c, d, a, M[1], s44, 0x85845dd1); /* 56 */ ++ II(a, b, c, d, M[8], s41, 0x6fa87e4f); /* 57 */ ++ II(d, a, b, c, M[15], s42, 0xfe2ce6e0); /* 58 */ ++ II(c, d, a, b, M[6], s43, 0xa3014314); /* 59 */ ++ II(b, c, d, a, M[13], s44, 0x4e0811a1); /* 60 */ ++ II(a, b, c, d, M[4], s41, 0xf7537e82); /* 61 */ ++ II(d, a, b, c, M[11], s42, 0xbd3af235); /* 62 */ ++ II(c, d, a, b, M[2], s43, 0x2ad7d2bb); /* 63 */ ++ II(b, c, d, a, M[9], s44, 0xeb86d391); /* 64 */ ++ ++ ctx->A += a; ++ ctx->B += b; ++ ctx->C += c; ++ ctx->D += d; ++} ++ ++void create_M_blocks(uint32_t * M, uint8_t * data) ++{ ++#ifdef HAVE_LITTLE_ENDIAN ++ memcpy((uint8_t *) M, data, 64); ++#endif /* HAVE_LITTLE_ENDIAN */ ++ ++#ifdef HAVE_BIG_ENDIAN ++ int i; ++ for (i = 0; i < 16; i++, data += 4) { ++ ((uint8_t *) (&M[i]))[0] = data[3]; ++ ((uint8_t *) (&M[i]))[1] = data[2]; ++ ((uint8_t *) (&M[i]))[2] = data[1]; ++ ((uint8_t *) (&M[i]))[3] = data[0]; ++ } ++#endif /* HAVE_BIG_ENDIAN */ ++} ++ ++void md5_compute(MD5_CTX * ctx, uint8_t * data, uint32_t len) ++{ ++ uint8_t pos = ((ctx->bitlen[0] >> 3) & 0x3f); ++ ++ /* First we update the bit length */ ++ if ((ctx->bitlen[0] += (len << 3)) < (len << 3)) ++ ctx->bitlen[1]++; ++ ctx->bitlen[1] += (len >> 29); /* len is expressed in bytes */ ++ ++ if (pos) { ++ /* Buffer is not empty */ ++ if (64 - pos >= len) { ++ memcpy(ctx->buf_cur, data, len); ++ ctx->buf_cur += len; ++ pos += len; ++ if (pos == 64) { ++ /* The current block is over */ ++ md5_over_block(ctx, ctx->buf); ++ ctx->buf_cur = ctx->buf; ++ } ++ return; ++ } else { ++ memcpy(ctx->buf_cur, data, 64 - pos); ++ md5_over_block(ctx, ctx->buf); ++ len -= (64 - pos); ++ data += (64 - pos); ++ ctx->buf_cur = ctx->buf; ++ } ++ } ++ while (len >= 64) { ++ md5_over_block(ctx, data); ++ len -= 64; ++ data += 64; ++ } ++ if (len) { ++ memcpy(ctx->buf_cur, data, len); ++ ctx->buf_cur += len; ++ } ++} ++ ++void md5_final(MD5_CTX * ctx, uint8_t * digest) ++{ ++ uint32_t rem_size; ++ uint8_t *buf_cur = ctx->buf_cur; ++ int i; ++ ++ rem_size = 64 - ((ctx->bitlen[0] >> 3) & 0x3f); ++ *(buf_cur++) = 0x80; ++ ++ if (rem_size > 8 + 1) { ++ /* We have enough room in the current block */ ++ for (i = 0; i < rem_size - 8 - 1; i++) { ++ *(buf_cur++) = 0; ++ } ++ } else { ++ /* We do not have enough room and need therefore to add a new ++ 64-byte block */ ++ for (i = 0; i < rem_size - 1; i++) { ++ *(buf_cur++) = 0; ++ } ++ md5_over_block(ctx, ctx->buf); ++ ++ buf_cur = ctx->buf; ++ for (i = 0; i < 64 - 8; i++) { ++ *(buf_cur++) = 0; ++ } ++ } ++#ifdef HAVE_LITTLE_ENDIAN ++ memcpy(buf_cur, (uint8_t *) ctx->bitlen, 8); ++#endif /* HAVE_LITTLE_ENDIAN */ ++ ++#ifdef HAVE_BIG_ENDIAN ++ *(buf_cur++) = (ctx->bitlen[0] >> 24) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[0] >> 16) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[0] >> 8) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[0] >> 0) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[1] >> 24) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[1] >> 16) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[1] >> 8) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[1] >> 0) & 0xff; ++#endif /* HAVE_BIG_ENDIAN */ ++ ++ md5_over_block(ctx, ctx->buf); ++ ++#ifdef HAVE_LITTLE_ENDIAN ++ memcpy(digest + 0, (uint8_t *) (&(ctx->A)), sizeof(uint32_t)); ++ memcpy(digest + 4, (uint8_t *) (&(ctx->B)), sizeof(uint32_t)); ++ memcpy(digest + 8, (uint8_t *) (&(ctx->C)), sizeof(uint32_t)); ++ memcpy(digest + 12, (uint8_t *) (&(ctx->D)), sizeof(uint32_t)); ++#endif /* HAVE_LITTLE_ENDIAN */ ++ ++#ifdef HAVE_BIG_ENDIAN ++ digest[0] = ((ctx->A) >> 24) & 0xff; ++ digest[1] = ((ctx->A) >> 16) & 0xff; ++ digest[2] = ((ctx->A) >> 8) & 0xff; ++ digest[3] = ((ctx->A) >> 0) & 0xff; ++ digest[4] = ((ctx->B) >> 24) & 0xff; ++ digest[5] = ((ctx->B) >> 16) & 0xff; ++ digest[6] = ((ctx->B) >> 8) & 0xff; ++ digest[7] = ((ctx->B) >> 0) & 0xff; ++ digest[8] = ((ctx->C) >> 24) & 0xff; ++ digest[9] = ((ctx->C) >> 16) & 0xff; ++ digest[10] = ((ctx->C) >> 8) & 0xff; ++ digest[11] = ((ctx->C) >> 0) & 0xff; ++ digest[12] = ((ctx->D) >> 24) & 0xff; ++ digest[13] = ((ctx->D) >> 16) & 0xff; ++ digest[14] = ((ctx->D) >> 8) & 0xff; ++ digest[15] = ((ctx->D) >> 0) & 0xff; ++#endif /* HAVE_BIG_ENDIAN */ ++} ++ ++void sha1_init(SHA1_CTX * ctx) ++{ ++ ctx->A = 0x67452301; ++ ctx->B = 0xefcdab89; ++ ctx->C = 0x98badcfe; ++ ctx->D = 0x10325476; ++ ctx->E = 0xc3d2e1f0; ++ ctx->buf_cur = ctx->buf; ++ ctx->bitlen[0] = ctx->bitlen[1] = 0; ++ memset(ctx->buf, 0, 64); ++} ++ ++void sha1_over_block(SHA1_CTX * ctx, uint8_t * data) ++{ ++ int i; ++ uint32_t W[80]; ++ uint32_t a = ctx->A; ++ uint32_t b = ctx->B; ++ uint32_t c = ctx->C; ++ uint32_t d = ctx->D; ++ uint32_t e = ctx->E; ++ uint32_t temp; ++ ++ create_W_blocks(W, data); ++ ++ /* Round 1 */ ++ for (i = 0; i < 20; i++) { ++ temp = LROLL(a, 5) + f(b, c, d) + e + W[i] + K1; ++ e = d; ++ d = c; ++ c = LROLL(b, 30); ++ b = a; ++ a = temp; ++ } ++ ++ /* Round 2 */ ++ for (i = 20; i < 40; i++) { ++ temp = LROLL(a, 5) + h(b, c, d) + e + W[i] + K2; ++ e = d; ++ d = c; ++ c = LROLL(b, 30); ++ b = a; ++ a = temp; ++ } ++ ++ /* Round 3 */ ++ for (i = 40; i < 60; i++) { ++ temp = LROLL(a, 5) + g(b, c, d) + e + W[i] + K3; ++ e = d; ++ d = c; ++ c = LROLL(b, 30); ++ b = a; ++ a = temp; ++ } ++ ++ /* Round 4 */ ++ for (i = 60; i < 80; i++) { ++ temp = LROLL(a, 5) + h(b, c, d) + e + W[i] + K4; ++ e = d; ++ d = c; ++ c = LROLL(b, 30); ++ b = a; ++ a = temp; ++ } ++ ++ ctx->A += a; ++ ctx->B += b; ++ ctx->C += c; ++ ctx->D += d; ++ ctx->E += e; ++} ++ ++void create_W_blocks(uint32_t * W, uint8_t * data) ++{ ++ int i; ++ ++#ifdef HAVE_BIG_ENDIAN ++ memcpy((uint8_t *) W, data, 64); ++#endif /* HAVE_BIG_ENDIAN */ ++ ++#ifdef HAVE_LITTLE_ENDIAN ++ for (i = 0; i < 16; i++, data += 4) { ++ ((uint8_t *) (&W[i]))[0] = data[3]; ++ ((uint8_t *) (&W[i]))[1] = data[2]; ++ ((uint8_t *) (&W[i]))[2] = data[1]; ++ ((uint8_t *) (&W[i]))[3] = data[0]; ++ } ++#endif /* HAVE_LITTLE_ENDIAN */ ++ for (i = 16; i < 80; i++) { ++ W[i] = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]; ++ W[i] = LROLL(W[i], 1); ++ } ++} ++ ++void sha1_compute(SHA1_CTX * ctx, uint8_t * data, uint32_t len) ++{ ++ uint8_t pos = ((ctx->bitlen[0] >> 3) & 0x3f); ++ ++ /* First we update the bit length */ ++ if ((ctx->bitlen[0] += (len << 3)) < (len << 3)) ++ ctx->bitlen[1]++; ++ ctx->bitlen[1] += (len >> 29); /* len is expressed in bytes */ ++ ++ if (pos) { ++ /* Buffer is not empty */ ++ if (64 - pos >= len) { ++ memcpy(ctx->buf_cur, data, len); ++ ctx->buf_cur += len; ++ pos += len; ++ if (pos == 64) { ++ /* The current block is over */ ++ sha1_over_block(ctx, ctx->buf); ++ ctx->buf_cur = ctx->buf; ++ } ++ return; ++ } else { ++ memcpy(ctx->buf_cur, data, 64 - pos); ++ sha1_over_block(ctx, ctx->buf); ++ len -= (64 - pos); ++ data += (64 - pos); ++ ctx->buf_cur = ctx->buf; ++ } ++ } ++ while (len >= 64) { ++ sha1_over_block(ctx, data); ++ len -= 64; ++ data += 64; ++ } ++ if (len) { ++ memcpy(ctx->buf_cur, data, len); ++ ctx->buf_cur += len; ++ } ++} ++ ++void sha1_final(SHA1_CTX * ctx, uint8_t * digest) ++{ ++ uint32_t rem_size; ++ uint8_t *buf_cur = ctx->buf_cur; ++ int i; ++ ++ rem_size = 64 - ((ctx->bitlen[0] >> 3) & 0x3f); ++ *(buf_cur++) = 0x80; ++ ++ if (rem_size > 8 + 1) { ++ /* We have enough room in the current block */ ++ for (i = 0; i < rem_size - 8 - 1; i++) { ++ *(buf_cur++) = 0; ++ } ++ } else { ++ /* We do not have enough room and need therefore to add a new ++ 64-byte block */ ++ for (i = 0; i < rem_size - 1; i++) { ++ *(buf_cur++) = 0; ++ } ++ sha1_over_block(ctx, ctx->buf); ++ ++ buf_cur = ctx->buf; ++ for (i = 0; i < 64 - 8; i++) { ++ *(buf_cur++) = 0; ++ } ++ } ++#ifdef HAVE_BIG_ENDIAN ++ memcpy(buf_cur, (uint8_t *) ctx->bitlen, 8); ++#endif /* HAVE_BIG_ENDIAN */ ++ ++#ifdef HAVE_LITTLE_ENDIAN ++ *(buf_cur++) = (ctx->bitlen[1] >> 24) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[1] >> 16) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[1] >> 8) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[1] >> 0) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[0] >> 24) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[0] >> 16) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[0] >> 8) & 0xff; ++ *(buf_cur++) = (ctx->bitlen[0] >> 0) & 0xff; ++#endif /* HAVE_LITTLE_ENDIAN */ ++ ++ sha1_over_block(ctx, ctx->buf); ++ ++#ifdef HAVE_BIG_ENDIAN ++ memcpy(digest + 0, (uint8_t *) (&(ctx->A)), sizeof(uint32_t)); ++ memcpy(digest + 4, (uint8_t *) (&(ctx->B)), sizeof(uint32_t)); ++ memcpy(digest + 8, (uint8_t *) (&(ctx->C)), sizeof(uint32_t)); ++ memcpy(digest + 12, (uint8_t *) (&(ctx->D)), sizeof(uint32_t)); ++ memcpy(digest + 16, (uint8_t *) (&(ctx->E)), sizeof(uint32_t)); ++#endif /* HAVE_BIG_ENDIAN */ ++ ++#ifdef HAVE_LITTLE_ENDIAN ++ digest[0] = ((ctx->A) >> 24) & 0xff; ++ digest[1] = ((ctx->A) >> 16) & 0xff; ++ digest[2] = ((ctx->A) >> 8) & 0xff; ++ digest[3] = ((ctx->A) >> 0) & 0xff; ++ digest[4] = ((ctx->B) >> 24) & 0xff; ++ digest[5] = ((ctx->B) >> 16) & 0xff; ++ digest[6] = ((ctx->B) >> 8) & 0xff; ++ digest[7] = ((ctx->B) >> 0) & 0xff; ++ digest[8] = ((ctx->C) >> 24) & 0xff; ++ digest[9] = ((ctx->C) >> 16) & 0xff; ++ digest[10] = ((ctx->C) >> 8) & 0xff; ++ digest[11] = ((ctx->C) >> 0) & 0xff; ++ digest[12] = ((ctx->D) >> 24) & 0xff; ++ digest[13] = ((ctx->D) >> 16) & 0xff; ++ digest[14] = ((ctx->D) >> 8) & 0xff; ++ digest[15] = ((ctx->D) >> 0) & 0xff; ++ digest[16] = ((ctx->E) >> 24) & 0xff; ++ digest[17] = ((ctx->E) >> 16) & 0xff; ++ digest[18] = ((ctx->E) >> 8) & 0xff; ++ digest[19] = ((ctx->E) >> 0) & 0xff; ++#endif /* HAVE_LITTLE_ENDIAN */ ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/hmac.h +@@ -0,0 +1,94 @@ ++/* ++ * MIPL Mobile IPv6 Message authentication algorithms ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _HMAC_H ++#define _HMAC_H ++ ++#include ++#include ++ ++#define HAVE_LITTLE_ENDIAN ++ ++#define NO_EXPIRY 1 /* For sec_as */ ++ ++#define ALG_AUTH_NONE 0 ++#define ALG_AUTH_HMAC_MD5 1 ++#define ALG_AUTH_HMAC_SHA1 2 ++ ++struct sec_as; ++struct ah_processing { ++ void *context; ++ struct sec_as *sas; ++ u_int8_t *key_auth; ++ u_int32_t key_auth_len; ++}; ++ ++struct antireplay { ++ u_int32_t count; ++ u_int32_t bitmap; ++}; ++ ++typedef struct { ++ u_int32_t A, B, C, D; ++ u_int32_t bitlen[2]; ++ u_int8_t* buf_cur; ++ u_int8_t buf[64]; ++} MD5_CTX; ++ ++typedef struct { ++ u_int32_t A, B, C, D, E; ++ u_int32_t bitlen[2]; ++ u_int8_t* buf_cur; ++ u_int8_t buf[64]; ++} SHA1_CTX; ++ ++ ++ ++int ah_hmac_md5_init (struct ah_processing *ahp, u_int8_t *key, u_int32_t key_len); ++void ah_hmac_md5_loop(struct ah_processing*, void*, u_int32_t); ++void ah_hmac_md5_result(struct ah_processing*, char*); ++int ah_hmac_sha1_init(struct ah_processing*, u_int8_t *key, u_int32_t key_len); ++void ah_hmac_sha1_loop(struct ah_processing*, void*, u_int32_t); ++void ah_hmac_sha1_result(struct ah_processing*, char*); ++ ++ ++#define AH_HDR_LEN 12 /* # of bytes for Next Header, Payload Length, ++ RESERVED, Security Parameters Index and ++ ++ Sequence Number Field */ ++ ++void md5_init(MD5_CTX *ctx); ++void md5_over_block(MD5_CTX *ctx, u_int8_t* data); ++void create_M_blocks(u_int32_t* M, u_int8_t* data); ++void md5_compute(MD5_CTX *ctx, u_int8_t* data, u_int32_t len); ++void md5_final(MD5_CTX *ctx, u_int8_t* digest); ++ ++void sha1_init(SHA1_CTX *ctx); ++void sha1_over_block(SHA1_CTX *ctx, u_int8_t* data); ++void create_W_blocks(u_int32_t* W, u_int8_t* data); ++void sha1_compute(SHA1_CTX *ctx, u_int8_t* data, u_int32_t len); ++void sha1_final(SHA1_CTX *ctx, u_int8_t* digest); ++ ++struct mipv6_acq { ++ struct in6_addr coa; ++ struct in6_addr haddr; ++ struct in6_addr peer; ++ u_int32_t spi; ++}; ++#define MIPV6_MAX_AUTH_DATA 20 ++ ++#define HMAC_MD5_HASH_LEN 16 ++#define HMAC_SHA1_HASH_LEN 20 ++#define HMAC_SHA1_KEY_SIZE 20 ++#define HMAC_MD5_ICV_LEN 12 /* RFC 2403 */ ++#define HMAC_SHA1_ICV_LEN 12 /* RFC 2404 */ ++ ++#endif /* _HMAC_H */ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/ioctl_mn.c +@@ -0,0 +1,142 @@ ++/* ++ * Mobile Node IOCTL Control device ++ * ++ * Authors: ++ * Henrik Petander ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "debug.h" ++#include "mdetect.h" ++#include "multiaccess_ctl.h" ++ ++/* Reserved for local / experimental use */ ++#define MAJOR_NUM 0xf9 ++ ++/* Get Care-of address information for Mobile Node */ ++#define IOCTL_GET_CAREOFADDR _IOWR(MAJOR_NUM, 9, void *) ++ ++#define MA_IOCTL_SET_IFACE_PREFERENCE _IOR (MAJOR_NUM, 13, void *) ++ ++/* The name of the device file */ ++#define CTLFILE "mipv6_dev" ++ ++static int inuse = 0; ++ ++static int mipv6_open(struct inode *inode, struct file *file) ++{ ++ DEBUG(DBG_INFO, "(%p)\n", file); ++ ++ if (inuse) ++ return -EBUSY; ++ ++ inuse++; ++ ++ MOD_INC_USE_COUNT; ++ ++ return 0; ++} ++ ++static int mipv6_close(struct inode *inode, struct file *file) ++{ ++ DEBUG(DBG_INFO, "(%p,%p)\n", inode, file); ++ inuse--; ++ ++ MOD_DEC_USE_COUNT; ++ ++ return 0; ++} ++ ++int mipv6_ioctl(struct inode *inode, struct file *file, ++ unsigned int ioctl_num, /* The number of the ioctl */ ++ unsigned long arg) /* The parameter to it */ ++{ ++ struct in6_addr careofaddr; ++ ++ /* Switch according to the ioctl called */ ++ switch (ioctl_num) { ++ case IOCTL_GET_CAREOFADDR: ++ DEBUG(DBG_DATADUMP, "IOCTL_GET_CAREOFADDR"); ++ /* First get home address from user and then look up ++ * the care-of address and return it ++ */ ++ if (copy_from_user(&careofaddr, (struct in6_addr *)arg, ++ sizeof(struct in6_addr)) < 0) { ++ DEBUG(DBG_WARNING, "Copy from user failed"); ++ return -EFAULT; ++ } ++ mipv6_get_care_of_address(&careofaddr, &careofaddr); ++ if (copy_to_user((struct in6_addr *)arg, &careofaddr, ++ sizeof(struct in6_addr)) < 0) { ++ DEBUG(DBG_WARNING, "copy_to_user failed"); ++ return -EFAULT; ++ } ++ break; ++ case MA_IOCTL_SET_IFACE_PREFERENCE: ++ DEBUG(DBG_INFO, "MA_IOCTL_SET_IFACE_PREFERENCE"); ++ ma_ctl_set_preference(arg); ++ break; ++ ++ default: ++ DEBUG(DBG_WARNING, "Unknown ioctl cmd (%d)", ioctl_num); ++ return -ENOENT; ++ } ++ return 0; ++} ++ ++struct file_operations Fops = { ++ owner: THIS_MODULE, ++ read: NULL, ++ write: NULL, ++ poll: NULL, ++ ioctl: mipv6_ioctl, ++ open: mipv6_open, ++ release: mipv6_close ++}; ++ ++ ++/* Initialize the module - Register the character device */ ++int mipv6_ioctl_mn_init(void) ++{ ++ int ret_val; ++ ++ /* Register the character device (atleast try) */ ++ ret_val = register_chrdev(MAJOR_NUM, CTLFILE, &Fops); ++ ++ /* Negative values signify an error */ ++ if (ret_val < 0) { ++ DEBUG(DBG_ERROR, "failed registering char device (err=%d)", ++ ret_val); ++ return ret_val; ++ } ++ ++ DEBUG(DBG_INFO, "Device number %x, success", MAJOR_NUM); ++ return 0; ++} ++ ++ ++/* Cleanup - unregister the appropriate file from /proc */ ++void mipv6_ioctl_mn_exit(void) ++{ ++ int ret; ++ /* Unregister the device */ ++ ret = unregister_chrdev(MAJOR_NUM, CTLFILE); ++ ++ /* If there's an error, report it */ ++ if (ret < 0) ++ DEBUG(DBG_ERROR, "errorcode: %d\n", ret); ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/mdetect.c +@@ -0,0 +1,1153 @@ ++/* ++ * Movement Detection Module ++ * ++ * Authors: ++ * Henrik Petander ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ * Handles the L3 movement detection of mobile node and also ++ * changing of its routes. ++ * ++ */ ++ ++/* ++ * Changes: ++ * ++ * Nanno Langstraat : Locking fixes ++ * Venkata Jagana : Locking fix ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#ifdef CONFIG_SYSCTL ++#include ++#endif /* CONFIG_SYSCTL */ ++ ++#include "util.h" ++#include "mdetect.h" ++#include "mn.h" ++#include "debug.h" ++#include "multiaccess_ctl.h" ++ ++#define START 0 ++#define CONTINUE 1 ++#define OK 2 ++#define DEBUG_MDETECT 7 ++ ++#define DEF_RTR_POLL_IVAL 5 /* In seconds */ ++ ++#define NO_RTR 0 ++#define RTR_SUSPECT 1 ++#define CURR_RTR_OK 2 ++ ++#define RA_RCVD 0 ++#define NA_RCVD 1 ++#define TIMEOUT 2 ++ ++#define MIPV6_MDF_NONE 0x0 ++#define MIPV6_MDF_HAS_RTR_PREV 0x1 ++ ++#define ROUTER_REACHABLE 1 ++#define RADV_MISSED 2 ++#define NOT_REACHABLE 3 ++ ++/* R_TIME_OUT paramater is used to make the decision when to change the ++ * default router, if the current one is unreachable. 2s is pretty aggressive ++ * and may result in hopping between two routers. OTOH a small value enhances ++ * the performance ++ */ ++#define R_TIME_OUT 30*HZ ++ ++/* maximum RA interval for router unreachability detection */ ++#define MAX_RADV_INTERVAL 6*HZ /* 6000 ms... */ ++ ++/* Threshold for exponential resending of router solicitations */ ++#define RS_RESEND_LINEAR 10*HZ ++ ++#define EAGER_CELL_SWITCHING 1 ++#define LAZY_CELL_SWITCHING 0 ++#define RESPECT_DAD 1 ++ ++#define ROUTER_ADDRESS 0x20 ++ ++/* RA flags */ ++#define ND_RA_FLAG_MANAGED 0x80 ++#define ND_RA_FLAG_OTHER 0x40 ++#define ND_RA_FLAG_HA 0x20 ++ ++/* DAD flags for global and link local addresses */ ++ ++#define COA_TENTATIVE 0x10 ++#define LLADDR_TENTATIVE 0x01 ++ ++struct router { ++ struct list_head list; ++ struct in6_addr ll_addr; ++ struct in6_addr raddr; /* Also contains prefix */ ++ __u8 link_addr[MAX_ADDR_LEN]; /* link layer address */ ++ __u8 link_addr_len; ++ __u8 state; ++ __u8 is_current; ++ __u8 reachable; ++ int ifindex; ++ int pfix_len; /* Length of the network prefix */ ++ unsigned long lifetime; /* from ra */ ++ __u32 last_ns_sent; ++ __u32 last_ra_rcvd; ++ __u32 interval; /* ra interval in milliseconds, 0 if not set */ ++ int glob_addr; /*Whether raddr contains also routers global address*/ ++ __u8 flags; /* RA flags, for example ha */ ++ struct in6_addr CoA; /* care-off address used with this router */ ++ int extra_addr_route; ++}; ++ ++/* dad could also be RESPECT_DAD for duplicate address detection of ++ new care-of addresses */ ++static int dad = 0; ++ ++/* Only one choice, nothing else implemented */ ++int max_rtr_reach_time = DEF_RTR_POLL_IVAL; ++ ++ ++int eager_cell_switching = EAGER_CELL_SWITCHING; /* Can be set to 0 via proc */ ++static spinlock_t router_lock; ++static spinlock_t ho_lock; ++ ++static void coa_timer_handler(unsigned long arg); ++static void timer_handler(unsigned long foo); ++static struct router *curr_router = NULL, *next_router = NULL; ++static struct timer_list r_timer = { function: timer_handler }; ++static struct timer_list coa_timer = { function: coa_timer_handler }; ++#define MAX_ROUTERS 1000 ++static LIST_HEAD(rtr_list); ++static int num_routers = 0; ++static struct handoff *_ho = NULL; ++/* ++ * Functions for handling the default router list, which movement ++ * detection uses for avoiding loops etc. ++ */ ++ ++/* TODO: Send NS to router after MAX interval has passed from last RA */ ++static int mipv6_router_state(struct router *rtr) { ++ if (rtr->interval) { ++ if (time_before(jiffies, (rtr->last_ra_rcvd + (rtr->interval * HZ) / 1000))) ++ return ROUTER_REACHABLE; ++ else ++ return NOT_REACHABLE; ++ } ++ else ++ if (time_after(jiffies, rtr->last_ra_rcvd + (rtr->lifetime * HZ))) ++ return NOT_REACHABLE; ++ return ROUTER_REACHABLE; ++} ++ ++/* searches for a specific router or any router that is reachable, ++ * if address is NULL. Also deletes obsolete routers. ++ */ ++static void mipv6_router_gc(void) ++{ ++ struct router *curr = NULL; ++ struct list_head *lh, *lh_tmp; ++ ++ DEBUG_FUNC(); ++ ++ list_for_each_safe(lh, lh_tmp, &rtr_list) { ++ curr = list_entry(lh, struct router, list); ++ if (mipv6_router_state(curr) == NOT_REACHABLE && !curr->is_current) { ++ num_routers--; ++ list_del_init(&curr->list); ++ DEBUG(DBG_DATADUMP, "Deleting unreachable router %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&curr->raddr)); ++ kfree(curr); ++ } ++ else { ++ DEBUG(DBG_DATADUMP, "NOT Deleting router %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&curr->raddr)); ++ } ++ } ++} ++ ++static struct router *mipv6_rtr_get(struct in6_addr *search_addr) ++{ ++ struct router *rtr = NULL; ++ struct list_head *lh; ++ ++ DEBUG_FUNC(); ++ ++ if (search_addr == NULL) ++ return NULL; ++ list_for_each(lh, &rtr_list) { ++ rtr = list_entry(lh, struct router, list); ++ if(!ipv6_addr_cmp(search_addr, &rtr->raddr)) { ++ return rtr; ++ } ++ } ++ return NULL; ++} ++ ++/* ++ * Adds router to list ++ */ ++static struct router *mipv6_rtr_add(struct router *nrt) ++{ ++ ++ struct router *rptr; ++ ++ DEBUG_FUNC(); ++ ++ /* check if someone is trying DoS attack, or we just have some ++ memory leaks... */ ++ if (num_routers > MAX_ROUTERS) { ++ DEBUG(DBG_CRITICAL, ++ "failed to add new router, MAX_ROUTERS exceeded"); ++ return NULL; ++ } ++ ++ rptr = kmalloc(sizeof(struct router), GFP_ATOMIC); ++ if (rptr) { ++ memcpy(rptr, nrt, sizeof(struct router)); ++ list_add(&rptr->list, &rtr_list); ++ num_routers++; ++ } ++ DEBUG(DBG_INFO, "Adding router: %x:%x:%x:%x:%x:%x:%x:%x, " ++ "lifetime : %d sec, adv.interval: %d millisec", ++ NIPV6ADDR(&rptr->raddr), rptr->lifetime, rptr->interval); ++ ++ DEBUG(DBG_INFO, "num_routers after addition: %d", num_routers); ++ return rptr; ++} ++ ++/* Cleans up the list */ ++static void list_free(struct router **curr_router_p) ++{ ++ struct router *tmp; ++ struct list_head *lh, *lh_tmp; ++ ++ DEBUG_FUNC(); ++ ++ DEBUG(DBG_INFO, "Freeing the router list"); ++ /* set curr_router->prev_router and curr_router NULL */ ++ *curr_router_p = NULL; ++ list_for_each_safe(lh, lh_tmp, &rtr_list) { ++ tmp = list_entry(lh, struct router, list); ++ DEBUG(DBG_INFO, "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&tmp->ll_addr)); ++ list_del(&tmp->list); ++ kfree(tmp); ++ num_routers--; ++ } ++} ++ ++int rs_state = START; ++ ++/* Sends router solicitations to all valid devices ++ * source = link local address (of sending interface) ++ * dstaddr = all routers multicast address ++ * Solicitations are sent at an exponentially decreasing rate ++ * ++ * TODO: send solicitation first at a normal rate (from ipv6) and ++ * after that use the exponentially increasing intervals ++ */ ++static int rs_send(void) ++{ ++ struct net_device *dev; ++ struct in6_addr raddr, lladdr; ++ struct inet6_dev *in6_dev = NULL; ++ static int num_rs; ++ ++ if (rs_state == START) { ++ num_rs = 0; ++ rs_state = CONTINUE; ++ } else if (num_rs++ > MAX_RTR_SOLICITATIONS) ++ return HZ; ++ ++ ipv6_addr_all_routers(&raddr); ++ read_lock(&dev_base_lock); ++ ++ /* Send router solicitations to all interfaces */ ++ for (dev = dev_base; dev; dev = dev->next) { ++ if ((dev->flags & IFF_UP) && dev->type == ARPHRD_ETHER) { ++ DEBUG(DBG_DATADUMP, "Sending RS to device %s", ++ dev->name); ++ if (!ipv6_get_lladdr(dev, &lladdr)) { ++ ndisc_send_rs(dev, &lladdr, &raddr); ++ in6_dev = in6_dev_get(dev); ++ in6_dev->if_flags |= IF_RS_SENT; ++ in6_dev_put(in6_dev); ++ } else { ++ DEBUG(DBG_DATADUMP, "%s: device doesn't have link-local address!\n", dev->name); ++ continue; ++ } ++ } ++ ++ } ++ read_unlock(&dev_base_lock); ++ return RTR_SOLICITATION_INTERVAL; ++} ++ ++/* Create a new CoA for MN and also add a route to it if it is still tentative ++ to allow MN to get packets to the address immediately ++ */ ++static int form_coa(struct in6_addr *coa, struct in6_addr *pfix, ++ int plen, int ifindex) ++{ ++ struct net_device *dev; ++ struct inet6_dev *in6_dev; ++ int ret = 0; ++ ++ if ((dev = dev_get_by_index(ifindex)) == NULL) { ++ DEBUG(DBG_WARNING, "Device is not present"); ++ return -1; ++ } ++ if ((in6_dev = in6_dev_get(dev)) == NULL) { ++ DEBUG(DBG_WARNING, "inet6_dev is not present"); ++ dev_put(dev); ++ return -1; ++ } ++ coa->s6_addr32[0] = pfix->s6_addr32[0]; ++ coa->s6_addr32[1] = pfix->s6_addr32[1]; ++ ++ if (ipv6_generate_eui64(coa->s6_addr + 8, dev) && ++ ipv6_inherit_eui64(coa->s6_addr + 8, in6_dev)) { ++ in6_dev_put(in6_dev); ++ dev_put(dev); ++ return -1; ++ } ++ if (ipv6_chk_addr(coa, dev) == 0) { ++ DEBUG(DBG_WARNING, "care-of address still tentative"); ++ ret = 1; ++ } ++ DEBUG(DBG_INFO, "Formed new CoA: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(coa)); ++ ++ in6_dev_put(in6_dev); ++ dev_put(dev); ++ return ret; ++} ++ ++static inline int rtr_is_gw(struct router *rtr, struct rt6_info *rt) ++{ ++ return ((rt->rt6i_flags & RTF_GATEWAY) && ++ !ipv6_addr_cmp(&rt->rt6i_gateway, &rtr->ll_addr)); ++} ++ ++static inline int is_prefix_route(struct router *rtr, struct rt6_info *rt) ++{ ++ return (!(rt->rt6i_flags & RTF_GATEWAY) && ++ mipv6_prefix_compare(&rt->rt6i_dst.addr, &rtr->raddr, ++ rtr->pfix_len)); ++} ++ ++/* ++ * Function that determines whether given rt6_info should be destroyed ++ * (negative => destroy rt6_info, zero or positive => do nothing) ++ */ ++static int mn_route_cleaner(struct rt6_info *rt, void *arg) ++{ ++ int type; ++ ++ struct router *rtr = (struct router *)arg; ++ ++ int ret = -1; ++ ++ DEBUG_FUNC(); ++ ++ if (!rt || !rtr) { ++ DEBUG(DBG_ERROR, "mn_route_cleaner: rt or rtr NULL"); ++ return 0; ++ } ++ ++ /* Do not delete routes to local addresses or to multicast ++ * addresses, since we need them to get router advertisements ++ * etc. Multicast addresses are more tricky, but we don't ++ * delete them in any case. The routing mechanism is not optimal for ++ * multihoming. ++ * ++ * Also keep all new prefix routes, gateway routes through rtr and ++ * all remaining default routes (including those used for reverse ++ * tunneling) ++ */ ++ type = ipv6_addr_type(&rt->rt6i_dst.addr); ++ ++ if ((type & (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL)) || ++ rt->rt6i_dev == &loopback_dev || rtr_is_gw(rtr, rt) || ++ is_prefix_route(rtr, rt) || (rt->rt6i_flags & RTF_DEFAULT)) ++ ret = 0; ++ ++ /* delete all others */ ++ ++ if (rt->rt6i_dev != &loopback_dev) { ++ DEBUG(DEBUG_MDETECT, ++ "%s route:\n" ++ "dev: %s,\n" ++ "gw: %x:%x:%x:%x:%x:%x:%x:%x,\n" ++ "flags: %x,\n" ++ "metric: %d,\n" ++ "src: %x:%x:%x:%x:%x:%x:%x:%x,\n" ++ "dst: %x:%x:%x:%x:%x:%x:%x:%x,\n" ++ "plen: %d\n", ++ (ret ? "Deleting" : "Keeping"), ++ rt->rt6i_dev->name, ++ NIPV6ADDR(&rt->rt6i_gateway), ++ rt->rt6i_flags, ++ rt->rt6i_metric, ++ NIPV6ADDR(&rt->rt6i_src.addr), ++ NIPV6ADDR(&rt->rt6i_dst.addr), ++ rt->rt6i_dst.plen); ++ } ++ return ret; ++} ++ ++/* ++ * Deletes old routes ++ */ ++static __inline__ void delete_routes(struct router *rtr) ++{ ++ DEBUG_FUNC(); ++ ++ /* Routing table is locked to ensure that nobody uses its */ ++ write_lock_bh(&rt6_lock); ++ DEBUG(DBG_INFO, "mipv6: Purging routes"); ++ /* TODO: Does not prune, should it? */ ++ fib6_clean_tree(&ip6_routing_table, ++ mn_route_cleaner, 0, rtr); ++ write_unlock_bh(&rt6_lock); ++ ++} ++ ++ ++static __inline__ void delete_coas(struct router *rtr) ++{ ++ struct net_device *dev; ++ struct inet6_dev *idev; ++ struct inet6_ifaddr *ifa; ++ ++ dev = dev_get_by_index(rtr->ifindex); ++ if (!dev) ++ return; ++ ++ idev = in6_dev_get(dev); ++ ++ if (idev) { ++ read_lock_bh(&idev->lock); ++ ifa = idev->addr_list; ++ while (ifa) { ++ int keep; ++ spin_lock(&ifa->lock); ++ ++ keep = (ifa->flags&(IFA_F_PERMANENT|IFA_F_HOMEADDR) || ++ !ipv6_addr_cmp(&ifa->addr, &rtr->CoA)); ++ ++ spin_unlock(&ifa->lock); ++ ++ if (keep) ++ ifa = ifa->if_next; ++ else { ++ in6_ifa_hold(ifa); ++ read_unlock_bh(&idev->lock); ++ ++ ipv6_del_addr(ifa); ++ ++ read_lock_bh(&idev->lock); ++ ifa = idev->addr_list; ++ } ++ } ++ read_unlock_bh(&idev->lock); ++ in6_dev_put(idev); ++ } ++ dev_put(dev); ++} ++ ++int next_mdet_state[3][3] = {{CURR_RTR_OK, NO_RTR, NO_RTR}, ++ {CURR_RTR_OK, CURR_RTR_OK, NO_RTR}, ++ {CURR_RTR_OK, CURR_RTR_OK, RTR_SUSPECT}}; ++ ++char *states[3] = {"NO_RTR", "RTR_SUSPECT", "CURR_RTR_OK"}; ++char *events[3] = {"RA_RCVD", "NA_RCVD", "TIMEOUT"}; ++ ++/* State transitions ++ * NO_RTR, RA_RCVD -> CURR_RTR_OK ++ * NO_RTR, NA_RCVD -> NO_RTR ++ * NO_RTR, TIMEOUT -> NO_RTR ++ ++ * RTR_SUSPECT, RA_RCVD -> CURR_RTR_OK ++ * RTR_SUSPECT, NA_RCVD -> CURR_RTR_OK ++ * RTR_SUSPECT, TIMEOUT -> NO_RTR ++ ++ * CURR_RTR_OK, RA_RCVD -> CURR_RTR_OK ++ * CURR_RTR_OK, NA_RCVD -> CURR_RTR_OK ++ * CURR_RTR_OK, TIMEOUT -> RTR_SUSPECT ++ */ ++static int _curr_state = NO_RTR; ++ ++#if 0 ++static int get_mdet_state(void){ ++ int state; ++ spin_lock_bh(&router_lock); ++ state = _curr_state; ++ spin_unlock_bh(&router_lock); ++ return state; ++} ++#endif ++ ++/* Needs to be called with router_lock locked */ ++static int mdet_statemachine(int event) ++{ ++ ++ if (event > 2 || _curr_state > 2) { ++ DEBUG(DBG_ERROR, "Got illegal event or curr_state"); ++ return -1; ++ } ++ ++ DEBUG(DBG_DATADUMP, "Got event %s and curr_state is %s", ++ events[event], states[_curr_state]); ++ ++ _curr_state = next_mdet_state[_curr_state][event]; ++ DEBUG(DBG_DATADUMP, "Next state is %s", states[_curr_state]); ++ return _curr_state; ++} ++ ++static void mipv6_do_ll_dad(int ifindex) ++{ ++ struct net_device *dev = dev_get_by_index(ifindex); ++ if (dev) { ++ struct in6_addr lladdr; ++ struct inet6_ifaddr *ifa; ++ if (!ipv6_get_lladdr(dev, &lladdr) && ++ (ifa = ipv6_get_ifaddr(&lladdr, dev)) != NULL) { ++ spin_lock_bh(&ifa->lock); ++ if (!(ifa->flags & IFA_F_TENTATIVE)) { ++ ifa->flags |= IFA_F_TENTATIVE; ++ spin_unlock_bh(&ifa->lock); ++ addrconf_dad_start(ifa, 0); ++ } else ++ spin_unlock_bh(&ifa->lock); ++ ++ } ++ dev_put(dev); ++ } ++} ++/* ++ * Changes the router, called from ndisc.c if mipv6_router_event ++ * returns true. ++ */ ++ ++static void mipv6_change_router(void) ++{ ++ struct in6_addr coa; ++ int ret, ifindex; ++ ++ DEBUG_FUNC(); ++ ++ ++ if (next_router == NULL) ++ return; ++ ++ spin_lock(&router_lock); ++ ++ ++ if (curr_router != NULL && ++ !ipv6_addr_cmp(&curr_router->ll_addr, &next_router->ll_addr)) { ++ DEBUG(DBG_INFO,"Trying to handoff from: " ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&curr_router->ll_addr)); ++ DEBUG(DBG_INFO,"Trying to handoff to: " ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&next_router->ll_addr)); ++ next_router = NULL; /* Let's not leave dangling pointers */ ++ spin_unlock(&router_lock); ++ return; ++ } ++ ret = form_coa(&next_router->CoA, &next_router->raddr, ++ next_router->pfix_len, next_router->ifindex); ++ if (ret < 0) { ++ DEBUG(DBG_ERROR, "handoff: Creation of coa failed"); ++ spin_unlock(&router_lock); ++ return; ++ } else if (ret > 0) ++ next_router->flags |= COA_TENTATIVE; ++ ++ mdet_statemachine(RA_RCVD); /* TODO: What if DAD fails... */ ++ if (next_router->interval) ++ mod_timer(&r_timer, jiffies + ++ (next_router->interval * HZ)/1000); ++ else ++ mod_timer(&r_timer, jiffies + max_rtr_reach_time * HZ); ++ ++ ++ if (ret == 0) { ++ ipv6_addr_copy(&coa, &next_router->CoA); ++ ifindex = next_router->ifindex; ++ spin_unlock(&router_lock); ++ mipv6_mdet_finalize_ho(&coa, ifindex); ++ return; ++ } ++ spin_unlock(&router_lock); ++ ++} ++static unsigned long ns_send(void) ++{ ++ struct neighbour *neigh; ++ struct net_device *dev; ++ struct in6_addr *raddr; ++ ++ DEBUG(DBG_DATADUMP, "Sending Neighbour solicitation to default router to verify its reachability"); ++ if (!curr_router) ++ return HZ; ++ if ((dev = dev_get_by_index(curr_router->ifindex)) == NULL) ++ return HZ; ++ if ((neigh = ndisc_get_neigh(dev, &curr_router->ll_addr)) == NULL) { ++ dev_put(dev); ++ return HZ; ++ } ++ if (curr_router->glob_addr) ++ raddr = &curr_router->raddr; ++ else ++ raddr = &curr_router->ll_addr; ++ ++ curr_router->last_ns_sent = jiffies; ++ ndisc_send_ns(dev, neigh, raddr, raddr, NULL); ++ ++ neigh_release(neigh); ++ dev_put(dev); ++ return HZ/5; /* Wait 200ms for a reply */ ++} ++ ++static int na_rcvd(void) ++{ ++ int neigh_ok = 0; ++ struct neighbour *neigh; ++ struct net_device *dev; ++ ++ if (!curr_router) ++ return 0; ++ if ((dev = dev_get_by_index(curr_router->ifindex)) == NULL) ++ return 0; ++ if ((neigh = ndisc_get_neigh(dev, &curr_router->ll_addr)) == NULL) { ++ dev_put(dev); ++ return 0; ++ } ++ if (neigh->flags & NTF_ROUTER && ++ (time_after(neigh->confirmed, curr_router->last_ns_sent) || ++ neigh->confirmed == curr_router->last_ns_sent)) { ++ neigh_ok = 1; ++ DEBUG(DBG_DATADUMP, "Mdetect event: NA rcvd from curr rtr"); ++ } else ++ DEBUG(DBG_DATADUMP, "Mdetect event: NA NOT rcvd from curr rtr within time limit"); ++ neigh_release(neigh); ++ dev_put(dev); ++ return neigh_ok; ++} ++ ++static void coa_timer_handler(unsigned long dummy) ++{ ++ ++ spin_lock_bh(&ho_lock); ++ if (_ho) { ++ DEBUG(DBG_INFO, "Starting handoff after DAD"); ++ mipv6_mobile_node_moved(_ho); ++ kfree(_ho); ++ _ho = NULL; ++ } ++ spin_unlock_bh(&ho_lock); ++} ++static void timer_handler(unsigned long foo) ++{ ++ unsigned long timeout; ++ int state; ++ spin_lock_bh(&router_lock); ++ ++ if (_curr_state != NO_RTR) ++ rs_state = START; ++ ++ if (_curr_state == RTR_SUSPECT && na_rcvd()) { ++ state = mdet_statemachine(NA_RCVD); ++ timeout = curr_router->interval ? curr_router->interval : max_rtr_reach_time * HZ; ++ } else { ++ state = mdet_statemachine(TIMEOUT); ++ if (state == NO_RTR) ++ timeout = rs_send(); ++ else /* RTR_SUSPECT */ ++ timeout = ns_send(); ++ } ++ if (!timeout) ++ timeout = HZ; ++ ++ mipv6_router_gc(); ++ mod_timer(&r_timer, jiffies + timeout); ++ spin_unlock_bh(&router_lock); ++} ++ ++/** ++ * mipv6_get_care_of_address - get node's care-of primary address ++ * @homeaddr: one of node's home addresses ++ * @coaddr: buffer to store care-of address ++ * ++ * Stores the current care-of address in the @coaddr, assumes ++ * addresses in EUI-64 format. Since node might have several home ++ * addresses caller MUST supply @homeaddr. If node is at home ++ * @homeaddr is stored in @coaddr. Returns 0 on success, otherwise a ++ * negative value. ++ **/ ++int mipv6_get_care_of_address( ++ struct in6_addr *homeaddr, struct in6_addr *coaddr) ++{ ++ ++ DEBUG_FUNC(); ++ ++ if (homeaddr == NULL) ++ return -1; ++ spin_lock_bh(&router_lock); ++ if (curr_router == NULL || mipv6_mn_is_at_home(homeaddr) || ++ mipv6_prefix_compare(homeaddr, &curr_router->raddr, 64) || ++ curr_router->flags&COA_TENTATIVE) { ++ DEBUG(DBG_INFO, ++ "mipv6_get_care_of_address: returning home address"); ++ ipv6_addr_copy(coaddr, homeaddr); ++ spin_unlock_bh(&router_lock); ++ return 0; ++ ++ } ++ ++ /* At home or address check failure probably due to dad wait */ ++ if (mipv6_prefix_compare(&curr_router->raddr, homeaddr, ++ curr_router->pfix_len) ++ || (dad == RESPECT_DAD && ++ (ipv6_chk_addr(coaddr, NULL) == 0))) { ++ ipv6_addr_copy(coaddr, homeaddr); ++ } else { ++ ipv6_addr_copy(coaddr, &curr_router->CoA); ++ } ++ ++ spin_unlock_bh(&router_lock); ++ return 0; ++} ++ ++int mipv6_mdet_del_if(int ifindex) ++{ ++ struct router *curr = NULL; ++ struct list_head *lh, *lh_tmp; ++ ++ spin_lock_bh(&router_lock); ++ list_for_each_safe(lh, lh_tmp, &rtr_list) { ++ curr = list_entry(lh, struct router, list); ++ if (curr->ifindex == ifindex) { ++ num_routers--; ++ list_del_init(&curr->list); ++ DEBUG(DBG_DATADUMP, "Deleting router %x:%x:%x:%x:%x:%x:%x:%x on interface %d", ++ NIPV6ADDR(&curr->raddr), ifindex); ++ if (curr_router == curr) ++ curr_router = NULL; ++ kfree(curr); ++ } ++ } ++ spin_unlock_bh(&router_lock); ++ return 0; ++} ++ ++void mipv6_mdet_retrigger_ho(void) ++{ ++ struct handoff ho; ++ ++ spin_lock_bh(&router_lock); ++ if (curr_router != NULL) { ++ ho.coa = &curr_router->CoA; ++ ho.plen = curr_router->pfix_len; ++ ho.ifindex = curr_router->ifindex; ++ ipv6_addr_copy(&ho.rtr_addr, &curr_router->raddr); ++ ho.home_address = (curr_router->glob_addr && ++ curr_router->flags&ND_RA_FLAG_HA); ++ } ++ spin_unlock_bh(&router_lock); ++ mipv6_mobile_node_moved(&ho); ++} ++ ++void mipv6_mdet_set_curr_rtr_reachable(int reachable) ++{ ++ spin_lock_bh(&router_lock); ++ if (curr_router != NULL) { ++ curr_router->reachable = reachable; ++ } ++ spin_unlock_bh(&router_lock); ++ ++} ++ ++int mipv6_mdet_finalize_ho(const struct in6_addr *coa, const int ifindex) ++{ ++ int dummy; ++ struct handoff ho; ++ struct router *tmp; ++ struct net_device *dev; ++ struct in6_addr ll_addr; ++ ++ spin_lock_bh(&router_lock); ++ ++ if (!next_router) { ++ spin_unlock_bh(&router_lock); ++ return 0; ++ } ++ ++ dev = dev_get_by_index(next_router->ifindex); ++ ++ if (ipv6_get_lladdr(dev, &ll_addr) == 0) { ++ if (ipv6_addr_cmp(&ll_addr, coa) == 0) ++ DEBUG(DBG_INFO, "DAD for link local address completed"); ++ next_router->flags &= ~LLADDR_TENTATIVE; ++ } ++ ++ dev_put(dev); ++ ++ if (mipv6_prefix_compare(coa, &next_router->CoA, ++ next_router->pfix_len)) { ++ DEBUG(DBG_INFO, "DAD for Care-of address completed"); ++ next_router->flags &= ~COA_TENTATIVE; ++ } ++ if (!(next_router->flags&LLADDR_TENTATIVE) && !(next_router->flags&COA_TENTATIVE)) { ++ DEBUG(DBG_INFO, "%s: Proceeding with handoff after DAD\n", __FUNCTION__); ++ tmp = curr_router; ++ curr_router = next_router; ++ curr_router->is_current = 1; ++ next_router = NULL; ++ curr_router->flags &= ~COA_TENTATIVE; ++ delete_routes(curr_router); ++ delete_coas(curr_router); ++ if (tmp) { ++ struct net_device *dev_old = dev_get_by_index(tmp->ifindex); ++ struct rt6_info *rt = NULL; ++ if (dev_old) { ++ rt = rt6_get_dflt_router(&tmp->ll_addr, dev_old); ++ dev_put(dev_old); ++ } ++ if (rt) ++ ip6_del_rt(rt, NULL); ++ tmp->is_current = 0; ++ } ++ ++ ma_ctl_upd_iface(curr_router->ifindex, MA_IFACE_CURRENT, &dummy); ++ ma_ctl_upd_iface(curr_router->ifindex, MA_IFACE_CURRENT, &dummy); ++ ++ ++ ho.coa = &curr_router->CoA; ++ ho.plen = curr_router->pfix_len; ++ ho.ifindex = curr_router->ifindex; ++ ipv6_addr_copy(&ho.rtr_addr, &curr_router->raddr); ++ ho.home_address = (curr_router->glob_addr && ++ curr_router->flags&ND_RA_FLAG_HA); ++ ++ spin_unlock_bh(&router_lock); ++ mipv6_mobile_node_moved(&ho); ++ } else ++ spin_unlock_bh(&router_lock); ++ return 0; ++} ++/* Decides whether router candidate is the same router as current rtr ++ * based on prefix / global addresses of the routers and their link local ++ * addresses ++ */ ++static int is_current_rtr(struct router *nrt, struct router *crt) ++{ ++ DEBUG_FUNC(); ++ ++ DEBUG(DEBUG_MDETECT, "Current router: " ++ "%x:%x:%x:%x:%x:%x:%x:%x and", NIPV6ADDR(&crt->raddr)); ++ DEBUG(DEBUG_MDETECT, "Candidate router: " ++ "%x:%x:%x:%x:%x:%x:%x:%x", NIPV6ADDR(&nrt->raddr)); ++ ++ return (!ipv6_addr_cmp(&nrt->raddr,&crt->raddr) && ++ !ipv6_addr_cmp(&nrt->ll_addr, &crt->ll_addr)); ++} ++ ++/* ++ * Change next router to nrtr ++ * Returns 1, if router has been changed. ++ */ ++ ++static int change_next_rtr(struct router *nrtr, struct router *ortr) ++{ ++ int changed = 0; ++ DEBUG_FUNC(); ++ ++ if (!next_router || ipv6_addr_cmp(&nrtr->raddr, &next_router->raddr)) { ++ changed = 1; ++ } ++ next_router = nrtr; ++ return changed; ++} ++static int clean_ncache(struct router *nrt, struct router *ort, int same_if) ++{ ++ struct net_device *ortdev; ++ DEBUG_FUNC(); ++ ++ /* Always call ifdown after a handoff to ensure proper routing */ ++ ++ if (!ort) ++ return 0; ++ if ((ortdev = dev_get_by_index(ort->ifindex)) == NULL) { ++ DEBUG(DBG_WARNING, "Device is not present"); ++ return -1; ++ } ++ neigh_ifdown(&nd_tbl, ortdev); ++ dev_put(ortdev); ++ return 0; ++} ++ ++static int mdet_get_if_preference(int ifi) ++{ ++ int pref = 0; ++ ++ DEBUG_FUNC(); ++ ++ pref = ma_ctl_get_preference(ifi); ++ ++ DEBUG(DEBUG_MDETECT, "ifi: %d preference %d", ifi, pref); ++ ++ return pref; ++} ++ ++/* ++ * Called from mipv6_mn_ra_rcv to determine whether to do a handoff. ++ */ ++static int mipv6_router_event(struct router *rptr) ++{ ++ struct router *nrt = NULL; ++ int new_router = 0, same_if = 1; ++ int oldstate = _curr_state; ++ int addrtype = ipv6_addr_type(&rptr->raddr); ++ ++ DEBUG_FUNC(); ++ ++ if (rptr->lifetime == 0) ++ return MIPV6_IGN_RTR; ++ DEBUG(DEBUG_MDETECT, "Received a RA from router: " ++ "%x:%x:%x:%x:%x:%x:%x:%x", NIPV6ADDR(&rptr->raddr)); ++ spin_lock(&router_lock); ++ ++ /* Add or update router entry */ ++ if ((nrt = mipv6_rtr_get(&rptr->raddr)) == NULL) { ++ if (addrtype == IPV6_ADDR_ANY || (nrt = mipv6_rtr_add(rptr)) == NULL) { ++ spin_unlock(&router_lock); ++ return MIPV6_IGN_RTR; ++ } ++ DEBUG(DBG_INFO, "Router not on list,adding it to the list"); ++ new_router = 1; ++ } ++ nrt->last_ra_rcvd = jiffies; ++ nrt->state = ROUTER_REACHABLE; ++ nrt->interval = rptr->interval; ++ nrt->lifetime = rptr->lifetime; ++ nrt->ifindex = rptr->ifindex; ++ nrt->flags = rptr->flags; ++ nrt->glob_addr = rptr->glob_addr; ++ ++ /* Whether from current router */ ++ if (curr_router && curr_router->reachable && ++ is_current_rtr(nrt, curr_router)) { ++ if (nrt->interval) ++ mod_timer(&r_timer, jiffies + (nrt->interval * HZ)/1000); ++ else ++ mod_timer(&r_timer, jiffies + max_rtr_reach_time * HZ); ++ mdet_statemachine(RA_RCVD); ++ spin_unlock(&router_lock); ++ return MIPV6_ADD_RTR; ++ } else if (oldstate == NO_RTR) { ++ rt6_purge_dflt_routers(0); /* For multiple interface case */ ++ DEBUG(DBG_INFO, "No router or router not reachable, switching to new one"); ++ goto handoff; ++ } ++ if (!curr_router) { ++ /* Startup */ ++ goto handoff; ++ } ++ /* Router behind same interface as current one ?*/ ++ same_if = (nrt->ifindex == curr_router->ifindex); ++ /* Switch to new router behind same interface if eager cell ++ * switching is used or if the interface is preferred ++ */ ++ if ((new_router && eager_cell_switching && same_if) || ++ (mdet_get_if_preference(nrt->ifindex) > ++ mdet_get_if_preference(curr_router->ifindex))) { ++ DEBUG(DBG_INFO, "Switching to new router."); ++ goto handoff; ++ } ++ ++ /* No handoff, don't add default route */ ++ DEBUG(DEBUG_MDETECT, "Ignoring RA"); ++ spin_unlock(&router_lock); ++ return MIPV6_IGN_RTR; ++handoff: ++ clean_ncache(nrt, curr_router, same_if); ++ nrt->reachable = 1; ++ if (same_if && change_next_rtr(nrt, curr_router)) { ++ mipv6_do_ll_dad(nrt->ifindex); ++ nrt->flags |= LLADDR_TENTATIVE; ++ } ++ spin_unlock(&router_lock); ++ ++ return MIPV6_CHG_RTR; ++} ++ ++/* ++ * Called from ndisc.c's router_discovery. ++ */ ++ ++static inline int ret_to_ha(struct in6_addr *addr) ++{ ++ int res = 0; ++ struct mn_info *minfo; ++ read_lock(&mn_info_lock); ++ minfo = mipv6_mninfo_get_by_ha(addr); ++ if (minfo != NULL) { ++ spin_lock(&minfo->lock); ++ if (minfo->has_home_reg) { ++ res = 1; ++ } ++ spin_unlock(&minfo->lock); ++ } ++ read_unlock(&mn_info_lock); ++ return res; ++} ++ ++static int mipv6_mn_ra_rcv(struct sk_buff *skb, struct ndisc_options *ndopts) ++{ ++ int ifi = ((struct inet6_skb_parm *)skb->cb)->iif; ++ struct ra_msg *ra = (struct ra_msg *) skb->h.raw; ++ struct in6_addr *saddr = &skb->nh.ipv6h->saddr; ++ struct router nrt; ++ struct in6_addr *ha = NULL; ++ u8 *lladdr = NULL; ++ int res; ++ DEBUG_FUNC(); ++ ++ memset(&nrt, 0, sizeof(struct router)); ++ ++ if (ra->icmph.icmp6_home_agent) { ++ nrt.flags |= ND_RA_FLAG_HA; ++ DEBUG(DBG_DATADUMP, "RA has ND_RA_FLAG_HA up"); ++ } ++ ++ if (ra->icmph.icmp6_addrconf_managed) { ++ nrt.flags |= ND_RA_FLAG_MANAGED; ++ DEBUG(DBG_DATADUMP, "RA has ND_RA_FLAG_MANAGED up"); ++ } ++ ++ if (ra->icmph.icmp6_addrconf_other) { ++ nrt.flags |= ND_RA_FLAG_OTHER; ++ DEBUG(DBG_DATADUMP, "RA has ND_RA_FLAG_OTHER up"); ++ } ++ ++ ipv6_addr_copy(&nrt.ll_addr, saddr); ++ nrt.ifindex = ifi; ++ nrt.lifetime = ntohs(ra->icmph.icmp6_rt_lifetime); ++ ++ if (ndopts->nd_opts_src_lladdr) { ++ lladdr = (u8 *) ndopts->nd_opts_src_lladdr+2; ++ nrt.link_addr_len = skb->dev->addr_len; ++ memcpy(nrt.link_addr, lladdr, nrt.link_addr_len); ++ } ++ if (ndopts->nd_opts_pi) { ++ struct nd_opt_hdr *p; ++ for (p = ndopts->nd_opts_pi; ++ p; ++ p = ndisc_next_option(p, ndopts->nd_opts_pi_end)) { ++ struct prefix_info *pinfo; ++ int update = 0; ++ ++ pinfo = (struct prefix_info *) p; ++ ++ if (!pinfo->autoconf) ++ continue; ++ ++ if ((pinfo->router_address && ++ (update = ret_to_ha(&pinfo->prefix))) || ++ ipv6_addr_type(&nrt.raddr) != IPV6_ADDR_UNICAST) { ++ ipv6_addr_copy(&nrt.raddr, &pinfo->prefix); ++ nrt.pfix_len = pinfo->prefix_len; ++ if (pinfo->router_address) ++ nrt.glob_addr = 1; ++ else ++ nrt.glob_addr = 0; ++ if (update) ++ ha = &pinfo->prefix; ++ DEBUG(DBG_DATADUMP, "Address of the received " ++ "prefix info option: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&nrt.raddr)); ++ DEBUG(DBG_DATADUMP, "the length of the prefix is %d", ++ nrt.pfix_len); ++ } ++ } ++ } ++ if (ndopts->nd_opts_rai) { ++ nrt.interval = ntohl(*(__u32 *)(ndopts->nd_opts_rai+4)); ++ DEBUG(DBG_DATADUMP, ++ "received router interval option with interval : %d ", ++ nrt.interval / HZ); ++ ++ if (nrt.interval > MAX_RADV_INTERVAL) { ++ nrt.interval = 0; ++ DEBUG(DBG_DATADUMP, "but we are using: %d, " ++ "because interval>MAX_RADV_INTERVAL", ++ nrt.interval / HZ); ++ } ++ } ++ ++ res = mipv6_router_event(&nrt); ++ ++ if (ha && lladdr) { ++ mipv6_mn_ha_nd_update(__dev_get_by_index(ifi), ha, lladdr); ++ } ++ return res; ++} ++ ++int __init mipv6_initialize_mdetect(void) ++{ ++ ++ DEBUG_FUNC(); ++ ++ spin_lock_init(&router_lock); ++ spin_lock_init(&ho_lock); ++ init_timer(&coa_timer); ++ init_timer(&r_timer); ++ r_timer.expires = jiffies + HZ; ++ add_timer(&r_timer); ++ ++ /* Actual HO, also deletes old routes after the addition of new ones ++ in ndisc */ ++ MIPV6_SETCALL(mipv6_change_router, mipv6_change_router); ++ ++ MIPV6_SETCALL(mipv6_ra_rcv, mipv6_mn_ra_rcv); ++ ++ return 0; ++} ++ ++int __exit mipv6_shutdown_mdetect() ++{ ++ ++ DEBUG_FUNC(); ++ ++ MIPV6_RESETCALL(mipv6_ra_rcv); ++ MIPV6_RESETCALL(mipv6_change_router); ++ spin_lock_bh(&router_lock); ++ spin_lock(&ho_lock); ++ del_timer(&coa_timer); ++ del_timer(&r_timer); ++ /* Free the memory allocated by router list */ ++ list_free(&curr_router); ++ if (_ho) ++ kfree(_ho); ++ spin_unlock(&ho_lock); ++ spin_unlock_bh(&router_lock); ++ return 0; ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/mdetect.h +@@ -0,0 +1,37 @@ ++/* ++ * MIPL Mobile IPv6 Movement detection module header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _MDETECT_H ++#define _MDETECT_H ++ ++struct handoff { ++ int home_address; /* Is the coa a home address */ ++ int ifindex; ++ int plen; ++ struct in6_addr *coa; ++ struct in6_addr rtr_addr; /* Prefix or rtr address if coa is home address */ ++}; ++ ++int mipv6_initialize_mdetect(void); ++ ++int mipv6_shutdown_mdetect(void); ++ ++int mipv6_get_care_of_address(struct in6_addr *homeaddr, struct in6_addr *coa); ++ ++int mipv6_mdet_del_if(int ifindex); ++ ++int mipv6_mdet_finalize_ho(const struct in6_addr *coa, const int ifindex); ++ ++void mipv6_mdet_retrigger_ho(void); ++ ++void mipv6_mdet_set_curr_rtr_reachable(int reachable); ++ ++#endif /* _MDETECT_H */ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/mipv6_icmp.c +@@ -0,0 +1,342 @@ ++/** ++ * Generic icmp routines ++ * ++ * Authors: ++ * Jaakko Laine , ++ * Ville Nuorvala ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "debug.h" ++#include "bcache.h" ++#include "mipv6_icmp.h" ++#include "config.h" ++ ++struct mipv6_icmpv6_msg { ++ struct icmp6hdr icmph; ++ __u8 *data; ++ struct in6_addr *daddr; ++ int len; ++ __u32 csum; ++}; ++ ++#define MIPV6_ICMP_HOP_LIMIT 64 ++ ++static struct socket *mipv6_icmpv6_socket = NULL; ++static __u16 identifier = 0; ++ ++int mipv6_icmpv6_no_rcv(struct sk_buff *skb) ++{ ++ return 0; ++} ++ ++static int mipv6_icmpv6_xmit_holder = -1; ++ ++static int mipv6_icmpv6_xmit_lock_bh(void) ++{ ++ if (!spin_trylock(&mipv6_icmpv6_socket->sk->lock.slock)) { ++ if (mipv6_icmpv6_xmit_holder == smp_processor_id()) ++ return -EAGAIN; ++ spin_lock(&mipv6_icmpv6_socket->sk->lock.slock); ++ } ++ mipv6_icmpv6_xmit_holder = smp_processor_id(); ++ return 0; ++} ++ ++static __inline__ int mipv6_icmpv6_xmit_lock(void) ++{ ++ int ret; ++ local_bh_disable(); ++ ret = mipv6_icmpv6_xmit_lock_bh(); ++ if (ret) ++ local_bh_enable(); ++ return ret; ++} ++ ++static void mipv6_icmpv6_xmit_unlock_bh(void) ++{ ++ mipv6_icmpv6_xmit_holder = -1; ++ spin_unlock(&mipv6_icmpv6_socket->sk->lock.slock); ++} ++ ++static __inline__ void mipv6_icmpv6_xmit_unlock(void) ++{ ++ mipv6_icmpv6_xmit_unlock_bh(); ++ local_bh_enable(); ++} ++ ++ ++/** ++ * mipv6_icmpv6_dest_unreach - Destination Unreachable ICMP error message handler ++ * @skb: buffer containing ICMP error message ++ * ++ * Special Mobile IPv6 ICMP handling. If Correspondent Node receives ++ * persistent ICMP Destination Unreachable messages for a destination ++ * in its Binding Cache, the binding should be deleted. See draft ++ * section 8.8. ++ **/ ++static int mipv6_icmpv6_rcv_dest_unreach(struct sk_buff *skb) ++{ ++ struct icmp6hdr *icmph = (struct icmp6hdr *) skb->h.raw; ++ struct ipv6hdr *ipv6h = (struct ipv6hdr *) (icmph + 1); ++ int left = (skb->tail - skb->h.raw) - sizeof(*icmph)- sizeof(ipv6h); ++ struct ipv6_opt_hdr *eh; ++ struct rt2_hdr *rt2h = NULL; ++ struct in6_addr *daddr = &ipv6h->daddr; ++ struct in6_addr *saddr = &ipv6h->saddr; ++ int hdrlen, nexthdr = ipv6h->nexthdr; ++ struct mipv6_bce bce; ++ DEBUG_FUNC(); ++ ++ eh = (struct ipv6_opt_hdr *) (ipv6h + 1); ++ ++ while (left > 0) { ++ if (nexthdr != NEXTHDR_HOP && nexthdr != NEXTHDR_DEST && ++ nexthdr != NEXTHDR_ROUTING) ++ return 0; ++ ++ hdrlen = ipv6_optlen(eh); ++ if (hdrlen > left) ++ return 0; ++ ++ if (nexthdr == NEXTHDR_ROUTING) { ++ struct ipv6_rt_hdr *rth = (struct ipv6_rt_hdr *) eh; ++ ++ if (rth->type == IPV6_SRCRT_TYPE_2) { ++ if (hdrlen != sizeof(struct rt2_hdr)) ++ return 0; ++ ++ rt2h = (struct rt2_hdr *) rth; ++ ++ if (rt2h->rt_hdr.segments_left > 0) ++ daddr = &rt2h->addr; ++ break; ++ } ++ } ++ /* check for home address option in case this node is a MN */ ++ if (nexthdr == NEXTHDR_DEST) { ++ __u8 *raw = (__u8 *) eh; ++ __u16 i = 2; ++ while (1) { ++ struct mipv6_dstopt_homeaddr *hao; ++ ++ if (i + sizeof (*hao) > hdrlen) ++ break; ++ ++ hao = (struct mipv6_dstopt_homeaddr *) &raw[i]; ++ ++ if (hao->type == MIPV6_TLV_HOMEADDR && ++ hao->length == sizeof(struct in6_addr)) { ++ saddr = &hao->addr; ++ break; ++ } ++ if (hao->type) ++ i += hao->length + 2; ++ else ++ i++; ++ } ++ ++ } ++ nexthdr = eh->nexthdr; ++ eh = (struct ipv6_opt_hdr *) ((u8 *) eh + hdrlen); ++ left -= hdrlen; ++ } ++ if (rt2h == NULL) return 0; ++ ++ if (mipv6_bcache_get(daddr, saddr, &bce) == 0 && !(bce.flags&HOME_REGISTRATION)) { ++ /* A primitive algorithm for detecting persistent ICMP destination unreachable messages */ ++ if (bce.destunr_count && ++ time_after(jiffies, ++ bce.last_destunr + MIPV6_DEST_UNR_IVAL*HZ)) ++ bce.destunr_count = 0; ++ ++ bce.destunr_count++; ++ ++ mipv6_bcache_icmp_err(daddr, saddr, bce.destunr_count); ++ ++ if (bce.destunr_count > MIPV6_MAX_DESTUNREACH && mipv6_bcache_delete(daddr, saddr, CACHE_ENTRY) == 0) { ++ DEBUG(DBG_INFO, "Deleted bcache entry " ++ "%x:%x:%x:%x:%x:%x:%x:%x " ++ "%x:%x:%x:%x:%x:%x:%x:%x (reason: " ++ "%d dest unreachables) ", ++ NIPV6ADDR(daddr), NIPV6ADDR(saddr), bce.destunr_count); ++ } ++ } ++ return 0; ++} ++ ++static int mipv6_icmpv6_getfrag(const void *data, struct in6_addr *saddr, ++ char *buff, unsigned int offset, ++ unsigned int len) ++{ ++ struct mipv6_icmpv6_msg *msg = (struct mipv6_icmpv6_msg *) data; ++ struct icmp6hdr *icmph; ++ __u32 csum; ++ ++ if (offset) { ++ msg->csum = csum_partial_copy_nocheck(msg->data + offset - ++ sizeof(*icmph), buff, ++ len, msg->csum); ++ return 0; ++ } ++ ++ csum = csum_partial_copy_nocheck((__u8 *) &msg->icmph, buff, ++ sizeof(*icmph), msg->csum); ++ ++ csum = csum_partial_copy_nocheck(msg->data, buff + sizeof(*icmph), ++ len - sizeof(*icmph), csum); ++ ++ icmph = (struct icmp6hdr *) buff; ++ ++ icmph->icmp6_cksum = csum_ipv6_magic(saddr, msg->daddr, msg->len, ++ IPPROTO_ICMPV6, csum); ++ return 0; ++} ++ ++/** ++ * mipv6_icmpv6_send - generic icmpv6 message send ++ * @daddr: destination address ++ * @saddr: source address ++ * @type: icmp type ++ * @code: icmp code ++ * @id: packet identifier. If null, uses internal counter to get new id ++ * @data: packet data ++ * @datalen: length of data in bytes ++ */ ++void mipv6_icmpv6_send(struct in6_addr *daddr, struct in6_addr *saddr, int type, ++ int code, __u16 *id, __u16 flags, void *data, int datalen) ++{ ++ struct sock *sk = mipv6_icmpv6_socket->sk; ++ struct flowi fl; ++ struct mipv6_icmpv6_msg msg; ++ ++ DEBUG_FUNC(); ++ ++ fl.proto = IPPROTO_ICMPV6; ++ fl.fl6_dst = daddr; ++ fl.fl6_src = saddr; ++ fl.fl6_flowlabel = 0; ++ fl.uli_u.icmpt.type = type; ++ fl.uli_u.icmpt.code = code; ++ ++ msg.icmph.icmp6_type = type; ++ msg.icmph.icmp6_code = code; ++ msg.icmph.icmp6_cksum = 0; ++ ++ if (id) ++ msg.icmph.icmp6_identifier = htons(*id); ++ else ++ msg.icmph.icmp6_identifier = htons(identifier++); ++ ++ msg.icmph.icmp6_sequence = htons(flags); ++ msg.data = data; ++ msg.csum = 0; ++ msg.len = datalen + sizeof(struct icmp6hdr); ++ msg.daddr = daddr; ++ ++ if (mipv6_icmpv6_xmit_lock()) ++ return; ++ ++ ip6_build_xmit(sk, mipv6_icmpv6_getfrag, &msg, &fl, msg.len, NULL, -1, ++ MSG_DONTWAIT); ++ ++ ICMP6_INC_STATS_BH(Icmp6OutMsgs); ++ mipv6_icmpv6_xmit_unlock(); ++} ++ ++/** ++ * icmp6_rcv - ICMPv6 receive and multiplex ++ * @skb: buffer containing ICMP message ++ * ++ * Generic ICMPv6 receive function to multiplex messages to approriate ++ * handlers. Only used for ICMP messages with special handling in ++ * Mobile IPv6. ++ **/ ++static void icmp6_rcv(struct sk_buff *skb) ++{ ++ struct icmp6hdr *hdr; ++ ++ if (skb_is_nonlinear(skb) && ++ skb_linearize(skb, GFP_ATOMIC) != 0) { ++ kfree_skb(skb); ++ return; ++ } ++ __skb_push(skb, skb->data-skb->h.raw); ++ ++ hdr = (struct icmp6hdr *) skb->h.raw; ++ ++ switch (hdr->icmp6_type) { ++ case ICMPV6_DEST_UNREACH: ++ mipv6_icmpv6_rcv_dest_unreach(skb); ++ break; ++ ++ case ICMPV6_PARAMPROB: ++ mip6_fn.icmpv6_paramprob_rcv(skb); ++ break; ++ ++ case MIPV6_DHAAD_REPLY: ++ mip6_fn.icmpv6_dhaad_rep_rcv(skb); ++ break; ++ ++ case MIPV6_PREFIX_ADV: ++ mip6_fn.icmpv6_pfxadv_rcv(skb); ++ break; ++ ++ case MIPV6_DHAAD_REQUEST: ++ mip6_fn.icmpv6_dhaad_req_rcv(skb); ++ break; ++ ++ case MIPV6_PREFIX_SOLICIT: ++ mip6_fn.icmpv6_pfxsol_rcv(skb); ++ break; ++ } ++} ++ ++int mipv6_icmpv6_init(void) ++{ ++ struct sock *sk; ++ int err; ++ ++ if ((mipv6_icmpv6_socket = sock_alloc()) == NULL) { ++ DEBUG(DBG_ERROR, "Cannot allocate mipv6_icmpv6_socket"); ++ return -1; ++ } ++ mipv6_icmpv6_socket->type = SOCK_RAW; ++ ++ if ((err = sock_create(PF_INET6, SOCK_RAW, IPPROTO_ICMP, ++ &mipv6_icmpv6_socket)) < 0) { ++ DEBUG(DBG_ERROR, "Cannot initialize mipv6_icmpv6_socket"); ++ sock_release(mipv6_icmpv6_socket); ++ mipv6_icmpv6_socket = NULL; /* For safety */ ++ return err; ++ } ++ sk = mipv6_icmpv6_socket->sk; ++ sk->allocation = GFP_ATOMIC; ++ sk->prot->unhash(sk); ++ ++ /* Register our ICMP handler */ ++ MIPV6_SETCALL(mipv6_icmp_rcv, icmp6_rcv); ++ return 0; ++} ++ ++void mipv6_icmpv6_exit(void) ++{ ++ MIPV6_RESETCALL(mipv6_icmp_rcv); ++ if (mipv6_icmpv6_socket) ++ sock_release(mipv6_icmpv6_socket); ++ mipv6_icmpv6_socket = NULL; /* For safety */ ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/mipv6_icmp.h +@@ -0,0 +1,43 @@ ++/* ++ * MIPL Mobile IPv6 ICMP send and receive prototypes ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _MIPV6_ICMP ++#define _MIPV6_ICMP ++ ++#include ++#include ++ ++void mipv6_icmpv6_send(struct in6_addr *daddr, struct in6_addr *saddr, ++ int type, int code, __u16 *id, __u16 flags, ++ void *data, int datalen); ++ ++void mipv6_icmpv6_send_dhaad_req(struct in6_addr *home_addr, int plen, __u16 dhaad_id); ++ ++void mipv6_icmpv6_send_dhaad_rep(int ifindex, __u16 id, struct in6_addr *daddr); ++/* No handling */ ++int mipv6_icmpv6_no_rcv(struct sk_buff *skb); ++ ++/* Receive DHAAD Reply message */ ++int mipv6_icmpv6_rcv_dhaad_rep(struct sk_buff *skb); ++/* Receive Parameter Problem message */ ++int mipv6_icmpv6_rcv_paramprob(struct sk_buff *skb); ++/* Receive prefix advertisements */ ++int mipv6_icmpv6_rcv_pfx_adv(struct sk_buff *skb); ++ ++/* Receive DHAAD Request message */ ++int mipv6_icmpv6_rcv_dhaad_req(struct sk_buff *skb); ++/* Receive prefix solicitations */ ++int mipv6_icmpv6_rcv_pfx_sol(struct sk_buff *skb); ++ ++int mipv6_icmpv6_init(void); ++void mipv6_icmpv6_exit(void); ++ ++#endif +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/mipv6_icmp_ha.c +@@ -0,0 +1,158 @@ ++/* ++ * Home Agent specific ICMP routines ++ * ++ * Authors: ++ * Antti Tuominen ++ * Jaakko Laine ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "halist.h" ++#include "debug.h" ++#include "mipv6_icmp.h" ++//#include "prefix.h" ++ ++/* Is this the easiest way of checking on ++ * which interface an anycast address is ? ++ */ ++static int find_ac_dev(struct in6_addr *addr) ++{ ++ int ifindex = 0; ++ struct net_device *dev; ++ read_lock(&dev_base_lock); ++ for (dev=dev_base; dev; dev=dev->next) { ++ if (ipv6_chk_acast_addr(dev, addr)) { ++ ifindex = dev->ifindex; ++ break; ++ } ++ } ++ read_unlock(&dev_base_lock); ++ return ifindex; ++} ++ ++/** ++ * mipv6_icmpv6_send_dhaad_rep - Reply to DHAAD Request ++ * @ifindex: index of interface request was received from ++ * @id: request's identification number ++ * @daddr: requester's IPv6 address ++ * ++ * When Home Agent receives Dynamic Home Agent Address Discovery ++ * request, it replies with a list of home agents available on the ++ * home link. ++ */ ++void mipv6_icmpv6_send_dhaad_rep(int ifindex, __u16 id, struct in6_addr *daddr) ++{ ++ __u8 *data = NULL; ++ struct in6_addr home, *ha_addrs = NULL; ++ int addr_count, max_addrs, size = 0; ++ ++ if (daddr == NULL) ++ return; ++ ++ if (mipv6_ha_get_addr(ifindex, &home) < 0) { ++ DEBUG(DBG_INFO, "Not Home Agent in this interface"); ++ return; ++ } ++ ++ /* We send all available HA addresses, not exceeding a maximum ++ * number we can fit in a packet with minimum IPv6 MTU (to ++ * avoid fragmentation). ++ */ ++ max_addrs = 76; ++ addr_count = mipv6_ha_get_pref_list(ifindex, &ha_addrs, max_addrs); ++ ++ if (addr_count < 0) return; ++ ++ if (addr_count != 0 && ha_addrs == NULL) { ++ DEBUG(DBG_ERROR, "addr_count = %d but return no addresses", ++ addr_count); ++ return; ++ } ++ data = (u8 *)ha_addrs; ++ ++ size = addr_count * sizeof(struct in6_addr); ++ ++ mipv6_icmpv6_send(daddr, &home, MIPV6_DHAAD_REPLY, ++ 0, &id, 0, data, size); ++ if (ha_addrs) { ++ data = NULL; ++ kfree(ha_addrs); ++ } ++} ++ ++/** ++ * mipv6_icmpv6_dhaad_req - Home Agent Address Discovery Request ICMP handler ++ * @skb: buffer containing ICMP information message ++ * ++ * Special Mobile IPv6 ICMP message. Handles Dynamic Home Agent ++ * Address Discovery Request messages. ++ **/ ++int mipv6_icmpv6_rcv_dhaad_req(struct sk_buff *skb) ++{ ++ struct icmp6hdr *phdr = (struct icmp6hdr *) skb->h.raw; ++ struct in6_addr *saddr = &skb->nh.ipv6h->saddr; ++ struct in6_addr *daddr = &skb->nh.ipv6h->daddr; ++ __u16 identifier; ++ int ifindex = 0; ++ ++ DEBUG_FUNC(); ++ ++ /* Invalid packet checks. */ ++ if (phdr->icmp6_code != 0) ++ return 0; ++ ++ identifier = ntohs(phdr->icmp6_identifier); ++ ++ /* ++ * Make sure we have the right ifindex (if the ++ * req came through another interface. ++ */ ++ ifindex = find_ac_dev(daddr); ++ if (ifindex == 0) { ++ DEBUG(DBG_WARNING, "received dhaad request to anycast address %x:%x:%x:%x:%x:%x:%x:%x" ++ " on which prefix we are not HA", ++ NIPV6ADDR(daddr)); ++ return 0; ++ } ++ ++ /* ++ * send reply with list ++ */ ++ mipv6_icmpv6_send_dhaad_rep(ifindex, identifier, saddr); ++ return 1; ++} ++#if 0 ++/** ++ * mipv6_icmpv6_handle_pfx_sol - handle prefix solicitations ++ * @skb: sk_buff including the icmp6 message ++ */ ++int mipv6_icmpv6_rcv_pfx_sol(struct sk_buff *skb) ++{ ++ struct in6_addr *saddr = &skb->nh.ipv6h->saddr; ++ struct in6_addr *daddr = &skb->nh.ipv6h->daddr; ++ struct inet6_ifaddr *ifp; ++ ++ DEBUG_FUNC(); ++ ++ if (!(ifp = ipv6_get_ifaddr(daddr, NULL))) ++ return -1; ++ ++ in6_ifa_put(ifp); ++ mipv6_pfx_cancel_send(saddr, -1); ++ ++ return 0; ++} ++#endif +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/mipv6_icmp_mn.c +@@ -0,0 +1,273 @@ ++/* ++ * Mobile Node specific ICMP routines ++ * ++ * Authors: ++ * Antti Tuominen ++ * Jaakko Laine ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "mn.h" ++#include "bul.h" ++#include "mdetect.h" ++#include "debug.h" ++#include "mipv6_icmp.h" ++#include "util.h" ++//#include "prefix.h" ++ ++#define INFINITY 0xffffffff ++ ++/** ++ * mipv6_icmpv6_paramprob - Parameter Problem ICMP error message handler ++ * @skb: buffer containing ICMP error message ++ * ++ * Special Mobile IPv6 ICMP handling. If Mobile Node receives ICMP ++ * Parameter Problem message when using a Home Address Option, ++ * offending node should be logged and error message dropped. If ++ * error is received because of a Binding Update, offending node ++ * should be recorded in Binding Update List and no more Binding ++ * Updates should be sent to this destination. See RFC 3775 section ++ * 10.15. ++ **/ ++int mipv6_icmpv6_rcv_paramprob(struct sk_buff *skb) ++{ ++ struct icmp6hdr *phdr = (struct icmp6hdr *) skb->h.raw; ++ struct in6_addr *saddr = skb ? &skb->nh.ipv6h->saddr : NULL; ++ struct in6_addr *daddr = skb ? &skb->nh.ipv6h->daddr : NULL; ++ struct ipv6hdr *hdr = (struct ipv6hdr *) (phdr + 1); ++ int ulen = (skb->tail - (unsigned char *) (phdr + 1)); ++ ++ int errptr; ++ __u8 *off_octet; ++ ++ DEBUG_FUNC(); ++ ++ /* We only handle code 1 & 2 messages. */ ++ if (phdr->icmp6_code != ICMPV6_UNK_NEXTHDR && ++ phdr->icmp6_code != ICMPV6_UNK_OPTION) ++ return 0; ++ ++ /* Find offending octet in the original packet. */ ++ errptr = ntohl(phdr->icmp6_pointer); ++ ++ /* There is not enough of the original packet left to figure ++ * out what went wrong. Bail out. */ ++ if (ulen <= errptr) ++ return 0; ++ ++ off_octet = ((__u8 *) hdr + errptr); ++ DEBUG(DBG_INFO, "Parameter problem: offending octet %d [0x%2x]", ++ errptr, *off_octet); ++ ++ /* If CN did not understand Mobility Header, set BUL entry to ++ * ACK_ERROR so no further BUs are sumbitted to this CN. */ ++ if (phdr->icmp6_code == ICMPV6_UNK_NEXTHDR && ++ *off_octet == IPPROTO_MOBILITY) { ++ struct bul_inval_args args; ++ args.all_rr_states = 1; ++ args.cn = saddr; ++ args.mn = daddr; ++ write_lock(&bul_lock); ++ mipv6_bul_iterate(mn_bul_invalidate, &args); ++ write_unlock(&bul_lock); ++ } ++ ++ /* If CN did not understand Home Address Option, we log an ++ * error and discard the error message. */ ++ if (phdr->icmp6_code == ICMPV6_UNK_OPTION && ++ *off_octet == MIPV6_TLV_HOMEADDR) { ++ DEBUG(DBG_WARNING, "Correspondent node does not " ++ "implement Home Address Option receipt."); ++ return 1; ++ } ++ return 0; ++} ++ ++/** ++ * mipv6_mn_dhaad_send_req - Send DHAAD Request to home network ++ * @home_addr: address to do DHAAD for ++ * @plen: prefix length for @home_addr ++ * ++ * Send Dynamic Home Agent Address Discovery Request to the Home ++ * Agents anycast address in the nodes home network. ++ **/ ++void ++mipv6_icmpv6_send_dhaad_req(struct in6_addr *home_addr, int plen, __u16 dhaad_id) ++{ ++ struct in6_addr ha_anycast; ++ struct in6_addr careofaddr; ++ ++ if (mipv6_get_care_of_address(home_addr, &careofaddr) < 0) { ++ DEBUG(DBG_WARNING, "Could not get node's Care-of Address"); ++ return; ++ } ++ ++ if (mipv6_ha_anycast(&ha_anycast, home_addr, plen) < 0) { ++ DEBUG(DBG_WARNING, ++ "Could not get Home Agent Anycast address for home address %x:%x.%x:%x:%x:%x:%x:%x/%d", ++ NIPV6ADDR(home_addr), plen); ++ return; ++ } ++ ++ mipv6_icmpv6_send(&ha_anycast, &careofaddr, MIPV6_DHAAD_REQUEST, 0, ++ &dhaad_id, 0, NULL, 0); ++ ++} ++ ++/** ++ * mipv6_icmpv6_dhaad_rep - Home Agent Address Discovery Reply ICMP handler ++ * @skb: buffer containing ICMP information message ++ * ++ * Special Mobile IPv6 ICMP message. Handles Dynamic Home Agent ++ * Address Discovery Reply messages. ++ **/ ++int mipv6_icmpv6_rcv_dhaad_rep(struct sk_buff *skb) ++{ ++ struct icmp6hdr *phdr = (struct icmp6hdr *) skb->h.raw; ++ struct in6_addr *address; ++ struct in6_addr *saddr = &skb->nh.ipv6h->saddr; ++ __u16 identifier; ++ int ulen = (skb->tail - (unsigned char *) ((__u32 *) phdr + 2)); ++ int i; ++ struct in6_addr home_addr, coa; ++ struct in6_addr *first_ha = NULL; ++ struct mn_info *minfo; ++ int n_addr = ulen / sizeof(struct in6_addr); ++ ++ DEBUG_FUNC(); ++ ++ /* Invalid packet checks. */ ++ if (ulen % sizeof(struct in6_addr) != 0) ++ return 0; ++ ++ if (phdr->icmp6_code != 0) ++ return 0; ++ ++ identifier = ntohs(phdr->icmp6_identifier); ++ if (ulen > 0) { ++ address = (struct in6_addr *) ((__u32 *) phdr + 2); ++ } else { ++ address = saddr; ++ n_addr = 1; ++ } ++ ++ /* receive list of home agent addresses ++ * add to home agents list ++ */ ++ DEBUG(DBG_INFO, "DHAAD: got %d home agents", n_addr); ++ ++ first_ha = address; ++ ++ /* lookup H@ with identifier */ ++ read_lock(&mn_info_lock); ++ minfo = mipv6_mninfo_get_by_id(identifier); ++ if (!minfo) { ++ read_unlock(&mn_info_lock); ++ DEBUG(DBG_INFO, "no mninfo with id %d", ++ identifier); ++ return 0; ++ } ++ spin_lock(&minfo->lock); ++ ++ /* Logic: ++ * 1. if old HA on list, prefer it ++ * 2. otherwise first HA on list prefered ++ */ ++ for (i = 0; i < n_addr; i++) { ++ DEBUG(DBG_INFO, "HA[%d] %x:%x:%x:%x:%x:%x:%x:%x", ++ i, NIPV6ADDR(address)); ++ if (ipv6_addr_cmp(&minfo->ha, address) == 0) { ++ spin_unlock(&minfo->lock); ++ read_unlock(&mn_info_lock); ++ return 0; ++ } ++ address++; ++ } ++ ipv6_addr_copy(&minfo->ha, first_ha); ++ spin_unlock(&minfo->lock); ++ ipv6_addr_copy(&home_addr, &minfo->home_addr); ++ read_unlock(&mn_info_lock); ++ ++ mipv6_get_care_of_address(&home_addr, &coa); ++ init_home_registration(&home_addr, &coa); ++ ++ return 1; ++} ++#if 0 ++/** ++ * mipv6_icmpv6_handle_pfx_adv - handle prefix advertisements ++ * @skb: sk_buff including the icmp6 message ++ */ ++int mipv6_icmpv6_rcv_pfx_adv(struct sk_buff *skb) ++{ ++ struct icmp6hdr *hdr = (struct icmp6hdr *) skb->h.raw; ++ struct in6_addr *saddr = &skb->nh.ipv6h->saddr; ++ struct in6_addr *daddr = &skb->nh.ipv6h->daddr; ++ __u8 *opt = (__u8 *) (hdr + 1); ++ int optlen = (skb->tail - opt); ++ unsigned long min_expire = INFINITY; ++ struct inet6_skb_parm *parm = (struct inet6_skb_parm *) skb->cb; ++ ++ DEBUG_FUNC(); ++ ++ while (optlen > 0) { ++ int len = opt[1] << 3; ++ if (len == 0) ++ goto set_timer; ++ ++ if (opt[0] == ND_OPT_PREFIX_INFO) { ++ int ifindex; ++ unsigned long expire; ++ struct prefix_info *pinfo = ++ (struct prefix_info *) opt; ++ struct net_device *dev; ++ struct mn_info *mninfo; ++ ++ read_lock(&mn_info_lock); ++ mninfo = mipv6_mninfo_get_by_ha(saddr); ++ if (mninfo == NULL) { ++ ifindex = 0; ++ } else { ++ spin_lock(&mninfo->lock); ++ ifindex = mninfo->ifindex; ++ spin_unlock(&mninfo->lock); ++ mninfo = NULL; ++ } ++ read_unlock(&mn_info_lock); ++ ++ if (!(dev = dev_get_by_index(ifindex))) { ++ DEBUG(DBG_WARNING, "Cannot find device by index %d", parm->iif); ++ goto nextopt; ++ } ++ ++ expire = ntohl(pinfo->valid); ++ expire = expire == 0 ? INFINITY : expire; ++ ++ min_expire = expire < min_expire ? expire : min_expire; ++ ++ dev_put(dev); ++ } ++ ++nextopt: ++ optlen -= len; ++ opt += len; ++ } ++ ++set_timer: ++ ++ mipv6_pfx_add_home(parm->iif, saddr, daddr, min_expire); ++ return 0; ++} ++#endif +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/mn.c +@@ -0,0 +1,1521 @@ ++/* ++ * Mobile-node functionality ++ * ++ * Authors: ++ * Sami Kivisaari ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "util.h" ++#include "mdetect.h" ++#include "bul.h" ++#include "mobhdr.h" ++#include "debug.h" ++#include "mn.h" ++#include "mipv6_icmp.h" ++#include "multiaccess_ctl.h" ++//#include "prefix.h" ++#include "tunnel_mn.h" ++#include "stats.h" ++#include "config.h" ++ ++#define MIPV6_BUL_SIZE 128 ++ ++static LIST_HEAD(mn_info_list); ++ ++/* Lock for list of MN infos */ ++rwlock_t mn_info_lock = RW_LOCK_UNLOCKED; ++ ++static spinlock_t ifrh_lock = SPIN_LOCK_UNLOCKED; ++ ++struct ifr_holder { ++ struct list_head list; ++ struct in6_ifreq ifr; ++ int old_ifi; ++ struct handoff *ho; ++}; ++ ++LIST_HEAD(ifrh_list); ++ ++static struct tq_struct mv_home_addr_task; ++ ++/* Determines whether manually configured home addresses are preferred as ++ * source addresses over dynamically configured ones ++ */ ++int mipv6_use_preconfigured_hoaddr = 1; ++ ++/* Determines whether home addresses, which are at home are preferred as ++ * source addresses over other home addresses ++ */ ++int mipv6_use_topol_corr_hoaddr = 0; ++ ++static spinlock_t icmpv6_id_lock = SPIN_LOCK_UNLOCKED; ++static __u16 icmpv6_id = 0; ++ ++static inline __u16 mipv6_get_dhaad_id(void) ++{ ++ __u16 ret; ++ spin_lock_bh(&icmpv6_id_lock); ++ ret = ++icmpv6_id; ++ spin_unlock_bh(&icmpv6_id_lock); ++ return ret; ++} ++ ++/** ++ * mipv6_mninfo_get_by_home - Returns mn_info for a home address ++ * @haddr: home address of MN ++ * ++ * Returns mn_info on success %NULL otherwise. Caller MUST hold ++ * @mn_info_lock (read or write). ++ **/ ++struct mn_info *mipv6_mninfo_get_by_home(struct in6_addr *haddr) ++{ ++ struct list_head *lh; ++ struct mn_info *minfo; ++ ++ DEBUG_FUNC(); ++ ++ if (!haddr) ++ return NULL; ++ ++ list_for_each(lh, &mn_info_list) { ++ minfo = list_entry(lh, struct mn_info, list); ++ spin_lock(&minfo->lock); ++ if (!ipv6_addr_cmp(&minfo->home_addr, haddr)) { ++ spin_unlock(&minfo->lock); ++ return minfo; ++ } ++ spin_unlock(&minfo->lock); ++ } ++ return NULL; ++} ++ ++/** ++ * mipv6_mninfo_get_by_ha - Lookup mn_info with Home Agent address ++ * @home_agent: Home Agent address ++ * ++ * Searches for a mn_info entry with @ha set to @home_agent. You MUST ++ * hold @mn_info_lock when calling this function. Returns pointer to ++ * mn_info entry or %NULL on failure. ++ **/ ++struct mn_info *mipv6_mninfo_get_by_ha(struct in6_addr *home_agent) ++{ ++ struct list_head *lh; ++ struct mn_info *minfo; ++ ++ if (!home_agent) ++ return NULL; ++ ++ list_for_each(lh, &mn_info_list) { ++ minfo = list_entry(lh, struct mn_info, list); ++ spin_lock(&minfo->lock); ++ if (!ipv6_addr_cmp(&minfo->ha, home_agent)) { ++ spin_unlock(&minfo->lock); ++ return minfo; ++ } ++ spin_unlock(&minfo->lock); ++ } ++ return NULL; ++} ++ ++/** ++ * mipv6_mninfo_get_by_id - Lookup mn_info with id ++ * @id: DHAAD identifier ++ * ++ * Searches for a mn_info entry with @dhaad_id set to @id. You MUST ++ * hold @mn_info_lock when calling this function. Returns pointer to ++ * mn_info entry or %NULL on failure. ++ **/ ++struct mn_info *mipv6_mninfo_get_by_id(unsigned short id) ++{ ++ struct list_head *lh; ++ struct mn_info *minfo = 0; ++ ++ list_for_each(lh, &mn_info_list) { ++ minfo = list_entry(lh, struct mn_info, list); ++ spin_lock(&minfo->lock); ++ if (minfo->dhaad_id == id) { ++ spin_unlock(&minfo->lock); ++ return minfo; ++ } ++ spin_unlock(&minfo->lock); ++ } ++ return NULL; ++} ++ ++/** ++ * mipv6_mninfo_add - Adds a new home info for MN ++ * @ifindex: Interface for home address ++ * @home_addr: Home address of MN, must be set ++ * @plen: prefix length of the home address, must be set ++ * @isathome : home address at home ++ * @lifetime: lifetime of the home address, 0 is infinite ++ * @ha: home agent for the home address ++ * @ha_plen: prefix length of home agent's address, can be zero ++ * @ha_lifetime: Lifetime of the home address, 0 is infinite ++ * ++ * The function adds a new home info entry for MN, allowing it to ++ * register the home address with the home agent. Starts home ++ * registration process. If @ha is %ADDRANY, DHAAD is performed to ++ * find a home agent. Returns 0 on success, a negative value ++ * otherwise. Caller MUST NOT hold @mn_info_lock or ++ * @addrconf_hash_lock. ++ **/ ++void mipv6_mninfo_add(int ifindex, struct in6_addr *home_addr, int plen, ++ int isathome, unsigned long lifetime, struct in6_addr *ha, ++ int ha_plen, unsigned long ha_lifetime, int man_conf) ++{ ++ struct mn_info *minfo; ++ struct in6_addr coa; ++ ++ DEBUG_FUNC(); ++ ++ write_lock_bh(&mn_info_lock); ++ if ((minfo = mipv6_mninfo_get_by_home(home_addr)) != NULL){ ++ DEBUG(1, "MN info already exists"); ++ write_unlock_bh(&mn_info_lock); ++ return; ++ } ++ minfo = kmalloc(sizeof(struct mn_info), GFP_ATOMIC); ++ if (!minfo) { ++ write_unlock_bh(&mn_info_lock); ++ return; ++ } ++ memset(minfo, 0, sizeof(struct mn_info)); ++ spin_lock_init(&minfo->lock); ++ ++ ++ ipv6_addr_copy(&minfo->home_addr, home_addr); ++ ++ if (ha) ++ ipv6_addr_copy(&minfo->ha, ha); ++ if (ha_plen < 128 && ha_plen > 0) ++ minfo->home_plen = ha_plen; ++ else minfo->home_plen = 64; ++ ++ minfo->ifindex_user = ifindex; /* Ifindex for tunnel interface */ ++ minfo->ifindex = ifindex; /* Interface on which home address is currently conf'd */ ++ /* TODO: we should get home address lifetime from somewhere */ ++ /* minfo->home_addr_expires = jiffies + lifetime * HZ; */ ++ ++ /* manual configuration flag cannot be unset by dynamic updates ++ * from prefix advertisements ++ */ ++ if (!minfo->man_conf) minfo->man_conf = man_conf; ++ minfo->is_at_home = isathome; ++ ++ list_add(&minfo->list, &mn_info_list); ++ write_unlock_bh(&mn_info_lock); ++ ++ if (mipv6_get_care_of_address(home_addr, &coa) == 0) ++ init_home_registration(home_addr, &coa); ++} ++ ++/** ++ * mipv6_mninfo_del - Delete home info for MN ++ * @home_addr : Home address or prefix ++ * @del_dyn_only : Delete only dynamically created home entries ++ * ++ * Deletes every mn_info entry that matches the first plen bits of ++ * @home_addr. Returns number of deleted entries on success and a ++ * negative value otherwise. Caller MUST NOT hold @mn_info_lock. ++ **/ ++int mipv6_mninfo_del(struct in6_addr *home_addr, int del_dyn_only) ++{ ++ struct list_head *lh, *next; ++ struct mn_info *minfo; ++ int ret = -1; ++ if (!home_addr) ++ return -1; ++ ++ write_lock(&mn_info_lock); ++ ++ list_for_each_safe(lh, next, &mn_info_list) { ++ minfo = list_entry(lh, struct mn_info, list); ++ if (ipv6_addr_cmp(&minfo->home_addr, home_addr) == 0 ++ && ((!minfo->man_conf && del_dyn_only) || !del_dyn_only)){ ++ list_del(&minfo->list); ++ kfree(minfo); ++ ret++; ++ } ++ } ++ write_unlock(&mn_info_lock); ++ return ret; ++} ++ ++void mipv6_mn_set_home(int ifindex, struct in6_addr *homeaddr, int plen, ++ struct in6_addr *homeagent, int ha_plen) ++{ ++ mipv6_mninfo_add(ifindex, homeaddr, plen, 0, 0, ++ homeagent, ha_plen, 0, 1); ++} ++ ++static int skip_dad(struct in6_addr *addr) ++{ ++ struct mn_info *minfo; ++ int ret = 0; ++ ++ if (addr == NULL) { ++ DEBUG(DBG_CRITICAL, "Null argument"); ++ return 0; ++ } ++ read_lock_bh(&mn_info_lock); ++ if ((minfo = mipv6_mninfo_get_by_home(addr)) != NULL) { ++ if ((minfo->is_at_home != MN_NOT_AT_HOME) && (minfo->has_home_reg)) ++ ret = 1; ++ DEBUG(DBG_INFO, "minfo->is_at_home = %d, minfo->has_home_reg = %d", ++ minfo->is_at_home, minfo->has_home_reg); ++ } ++ read_unlock_bh(&mn_info_lock); ++ ++ return ret; ++} ++/** ++ * mipv6_mn_is_home_addr - Determines if addr is node's home address ++ * @addr: IPv6 address ++ * ++ * Returns 1 if addr is node's home address. Otherwise returns zero. ++ **/ ++int mipv6_mn_is_home_addr(struct in6_addr *addr) ++{ ++ int ret = 0; ++ ++ if (addr == NULL) { ++ DEBUG(DBG_CRITICAL, "Null argument"); ++ return -1; ++ } ++ read_lock_bh(&mn_info_lock); ++ if (mipv6_mninfo_get_by_home(addr)) ++ ret = 1; ++ read_unlock_bh(&mn_info_lock); ++ ++ return (ret); ++} ++ ++/** ++ * mipv6_mn_is_at_home - determine if node is home for a home address ++ * @home_addr : home address of MN ++ * ++ * Returns 1 if home address in question is in the home network, 0 ++ * otherwise. Caller MUST NOT not hold @mn_info_lock. ++ **/ ++int mipv6_mn_is_at_home(struct in6_addr *home_addr) ++{ ++ struct mn_info *minfo; ++ int ret = 0; ++ read_lock_bh(&mn_info_lock); ++ if ((minfo = mipv6_mninfo_get_by_home(home_addr)) != NULL) { ++ spin_lock(&minfo->lock); ++ ret = (minfo->is_at_home == MN_AT_HOME); ++ spin_unlock(&minfo->lock); ++ } ++ read_unlock_bh(&mn_info_lock); ++ return ret; ++} ++void mipv6_mn_set_home_reg(struct in6_addr *home_addr, int has_home_reg) ++{ ++ struct mn_info *minfo; ++ read_lock_bh(&mn_info_lock); ++ ++ if ((minfo = mipv6_mninfo_get_by_home(home_addr)) != NULL) { ++ spin_lock(&minfo->lock); ++ minfo->has_home_reg = has_home_reg; ++ spin_unlock(&minfo->lock); ++ } ++ read_unlock_bh(&mn_info_lock); ++} ++ ++static int mn_inet6addr_event( ++ struct notifier_block *nb, unsigned long event, void *ptr) ++{ ++ struct inet6_ifaddr *ifp = (struct inet6_ifaddr *)ptr; ++ ++ switch (event) { ++ case NETDEV_UP: ++ /* Is address a valid coa ?*/ ++ if (!(ifp->flags & IFA_F_TENTATIVE)) ++ mipv6_mdet_finalize_ho(&ifp->addr, ++ ifp->idev->dev->ifindex); ++ else if(skip_dad(&ifp->addr)) ++ ifp->flags &= ~IFA_F_TENTATIVE; ++ break; ++ case NETDEV_DOWN: ++#if 0 ++ /* This is useless with manually configured home ++ addresses, which will not expire ++ */ ++ mipv6_mninfo_del(&ifp->addr, 0); ++#endif ++ break; ++ ++ } ++ ++ return NOTIFY_DONE; ++} ++ ++struct notifier_block mipv6_mn_inet6addr_notifier = { ++ mn_inet6addr_event, ++ NULL, ++ 0 /* check if using zero is ok */ ++}; ++ ++static void mipv6_get_saddr_hook(struct in6_addr *homeaddr) ++{ ++ int found = 0, reiter = 0; ++ struct list_head *lh; ++ struct mn_info *minfo = NULL; ++ struct in6_addr coa; ++ ++ read_lock_bh(&mn_info_lock); ++restart: ++ list_for_each(lh, &mn_info_list) { ++ minfo = list_entry(lh, struct mn_info, list); ++ if ((ipv6_addr_scope(homeaddr) != ipv6_addr_scope(&minfo->home_addr)) ++ || ipv6_chk_addr(&minfo->home_addr, NULL) == 0) ++ continue; ++ ++ spin_lock(&minfo->lock); ++ if (minfo->is_at_home == MN_AT_HOME || minfo->has_home_reg) { ++ if ((mipv6_use_topol_corr_hoaddr && ++ minfo->is_at_home == MN_AT_HOME) || ++ (mipv6_use_preconfigured_hoaddr && ++ minfo->man_conf) || ++ (!(mipv6_use_preconfigured_hoaddr || ++ mipv6_use_topol_corr_hoaddr) || reiter)) { ++ spin_unlock(&minfo->lock); ++ ipv6_addr_copy(homeaddr, &minfo->home_addr); ++ found = 1; ++ break; ++ } ++ } ++ spin_unlock(&minfo->lock); ++ } ++ if (!found && !reiter) { ++ reiter = 1; ++ goto restart; ++ } ++ ++ if (!found && minfo && ++ !mipv6_get_care_of_address(&minfo->home_addr, &coa)) { ++ ipv6_addr_copy(homeaddr, &coa); ++ } ++ read_unlock_bh(&mn_info_lock); ++ ++ DEBUG(DBG_DATADUMP, "Source address selection: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(homeaddr)); ++ return; ++} ++ ++static void mv_home_addr(void *arg) ++{ ++ mm_segment_t oldfs; ++ int err = 0, new_if = 0; ++ struct list_head *lh, *next; ++ struct ifr_holder *ifrh; ++ LIST_HEAD(list); ++ ++ DEBUG(DBG_INFO, "mipv6 move home address task"); ++ ++ spin_lock_bh(&ifrh_lock); ++ list_splice_init(&ifrh_list, &list); ++ spin_unlock_bh(&ifrh_lock); ++ ++ oldfs = get_fs(); set_fs(KERNEL_DS); ++ list_for_each_safe(lh, next, &list) { ++ ifrh = list_entry(lh, struct ifr_holder, list); ++ if (ifrh->old_ifi) { ++ new_if = ifrh->ifr.ifr6_ifindex; ++ ifrh->ifr.ifr6_ifindex = ifrh->old_ifi; ++ err = addrconf_del_ifaddr(&ifrh->ifr); ++ ifrh->ifr.ifr6_ifindex = new_if; ++ if (err < 0) ++ DEBUG(DBG_WARNING, "removal of home address %x:%x:%x:%x:%x:%x:%x:%x from" ++ " old interface %d failed with status %d", ++ NIPV6ADDR(&ifrh->ifr.ifr6_addr), ifrh->old_ifi, err); ++ } ++ if(!err) { ++ err = addrconf_add_ifaddr(&ifrh->ifr); ++ } ++ if (ifrh->ho) { ++ DEBUG(DBG_INFO, "Calling mobile_node moved after moving home address to new if"); ++ mipv6_mobile_node_moved(ifrh->ho); ++ } ++ list_del(&ifrh->list); ++ kfree(ifrh); ++ } ++ set_fs(oldfs); ++ ++ if (err < 0) ++ DEBUG(DBG_WARNING, "adding of home address to a new interface %d failed %d", new_if, err); ++ else { ++ DEBUG(DBG_WARNING, "adding of home address to a new interface OK"); ++ } ++} ++ ++struct dhaad_halist { ++ struct list_head list; ++ struct in6_addr addr; ++ int retry; ++}; ++ ++/* clear all has from candidate list. do this when a new dhaad reply ++ * is received. */ ++int mipv6_mn_flush_ha_candidate(struct list_head *ha) ++{ ++ struct list_head *p, *tmp; ++ struct dhaad_halist *e; ++ ++ list_for_each_safe(p, tmp, ha) { ++ e = list_entry(p, struct dhaad_halist, list); ++ list_del(p); ++ kfree(e); ++ e = NULL; ++ } ++ return 0; ++} ++ ++/* add new ha to candidates. only done when dhaad reply is received. */ ++int mipv6_mn_add_ha_candidate(struct list_head *ha, struct in6_addr *addr) ++{ ++ struct dhaad_halist *e; ++ ++ e = kmalloc(sizeof(*e), GFP_ATOMIC); ++ memset(e, 0, sizeof(*e)); ++ ipv6_addr_copy(&e->addr, addr); ++ ++ list_add_tail(&e->list, ha); ++ return 0; ++} ++ ++#define MAX_RETRIES_PER_HA 3 ++ ++/* get next ha candidate. this is done when dhaad reply has been ++ * received and we want to register with the best available ha. */ ++int mipv6_mn_get_ha_candidate(struct list_head *ha, struct in6_addr *addr) ++{ ++ struct list_head *p; ++ ++ list_for_each(p, ha) { ++ struct dhaad_halist *e; ++ e = list_entry(p, typeof(*e), list); ++ if (e->retry >= 0 && e->retry < MAX_RETRIES_PER_HA) { ++ ipv6_addr_copy(addr, &e->addr); ++ return 0; ++ } ++ } ++ return -1; ++} ++ ++/* change candidate status. if registration with ha fails, we ++ * increase retry for ha candidate. if retry is >= 3 we set it to -1 ++ * (failed), do get_ha_candidate() again */ ++int mipv6_mn_try_ha_candidate(struct list_head *ha, struct in6_addr *addr) ++{ ++ struct list_head *p; ++ ++ list_for_each(p, ha) { ++ struct dhaad_halist *e; ++ e = list_entry(p, typeof(*e), list); ++ if (ipv6_addr_cmp(addr, &e->addr) == 0) { ++ if (e->retry >= MAX_RETRIES_PER_HA) e->retry = -1; ++ else if (e->retry >= 0) e->retry++; ++ return 0; ++ } ++ } ++ return -1; ++} ++ ++/** ++ * mipv6_mn_get_bulifetime - Get lifetime for a binding update ++ * @home_addr: home address for BU ++ * @coa: care-of address for BU ++ * @flags: flags used for BU ++ * ++ * Returns maximum lifetime for BUs determined by the lifetime of ++ * care-of address and the lifetime of home address. ++ **/ ++__u32 mipv6_mn_get_bulifetime(struct in6_addr *home_addr, struct in6_addr *coa, ++ __u8 flags) ++{ ++ struct inet6_ifaddr *ifp_hoa, *ifp_coa; ++ __u32 lifetime = (flags & MIPV6_BU_F_HOME ? ++ HA_BU_DEF_LIFETIME : CN_BU_DEF_LIFETIME); ++ ++ ifp_hoa = ipv6_get_ifaddr(home_addr, NULL); ++ if(!ifp_hoa) { ++ DEBUG(DBG_INFO, "home address missing"); ++ return 0; ++ } ++ if (!(ifp_hoa->flags & IFA_F_PERMANENT)){ ++ if (ifp_hoa->valid_lft) ++ lifetime = min_t(__u32, lifetime, ifp_hoa->valid_lft); ++ else ++ DEBUG(DBG_ERROR, "Zero lifetime for home address"); ++ } ++ in6_ifa_put(ifp_hoa); ++ ++ ifp_coa = ipv6_get_ifaddr(coa, NULL); ++ if (!ifp_coa) { ++ DEBUG(DBG_INFO, "care-of address missing"); ++ return 0; ++ } ++ if (!(ifp_coa->flags & IFA_F_PERMANENT)) { ++ if(ifp_coa->valid_lft) ++ lifetime = min_t(__u32, lifetime, ifp_coa->valid_lft); ++ else ++ DEBUG(DBG_ERROR, ++ "Zero lifetime for care-of address"); ++ } ++ in6_ifa_put(ifp_coa); ++ ++ DEBUG(DBG_INFO, "Lifetime for binding is %ld", lifetime); ++ return lifetime; ++} ++ ++static int ++mipv6_mn_tnl_rcv_send_bu_hook(struct ip6_tnl *t, struct sk_buff *skb) ++{ ++ struct ipv6hdr *inner; ++ struct ipv6hdr *outer = skb->nh.ipv6h; ++ struct mn_info *minfo = NULL; ++ __u32 lifetime; ++ __u8 user_flags = 0; ++ ++ DEBUG_FUNC(); ++ ++ if (!is_mip6_tnl(t)) ++ return IP6_TNL_ACCEPT; ++ ++ if (!mip6node_cnf.accept_ret_rout) { ++ DEBUG(DBG_INFO, "Return routability administratively disabled" ++ " not doing route optimization"); ++ return IP6_TNL_ACCEPT; ++ } ++ if (!pskb_may_pull(skb, skb->h.raw-skb->data+sizeof(*inner))) ++ return IP6_TNL_DROP; ++ ++ inner = (struct ipv6hdr *)skb->h.raw; ++ ++ read_lock(&mn_info_lock); ++ minfo = mipv6_mninfo_get_by_home(&inner->daddr); ++ ++ if (!minfo) { ++ DEBUG(DBG_WARNING, "MN info missing"); ++ read_unlock(&mn_info_lock); ++ return IP6_TNL_ACCEPT; ++ } ++ DEBUG(DBG_DATADUMP, "MIPV6 MN: Received a tunneled IPv6 packet" ++ " to %x:%x:%x:%x:%x:%x:%x:%x," ++ " from %x:%x:%x:%x:%x:%x:%x:%x with\n tunnel header" ++ "daddr: %x:%x:%x:%x:%x:%x:%x:%x," ++ "saddr: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&inner->daddr), NIPV6ADDR(&inner->saddr), ++ NIPV6ADDR(&outer->daddr), NIPV6ADDR(&outer->saddr)); ++ ++ spin_lock(&minfo->lock); ++ ++ /* We don't send bus in response to all tunneled packets */ ++ ++ if (!ipv6_addr_cmp(&minfo->ha, &inner->saddr)) { ++ spin_unlock(&minfo->lock); ++ read_unlock(&mn_info_lock); ++ DEBUG(DBG_ERROR, "HA BUG: Received a tunneled packet " ++ "originally sent by home agent, not sending BU"); ++ return IP6_TNL_ACCEPT; ++ } ++ spin_unlock(&minfo->lock); ++ read_unlock(&mn_info_lock); ++ ++ DEBUG(DBG_DATADUMP, "Sending BU to correspondent node"); ++ ++ user_flags |= mip6node_cnf.bu_cn_ack ? MIPV6_BU_F_ACK : 0; ++ ++ if (inner->nexthdr != IPPROTO_DSTOPTS && ++ inner->nexthdr != IPPROTO_MOBILITY) { ++ struct in6_addr coa; ++ /* Don't start RR when receiving ICMP error messages */ ++ if (inner->nexthdr == IPPROTO_ICMPV6) { ++ int ptr = (u8*)(inner+1) - skb->data; ++ u8 type; ++ ++ if (skb_copy_bits(skb, ++ ptr+offsetof(struct icmp6hdr, ++ icmp6_type), ++ &type, 1) ++ || !(type & ICMPV6_INFOMSG_MASK)) { ++ return IP6_TNL_ACCEPT; ++ } ++ } ++ lifetime = mipv6_mn_get_bulifetime(&inner->daddr, ++ &outer->daddr, 0); ++ if (lifetime && ++ !mipv6_get_care_of_address(&inner->daddr, &coa)) { ++ write_lock(&bul_lock); ++ mipv6_send_bu(&inner->daddr, &inner->saddr, &coa, ++ INITIAL_BINDACK_TIMEOUT, ++ MAX_BINDACK_TIMEOUT, 1, ++ user_flags, ++ lifetime, NULL); ++ write_unlock(&bul_lock); ++ } ++ } ++ DEBUG(DBG_DATADUMP, "setting rcv_tunnel flag in skb"); ++ skb->security |= MIPV6_RCV_TUNNEL; ++ return IP6_TNL_ACCEPT; ++} ++ ++static struct ip6_tnl_hook_ops mipv6_mn_tnl_rcv_send_bu_ops = { ++ {NULL, NULL}, ++ IP6_TNL_PRE_DECAP, ++ IP6_TNL_PRI_FIRST, ++ mipv6_mn_tnl_rcv_send_bu_hook ++}; ++ ++static int ++mipv6_mn_tnl_xmit_stats_hook(struct ip6_tnl *t, struct sk_buff *skb) ++{ ++ DEBUG_FUNC(); ++ if (is_mip6_tnl(t)) ++ MIPV6_INC_STATS(n_encapsulations); ++ return IP6_TNL_ACCEPT; ++} ++ ++static struct ip6_tnl_hook_ops mipv6_mn_tnl_xmit_stats_ops = { ++ {NULL, NULL}, ++ IP6_TNL_PRE_ENCAP, ++ IP6_TNL_PRI_LAST, ++ mipv6_mn_tnl_xmit_stats_hook ++}; ++ ++static int ++mipv6_mn_tnl_rcv_stats_hook(struct ip6_tnl *t, struct sk_buff *skb) ++{ ++ DEBUG_FUNC(); ++ if (is_mip6_tnl(t)) ++ MIPV6_INC_STATS(n_decapsulations); ++ return IP6_TNL_ACCEPT; ++} ++ ++static struct ip6_tnl_hook_ops mipv6_mn_tnl_rcv_stats_ops = { ++ {NULL, NULL}, ++ IP6_TNL_PRE_DECAP, ++ IP6_TNL_PRI_LAST, ++ mipv6_mn_tnl_rcv_stats_hook ++}; ++ ++static void mn_check_tunneled_packet(struct sk_buff *skb) ++{ ++ DEBUG_FUNC(); ++ /* If tunnel flag was set */ ++ if (skb->security & MIPV6_RCV_TUNNEL) { ++ struct in6_addr coa; ++ __u32 lifetime; ++ __u8 user_flags = 0; ++ int ptr = (u8*)(skb->nh.ipv6h+1) - skb->data; ++ int len = skb->len - ptr; ++ __u8 nexthdr = skb->nh.ipv6h->nexthdr; ++ ++ if (len < 0) ++ return; ++ ++ ptr = ipv6_skip_exthdr(skb, ptr, &nexthdr, len); ++ if (ptr < 0) ++ return; ++ ++ if (!mip6node_cnf.accept_ret_rout) { ++ DEBUG(DBG_INFO, "Return routability administratively disabled"); ++ return; ++ } ++ if (nexthdr == IPPROTO_MOBILITY) ++ return; ++ ++ /* Don't start RR when receiving ICMP error messages */ ++ if (nexthdr == IPPROTO_ICMPV6) { ++ u8 type; ++ ++ if (skb_copy_bits(skb, ++ ptr+offsetof(struct icmp6hdr, ++ icmp6_type), ++ &type, 1) ++ || !(type & ICMPV6_INFOMSG_MASK)) { ++ return; ++ } ++ } ++ user_flags |= mip6node_cnf.bu_cn_ack ? MIPV6_BU_F_ACK : 0; ++ mipv6_get_care_of_address(&skb->nh.ipv6h->daddr, &coa); ++ lifetime = mipv6_mn_get_bulifetime(&skb->nh.ipv6h->daddr, ++ &coa, 0); ++ ++ DEBUG(DBG_WARNING, "packet to address %x:%x:%x:%x:%x:%x:%x:%x" ++ "was tunneled. Sending BU to CN" ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&skb->nh.ipv6h->daddr), ++ NIPV6ADDR(&skb->nh.ipv6h->saddr)); ++ /* This should work also with home address option */ ++ ++ write_lock(&bul_lock); ++ mipv6_send_bu(&skb->nh.ipv6h->daddr, &skb->nh.ipv6h->saddr, ++ &coa, INITIAL_BINDACK_TIMEOUT, ++ MAX_BINDACK_TIMEOUT, 1, user_flags, ++ lifetime, NULL); ++ write_unlock(&bul_lock); ++ } ++} ++ ++static int sched_mv_home_addr_task(struct in6_addr *haddr, int plen_new, ++ int newif, int oldif, struct handoff *ho) ++{ ++ int alloc_size; ++ struct ifr_holder *ifrh; ++ ++ alloc_size = sizeof(*ifrh) + (ho ? sizeof(*ho): 0); ++ if ((ifrh = kmalloc(alloc_size, GFP_ATOMIC)) == NULL) { ++ DEBUG(DBG_ERROR, "Out of memory"); ++ return -1; ++ } ++ if (ho) { ++ ifrh->ho = (struct handoff *)((struct ifr_holder *)(ifrh + 1)); ++ memcpy(ifrh->ho, ho, sizeof(*ho)); ++ } else ++ ifrh->ho = NULL; ++ ++ /* must queue task to avoid deadlock with rtnl */ ++ ifrh->ifr.ifr6_ifindex = newif; ++ ifrh->ifr.ifr6_prefixlen = plen_new; ++ ipv6_addr_copy(&ifrh->ifr.ifr6_addr, haddr); ++ ifrh->old_ifi = oldif; ++ ++ spin_lock_bh(&ifrh_lock); ++ list_add_tail(&ifrh->list, &ifrh_list); ++ spin_unlock_bh(&ifrh_lock); ++ ++ schedule_task(&mv_home_addr_task); ++ ++ return 0; ++} ++ ++static void send_ret_home_ns(struct in6_addr *ha_addr, ++ struct in6_addr *home_addr, ++ int ifindex) ++{ ++ struct in6_addr nil; ++ struct in6_addr mcaddr; ++ struct net_device *dev = dev_get_by_index(ifindex); ++ if (!dev) ++ return; ++ memset(&nil, 0, sizeof(nil)); ++ addrconf_addr_solict_mult(home_addr, &mcaddr); ++ ndisc_send_ns(dev, NULL, home_addr, &mcaddr, &nil); ++ dev_put(dev); ++} ++ ++static inline int ha_is_reachable(int ifindex, struct in6_addr *ha) ++{ ++ struct net_device *dev; ++ int reachable = 0; ++ ++ dev = dev_get_by_index(ifindex); ++ if (dev) { ++ struct neighbour *neigh; ++ if ((neigh = ndisc_get_neigh(dev, ha)) != NULL) { ++ read_lock_bh(&neigh->lock); ++ if (neigh->nud_state&NUD_VALID) ++ reachable = 1; ++ read_unlock_bh(&neigh->lock); ++ neigh_release(neigh); ++ } ++ dev_put(dev); ++ } ++ return reachable; ++} ++ ++static int mn_ha_handoff(struct handoff *ho) ++{ ++ struct list_head *lh; ++ struct mn_info *minfo; ++ struct in6_addr *coa= ho->coa; ++ int wait_mv_home = 0; ++ ++ read_lock_bh(&mn_info_lock); ++ list_for_each(lh, &mn_info_list) { ++ __u8 has_home_reg; ++ int ifindex; ++ struct in6_addr ha; ++ __u8 athome; ++ __u32 lifetime; ++ struct mipv6_bul_entry *entry = NULL; ++ ++ minfo = list_entry(lh, struct mn_info, list); ++ spin_lock(&minfo->lock); ++ has_home_reg = minfo->has_home_reg; ++ ifindex = minfo->ifindex; ++ ipv6_addr_copy(&ha, &minfo->ha); ++ ++ if (mipv6_prefix_compare(&ho->rtr_addr, &minfo->home_addr, ++ ho->plen)) { ++ if (minfo->has_home_reg) ++ athome = minfo->is_at_home = MN_RETURNING_HOME; ++ else ++ athome = minfo->is_at_home = MN_AT_HOME; ++ coa = &minfo->home_addr; ++ ++ spin_unlock(&minfo->lock); ++#if 0 ++ /* Cancel prefix solicitation, rtr is our HA */ ++ mipv6_pfx_cancel_send(&ho->rtr_addr, ifindex); ++#endif ++ minfo->ifindex = ho->ifindex; ++ ++ if (minfo->has_home_reg && ++ !ha_is_reachable(ho->ifindex, &minfo->ha)) { ++ send_ret_home_ns(&minfo->ha, ++ &minfo->home_addr, ++ ho->ifindex); ++ mipv6_mdet_set_curr_rtr_reachable(0); ++ wait_mv_home++; ++ } ++ if (ifindex != ho->ifindex){ ++ wait_mv_home++; ++ DEBUG(DBG_INFO, ++ "Moving home address back to " ++ "the home interface"); ++ sched_mv_home_addr_task(&minfo->home_addr, ++ 128, ++ ho->ifindex, ++ ifindex, ho); ++ } ++ if (!has_home_reg || wait_mv_home) ++ continue; ++ ++ lifetime = 0; ++ ++ } else { ++ athome = minfo->is_at_home = MN_NOT_AT_HOME; ++ if (minfo->ifindex_user != minfo->ifindex) { ++ DEBUG(DBG_INFO, "Scheduling home address move to virtual interface"); ++ sched_mv_home_addr_task(&minfo->home_addr, ++ 128, ++ minfo->ifindex_user, ++ minfo->ifindex, ho); /* Is minfo->ifindex correct */ ++ ++ wait_mv_home++; ++ } ++ minfo->ifindex = minfo->ifindex_user; ++ spin_unlock(&minfo->lock); ++ if (wait_mv_home) ++ continue; ++ if (!has_home_reg && ++ init_home_registration(&minfo->home_addr, ++ ho->coa)) { ++ continue; ++ } ++ lifetime = mipv6_mn_get_bulifetime(&minfo->home_addr, ++ ho->coa, ++ MIPV6_BU_F_HOME); ++ ++ } ++ write_lock(&bul_lock); ++ if (!(entry = mipv6_bul_get(&ha, &minfo->home_addr)) || ++ !(entry->flags & MIPV6_BU_F_HOME)) { ++ DEBUG(DBG_ERROR, ++ "Unable to find home registration for " ++ "home address: %x:%x:%x:%x:%x:%x:%x:%x!\n", ++ NIPV6ADDR(&minfo->home_addr)); ++ write_unlock(&bul_lock); ++ continue; ++ } ++ DEBUG(DBG_INFO, "Sending home de ? %d registration for " ++ "home address: %x:%x:%x:%x:%x:%x:%x:%x\n" ++ "to home agent %x:%x:%x:%x:%x:%x:%x:%x, " ++ "with lifetime %ld", ++ (athome != MN_NOT_AT_HOME), ++ NIPV6ADDR(&entry->home_addr), ++ NIPV6ADDR(&entry->cn_addr), lifetime); ++ mipv6_send_bu(&entry->home_addr, &entry->cn_addr, ++ coa, INITIAL_BINDACK_TIMEOUT, ++ MAX_BINDACK_TIMEOUT, 1, entry->flags, ++ lifetime, NULL); ++ write_unlock(&bul_lock); ++ ++ } ++ read_unlock_bh(&mn_info_lock); ++ return wait_mv_home; ++} ++/** ++ * mn_cn_handoff - called for every bul entry to send BU to CN ++ * @rawentry: bul entry ++ * @args: handoff event ++ * @sortkey: ++ * ++ * Since MN can have many home addresses and home networks, every BUL ++ * entry needs to be checked ++ **/ ++int mn_cn_handoff(void *rawentry, void *args, unsigned long *sortkey) ++{ ++ struct mipv6_bul_entry *entry = (struct mipv6_bul_entry *)rawentry; ++ struct in6_addr *coa = (struct in6_addr *)args; ++ ++ DEBUG_FUNC(); ++ ++ /* Home registrations already handled by mn_ha_handoff */ ++ if (entry->flags & MIPV6_BU_F_HOME) ++ return ITERATOR_CONT; ++ ++ /* BUL is locked by mipv6_mobile_node_moved which calls us ++ through mipv6_bul_iterate */ ++ ++ if (mipv6_prefix_compare(coa, ++ &entry->home_addr, ++ 64)) { ++ mipv6_send_bu(&entry->home_addr, &entry->cn_addr, ++ &entry->home_addr, INITIAL_BINDACK_TIMEOUT, ++ MAX_BINDACK_TIMEOUT, 1, entry->flags, 0, ++ NULL); ++ } else { ++ u32 lifetime = mipv6_mn_get_bulifetime(&entry->home_addr, ++ coa, ++ entry->flags); ++ mipv6_send_bu(&entry->home_addr, &entry->cn_addr, ++ coa, INITIAL_BINDACK_TIMEOUT, ++ MAX_BINDACK_TIMEOUT, 1, entry->flags, ++ lifetime, NULL); ++ } ++ return ITERATOR_CONT; ++} ++ ++ ++int mn_bul_invalidate(void *rawentry, void *args, unsigned long *sortkey) ++{ ++ struct mipv6_bul_entry *bul = (struct mipv6_bul_entry *)rawentry; ++ struct bul_inval_args *arg = (struct bul_inval_args *)args; ++ ++ DEBUG_FUNC(); ++ ++ if (!ipv6_addr_cmp(arg->cn, &bul->cn_addr) && ++ (!ipv6_addr_cmp(arg->mn, &bul->home_addr) || ++ !ipv6_addr_cmp(arg->mn, &bul->coa))) { ++ if (arg->all_rr_states || !bul->rr || ++ (bul->rr->rr_state != RR_INIT && ++ bul->rr->rr_state != RR_DONE)) { ++ bul->state = ACK_ERROR; ++ bul->callback = bul_entry_expired; ++ bul->callback_time = jiffies + ++ DUMB_CN_BU_LIFETIME * HZ; ++ bul->expire = bul->callback_time; ++ DEBUG(DBG_INFO, "BUL entry set to ACK_ERROR"); ++ mipv6_bul_reschedule(bul); ++ } ++ } ++ return ITERATOR_CONT; ++} ++/** ++ * init_home_registration - start Home Registration process ++ * @home_addr: home address ++ * @coa: care-of address ++ * ++ * Checks whether we have a Home Agent address for this home address. ++ * If not starts Dynamic Home Agent Address Discovery. Otherwise ++ * tries to register with home agent if not already registered. ++ * Returns 1, if home registration process is started and 0 otherwise ++ **/ ++int init_home_registration(struct in6_addr *home_addr, struct in6_addr *coa) ++{ ++ struct mn_info *hinfo; ++ struct in6_addr ha; ++ __u8 man_conf; ++ int ifindex; ++ __u32 lifetime; ++ __u8 user_flags = 0, flags; ++ ++ DEBUG_FUNC(); ++ ++ read_lock_bh(&mn_info_lock); ++ if ((hinfo = mipv6_mninfo_get_by_home(home_addr)) == NULL) { ++ DEBUG(DBG_ERROR, "No mn_info found for address: " ++ "%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(home_addr)); ++ read_unlock_bh(&mn_info_lock); ++ return -ENOENT; ++ } ++ spin_lock(&hinfo->lock); ++ if (mipv6_prefix_compare(&hinfo->home_addr, coa, hinfo->home_plen)) { ++ spin_unlock(&hinfo->lock); ++ read_unlock_bh(&mn_info_lock); ++ DEBUG(DBG_INFO, "Adding home address, MN at home"); ++ return 1; ++ } ++ if (ipv6_addr_any(&hinfo->ha)) { ++ int dhaad_id = mipv6_get_dhaad_id(); ++ hinfo->dhaad_id = dhaad_id; ++ spin_unlock(&hinfo->lock); ++ mipv6_icmpv6_send_dhaad_req(home_addr, hinfo->home_plen, dhaad_id); ++ read_unlock_bh(&mn_info_lock); ++ DEBUG(DBG_INFO, ++ "Home Agent address not set, initiating DHAAD"); ++ return 1; ++ } ++ ipv6_addr_copy(&ha, &hinfo->ha); ++ man_conf = hinfo->man_conf; ++ ifindex = hinfo->ifindex; ++ spin_unlock(&hinfo->lock); ++ read_unlock_bh(&mn_info_lock); ++#if 0 ++ if (man_conf) ++ mipv6_pfx_add_ha(&ha, coa, ifindex); ++#endif ++ if (mipv6_bul_exists(&ha, home_addr)) { ++ DEBUG(DBG_INFO, "BU already sent to HA"); ++ return 0; ++ } ++ /* user flags received through sysctl */ ++ user_flags |= mip6node_cnf.bu_lladdr ? MIPV6_BU_F_LLADDR : 0; ++ user_flags |= mip6node_cnf.bu_keymgm ? MIPV6_BU_F_KEYMGM : 0; ++ ++ flags = MIPV6_BU_F_HOME | MIPV6_BU_F_ACK | user_flags; ++ ++ lifetime = mipv6_mn_get_bulifetime(home_addr, coa, flags); ++ ++ DEBUG(DBG_INFO, "Sending initial home registration for " ++ "home address: %x:%x:%x:%x:%x:%x:%x:%x\n" ++ "to home agent %x:%x:%x:%x:%x:%x:%x:%x, " ++ "with lifetime %ld, prefixlength %d", ++ NIPV6ADDR(home_addr), NIPV6ADDR(&ha), lifetime, 0); ++ ++ write_lock_bh(&bul_lock); ++ mipv6_send_bu(home_addr, &ha, coa, INITIAL_BINDACK_DAD_TIMEOUT, ++ MAX_BINDACK_TIMEOUT, 1, flags, lifetime, NULL); ++ write_unlock_bh(&bul_lock); ++ ++ return 1; ++} ++ ++/** ++ * mipv6_mobile_node_moved - Send BUs to all HAs and CNs ++ * @ho: handoff structure contains the new and previous routers ++ * ++ * Event for handoff. Sends BUs everyone on Binding Update List. ++ **/ ++int mipv6_mobile_node_moved(struct handoff *ho) ++{ ++#if 0 ++ int bu_to_prev_router = 1; ++#endif ++ int dummy; ++ ++ DEBUG_FUNC(); ++ ++ ma_ctl_upd_iface(ho->ifindex, ++ MA_IFACE_CURRENT | MA_IFACE_HAS_ROUTER, &dummy); ++ ++ /* First send BU to HA, then to all other nodes that are on BU list */ ++ if (mn_ha_handoff(ho) != 0) ++ return 0; /* Wait for move home address task */ ++#if 0 ++ /* Add current care-of address to mn_info list, if current router acts ++ as a HA.*/ ++ ++ if (ho->home_address && bu_to_prev_router) ++ mipv6_mninfo_add(ho->coa, ho->plen, ++ MN_AT_HOME, 0, &ho->rtr_addr, ++ ho->plen, ROUTER_BU_DEF_LIFETIME, ++ 0); ++ ++#endif ++ return 0; ++} ++ ++/** ++ * mipv6_mn_send_home_na - send NA when returning home ++ * @haddr: home address to advertise ++ * ++ * After returning home, MN must advertise all its valid addresses in ++ * home link to all nodes. ++ **/ ++void mipv6_mn_send_home_na(struct in6_addr *haddr) ++{ ++ struct net_device *dev = NULL; ++ struct in6_addr mc_allnodes; ++ struct mn_info *hinfo = NULL; ++ ++ read_lock(&mn_info_lock); ++ hinfo = mipv6_mninfo_get_by_home(haddr); ++ if (!hinfo) { ++ read_unlock(&mn_info_lock); ++ return; ++ } ++ spin_lock(&hinfo->lock); ++ hinfo->is_at_home = MN_AT_HOME; ++ dev = dev_get_by_index(hinfo->ifindex); ++ spin_unlock(&hinfo->lock); ++ read_unlock(&mn_info_lock); ++ if (dev == NULL) { ++ DEBUG(DBG_ERROR, "Send home_na: device not found."); ++ return; ++ } ++ ++ ipv6_addr_all_nodes(&mc_allnodes); ++ ndisc_send_na(dev, NULL, &mc_allnodes, haddr, 0, 0, 1, 1); ++ dev_put(dev); ++} ++ ++static int mn_use_hao(struct in6_addr *daddr, struct in6_addr *saddr) ++{ ++ struct mipv6_bul_entry *entry; ++ struct mn_info *minfo = NULL; ++ int add_ha = 0; ++ ++ read_lock_bh(&mn_info_lock); ++ minfo = mipv6_mninfo_get_by_home(saddr); ++ if (minfo && minfo->is_at_home != MN_AT_HOME) { ++ read_lock_bh(&bul_lock); ++ if ((entry = mipv6_bul_get(daddr, saddr)) == NULL) { ++ read_unlock_bh(&bul_lock); ++ read_unlock_bh(&mn_info_lock); ++ return add_ha; ++ } ++ add_ha = (entry->state != ACK_ERROR && ++ (!entry->rr || entry->rr->rr_state == RR_DONE || ++ entry->flags & MIPV6_BU_F_HOME)); ++ read_unlock_bh(&bul_lock); ++ } ++ read_unlock_bh(&mn_info_lock); ++ return add_ha; ++} ++ ++static int ++mn_dev_event(struct notifier_block *nb, unsigned long event, void *ptr) ++{ ++ struct net_device *dev = ptr; ++ struct list_head *lh; ++ struct mn_info *minfo; ++ int newif = 0; ++ ++ /* here are probably the events we need to worry about */ ++ switch (event) { ++ case NETDEV_UP: ++ DEBUG(DBG_DATADUMP, "New netdevice %s registered.", dev->name); ++ if (dev->type != ARPHRD_LOOPBACK && !dev_is_mip6_tnl(dev)) ++ ma_ctl_add_iface(dev->ifindex); ++ ++ break; ++ case NETDEV_GOING_DOWN: ++ DEBUG(DBG_DATADUMP, "Netdevice %s disappeared.", dev->name); ++ /* ++ * Go through mn_info list and move all home addresses on the ++ * netdev going down to a new device. This will make it ++ * practically impossible for the home address to return home, ++ * but allow MN to retain its connections using the address. ++ */ ++ ++ read_lock_bh(&mn_info_lock); ++ list_for_each(lh, &mn_info_list) { ++ minfo = list_entry(lh, struct mn_info, list); ++ spin_lock(&minfo->lock); ++ if (minfo->ifindex == dev->ifindex) { ++ if (sched_mv_home_addr_task(&minfo->home_addr, 128, ++ minfo->ifindex_user, ++ 0, NULL) < 0) { ++ minfo->ifindex = 0; ++ spin_unlock(&minfo->lock); ++ read_unlock_bh(&mn_info_lock); ++ return NOTIFY_DONE; ++ } else { ++ minfo->ifindex = minfo->ifindex_user; ++ if (minfo->is_at_home) { ++ minfo->is_at_home = 0; ++ ++ } ++ newif = minfo->ifindex_user; ++ } ++ } ++ spin_unlock(&minfo->lock); ++ } ++ ++ read_unlock_bh(&mn_info_lock); ++ } ++ ma_ctl_upd_iface(dev->ifindex, MA_IFACE_NOT_PRESENT, &newif); ++ mipv6_mdet_del_if(dev->ifindex); ++ ++ return NOTIFY_DONE; ++} ++ ++struct notifier_block mipv6_mn_dev_notifier = { ++ mn_dev_event, ++ NULL, ++ 0 /* check if using zero is ok */ ++}; ++ ++static void deprecate_addr(struct mn_info *minfo) ++{ ++ /* ++ * Lookup address from IPv6 address list and set deprecated flag ++ */ ++ ++} ++ ++/* ++ * Required because we can only modify addresses after the packet is ++ * constructed. We otherwise mess with higher level protocol ++ * pseudoheaders. With strict protocol layering life would be SO much ++ * easier! ++ */ ++static unsigned int modify_xmit_addrs(unsigned int hooknum, ++ struct sk_buff **pskb, ++ const struct net_device *in, ++ const struct net_device *out, ++ int (*okfn) (struct sk_buff *)) ++{ ++ struct sk_buff *skb = *pskb; ++ ++ DEBUG_FUNC(); ++ ++ if (skb) { ++ struct ipv6hdr *hdr = skb->nh.ipv6h; ++ struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb; ++ struct mipv6_bul_entry *bule; ++ struct in6_addr *daddr; ++ ++ if (!ipv6_addr_any(&opt->hoa)) ++ daddr = &opt->hoa; ++ else ++ daddr = &hdr->daddr; ++ ++ /* We don't consult bul when sending a BU to avoid deadlock, since ++ * BUL is already locked. ++ */ ++ ++ ++ if (opt->mipv6_flags & MIPV6_SND_HAO && ++ !(opt->mipv6_flags & MIPV6_SND_BU)) { ++ write_lock(&bul_lock); ++ bule = mipv6_bul_get(daddr, &hdr->saddr); ++ if (!bule) { ++ write_unlock(&bul_lock); ++ return NF_ACCEPT; ++ } ++ if (!bule->rr || bule->rr->rr_state == RR_DONE || ++ bule->flags & MIPV6_BU_F_HOME) { ++ DEBUG(DBG_DATADUMP, ++ "Replace source address with CoA and reroute"); ++ ipv6_addr_copy(&hdr->saddr, &bule->coa); ++ skb->nfcache |= NFC_ALTERED; ++ } ++ write_unlock(&bul_lock); ++ } else if (opt->mipv6_flags & MIPV6_SND_HAO) { ++ mipv6_get_care_of_address(&hdr->saddr, &hdr->saddr); ++ skb->nfcache |= NFC_ALTERED; ++ } ++ } ++ return NF_ACCEPT; ++} ++ ++/* We set a netfilter hook so that we can modify outgoing packet's ++ * source addresses ++ */ ++struct nf_hook_ops addr_modify_hook_ops = { ++ {NULL, NULL}, /* List head, no predecessor, no successor */ ++ modify_xmit_addrs, ++ PF_INET6, ++ NF_IP6_LOCAL_OUT, ++ NF_IP6_PRI_FIRST /* Should be of EXTREMELY high priority since we ++ * do not want to mess with IPSec (possibly ++ * implemented as packet filter) ++ */ ++}; ++ ++#define MN_INFO_LEN 77 ++ ++static int mn_proc_info(char *buffer, char **start, off_t offset, ++ int length) ++{ ++ struct list_head *p; ++ struct mn_info *minfo; ++ int len = 0, skip = 0; ++ ++ DEBUG_FUNC(); ++ ++ read_lock_bh(&mn_info_lock); ++ list_for_each(p, &mn_info_list) { ++ if (len < offset / MN_INFO_LEN) { ++ skip++; ++ continue; ++ } ++ if (len >= length) ++ break; ++ minfo = list_entry(p, struct mn_info, list); ++ spin_lock(&minfo->lock); ++ len += sprintf(buffer + len, "%02d %08x%08x%08x%08x %02x " ++ "%08x%08x%08x%08x %d %d\n", ++ minfo->ifindex, ++ ntohl(minfo->home_addr.s6_addr32[0]), ++ ntohl(minfo->home_addr.s6_addr32[1]), ++ ntohl(minfo->home_addr.s6_addr32[2]), ++ ntohl(minfo->home_addr.s6_addr32[3]), ++ minfo->home_plen, ++ ntohl(minfo->ha.s6_addr32[0]), ++ ntohl(minfo->ha.s6_addr32[1]), ++ ntohl(minfo->ha.s6_addr32[2]), ++ ntohl(minfo->ha.s6_addr32[3]), ++ minfo->is_at_home, minfo->has_home_reg); ++ spin_unlock(&minfo->lock); ++ } ++ read_unlock_bh(&mn_info_lock); ++ ++ *start = buffer; ++ if (offset) ++ *start += offset % MN_INFO_LEN; ++ ++ len -= offset % MN_INFO_LEN; ++ ++ if (len > length) ++ len = length; ++ if (len < 0) ++ len = 0; ++ ++ return len; ++} ++ ++int mipv6_mn_ha_nd_update(struct net_device *dev, ++ struct in6_addr *ha, u8 *lladdr) ++{ ++ int valid = 0; ++ struct neighbour *neigh; ++ if ((neigh = ndisc_get_neigh(dev, ha))) { ++ read_lock(&neigh->lock); ++ valid = neigh->nud_state & NUD_VALID; ++ read_unlock(&neigh->lock); ++ if (!valid && lladdr) ++ neigh_update(neigh, lladdr, NUD_REACHABLE, 0, 1); ++ neigh_release(neigh); ++ } ++ return valid; ++} ++ ++int mipv6_mn_ha_probe(struct inet6_ifaddr *ifp, u8 *lladdr) ++{ ++ struct mn_info *minfo; ++ ++ if (!(minfo = mipv6_mninfo_get_by_home(&ifp->addr)) || ++ ipv6_addr_any(&minfo->ha)) ++ return 0; ++ ++ if (mipv6_mn_ha_nd_update(ifp->idev->dev, &minfo->ha, lladdr)) ++ mipv6_mdet_retrigger_ho(); ++ return 1; ++} ++ ++int __init mipv6_mn_init(void) ++{ ++ struct net_device *dev; ++ ++ DEBUG_FUNC(); ++ ++ if (mipv6_add_tnl_to_ha()) ++ return -ENODEV; ++ ++ mipv6_bul_init(MIPV6_BUL_SIZE); ++ mip6_fn.mn_use_hao = mn_use_hao; ++ mip6_fn.mn_check_tunneled_packet = mn_check_tunneled_packet; ++ INIT_TQUEUE(&mv_home_addr_task, mv_home_addr, NULL); ++ ++ ma_ctl_init(); ++ for (dev = dev_base; dev; dev = dev->next) { ++ if (dev->flags & IFF_UP && ++ dev->type != ARPHRD_LOOPBACK && !dev_is_mip6_tnl(dev)) { ++ ma_ctl_add_iface(dev->ifindex); ++ } ++ } ++ DEBUG(DBG_INFO, "Multiaccess support initialized"); ++ ++ register_netdevice_notifier(&mipv6_mn_dev_notifier); ++ register_inet6addr_notifier(&mipv6_mn_inet6addr_notifier); ++ ++ ip6ip6_tnl_register_hook(&mipv6_mn_tnl_rcv_send_bu_ops); ++ ip6ip6_tnl_register_hook(&mipv6_mn_tnl_xmit_stats_ops); ++ ip6ip6_tnl_register_hook(&mipv6_mn_tnl_rcv_stats_ops); ++ ++ MIPV6_SETCALL(mipv6_set_home, mipv6_mn_set_home); ++ ++ mipv6_initialize_mdetect(); ++ ++ /* COA to home transformation hook */ ++ MIPV6_SETCALL(mipv6_get_home_address, mipv6_get_saddr_hook); ++ MIPV6_SETCALL(mipv6_mn_ha_probe, mipv6_mn_ha_probe); ++ MIPV6_SETCALL(mipv6_is_home_addr, mipv6_mn_is_home_addr); ++ proc_net_create("mip6_mninfo", 0, mn_proc_info); ++ /* Set packet modification hook (source addresses) */ ++ nf_register_hook(&addr_modify_hook_ops); ++ ++ return 0; ++} ++ ++void __exit mipv6_mn_exit(void) ++{ ++ struct list_head *lh, *tmp; ++ struct mn_info *minfo; ++ DEBUG_FUNC(); ++ ++ mip6_fn.mn_use_hao = NULL; ++ mip6_fn.mn_check_tunneled_packet = NULL; ++ ++ MIPV6_RESETCALL(mipv6_set_home); ++ MIPV6_RESETCALL(mipv6_get_home_address); ++ MIPV6_RESETCALL(mipv6_mn_ha_probe); ++ MIPV6_RESETCALL(mipv6_is_home_addr); ++ nf_unregister_hook(&addr_modify_hook_ops); ++ proc_net_remove("mip6_mninfo"); ++ mipv6_shutdown_mdetect(); ++ ip6ip6_tnl_unregister_hook(&mipv6_mn_tnl_rcv_stats_ops); ++ ip6ip6_tnl_unregister_hook(&mipv6_mn_tnl_xmit_stats_ops); ++ ip6ip6_tnl_unregister_hook(&mipv6_mn_tnl_rcv_send_bu_ops); ++ ma_ctl_clean(); ++ ++ unregister_inet6addr_notifier(&mipv6_mn_inet6addr_notifier); ++ unregister_netdevice_notifier(&mipv6_mn_dev_notifier); ++ write_lock_bh(&mn_info_lock); ++ ++ list_for_each_safe(lh, tmp, &mn_info_list) { ++ minfo = list_entry(lh, struct mn_info, list); ++ if (minfo->is_at_home == MN_NOT_AT_HOME) ++ deprecate_addr(minfo); ++ list_del(&minfo->list); ++ kfree(minfo); ++ } ++ write_unlock_bh(&mn_info_lock); ++ mipv6_bul_exit(); ++ flush_scheduled_tasks(); ++ mipv6_del_tnl_to_ha(); ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/mn.h +@@ -0,0 +1,96 @@ ++/* ++ * MIPL Mobile IPv6 Mobile Node header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _MN_H ++#define _MN_H ++ ++#include ++ ++/* constants for sending of BUs*/ ++#define HA_BU_DEF_LIFETIME 10000 ++#define CN_BU_DEF_LIFETIME 420 /* Max lifetime for RR bindings from RFC 3775 */ ++#define DUMB_CN_BU_LIFETIME 600 /* BUL entry lifetime in case of dumb CN */ ++#define ROUTER_BU_DEF_LIFETIME 30 /* For packet forwarding from previous coa */ ++#define ERROR_DEF_LIFETIME DUMB_CN_BU_LIFETIME ++ ++extern rwlock_t mn_info_lock; ++ ++#define MN_NOT_AT_HOME 0 ++#define MN_RETURNING_HOME 1 ++#define MN_AT_HOME 2 ++ ++/* ++ * Mobile Node information record ++ */ ++struct mn_info { ++ struct in6_addr home_addr; ++ struct in6_addr ha; ++ __u8 home_plen; ++ __u8 is_at_home; ++ __u8 has_home_reg; ++ __u8 man_conf; ++ int ifindex; ++ int ifindex_user; ++ unsigned long home_addr_expires; ++ unsigned short dhaad_id; ++ struct list_head list; ++ spinlock_t lock; ++}; ++ ++/* prototypes for interface functions */ ++int mipv6_mn_init(void); ++void mipv6_mn_exit(void); ++ ++struct handoff; ++ ++/* Interface to movement detection */ ++int mipv6_mobile_node_moved(struct handoff *ho); ++ ++void mipv6_mn_send_home_na(struct in6_addr *haddr); ++/* Init home reg. with coa */ ++int init_home_registration(struct in6_addr *home_addr, struct in6_addr *coa); ++ ++/* mn_info functions that require locking by caller */ ++struct mn_info *mipv6_mninfo_get_by_home(struct in6_addr *haddr); ++ ++struct mn_info *mipv6_mninfo_get_by_ha(struct in6_addr *home_agent); ++ ++struct mn_info *mipv6_mninfo_get_by_id(unsigned short id); ++ ++/* "safe" mn_info functions */ ++void mipv6_mninfo_add(int ifindex, struct in6_addr *home_addr, int plen, ++ int isathome, unsigned long lifetime, struct in6_addr *ha, ++ int ha_plen, unsigned long ha_lifetime, int man_conf); ++ ++int mipv6_mninfo_del(struct in6_addr *home_addr, int del_dyn_only); ++ ++void mipv6_mn_set_home_reg(struct in6_addr *home_addr, int has_home_reg); ++ ++int mipv6_mn_is_at_home(struct in6_addr *addr); ++ ++int mipv6_mn_is_home_addr(struct in6_addr *addr); ++ ++__u32 mipv6_mn_get_bulifetime(struct in6_addr *home_addr, ++ struct in6_addr *coa, __u8 flags); ++int mn_cn_handoff(void *rawentry, void *args, unsigned long *sortkey); ++ ++int mipv6_mn_ha_nd_update(struct net_device *dev, ++ struct in6_addr *ha, u8 *lladdr); ++ ++struct bul_inval_args { ++ int all_rr_states; ++ struct in6_addr *cn; ++ struct in6_addr *mn; ++}; ++ ++int mn_bul_invalidate(void *rawentry, void *args, unsigned long *sortkey); ++ ++#endif /* _MN_H */ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/mobhdr.h +@@ -0,0 +1,101 @@ ++/* ++ * MIPL Mobile IPv6 Mobility Header send and receive ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _MOBHDR_H ++#define _MOBHDR_H ++ ++#include ++ ++/* RR states for mipv6_send_bu() */ ++#define RR_INIT 0x00 ++#define RR_WAITH 0x01 ++#define RR_WAITC 0x02 ++#define RR_WAITHC 0x13 ++#define RR_DONE 0x10 ++ ++#define MH_UNKNOWN_CN 1 ++#define MH_AUTH_FAILED 2 ++#define MH_SEQUENCE_MISMATCH 3 ++ ++struct mipv6_bul_entry; ++struct sk_buff; ++ ++int mipv6_mh_common_init(void); ++void mipv6_mh_common_exit(void); ++int mipv6_mh_mn_init(void); ++void mipv6_mh_mn_exit(void); ++ ++struct mipv6_mh_opt { ++ struct mipv6_mo_alt_coa *alt_coa; ++ struct mipv6_mo_nonce_indices *nonce_indices; ++ struct mipv6_mo_bauth_data *auth_data; ++ struct mipv6_mo_br_advice *br_advice; ++ int freelen; ++ int totlen; ++ u8 *next_free; ++ u8 data[0]; ++}; ++ ++struct mobopt { ++ struct mipv6_mo_alt_coa *alt_coa; ++ struct mipv6_mo_nonce_indices *nonce_indices; ++ struct mipv6_mo_bauth_data *auth_data; ++ struct mipv6_mo_br_advice *br_advice; ++}; ++ ++struct mipv6_mh_opt *alloc_mh_opts(int totlen); ++int append_mh_opt(struct mipv6_mh_opt *ops, u8 type, u8 len, void *data); ++int parse_mo_tlv(void *mos, int len, struct mobopt *opts); ++int mipv6_add_pad(u8 *data, int n); ++ ++struct mipv6_auth_parm { ++ struct in6_addr *coa; ++ struct in6_addr *cn_addr; ++ __u8 *k_bu; ++}; ++ ++int send_mh(struct in6_addr *daddr, struct in6_addr *saddr, ++ u8 msg_type, u8 msg_len, u8 *msg, ++ struct in6_addr *hao_addr, struct in6_addr *rth_addr, ++ struct mipv6_mh_opt *ops, struct mipv6_auth_parm *parm); ++ ++int mipv6_mh_register(int type, int (*func)(struct sk_buff *, ++ struct in6_addr *, struct in6_addr *, ++ struct in6_addr *, struct in6_addr *, struct mipv6_mh *)); ++ ++void mipv6_mh_unregister(int type); ++ ++int mipv6_send_brr(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct mipv6_mh_opt *ops); ++ ++int mipv6_send_bu(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct in6_addr *coa, __u32 initdelay, ++ __u32 maxackdelay, __u8 exp, __u8 flags, ++ __u32 lifetime, struct mipv6_mh_opt *ops); ++ ++int mipv6_send_be(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct in6_addr *home, __u8 status); ++ ++int mipv6_send_ba(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct in6_addr *auth_coa, struct in6_addr *rep_coa, ++ u8 status, u16 sequence, u32 lifetime, u8 *k_bu); ++ ++/* Binding Authentication Data Option routines */ ++#define MAX_HASH_LENGTH 20 ++#define MIPV6_RR_MAC_LENGTH 12 ++ ++int mipv6_auth_build(struct in6_addr *cn_addr, struct in6_addr *coa, ++ __u8 *opt, __u8 *aud_data, __u8 *k_bu); ++ ++int mipv6_auth_check(struct in6_addr *cn_addr, struct in6_addr *coa, ++ __u8 *opt, __u8 optlen, struct mipv6_mo_bauth_data *aud, ++ __u8 *k_bu); ++#endif /* _MOBHDR_H */ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/mobhdr_common.c +@@ -0,0 +1,1210 @@ ++/* ++ * Mobile IPv6 Mobility Header Common Functions ++ * ++ * Authors: ++ * Antti Tuominen ++ * ++ * $Id: s.mh_recv.c 1.159 02/10/16 15:01:29+03:00 antti@traci.mipl.mediapoli.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. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "stats.h" ++#include "debug.h" ++#include "mobhdr.h" ++#include "bcache.h" ++ ++#include "rr_crypto.h" ++#include "exthdrs.h" ++#include "config.h" ++ ++#define MIPV6_MH_MAX MIPV6_MH_BE ++struct mh_proto { ++ int (*func) (struct sk_buff *, ++ struct in6_addr *, struct in6_addr *, ++ struct in6_addr *, struct in6_addr *, ++ struct mipv6_mh *); ++}; ++ ++static struct mh_proto mh_rcv[MIPV6_MH_MAX]; ++ ++int mipv6_mh_register(int type, int (*func)(struct sk_buff *, ++ struct in6_addr *, struct in6_addr *, ++ struct in6_addr *, struct in6_addr *, struct mipv6_mh *)) ++{ ++ if (mh_rcv[type].func != NULL) ++ return -1; ++ ++ mh_rcv[type].func = func; ++ ++ return 0; ++} ++ ++void mipv6_mh_unregister(int type) ++{ ++ if (type < 0 || type > MIPV6_MH_MAX) ++ return; ++ ++ mh_rcv[type].func = NULL; ++} ++ ++struct socket *mipv6_mh_socket = NULL; ++ ++/* TODO: Fix fragmentation */ ++static int dstopts_getfrag( ++ const void *data, struct in6_addr *addr, ++ char *buff, unsigned int offset, unsigned int len) ++{ ++ memcpy(buff, data + offset, len); ++ return 0; ++} ++ ++struct mipv6_mh_opt *alloc_mh_opts(int totlen) ++{ ++ struct mipv6_mh_opt *ops; ++ ++ ops = kmalloc(sizeof(*ops) + totlen, GFP_ATOMIC); ++ if (ops == NULL) ++ return NULL; ++ ++ memset(ops, 0, sizeof(*ops)); ++ ops->next_free = ops->data; ++ ops->freelen = totlen; ++ ++ return ops; ++} ++ ++int append_mh_opt(struct mipv6_mh_opt *ops, u8 type, u8 len, void *data) ++{ ++ struct mipv6_mo *mo; ++ ++ if (ops->next_free == NULL) { ++ DEBUG(DBG_ERROR, "No free room for option"); ++ return -ENOMEM; ++ } ++ if (ops->freelen < len + 2) { ++ DEBUG(DBG_ERROR, "No free room for option"); ++ return -ENOMEM; ++ } ++ else { ++ ops->freelen -= (len + 2); ++ ops->totlen += (len + 2); ++ } ++ ++ mo = (struct mipv6_mo *)ops->next_free; ++ mo->type = type; ++ mo->length = len; ++ ++ switch (type) { ++ case MIPV6_OPT_ALTERNATE_COA: ++ ops->alt_coa = (struct mipv6_mo_alt_coa *)mo; ++ ipv6_addr_copy(&ops->alt_coa->addr, (struct in6_addr *)data); ++ break; ++ case MIPV6_OPT_NONCE_INDICES: ++ DEBUG(DBG_INFO, "Added nonce indices pointer"); ++ ops->nonce_indices = (struct mipv6_mo_nonce_indices *)mo; ++ ops->nonce_indices->home_nonce_i = *(__u16 *)data; ++ ops->nonce_indices->careof_nonce_i = *((__u16 *)data + 1); ++ break; ++ case MIPV6_OPT_AUTH_DATA: ++ DEBUG(DBG_INFO, "Added opt auth_data pointer"); ++ ops->auth_data = (struct mipv6_mo_bauth_data *)mo; ++ break; ++ case MIPV6_OPT_BIND_REFRESH_ADVICE: ++ ops->br_advice = (struct mipv6_mo_br_advice *)mo; ++ ops->br_advice->refresh_interval = htons(*(u16 *)data); ++ break; ++ default: ++ DEBUG(DBG_ERROR, "Unknow option type"); ++ break; ++ } ++ ++ if (ops->freelen == 0) ++ ops->next_free = NULL; ++ else ++ ops->next_free += (len + 2); ++ ++ return 0; ++} ++ ++/* ++ * Calculates required padding with xn + y requirement with offset ++ */ ++static inline int optpad(int xn, int y, int offset) ++{ ++ return ((y - offset) & (xn - 1)); ++} ++ ++static int option_pad(int type, int offset) ++{ ++ if (type == MIPV6_OPT_ALTERNATE_COA) ++ return optpad(8, 6, offset); /* 8n + 6 */ ++ if (type == MIPV6_OPT_BIND_REFRESH_ADVICE || ++ type == MIPV6_OPT_NONCE_INDICES) ++ return optpad(2, 0, offset); /* 2n */ ++ return 0; ++} ++ ++/* ++ * Add Pad1 or PadN option to data ++ */ ++int mipv6_add_pad(u8 *data, int n) ++{ ++ struct mipv6_mo_padn *padn; ++ ++ if (n <= 0) return 0; ++ if (n == 1) { ++ *data = MIPV6_OPT_PAD1; ++ return 1; ++ } ++ padn = (struct mipv6_mo_padn *)data; ++ padn->type = MIPV6_OPT_PADN; ++ padn->length = n - 2; ++ memset(padn->data, 0, n - 2); ++ return n; ++} ++ ++/* ++ * Write options to mobility header buffer ++ */ ++static int prepare_mh_opts(u8 *optdata, int off, struct mipv6_mh_opt *ops) ++{ ++ u8 *nextopt = optdata; ++ int offset = off, pad = 0; ++ ++ if (ops == NULL) { ++ nextopt = NULL; ++ return -1; ++ } ++ ++ if (ops->alt_coa) { ++ pad = option_pad(MIPV6_OPT_ALTERNATE_COA, offset); ++ nextopt += mipv6_add_pad(nextopt, pad); ++ memcpy(nextopt, ops->alt_coa, sizeof(struct mipv6_mo_alt_coa)); ++ nextopt += sizeof(struct mipv6_mo_alt_coa); ++ offset += pad + sizeof(struct mipv6_mo_alt_coa); ++ } ++ ++ if (ops->br_advice) { ++ pad = option_pad(MIPV6_OPT_BIND_REFRESH_ADVICE, offset); ++ nextopt += mipv6_add_pad(nextopt, pad); ++ memcpy(nextopt, ops->br_advice, sizeof(struct mipv6_mo_br_advice)); ++ nextopt += sizeof(struct mipv6_mo_br_advice); ++ offset += pad + sizeof(struct mipv6_mo_br_advice); ++ } ++ ++ if (ops->nonce_indices) { ++ pad = option_pad(MIPV6_OPT_NONCE_INDICES, offset); ++ nextopt += mipv6_add_pad(nextopt, pad); ++ memcpy(nextopt, ops->nonce_indices, sizeof(struct mipv6_mo_nonce_indices)); ++ nextopt += sizeof(struct mipv6_mo_nonce_indices); ++ offset += pad + sizeof(struct mipv6_mo_nonce_indices); ++ } ++ ++ if (ops->auth_data) { ++ /* This option should always be the last. Header ++ * length must be a multiple of 8 octects, so we pad ++ * if necessary. */ ++ pad = optpad(8, 0, offset + ops->auth_data->length + 2); ++ nextopt += mipv6_add_pad(nextopt, pad); ++ memcpy(nextopt, ops->auth_data, ops->auth_data->length + 2); ++ nextopt += ops->auth_data->length + 2; ++ } ++ nextopt = NULL; ++ ++ return 0; ++} ++ ++static int calculate_mh_opts(struct mipv6_mh_opt *ops, int mh_len) ++{ ++ int offset = mh_len; ++ ++ if (ops == NULL) ++ return 0; ++ ++ if (ops->alt_coa) ++ offset += sizeof(struct mipv6_mo_alt_coa) ++ + option_pad(MIPV6_OPT_ALTERNATE_COA, offset); ++ ++ if (ops->br_advice) ++ offset += sizeof(struct mipv6_mo_br_advice) ++ + option_pad(MIPV6_OPT_BIND_REFRESH_ADVICE, offset); ++ ++ if (ops->nonce_indices) ++ offset += sizeof(struct mipv6_mo_nonce_indices) ++ + option_pad(MIPV6_OPT_NONCE_INDICES, offset); ++ ++ if (ops->auth_data) /* no alignment */ ++ offset += ops->auth_data->length + 2; ++ ++ return offset - mh_len; ++} ++ ++/* ++ * ++ * Mobility Header Message send functions ++ * ++ */ ++ ++/** ++ * send_mh - builds and sends a MH msg ++ * ++ * @daddr: destination address for packet ++ * @saddr: source address for packet ++ * @msg_type: type of MH ++ * @msg_len: message length ++ * @msg: MH type specific data ++ * @hao_addr: home address for home address option ++ * @rth_addr: routing header address ++ * @ops: mobility options ++ * @parm: auth data ++ * ++ * Builds MH, appends the type specific msg data to the header and ++ * sends the packet with a home address option, if a home address was ++ * given. Returns 0, if everything succeeded and a negative error code ++ * otherwise. ++ **/ ++int send_mh(struct in6_addr *daddr, ++ struct in6_addr *saddr, ++ u8 msg_type, u8 msg_len, u8 *msg, ++ struct in6_addr *hao_addr, ++ struct in6_addr *rth_addr, ++ struct mipv6_mh_opt *ops, ++ struct mipv6_auth_parm *parm) ++{ ++ struct flowi fl; ++ struct mipv6_mh *mh; ++ struct sock *sk = mipv6_mh_socket->sk; ++ struct ipv6_txoptions *txopt = NULL; ++ int tot_len = sizeof(struct mipv6_mh) + msg_len; ++ int padded_len = 0, txopt_len = 0; ++ ++ DEBUG_FUNC(); ++ /* Add length of options */ ++ tot_len += calculate_mh_opts(ops, tot_len); ++ /* Needs to be a multiple of 8 octets */ ++ padded_len = tot_len + optpad(8, 0, tot_len); ++ ++ mh = sock_kmalloc(sk, padded_len, GFP_ATOMIC); ++ if (!mh) { ++ DEBUG(DBG_ERROR, "memory allocation failed"); ++ return -ENOMEM; ++ } ++ ++ memset(&fl, 0, sizeof(fl)); ++ fl.proto = IPPROTO_MOBILITY; ++ fl.fl6_dst = daddr; ++ fl.fl6_src = saddr; ++ fl.fl6_flowlabel = 0; ++ fl.oif = sk->bound_dev_if; ++ ++ if (hao_addr || rth_addr) { ++ __u8 *opt_ptr; ++ ++ if (hao_addr) ++ txopt_len += sizeof(struct mipv6_dstopt_homeaddr) + 6; ++ if (rth_addr) ++ txopt_len += sizeof(struct rt2_hdr); ++ ++ txopt_len += sizeof(*txopt); ++ txopt = sock_kmalloc(sk, txopt_len, GFP_ATOMIC); ++ if (txopt == NULL) { ++ DEBUG(DBG_ERROR, "No socket space left"); ++ sock_kfree_s(sk, mh, padded_len); ++ return -ENOMEM; ++ } ++ memset(txopt, 0, txopt_len); ++ txopt->tot_len = txopt_len; ++ opt_ptr = (__u8 *) (txopt + 1); ++ if (hao_addr) { ++ int holen = sizeof(struct mipv6_dstopt_homeaddr) + 6; ++ txopt->dst1opt = (struct ipv6_opt_hdr *) opt_ptr; ++ txopt->opt_flen += holen; ++ opt_ptr += holen; ++ mipv6_append_dst1opts(txopt->dst1opt, saddr, ++ NULL, holen); ++ txopt->mipv6_flags = MIPV6_SND_HAO | MIPV6_SND_BU; ++ } ++ if (rth_addr) { ++ int rtlen = sizeof(struct rt2_hdr); ++ txopt->srcrt2 = (struct ipv6_rt_hdr *) opt_ptr; ++ txopt->opt_nflen += rtlen; ++ opt_ptr += rtlen; ++ mipv6_append_rt2hdr(txopt->srcrt2, rth_addr); ++ } ++ } ++ ++ /* Fill in the fields of MH */ ++ mh->payload = NEXTHDR_NONE; ++ mh->length = (padded_len >> 3) - 1; /* Units of 8 octets - 1 */ ++ mh->type = msg_type; ++ mh->reserved = 0; ++ mh->checksum = 0; ++ ++ memcpy(mh->data, msg, msg_len); ++ prepare_mh_opts(mh->data + msg_len, msg_len + sizeof(*mh), ops); ++ /* If BAD is present, this is already done. */ ++ mipv6_add_pad((u8 *)mh + tot_len, padded_len - tot_len); ++ ++ if (parm && parm->k_bu && ops && ops->auth_data) { ++ /* Calculate the position of the authorization data before adding checksum*/ ++ mipv6_auth_build(parm->cn_addr, parm->coa, (__u8 *)mh, ++ (__u8 *)mh + padded_len - MIPV6_RR_MAC_LENGTH, parm->k_bu); ++ } ++ /* Calculate the MH checksum */ ++ mh->checksum = csum_ipv6_magic(fl.fl6_src, fl.fl6_dst, ++ padded_len, IPPROTO_MOBILITY, ++ csum_partial((char *)mh, padded_len, 0)); ++ ip6_build_xmit(sk, dstopts_getfrag, mh, &fl, padded_len, txopt, 255, ++ MSG_DONTWAIT); ++ /* dst cache must be cleared so RR messages can be routed through ++ different interfaces */ ++ sk_dst_reset(sk); ++ ++ if (txopt_len) ++ sock_kfree_s(sk, txopt, txopt_len); ++ sock_kfree_s(sk, mh, padded_len); ++ return 0; ++} ++ ++/** ++ * mipv6_send_brr - send a Binding Refresh Request ++ * @saddr: source address for BRR ++ * @daddr: destination address for BRR ++ * @ops: mobility options ++ * ++ * Sends a binding request. On a mobile node, use the mobile node's ++ * home address for @saddr. Returns 0 on success, negative on ++ * failure. ++ **/ ++int mipv6_send_brr(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct mipv6_mh_opt *ops) ++{ ++ struct mipv6_mh_brr br; ++ ++ memset(&br, 0, sizeof(br)); ++ /* We don't need to explicitly add a RH to brr, since it will be ++ * included automatically, if a BCE exists ++ */ ++ MIPV6_INC_STATS(n_brr_sent); ++ return send_mh(daddr, saddr, MIPV6_MH_BRR, sizeof(br), (u8 *)&br, ++ NULL, NULL, ops, NULL); ++} ++ ++/** ++ * mipv6_send_ba - send a Binding Acknowledgement ++ * @saddr: source address for BA ++ * @daddr: destination address for BA ++ * @reply_coa: destination care-of address of MN ++ * @auth_coa: care-of address of MN used for authentication ++ * @status: status field value ++ * @sequence: sequence number from BU ++ * @lifetime: granted lifetime for binding in seconds ++ * @ops: mobility options ++ * ++ * Send a binding acknowledgement. On a mobile node, use the mobile ++ * node's home address for saddr. Returns 0 on success, non-zero on ++ * failure. ++ **/ ++int mipv6_send_ba(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct in6_addr *auth_coa, struct in6_addr *rep_coa, ++ u8 status, u16 sequence, u32 lifetime, u8 *k_bu) ++{ ++ struct mipv6_mh_ba ba; ++ struct mipv6_auth_parm parm; ++ struct mipv6_mh_opt *ops = NULL; ++ int ops_len = 0, ret = 0; ++ struct mipv6_bce bc_entry; ++ int coming_home = 0; ++ int bypass_tnl = 0; ++ ++ memset(&ba, 0, sizeof(ba)); ++ ++ ba.status = status; ++ ba.sequence = htons(sequence); ++ ba.lifetime = htons(lifetime >> 2); ++ ++ DEBUG(DBG_INFO, "sending a status %d BA %s authenticator to MN \n" ++ "%x:%x:%x:%x:%x:%x:%x:%x at care of address \n" ++ "%x:%x:%x:%x:%x:%x:%x:%x : with lifetime %d and \n" ++ " sequence number %d", ++ status, k_bu ? "with" : "without", ++ NIPV6ADDR(daddr), NIPV6ADDR(auth_coa), lifetime, sequence); ++ ++ memset(&parm, 0, sizeof(parm)); ++ parm.coa = auth_coa; ++ parm.cn_addr = saddr; ++ ++ if (k_bu) { ++ ops_len += sizeof(struct mipv6_mo_bauth_data) + ++ MIPV6_RR_MAC_LENGTH; ++ parm.k_bu = k_bu; ++ } ++ ++ if (mip6node_cnf.binding_refresh_advice) { ++ ops_len += sizeof(struct mipv6_mo_br_advice); ++ } ++ if (ops_len) { ++ ops = alloc_mh_opts(ops_len); ++ if (ops == NULL) { ++ DEBUG(DBG_WARNING, "Out of memory"); ++ return -ENOMEM; ++ } ++ if (mip6node_cnf.binding_refresh_advice > 0) { ++ if (append_mh_opt(ops, MIPV6_OPT_BIND_REFRESH_ADVICE, 2, ++ &mip6node_cnf.binding_refresh_advice) < 0) { ++ DEBUG(DBG_WARNING, "Adding BRA failed"); ++ if (ops) ++ kfree(ops); ++ return -ENOMEM; ++ } ++ } ++ if (k_bu) { ++ if (append_mh_opt(ops, MIPV6_OPT_AUTH_DATA, ++ MIPV6_RR_MAC_LENGTH, NULL) < 0) { ++ DEBUG(DBG_WARNING, "Adding BAD failed"); ++ if (ops) ++ kfree(ops); ++ return -ENOMEM; ++ } ++ } ++ } ++ coming_home = !ipv6_addr_cmp(rep_coa, daddr); ++ ++ bypass_tnl = (coming_home && ++ !mipv6_bcache_get(daddr, saddr, &bc_entry) && ++ bc_entry.flags&MIPV6_BU_F_HOME && ++ status >= 128); ++ ++ if (bypass_tnl && mip6_fn.bce_tnl_rt_del) ++ mip6_fn.bce_tnl_rt_del(&bc_entry.coa, ++ &bc_entry.our_addr, ++ &bc_entry.home_addr); ++ ++ if (coming_home) ++ ret = send_mh(daddr, saddr, MIPV6_MH_BA, sizeof(ba), (u8 *)&ba, ++ NULL, NULL, ops, &parm); ++ else ++ ret = send_mh(daddr, saddr, MIPV6_MH_BA, sizeof(ba), (u8 *)&ba, ++ NULL, rep_coa, ops, &parm); ++ ++ if (bypass_tnl && mip6_fn.bce_tnl_rt_add) ++ mip6_fn.bce_tnl_rt_add(&bc_entry.coa, ++ &bc_entry.our_addr, ++ &bc_entry.home_addr); ++ ++ if (ret == 0) { ++ if (status < 128) { ++ MIPV6_INC_STATS(n_ba_sent); ++ } else { ++ MIPV6_INC_STATS(n_ban_sent); ++ } ++ } ++ ++ if (ops) ++ kfree(ops); ++ ++ return 0; ++} ++ ++/** ++ * mipv6_send_be - send a Binding Error message ++ * @saddr: source address for BE ++ * @daddr: destination address for BE ++ * @home: Home Address in offending packet (if any) ++ * ++ * Sends a binding error. On a mobile node, use the mobile node's ++ * home address for @saddr. Returns 0 on success, negative on ++ * failure. ++ **/ ++int mipv6_send_be(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct in6_addr *home, __u8 status) ++{ ++ struct mipv6_mh_be be; ++ int ret = 0; ++ struct mipv6_bce bc_entry; ++ int bypass_tnl = 0; ++ ++ if (ipv6_addr_is_multicast(daddr)) ++ return -EINVAL; ++ ++ memset(&be, 0, sizeof(be)); ++ be.status = status; ++ if (home) ++ ipv6_addr_copy(&be.home_addr, home); ++ ++ if (mipv6_bcache_get(daddr, saddr, &bc_entry) == 0 && ++ bc_entry.flags&MIPV6_BU_F_HOME) ++ bypass_tnl = 1; ++ ++ if (bypass_tnl && mip6_fn.bce_tnl_rt_del) ++ mip6_fn.bce_tnl_rt_del(&bc_entry.coa, ++ &bc_entry.our_addr, ++ &bc_entry.home_addr); ++ ++ ret = send_mh(daddr, saddr, MIPV6_MH_BE, sizeof(be), (u8 *)&be, ++ NULL, NULL, NULL, NULL); ++ ++ if (bypass_tnl && mip6_fn.bce_tnl_rt_add) ++ mip6_fn.bce_tnl_rt_add(&bc_entry.coa, ++ &bc_entry.our_addr, ++ &bc_entry.home_addr); ++ ++ if (ret == 0) ++ MIPV6_INC_STATS(n_be_sent); ++ ++ return ret; ++} ++ ++/** ++ * mipv6_send_addr_test - send a HoT or CoT message ++ * @saddr: source address ++ * @daddr: destination address ++ * @msg_type: HoT or CoT message ++ * @init: HoTI or CoTI message ++ * ++ * Send a reply to HoTI or CoTI message. ++ **/ ++static int mipv6_send_addr_test(struct in6_addr *saddr, ++ struct in6_addr *daddr, ++ int msg_type, ++ struct mipv6_mh_addr_ti *init) ++{ ++ u_int8_t *kgen_token = NULL; ++ struct mipv6_mh_addr_test addr_test; ++ struct mipv6_rr_nonce *nonce; ++ struct mipv6_mh_opt *ops = NULL; ++ int ret = 0; ++ ++ DEBUG_FUNC(); ++ ++ if ((nonce = mipv6_rr_get_new_nonce())== NULL) { ++ DEBUG(DBG_WARNING, "Nonce creation failed"); ++ return 0; ++ } ++ if (mipv6_rr_cookie_create(daddr, &kgen_token, nonce->index)) { ++ DEBUG(DBG_WARNING, "No cookie"); ++ return 0; ++ } ++ ++ addr_test.nonce_index = nonce->index; ++ memcpy(addr_test.init_cookie, init->init_cookie, ++ MIPV6_RR_COOKIE_LENGTH); ++ memcpy(addr_test.kgen_token, kgen_token, ++ MIPV6_RR_COOKIE_LENGTH); ++ ++ /* No options defined */ ++ ret = send_mh(daddr, saddr, msg_type, sizeof(addr_test), ++ (u8 *)&addr_test, NULL, NULL, ops, NULL); ++ ++ if (ret == 0) { ++ if (msg_type == MIPV6_MH_HOT) { ++ MIPV6_INC_STATS(n_hot_sent); ++ } else { ++ MIPV6_INC_STATS(n_cot_sent); ++ } ++ } ++ ++ return 0; ++} ++ ++static void bc_cache_add(int ifindex, struct in6_addr *daddr, ++ struct in6_addr *haddr, struct in6_addr *coa, ++ struct in6_addr *rep_coa, __u32 lifetime, ++ __u16 sequence, __u8 flags, __u8 *k_bu) ++{ ++ __u8 ba_status = SUCCESS; ++ ++ if (lifetime > MAX_RR_BINDING_LIFE) ++ lifetime = MAX_RR_BINDING_LIFE; ++ ++ if (mipv6_bcache_add(ifindex, daddr, haddr, coa, lifetime, ++ sequence, flags, CACHE_ENTRY) != 0) { ++ DEBUG(DBG_ERROR, "binding failed."); ++ ba_status = INSUFFICIENT_RESOURCES; ++ } ++ ++ if (flags & MIPV6_BU_F_ACK) { ++ DEBUG(DBG_INFO, "sending ack (code=%d)", ba_status); ++ mipv6_send_ba(daddr, haddr, coa, rep_coa, ba_status, sequence, ++ lifetime, k_bu); ++ } ++} ++ ++static void bc_cn_home_add(int ifindex, struct in6_addr *daddr, ++ struct in6_addr *haddr, struct in6_addr *coa, ++ struct in6_addr *rep_coa, __u32 lifetime, ++ __u16 sequence, __u8 flags, __u8 *k_bu) ++{ ++ mipv6_send_ba(daddr, haddr, coa, rep_coa, ++ HOME_REGISTRATION_NOT_SUPPORTED, ++ sequence, lifetime, k_bu); ++} ++ ++static void bc_cache_delete(struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u16 sequence, __u8 flags, ++ __u8 *k_bu) ++{ ++ __u8 status = SUCCESS; ++ ++ /* Cached Care-of Address Deregistration */ ++ if (mipv6_bcache_exists(haddr, daddr) == CACHE_ENTRY) { ++ mipv6_bcache_delete(haddr, daddr, CACHE_ENTRY); ++ } else { ++ DEBUG(DBG_INFO, "entry is not in cache"); ++ status = REASON_UNSPECIFIED; ++ } ++ if (flags & MIPV6_BU_F_ACK) { ++ mipv6_send_ba(daddr, haddr, coa, rep_coa, status, sequence, ++ 0, k_bu); ++ } ++} ++ ++static void bc_cn_home_delete(struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u16 sequence, __u8 flags, ++ __u8 *k_bu) ++{ ++} ++ ++/** ++ * parse_mo_tlv - Parse TLV-encoded Mobility Options ++ * @mos: pointer to Mobility Options ++ * @len: total length of options ++ * @opts: structure to store option pointers ++ * ++ * Parses Mobility Options passed in @mos. Stores pointers in @opts ++ * to all valid mobility options found in @mos. Unknown options and ++ * padding (%MIPV6_OPT_PAD1 and %MIPV6_OPT_PADN) is ignored and ++ * skipped. ++ **/ ++int parse_mo_tlv(void *mos, int len, struct mobopt *opts) ++{ ++ struct mipv6_mo *curr = (struct mipv6_mo *)mos; ++ int left = len; ++ ++ while (left > 0) { ++ int optlen = 0; ++ if (curr->type == MIPV6_OPT_PAD1) ++ optlen = 1; ++ else ++ optlen = 2 + curr->length; ++ ++ if (optlen > left) ++ goto bad; ++ ++ switch (curr->type) { ++ case MIPV6_OPT_PAD1: ++ DEBUG(DBG_DATADUMP, "MIPV6_OPT_PAD1 at %x", curr); ++ break; ++ case MIPV6_OPT_PADN: ++ DEBUG(DBG_DATADUMP, "MIPV6_OPT_PADN at %x", curr); ++ break; ++ case MIPV6_OPT_ALTERNATE_COA: ++ DEBUG(DBG_DATADUMP, "MIPV6_OPT_ACOA at %x", curr); ++ opts->alt_coa = (struct mipv6_mo_alt_coa *)curr; ++ break; ++ case MIPV6_OPT_NONCE_INDICES: ++ DEBUG(DBG_DATADUMP, "MIPV6_OPT_NONCE_INDICES at %x", curr); ++ opts->nonce_indices = ++ (struct mipv6_mo_nonce_indices *)curr; ++ break; ++ case MIPV6_OPT_AUTH_DATA: ++ DEBUG(DBG_DATADUMP, "MIPV6_OPT_AUTH_DATA at %x", curr); ++ opts->auth_data = (struct mipv6_mo_bauth_data *)curr; ++ break; ++ case MIPV6_OPT_BIND_REFRESH_ADVICE: ++ DEBUG(DBG_DATADUMP, "MIPV6_OPT_BIND_REFRESH_ADVICE at %x", curr); ++ opts->br_advice = (struct mipv6_mo_br_advice *)curr; ++ break; ++ default: ++ DEBUG(DBG_INFO, "MO Unknown option type %d at %x, ignoring.", ++ curr->type, curr); ++ /* unknown mobility option, ignore and skip */ ++ } ++ ++ (u8 *)curr += optlen; ++ left -= optlen; ++ } ++ ++ if (left == 0) ++ return 0; ++ bad: ++ return -1; ++} ++ ++/* ++ * ++ * Mobility Header Message handlers ++ * ++ */ ++ ++static int mipv6_handle_mh_testinit(struct sk_buff *skb, ++ struct in6_addr *cn, ++ struct in6_addr *lcoa, ++ struct in6_addr *saddr, ++ struct in6_addr *fcoa, ++ struct mipv6_mh *mh) ++{ ++ struct mipv6_mh_addr_ti *ti = (struct mipv6_mh_addr_ti *)mh->data; ++ int msg_len = (mh->length+1) << 3; ++ int opt_len; ++ DEBUG_FUNC(); ++ ++ if (msg_len > skb->len) ++ return -1; ++ ++ opt_len = msg_len - sizeof(*mh) - sizeof(*ti); ++ ++ if (opt_len < 0) { ++ __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw; ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, pos, skb->dev); ++ ++ DEBUG(DBG_INFO, "Mobility Header length less than H/C TestInit"); ++ return -1; ++ } ++ if (!mip6node_cnf.accept_ret_rout) { ++ DEBUG(DBG_INFO, "Return routability administratively disabled"); ++ return -1; ++ } ++ if (lcoa || fcoa) { ++ DEBUG(DBG_INFO, "H/C TestInit has HAO or RTH2, dropped."); ++ return -1; ++ } ++ ++ if (mh->type == MIPV6_MH_HOTI) { ++ MIPV6_INC_STATS(n_hoti_rcvd); ++ return mipv6_send_addr_test(cn, saddr, MIPV6_MH_HOT, ti); ++ } else if (mh->type == MIPV6_MH_COTI) { ++ MIPV6_INC_STATS(n_coti_rcvd); ++ return mipv6_send_addr_test(cn, saddr, MIPV6_MH_COT, ti); ++ } else ++ return -1; /* Impossible to get here */ ++} ++ ++/** ++ * mipv6_handle_mh_bu - Binding Update handler ++ * @src: care-of address of sender ++ * @dst: our address ++ * @haddr: home address of sender ++ * @mh: pointer to the beginning of the Mobility Header ++ * ++ * Handles Binding Update. Packet and offset to option are passed. ++ * Returns 0 on success, otherwise negative. ++ **/ ++static int mipv6_handle_mh_bu(struct sk_buff *skb, ++ struct in6_addr *dst, ++ struct in6_addr *unused, ++ struct in6_addr *haddr, ++ struct in6_addr *coaddr, ++ struct mipv6_mh *mh) ++{ ++ struct mipv6_mh_bu *bu = (struct mipv6_mh_bu *)mh->data; ++ int msg_len = (mh->length+1) << 3; ++ int opt_len; ++ int auth = 0; ++ int dereg; /* Is this deregistration? */ ++ int addr_type; ++ ++ struct mipv6_bce bc_entry; ++ struct in6_addr *coa, *reply_coa; ++ __u8 *key_bu = NULL; /* RR BU authentication key */ ++ __u8 flags = bu->flags; ++ __u16 sequence; ++ __u32 lifetime; ++ __u16 nonce_ind = (__u16) -1; ++ ++ if (msg_len > skb->len) ++ return -1; ++ ++ opt_len = msg_len - sizeof(*mh) - sizeof(*bu); ++ ++ if (opt_len < 0) { ++ __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw; ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, pos, skb->dev); ++ ++ DEBUG(DBG_INFO, "Mobility Header length less than BU"); ++ MIPV6_INC_STATS(n_bu_drop.invalid); ++ return -1; ++ } ++ ++ addr_type = ipv6_addr_type(haddr); ++ if (addr_type&IPV6_ADDR_LINKLOCAL || !(addr_type&IPV6_ADDR_UNICAST)) ++ return -EINVAL; ++ ++ /* If HAO not present, CoA == HAddr */ ++ if (coaddr == NULL) ++ coa = haddr; ++ else { ++ coa = coaddr; ++ addr_type = ipv6_addr_type(coa); ++ if (addr_type&IPV6_ADDR_LINKLOCAL || ++ !(addr_type&IPV6_ADDR_UNICAST)) ++ return -EINVAL; ++ } ++ reply_coa = coa; ++ ++ sequence = ntohs(bu->sequence); ++ if (bu->lifetime == 0xffff) ++ lifetime = 0xffffffff; ++ else ++ lifetime = ntohs(bu->lifetime) << 2; ++ ++ dereg = (ipv6_addr_cmp(haddr, coa) == 0 || lifetime == 0); ++ ++ if (opt_len > 0) { ++ struct mobopt opts; ++ memset(&opts, 0, sizeof(opts)); ++ if (parse_mo_tlv(bu + 1, opt_len, &opts) < 0) { ++ MIPV6_INC_STATS(n_bu_drop.invalid); ++ return -1; ++ } ++ /* ++ * MIPV6_OPT_AUTH_DATA, MIPV6_OPT_NONCE_INDICES, ++ * MIPV6_OPT_ALT_COA ++ */ ++ if (opts.alt_coa) { ++ coa = &opts.alt_coa->addr; ++ dereg = (ipv6_addr_cmp(haddr, coa) == 0 || lifetime == 0); ++ } ++ addr_type = ipv6_addr_type(coa); ++ if (addr_type&IPV6_ADDR_LINKLOCAL || ++ !(addr_type&IPV6_ADDR_UNICAST)) ++ return -EINVAL; ++ ++ if (flags & MIPV6_BU_F_HOME) { ++ if (opts.nonce_indices) ++ return -1; ++ } else { ++ u8 ba_status = 0; ++ u8 *h_ckie = NULL, *c_ckie = NULL; /* Home and care-of cookies */ ++ ++ /* BUs to CN MUST include authorization data and nonce indices options */ ++ if (!opts.auth_data || !opts.nonce_indices) { ++ DEBUG(DBG_WARNING, ++ "Route optimization BU without authorization material, aborting processing"); ++ return MH_AUTH_FAILED; ++ } ++ if (mipv6_rr_cookie_create( ++ haddr, &h_ckie, opts.nonce_indices->home_nonce_i) < 0) { ++ DEBUG(DBG_WARNING, ++ "mipv6_rr_cookie_create failed for home cookie"); ++ ba_status = EXPIRED_HOME_NONCE_INDEX; ++ } ++ nonce_ind = opts.nonce_indices->home_nonce_i; ++ /* Don't create the care-of cookie, if MN deregisters */ ++ if (!dereg && mipv6_rr_cookie_create( ++ coa, &c_ckie, ++ opts.nonce_indices->careof_nonce_i) < 0) { ++ DEBUG(DBG_WARNING, ++ "mipv6_rr_cookie_create failed for coa cookie"); ++ if (ba_status == 0) ++ ba_status = EXPIRED_CAREOF_NONCE_INDEX; ++ else ++ ba_status = EXPIRED_NONCES; ++ } ++ if (ba_status == 0) { ++ if (dereg) ++ key_bu = mipv6_rr_key_calc(h_ckie, NULL); ++ else ++ key_bu = mipv6_rr_key_calc(h_ckie, c_ckie); ++ mh->checksum = 0;/* TODO: Don't mangle the packet */ ++ if (key_bu && mipv6_auth_check( ++ dst, coa, (__u8 *)mh, msg_len + sizeof(*mh), opts.auth_data, key_bu) == 0) { ++ DEBUG(DBG_INFO, "mipv6_auth_check OK for BU"); ++ auth = 1; ++ } else { ++ DEBUG(DBG_WARNING, ++ "BU Authentication failed"); ++ } ++ } ++ if (h_ckie) ++ kfree(h_ckie); ++ if (c_ckie) ++ kfree(c_ckie); ++ if (ba_status != 0) { ++ MIPV6_INC_STATS(n_bu_drop.auth); ++ mipv6_send_ba(dst, haddr, coa, ++ reply_coa, ba_status, ++ sequence, 0, NULL); ++ goto out; ++ } ++ } ++ ++ } ++ /* Require authorization option for RO, home reg is protected by IPsec */ ++ if (!(flags & MIPV6_BU_F_HOME) && !auth) { ++ MIPV6_INC_STATS(n_bu_drop.auth); ++ if (key_bu) ++ kfree(key_bu); ++ return MH_AUTH_FAILED; ++ } ++ ++ if (mipv6_bcache_get(haddr, dst, &bc_entry) == 0) { ++ if ((bc_entry.flags&MIPV6_BU_F_HOME) != ++ (flags&MIPV6_BU_F_HOME)) { ++ DEBUG(DBG_INFO, ++ "Registration type change. Sending BA REG_TYPE_CHANGE_FORBIDDEN"); ++ mipv6_send_ba(dst, haddr, coa, reply_coa, ++ REG_TYPE_CHANGE_FORBIDDEN, ++ sequence, lifetime, key_bu); ++ goto out; ++ } ++ if (!MIPV6_SEQ_GT(sequence, bc_entry.seq)) { ++ DEBUG(DBG_INFO, ++ "Sequence number mismatch. Sending BA SEQUENCE_NUMBER_OUT_OF_WINDOW"); ++ mipv6_send_ba(dst, haddr, coa, reply_coa, ++ SEQUENCE_NUMBER_OUT_OF_WINDOW, ++ bc_entry.seq, lifetime, key_bu); ++ goto out; ++ } ++ } ++ ++ if (!dereg) { ++ int ifindex; ++ struct rt6_info *rt; ++ ++ /* Avoid looping binding cache entries */ ++ if (mipv6_bcache_get(coa, dst, &bc_entry) == 0) { ++ DEBUG(DBG_WARNING, "Looped BU, dropping the packet"); ++ goto out; ++ } ++ DEBUG(DBG_INFO, "calling bu_add."); ++ if ((rt = rt6_lookup(haddr, dst, 0, 0)) != NULL) { ++ ifindex = rt->rt6i_dev->ifindex; ++ dst_release(&rt->u.dst); ++ } else { ++ /* ++ * Can't process the BU since the right interface is ++ * not found. ++ */ ++ DEBUG(DBG_WARNING, "No route entry found for handling " ++ "a BU request, (using 0 as index)"); ++ ifindex = 0; ++ } ++ if (flags & MIPV6_BU_F_HOME) ++ mip6_fn.bce_home_add(ifindex, dst, haddr, coa, ++ reply_coa, lifetime, sequence, ++ flags, key_bu); ++ else ++ mip6_fn.bce_cache_add(ifindex, dst, haddr, coa, ++ reply_coa, lifetime, sequence, ++ flags, key_bu); ++ } else { ++ DEBUG(DBG_INFO, "calling BCE delete."); ++ ++ if (flags & MIPV6_BU_F_HOME) ++ mip6_fn.bce_home_del(dst, haddr, coa, reply_coa, ++ sequence, flags, key_bu); ++ else { ++ mipv6_rr_invalidate_nonce(nonce_ind); ++ mip6_fn.bce_cache_del(dst, haddr, coa, reply_coa, ++ sequence, flags, key_bu); ++ } ++ } ++ out: ++ MIPV6_INC_STATS(n_bu_rcvd); ++ if (key_bu) ++ kfree(key_bu); ++ return 0; ++} ++ ++static int mipv6_mh_rcv(struct sk_buff *skb) ++{ ++ struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb; ++ struct mipv6_mh *mh; ++ struct in6_addr *lhome, *fhome, *lcoa = NULL, *fcoa = NULL; ++ int ret = 0; ++ ++ fhome = &skb->nh.ipv6h->saddr; ++ lhome = &skb->nh.ipv6h->daddr; ++ ++ if (opt->hao != 0) { ++ struct mipv6_dstopt_homeaddr *hao; ++ hao = (struct mipv6_dstopt_homeaddr *)(skb->nh.raw + opt->hao); ++ fcoa = &hao->addr; ++ } ++ ++ if (opt->srcrt2 != 0) { ++ struct rt2_hdr *rt2; ++ rt2 = (struct rt2_hdr *)((u8 *)skb->nh.raw + opt->srcrt2); ++ lcoa = &rt2->addr; ++ } ++ ++ /* Verify checksum is correct */ ++ if (skb->ip_summed == CHECKSUM_HW) { ++ skb->ip_summed = CHECKSUM_UNNECESSARY; ++ if (csum_ipv6_magic(fhome, lhome, skb->len, IPPROTO_MOBILITY, ++ skb->csum)) { ++ if (net_ratelimit()) ++ printk(KERN_WARNING "MIPv6 MH hw checksum failed\n"); ++ skb->ip_summed = CHECKSUM_NONE; ++ } ++ } ++ if (skb->ip_summed == CHECKSUM_NONE) { ++ if (csum_ipv6_magic(fhome, lhome, skb->len, IPPROTO_MOBILITY, ++ skb_checksum(skb, 0, skb->len, 0))) { ++ if (net_ratelimit()) ++ printk(KERN_WARNING "MIPv6 MH checksum failed\n"); ++ goto bad; ++ } ++ } ++ ++ if (!pskb_may_pull(skb, skb->h.raw-skb->data+sizeof(*mh)) || ++ !pskb_may_pull(skb, ++ skb->h.raw-skb->data+((skb->h.raw[1]+1)<<3))) { ++ DEBUG(DBG_INFO, "MIPv6 MH invalid length"); ++ kfree_skb(skb); ++ return 0; ++ } ++ ++ mh = (struct mipv6_mh *) skb->h.raw; ++ ++ /* Verify there are no more headers after the MH */ ++ if (mh->payload != NEXTHDR_NONE) { ++ __u32 pos = (__u32)&mh->payload - (__u32)skb->nh.raw; ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, pos, skb->dev); ++ ++ DEBUG(DBG_INFO, "MIPv6 MH error"); ++ goto bad; ++ } ++ ++ if (mh->type > MIPV6_MH_MAX) { ++ /* send binding error */ ++ printk("Invalid mobility header type (%d)\n", mh->type); ++ mipv6_send_be(lhome, fcoa ? fcoa : fhome, ++ fcoa ? fhome : NULL, ++ MIPV6_BE_UNKNOWN_MH_TYPE); ++ goto bad; ++ } ++ if (mh_rcv[mh->type].func != NULL) { ++ ret = mh_rcv[mh->type].func(skb, lhome, lcoa, fhome, fcoa, mh); ++ } else { ++ DEBUG(DBG_INFO, "No handler for MH Type %d", mh->type); ++ goto bad; ++ } ++ ++ kfree_skb(skb); ++ return 0; ++ ++bad: ++ MIPV6_INC_STATS(n_mh_in_error); ++ kfree_skb(skb); ++ return 0; ++ ++} ++ ++#if LINUX_VERSION_CODE >= 0x2052a ++struct inet6_protocol mipv6_mh_protocol = ++{ ++ mipv6_mh_rcv, /* handler */ ++ NULL /* error control */ ++}; ++#else ++struct inet6_protocol mipv6_mh_protocol = ++{ ++ mipv6_mh_rcv, /* handler */ ++ NULL, /* error control */ ++ NULL, /* next */ ++ IPPROTO_MOBILITY, /* protocol ID */ ++ 0, /* copy */ ++ NULL, /* data */ ++ "MIPv6 MH" /* name */ ++}; ++#endif ++ ++/* ++ * ++ * Code module init/exit functions ++ * ++ */ ++ ++int __init mipv6_mh_common_init(void) ++{ ++ struct sock *sk; ++ int err; ++ ++ mip6_fn.bce_home_add = bc_cn_home_add; ++ mip6_fn.bce_cache_add = bc_cache_add; ++ mip6_fn.bce_home_del = bc_cn_home_delete; ++ mip6_fn.bce_cache_del = bc_cache_delete; ++ ++ mipv6_mh_socket = sock_alloc(); ++ if (mipv6_mh_socket == NULL) { ++ printk(KERN_ERR ++ "Failed to create the MIP6 MH control socket.\n"); ++ return -1; ++ } ++ mipv6_mh_socket->type = SOCK_RAW; ++ ++ if ((err = sock_create(PF_INET6, SOCK_RAW, IPPROTO_MOBILITY, ++ &mipv6_mh_socket)) < 0) { ++ printk(KERN_ERR ++ "Failed to initialize the MIP6 MH control socket (err %d).\n", ++ err); ++ sock_release(mipv6_mh_socket); ++ mipv6_mh_socket = NULL; /* for safety */ ++ return err; ++ } ++ ++ sk = mipv6_mh_socket->sk; ++ sk->allocation = GFP_ATOMIC; ++ sk->sndbuf = 64 * 1024 + sizeof(struct sk_buff); ++ sk->prot->unhash(sk); ++ ++ memset(&mh_rcv, 0, sizeof(mh_rcv)); ++ mh_rcv[MIPV6_MH_HOTI].func = mipv6_handle_mh_testinit; ++ mh_rcv[MIPV6_MH_COTI].func = mipv6_handle_mh_testinit; ++ mh_rcv[MIPV6_MH_BU].func = mipv6_handle_mh_bu; ++ ++#if LINUX_VERSION_CODE >= 0x2052a ++ if (inet6_add_protocol(&mipv6_mh_protocol, IPPROTO_MOBILITY) < 0) { ++ printk(KERN_ERR "Failed to register MOBILITY protocol\n"); ++ sock_release(mipv6_mh_socket); ++ mipv6_mh_socket = NULL; ++ return -EAGAIN; ++ } ++#else ++ inet6_add_protocol(&mipv6_mh_protocol); ++#endif ++ /* To disable the use of dst_cache, ++ * which slows down the sending of BUs ?? ++ */ ++ sk->dst_cache=NULL; ++ ++ return 0; ++} ++ ++void __exit mipv6_mh_common_exit(void) ++{ ++ if (mipv6_mh_socket) sock_release(mipv6_mh_socket); ++ mipv6_mh_socket = NULL; /* For safety. */ ++ ++#if LINUX_VERSION_CODE >= 0x2052a ++ inet6_del_protocol(&mipv6_mh_protocol, IPPROTO_MOBILITY); ++#else ++ inet6_del_protocol(&mipv6_mh_protocol); ++#endif ++ memset(&mh_rcv, 0, sizeof(mh_rcv)); ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/mobhdr_mn.c +@@ -0,0 +1,1155 @@ ++/* ++ * Mobile IPv6 Mobility Header Functions for Mobile Node ++ * ++ * Authors: ++ * Antti Tuominen ++ * Niklas Kämpe ++ * Henrik Petander ++ * ++ * $Id:$ ++ * ++ * 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. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mobhdr.h" ++#include "mn.h" ++#include "bul.h" ++#include "rr_crypto.h" ++#include "debug.h" ++#include "util.h" ++#include "stats.h" ++ ++int rr_configured = 1; ++ ++/* Return value of mipv6_rr_state() */ ++#define NO_RR 0 ++#define DO_RR 1 ++#define RR_FOR_COA 2 ++#define INPROGRESS_RR 3 ++ ++/** ++ * send_bu_msg - sends a Binding Update ++ * @bulentry : BUL entry with the information for building a BU ++ * ++ * Function builds a BU msg based on the contents of a bul entry. ++ * Does not change the bul entry. ++ **/ ++static int send_bu_msg(struct mipv6_bul_entry *binding) ++{ ++ int auth = 0; /* Use auth */ ++ int ret = 0; ++ struct mipv6_auth_parm parm; ++ struct mipv6_mh_bu bu; ++ ++ if (!binding) { ++ DEBUG(DBG_ERROR, "called with a null bul entry"); ++ return -1; ++ } ++ ++ memset(&parm, 0, sizeof(parm)); ++ if (mipv6_prefix_compare(&binding->coa, &binding->home_addr, 64)) ++ parm.coa = &binding->home_addr; ++ else ++ parm.coa = &binding->coa; ++ parm.cn_addr = &binding->cn_addr; ++ ++ if (binding->rr && binding->rr->kbu) { ++ DEBUG(DBG_INFO, "Binding with key"); ++ auth = 1; ++ parm.k_bu = binding->rr->kbu; ++ } ++ memset(&bu, 0, sizeof(bu)); ++ bu.flags = binding->flags; ++ bu.sequence = htons(binding->seq); ++ bu.lifetime = htons(binding->lifetime >> 2); ++ bu.reserved = 0; ++ ++ ret = send_mh(&binding->cn_addr, &binding->home_addr, ++ MIPV6_MH_BU, sizeof(bu), (u8 *)&bu, ++ &binding->home_addr, NULL, ++ binding->ops, &parm); ++ ++ if (ret == 0) ++ MIPV6_INC_STATS(n_bu_sent); ++ ++ return ret; ++} ++ ++/** ++ * mipv6_send_addr_test_init - send a HoTI or CoTI message ++ * @saddr: source address for H/CoTI ++ * @daddr: destination address for H/CoTI ++ * @msg_type: Identifies whether HoTI or CoTI ++ * @init_cookie: the HoTi or CoTi init cookie ++ * ++ * The message will be retransmitted till we get a HoT or CoT message, since ++ * our caller (mipv6_RR_start) has entered this message in the BUL with ++ * exponential backoff retramission set. ++ */ ++static int mipv6_send_addr_test_init(struct in6_addr *saddr, ++ struct in6_addr *daddr, ++ u8 msg_type, ++ u8 *init_cookie) ++{ ++ struct mipv6_mh_addr_ti ti; ++ struct mipv6_mh_opt *ops = NULL; ++ int ret = 0; ++ ++ /* Set reserved and copy the cookie from address test init msg */ ++ ti.reserved = 0; ++ mipv6_rr_mn_cookie_create(init_cookie); ++ memcpy(ti.init_cookie, init_cookie, MIPV6_RR_COOKIE_LENGTH); ++ ++ ret = send_mh(daddr, saddr, msg_type, sizeof(ti), (u8 *)&ti, ++ NULL, NULL, ops, NULL); ++ if (ret == 0) { ++ if (msg_type == MIPV6_MH_HOTI) { ++ MIPV6_INC_STATS(n_hoti_sent); ++ } else { ++ MIPV6_INC_STATS(n_coti_sent); ++ } ++ } ++ ++ return ret; ++} ++ ++/* ++ * ++ * Callback handlers for binding update list ++ * ++ */ ++ ++/* Return value 0 means keep entry, non-zero means discard entry. */ ++ ++/* Callback for BUs not requiring acknowledgement ++ */ ++int bul_entry_expired(struct mipv6_bul_entry *bulentry) ++{ ++ /* Lifetime expired, delete entry. */ ++ DEBUG(DBG_INFO, "bul entry 0x%p lifetime expired, deleting entry", ++ bulentry); ++ return 1; ++} ++ ++/* Callback for BUs requiring acknowledgement with exponential resending ++ * scheme */ ++static int bul_resend_exp(struct mipv6_bul_entry *bulentry) ++{ ++ unsigned long now = jiffies; ++ ++ DEBUG(DBG_INFO, "(0x%x) resending bu", (int) bulentry); ++ ++ ++ /* If sending a de-registration, do not care about the ++ * lifetime value, as de-registrations are normally sent with ++ * a zero lifetime value. If the entry is a home entry get the ++ * current lifetime. ++ */ ++ ++ if (bulentry->lifetime != 0) { ++ bulentry->lifetime = mipv6_mn_get_bulifetime( ++ &bulentry->home_addr, &bulentry->coa, bulentry->flags); ++ ++ bulentry->expire = now + bulentry->lifetime * HZ; ++ } else { ++ bulentry->expire = now + HOME_RESEND_EXPIRE * HZ; ++ } ++ if (bulentry->rr) { ++ /* Redo RR, if cookies have expired */ ++ if (time_after(jiffies, bulentry->rr->home_time + MAX_TOKEN_LIFE * HZ)) ++ bulentry->rr->rr_state |= RR_WAITH; ++ if (time_after(jiffies, bulentry->rr->careof_time + MAX_NONCE_LIFE * HZ)) ++ bulentry->rr->rr_state |= RR_WAITC; ++ ++ if (bulentry->rr->rr_state & RR_WAITH) { ++ /* Resend HoTI directly */ ++ mipv6_send_addr_test_init(&bulentry->home_addr, ++ &bulentry->cn_addr, MIPV6_MH_HOTI, ++ bulentry->rr->hot_cookie); ++ } ++ if (bulentry->rr->rr_state & RR_WAITC) { ++ /* Resend CoTI directly */ ++ mipv6_send_addr_test_init(&bulentry->coa, ++ &bulentry->cn_addr, MIPV6_MH_COTI, ++ bulentry->rr->cot_cookie); ++ } ++ goto out; ++ } ++ ++ bulentry->seq++; ++ ++ if (send_bu_msg(bulentry) < 0) ++ DEBUG(DBG_ERROR, "Resending of BU failed"); ++ ++out: ++ /* Schedule next retransmission */ ++ if (bulentry->delay < bulentry->maxdelay) { ++ bulentry->delay = 2 * bulentry->delay; ++ if (bulentry->delay > bulentry->maxdelay) { ++ /* can happen if maxdelay is not power(mindelay, 2) */ ++ bulentry->delay = bulentry->maxdelay; ++ } ++ } else if (bulentry->flags & MIPV6_BU_F_HOME) { ++ /* Home registration - continue sending BU at maxdelay rate */ ++ DEBUG(DBG_INFO, "Sending BU to HA after max ack wait time " ++ "reached(0x%x)", (int) bulentry); ++ bulentry->delay = bulentry->maxdelay; ++ } else if (!(bulentry->flags & MIPV6_BU_F_HOME)) { ++ /* Failed to get BA from a CN */ ++ bulentry->callback_time = now; ++ return -1; ++ } ++ ++ bulentry->callback_time = now + bulentry->delay * HZ; ++ return 0; ++} ++ ++ ++ ++/* Callback for sending a registration refresh BU ++ */ ++static int bul_refresh(struct mipv6_bul_entry *bulentry) ++{ ++ unsigned long now = jiffies; ++ ++ /* Refresh interval passed, send new BU */ ++ DEBUG(DBG_INFO, "bul entry 0x%x refresh interval passed, sending new BU", (int) bulentry); ++ if (bulentry->lifetime == 0) ++ return 0; ++ ++ /* Set new maximum lifetime and expiration time */ ++ bulentry->lifetime = mipv6_mn_get_bulifetime(&bulentry->home_addr, ++ &bulentry->coa, ++ bulentry->flags); ++ bulentry->expire = now + bulentry->lifetime * HZ; ++ bulentry->seq++; ++ /* Send update */ ++ if (send_bu_msg(bulentry) < 0) ++ DEBUG(DBG_ERROR, "Resending of BU failed"); ++ ++ if (time_after_eq(now, bulentry->expire)) { ++ /* Sanity check */ ++ DEBUG(DBG_ERROR, "bul entry expire time in history - setting expire to %u secs", ERROR_DEF_LIFETIME); ++ bulentry->lifetime = ERROR_DEF_LIFETIME; ++ bulentry->expire = now + ERROR_DEF_LIFETIME*HZ; ++ } ++ ++ /* Set up retransmission */ ++ bulentry->state = RESEND_EXP; ++ bulentry->callback = bul_resend_exp; ++ bulentry->callback_time = now + INITIAL_BINDACK_TIMEOUT*HZ; ++ bulentry->delay = INITIAL_BINDACK_TIMEOUT; ++ bulentry->maxdelay = MAX_BINDACK_TIMEOUT; ++ ++ return 0; ++} ++ ++static int mipv6_send_RR_bu(struct mipv6_bul_entry *bulentry) ++{ ++ int ret; ++ int ops_len = 0; ++ u16 nonces[2]; ++ ++ DEBUG(DBG_INFO, "Sending BU to CN %x:%x:%x:%x:%x:%x:%x:%x " ++ "for home address %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(&bulentry->cn_addr), NIPV6ADDR(&bulentry->home_addr)); ++ nonces[0] = bulentry->rr->home_nonce_index; ++ nonces[1] = bulentry->rr->careof_nonce_index; ++ ops_len = sizeof(struct mipv6_mo_bauth_data) + MIPV6_RR_MAC_LENGTH + ++ sizeof(struct mipv6_mo_nonce_indices); ++ if (bulentry->ops) { ++ DEBUG(DBG_WARNING, "Bul entry had existing mobility options, freeing them"); ++ kfree(bulentry->ops); ++ } ++ bulentry->ops = alloc_mh_opts(ops_len); ++ ++ if (!bulentry->ops) ++ return -ENOMEM; ++ if (append_mh_opt(bulentry->ops, MIPV6_OPT_NONCE_INDICES, ++ sizeof(struct mipv6_mo_nonce_indices) - 2, nonces) < 0) ++ return -ENOMEM; ++ ++ if (append_mh_opt(bulentry->ops, MIPV6_OPT_AUTH_DATA, ++ MIPV6_RR_MAC_LENGTH, NULL) < 0) ++ return -ENOMEM; ++ /* RR procedure is over, send a BU */ ++ if (!(bulentry->flags & MIPV6_BU_F_ACK)) { ++ DEBUG(DBG_INFO, "Setting bul callback to bul_entry_expired"); ++ bulentry->state = ACK_OK; ++ bulentry->callback = bul_entry_expired; ++ bulentry->callback_time = jiffies + HZ * bulentry->lifetime; ++ bulentry->expire = jiffies + HZ * bulentry->lifetime; ++ } ++ else { ++ bulentry->callback_time = jiffies + HZ; ++ bulentry->expire = jiffies + HZ * bulentry->lifetime; ++ } ++ ++ ret = send_bu_msg(bulentry); ++ mipv6_bul_reschedule(bulentry); ++ return ret; ++} ++ ++static int mipv6_rr_state(struct mipv6_bul_entry *bul, struct in6_addr *saddr, ++ struct in6_addr *coa, __u8 flags) ++{ ++ if (!rr_configured) ++ return NO_RR; ++ if (flags & MIPV6_BU_F_HOME) { ++ /* We don't need RR, this is a Home Registration */ ++ return NO_RR; ++ } ++ if (!bul || !bul->rr) { ++ /* First time BU to CN, need RR */ ++ return DO_RR; ++ } ++ ++ switch (bul->rr->rr_state) { ++ case RR_INIT: ++ /* Need RR if first BU to CN */ ++ return DO_RR; ++ case RR_DONE: ++ /* If MN moves to a new coa, do RR for it */ ++ if (!ipv6_addr_cmp(&bul->coa, coa)) ++ return NO_RR; ++ else ++ return DO_RR; ++ default: ++ /* ++ * We are in the middle of RR, the HoTI and CoTI have been ++ * sent. But we haven't got HoT and CoT from the CN, so ++ * don't do anything more at this time. ++ */ ++ return INPROGRESS_RR; ++ } ++} ++ ++/** ++ * mipv6_RR_start - Start Return Routability procedure ++ * @home_addr: home address ++ * @cn_addr: correspondent address ++ * @coa: care-of address ++ * @entry: binding update list entry (if any) ++ * @initdelay: initial ack timeout ++ * @maxackdelay: maximum ack timeout ++ * @flags: flags ++ * @lifetime: lifetime of binding ++ * @ops: mobility options ++ * ++ * Caller must hold @bul_lock (write). ++ **/ ++static int mipv6_RR_start(struct in6_addr *home_addr, struct in6_addr *cn_addr, ++ struct in6_addr *coa, struct mipv6_bul_entry *entry, ++ __u32 initdelay, __u32 maxackdelay, __u8 flags, ++ __u32 lifetime, struct mipv6_mh_opt *ops) ++{ ++ int ret = -1; ++ struct mipv6_bul_entry *bulentry = entry; ++ struct mipv6_rr_info *rr = NULL; ++ int seq = 0; ++ DEBUG_FUNC(); ++ ++ /* Do RR procedure only for care-of address after handoff, ++ if home cookie is still valid */ ++ if (bulentry && bulentry->rr) { ++ if (time_before(jiffies, bulentry->rr->home_time + MAX_NONCE_LIFE * HZ) && ++ lifetime && !(ipv6_addr_cmp(home_addr, coa) == 0)) { ++ mipv6_rr_mn_cookie_create(bulentry->rr->cot_cookie); ++ DEBUG(DBG_INFO, "Bul entry and rr info exist, only doing RR for CoA"); ++ ipv6_addr_copy(&bulentry->coa, coa); ++ bulentry->rr->rr_state |= RR_WAITC; ++ } else if (!lifetime) { /* Send only HoTi when returning home */ ++ mipv6_rr_mn_cookie_create(bulentry->rr->hot_cookie); ++ DEBUG(DBG_INFO, "Bul entry and rr info exist, only doing RR for HoA"); ++ ipv6_addr_copy(&bulentry->coa, coa); /* Home address as CoA */ ++ bulentry->rr->rr_state |= RR_WAITH; ++ } ++ } else { ++ DEBUG(DBG_INFO, "Doing RR for both HoA and CoA"); ++ rr = kmalloc(sizeof(*rr), GFP_ATOMIC); ++ memset(rr, 0, sizeof(*rr)); ++ rr->rr_state = RR_WAITHC; ++ } ++ if (bulentry) { ++ if (bulentry->state == ACK_ERROR) ++ goto out; ++ seq = bulentry->seq + 1; ++ } else ++ seq = 0; ++ /* Save the info in the BUL to retransmit the BU after RR is done */ ++ /* Caller must hold bul_lock (write) since we don't */ ++ ++ if ((bulentry = mipv6_bul_add(cn_addr, home_addr, coa, ++ min_t(__u32, lifetime, MAX_RR_BINDING_LIFE), ++ seq, flags, bul_resend_exp, initdelay, ++ RESEND_EXP, initdelay, ++ maxackdelay, ops, ++ rr)) == NULL) { ++ DEBUG(DBG_INFO, "couldn't update BUL for HoTi"); ++ goto out; ++ } ++ ++ rr = bulentry->rr; ++ if (rr->rr_state&RR_WAITH) ++ mipv6_send_addr_test_init(home_addr, cn_addr, MIPV6_MH_HOTI, ++ rr->hot_cookie); ++ if (ipv6_addr_cmp(home_addr, coa) && lifetime) ++ mipv6_send_addr_test_init(coa, cn_addr, MIPV6_MH_COTI, rr->cot_cookie); ++ else { ++ bulentry->rr->rr_state &= ~RR_WAITC; ++ } ++ ret = 0; ++out: ++ return ret; ++} ++ ++/* ++ * Status codes for mipv6_ba_rcvd() ++ */ ++#define STATUS_UPDATE 0 ++#define STATUS_REMOVE 1 ++ ++/** ++ * mipv6_ba_rcvd - Update BUL for this Binding Acknowledgement ++ * @ifindex: interface BA came from ++ * @cnaddr: sender IPv6 address ++ * @home_addr: home address ++ * @sequence: sequence number ++ * @lifetime: lifetime granted by Home Agent in seconds ++ * @refresh: recommended resend interval ++ * @status: %STATUS_UPDATE (ack) or %STATUS_REMOVE (nack) ++ * ++ * This function must be called to notify the module of the receipt of ++ * a binding acknowledgement so that it can cease retransmitting the ++ * option. The caller must have validated the acknowledgement before calling ++ * this function. 'status' can be either STATUS_UPDATE in which case the ++ * binding acknowledgement is assumed to be valid and the corresponding ++ * binding update list entry is updated, or STATUS_REMOVE in which case ++ * the corresponding binding update list entry is removed (this can be ++ * used upon receiving a negative acknowledgement). ++ * Returns 0 if a matching binding update has been sent or non-zero if ++ * not. ++ */ ++static int mipv6_ba_rcvd(int ifindex, struct in6_addr *cnaddr, ++ struct in6_addr *home_addr, ++ u16 sequence, u32 lifetime, ++ u32 refresh, int status) ++{ ++ struct mipv6_bul_entry *bulentry; ++ unsigned long now = jiffies; ++ struct in6_addr coa; ++ ++ DEBUG(DBG_INFO, "BA received with sequence number 0x%x, status: %d", ++ (int) sequence, status); ++ ++ /* Find corresponding entry in binding update list. */ ++ write_lock(&bul_lock); ++ if ((bulentry = mipv6_bul_get(cnaddr, home_addr)) == NULL) { ++ DEBUG(DBG_INFO, "- discarded, no entry in bul matches BA source address"); ++ write_unlock(&bul_lock); ++ return -1; ++ } ++ ++ ipv6_addr_copy(&coa, &bulentry->coa); ++ if (status == SEQUENCE_NUMBER_OUT_OF_WINDOW) { ++ __u32 lifetime = mipv6_mn_get_bulifetime(&bulentry->home_addr, ++ &bulentry->coa, ++ bulentry->flags); ++ bulentry->seq = sequence; ++ ++ mipv6_send_bu(&bulentry->home_addr, &bulentry->cn_addr, ++ &bulentry->coa, INITIAL_BINDACK_TIMEOUT, ++ MAX_BINDACK_TIMEOUT, 1, bulentry->flags, ++ lifetime, NULL); ++ write_unlock(&bul_lock); ++ return 0; ++ } else if (status >= REASON_UNSPECIFIED) { ++ int err; ++ int at_home = MN_NOT_AT_HOME; ++ DEBUG(DBG_WARNING, "- NACK - BA status: %d, deleting bul entry", status); ++ if (bulentry->flags & MIPV6_BU_F_HOME) { ++ struct mn_info *minfo; ++ read_lock(&mn_info_lock); ++ minfo = mipv6_mninfo_get_by_home(home_addr); ++ if (minfo) { ++ spin_lock(&minfo->lock); ++ if (minfo->is_at_home != MN_NOT_AT_HOME) ++ minfo->is_at_home = MN_AT_HOME; ++ at_home = minfo->is_at_home; ++ minfo->has_home_reg = 0; ++ spin_unlock(&minfo->lock); ++ } ++ read_unlock(&mn_info_lock); ++ DEBUG(DBG_ERROR, "Home registration failed: BA status: %d, deleting bul entry", status); ++ } ++ write_unlock(&bul_lock); ++ err = mipv6_bul_delete(cnaddr, home_addr); ++ if (at_home == MN_AT_HOME) { ++ mipv6_mn_send_home_na(home_addr); ++ write_lock_bh(&bul_lock); ++ mipv6_bul_iterate(mn_cn_handoff, &coa); ++ write_unlock_bh(&bul_lock); ++ } ++ return err; ++ } ++ bulentry->state = ACK_OK; ++ ++ if (bulentry->flags & MIPV6_BU_F_HOME && lifetime > 0) { ++ /* For home registrations: schedule a refresh binding update. ++ * Use the refresh interval given by home agent or 80% ++ * of lifetime, whichever is less. ++ * ++ * Adjust binding lifetime if 'granted' lifetime ++ * (lifetime value in received binding acknowledgement) ++ * is shorter than 'requested' lifetime (lifetime ++ * value sent in corresponding binding update). ++ * max((L_remain - (L_update - L_ack)), 0) ++ */ ++ if (lifetime * HZ < (bulentry->expire - bulentry->lastsend)) { ++ bulentry->expire = ++ max_t(__u32, bulentry->expire - ++ ((bulentry->expire - bulentry->lastsend) - ++ lifetime * HZ), jiffies + ++ ERROR_DEF_LIFETIME * HZ); ++ } ++ if (refresh > lifetime || refresh == 0) ++ refresh = 4 * lifetime / 5; ++ DEBUG(DBG_INFO, "setting callback for expiration of" ++ " a Home Registration: lifetime:%d, refresh:%d", ++ lifetime, refresh); ++ bulentry->callback = bul_refresh; ++ bulentry->callback_time = now + refresh * HZ; ++ bulentry->expire = now + lifetime * HZ; ++ bulentry->lifetime = lifetime; ++ if (time_after_eq(jiffies, bulentry->expire)) { ++ /* Sanity check */ ++ DEBUG(DBG_ERROR, "bul entry expire time in history - setting expire to %u secs", ++ ERROR_DEF_LIFETIME); ++ bulentry->expire = jiffies + ERROR_DEF_LIFETIME * HZ; ++ } ++ mipv6_mn_set_home_reg(home_addr, 1); ++ mipv6_bul_iterate(mn_cn_handoff, &coa); ++ } else if ((bulentry->flags & MIPV6_BU_F_HOME) && bulentry->lifetime == 0) { ++ write_unlock(&bul_lock); ++ DEBUG(DBG_INFO, "Got BA for deregistration BU"); ++ mipv6_mn_set_home_reg(home_addr, 0); ++ mipv6_bul_delete(cnaddr, home_addr); ++ mipv6_mn_send_home_na(home_addr); ++ ++ write_lock_bh(&bul_lock); ++ mipv6_bul_iterate(mn_cn_handoff, &coa); ++ write_unlock_bh(&bul_lock); ++ return 0; ++ } ++ ++ mipv6_bul_reschedule(bulentry); ++ write_unlock(&bul_lock); ++ ++ return 0; ++} ++ ++static int mipv6_handle_mh_HC_test(struct sk_buff *skb, ++ struct in6_addr *saddr, ++ struct in6_addr *fcoa, ++ struct in6_addr *cn, ++ struct in6_addr *lcoa, ++ struct mipv6_mh *mh) ++{ ++ int ret = 0; ++ int msg_len = (mh->length+1) << 3; ++ int opt_len; ++ ++ struct mipv6_mh_addr_test *tm = (struct mipv6_mh_addr_test *)mh->data; ++ struct mipv6_bul_entry *bulentry; ++ ++ DEBUG_FUNC(); ++ ++ if (msg_len > skb->len) ++ return -1; ++ ++ opt_len = msg_len - sizeof(*mh) - sizeof(*tm); ++ ++ if (opt_len < 0) { ++ __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw; ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, pos, skb->dev); ++ ++ DEBUG(DBG_INFO, "Mobility Header length less than H/C Test"); ++ return -1; ++ } ++ if (fcoa || lcoa) { ++ DEBUG(DBG_INFO, "H/C Test has HAO or RTH2, dropped."); ++ return -1; ++ } ++ write_lock(&bul_lock); ++ ++ /* We need to get the home address, since CoT only has the CoA*/ ++ if (mh->type == MIPV6_MH_COT) { ++ if ((bulentry = mipv6_bul_get_by_ccookie(cn, tm->init_cookie)) == NULL) { ++ DEBUG(DBG_ERROR, "has no BUL or RR state for " ++ "source:%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(cn)); ++ write_unlock(&bul_lock); ++ return -1; ++ } ++ } else { /* HoT has the home address */ ++ if (((bulentry = mipv6_bul_get(cn, saddr)) == NULL) || !bulentry->rr) { ++ DEBUG(DBG_ERROR, "has no BUL or RR state for " ++ "source:%x:%x:%x:%x:%x:%x:%x:%x " ++ "dest:%x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(cn), NIPV6ADDR(saddr)); ++ write_unlock(&bul_lock); ++ return -1; ++ } ++ } ++ ++ switch (mh->type) { ++ case MIPV6_MH_HOT: ++ if ((bulentry->rr->rr_state & RR_WAITH) == 0) { ++ DEBUG(DBG_ERROR, "Not waiting for a Home Test message"); ++ goto out; ++ } ++ /* ++ * Make sure no home cookies have been received yet. ++ * TODO: Check not being put in at this time since subsequent ++ * BU's after this time will have home cookie stored. ++ */ ++ ++ /* Check if the cookie received is the right one */ ++ if (!mipv6_equal_cookies(tm->init_cookie, ++ bulentry->rr->hot_cookie)) { ++ /* Invalid cookie, might be an old cookie */ ++ DEBUG(DBG_WARNING, "Received HoT cookie does not match stored cookie"); ++ goto out; ++ } ++ DEBUG(DBG_INFO, "Got Care-of Test message"); ++ bulentry->rr->rr_state &= ~RR_WAITH; ++ memcpy(bulentry->rr->home_cookie, tm->kgen_token, MIPV6_COOKIE_LEN); ++ bulentry->rr->home_nonce_index = tm->nonce_index; ++ bulentry->rr->home_time = jiffies; ++ ret = 1; ++ break; ++ ++ case MIPV6_MH_COT: ++ if ((bulentry->rr->rr_state & RR_WAITC) == 0) { ++ DEBUG(DBG_ERROR, "Not waiting for a Home Test message"); ++ goto out; ++ } ++ /* ++ * Make sure no home cookies have been received yet. ++ * TODO: Check not being put in at this time since subsequent ++ * BU's at this time will have careof cookie stored. ++ */ ++ ++ /* Check if the cookie received is the right one */ ++ if (!mipv6_equal_cookies(tm->init_cookie, ++ bulentry->rr->cot_cookie)) { ++ DEBUG(DBG_INFO, "Received CoT cookie does not match stored cookie"); ++ goto out; ++ } ++ bulentry->rr->rr_state &= ~RR_WAITC; ++ memcpy(bulentry->rr->careof_cookie, tm->kgen_token, MIPV6_COOKIE_LEN); ++ bulentry->rr->careof_nonce_index = tm->nonce_index; ++ bulentry->rr->careof_time = jiffies; ++ ret = 1; ++ break; ++ default: ++ /* Impossible to get here */ ++ break; ++ } ++out: ++ if (bulentry->rr->rr_state == RR_DONE) { ++ if (bulentry->rr->kbu) /* First free any old keys */ ++ kfree(bulentry->rr->kbu); ++ /* Store the session key to be used in BU's */ ++ if (ipv6_addr_cmp(&bulentry->coa, &bulentry->home_addr) && bulentry->lifetime) ++ bulentry->rr->kbu = mipv6_rr_key_calc(bulentry->rr->home_cookie, ++ bulentry->rr->careof_cookie); ++ else ++ bulentry->rr->kbu = mipv6_rr_key_calc(bulentry->rr->home_cookie, ++ NULL); ++ /* RR procedure is over, send a BU */ ++ mipv6_send_RR_bu(bulentry); ++ } ++ write_unlock(&bul_lock); ++ return ret; ++} ++ ++/** ++ * mipv6_handle_mh_brr - Binding Refresh Request handler ++ * @home: home address ++ * @coa: care-of address ++ * @cn: source of this packet ++ * @mh: pointer to the beginning of the Mobility Header ++ * ++ * Handles Binding Refresh Request. Packet and offset to option are ++ * passed. Returns 0 on success, otherwise negative. ++ **/ ++static int mipv6_handle_mh_brr(struct sk_buff *skb, ++ struct in6_addr *home, ++ struct in6_addr *unused1, ++ struct in6_addr *cn, ++ struct in6_addr *unused2, ++ struct mipv6_mh *mh) ++{ ++ struct mipv6_mh_brr *brr = (struct mipv6_mh_brr *)mh->data; ++ struct mipv6_bul_entry *binding; ++ int msg_len = (mh->length+1) << 3; ++ int opt_len; ++ ++ if (msg_len > skb->len) ++ return -1; ++ ++ opt_len = msg_len - sizeof(*mh) - sizeof(*brr); ++ ++ if (opt_len < 0) { ++ __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw; ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, pos, skb->dev); ++ ++ DEBUG(DBG_WARNING, "Mobility Header length less than BRR"); ++ MIPV6_INC_STATS(n_brr_drop.invalid); ++ return -1; ++ } ++ ++ /* check we know src, else drop */ ++ write_lock(&bul_lock); ++ if ((binding = mipv6_bul_get(cn, home)) == NULL) { ++ MIPV6_INC_STATS(n_brr_drop.misc); ++ write_unlock(&bul_lock); ++ return MH_UNKNOWN_CN; ++ } ++ ++ MIPV6_INC_STATS(n_brr_rcvd); ++ ++ if (opt_len > 0) { ++ struct mobopt opts; ++ memset(&opts, 0, sizeof(opts)); ++ if (parse_mo_tlv(brr + 1, opt_len, &opts) < 0) { ++ write_unlock(&bul_lock); ++ return -1; ++ } ++ /* ++ * MIPV6_OPT_AUTH_DATA ++ */ ++ } ++ ++ /* must hold bul_lock (write) */ ++ mipv6_RR_start(home, cn, &binding->coa, binding, binding->delay, ++ binding->maxdelay, binding->flags, ++ binding->lifetime, binding->ops); ++ ++ write_unlock(&bul_lock); ++ /* MAY also decide to delete binding and send zero lifetime BU ++ with alt-coa set to home address */ ++ ++ return 0; ++} ++ ++/** ++ * mipv6_handle_mh_ba - Binding Acknowledgement handler ++ * @src: source of this packet ++ * @coa: care-of address ++ * @home: home address ++ * @mh: pointer to the beginning of the Mobility Header ++ * ++ **/ ++static int mipv6_handle_mh_ba(struct sk_buff *skb, ++ struct in6_addr *home, ++ struct in6_addr *coa, ++ struct in6_addr *src, ++ struct in6_addr *unused, ++ struct mipv6_mh *mh) ++{ ++ struct mipv6_mh_ba *ba = (struct mipv6_mh_ba *)mh->data; ++ struct mipv6_bul_entry *binding = NULL; ++ struct mobopt opts; ++ int msg_len = (mh->length+1) << 3; ++ int opt_len; ++ ++ int auth = 1, req_auth = 1, refresh = -1, ifindex = 0; ++ u32 lifetime, sequence; ++ ++ if (msg_len > skb->len) ++ return -1; ++ ++ opt_len = msg_len - sizeof(*mh) - sizeof(*ba); ++ ++ if (opt_len < 0) { ++ __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw; ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, pos, skb->dev); ++ ++ DEBUG(DBG_WARNING, "Mobility Header length less than BA"); ++ MIPV6_INC_STATS(n_ba_drop.invalid); ++ return -1; ++ } ++ ++ lifetime = ntohs(ba->lifetime) << 2; ++ sequence = ntohs(ba->sequence); ++ ++ if (opt_len > 0) { ++ memset(&opts, 0, sizeof(opts)); ++ if (parse_mo_tlv(ba + 1, opt_len, &opts) < 0) ++ return -1; ++ /* ++ * MIPV6_OPT_AUTH_DATA, MIPV6_OPT_BR_ADVICE ++ */ ++ if (opts.br_advice) ++ refresh = ntohs(opts.br_advice->refresh_interval); ++ } ++ ++ if (ba->status >= EXPIRED_HOME_NONCE_INDEX && ++ ba->status <= EXPIRED_NONCES) ++ req_auth = 0; ++ ++ write_lock(&bul_lock); ++ binding = mipv6_bul_get(src, home); ++ if (!binding) { ++ DEBUG(DBG_INFO, "No binding, BA dropped."); ++ write_unlock(&bul_lock); ++ return -1; ++ } ++ ++ if (opts.auth_data && binding->rr && ++ (mipv6_auth_check(src, coa, (__u8 *)mh, msg_len, ++ opts.auth_data, binding->rr->kbu) == 0)) ++ auth = 1; ++ ++ if (req_auth && binding->rr && !auth) { ++ DEBUG(DBG_INFO, "BA Authentication failed."); ++ MIPV6_INC_STATS(n_ba_drop.auth); ++ write_unlock(&bul_lock); ++ return MH_AUTH_FAILED; ++ } ++ ++ if (ba->status == SEQUENCE_NUMBER_OUT_OF_WINDOW) { ++ DEBUG(DBG_INFO, ++ "Sequence number out of window, setting seq to %d", ++ sequence); ++ } else if (binding->seq != sequence) { ++ DEBUG(DBG_INFO, "BU/BA Sequence Number mismatch %d != %d", ++ binding->seq, sequence); ++ MIPV6_INC_STATS(n_ba_drop.invalid); ++ write_unlock(&bul_lock); ++ return MH_SEQUENCE_MISMATCH; ++ } ++ if (ba->status == EXPIRED_HOME_NONCE_INDEX || ba->status == EXPIRED_NONCES) { ++ if (binding->rr) { ++ /* Need to resend home test init to CN */ ++ binding->rr->rr_state |= RR_WAITH; ++ mipv6_send_addr_test_init(&binding->home_addr, ++ &binding->cn_addr, ++ MIPV6_MH_HOTI, ++ binding->rr->hot_cookie); ++ MIPV6_INC_STATS(n_ban_rcvd); ++ } else { ++ DEBUG(DBG_WARNING, "Got BA with status EXPIRED_HOME_NONCE_INDEX" ++ "for non-RR BU"); ++ MIPV6_INC_STATS(n_ba_drop.invalid); ++ } ++ write_unlock(&bul_lock); ++ return 0; ++ } ++ if (ba->status == EXPIRED_CAREOF_NONCE_INDEX || ba->status == EXPIRED_NONCES) { ++ if (binding->rr) { ++ /* Need to resend care-of test init to CN */ ++ binding->rr->rr_state |= RR_WAITC; ++ mipv6_send_addr_test_init(&binding->coa, ++ &binding->cn_addr, ++ MIPV6_MH_COTI, ++ binding->rr->cot_cookie); ++ MIPV6_INC_STATS(n_ban_rcvd); ++ } else { ++ DEBUG(DBG_WARNING, "Got BA with status EXPIRED_HOME_CAREOF_INDEX" ++ "for non-RR BU"); ++ MIPV6_INC_STATS(n_ba_drop.invalid); ++ } ++ write_unlock(&bul_lock); ++ return 0; ++ } ++ write_unlock(&bul_lock); ++ ++ if (ba->status >= REASON_UNSPECIFIED) { ++ DEBUG(DBG_INFO, "Binding Ack status : %d indicates error", ba->status); ++ mipv6_ba_rcvd(ifindex, src, home, sequence, lifetime, ++ refresh, ba->status); ++ MIPV6_INC_STATS(n_ban_rcvd); ++ return 0; ++ } ++ MIPV6_INC_STATS(n_ba_rcvd); ++ if (mipv6_ba_rcvd(ifindex, src, home, ntohs(ba->sequence), lifetime, ++ refresh, ba->status)) { ++ DEBUG(DBG_WARNING, "mipv6_ba_rcvd failed"); ++ } ++ ++ return 0; ++} ++ ++/** ++ * mipv6_handle_mh_be - Binding Error handler ++ * @cn: source of this packet ++ * @coa: care-of address ++ * @home: home address ++ * @mh: pointer to the beginning of the Mobility Header ++ * ++ **/ ++ ++static int mipv6_handle_mh_be(struct sk_buff *skb, ++ struct in6_addr *home, ++ struct in6_addr *coa, ++ struct in6_addr *cn, ++ struct in6_addr *unused, ++ struct mipv6_mh *mh) ++{ ++ struct mipv6_mh_be *be = (struct mipv6_mh_be *)mh->data; ++ int msg_len = (mh->length+1) << 3; ++ int opt_len; ++ struct in6_addr *hoa; ++ struct bul_inval_args args; ++ ++ DEBUG_FUNC(); ++ ++ if (msg_len > skb->len) ++ return -1; ++ ++ opt_len = msg_len - sizeof(*mh) - sizeof(*be); ++ ++ if (opt_len < 0) { ++ __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw; ++ icmpv6_send(skb, ICMPV6_PARAMPROB, ++ ICMPV6_HDR_FIELD, pos, skb->dev); ++ ++ DEBUG(DBG_WARNING, "Mobility Header length less than BE"); ++ MIPV6_INC_STATS(n_be_drop.invalid); ++ return -1; ++ } ++ ++ ++ if (!ipv6_addr_any(&be->home_addr)) ++ hoa = &be->home_addr; ++ else ++ hoa = home; ++ ++ MIPV6_INC_STATS(n_be_rcvd); ++ ++ args.all_rr_states = 0; ++ args.cn = cn; ++ args.mn = hoa; ++ ++ switch (be->status) { ++ case 1: /* Home Address Option used without a binding */ ++ /* Get ULP information about CN-MN communication. If ++ nothing in progress, MUST delete. Otherwise MAY ++ ignore. */ ++ args.all_rr_states = 1; ++ case 2: /* Received unknown MH type */ ++ /* If not expecting ack, SHOULD ignore. If MH ++ extension in use, stop it. If not, stop RO for ++ this CN. */ ++ write_lock(&bul_lock); ++ mipv6_bul_iterate(mn_bul_invalidate, &args); ++ write_unlock(&bul_lock); ++ break; ++ } ++ ++ return 0; ++} ++ ++/* ++ * mipv6_bu_rate_limit() : Takes a bulentry, a COA and 'flags' to check ++ * whether BU being sent is for Home Registration or not. ++ * ++ * If the number of BU's sent is fewer than MAX_FAST_UPDATES, this BU ++ * is allowed to be sent at the MAX_UPDATE_RATE. ++ * If the number of BU's sent is greater than or equal to MAX_FAST_UPDATES, ++ * this BU is allowed to be sent at the SLOW_UPDATE_RATE. ++ * ++ * Assumption : This function is not re-entrant. and the caller holds the ++ * bulentry lock (by calling mipv6_bul_get()) to stop races with other ++ * CPU's executing this same function. ++ * ++ * Side-Effects. Either of the following could on success : ++ * 1. Sets consecutive_sends to 1 if the entry is a Home agent ++ * registration or the COA has changed. ++ * 2. Increments consecutive_sends if the number of BU's sent so ++ * far is less than MAX_FAST_UPDATES, and this BU is being sent ++ * atleast MAX_UPDATE_RATE after previous one. ++ * ++ * Return Value : 0 on Success, -1 on Failure ++ */ ++static int mipv6_bu_rate_limit(struct mipv6_bul_entry *bulentry, ++ struct in6_addr *coa, __u8 flags) ++{ ++ if ((flags & MIPV6_BU_F_HOME) || ipv6_addr_cmp(&bulentry->coa, coa)) { ++ /* Home Agent Registration or different COA - restart from 1 */ ++ bulentry->consecutive_sends = 1; ++ return 0; ++ } ++ ++ if (bulentry->consecutive_sends < MAX_FAST_UPDATES) { ++ /* First MAX_FAST_UPDATES can be sent at MAX_UPDATE_RATE */ ++ if (jiffies - bulentry->lastsend < MAX_UPDATE_RATE * HZ) { ++ return -1; ++ } ++ bulentry->consecutive_sends ++; ++ } else { ++ /* Remaining updates SHOULD be sent at SLOW_UPDATE_RATE */ ++ if (jiffies - bulentry->lastsend < SLOW_UPDATE_RATE * HZ) { ++ return -1; ++ } ++ /* Don't inc 'consecutive_sends' to avoid overflow to zero */ ++ } ++ /* OK to send a BU */ ++ return 0; ++} ++ ++/** ++ * mipv6_send_bu - send a Binding Update ++ * @saddr: source address for BU ++ * @daddr: destination address for BU ++ * @coa: care-of address for MN ++ * @initdelay: initial BA wait timeout ++ * @maxackdelay: maximum BA wait timeout ++ * @exp: exponention back off ++ * @flags: flags for BU ++ * @lifetime: granted lifetime for binding ++ * @ops: mobility options ++ * ++ * Send a binding update. 'flags' may contain any of %MIPV6_BU_F_ACK, ++ * %MIPV6_BU_F_HOME, %MIPV6_BU_F_ROUTER bitwise ORed. If ++ * %MIPV6_BU_F_ACK is included retransmission will be attempted until ++ * the update has been acknowledged. Retransmission is done if no ++ * acknowledgement is received within @initdelay seconds. @exp ++ * specifies whether to use exponential backoff (@exp != 0) or linear ++ * backoff (@exp == 0). For exponential backoff the time to wait for ++ * an acknowledgement is doubled on each retransmission until a delay ++ * of @maxackdelay, after which retransmission is no longer attempted. ++ * For linear backoff the delay is kept constant and @maxackdelay ++ * specifies the maximum number of retransmissions instead. If ++ * sub-options are present ops must contain all sub-options to be ++ * added. On a mobile node, use the mobile node's home address for ++ * @saddr. Returns 0 on success, non-zero on failure. ++ * ++ * Caller may not hold @bul_lock. ++ **/ ++int mipv6_send_bu(struct in6_addr *saddr, struct in6_addr *daddr, ++ struct in6_addr *coa, u32 initdelay, ++ u32 maxackdelay, u8 exp, u8 flags, u32 lifetime, ++ struct mipv6_mh_opt *ops) ++{ ++ int ret; ++ __u8 state; ++ __u16 seq = 0; ++ int (*callback)(struct mipv6_bul_entry *); ++ __u32 callback_time; ++ struct mipv6_bul_entry *bulentry; ++ ++ /* First a sanity check: don't send BU to local addresses */ ++ if(ipv6_chk_addr(daddr, NULL)) { ++ DEBUG(DBG_ERROR, "BUG: Trying to send BU to local address"); ++ return -1; ++ } ++ DEBUG(DBG_INFO, "Sending BU to CN %x:%x:%x:%x:%x:%x:%x:%x " ++ "for home address %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(daddr), NIPV6ADDR(saddr)); ++ ++ if ((bulentry = mipv6_bul_get(daddr, saddr)) != NULL) { ++ if (bulentry->state == ACK_ERROR) { ++ /* ++ * Don't send any more BU's to nodes which don't ++ * understanding one. ++ */ ++ DEBUG(DBG_INFO, "Not sending BU to node which doesn't" ++ " understand one"); ++ return -1; ++ } ++ if (mipv6_bu_rate_limit(bulentry, coa, flags) < 0) { ++ DEBUG(DBG_DATADUMP, "Limiting BU sent."); ++ return 0; ++ } ++ } ++ ++ switch (mipv6_rr_state(bulentry, saddr, coa, flags)) { ++ case INPROGRESS_RR: ++ /* We are already doing RR, don't do BU at this time, it is ++ * done automatically later */ ++ DEBUG(DBG_INFO, "RR in progress not sending BU"); ++ return 0; ++ ++ case DO_RR: ++ /* Just do RR and return, BU is done automatically later */ ++ DEBUG(DBG_INFO, "starting RR" ); ++ mipv6_RR_start(saddr, daddr, coa, bulentry, initdelay, ++ maxackdelay, flags, lifetime, ops); ++ return 0; ++ ++ case NO_RR: ++ DEBUG(DBG_DATADUMP, "No RR necessary" ); ++ default: ++ break; ++ } ++ ++ if (bulentry) ++ seq = bulentry->seq + 1; ++ ++ /* Add to binding update list */ ++ ++ if (flags & MIPV6_BU_F_ACK) { ++ DEBUG(DBG_INFO, "Setting bul callback to bul_resend_exp"); ++ /* Send using exponential backoff */ ++ state = RESEND_EXP; ++ callback = bul_resend_exp; ++ callback_time = initdelay; ++ } else { ++ DEBUG(DBG_INFO, "Setting bul callback to bul_entry_expired"); ++ /* No acknowledgement/resending required */ ++ state = ACK_OK; /* pretend we got an ack */ ++ callback = bul_entry_expired; ++ callback_time = lifetime; ++ } ++ ++ /* BU only for the home address */ ++ /* We must hold bul_lock (write) while calling add */ ++ if ((bulentry = mipv6_bul_add(daddr, saddr, coa, lifetime, seq, ++ flags, callback, callback_time, ++ state, initdelay, maxackdelay, ops, ++ NULL)) == NULL) { ++ DEBUG(DBG_INFO, "couldn't update BUL"); ++ return 0; ++ } ++ ret = send_bu_msg(bulentry); ++ ++ return ret; ++} ++ ++int __init mipv6_mh_mn_init(void) ++{ ++ mipv6_mh_register(MIPV6_MH_HOT, mipv6_handle_mh_HC_test); ++ mipv6_mh_register(MIPV6_MH_COT, mipv6_handle_mh_HC_test); ++ mipv6_mh_register(MIPV6_MH_BA, mipv6_handle_mh_ba); ++ mipv6_mh_register(MIPV6_MH_BRR, mipv6_handle_mh_brr); ++ mipv6_mh_register(MIPV6_MH_BE, mipv6_handle_mh_be); ++ ++ return 0; ++} ++ ++void __exit mipv6_mh_mn_exit(void) ++{ ++ mipv6_mh_unregister(MIPV6_MH_HOT); ++ mipv6_mh_unregister(MIPV6_MH_COT); ++ mipv6_mh_unregister(MIPV6_MH_BA); ++ mipv6_mh_unregister(MIPV6_MH_BRR); ++ mipv6_mh_unregister(MIPV6_MH_BE); ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/module_cn.c +@@ -0,0 +1,167 @@ ++/* ++ * Mobile IPv6 Common Module ++ * ++ * Authors: ++ * Sami Kivisaari ++ * Antti Tuominen ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++ ++#ifdef CONFIG_SYSCTL ++#include ++#endif /* CONFIG_SYSCTL */ ++ ++#include ++ ++#include "bcache.h" ++#include "mipv6_icmp.h" ++#include "stats.h" ++#include "mobhdr.h" ++#include "exthdrs.h" ++ ++int mipv6_debug = 1; ++ ++#if defined(MODULE) && LINUX_VERSION_CODE > 0x20115 ++MODULE_AUTHOR("MIPL Team"); ++MODULE_DESCRIPTION("Mobile IPv6"); ++MODULE_LICENSE("GPL"); ++MODULE_PARM(mipv6_debug, "i"); ++#endif ++ ++#include "config.h" ++ ++struct mip6_func mip6_fn; ++struct mip6_conf mip6node_cnf = { ++ capabilities: CAP_CN, ++ accept_ret_rout: 1, ++ max_rtr_reachable_time: 0, ++ eager_cell_switching: 0, ++ max_num_tunnels: 0, ++ min_num_tunnels: 0, ++ binding_refresh_advice: 0, ++ bu_lladdr: 0, ++ bu_keymgm: 0, ++ bu_cn_ack: 0 ++}; ++ ++#define MIPV6_BCACHE_SIZE 128 ++ ++/********************************************************************** ++ * ++ * MIPv6 CN Module Init / Cleanup ++ * ++ **********************************************************************/ ++ ++#ifdef CONFIG_SYSCTL ++/* Sysctl table */ ++ctl_table mipv6_mobility_table[] = { ++ {NET_IPV6_MOBILITY_DEBUG, "debuglevel", ++ &mipv6_debug, sizeof(int), 0644, NULL, ++ &proc_dointvec}, ++ {NET_IPV6_MOBILITY_RETROUT, "accept_return_routability", ++ &mip6node_cnf.accept_ret_rout, sizeof(int), 0644, NULL, ++ &proc_dointvec}, ++ {0} ++}; ++ctl_table mipv6_table[] = { ++ {NET_IPV6_MOBILITY, "mobility", NULL, 0, 0555, mipv6_mobility_table}, ++ {0} ++}; ++ ++static struct ctl_table_header *mipv6_sysctl_header; ++static struct ctl_table mipv6_net_table[]; ++static struct ctl_table mipv6_root_table[]; ++ ++ctl_table mipv6_net_table[] = { ++ {NET_IPV6, "ipv6", NULL, 0, 0555, mipv6_table}, ++ {0} ++}; ++ ++ctl_table mipv6_root_table[] = { ++ {CTL_NET, "net", NULL, 0, 0555, mipv6_net_table}, ++ {0} ++}; ++#endif /* CONFIG_SYSCTL */ ++ ++extern void mipv6_rr_init(void); ++ ++/* Initialize the module */ ++static int __init mip6_init(void) ++{ ++ int err = 0; ++ ++ printk(KERN_INFO "MIPL Mobile IPv6 for Linux Correspondent Node %s (%s)\n", ++ MIPLVERSION, MIPV6VERSION); ++ ++#ifdef CONFIG_IPV6_MOBILITY_DEBUG ++ printk(KERN_INFO "Debug-level: %d\n", mipv6_debug); ++#endif ++ ++ if ((err = mipv6_bcache_init(MIPV6_BCACHE_SIZE)) < 0) ++ goto bcache_fail; ++ ++ if ((err = mipv6_icmpv6_init()) < 0) ++ goto icmp_fail; ++ ++ if ((err = mipv6_stats_init()) < 0) ++ goto stats_fail; ++ mipv6_rr_init(); ++ ++#ifdef CONFIG_SYSCTL ++ mipv6_sysctl_header = register_sysctl_table(mipv6_root_table, 0); ++#endif ++ ++ if ((err = mipv6_mh_common_init()) < 0) ++ goto mh_fail; ++ ++ MIPV6_SETCALL(mipv6_modify_txoptions, mipv6_modify_txoptions); ++ ++ MIPV6_SETCALL(mipv6_handle_homeaddr, mipv6_handle_homeaddr); ++ MIPV6_SETCALL(mipv6_icmp_swap_addrs, mipv6_icmp_swap_addrs); ++ ++ return 0; ++ ++mh_fail: ++#ifdef CONFIG_SYSCTL ++ unregister_sysctl_table(mipv6_sysctl_header); ++#endif ++ mipv6_stats_exit(); ++stats_fail: ++ mipv6_icmpv6_exit(); ++icmp_fail: ++ mipv6_bcache_exit(); ++bcache_fail: ++ return err; ++} ++module_init(mip6_init); ++ ++#ifdef MODULE ++/* Cleanup module */ ++static void __exit mip6_exit(void) ++{ ++ printk(KERN_INFO "mip6_base.o exiting.\n"); ++#ifdef CONFIG_SYSCTL ++ unregister_sysctl_table(mipv6_sysctl_header); ++#endif ++ ++ /* Invalidate all custom kernel hooks. No need to do this ++ separately for all hooks. */ ++ mipv6_invalidate_calls(); ++ ++ mipv6_mh_common_exit(); ++ mipv6_stats_exit(); ++ mipv6_icmpv6_exit(); ++ mipv6_bcache_exit(); ++} ++module_exit(mip6_exit); ++#endif /* MODULE */ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/module_ha.c +@@ -0,0 +1,264 @@ ++/* ++ * Mobile IPv6 Home Agent Module ++ * ++ * Authors: ++ * Sami Kivisaari ++ * Antti Tuominen ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++ ++#ifdef CONFIG_SYSCTL ++#include ++#endif /* CONFIG_SYSCTL */ ++ ++#include ++#include ++ ++#include "mobhdr.h" ++#include "tunnel_ha.h" ++#include "ha.h" ++#include "halist.h" ++#include "mipv6_icmp.h" ++//#include "prefix.h" ++#include "bcache.h" ++#include "debug.h" ++ ++int mipv6_use_auth = 0; ++ ++#if defined(MODULE) && LINUX_VERSION_CODE > 0x20115 ++MODULE_AUTHOR("MIPL Team"); ++MODULE_DESCRIPTION("Mobile IPv6 Home Agent"); ++MODULE_LICENSE("GPL"); ++#endif ++ ++#include "config.h" ++ ++#define MIPV6_HALIST_SIZE 128 ++struct ha_info_opt { ++ u8 type; ++ u8 len; ++ u16 res; ++ u16 pref; ++ u16 ltime; ++}; ++/* ++ * Called from ndisc.c's router_discovery. ++ */ ++static int mipv6_ha_ra_rcv(struct sk_buff *skb, struct ndisc_options *ndopts) ++{ ++ unsigned int ha_info_pref = 0, ha_info_lifetime; ++ int ifi = ((struct inet6_skb_parm *)skb->cb)->iif; ++ struct ra_msg *ra = (struct ra_msg *) skb->h.raw; ++ struct in6_addr *saddr = &skb->nh.ipv6h->saddr; ++ struct in6_addr ll_addr; ++ struct hal { ++ struct in6_addr prefix; ++ int plen; ++ struct hal *next; ++ }; ++ ++ DEBUG_FUNC(); ++ ++ ha_info_lifetime = ntohs(ra->icmph.icmp6_rt_lifetime); ++ ipv6_addr_copy(&ll_addr, saddr); ++ ++ if (ndopts->nd_opts_hai) { ++ struct ha_info_opt *hai = (struct ha_info_opt *)ndopts->nd_opts_hai; ++ ha_info_pref = ntohs(hai->pref); ++ ha_info_lifetime = ntohs(hai->ltime); ++ DEBUG(DBG_DATADUMP, ++ "received home agent info with preference : %d and lifetime : %d", ++ ha_info_pref, ha_info_lifetime); ++ } ++ if (ndopts->nd_opts_pi) { ++ struct nd_opt_hdr *p; ++ for (p = ndopts->nd_opts_pi; ++ p; ++ p = ndisc_next_option(p, ndopts->nd_opts_pi_end)) { ++ struct prefix_info *pinfo; ++ ++ pinfo = (struct prefix_info *) p; ++ ++ if (pinfo->router_address) { ++ DEBUG(DBG_DATADUMP, "Adding router address to " ++ "ha queue \n"); ++ /* If RA has H bit set and Prefix Info ++ * Option R bit set, queue this ++ * address to be added to Home Agents ++ * List. ++ */ ++ if (ipv6_addr_type(&pinfo->prefix) & ++ IPV6_ADDR_LINKLOCAL) ++ continue; ++ if (!ra->icmph.icmp6_home_agent || !ha_info_lifetime) { ++ mipv6_halist_delete(&pinfo->prefix); ++ continue; ++ } else { ++ ++ mipv6_halist_add(ifi, &pinfo->prefix, ++ pinfo->prefix_len, &ll_addr, ++ ha_info_pref, ha_info_lifetime); ++ } ++ ++ } ++ ++ } ++ } ++ return MIPV6_ADD_RTR; ++} ++ ++/********************************************************************** ++ * ++ * MIPv6 Module Init / Cleanup ++ * ++ **********************************************************************/ ++ ++#ifdef CONFIG_SYSCTL ++/* Sysctl table */ ++extern int ++mipv6_max_tnls_sysctl(ctl_table *, int, struct file *, void *, size_t *); ++ ++extern int ++mipv6_min_tnls_sysctl(ctl_table *, int, struct file *, void *, size_t *); ++ ++int max_adv = ~(u16)0; ++int min_zero = 0; ++ctl_table mipv6_mobility_table[] = { ++ {NET_IPV6_MOBILITY_BINDING_REFRESH, "binding_refresh_advice", ++ &mip6node_cnf.binding_refresh_advice, sizeof(int), 0644, NULL, ++ &proc_dointvec_minmax, &sysctl_intvec, 0, &min_zero, &max_adv}, ++ ++ {NET_IPV6_MOBILITY_MAX_TNLS, "max_tnls", &mipv6_max_tnls, sizeof(int), ++ 0644, NULL, &mipv6_max_tnls_sysctl}, ++ {NET_IPV6_MOBILITY_MIN_TNLS, "min_tnls", &mipv6_min_tnls, sizeof(int), ++ 0644, NULL, &mipv6_min_tnls_sysctl}, ++ {0} ++}; ++ctl_table mipv6_table[] = { ++ {NET_IPV6_MOBILITY, "mobility", NULL, 0, 0555, mipv6_mobility_table}, ++ {0} ++}; ++ ++static struct ctl_table_header *mipv6_sysctl_header; ++static struct ctl_table mipv6_net_table[]; ++static struct ctl_table mipv6_root_table[]; ++ ++ctl_table mipv6_net_table[] = { ++ {NET_IPV6, "ipv6", NULL, 0, 0555, mipv6_table}, ++ {0} ++}; ++ ++ctl_table mipv6_root_table[] = { ++ {CTL_NET, "net", NULL, 0, 0555, mipv6_net_table}, ++ {0} ++}; ++#endif /* CONFIG_SYSCTL */ ++ ++extern void mipv6_check_dad(struct in6_addr *haddr); ++extern void mipv6_dad_init(void); ++extern void mipv6_dad_exit(void); ++extern int mipv6_forward(struct sk_buff *); ++ ++/* Initialize the module */ ++static int __init mip6_ha_init(void) ++{ ++ int err = 0; ++ ++ printk(KERN_INFO "MIPL Mobile IPv6 for Linux Home Agent %s (%s)\n", ++ MIPLVERSION, MIPV6VERSION); ++ mip6node_cnf.capabilities = CAP_CN | CAP_HA; ++ ++ mip6_fn.icmpv6_dhaad_rep_rcv = mipv6_icmpv6_no_rcv; ++ mip6_fn.icmpv6_dhaad_req_rcv = mipv6_icmpv6_rcv_dhaad_req; ++ mip6_fn.icmpv6_pfxadv_rcv = mipv6_icmpv6_no_rcv; ++ mip6_fn.icmpv6_pfxsol_rcv = mipv6_icmpv6_no_rcv; ++ mip6_fn.icmpv6_paramprob_rcv = mipv6_icmpv6_no_rcv; ++ ++#ifdef CONFIG_IPV6_MOBILITY_DEBUG ++ printk(KERN_INFO "Debug-level: %d\n", mipv6_debug); ++#endif ++ ++#ifdef CONFIG_SYSCTL ++ mipv6_sysctl_header = register_sysctl_table(mipv6_root_table, 0); ++#endif ++ mipv6_initialize_tunnel(); ++ ++ if ((err = mipv6_ha_init()) < 0) ++ goto ha_fail; ++ ++ MIPV6_SETCALL(mipv6_ra_rcv, mipv6_ha_ra_rcv); ++ MIPV6_SETCALL(mipv6_forward, mipv6_forward); ++ mipv6_dad_init(); ++ MIPV6_SETCALL(mipv6_check_dad, mipv6_check_dad); ++ ++ if ((err = mipv6_halist_init(MIPV6_HALIST_SIZE)) < 0) ++ goto halist_fail; ++ ++// mipv6_initialize_pfx_icmpv6(); ++ ++ return 0; ++ ++halist_fail: ++ mipv6_dad_exit(); ++ mipv6_ha_exit(); ++ha_fail: ++ mipv6_shutdown_tunnel(); ++ ++ mip6_fn.icmpv6_dhaad_rep_rcv = NULL; ++ mip6_fn.icmpv6_dhaad_req_rcv = NULL; ++ mip6_fn.icmpv6_pfxadv_rcv = NULL; ++ mip6_fn.icmpv6_pfxsol_rcv = NULL; ++ mip6_fn.icmpv6_paramprob_rcv = NULL; ++ ++ MIPV6_RESETCALL(mipv6_ra_rcv); ++ MIPV6_RESETCALL(mipv6_forward); ++ MIPV6_RESETCALL(mipv6_check_dad); ++ ++#ifdef CONFIG_SYSCTL ++ unregister_sysctl_table(mipv6_sysctl_header); ++#endif ++ return err; ++} ++module_init(mip6_ha_init); ++ ++#ifdef MODULE ++/* Cleanup module */ ++static void __exit mip6_ha_exit(void) ++{ ++ printk(KERN_INFO "mip6_ha.o exiting.\n"); ++ mip6node_cnf.capabilities &= ~(int)CAP_HA; ++ ++ mipv6_bcache_cleanup(HOME_REGISTRATION); ++ ++ MIPV6_RESETCALL(mipv6_ra_rcv); ++ MIPV6_RESETCALL(mipv6_forward); ++ MIPV6_RESETCALL(mipv6_check_dad); ++ ++ mipv6_halist_exit(); ++// mipv6_shutdown_pfx_icmpv6(); ++ ++ mip6_fn.icmpv6_dhaad_rep_rcv = NULL; ++ mip6_fn.icmpv6_dhaad_req_rcv = NULL; ++ mip6_fn.icmpv6_pfxadv_rcv = NULL; ++ mip6_fn.icmpv6_pfxsol_rcv = NULL; ++ mip6_fn.icmpv6_paramprob_rcv = NULL; ++ ++ mipv6_dad_exit(); ++ mipv6_ha_exit(); ++ mipv6_shutdown_tunnel(); ++#ifdef CONFIG_SYSCTL ++ unregister_sysctl_table(mipv6_sysctl_header); ++#endif ++} ++module_exit(mip6_ha_exit); ++#endif /* MODULE */ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/module_mn.c +@@ -0,0 +1,188 @@ ++/* ++ * Mobile IPv6 Mobile Node Module ++ * ++ * Authors: ++ * Sami Kivisaari ++ * Antti Tuominen ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++ ++#ifdef CONFIG_SYSCTL ++#include ++#endif /* CONFIG_SYSCTL */ ++ ++#include ++ ++extern int mipv6_debug; ++int mipv6_use_auth = 0; ++ ++#if defined(MODULE) && LINUX_VERSION_CODE > 0x20115 ++MODULE_AUTHOR("MIPL Team"); ++MODULE_DESCRIPTION("Mobile IPv6 Mobile Node"); ++MODULE_LICENSE("GPL"); ++MODULE_PARM(mipv6_debug, "i"); ++#endif ++ ++#include "config.h" ++ ++#include "mobhdr.h" ++#include "mn.h" ++#include "mipv6_icmp.h" ++//#include "prefix.h" ++ ++/* TODO: These will go as soon as we get rid of the last two ioctls */ ++extern int mipv6_ioctl_mn_init(void); ++extern void mipv6_ioctl_mn_exit(void); ++ ++/********************************************************************** ++ * ++ * MIPv6 Module Init / Cleanup ++ * ++ **********************************************************************/ ++ ++#ifdef CONFIG_SYSCTL ++/* Sysctl table */ ++ ++extern int max_rtr_reach_time; ++extern int eager_cell_switching; ++ ++static int max_reach = 1000; ++static int min_reach = 1; ++static int max_one = 1; ++static int min_zero = 0; ++ ++extern int ++mipv6_mdetect_mech_sysctl(ctl_table *, int, struct file *, void *, size_t *); ++ ++extern int ++mipv6_router_reach_sysctl(ctl_table *, int, struct file *, void *, size_t *); ++ ++ctl_table mipv6_mobility_table[] = { ++ {NET_IPV6_MOBILITY_BU_F_LLADDR, "bu_flag_lladdr", ++ &mip6node_cnf.bu_lladdr, sizeof(int), 0644, NULL, ++ &proc_dointvec_minmax, &sysctl_intvec, 0, &min_zero, &max_one}, ++ {NET_IPV6_MOBILITY_BU_F_KEYMGM, "bu_flag_keymgm", ++ &mip6node_cnf.bu_keymgm, sizeof(int), 0644, NULL, ++ &proc_dointvec_minmax, &sysctl_intvec, 0, &min_zero, &max_one}, ++ {NET_IPV6_MOBILITY_BU_F_CN_ACK, "bu_flag_cn_ack", ++ &mip6node_cnf.bu_cn_ack, sizeof(int), 0644, NULL, ++ &proc_dointvec_minmax, &sysctl_intvec, 0, &min_zero, &max_one}, ++ ++ {NET_IPV6_MOBILITY_ROUTER_REACH, "max_router_reachable_time", ++ &max_rtr_reach_time, sizeof(int), 0644, NULL, ++ &proc_dointvec_minmax, &sysctl_intvec, 0, &min_reach, &max_reach}, ++ ++ {NET_IPV6_MOBILITY_MDETECT_MECHANISM, "eager_cell_switching", ++ &eager_cell_switching, sizeof(int), 0644, NULL, ++ &proc_dointvec_minmax, &sysctl_intvec, 0, &min_zero, &max_one}, ++ ++ {0} ++}; ++ctl_table mipv6_table[] = { ++ {NET_IPV6_MOBILITY, "mobility", NULL, 0, 0555, mipv6_mobility_table}, ++ {0} ++}; ++ ++static struct ctl_table_header *mipv6_sysctl_header; ++static struct ctl_table mipv6_net_table[]; ++static struct ctl_table mipv6_root_table[]; ++ ++ctl_table mipv6_net_table[] = { ++ {NET_IPV6, "ipv6", NULL, 0, 0555, mipv6_table}, ++ {0} ++}; ++ ++ctl_table mipv6_root_table[] = { ++ {CTL_NET, "net", NULL, 0, 0555, mipv6_net_table}, ++ {0} ++}; ++#endif /* CONFIG_SYSCTL */ ++ ++/* Initialize the module */ ++static int __init mip6_mn_init(void) ++{ ++ int err = 0; ++ ++ printk(KERN_INFO "MIPL Mobile IPv6 for Linux Mobile Node %s (%s)\n", ++ MIPLVERSION, MIPV6VERSION); ++ mip6node_cnf.capabilities = CAP_CN | CAP_MN; ++ ++#ifdef CONFIG_IPV6_MOBILITY_DEBUG ++ printk(KERN_INFO "Debug-level: %d\n", mipv6_debug); ++#endif ++ ++#ifdef CONFIG_SYSCTL ++ mipv6_sysctl_header = register_sysctl_table(mipv6_root_table, 0); ++#endif ++ if ((err = mipv6_mn_init()) < 0) ++ goto mn_fail; ++ ++ mipv6_mh_mn_init(); ++ ++ mip6_fn.icmpv6_dhaad_rep_rcv = mipv6_icmpv6_rcv_dhaad_rep; ++ mip6_fn.icmpv6_dhaad_req_rcv = mipv6_icmpv6_no_rcv; ++ mip6_fn.icmpv6_pfxadv_rcv = mipv6_icmpv6_no_rcv; ++ mip6_fn.icmpv6_pfxsol_rcv = mipv6_icmpv6_no_rcv; ++ mip6_fn.icmpv6_paramprob_rcv = mipv6_icmpv6_rcv_paramprob; ++ ++// mipv6_initialize_pfx_icmpv6(); ++ ++ if ((err = mipv6_ioctl_mn_init()) < 0) ++ goto ioctl_fail; ++ ++ return 0; ++ ++ioctl_fail: ++// mipv6_shutdown_pfx_icmpv6(); ++ ++ mip6_fn.icmpv6_dhaad_rep_rcv = NULL; ++ mip6_fn.icmpv6_dhaad_req_rcv = NULL; ++ mip6_fn.icmpv6_pfxadv_rcv = NULL; ++ mip6_fn.icmpv6_pfxsol_rcv = NULL; ++ mip6_fn.icmpv6_paramprob_rcv = NULL; ++ ++ mipv6_mh_mn_exit(); ++ mipv6_mn_exit(); ++mn_fail: ++#ifdef CONFIG_SYSCTL ++ unregister_sysctl_table(mipv6_sysctl_header); ++#endif ++ return err; ++} ++module_init(mip6_mn_init); ++ ++#ifdef MODULE ++/* Cleanup module */ ++static void __exit mip6_mn_exit(void) ++{ ++ printk(KERN_INFO "mip6_mn.o exiting.\n"); ++ mip6node_cnf.capabilities &= ~(int)CAP_MN; ++ ++ mipv6_ioctl_mn_exit(); ++// mipv6_shutdown_pfx_icmpv6(); ++ ++ mip6_fn.icmpv6_dhaad_rep_rcv = NULL; ++ mip6_fn.icmpv6_dhaad_req_rcv = NULL; ++ mip6_fn.icmpv6_pfxadv_rcv = NULL; ++ mip6_fn.icmpv6_pfxsol_rcv = NULL; ++ mip6_fn.icmpv6_paramprob_rcv = NULL; ++ ++ mipv6_mn_exit(); ++ ++/* common cleanup */ ++#ifdef CONFIG_SYSCTL ++ unregister_sysctl_table(mipv6_sysctl_header); ++#endif ++} ++module_exit(mip6_mn_exit); ++#endif /* MODULE */ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/multiaccess_ctl.c +@@ -0,0 +1,287 @@ ++/* ++ * 2001 (c) Oy L M Ericsson Ab ++ * ++ * Author: NomadicLab / Ericsson Research ++ * ++ * $Id$ ++ * ++ */ ++ ++/* ++ * Vertical hand-off information manager ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "multiaccess_ctl.h" ++#include "debug.h" ++ ++/* ++ * Local variables ++ */ ++static LIST_HEAD(if_list); ++ ++/* Internal interface information list */ ++struct ma_if_info { ++ struct list_head list; ++ int interface_id; ++ int preference; ++ __u8 status; ++}; ++ ++/** ++ * ma_ctl_get_preference - get preference value for interface ++ * @ifi: interface index ++ * ++ * Returns integer value preference for given interface. ++ **/ ++int ma_ctl_get_preference(int ifi) ++{ ++ struct list_head *lh; ++ struct ma_if_info *info; ++ int pref = 0; ++ ++ list_for_each(lh, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ if (info->interface_id == ifi) { ++ pref = info->preference; ++ return pref; ++ } ++ } ++ return -1; ++} ++/** ++ * ma_ctl_get_preference - get preference value for interface ++ * @ifi: interface index ++ * ++ * Returns integer value interface index for interface with highest preference. ++ **/ ++int ma_ctl_get_preferred_if(void) ++{ ++ struct list_head *lh; ++ struct ma_if_info *info, *pref_if = NULL; ++ ++ list_for_each(lh, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ if (!pref_if || (info->preference > pref_if->preference)) { ++ pref_if = info; ++ } ++ } ++ if (pref_if) return pref_if->interface_id; ++ return 0; ++} ++/** ++ * ma_ctl_set_preference - set preference for interface ++ * @arg: ioctl args ++ * ++ * Sets preference of an existing interface (called by ioctl). ++ **/ ++void ma_ctl_set_preference(unsigned long arg) ++{ ++ struct list_head *lh; ++ struct ma_if_info *info; ++ struct ma_if_uinfo uinfo; ++ ++ memset(&uinfo, 0, sizeof(struct ma_if_uinfo)); ++ if (copy_from_user(&uinfo, (struct ma_if_uinfo *)arg, ++ sizeof(struct ma_if_uinfo)) < 0) { ++ DEBUG(DBG_WARNING, "copy_from_user failed"); ++ return; ++ } ++ ++ /* check if the interface exists */ ++ list_for_each(lh, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ if (info->interface_id == uinfo.interface_id) { ++ info->preference = uinfo.preference; ++ return; ++ } ++ } ++} ++ ++/** ++ * ma_ctl_add_iface - add new interface to list ++ * @if_index: interface index ++ * ++ * Adds new interface entry to preference list. Preference is set to ++ * the same value as @if_index. Entry @status is set to ++ * %MA_IFACE_NOT_USED. ++ **/ ++void ma_ctl_add_iface(int if_index) ++{ ++ struct list_head *lh; ++ struct ma_if_info *info; ++ ++ DEBUG_FUNC(); ++ ++ /* check if the interface already exists */ ++ list_for_each(lh, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ if (info->interface_id == if_index) { ++ info->status = MA_IFACE_NOT_USED; ++ info->preference = if_index; ++ return; ++ } ++ } ++ ++ info = kmalloc(sizeof(struct ma_if_info), GFP_ATOMIC); ++ if (info == NULL) { ++ DEBUG(DBG_ERROR, "Out of memory"); ++ return; ++ } ++ memset(info, 0, sizeof(struct ma_if_info)); ++ info->interface_id = if_index; ++ info->preference = if_index; ++ info->status = MA_IFACE_NOT_USED; ++ list_add(&info->list, &if_list); ++} ++ ++/** ++ * ma_ctl_del_iface - remove entry from the list ++ * @if_index: interface index ++ * ++ * Removes entry for interface @if_index from preference list. ++ **/ ++int ma_ctl_del_iface(int if_index) ++{ ++ struct list_head *lh, *next; ++ struct ma_if_info *info; ++ ++ DEBUG_FUNC(); ++ ++ /* if the iface exists, change availability to 0 */ ++ list_for_each_safe(lh, next, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ if (info->interface_id == if_index) { ++ list_del(&info->list); ++ kfree(info); ++ return 0; ++ } ++ } ++ ++ return -1; ++} ++ ++/** ++ * ma_ctl_upd_iface - update entry (and list) ++ * @if_index: interface to update ++ * @status: new status for interface ++ * @change_if_index: new interface ++ * ++ * Updates @if_index entry on preference list. Entry status is set to ++ * @status. If new @status is %MA_IFACE_CURRENT, updates list to have ++ * only one current device. If @status is %MA_IFACE_NOT_PRESENT, ++ * entry is deleted and further if entry had %MA_IFACE_CURRENT set, ++ * new current device is looked up and returned in @change_if_index. ++ * New preferred interface is also returned if current device changes ++ * to %MA_IFACE_NOT_USED. Returns 0 on success, otherwise negative. ++ **/ ++int ma_ctl_upd_iface(int if_index, int status, int *change_if_index) ++{ ++ struct list_head *lh, *tmp; ++ struct ma_if_info *info, *pref = NULL; ++ int found = 0; ++ ++ DEBUG_FUNC(); ++ ++ *change_if_index = 0; ++ ++ /* check if the interface exists */ ++ list_for_each_safe(lh, tmp, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ if (status == MA_IFACE_NOT_PRESENT) { ++ if (info->interface_id == if_index) { ++ list_del_init(&info->list); ++ kfree(info); ++ found = 1; ++ break; ++ } ++ } else if (status == MA_IFACE_CURRENT) { ++ if (info->interface_id == if_index) { ++ info->status |= MA_IFACE_CURRENT; ++ found = 1; ++ } else { ++ info->status |= MA_IFACE_NOT_USED; ++ } ++ } else if (status == MA_IFACE_NOT_USED) { ++ if (info->interface_id == if_index) { ++ if (info->status | MA_IFACE_CURRENT) { ++ found = 1; ++ } ++ info->status &= !MA_IFACE_CURRENT; ++ info->status |= MA_IFACE_NOT_USED; ++ info->status &= !MA_IFACE_HAS_ROUTER; ++ } ++ break; ++ } else if (status == MA_IFACE_HAS_ROUTER) { ++ if (info->interface_id == if_index) { ++ info->status |= MA_IFACE_HAS_ROUTER; ++ } ++ return 0; ++ } ++ } ++ ++ if (status & (MA_IFACE_NOT_USED|MA_IFACE_NOT_PRESENT) && found) { ++ /* select new interface */ ++ list_for_each(lh, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ if (pref == NULL || ((info->preference > pref->preference) && ++ info->status & MA_IFACE_HAS_ROUTER)) ++ pref = info; ++ } ++ if (pref) { ++ *change_if_index = pref->interface_id; ++ pref->status |= MA_IFACE_CURRENT; ++ } else { ++ *change_if_index = -1; ++ } ++ return 0; ++ } ++ ++ if (found) return 0; ++ ++ return -1; ++} ++ ++static int if_proc_info(char *buffer, char **start, off_t offset, ++ int length) ++{ ++ struct list_head *lh; ++ struct ma_if_info *info; ++ int len = 0; ++ ++ list_for_each(lh, &if_list) { ++ info = list_entry(lh, struct ma_if_info, list); ++ len += sprintf(buffer + len, "%02d %010d %1d %1d\n", ++ info->interface_id, info->preference, ++ !!(info->status & MA_IFACE_HAS_ROUTER), ++ !!(info->status & MA_IFACE_CURRENT)); ++ } ++ ++ *start = buffer + offset; ++ ++ len -= offset; ++ ++ if (len > length) len = length; ++ ++ return len; ++ ++} ++ ++void ma_ctl_init(void) ++{ ++ proc_net_create("mip6_iface", 0, if_proc_info); ++} ++ ++void ma_ctl_clean(void) ++{ ++ proc_net_remove("mip6_iface"); ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/multiaccess_ctl.h +@@ -0,0 +1,77 @@ ++/* ++ * 2001 (c) Oy L M Ericsson Ab ++ * ++ * Author: NomadicLab / Ericsson Research ++ * ++ * $Id$ ++ * ++ */ ++ ++#ifndef _MULTIACCESS_CTL_H ++#define _MULTIACCESS_CTL_H ++ ++/* status */ ++#define MA_IFACE_NOT_PRESENT 0x01 ++#define MA_IFACE_NOT_USED 0x02 ++#define MA_IFACE_HAS_ROUTER 0x04 ++#define MA_IFACE_CURRENT 0x10 ++ ++struct ma_if_uinfo { ++ int interface_id; ++ int preference; ++ __u8 status; ++}; ++/* ++ * @ma_ctl_get_preferred_id: returns most preferred interface id ++ */ ++int ma_ctl_get_preferred_if(void); ++ ++/* @ma_ctl_get_preference: returns preference for an interface ++ * @name: name of the interface (dev->name) ++ */ ++int ma_ctl_get_preference(int ifi); ++ ++/* ++ * Public function: ma_ctl_set_preference ++ * Description: Set preference of an existing interface (called by ioctl) ++ * Returns: ++ */ ++void ma_ctl_set_preference(unsigned long); ++ ++/* ++ * Public function: ma_ctl_add_iface ++ * Description: Inform control module to insert a new interface ++ * Returns: 0 if success, any other number means an error ++ */ ++void ma_ctl_add_iface(int); ++ ++/* ++ * Public function: ma_ctl_del_iface ++ * Description: Inform control module to remove an obsolete interface ++ * Returns: 0 if success, any other number means an error ++ */ ++int ma_ctl_del_iface(int); ++ ++/* ++ * Public function: ma_ctl_upd_iface ++ * Description: Inform control module of status change. ++ * Returns: 0 if success, any other number means an error ++ */ ++int ma_ctl_upd_iface(int, int, int *); ++ ++/* ++ * Public function: ma_ctl_init ++ * Description: XXX ++ * Returns: XXX ++ */ ++void ma_ctl_init(void); ++ ++/* ++ * Public function: ma_ctl_clean ++ * Description: XXX ++ * Returns: - ++ */ ++void ma_ctl_clean(void); ++ ++ ++#endif +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/ndisc_ha.c +@@ -0,0 +1,596 @@ ++/* ++ * Mobile IPv6 Duplicate Address Detection Functions ++ * ++ * Authors: ++ * Krishna Kumar ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "debug.h" ++#include "bcache.h" ++#include "ha.h" /* mipv6_generate_ll_addr */ ++ ++/* ++ * Binding Updates from MN are cached in this structure till DAD is performed. ++ * This structure is used to retrieve a pending Binding Update for the HA to ++ * reply to after performing DAD. The first cell is different from the rest as ++ * follows : ++ * 1. The first cell is used to chain the remaining cells. ++ * 2. The timeout of the first cell is used to delete expired entries ++ * in the list of cells, while the timeout of the other cells are ++ * used for timing out a NS request so as to reply to a BU. ++ * 3. The only elements of the first cell that are used are : ++ * next, prev, and callback_timer. ++ * ++ * TODO : Don't we need to do pneigh_lookup on the Link Local address ? ++ */ ++struct mipv6_dad_cell { ++ /* Information needed for DAD management */ ++ struct mipv6_dad_cell *next; /* Next element on the DAD list */ ++ struct mipv6_dad_cell *prev; /* Prev element on the DAD list */ ++ __u16 probes; /* Number of times to probe for addr */ ++ __u16 flags; /* Entry flags - see below */ ++ struct timer_list callback_timer; /* timeout for entry */ ++ ++ /* Information needed for performing DAD */ ++ struct inet6_ifaddr *ifp; ++ int ifindex; ++ struct in6_addr daddr; ++ struct in6_addr haddr; /* home address */ ++ struct in6_addr ll_haddr; /* Link Local value of haddr */ ++ struct in6_addr coa; ++ struct in6_addr rep_coa; ++ __u32 ba_lifetime; ++ __u16 sequence; ++ __u8 bu_flags; ++}; ++ ++/* Values for the 'flags' field in the mipv6_dad_cell */ ++#define DAD_INIT_ENTRY 0 ++#define DAD_DUPLICATE_ADDRESS 1 ++#define DAD_UNIQUE_ADDRESS 2 ++ ++/* Head of the pending DAD list */ ++static struct mipv6_dad_cell dad_cell_head; ++ ++/* Lock to access the pending DAD list */ ++static rwlock_t dad_lock = RW_LOCK_UNLOCKED; ++ ++/* Timer routine which deletes 'expired' entries in the DAD list */ ++static void mipv6_dad_delete_old_entries(unsigned long unused) ++{ ++ struct mipv6_dad_cell *curr, *next; ++ unsigned long next_time = 0; ++ ++ write_lock(&dad_lock); ++ curr = dad_cell_head.next; ++ while (curr != &dad_cell_head) { ++ next = curr->next; ++ if (curr->flags != DAD_INIT_ENTRY) { ++ if (curr->callback_timer.expires <= jiffies) { ++ /* Entry has expired, free it up. */ ++ curr->next->prev = curr->prev; ++ curr->prev->next = curr->next; ++ in6_ifa_put(curr->ifp); ++ kfree(curr); ++ } else if (next_time < ++ curr->callback_timer.expires) { ++ next_time = curr->callback_timer.expires; ++ } ++ } ++ curr = next; ++ } ++ write_unlock(&dad_lock); ++ if (next_time) { ++ /* ++ * Start another timer if more cells need to be removed at ++ * a later stage. ++ */ ++ dad_cell_head.callback_timer.expires = next_time; ++ add_timer(&dad_cell_head.callback_timer); ++ } ++} ++ ++/* ++ * Queue a timeout routine to clean up 'expired' DAD entries. ++ */ ++static void mipv6_start_dad_head_timer(struct mipv6_dad_cell *cell) ++{ ++ unsigned long expire = jiffies + ++ cell->ifp->idev->nd_parms->retrans_time * 10; ++ ++ if (!timer_pending(&dad_cell_head.callback_timer) || ++ expire < dad_cell_head.callback_timer.expires) { ++ /* ++ * Add timer if none pending, or mod the timer if new ++ * cell needs to be expired before existing timer runs. ++ * ++ * We let the cell remain as long as possible, so that ++ * new BU's as part of retransmissions don't have to go ++ * through DAD before replying. ++ */ ++ dad_cell_head.callback_timer.expires = expire; ++ ++ /* ++ * Keep the cell around for atleast some time to handle ++ * retransmissions or BU's due to fast MN movement. This ++ * is needed otherwise a previous timeout can delete all ++ * expired entries including this new one. ++ */ ++ cell->callback_timer.expires = jiffies + ++ cell->ifp->idev->nd_parms->retrans_time * 5; ++ if (!timer_pending(&dad_cell_head.callback_timer)) { ++ add_timer(&dad_cell_head.callback_timer); ++ } else { ++ mod_timer(&dad_cell_head.callback_timer, expire); ++ } ++ } ++} ++ ++ ++/* Join solicited node MC address */ ++static inline void mipv6_join_sol_mc_addr(struct in6_addr *addr, ++ struct net_device *dev) ++{ ++ struct in6_addr maddr; ++ ++ /* Join solicited node MC address */ ++ addrconf_addr_solict_mult(addr, &maddr); ++ ipv6_dev_mc_inc(dev, &maddr); ++} ++ ++/* Leave solicited node MC address */ ++static inline void mipv6_leave_sol_mc_addr(struct in6_addr *addr, ++ struct net_device *dev) ++{ ++ struct in6_addr maddr; ++ ++ addrconf_addr_solict_mult(addr, &maddr); ++ ipv6_dev_mc_dec(dev, &maddr); ++} ++ ++/* Send a NS */ ++static inline void mipv6_dad_send_ns(struct inet6_ifaddr *ifp, ++ struct in6_addr *haddr) ++{ ++ struct in6_addr unspec; ++ struct in6_addr mcaddr; ++ ++ ipv6_addr_set(&unspec, 0, 0, 0, 0); ++ addrconf_addr_solict_mult(haddr, &mcaddr); ++ ++ /* addr is 'unspec' since we treat this address as transient */ ++ ndisc_send_ns(ifp->idev->dev, NULL, haddr, &mcaddr, &unspec); ++} ++ ++/* ++ * Search for a home address in the list of pending DAD's. Called from ++ * Neighbor Advertisement ++ * Return values : ++ * -1 : No DAD entry found for this advertisement, or entry already ++ * finished processing. ++ * 0 : Entry found waiting for DAD to finish. ++ */ ++static int dad_search_haddr(struct in6_addr *ll_haddr, ++ struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u16 * seq, struct inet6_ifaddr **ifp) ++{ ++ struct mipv6_dad_cell *cell; ++ ++ read_lock(&dad_lock); ++ cell = dad_cell_head.next; ++ while (cell != &dad_cell_head && ++ ipv6_addr_cmp(&cell->ll_haddr, ll_haddr) && ++ ipv6_addr_cmp(&cell->haddr, ll_haddr)) { ++ cell = cell->next; ++ } ++ if (cell == &dad_cell_head || cell->flags != DAD_INIT_ENTRY) { ++ /* Not found element, or element already finished processing */ ++ if (cell != &dad_cell_head) { ++ /* ++ * Set the state to DUPLICATE, even if it was UNIQUE ++ * earlier. It is not needed to setup timer via ++ * mipv6_start_dad_head_timer since this must have ++ * already been done. ++ */ ++ cell->flags = DAD_DUPLICATE_ADDRESS; ++ } ++ read_unlock(&dad_lock); ++ return -1; ++ } ++ ++ /* ++ * The NA found an unprocessed entry in the DAD list. Expire this ++ * entry since another node advertised this address. Caller should ++ * reject BU (DAD failed). ++ */ ++ ipv6_addr_copy(daddr, &cell->daddr); ++ ipv6_addr_copy(haddr, &cell->haddr); ++ ipv6_addr_copy(coa, &cell->coa); ++ ipv6_addr_copy(rep_coa, &cell->rep_coa); ++ *seq = cell->sequence; ++ *ifp = cell->ifp; ++ ++ if (del_timer(&cell->callback_timer) == 0) { ++ /* Timer already deleted, race with Timeout Handler */ ++ /* No action needed */ ++ } ++ ++ cell->flags = DAD_DUPLICATE_ADDRESS; ++ ++ /* Now leave this address to avoid future processing of NA's */ ++ mipv6_leave_sol_mc_addr(&cell->ll_haddr, cell->ifp->idev->dev); ++ /* Leave also global address, if link local address was in use */ ++ if (ipv6_addr_cmp(&cell->ll_haddr, &cell->haddr)) ++ mipv6_leave_sol_mc_addr(&cell->haddr, cell->ifp->idev->dev); ++ /* Start dad_head timer to remove this entry */ ++ mipv6_start_dad_head_timer(cell); ++ ++ read_unlock(&dad_lock); ++ ++ return 0; ++} ++ ++/* ENTRY routine called via Neighbor Advertisement */ ++void mipv6_check_dad(struct in6_addr *ll_haddr) ++{ ++ struct in6_addr daddr, haddr, coa, rep_coa; ++ struct inet6_ifaddr *ifp; ++ __u16 seq; ++ ++ if (dad_search_haddr(ll_haddr, &daddr, &haddr, &coa, &rep_coa, &seq, ++ &ifp) < 0) { ++ /* ++ * Didn't find entry, or no action needed (the action has ++ * already been performed). ++ */ ++ return; ++ } ++ ++ /* ++ * A DAD cell was present, meaning that there is a pending BU ++ * request for 'haddr' - reject the BU. ++ */ ++ mipv6_bu_finish(ifp, 0, DUPLICATE_ADDR_DETECT_FAIL, ++ &daddr, &haddr, &coa, &rep_coa, 0, seq, 0, NULL); ++ return; ++} ++ ++/* ++ * Check if the passed 'cell' is in the list of pending DAD's. Called from ++ * the Timeout Handler. ++ * ++ * Assumes that the caller is holding the dad_lock in reader mode. ++ */ ++static int dad_search_cell(struct mipv6_dad_cell *cell) ++{ ++ struct mipv6_dad_cell *tmp; ++ ++ tmp = dad_cell_head.next; ++ while (tmp != &dad_cell_head && tmp != cell) { ++ tmp = tmp->next; ++ } ++ if (tmp == cell) { ++ if (cell->flags == DAD_INIT_ENTRY) { ++ /* Found valid entry */ ++ if (--cell->probes == 0) { ++ /* ++ * Retransmission's are over - return success. ++ */ ++ cell->flags = DAD_UNIQUE_ADDRESS; ++ ++ /* ++ * Leave this address to avoid future ++ * processing of NA's. ++ */ ++ mipv6_leave_sol_mc_addr(&cell->ll_haddr, ++ cell->ifp->idev-> ++ dev); ++ if (ipv6_addr_cmp(&cell->ll_haddr, &cell->haddr)) ++ mipv6_leave_sol_mc_addr(&cell->haddr, ++ cell->ifp->idev->dev); ++ /* start timeout to delete this cell. */ ++ mipv6_start_dad_head_timer(cell); ++ return 0; ++ } ++ /* ++ * Retransmission not finished, send another NS and ++ * return failure. ++ */ ++ mipv6_dad_send_ns(cell->ifp, &cell->ll_haddr); ++ if (ipv6_addr_cmp(&cell->ll_haddr, &cell->haddr)) ++ mipv6_leave_sol_mc_addr(&cell->haddr, ++ cell->ifp->idev->dev); ++ cell->callback_timer.expires = jiffies + ++ cell->ifp->idev->nd_parms->retrans_time; ++ add_timer(&cell->callback_timer); ++ } else { ++ /* ++ * This means that an NA was received before the ++ * timeout and when the state changed from ++ * DAD_INIT_ENTRY, the BU got failed as a result. ++ * There is nothing to be done. ++ */ ++ } ++ } ++ return -1; ++} ++ ++/* ENTRY routine called via Timeout */ ++static void mipv6_dad_timeout(unsigned long arg) ++{ ++ __u8 ba_status = SUCCESS; ++ struct in6_addr daddr; ++ struct in6_addr haddr; ++ struct in6_addr coa; ++ struct in6_addr rep_coa; ++ struct inet6_ifaddr *ifp; ++ int ifindex; ++ __u32 ba_lifetime; ++ __u16 sequence; ++ __u8 flags; ++ struct mipv6_dad_cell *cell = (struct mipv6_dad_cell *) arg; ++ ++ /* ++ * If entry is not in the list, we have already sent BU Failure ++ * after getting a NA. ++ */ ++ read_lock(&dad_lock); ++ if (dad_search_cell(cell) < 0) { ++ /* ++ * 'cell' is no longer valid (may not be in the list or ++ * is already processed, due to NA processing), or NS ++ * retransmissions are not yet over. ++ */ ++ read_unlock(&dad_lock); ++ return; ++ } ++ ++ /* This is the final Timeout. Send Bind Ack Success */ ++ ++ ifp = cell->ifp; ++ ifindex = cell->ifindex; ++ ba_lifetime = cell->ba_lifetime; ++ sequence = cell->sequence; ++ flags = cell->bu_flags; ++ ++ ipv6_addr_copy(&daddr, &cell->daddr); ++ ipv6_addr_copy(&haddr, &cell->haddr); ++ ipv6_addr_copy(&coa, &cell->coa); ++ ipv6_addr_copy(&rep_coa, &cell->rep_coa); ++ read_unlock(&dad_lock); ++ ++ /* Send BU Acknowledgement Success */ ++ mipv6_bu_finish(ifp, ifindex, ba_status, ++ &daddr, &haddr, &coa, &rep_coa, ++ ba_lifetime, sequence, flags, NULL); ++ return; ++} ++ ++/* ++ * Check if original home address exists in our DAD pending list, if so return ++ * the cell. ++ * ++ * Assumes that the caller is holding the dad_lock in writer mode. ++ */ ++static struct mipv6_dad_cell *mipv6_dad_get_cell(struct in6_addr *haddr) ++{ ++ struct mipv6_dad_cell *cell; ++ ++ cell = dad_cell_head.next; ++ while (cell != &dad_cell_head ++ && ipv6_addr_cmp(&cell->haddr, haddr)) { ++ cell = cell->next; ++ } ++ if (cell == &dad_cell_head) { ++ /* Not found element */ ++ return NULL; ++ } ++ return cell; ++} ++ ++/* ++ * Save all parameters needed for doing a Bind Ack in the mipv6_dad_cell ++ * structure. ++ */ ++static void mipv6_dad_save_cell(struct mipv6_dad_cell *cell, ++ struct inet6_ifaddr *ifp, int ifindex, ++ struct in6_addr *daddr, ++ struct in6_addr *haddr, ++ struct in6_addr *coa, ++ struct in6_addr *rep_coa, ++ __u32 ba_lifetime, ++ __u16 sequence, __u8 flags) ++{ ++ in6_ifa_hold(ifp); ++ cell->ifp = ifp; ++ cell->ifindex = ifindex; ++ ++ ipv6_addr_copy(&cell->daddr, daddr); ++ ipv6_addr_copy(&cell->haddr, haddr); ++ ipv6_addr_copy(&cell->coa, coa); ++ ipv6_addr_copy(&cell->rep_coa, rep_coa); ++ ++ /* Convert cell->ll_haddr to Link Local address */ ++ if (flags & MIPV6_BU_F_LLADDR) ++ mipv6_generate_ll_addr(&cell->ll_haddr, haddr); ++ else ++ ipv6_addr_copy(&cell->ll_haddr, haddr); ++ ++ cell->ba_lifetime = ba_lifetime; ++ cell->sequence = sequence; ++ cell->bu_flags = flags; ++} ++ ++/* ++ * Top level DAD routine for performing DAD. ++ * ++ * Return values ++ * 0 : Don't need to do DAD. ++ * 1 : Need to do DAD. ++ * -n : Error, where 'n' is the reason for the error. ++ * ++ * Assumption : DAD process has been optimized by using cached values upto ++ * some time. However sometimes this can cause problems. Eg. when the first ++ * BU was received, DAD might have failed. Before the second BU arrived, ++ * the node using MN's home address might have stopped using it, but still ++ * we will return DAD_DUPLICATE_ADDRESS based on the first DAD's result. Or ++ * this can go the other way around. However, it is a very small possibility ++ * and thus optimization is turned on by default. It is possible to change ++ * this feature (needs a little code-rewriting in this routine), but ++ * currently DAD result is being cached for performance reasons. ++ */ ++int mipv6_dad_start(struct inet6_ifaddr *ifp, int ifindex, ++ struct in6_addr *daddr, struct in6_addr *haddr, ++ struct in6_addr *coa, struct in6_addr *rep_coa, ++ __u32 ba_lifetime, __u16 sequence, __u8 flags) ++{ ++ int found; ++ struct mipv6_dad_cell *cell; ++ struct mipv6_bce bc_entry; ++ ++ if (ifp->idev->cnf.dad_transmits == 0) { ++ /* DAD is not configured on the HA, return SUCCESS */ ++ return 0; ++ } ++ ++ if (mipv6_bcache_get(haddr, daddr, &bc_entry) == 0) { ++ /* ++ * We already have an entry in our cache - don't need to ++ * do DAD as we are already defending this home address. ++ */ ++ return 0; ++ } ++ ++ write_lock(&dad_lock); ++ if ((cell = mipv6_dad_get_cell(haddr)) != NULL) { ++ /* ++ * An existing entry for BU was found in our cache due ++ * to retransmission of the BU or a new COA registration. ++ */ ++ switch (cell->flags) { ++ case DAD_INIT_ENTRY: ++ /* Old entry is waiting for DAD to complete */ ++ break; ++ case DAD_UNIQUE_ADDRESS: ++ /* DAD is finished successfully - return success. */ ++ write_unlock(&dad_lock); ++ return 0; ++ case DAD_DUPLICATE_ADDRESS: ++ /* ++ * DAD is finished and we got a NA while doing BU - ++ * return failure. ++ */ ++ write_unlock(&dad_lock); ++ return -DUPLICATE_ADDR_DETECT_FAIL; ++ default: ++ /* Unknown state - should never happen */ ++ DEBUG(DBG_WARNING, ++ "cell entry in unknown state : %d", ++ cell->flags); ++ write_unlock(&dad_lock); ++ return -REASON_UNSPECIFIED; ++ } ++ found = 1; ++ } else { ++ if ((cell = (struct mipv6_dad_cell *) ++ kmalloc(sizeof(struct mipv6_dad_cell), GFP_ATOMIC)) ++ == NULL) { ++ return -INSUFFICIENT_RESOURCES; ++ } ++ found = 0; ++ } ++ ++ mipv6_dad_save_cell(cell, ifp, ifindex, daddr, haddr, coa, rep_coa, ++ ba_lifetime, sequence, flags); ++ ++ if (!found) { ++ cell->flags = DAD_INIT_ENTRY; ++ cell->probes = ifp->idev->cnf.dad_transmits; ++ ++ /* Insert element on dad_cell_head list */ ++ dad_cell_head.prev->next = cell; ++ cell->next = &dad_cell_head; ++ cell->prev = dad_cell_head.prev; ++ dad_cell_head.prev = cell; ++ write_unlock(&dad_lock); ++ if (flags & MIPV6_BU_F_LLADDR) { ++ /* join the solicited node MC of the global homeaddr.*/ ++ mipv6_join_sol_mc_addr(&cell->haddr, ifp->idev->dev); ++ /* Send a NS */ ++ mipv6_dad_send_ns(ifp, &cell->haddr); ++ } ++ /* join the solicited node MC of the homeaddr. */ ++ mipv6_join_sol_mc_addr(&cell->ll_haddr, ifp->idev->dev); ++ ++ /* Send a NS */ ++ mipv6_dad_send_ns(ifp, &cell->ll_haddr); ++ ++ /* Initialize timer for this cell to timeout the NS. */ ++ init_timer(&cell->callback_timer); ++ cell->callback_timer.data = (unsigned long) cell; ++ cell->callback_timer.function = mipv6_dad_timeout; ++ cell->callback_timer.expires = jiffies + ++ ifp->idev->nd_parms->retrans_time; ++ add_timer(&cell->callback_timer); ++ } else { ++ write_unlock(&dad_lock); ++ } ++ return 1; ++} ++ ++void __init mipv6_dad_init(void) ++{ ++ dad_cell_head.next = dad_cell_head.prev = &dad_cell_head; ++ init_timer(&dad_cell_head.callback_timer); ++ dad_cell_head.callback_timer.data = 0; ++ dad_cell_head.callback_timer.function = ++ mipv6_dad_delete_old_entries; ++} ++ ++void __exit mipv6_dad_exit(void) ++{ ++ struct mipv6_dad_cell *curr, *next; ++ ++ write_lock_bh(&dad_lock); ++ del_timer(&dad_cell_head.callback_timer); ++ ++ curr = dad_cell_head.next; ++ while (curr != &dad_cell_head) { ++ next = curr->next; ++ del_timer(&curr->callback_timer); ++ if (curr->flags == DAD_INIT_ENTRY) { ++ /* ++ * We were in DAD_INIT state and listening to the ++ * solicited node MC address - need to stop that. ++ */ ++ mipv6_leave_sol_mc_addr(&curr->ll_haddr, ++ curr->ifp->idev->dev); ++ if (ipv6_addr_cmp(&curr->ll_haddr, &curr->haddr)) ++ mipv6_leave_sol_mc_addr(&curr->haddr, ++ curr->ifp->idev->dev); ++ } ++ in6_ifa_put(curr->ifp); ++ kfree(curr); ++ curr = next; ++ } ++ dad_cell_head.next = dad_cell_head.prev = &dad_cell_head; ++ write_unlock_bh(&dad_lock); ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/prefix.c +@@ -0,0 +1,217 @@ ++/** ++ * Prefix solicitation and advertisement ++ * ++ * Authors: ++ * Jaakko Laine ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mipv6_icmp.h" ++#include "debug.h" ++#include "sortedlist.h" ++#include "prefix.h" ++#include "config.h" ++ ++#define INFINITY 0xffffffff ++ ++struct timer_list pfx_timer; ++ ++struct list_head pfx_list; ++rwlock_t pfx_list_lock = RW_LOCK_UNLOCKED; ++ ++int compare_pfx_list_entry(const void *data1, const void *data2, ++ int datalen) ++{ ++ struct pfx_list_entry *e1 = (struct pfx_list_entry *) data1; ++ struct pfx_list_entry *e2 = (struct pfx_list_entry *) data2; ++ ++ return ((ipv6_addr_cmp(&e1->daddr, &e2->daddr) == 0) ++ && (e2->ifindex == -1 || e1->ifindex == e2->ifindex)); ++} ++ ++/** ++ * mipv6_pfx_cancel_send - cancel pending items to daddr from saddr ++ * @daddr: Destination address ++ * @ifindex: pending items on this interface will be canceled ++ * ++ * if ifindex == -1, all items to daddr will be removed ++ */ ++void mipv6_pfx_cancel_send(struct in6_addr *daddr, int ifindex) ++{ ++ unsigned long tmp; ++ struct pfx_list_entry entry; ++ ++ DEBUG_FUNC(); ++ ++ /* We'll just be comparing these parts... */ ++ memcpy(&entry.daddr, daddr, sizeof(struct in6_addr)); ++ entry.ifindex = ifindex; ++ ++ write_lock_bh(&pfx_list_lock); ++ ++ while (mipv6_slist_del_item(&pfx_list, &entry, ++ compare_pfx_list_entry) == 0) ++ ; ++ ++ if ((tmp = mipv6_slist_get_first_key(&pfx_list))) ++ mod_timer(&pfx_timer, tmp); ++ ++ write_unlock_bh(&pfx_list_lock); ++} ++ ++/** ++ * mipv6_pfx_add_ha - add a new HA to send prefix solicitations to ++ * @daddr: address of HA ++ * @saddr: our address to use as source address ++ * @ifindex: interface index ++ */ ++void mipv6_pfx_add_ha(struct in6_addr *daddr, struct in6_addr *saddr, ++ int ifindex) ++{ ++ unsigned long tmp; ++ struct pfx_list_entry entry; ++ ++ DEBUG_FUNC(); ++ ++ memcpy(&entry.daddr, daddr, sizeof(struct in6_addr)); ++ memcpy(&entry.saddr, saddr, sizeof(struct in6_addr)); ++ entry.retries = 0; ++ entry.ifindex = ifindex; ++ ++ write_lock_bh(&pfx_list_lock); ++ if (mipv6_slist_modify(&pfx_list, &entry, sizeof(struct pfx_list_entry), ++ jiffies + INITIAL_SOLICIT_TIMER * HZ, ++ compare_pfx_list_entry)) ++ DEBUG(DBG_WARNING, "Cannot add new HA to pfx list"); ++ ++ if ((tmp = mipv6_slist_get_first_key(&pfx_list))) ++ mod_timer(&pfx_timer, tmp); ++ write_unlock_bh(&pfx_list_lock); ++} ++ ++int mipv6_pfx_add_home(int ifindex, struct in6_addr *saddr, ++ struct in6_addr *daddr, unsigned long min_expire) ++{ ++ unsigned long tmp; ++ ++ write_lock(&pfx_list_lock); ++ ++ if (min_expire != INFINITY) { ++ unsigned long expire; ++ struct pfx_list_entry entry; ++ ++ memcpy(&entry.daddr, saddr, sizeof(struct in6_addr)); ++ memcpy(&entry.saddr, daddr, sizeof(struct in6_addr)); ++ entry.retries = 0; ++ entry.ifindex = ifindex; ++ ++ /* This is against the RFC 3775, but we need to set ++ * a minimum interval for a prefix solicitation. ++ * Otherwise a prefix solicitation storm will ++ * result if valid lifetime of the prefix is ++ * smaller than MAX_PFX_ADV_DELAY ++ */ ++ min_expire -= MAX_PFX_ADV_DELAY; ++ min_expire = min_expire < MIN_PFX_SOL_DELAY ? MIN_PFX_SOL_DELAY : min_expire; ++ ++ expire = jiffies + min_expire * HZ; ++ ++ if (mipv6_slist_modify(&pfx_list, &entry, ++ sizeof(struct pfx_list_entry), ++ expire, ++ compare_pfx_list_entry) != 0) ++ DEBUG(DBG_WARNING, "Cannot add new entry to pfx_list"); ++ } ++ ++ if ((tmp = mipv6_slist_get_first_key(&pfx_list))) ++ mod_timer(&pfx_timer, tmp); ++ ++ write_unlock(&pfx_list_lock); ++ ++ return 0; ++} ++ ++/** ++ * set_ha_pfx_list - manipulate pfx_list for HA when timer goes off ++ * @entry: pfx_list_entry that is due ++ */ ++static void set_ha_pfx_list(struct pfx_list_entry *entry) ++{ ++} ++ ++/** ++ * set_mn_pfx_list - manipulate pfx_list for MN when timer goes off ++ * @entry: pfx_list_entry that is due ++ */ ++static void set_mn_pfx_list(struct pfx_list_entry *entry) ++{ ++} ++ ++/** ++ * pfx_timer_handler - general timer handler ++ * @dummy: dummy ++ * ++ * calls set_ha_pfx_list and set_mn_pfx_list to do the thing when ++ * a timer goes off ++ */ ++static void pfx_timer_handler(unsigned long dummy) ++{ ++ unsigned long tmp; ++ struct pfx_list_entry *entry; ++ ++ DEBUG_FUNC(); ++ ++ write_lock(&pfx_list_lock); ++ if (!(entry = mipv6_slist_get_first(&pfx_list))) ++ goto out; ++ ++ if (mip6node_cnf.capabilities & CAP_HA) ++ set_ha_pfx_list(entry); ++ if (mip6node_cnf.capabilities & CAP_MN) ++ set_mn_pfx_list(entry); ++ if ((tmp = mipv6_slist_get_first_key(&pfx_list))) ++ mod_timer(&pfx_timer, tmp); ++ ++ out: ++ write_unlock(&pfx_list_lock); ++} ++ ++int mipv6_initialize_pfx_icmpv6(void) ++{ ++ INIT_LIST_HEAD(&pfx_list); ++ ++ init_timer(&pfx_timer); ++ pfx_timer.function = pfx_timer_handler; ++ ++ return 0; ++} ++ ++void mipv6_shutdown_pfx_icmpv6(void) ++{ ++ struct prefix_info *tmp; ++ ++ if (timer_pending(&pfx_timer)) ++ del_timer(&pfx_timer); ++ ++ write_lock_bh(&pfx_list_lock); ++ while ((tmp = mipv6_slist_del_first(&pfx_list))) ++ kfree(tmp); ++ write_unlock_bh(&pfx_list_lock); ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/prefix.h +@@ -0,0 +1,57 @@ ++/* ++ * MIPL Mobile IPv6 Prefix solicitation and advertisement ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _PREFIX_H ++#define _PREFIX_H ++ ++#include ++ ++struct pfx_list_entry { ++ struct in6_addr daddr; ++ struct in6_addr saddr; ++ int retries; ++ int ifindex; ++}; ++ ++extern struct list_head pfx_list; ++extern rwlock_t pfx_list_lock; ++extern struct timer_list pfx_timer; ++ ++int compare_pfx_list_entry(const void *data1, const void *data2, ++ int datalen); ++ ++/** ++ * mipv6_pfx_cancel_send - cancel pending pfx_advs/sols to daddr ++ * @daddr: destination address ++ * @ifindex: pending items on this interface will be canceled ++ * ++ * if ifindex == -1, all items to daddr will be removed ++ */ ++void mipv6_pfx_cancel_send(struct in6_addr *daddr, int ifindex); ++ ++/** ++ * mipv6_pfx_add_ha - add a new HA to send prefix solicitations to ++ * @daddr: address of HA ++ * @saddr: our address to use as source address ++ * @ifindex: interface index ++ */ ++void mipv6_pfx_add_ha(struct in6_addr *daddr, struct in6_addr *saddr, ++ int ifindex); ++ ++void mipv6_pfxs_modified(struct prefix_info *pinfo, int ifindex); ++ ++int mipv6_pfx_add_home(int ifindex, struct in6_addr *daddr, ++ struct in6_addr *saddr, unsigned long min_expire); ++ ++int mipv6_initialize_pfx_icmpv6(void); ++void mipv6_shutdown_pfx_icmpv6(void); ++ ++#endif +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/prefix_ha.c +@@ -0,0 +1,122 @@ ++/** ++ * Prefix advertisement for Home Agent ++ * ++ * Authors: ++ * Jaakko Laine ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mipv6_icmp.h" ++#include "debug.h" ++#include "sortedlist.h" ++#include "util.h" ++#include "bcache.h" ++#include "config.h" ++#include "prefix.h" ++ ++/** ++ * pfx_adv_iterator - modify pfx_list entries according to new prefix info ++ * @data: MN's home registration bcache_entry ++ * @args: new prefix info ++ * @sortkey: ignored ++ */ ++static int pfx_adv_iterator(void *data, void *args, unsigned long sortkey) ++{ ++ struct mipv6_bce *bc_entry = (struct mipv6_bce *) data; ++ struct prefix_info *pinfo = (struct prefix_info *) args; ++ ++ if (mipv6_prefix_compare(&bc_entry->coa, &pinfo->prefix, ++ pinfo->prefix_len) == 0) { ++ struct pfx_list_entry pfx_entry; ++ ++ memcpy(&pfx_entry.daddr, &bc_entry->coa, ++ sizeof(struct in6_addr)); ++ memcpy(&pfx_entry.daddr, &bc_entry->our_addr, ++ sizeof(struct in6_addr)); ++ pfx_entry.retries = 0; ++ pfx_entry.ifindex = bc_entry->ifindex; ++ ++ mipv6_slist_modify(&pfx_list, &pfx_entry, ++ sizeof(struct pfx_list_entry), ++ jiffies + ++ net_random() % (MAX_PFX_ADV_DELAY * HZ), ++ compare_pfx_list_entry); ++ } ++ ++ return 0; ++} ++ ++struct homereg_iterator_args { ++ struct list_head *head; ++ int count; ++}; ++ ++static int homereg_iterator(void *data, void *args, unsigned long *sortkey) ++{ ++ struct mipv6_bce *entry = (struct mipv6_bce *) data; ++ struct homereg_iterator_args *state = ++ (struct homereg_iterator_args *) args; ++ ++ if (entry->type == HOME_REGISTRATION) { ++ mipv6_slist_add(state->head, entry, ++ sizeof(struct mipv6_bce), ++ state->count); ++ state->count++; ++ } ++ return 0; ++} ++ ++static int mipv6_bcache_get_homeregs(struct list_head *head) ++{ ++ struct homereg_iterator_args args; ++ ++ DEBUG_FUNC(); ++ ++ args.count = 0; ++ args.head = head; ++ ++ mipv6_bcache_iterate(homereg_iterator, &args); ++ return args.count; ++} ++ ++/** ++ * mipv6_prefix_added - prefix was added to interface, act accordingly ++ * @pinfo: prefix_info that was added ++ * @ifindex: interface index ++ */ ++void mipv6_pfxs_modified(struct prefix_info *pinfo, int ifindex) ++{ ++ int count; ++ unsigned long tmp; ++ struct list_head home_regs; ++ ++ DEBUG_FUNC(); ++ ++ INIT_LIST_HEAD(&home_regs); ++ ++ if (!(count = mipv6_bcache_get_homeregs(&home_regs))) ++ return; ++ ++ write_lock_bh(&pfx_list_lock); ++ mipv6_slist_for_each(&home_regs, pinfo, pfx_adv_iterator); ++ if ((tmp = mipv6_slist_get_first_key(&pfx_list))) ++ mod_timer(&pfx_timer, tmp); ++ write_unlock_bh(&pfx_list_lock); ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/rr_crypto.c +@@ -0,0 +1,255 @@ ++/* ++ * rr_cookie.c - Mobile IPv6 return routability crypto ++ * Author : Henrik Petander ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ * ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "debug.h" ++#include "hmac.h" ++#include "rr_crypto.h" ++ ++#define DBG_RR 5 ++ ++u8 k_CN[HMAC_SHA1_KEY_SIZE]; // secret key of CN ++ ++u16 curr_index = 0; ++ ++struct nonce_timestamp nonce_table[MAX_NONCES]; ++spinlock_t nonce_lock = SPIN_LOCK_UNLOCKED; ++void update_nonces(void); ++ ++/** nonce_is_fresh - whether the nonce was generated recently ++ * ++ * @non_ts : table entry containing the nonce and a timestamp ++ * @interval : if nonce was generated within interval seconds it is fresh ++ * ++ * Returns 1 if the nonce is fresh, 0 otherwise. ++ */ ++static int nonce_is_fresh(struct nonce_timestamp *non_ts, unsigned long interval) ++{ ++ if (time_before(jiffies, non_ts->timestamp + interval * HZ) && !non_ts->invalid) ++ return 1; ++ return 0; ++} ++void mipv6_rr_invalidate_nonce(u16 nonce_ind) ++{ ++ spin_lock_bh(&nonce_lock); ++ if (nonce_ind > MAX_NONCES) { ++ spin_unlock_bh(&nonce_lock); ++ return; ++ } ++ nonce_table[nonce_ind].invalid = 1; ++ spin_unlock_bh(&nonce_lock); ++} ++/* Returns a pointer to a new nonce */ ++struct mipv6_rr_nonce * mipv6_rr_get_new_nonce(void) ++{ ++ struct mipv6_rr_nonce *nce = kmalloc(sizeof(*nce), GFP_ATOMIC); ++ ++ if (!nce) ++ return NULL; ++ // Lock nonces here ++ spin_lock_bh(&nonce_lock); ++ // If nonce is not fresh create new one ++ if (!nonce_is_fresh(&nonce_table[curr_index], MIPV6_RR_NONCE_LIFETIME)) { ++ // increment the last nonce pointer and create new nonce ++ curr_index++; ++ // Wrap around ++ if (curr_index == MAX_NONCES) ++ curr_index = 0; ++ // Get random data to fill the nonce data ++ get_random_bytes(nonce_table[curr_index].nonce.data, MIPV6_RR_NONCE_DATA_LENGTH); ++ // Fill the index field ++ nonce_table[curr_index].nonce.index = curr_index; ++ nonce_table[curr_index].invalid = 0; ++ nonce_table[curr_index].timestamp = jiffies; ++ } ++ spin_unlock_bh(&nonce_lock); ++ memcpy(nce, &nonce_table[curr_index].nonce, sizeof(*nce)); ++ // Unlock nonces ++ return nce; ++} ++/** mipv6_rr_nonce_get_by_index - returns a nonce for index ++ * @nonce_ind : index of the nonce ++ * ++ * Returns a nonce or NULL if the nonce index was invalid or the nonce ++ * for the index was not fresh. ++ */ ++struct mipv6_rr_nonce * mipv6_rr_nonce_get_by_index(u16 nonce_ind) ++{ ++ struct mipv6_rr_nonce *nce = NULL; ++ ++ spin_lock_bh(&nonce_lock); ++ if (nonce_ind >= MAX_NONCES) { ++ DEBUG(DBG_WARNING, "Nonce index field from BU invalid"); ++ ++ /* Here a double of the nonce_lifetime is used for freshness ++ * verification, since the nonces ++ * are not created in response to every initiator packet ++ */ ++ } else if (nonce_is_fresh(&nonce_table[nonce_ind], 2 * MIPV6_RR_NONCE_LIFETIME)) { ++ nce = kmalloc(sizeof(*nce), GFP_ATOMIC); ++ memcpy(nce, &nonce_table[nonce_ind].nonce, sizeof(*nce)); ++ } ++ spin_unlock_bh(&nonce_lock); ++ ++ return nce; ++} ++ ++/* Fills rr test init cookies with random bytes */ ++void mipv6_rr_mn_cookie_create(u8 *cookie) ++{ ++ get_random_bytes(cookie, MIPV6_RR_COOKIE_LENGTH); ++} ++ ++/** mipv6_rr_cookie_create - builds a home or care-of cookie ++ * ++ * @addr : the home or care-of address from HoTI or CoTI ++ * @ckie : memory where the cookie is copied to ++ * @nce : pointer to a nonce used for the calculation, nce is freed during the function ++ * ++ */ ++int mipv6_rr_cookie_create(struct in6_addr *addr, u8 **ckie, ++ u16 nonce_index) ++{ ++ struct ah_processing ah_proc; ++ u8 digest[HMAC_SHA1_HASH_LEN]; ++ struct mipv6_rr_nonce *nce; ++ ++ if ((nce = mipv6_rr_nonce_get_by_index(nonce_index))== NULL) ++ return -1; ++ ++ if (*ckie == NULL && (*ckie = kmalloc(MIPV6_RR_COOKIE_LENGTH, ++ GFP_ATOMIC)) == NULL) { ++ kfree(nce); ++ return -1; ++ } ++ /* Calculate the full hmac-sha1 digest from address and nonce using the secret key of cn */ ++ ++ if (ah_hmac_sha1_init(&ah_proc, k_CN, HMAC_SHA1_KEY_SIZE) < 0) { ++ DEBUG(DBG_ERROR, "Hmac sha1 initialization failed"); ++ kfree(nce); ++ return -1; ++ } ++ ++ ah_hmac_sha1_loop(&ah_proc, addr, sizeof(*addr)); ++ ah_hmac_sha1_loop(&ah_proc, nce->data, MIPV6_RR_NONCE_DATA_LENGTH); ++ ah_hmac_sha1_result(&ah_proc, digest); ++ ++ ++ /* clean up nonce */ ++ kfree(nce); ++ ++ /* Copy first 64 bits of hash target to the cookie */ ++ memcpy(*ckie, digest, MIPV6_RR_COOKIE_LENGTH); ++ return 0; ++} ++ ++/** mipv6_rr_key_calc - creates BU authentication key ++ * ++ * @hoc : Home Cookie ++ * @coc : Care-of Cookie ++ * ++ * Returns BU authentication key of length HMAC_SHA1_KEY_SIZE or NULL in error cases, ++ * caller needs to free the key. ++ */ ++u8 *mipv6_rr_key_calc(u8 *hoc, u8 *coc) ++{ ++ ++ u8 *key_bu = kmalloc(HMAC_SHA1_KEY_SIZE, GFP_ATOMIC); ++ SHA1_CTX c; ++ ++ if (!key_bu) { ++ DEBUG(DBG_CRITICAL, "Memory allocation failed, could nort create BU authentication key"); ++ return NULL; ++ } ++ ++ /* Calculate the key from home and care-of cookies ++ * Kbu = sha1(home_cookie | care-of cookie) ++ * or KBu = sha1(home_cookie), if MN deregisters ++ */ ++ sha1_init(&c); ++ sha1_compute(&c, hoc, MIPV6_RR_COOKIE_LENGTH); ++ if (coc) ++ sha1_compute(&c, coc, MIPV6_RR_COOKIE_LENGTH); ++ sha1_final(&c, key_bu); ++ DEBUG(DBG_RR, "Home and Care-of cookies used for calculating key "); ++ debug_print_buffer(DBG_RR, hoc, MIPV6_RR_COOKIE_LENGTH); ++ if (coc) ++ debug_print_buffer(DBG_RR, coc, MIPV6_RR_COOKIE_LENGTH); ++ ++ return key_bu; ++} ++ ++void mipv6_rr_init(void) ++{ ++ get_random_bytes(k_CN, HMAC_SHA1_KEY_SIZE); ++ memset(nonce_table, 0, MAX_NONCES * sizeof(struct nonce_timestamp)); ++} ++ ++#ifdef TEST_MIPV6_RR_CRYPTO ++void mipv6_test_rr(void) ++{ ++ struct mipv6_rr_nonce *nonce; ++ struct in6_addr a1, a2; ++ int ind1, ind2; ++ u8 *ckie1 = NULL, *ckie2 = NULL; ++ u8 *key_mn = NULL, *key_cn = NULL; ++ mipv6_init_rr(); ++ ++ nonce = mipv6_rr_get_new_nonce(); ++ if (!nonce) { ++ printk("mipv6_rr_get_new_nonce() failed, at 1! \n"); ++ return; ++ } ++ mipv6_rr_cookie_create(&a1, &ckie1, nonce->index); ++ ind1 = nonce->index; ++ kfree(nonce); ++ ++ nonce = mipv6_rr_get_new_nonce(); ++ if (!nonce) { ++ printk("mipv6_rr_get_new_nonce() failed, at 2! \n"); ++ return; ++ } ++ ++ mipv6_rr_cookie_create(&a2, &ckie2, nonce->index); ++ ind2 = nonce->index; ++ key_mn = mipv6_rr_key_calc(ckie1, ckie2); ++ ++ /* Create home and coa cookies based on indices */ ++ mipv6_rr_cookie_create(&a1, &ckie1, ind1); ++ mipv6_rr_cookie_create(&a2, &ckie2, ind2); ++ key_cn = mipv6_rr_key_calc(ckie1, ckie2); ++ if (!key_cn || !key_mn) { ++ printk("creation of secret key failed!\n"); ++ return; ++ } ++ if(memcmp(key_cn, key_mn, HMAC_SHA1_KEY_SIZE)) ++ printk("mipv6_rr_key_calc produced different keys for MN and CN \n"); ++ else ++ printk("mipv6_rr_crypto test OK\n"); ++ kfree(nonce); ++ kfree(key_cn); ++ kfree(key_mn); ++} ++#endif +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/rr_crypto.h +@@ -0,0 +1,72 @@ ++/* ++ * MIPL Mobile IPv6 Return routability crypto prototypes ++ * ++ * $Id:$ ++ * ++ * 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. ++ */ ++ ++#ifndef _RR_CRYPTO ++#define _RR_CRYPTO ++ ++#include ++ ++/* Macros and data structures */ ++ ++#define MIPV6_RR_NONCE_LIFETIME 60 ++#define MIPV6_RR_NONCE_DATA_LENGTH 8 ++#define MIPV6_RR_COOKIE_LENGTH 8 ++#define COOKIE_SIZE 8 ++#define MAX_NONCES 4 ++#define HMAC_SHA1_KEY_SIZE 20 ++ ++struct mipv6_rr_nonce { ++ u_int16_t index; ++ u_int8_t data[MIPV6_RR_NONCE_DATA_LENGTH]; ++}; ++ ++struct nonce_timestamp { ++ struct mipv6_rr_nonce nonce; ++ unsigned long timestamp; ++ u_int8_t invalid; ++}; ++ ++/* Function definitions */ ++ ++/* Return 1 if equal, 0 if not */ ++static __inline__ int mipv6_equal_cookies(u8 *c1, u8 *c2) ++{ ++ return (memcmp(c1, c2, MIPV6_RR_COOKIE_LENGTH) == 0); ++} ++ ++/* Function declarations */ ++ ++/* Create cookie for HoTi and CoTi */ ++extern void mipv6_rr_mn_cookie_create(u8 *cookie); ++ ++/* Create cookie for HoT and CoT */ ++extern int mipv6_rr_cookie_create(struct in6_addr *addr, u8 **ckie, u16 nonce_index); ++ ++/* Calculate return routability key from home and care-of cookies, key length is ++ * HMAC_SHA1_KEY_SIZE ++ */ ++extern u_int8_t *mipv6_rr_key_calc(u8 *hoc, u8 *coc); ++ ++extern struct mipv6_rr_nonce *mipv6_rr_get_new_nonce(void); ++ ++/* For avoiding replay attacks when MN deregisters */ ++extern void mipv6_rr_invalidate_nonce(u16 nonce_index); ++/* ++ * initializes the return routability crypto ++ */ ++ ++void mipv6_rr_init(void); ++ ++#ifdef TEST_MIPV6_RR_CRYPTO ++void mipv6_test_rr(void); ++#endif /* TEST_MIPV6_RR_CRYPTO */ ++ ++#endif /* RR_CRYPTO */ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/sortedlist.c +@@ -0,0 +1,349 @@ ++/** ++ * Sorted list - linked list with sortkey. ++ * ++ * Authors: ++ * Jaakko Laine ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++struct mipv6_sorted_list_entry { ++ struct list_head list; ++ void *data; ++ int datalen; ++ unsigned long sortkey; ++}; ++ ++/** ++ * compare - compares two arbitrary data items ++ * @data1: first data item ++ * @data2: second data item ++ * @datalen: length of data items in bits ++ * ++ * datalen is in bits! ++ */ ++int mipv6_bitwise_compare(const void *data1, const void *data2, int datalen) ++{ ++ int n = datalen; ++ __u8 * ptr1 = (__u8 *)data1; ++ __u8 * ptr2 = (__u8 *)data2; ++ ++ for (; n>=0; n-=8, ptr1++, ptr2++) { ++ if (n >= 8) { ++ if (*ptr1 != *ptr2) ++ return 0; ++ } else { ++ if ((*ptr1 ^ *ptr2) & ((~0) << (8 - n))) ++ return 0; ++ } ++ } ++ ++ return 1; ++} ++ ++/** ++ * mipv6_slist_add - add an entry to sorted list ++ * @head: list_head of the sorted list ++ * @data: item to store ++ * @datalen: length of data (in bytes) ++ * @key: sortkey of item ++ * ++ * Allocates memory for entry and data ++ */ ++int mipv6_slist_add(struct list_head *head, void *data, int datalen, ++ unsigned long sortkey) ++{ ++ struct list_head *pos; ++ struct mipv6_sorted_list_entry *entry, *tmp, *next; ++ ++ entry = kmalloc(sizeof(struct mipv6_sorted_list_entry), GFP_ATOMIC); ++ ++ if (!entry) ++ return -1; ++ ++ entry->data = kmalloc(datalen, GFP_ATOMIC); ++ ++ if (!entry->data) { ++ kfree(entry); ++ return -1; ++ } ++ ++ memcpy(entry->data, data, datalen); ++ entry->datalen = datalen; ++ entry->sortkey = sortkey; ++ ++ if ((pos = head->next) == head) { ++ list_add(&entry->list, head); ++ return 0; ++ } ++ ++ tmp = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (entry->sortkey < tmp->sortkey) { ++ list_add(&entry->list, head); ++ return 0; ++ } ++ ++ for (; pos != head; pos = pos->next) { ++ tmp = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (pos->next == head) { ++ list_add(&entry->list, &tmp->list); ++ return 0; ++ } ++ next = list_entry(pos->next, struct mipv6_sorted_list_entry, list); ++ if (entry->sortkey >= tmp->sortkey && entry->sortkey < next->sortkey) { ++ list_add(&entry->list, &tmp->list); ++ return 0; ++ } ++ } ++ ++ /* never reached */ ++ return -1; ++} ++ ++/** ++ * mipv6_slist_get_first - get the first data item in the list ++ * @head: list_head of the sorted list ++ * ++ * Returns the actual data item, not copy, so don't kfree it ++ */ ++void *mipv6_slist_get_first(struct list_head *head) ++{ ++ struct mipv6_sorted_list_entry *entry; ++ ++ if (list_empty(head)) ++ return NULL; ++ ++ entry = list_entry(head->next, struct mipv6_sorted_list_entry, list); ++ return entry->data; ++} ++ ++/** ++ * mipv6_slist_del_first - delete (and get) the first item in list ++ * @head: list_head of the sorted list ++ * ++ * Remember to kfree the item ++ */ ++void *mipv6_slist_del_first(struct list_head *head) ++{ ++ void *tmp; ++ struct mipv6_sorted_list_entry *entry; ++ ++ if (list_empty(head)) ++ return NULL; ++ ++ entry = list_entry(head->next, struct mipv6_sorted_list_entry, list); ++ tmp = entry->data; ++ ++ list_del(head->next); ++ kfree(entry); ++ ++ return tmp; ++} ++ ++/** ++ * mipv6_slist_del_item - delete entry ++ * @head: list_head of the sorted list ++ * @data: item to delete ++ * @compare: function used for comparing the data items ++ * ++ * compare function needs to have prototype ++ * int (*compare)(const void *data1, const void *data2, int datalen) ++ */ ++int mipv6_slist_del_item(struct list_head *head, void *data, ++ int (*compare)(const void *data1, const void *data2, ++ int datalen)) ++{ ++ struct list_head *pos; ++ struct mipv6_sorted_list_entry *entry; ++ ++ for(pos = head->next; pos != head; pos = pos->next) { ++ entry = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (compare(data, entry->data, entry->datalen)) { ++ list_del(pos); ++ kfree(entry->data); ++ kfree(entry); ++ return 0; ++ } ++ } ++ ++ return -1; ++} ++ ++/** ++ * mipv6_slist_get_first_key - get sortkey of the first item ++ * @head: list_head of the sorted list ++ */ ++unsigned long mipv6_slist_get_first_key(struct list_head *head) ++{ ++ struct mipv6_sorted_list_entry *entry; ++ ++ if (list_empty(head)) ++ return 0; ++ ++ entry = list_entry(head->next, struct mipv6_sorted_list_entry, list); ++ return entry->sortkey; ++} ++ ++/** ++ * mipv6_slist_get_key - get sortkey of the data item ++ * @head: list_head of the sorted list ++ * @data: the item to search for ++ * @compare: function used for comparing the data items ++ * ++ * compare function needs to have prototype ++ * int (*compare)(const void *data1, const void *data2, int datalen) ++ */ ++unsigned long mipv6_slist_get_key(struct list_head *head, void *data, ++ int (*compare)(const void *data1, ++ const void *data2, ++ int datalen)) ++{ ++ struct list_head *pos; ++ struct mipv6_sorted_list_entry *entry; ++ ++ for(pos = head->next; pos != head; pos = pos->next) { ++ entry = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (compare(data, entry->data, entry->datalen)) ++ return entry->sortkey; ++ } ++ ++ return 0; ++} ++ ++/** ++ * mipv6_slist_get_data - get the data item identified by sortkey ++ * @head: list_head of the sorted list ++ * @key: sortkey of the item ++ * ++ * Returns the actual data item, not copy, so don't kfree it ++ */ ++void *mipv6_slist_get_data(struct list_head *head, unsigned long sortkey) ++{ ++ struct list_head *pos; ++ struct mipv6_sorted_list_entry *entry; ++ ++ list_for_each(pos, head) { ++ entry = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (entry->sortkey == sortkey) ++ return entry->data; ++ } ++ ++ return NULL; ++} ++ ++/** ++ * reorder_entry - move an entry to a new position according to sortkey ++ * @head: list_head of the sorted list ++ * @entry_pos: current place of the entry ++ * @key: new sortkey ++ */ ++static void reorder_entry(struct list_head *head, struct list_head *entry_pos, ++ unsigned long sortkey) ++{ ++ struct list_head *pos; ++ struct mipv6_sorted_list_entry *entry; ++ ++ list_del(entry_pos); ++ ++ for (pos = head->next; pos != head; pos = pos->next) { ++ entry = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (sortkey >= entry->sortkey) { ++ list_add(entry_pos, &entry->list); ++ return; ++ } ++ } ++ ++ list_add(entry_pos, head); ++} ++ ++/** ++ * mipv6_slist_modify - modify data item ++ * @head: list_head of the sorted list ++ * @data: item, whose sortkey is to be modified ++ * @datalen: datalen in bytes ++ * @new_key: new sortkey ++ * @compare: function used for comparing the data items ++ * ++ * Compies the new data on top of the old one, if compare function returns ++ * true. If there's no matching entry, new one will be created. ++ * Compare function needs to have prototype ++ * int (*compare)(const void *data1, const void *data2, int datalen) ++ */ ++int mipv6_slist_modify(struct list_head *head, void *data, int datalen, ++ unsigned long new_key, ++ int (*compare)(const void *data1, const void *data2, ++ int datalen)) ++{ ++ struct list_head *pos; ++ struct mipv6_sorted_list_entry *entry; ++ ++ for (pos = head->next; pos != head; pos = pos->next) { ++ entry = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (compare(data, entry->data, datalen)) { ++ memcpy(entry->data, data, datalen); ++ entry->sortkey = new_key; ++ reorder_entry(head, &entry->list, new_key); ++ return 0; ++ } ++ } ++ ++ return mipv6_slist_add(head, data, datalen, new_key); ++} ++ ++/** ++ * mipv6_slist_push_first - move the first entry to place indicated by new_key ++ * @head: list_head of the sorted list ++ * @new_key: new sortkey ++ */ ++int mipv6_slist_push_first(struct list_head *head, unsigned long new_key) ++{ ++ struct mipv6_sorted_list_entry *entry; ++ ++ if (list_empty(head)) ++ return -1; ++ ++ entry = list_entry(head->next, struct mipv6_sorted_list_entry, list); ++ entry->sortkey = new_key; ++ ++ reorder_entry(head, head->next, new_key); ++ return 0; ++} ++ ++/** ++ * mipv6_slist_for_each - apply func to every item in list ++ * @head: list_head of the sorted list ++ * @args: args to pass to func ++ * @func: function to use ++ * ++ * function must be of type ++ * int (*func)(void *data, void *args, unsigned long sortkey) ++ * List iteration will stop once func has been applied to every item ++ * or when func returns true ++ */ ++int mipv6_slist_for_each(struct list_head *head, void *args, ++ int (*func)(void *data, void *args, ++ unsigned long sortkey)) ++{ ++ struct list_head *pos; ++ struct mipv6_sorted_list_entry *entry; ++ ++ list_for_each(pos, head) { ++ entry = list_entry(pos, struct mipv6_sorted_list_entry, list); ++ if (func(entry->data, args, entry->sortkey)) ++ break; ++ } ++ ++ return 0; ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/sortedlist.h +@@ -0,0 +1,133 @@ ++/* ++ * Sorted list - linked list with sortkey ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++/** ++ * compare - compares two arbitrary data items ++ * @data1: first data item ++ * @data2: second data item ++ * @datalen: length of data items in bits ++ * ++ * datalen is in bits! ++ */ ++int mipv6_bitwise_compare(const void *data1, const void *data2, int datalen); ++ ++/** ++ * mipv6_slist_add - add an entry to sorted list ++ * @head: list_head of the sorted list ++ * @data: item to store ++ * @datalen: length of data (in bytes) ++ * @key: sortkey of item ++ * ++ * Allocates memory for entry and data ++ */ ++int mipv6_slist_add(struct list_head *head, void *data, int datalen, ++ unsigned long sortkey); ++ ++/** ++ * mipv6_slist_get_first - get the first data item in the list ++ * @head: list_head of the sorted list ++ * ++ * Returns the actual data item, not copy, so don't kfree it ++ */ ++void *mipv6_slist_get_first(struct list_head *head); ++ ++/** ++ * mipv6_slist_del_first - delete (and get) the first item in list ++ * @head: list_head of the sorted list ++ * ++ * Remember to kfree the item ++ */ ++void *mipv6_slist_del_first(struct list_head *head); ++ ++/** ++ * mipv6_slist_del_item - delete entry ++ * @head: list_head of the sorted list ++ * @data: item to delete ++ * @compare: function used for comparing the data items ++ * ++ * compare function needs to have prototype ++ * int (*compare)(const void *data1, const void *data2, int datalen) where ++ * datalen is in bits ++ */ ++int mipv6_slist_del_item(struct list_head *head, void *data, ++ int (*compare)(const void *data1, const void *data2, ++ int datalen)); ++ ++/** ++ * mipv6_slist_get_first_key - get sortkey of the first item ++ * @head: list_head of the sorted list ++ */ ++unsigned long mipv6_slist_get_first_key(struct list_head *head); ++ ++/** ++ * mipv6_slist_get_key - get sortkey of the data item ++ * @head: list_head of the sorted list ++ * @data: the item to search for ++ * @compare: function used for comparing the data items ++ * ++ * compare function needs to have prototype ++ * int (*compare)(const void *data1, const void *data2, int datalen) where ++ * datalen is in bits ++ */ ++unsigned long mipv6_slist_get_key(struct list_head *head, void *data, ++ int (*compare)(const void *data1, ++ const void *data2, ++ int datalen)); ++ ++/** ++ * mipv6_slist_get_data - get the data item identified by sortkey ++ * @head: list_head of the sorted list ++ * @key: sortkey of the item ++ * ++ * Returns the actual data item, not copy, so don't kfree it ++ */ ++void *mipv6_slist_get_data(struct list_head *head, unsigned long sortkey); ++ ++/** ++ * mipv6_slist_modify - modify data item ++ * @head: list_head of the sorted list ++ * @data: item, whose sortkey is to be modified ++ * @datalen: datalen in bytes ++ * @new_key: new sortkey ++ * @compare: function used for comparing the data items ++ * ++ * Compies the new data on top of the old one, if compare function returns ++ * non-negative. If there's no matching entry, new one will be created. ++ * Compare function needs to have prototype ++ * int (*compare)(const void *data1, const void *data2, int datalen) where ++ * datalen is in bits. ++ */ ++int mipv6_slist_modify(struct list_head *head, void *data, int datalen, ++ unsigned long new_key, ++ int (*compare)(const void *data1, const void *data2, ++ int datalen)); ++ ++/** ++ * mipv6_slist_push_first - move the first entry to place indicated by new_key ++ * @head: list_head of the sorted list ++ * @new_key: new sortkey ++ */ ++int mipv6_slist_push_first(struct list_head *head, unsigned long new_key); ++ ++/** ++ * mipv6_slist_for_each - apply func to every item in list ++ * @head: list_head of the sorted list ++ * @args: args to pass to func ++ * @func: function to use ++ * ++ * function must be of type ++ * int (*func)(void *data, void *args, unsigned long sortkey) ++ * List iteration will stop once func has been applied to every item ++ * or when func returns true ++ */ ++int mipv6_slist_for_each(struct list_head *head, void *args, ++ int (*func)(void *data, void *args, ++ unsigned long sortkey)); +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/stats.c +@@ -0,0 +1,90 @@ ++/* ++ * Statistics module ++ * ++ * Authors: ++ * Sami Kivisaari ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ * Changes: ++ * Krishna Kumar, ++ * Venkata Jagana : SMP locking fix ++ */ ++ ++#include ++#include ++#include "stats.h" ++ ++struct mipv6_statistics mipv6_stats; ++ ++static int proc_info_dump( ++ char *buffer, char **start, ++ off_t offset, int length) ++{ ++ struct inf { ++ char *name; ++ int *value; ++ } int_stats[] = { ++ {"NEncapsulations", &mipv6_stats.n_encapsulations}, ++ {"NDecapsulations", &mipv6_stats.n_decapsulations}, ++ {"NBindRefreshRqsRcvd", &mipv6_stats.n_brr_rcvd}, ++ {"NHomeTestInitsRcvd", &mipv6_stats.n_hoti_rcvd}, ++ {"NCareofTestInitsRcvd", &mipv6_stats.n_coti_rcvd}, ++ {"NHomeTestRcvd", &mipv6_stats.n_hot_rcvd}, ++ {"NCareofTestRcvd", &mipv6_stats.n_cot_rcvd}, ++ {"NBindUpdatesRcvd", &mipv6_stats.n_bu_rcvd}, ++ {"NBindAcksRcvd", &mipv6_stats.n_ba_rcvd}, ++ {"NBindNAcksRcvd", &mipv6_stats.n_ban_rcvd}, ++ {"NBindErrorsRcvd", &mipv6_stats.n_be_rcvd}, ++ {"NBindRefreshRqsSent", &mipv6_stats.n_brr_sent}, ++ {"NHomeTestInitsSent", &mipv6_stats.n_hoti_sent}, ++ {"NCareofTestInitsSent", &mipv6_stats.n_coti_sent}, ++ {"NHomeTestSent", &mipv6_stats.n_hot_sent}, ++ {"NCareofTestSent", &mipv6_stats.n_cot_sent}, ++ {"NBindUpdatesSent", &mipv6_stats.n_bu_sent}, ++ {"NBindAcksSent", &mipv6_stats.n_ba_sent}, ++ {"NBindNAcksSent", &mipv6_stats.n_ban_sent}, ++ {"NBindErrorsSent", &mipv6_stats.n_be_sent}, ++ {"NBindUpdatesDropAuth", &mipv6_stats.n_bu_drop.auth}, ++ {"NBindUpdatesDropInvalid", &mipv6_stats.n_bu_drop.invalid}, ++ {"NBindUpdatesDropMisc", &mipv6_stats.n_bu_drop.misc}, ++ {"NBindAcksDropAuth", &mipv6_stats.n_bu_drop.auth}, ++ {"NBindAcksDropInvalid", &mipv6_stats.n_bu_drop.invalid}, ++ {"NBindAcksDropMisc", &mipv6_stats.n_bu_drop.misc}, ++ {"NBindRqsDropAuth", &mipv6_stats.n_bu_drop.auth}, ++ {"NBindRqsDropInvalid", &mipv6_stats.n_bu_drop.invalid}, ++ {"NBindRqsDropMisc", &mipv6_stats.n_bu_drop.misc} ++ }; ++ ++ int i; ++ int len = 0; ++ for(i=0; i length) len = length; ++ ++ return len; ++} ++ ++int mipv6_stats_init(void) ++{ ++ memset(&mipv6_stats, 0, sizeof(struct mipv6_statistics)); ++ proc_net_create("mip6_stat", 0, proc_info_dump); ++ return 0; ++} ++ ++void mipv6_stats_exit(void) ++{ ++ proc_net_remove("mip6_stat"); ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/stats.h +@@ -0,0 +1,71 @@ ++/* ++ * MIPL Mobile IPv6 Statistics header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _STATS_H ++#define _STATS_H ++ ++struct mipv6_drop { ++ __u32 auth; ++ __u32 invalid; ++ __u32 misc; ++}; ++ ++struct mipv6_statistics { ++ int n_encapsulations; ++ int n_decapsulations; ++ int n_mh_in_msg; ++ int n_mh_in_error; ++ int n_mh_out_msg; ++ int n_mh_out_error; ++ ++ int n_brr_rcvd; ++ int n_hoti_rcvd; ++ int n_coti_rcvd; ++ int n_hot_rcvd; ++ int n_cot_rcvd; ++ int n_bu_rcvd; ++ int n_ba_rcvd; ++ int n_ban_rcvd; ++ int n_be_rcvd; ++ ++ int n_brr_sent; ++ int n_hoti_sent; ++ int n_coti_sent; ++ int n_hot_sent; ++ int n_cot_sent; ++ int n_bu_sent; ++ int n_ba_sent; ++ int n_ban_sent; ++ int n_be_sent; ++ ++ int n_ha_rcvd; ++ int n_ha_sent; ++ ++ struct mipv6_drop n_bu_drop; ++ struct mipv6_drop n_ba_drop; ++ struct mipv6_drop n_brr_drop; ++ struct mipv6_drop n_be_drop; ++ struct mipv6_drop n_ha_drop; ++}; ++ ++extern struct mipv6_statistics mipv6_stats; ++ ++#ifdef CONFIG_SMP ++/* atomic_t is max 24 bits long */ ++#define MIPV6_INC_STATS(X) atomic_inc((atomic_t *)&mipv6_stats.X); ++#else ++#define MIPV6_INC_STATS(X) mipv6_stats.X++; ++#endif ++ ++int mipv6_stats_init(void); ++void mipv6_stats_exit(void); ++ ++#endif +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/tunnel.h +@@ -0,0 +1,35 @@ ++/* ++ * MIPL Mobile IPv6 IP6-IP6 tunneling header file ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _TUNNEL_H ++#define _TUNNEL_H ++ ++#include ++#include ++#include ++ ++static __inline__ int is_mip6_tnl(struct ip6_tnl *t) ++{ ++ return (t != NULL && ++ t->parms.flags & IP6_TNL_F_KERNEL_DEV && ++ t->parms.flags & IP6_TNL_F_MIP6_DEV); ++ ++} ++ ++static __inline__ int dev_is_mip6_tnl(struct net_device *dev) ++{ ++ struct ip6_tnl *t = (struct ip6_tnl *)dev->priv; ++ return (dev->type == ARPHRD_TUNNEL6 && is_mip6_tnl(t)); ++} ++ ++ ++#endif ++ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/tunnel_ha.c +@@ -0,0 +1,264 @@ ++/* ++ * IPv6-IPv6 tunneling module ++ * ++ * Authors: ++ * Sami Kivisaari ++ * Ville Nuorvala ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef CONFIG_SYSCTL ++#include ++#endif /* CONFIG_SYSCTL */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "tunnel.h" ++#include "debug.h" ++#include "stats.h" ++#include "config.h" ++ ++#define MIPV6_TNL_MAX IP6_TNL_MAX ++#define MIPV6_TNL_MIN 1 ++ ++int mipv6_max_tnls = 3; ++int mipv6_min_tnls = 1; ++ ++DECLARE_MUTEX(tnl_sem); ++ ++int mipv6_max_tnls_sysctl(ctl_table *ctl, int write, struct file *filp, ++ void *buffer, size_t *lenp) ++{ ++ int err; ++ ++ DEBUG_FUNC(); ++ ++ down(&tnl_sem); ++ if (write) { ++ int diff; ++ int old_max_tnls = mipv6_max_tnls; ++ err = proc_dointvec(ctl, write, filp, buffer, lenp); ++ if (err < 0) ++ goto out; ++ if (mipv6_max_tnls < mipv6_min_tnls || ++ mipv6_max_tnls > MIPV6_TNL_MAX) { ++ mipv6_max_tnls = old_max_tnls; ++ goto out; ++ } ++ if (mipv6_max_tnls < old_max_tnls) { ++ diff = old_max_tnls - mipv6_max_tnls; ++ ip6ip6_tnl_dec_max_kdev_count(diff); ++ } else if (mipv6_max_tnls > old_max_tnls) { ++ diff = mipv6_max_tnls - old_max_tnls; ++ ip6ip6_tnl_inc_max_kdev_count(diff); ++ } ++ } else { ++ err = proc_dointvec(ctl, write, filp, buffer, lenp); ++ } ++out: ++ up(&tnl_sem); ++ return err; ++} ++ ++int mipv6_min_tnls_sysctl(ctl_table *ctl, int write, struct file *filp, ++ void *buffer, size_t *lenp) ++{ ++ int err; ++ ++ DEBUG_FUNC(); ++ ++ down(&tnl_sem); ++ if (write) { ++ int diff; ++ int old_min_tnls = mipv6_min_tnls; ++ err = proc_dointvec(ctl, write, filp, buffer, lenp); ++ if (err < 0) ++ goto out; ++ if (mipv6_min_tnls > mipv6_max_tnls || ++ mipv6_min_tnls < MIPV6_TNL_MIN) { ++ mipv6_min_tnls = old_min_tnls; ++ goto out; ++ } ++ if (mipv6_min_tnls < old_min_tnls) { ++ diff = old_min_tnls - mipv6_min_tnls; ++ ip6ip6_tnl_dec_min_kdev_count(diff); ++ } else if (mipv6_min_tnls > old_min_tnls) { ++ diff = mipv6_min_tnls - old_min_tnls; ++ ip6ip6_tnl_inc_min_kdev_count(diff); ++ } ++ } else { ++ err = proc_dointvec(ctl, write, filp, buffer, lenp); ++ } ++out: ++ up(&tnl_sem); ++ return err; ++} ++ ++static __inline__ int mipv6_tnl_add(struct in6_addr *remote, ++ struct in6_addr *local) ++{ ++ struct ip6_tnl_parm p; ++ int ret; ++ ++ DEBUG_FUNC(); ++ ++ memset(&p, 0, sizeof(p)); ++ p.proto = IPPROTO_IPV6; ++ ipv6_addr_copy(&p.laddr, local); ++ ipv6_addr_copy(&p.raddr, remote); ++ p.hop_limit = 255; ++ p.flags = (IP6_TNL_F_KERNEL_DEV | IP6_TNL_F_MIP6_DEV | ++ IP6_TNL_F_IGN_ENCAP_LIMIT); ++ ++ ret = ip6ip6_kernel_tnl_add(&p); ++ if (ret > 0) { ++ DEBUG(DBG_INFO, "added tunnel from: " ++ "%x:%x:%x:%x:%x:%x:%x:%x to: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(local), NIPV6ADDR(remote)); ++ } else { ++ DEBUG(DBG_WARNING, "unable to add tunnel from: " ++ "%x:%x:%x:%x:%x:%x:%x:%x to: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(local), NIPV6ADDR(remote)); ++ } ++ return ret; ++} ++ ++static __inline__ int mipv6_tnl_del(struct in6_addr *remote, ++ struct in6_addr *local) ++{ ++ struct ip6_tnl *t = ip6ip6_tnl_lookup(remote, local); ++ ++ DEBUG_FUNC(); ++ ++ if (t != NULL && (t->parms.flags & IP6_TNL_F_MIP6_DEV)) { ++ DEBUG(DBG_INFO, "deleting tunnel from: " ++ "%x:%x:%x:%x:%x:%x:%x:%x to: %x:%x:%x:%x:%x:%x:%x:%x", ++ NIPV6ADDR(local), NIPV6ADDR(remote)); ++ ++ return ip6ip6_kernel_tnl_del(t); ++ } ++ return 0; ++} ++ ++static int add_route_to_mn(struct in6_addr *coa, struct in6_addr *ha_addr, ++ struct in6_addr *home_addr) ++{ ++ struct in6_rtmsg rtmsg; ++ int err; ++ struct ip6_tnl *t = ip6ip6_tnl_lookup(coa, ha_addr); ++ ++ if (!is_mip6_tnl(t)) { ++ DEBUG(DBG_CRITICAL,"Tunnel missing"); ++ return -ENODEV; ++ } ++ ++ DEBUG(DBG_INFO, "adding route to: %x:%x:%x:%x:%x:%x:%x:%x via " ++ "tunnel device", NIPV6ADDR(home_addr)); ++ ++ memset(&rtmsg, 0, sizeof(rtmsg)); ++ ipv6_addr_copy(&rtmsg.rtmsg_dst, home_addr); ++ rtmsg.rtmsg_dst_len = 128; ++ rtmsg.rtmsg_type = RTMSG_NEWROUTE; ++ rtmsg.rtmsg_flags = RTF_UP | RTF_NONEXTHOP | RTF_HOST | RTF_MOBILENODE; ++ rtmsg.rtmsg_ifindex = t->dev->ifindex; ++ rtmsg.rtmsg_metric = IP6_RT_PRIO_MIPV6; ++ if ((err = ip6_route_add(&rtmsg, NULL)) == -EEXIST) { ++ err = 0; ++ } ++ return err; ++} ++ ++static void del_route_to_mn(struct in6_addr *coa, struct in6_addr *ha_addr, ++ struct in6_addr *home_addr) ++{ ++ struct ip6_tnl *t = ip6ip6_tnl_lookup(coa, ha_addr); ++ ++ DEBUG_FUNC(); ++ ++ if (is_mip6_tnl(t)) { ++ struct in6_rtmsg rtmsg; ++ ++ DEBUG(DBG_INFO, "deleting route to: %x:%x:%x:%x:%x:%x:%x:%x " ++ " via tunnel device", NIPV6ADDR(home_addr)); ++ ++ memset(&rtmsg, 0, sizeof(rtmsg)); ++ ipv6_addr_copy(&rtmsg.rtmsg_dst, home_addr); ++ rtmsg.rtmsg_dst_len = 128; ++ rtmsg.rtmsg_ifindex = t->dev->ifindex; ++ rtmsg.rtmsg_metric = IP6_RT_PRIO_MIPV6; ++ ip6_route_del(&rtmsg, NULL); ++ } ++} ++ ++ ++int mipv6_add_tnl_to_mn(struct in6_addr *coa, ++ struct in6_addr *ha_addr, ++ struct in6_addr *home_addr) ++{ ++ int ret; ++ ++ DEBUG_FUNC(); ++ ++ ret = mipv6_tnl_add(coa, ha_addr); ++ ++ if (ret > 0) { ++ int err = add_route_to_mn(coa, ha_addr, home_addr); ++ if (err) { ++ if (err != -ENODEV) { ++ mipv6_tnl_del(coa, ha_addr); ++ } ++ return err; ++ } ++ } ++ return ret; ++} ++ ++int mipv6_del_tnl_to_mn(struct in6_addr *coa, ++ struct in6_addr *ha_addr, ++ struct in6_addr *home_addr) ++{ ++ DEBUG_FUNC(); ++ del_route_to_mn(coa, ha_addr, home_addr); ++ return mipv6_tnl_del(coa, ha_addr); ++} ++ ++__init void mipv6_initialize_tunnel(void) ++{ ++ down(&tnl_sem); ++ ip6ip6_tnl_inc_max_kdev_count(mipv6_max_tnls); ++ ip6ip6_tnl_inc_min_kdev_count(mipv6_min_tnls); ++ up(&tnl_sem); ++ mip6_fn.bce_tnl_rt_add = add_route_to_mn; ++ mip6_fn.bce_tnl_rt_del = del_route_to_mn; ++} ++ ++__exit void mipv6_shutdown_tunnel(void) ++{ ++ mip6_fn.bce_tnl_rt_del = NULL; ++ mip6_fn.bce_tnl_rt_add = NULL; ++ down(&tnl_sem); ++ ip6ip6_tnl_dec_min_kdev_count(mipv6_min_tnls); ++ ip6ip6_tnl_dec_max_kdev_count(mipv6_max_tnls); ++ up(&tnl_sem); ++} ++ +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/tunnel_ha.h +@@ -0,0 +1,20 @@ ++#ifndef _TUNNEL_HA_H ++#define _TUNNEL_HA_H ++ ++#include "tunnel.h" ++ ++extern int mipv6_max_tnls; ++extern int mipv6_min_tnls; ++ ++extern void mipv6_initialize_tunnel(void); ++extern void mipv6_shutdown_tunnel(void); ++ ++extern int mipv6_add_tnl_to_mn(struct in6_addr *coa, ++ struct in6_addr *ha_addr, ++ struct in6_addr *home_addr); ++ ++extern int mipv6_del_tnl_to_mn(struct in6_addr *coa, ++ struct in6_addr *ha_addr, ++ struct in6_addr *home_addr); ++ ++#endif +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/tunnel_mn.c +@@ -0,0 +1,160 @@ ++/* ++ * IPv6-IPv6 tunneling module ++ * ++ * Authors: ++ * Sami Kivisaari ++ * Ville Nuorvala ++ * ++ * $Id$ ++ * ++ * 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. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef CONFIG_SYSCTL ++#include ++#endif /* CONFIG_SYSCTL */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "tunnel.h" ++#include "debug.h" ++#include "stats.h" ++ ++static struct net_device *mn_ha_tdev; ++ ++static spinlock_t mn_ha_lock = SPIN_LOCK_UNLOCKED; ++ ++static __inline__ int add_reverse_route(struct in6_addr *ha_addr, ++ struct in6_addr *home_addr, ++ struct net_device *tdev) ++{ ++ struct in6_rtmsg rtmsg; ++ int err; ++ ++ DEBUG_FUNC(); ++ ++ memset(&rtmsg, 0, sizeof(rtmsg)); ++ rtmsg.rtmsg_type = RTMSG_NEWROUTE; ++ ipv6_addr_copy(&rtmsg.rtmsg_src, home_addr); ++ rtmsg.rtmsg_src_len = 128; ++ rtmsg.rtmsg_flags = RTF_UP | RTF_DEFAULT; ++ rtmsg.rtmsg_ifindex = tdev->ifindex; ++ rtmsg.rtmsg_metric = IP6_RT_PRIO_MIPV6; ++ if ((err = ip6_route_add(&rtmsg, NULL)) == -EEXIST) { ++ return 0; ++ } ++ return err; ++} ++ ++static __inline__ void del_reverse_route(struct in6_addr *ha_addr, ++ struct in6_addr *home_addr, ++ struct net_device *tdev) ++{ ++ struct in6_rtmsg rtmsg; ++ ++ DEBUG(DBG_INFO, "removing reverse route via tunnel device"); ++ ++ memset(&rtmsg, 0, sizeof(rtmsg)); ++ ipv6_addr_copy(&rtmsg.rtmsg_src, home_addr); ++ rtmsg.rtmsg_src_len = 128; ++ rtmsg.rtmsg_ifindex = tdev->ifindex; ++ rtmsg.rtmsg_metric = IP6_RT_PRIO_MIPV6; ++ ip6_route_del(&rtmsg, NULL); ++} ++ ++int mipv6_add_tnl_to_ha(void) ++{ ++ struct ip6_tnl_parm p; ++ struct ip6_tnl *t; ++ int err; ++ ++ DEBUG_FUNC(); ++ ++ memset(&p, 0, sizeof(p)); ++ p.proto = IPPROTO_IPV6; ++ p.hop_limit = 255; ++ p.flags = (IP6_TNL_F_KERNEL_DEV | IP6_TNL_F_MIP6_DEV | ++ IP6_TNL_F_IGN_ENCAP_LIMIT); ++ strcpy(p.name, "mip6mnha1"); ++ ++ rtnl_lock(); ++ if ((err = ip6ip6_tnl_create(&p, &t))) { ++ rtnl_unlock(); ++ return err; ++ } ++ spin_lock_bh(&mn_ha_lock); ++ ++ if (!mn_ha_tdev) { ++ mn_ha_tdev = t->dev; ++ dev_hold(mn_ha_tdev); ++ } ++ spin_unlock_bh(&mn_ha_lock); ++ dev_open(t->dev); ++ rtnl_unlock(); ++ return 0; ++} ++ ++int mipv6_mv_tnl_to_ha(struct in6_addr *ha_addr, ++ struct in6_addr *coa, ++ struct in6_addr *home_addr) ++{ ++ int err = -ENODEV; ++ ++ DEBUG_FUNC(); ++ ++ spin_lock_bh(&mn_ha_lock); ++ if (mn_ha_tdev) { ++ struct ip6_tnl_parm p; ++ memset(&p, 0, sizeof(p)); ++ p.proto = IPPROTO_IPV6; ++ ipv6_addr_copy(&p.laddr, coa); ++ ipv6_addr_copy(&p.raddr, ha_addr); ++ p.hop_limit = 255; ++ p.flags = (IP6_TNL_F_KERNEL_DEV | IP6_TNL_F_MIP6_DEV | ++ IP6_TNL_F_IGN_ENCAP_LIMIT); ++ ++ ip6ip6_tnl_change((struct ip6_tnl *) mn_ha_tdev->priv, &p); ++ if (ipv6_addr_cmp(coa, home_addr)) { ++ err = add_reverse_route(ha_addr, home_addr, ++ mn_ha_tdev); ++ } else { ++ del_reverse_route(ha_addr, home_addr, mn_ha_tdev); ++ err = 0; ++ } ++ } ++ spin_unlock_bh(&mn_ha_lock); ++ return err; ++} ++ ++void mipv6_del_tnl_to_ha(void) ++{ ++ struct net_device *dev; ++ ++ DEBUG_FUNC(); ++ ++ rtnl_lock(); ++ spin_lock_bh(&mn_ha_lock); ++ dev = mn_ha_tdev; ++ mn_ha_tdev = NULL; ++ spin_unlock_bh(&mn_ha_lock); ++ dev_put(dev); ++ unregister_netdevice(dev); ++ rtnl_unlock(); ++} +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/tunnel_mn.h +@@ -0,0 +1,14 @@ ++#ifndef _TUNNEL_MN_H ++#define _TUNNEL_MN_H ++ ++#include "tunnel.h" ++ ++extern int mipv6_add_tnl_to_ha(void); ++ ++extern int mipv6_mv_tnl_to_ha(struct in6_addr *ha_addr, ++ struct in6_addr *coa, ++ struct in6_addr *home_addr); ++ ++extern int mipv6_del_tnl_to_ha(void); ++ ++#endif +--- /dev/null ++++ linux-2.4.27/net/ipv6/mobile_ip6/util.h +@@ -0,0 +1,91 @@ ++/* ++ * MIPL Mobile IPv6 Utility functions ++ * ++ * $Id$ ++ * ++ * 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. ++ */ ++ ++#ifndef _UTIL_H ++#define _UTIL_H ++ ++#include ++#include ++ ++/** ++ * mipv6_prefix_compare - Compare two IPv6 prefixes ++ * @addr: IPv6 address ++ * @prefix: IPv6 address ++ * @nprefix: number of bits to compare ++ * ++ * Perform prefix comparison bitwise for the @nprefix first bits ++ * Returns 1, if the prefixes are the same, 0 otherwise ++ **/ ++static inline int mipv6_prefix_compare(const struct in6_addr *addr, ++ const struct in6_addr *prefix, ++ const unsigned int pfix_len) ++{ ++ int i; ++ unsigned int nprefix = pfix_len; ++ ++ if (nprefix > 128) ++ return 0; ++ ++ for (i = 0; nprefix > 0; nprefix -= 32, i++) { ++ if (nprefix >= 32) { ++ if (addr->s6_addr32[i] != prefix->s6_addr32[i]) ++ return 0; ++ } else { ++ if (((addr->s6_addr32[i] ^ prefix->s6_addr32[i]) & ++ ((~0) << (32 - nprefix))) != 0) ++ return 0; ++ return 1; ++ } ++ } ++ ++ return 1; ++} ++ ++/** ++ * homeagent_anycast - Compute Home Agent anycast address ++ * @ac_addr: append home agent anycast suffix to passed prefix ++ * @prefix: prefix ha anycast address is generated from ++ * @plen: length of prefix in bits ++ * ++ * Calculate corresponding Home Agent Anycast Address (RFC2526) in a ++ * given subnet. ++ */ ++static inline int ++mipv6_ha_anycast(struct in6_addr *ac_addr, struct in6_addr *prefix, int plen) ++{ ++ if (plen <= 0 || plen > 120) { ++ /* error, interface id should be minimum 8 bits */ ++ return -1; ++ } ++ ipv6_addr_copy(ac_addr, prefix); ++ ++ if (plen < 32) ++ ac_addr->s6_addr32[0] |= htonl((u32)(~0) >> plen); ++ if (plen < 64) ++ ac_addr->s6_addr32[1] |= htonl((u32)(~0) >> (plen > 32 ? plen % 32 : 0)); ++ if (plen < 92) ++ ac_addr->s6_addr32[2] |= htonl((u32)(~0) >> (plen > 64 ? plen % 32 : 0)); ++ if (plen <= 120) ++ ac_addr->s6_addr32[3] |= htonl((u32)(~0) >> (plen > 92 ? plen % 32 : 0)); ++ ++ /* RFC2526: for interface identifiers in EUI-64 ++ * format, the universal/local bit in the interface ++ * identifier MUST be set to 0. */ ++ if (plen == 64) { ++ ac_addr->s6_addr32[2] &= (int)htonl(0xfdffffff); ++ } ++ /* Mobile IPv6 Home-Agents anycast id (0x7e) */ ++ ac_addr->s6_addr32[3] &= (int)htonl(0xfffffffe); ++ ++ return 0; ++} ++ ++#endif /* _UTIL_H */ +--- linux-2.4.27/net/ipv6/ndisc.c~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/ipv6/ndisc.c +@@ -23,6 +23,7 @@ + * and moved to net/core. + * Pekka Savola : RFC2461 validation + * YOSHIFUJI Hideaki @USAGI : Verify ND options properly ++ * Ville Nuorvala : RFC2461 fixes to proxy ND + */ + + /* Set to 3 to get tracing... */ +@@ -70,6 +71,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -187,6 +189,8 @@ + case ND_OPT_TARGET_LL_ADDR: + case ND_OPT_MTU: + case ND_OPT_REDIRECT_HDR: ++ case ND_OPT_RTR_ADV_INTERVAL: ++ case ND_OPT_HOME_AGENT_INFO: + if (ndopts->nd_opt_array[nd_opt->nd_opt_type]) { + ND_PRINTK2((KERN_WARNING + "ndisc_parse_options(): duplicated ND6 option found: type=%d\n", +@@ -372,8 +376,8 @@ + */ + + void ndisc_send_na(struct net_device *dev, struct neighbour *neigh, +- struct in6_addr *daddr, struct in6_addr *solicited_addr, +- int router, int solicited, int override, int inc_opt) ++ struct in6_addr *daddr, struct in6_addr *solicited_addr, ++ int router, int solicited, int override, int inc_opt) + { + static struct in6_addr tmpaddr; + struct inet6_ifaddr *ifp; +@@ -766,7 +770,8 @@ + int addr_type = ipv6_addr_type(saddr); + + if (in6_dev && in6_dev->cnf.forwarding && +- (addr_type & IPV6_ADDR_UNICAST) && ++ (addr_type & IPV6_ADDR_UNICAST || ++ addr_type == IPV6_ADDR_ANY) && + pneigh_lookup(&nd_tbl, &msg->target, dev, 0)) { + int inc = ipv6_addr_type(daddr)&IPV6_ADDR_MULTICAST; + +@@ -778,13 +783,21 @@ + nd_tbl.stats.rcv_probes_mcast++; + else + nd_tbl.stats.rcv_probes_ucast++; +- +- neigh = neigh_event_ns(&nd_tbl, lladdr, saddr, dev); + +- if (neigh) { +- ndisc_send_na(dev, neigh, saddr, &msg->target, +- 0, 1, 0, 1); +- neigh_release(neigh); ++ if (addr_type & IPV6_ADDR_UNICAST) { ++ neigh = neigh_event_ns(&nd_tbl, lladdr, saddr, dev); ++ ++ if (neigh) { ++ ndisc_send_na(dev, neigh, saddr, &msg->target, ++ 0, 1, 0, 1); ++ neigh_release(neigh); ++ } ++ } else { ++ /* the proxy should also protect against DAD */ ++ struct in6_addr maddr; ++ ipv6_addr_all_nodes(&maddr); ++ ndisc_send_na(dev, NULL, &maddr, &msg->target, ++ 0, 0, 0, 1); + } + } else { + struct sk_buff *n = skb_clone(skb, GFP_ATOMIC); +@@ -849,6 +862,9 @@ + if (ifp->flags & IFA_F_TENTATIVE) { + addrconf_dad_failure(ifp); + return; ++ } else if (ndisc_mip_mn_ha_probe(ifp, lladdr)) { ++ in6_ifa_put(ifp); ++ return; + } + /* What should we make now? The advertisement + is invalid, but ndisc specs say nothing +@@ -887,6 +903,7 @@ + msg->icmph.icmp6_override, 1); + neigh_release(neigh); + } ++ ndisc_check_mipv6_dad(&msg->target); + } + + static void ndisc_router_discovery(struct sk_buff *skb) +@@ -894,6 +911,7 @@ + struct ra_msg *ra_msg = (struct ra_msg *) skb->h.raw; + struct neighbour *neigh; + struct inet6_dev *in6_dev; ++ int change_rtr; + struct rt6_info *rt; + int lifetime; + struct ndisc_options ndopts; +@@ -923,10 +941,6 @@ + ND_PRINTK1("RA: can't find in6 device\n"); + return; + } +- if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_ra) { +- in6_dev_put(in6_dev); +- return; +- } + + if (!ndisc_parse_options(opt, optlen, &ndopts)) { + in6_dev_put(in6_dev); +@@ -935,7 +949,12 @@ + "ICMP6 RA: invalid ND option, ignored.\n"); + return; + } ++ change_rtr = ndisc_mipv6_ra_rcv(skb, &ndopts); + ++ if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_ra) { ++ in6_dev_put(in6_dev); ++ return; ++ } + if (in6_dev->if_flags & IF_RS_SENT) { + /* + * flag that an RA was received after an RS was sent +@@ -963,8 +982,7 @@ + ip6_del_rt(rt, NULL); + rt = NULL; + } +- +- if (rt == NULL && lifetime) { ++ if (rt == NULL && lifetime && change_rtr) { + ND_PRINTK2("ndisc_rdisc: adding default router\n"); + + rt = rt6_add_dflt_router(&skb->nh.ipv6h->saddr, skb->dev); +@@ -1087,6 +1105,8 @@ + if (rt) + dst_release(&rt->u.dst); + in6_dev_put(in6_dev); ++ ++ ndisc_mipv6_change_router(change_rtr); + } + + static void ndisc_redirect_rcv(struct sk_buff *skb) +--- linux-2.4.27/net/ipv6/raw.c~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/ipv6/raw.c +@@ -43,6 +43,7 @@ + #include + #include + #include ++#include + + #include + +@@ -641,6 +642,7 @@ + hdr.daddr = daddr; + else + hdr.daddr = NULL; ++ hdr.daddr = mipv6_get_fake_hdr_daddr(hdr.daddr, daddr); + + err = ip6_build_xmit(sk, rawv6_frag_cksum, &hdr, &fl, len, + opt, hlimit, msg->msg_flags); +--- linux-2.4.27/net/ipv6/route.c~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/ipv6/route.c +@@ -49,6 +49,7 @@ + #include + #include + #include ++#include + + #include + +@@ -363,12 +364,8 @@ + rt->u.dst.flags |= DST_HOST; + + #ifdef CONFIG_IPV6_SUBTREES +- if (rt->rt6i_src.plen && saddr) { +- ipv6_addr_copy(&rt->rt6i_src.addr, saddr); +- rt->rt6i_src.plen = 128; +- } ++ rt->rt6i_src.plen = ort->rt6i_src.plen; + #endif +- + rt->rt6i_nexthop = ndisc_get_neigh(rt->rt6i_dev, &rt->rt6i_gateway); + + dst_hold(&rt->u.dst); +@@ -511,14 +508,19 @@ + struct rt6_info *rt; + int strict; + int attempts = 3; ++ struct in6_addr *saddr; + ++ if (ipv6_chk_addr(fl->nl_u.ip6_u.daddr, NULL)) ++ saddr = NULL; ++ else ++ saddr = fl->nl_u.ip6_u.saddr; ++ + strict = ipv6_addr_type(fl->nl_u.ip6_u.daddr) & (IPV6_ADDR_MULTICAST|IPV6_ADDR_LINKLOCAL); + + relookup: + read_lock_bh(&rt6_lock); + +- fn = fib6_lookup(&ip6_routing_table, fl->nl_u.ip6_u.daddr, +- fl->nl_u.ip6_u.saddr); ++ fn = fib6_lookup(&ip6_routing_table, fl->nl_u.ip6_u.daddr, saddr); + + restart: + rt = fn->leaf; +@@ -663,25 +665,6 @@ + return (atomic_read(&ip6_dst_ops.entries) > ip6_rt_max_size); + } + +-/* Clean host part of a prefix. Not necessary in radix tree, +- but results in cleaner routing tables. +- +- Remove it only when all the things will work! +- */ +- +-static void ipv6_addr_prefix(struct in6_addr *pfx, +- const struct in6_addr *addr, int plen) +-{ +- int b = plen&0x7; +- int o = plen>>3; +- +- memcpy(pfx->s6_addr, addr, o); +- if (o < 16) +- memset(pfx->s6_addr + o, 0, 16 - o); +- if (b != 0) +- pfx->s6_addr[o] = addr->s6_addr[o]&(0xff00 >> b); +-} +- + static int ipv6_get_mtu(struct net_device *dev) + { + int mtu = IPV6_MIN_MTU; +@@ -810,7 +793,7 @@ + if (!(gwa_type&IPV6_ADDR_UNICAST)) + goto out; + +- grt = rt6_lookup(gw_addr, NULL, rtmsg->rtmsg_ifindex, 1); ++ grt = rt6_lookup(gw_addr, &rtmsg->rtmsg_src, rtmsg->rtmsg_ifindex, 1); + + err = -EHOSTUNREACH; + if (grt == NULL) +@@ -848,7 +831,15 @@ + goto out; + } + } +- ++#ifdef USE_IPV6_MOBILITY ++ /* If destination is mobile node, add special skb->dst->input ++ * function for proxy ND. ++ */ ++ if (rtmsg->rtmsg_flags & RTF_MOBILENODE) { ++ rt->u.dst.input = ip6_mipv6_forward; ++ } ++#endif /* CONFIG_IPV6_MOBILITY */ ++ + if (ipv6_addr_is_multicast(&rt->rt6i_dst.addr)) + rt->rt6i_hoplimit = IPV6_DEFAULT_MCASTHOPS; + else +@@ -936,7 +927,7 @@ + struct rt6_info *rt, *nrt; + + /* Locate old route to this destination. */ +- rt = rt6_lookup(dest, NULL, neigh->dev->ifindex, 1); ++ rt = rt6_lookup(dest, saddr, neigh->dev->ifindex, 1); + + if (rt == NULL) + return; +@@ -1003,6 +994,9 @@ + nrt = ip6_rt_copy(rt); + if (nrt == NULL) + goto out; ++#ifdef CONFIG_IPV6_SUBTREES ++ nrt->rt6i_src.plen = rt->rt6i_src.plen; ++#endif + + nrt->rt6i_flags = RTF_GATEWAY|RTF_UP|RTF_DYNAMIC|RTF_CACHE; + if (on_link) +@@ -1104,6 +1098,9 @@ + nrt = ip6_rt_copy(rt); + if (nrt == NULL) + goto out; ++#ifdef CONFIG_IPV6_SUBTREES ++ nrt->rt6i_src.plen = rt->rt6i_src.plen; ++#endif + ipv6_addr_copy(&nrt->rt6i_dst.addr, daddr); + nrt->rt6i_dst.plen = 128; + nrt->u.dst.flags |= DST_HOST; +--- linux-2.4.27/net/ipv6/tcp_ipv6.c~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/ipv6/tcp_ipv6.c +@@ -50,6 +50,7 @@ + #include + #include + #include ++#include + + #include + +@@ -557,6 +558,7 @@ + struct flowi fl; + struct dst_entry *dst; + int addr_type; ++ int reroute = 0; + int err; + + if (addr_len < SIN6_LEN_RFC2133) +@@ -660,7 +662,7 @@ + + fl.proto = IPPROTO_TCP; + fl.fl6_dst = &np->daddr; +- fl.fl6_src = saddr; ++ fl.fl6_src = saddr; + fl.oif = sk->bound_dev_if; + fl.uli_u.ports.dport = usin->sin6_port; + fl.uli_u.ports.sport = sk->sport; +@@ -669,31 +671,46 @@ + struct rt0_hdr *rt0 = (struct rt0_hdr *) np->opt->srcrt; + fl.nl_u.ip6_u.daddr = rt0->addr; + } +- + dst = ip6_route_output(sk, &fl); +- ++#ifdef CONFIG_IPV6_SUBTREES ++ reroute = (saddr == NULL); ++#endif + if ((err = dst->error) != 0) { + dst_release(dst); + goto failure; + } +- +- ip6_dst_store(sk, dst, NULL); +- sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM; +- ++ if (!reroute) { ++ ip6_dst_store(sk, dst, NULL, NULL); ++ sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM; ++ } + if (saddr == NULL) { + err = ipv6_get_saddr(dst, &np->daddr, &saddr_buf); ++ ++ if (reroute) ++ dst_release(dst); + if (err) + goto failure; + + saddr = &saddr_buf; ++ ipv6_addr_copy(&np->rcv_saddr, saddr); ++#ifdef CONFIG_IPV6_SUBTREES ++ fl.fl6_src = saddr; ++ dst = ip6_route_output(sk, &fl); ++ ++ if ((err = dst->error) != 0) { ++ dst_release(dst); ++ goto failure; ++ } ++ ip6_dst_store(sk, dst, NULL, NULL); ++ sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM; ++#endif + } + + /* set the source address */ +- ipv6_addr_copy(&np->rcv_saddr, saddr); + ipv6_addr_copy(&np->saddr, saddr); + sk->rcv_saddr= LOOPBACK4_IPV6; + +- tp->ext_header_len = 0; ++ tp->ext_header_len = tcp_v6_get_mipv6_header_len(); + if (np->opt) + tp->ext_header_len = np->opt->opt_flen+np->opt->opt_nflen; + tp->mss_clamp = IPV6_MIN_MTU - sizeof(struct tcphdr) - sizeof(struct ipv6hdr); +@@ -1338,7 +1355,7 @@ + #endif + MOD_INC_USE_COUNT; + +- ip6_dst_store(newsk, dst, NULL); ++ ip6_dst_store(newsk, dst, NULL, NULL); + sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM; + + newtp = &(newsk->tp_pinfo.af_tcp); +@@ -1383,7 +1400,7 @@ + sock_kfree_s(sk, opt, opt->tot_len); + } + +- newtp->ext_header_len = 0; ++ newtp->ext_header_len = tcp_v6_get_mipv6_header_len(); + if (np->opt) + newtp->ext_header_len = np->opt->opt_nflen + np->opt->opt_flen; + +@@ -1710,7 +1727,7 @@ + return err; + } + +- ip6_dst_store(sk, dst, NULL); ++ ip6_dst_store(sk, dst, NULL, NULL); + sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM; + } + +@@ -1749,7 +1766,7 @@ + return -sk->err_soft; + } + +- ip6_dst_store(sk, dst, NULL); ++ ip6_dst_store(sk, dst, NULL, NULL); + } + + skb->dst = dst_clone(dst); +--- linux-2.4.27/net/ipv6/udp.c~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/ipv6/udp.c +@@ -48,6 +48,7 @@ + #include + #include + #include ++#include + + #include + +@@ -232,6 +233,7 @@ + struct ip6_flowlabel *flowlabel = NULL; + int addr_type; + int err; ++ int reroute = 0; + + if (usin->sin6_family == AF_INET) { + if (__ipv6_only_sock(sk)) +@@ -331,7 +333,7 @@ + + fl.proto = IPPROTO_UDP; + fl.fl6_dst = &np->daddr; +- fl.fl6_src = &saddr; ++ fl.fl6_src = NULL; + fl.oif = sk->bound_dev_if; + fl.uli_u.ports.dport = sk->dport; + fl.uli_u.ports.sport = sk->sport; +@@ -348,29 +350,44 @@ + struct rt0_hdr *rt0 = (struct rt0_hdr *) np->opt->srcrt; + fl.fl6_dst = rt0->addr; + } +- + dst = ip6_route_output(sk, &fl); +- + if ((err = dst->error) != 0) { + dst_release(dst); + fl6_sock_release(flowlabel); +- return err; +- } +- +- ip6_dst_store(sk, dst, fl.fl6_dst); +- ++ return err; ++ } ++#ifdef CONFIG_IPV6_SUBTREES ++ reroute = (fl.fl6_src == NULL); ++#endif + /* get the source adddress used in the apropriate device */ + + err = ipv6_get_saddr(dst, daddr, &saddr); + ++ if (reroute) ++ dst_release(dst); ++ + if (err == 0) { +- if(ipv6_addr_any(&np->saddr)) ++#ifdef CONFIG_IPV6_SUBTREES ++ if (reroute) { ++ fl.fl6_src = &saddr; ++ dst = ip6_route_output(sk, &fl); ++ if ((err = dst->error) != 0) { ++ dst_release(dst); ++ fl6_sock_release(flowlabel); ++ return err; ++ } ++ } ++#endif ++ if(ipv6_addr_any(&np->saddr)) { + ipv6_addr_copy(&np->saddr, &saddr); +- ++ fl.fl6_src = &np->saddr; ++ } + if(ipv6_addr_any(&np->rcv_saddr)) { + ipv6_addr_copy(&np->rcv_saddr, &saddr); + sk->rcv_saddr = LOOPBACK4_IPV6; + } ++ ip6_dst_store(sk, dst, fl.fl6_dst, ++ fl.fl6_src == &np->saddr ? fl.fl6_src : NULL); + sk->state = TCP_ESTABLISHED; + } + fl6_sock_release(flowlabel); +@@ -889,6 +906,7 @@ + opt = fl6_merge_options(&opt_space, flowlabel, opt); + if (opt && opt->srcrt) + udh.daddr = daddr; ++ udh.daddr = mipv6_get_fake_hdr_daddr(udh.daddr, daddr); + + udh.uh.source = sk->sport; + udh.uh.len = len < 0x10000 ? htons(len) : 0; +--- linux-2.4.27/net/netsyms.c~mipv6-1.1-v2.4.26 ++++ linux-2.4.27/net/netsyms.c +@@ -190,6 +190,7 @@ + #endif + EXPORT_SYMBOL(pneigh_lookup); + EXPORT_SYMBOL(pneigh_enqueue); ++EXPORT_SYMBOL(pneigh_delete); + EXPORT_SYMBOL(neigh_destroy); + EXPORT_SYMBOL(neigh_parms_alloc); + EXPORT_SYMBOL(neigh_parms_release); diff --git a/recipes/linux/files/usb-gadget-ether-compat.patch b/recipes/linux/files/usb-gadget-ether-compat.patch new file mode 100644 index 0000000000..9e336cfa3c --- /dev/null +++ b/recipes/linux/files/usb-gadget-ether-compat.patch @@ -0,0 +1,30 @@ +--- kernel/drivers/usb/gadget/ether.c 2005-04-24 12:40:08.867411535 +0200 ++++ /tmp/ether.c 2005-04-24 12:39:02.119093498 +0200 +@@ -231,6 +231,16 @@ + MODULE_PARM(host_addr, "s"); + MODULE_PARM_DESC(host_addr, "Host Ethernet Address"); + ++#ifdef CONFIG_USB_ETH_RNDIS ++/* setting it to 1 disables the RNDIS extension, ++ * needed to make g_ether compatible with usbnet from kernel < 2.6.10: ++ * - simple vendor/product ++ * - just the CDC interface ++ */ ++static u8 __initdata compat_mode; ++MODULE_PARM(compat_mode, "b"); ++MODULE_PARM_DESC(compat_mode, "non-zero value reverts to traditional usbnet compatibility (RNDIS disabled)"); ++#endif + + /*-------------------------------------------------------------------------*/ + +@@ -2336,6 +2346,10 @@ + #endif + #ifndef CONFIG_USB_ETH_RNDIS + rndis = 0; ++#else ++ if (compat_mode) { ++ rndis = 0; ++ } + #endif + + /* Because most host side USB stacks handle CDC Ethernet, that -- cgit 1.2.3-korg