GIT 80a3be1c33ff7685fcac6a2c9e41391b25306b91 git+ssh://master.kernel.org/pub/scm/linux/kernel/git/jikos/ipwireless_cs.git#ipw-devel commit Author: Jiri Kosina Date: Tue Jan 23 13:12:09 2007 +0100 ipwireless_cs: Fix stack overflow in debug mode (no. 2) when ipwireless_cs_debug == 1, there is a stack overflow in do_receive_packet(). Fix it by allocating proper space for every byte in the packet. Signed-off-by: Jiri Kosina commit a00fa1026f2b6090c9ec7feecb2db26893daa80c Author: Jiri Kosina Date: Mon Jan 22 15:36:33 2007 +0100 ipwireless_cs: Fix stack overflow in debug mode when ipwireless_cs_debug == 1, there is a stack overflow in do_send_fragment(). Fix it by allocating proper space for every byte in the packet fragment. Signed-off-by: Jiri Kosina commit ff46a9f5bfb8e8ca42daceff99ea73c6bfdf9a76 Author: Jiri Kosina Date: Mon Jan 22 15:24:01 2007 +0100 ipwireless_cs: significant code cleanups - interrupt handling splitted into multiple functions - handling of setup packet cleaned up and splitted to be readable - more CodingStyle work Signed-off-by: Jiri Kosina commit ace3c3535632b1a81381e87ef8be5f2efba9fc06 Author: Jiri Kosina Date: Mon Jan 22 11:51:45 2007 +0100 ipwireless_cs: convert the driver to use in-kernel linked lists Move away from own implementation of linked list in favor of list.h Signed-off-by: Jiri Kosina commit f956ba024a96fa5405252df5194057e356fa72c2 Author: Jiri Kosina Date: Thu Jan 18 14:49:15 2007 +0100 ipwireless_cs: small code cleanups Fixed //-style comments, unified conditions that belong together Signed-off-by: Jiri Kosina commit 1471145119a0cae7ed6bb0818779c0cd2d5fb844 Author: Jiri Kosina Date: Thu Jan 18 11:07:40 2007 +0100 ipwireless_cs: make RAS the default channel RAS could be used completely instead of DIALLER channel. This also makes secondary channel unneeded. Signed-off-by: Jiri Kosina commit 490828d4ced805410d08acc46e56410a3aacdaeb Author: Jiri Kosina Date: Mon Jan 15 15:09:11 2007 +0100 ipwireless_cs: make the V3 card ppp connections work V3 card requires the dial commands to be sent on RAS channel and not DIAL channel - when being sent on DIAL channel, the PPP framer on the card is not configured properly. Signed-off-by: Jiri Kosina commit 6b373c6676410f3ef274fbb999d2a3985e1cfdfa Author: Jiri Kosina Date: Fri Jan 12 16:18:18 2007 +0100 ipwireless-cs: some code cleanup The code doesn't comply with CodingStyle too much. Push it a bit in the right direction. Signed-off-by: Jiri Kosina commit 458a99cfbf7fecc51985116233b420e90efc2dfe Author: Jiri Kosina Date: Fri Jan 12 13:31:50 2007 +0100 ipwireless_cs: initial code merge into the tree Signed-off-by: Jiri Kosina drivers/char/pcmcia/Kconfig | 8 drivers/char/pcmcia/Makefile | 4 drivers/char/pcmcia/ipwireless_cs_hardware.c | 1606 ++++++++++++++++++++ drivers/char/pcmcia/ipwireless_cs_hardware.h | 67 + drivers/char/pcmcia/ipwireless_cs_main.c | 524 +++++++ drivers/char/pcmcia/ipwireless_cs_main.h | 77 + drivers/char/pcmcia/ipwireless_cs_network.c | 483 ++++++ drivers/char/pcmcia/ipwireless_cs_network.h | 46 + drivers/char/pcmcia/ipwireless_cs_setup_protocol.h | 118 + drivers/char/pcmcia/ipwireless_cs_tty.c | 779 ++++++++++ drivers/char/pcmcia/ipwireless_cs_tty.h | 52 + 11 files changed, 3764 insertions(+), 0 deletions(-) diff --git a/drivers/char/pcmcia/Kconfig b/drivers/char/pcmcia/Kconfig index 27c1179..8dd6a0c 100644 --- a/drivers/char/pcmcia/Kconfig +++ b/drivers/char/pcmcia/Kconfig @@ -41,6 +41,14 @@ config CARDMAN_4040 PC/SC ifdhandler provided from the Omnikey homepage (http://www.omnikey.com/), or a current development version of OpenCT (http://www.opensc.org/). +config IPWIRELESS_CS + tristate "IPWireless 3G UMTS PCMCIA card support" + depends on PCMCIA + select PPP + help + This is a driver for 3G UMTS PCMCIA card from IPWireless company. In + some countries (for example Czech Republic, T-Mobile ISP) this card + is shipped for service called UMTS 4G. endmenu diff --git a/drivers/char/pcmcia/Makefile b/drivers/char/pcmcia/Makefile index 0aae209..f59c399 100644 --- a/drivers/char/pcmcia/Makefile +++ b/drivers/char/pcmcia/Makefile @@ -4,6 +4,10 @@ # # Makefile for the Linux PCMCIA char device drivers. # +ipwireless_cs-objs := ipwireless_cs_hardware.o ipwireless_cs_main.o ipwireless_cs_network.o ipwireless_cs_tty.o + obj-$(CONFIG_SYNCLINK_CS) += synclink_cs.o obj-$(CONFIG_CARDMAN_4000) += cm4000_cs.o obj-$(CONFIG_CARDMAN_4040) += cm4040_cs.o +obj-$(CONFIG_IPWIRELESS_CS) += ipwireless_cs.o + diff --git a/drivers/char/pcmcia/ipwireless_cs_hardware.c b/drivers/char/pcmcia/ipwireless_cs_hardware.c new file mode 100644 index 0000000..0249e83 --- /dev/null +++ b/drivers/char/pcmcia/ipwireless_cs_hardware.c @@ -0,0 +1,1606 @@ +/* + * IPWireless 3G PCMCIA Network Driver + * + * Original code + * by Stephen Blackheath , + * Ben Martel + * + * Copyrighted as follows: + * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) + * + * Various driver changes and rewrites, port to new kernels + * Copyright (C) 2006-2007 Jiri Kosina + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipwireless_cs_hardware.h" +#include "ipwireless_cs_setup_protocol.h" +#include "ipwireless_cs_network.h" +#include "ipwireless_cs_main.h" + +/* Function prototypes */ +static void do_setup_hardware(struct ipw_hardware_t *ipw); +static void handle_received_SETUP_packet(struct ipw_hardware_t *ipw, + unsigned int address, + u_char * data, int len, + int is_last); +static void ipwireless_setup_timer(unsigned long data); +static void do_io(struct ipw_hardware_t *hw); +static void do_close_hardware(struct ipw_hardware_t *hw); +static int is_card_present(struct ipw_hardware_t *hw); + +/*#define TIMING_DIAGNOSTICS*/ + +#ifdef TIMING_DIAGNOSTICS + +static unsigned long last_report_time; +static unsigned long read_time; +static unsigned long write_time; +static unsigned long read_bytes; +static unsigned long write_bytes; +static void report_timing(void) +{ + unsigned long since = jiffies - last_report_time; + /* If it's been more than one second... */ + if (since >= HZ) { + int first = last_report_time == 0; + last_report_time = jiffies; + if (!first) { + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": %u us elapsed - read %lu bytes in %u us, wrote %lu bytes in %u us\n", + jiffies_to_usecs(since), read_bytes, + jiffies_to_usecs(read_time), write_bytes, + jiffies_to_usecs(write_time)); + } + read_time = write_time = 0; + read_bytes = write_bytes = 0; + } +} +#endif + + +/* Imported IPW definitions */ + +#define LL_MTU_V1 318 +#define LL_MTU_V2 250 +#define LL_MTU_MAX (LL_MTU_V1>LL_MTU_V2?LL_MTU_V1:LL_MTU_V2) + +#define PRIO_DATA 2 +#define PRIO_CTRL 1 +#define PRIO_SETUP 0 + +/* Addresses */ +#define ADDR_SETUP_PROT 0 + +/* Protocol ids */ +enum { + /* Identifier for the Com Data protocol */ + TL_PROTOCOLID_COM_DATA = 0, + + /* Identifier for the Com Control protocol */ + TL_PROTOCOLID_COM_CTRL = 1, + + /* Identifier for the Setup protocol */ + TL_PROTOCOLID_SETUP = 2 +}; +/* Number of bytes in NL packet header (can not do + * sizeof(NLPacketHeader) since it's a bitfield) */ +#define NL_FIRST_PACKET_HEADER_SIZE 3 + +/* Number of bytes in NL packet header (can not do + * sizeof(NLPacketHeader) since it's a bitfield) */ +#define NL_FOLLOWING_PACKET_HEADER_SIZE 1 + +typedef struct NLFirstPacketHeader { +#if defined(__BIG_ENDIAN) + unsigned int firstLast:2; + unsigned int address:3; + unsigned int protocol:3; +#else + unsigned int protocol:3; + unsigned int address:3; + unsigned int firstLast:2; +#endif + u_char length_lsb; + u_char length_msb; +} __attribute__ ((__packed__)) NLFirstPacketHeader; + +typedef struct NLPacketHeader { +#if defined(__BIG_ENDIAN) + unsigned int firstLast:2; + unsigned int address:3; + unsigned int protocol:3; +#else + unsigned int protocol:3; + unsigned int address:3; + unsigned int firstLast:2; +#endif +} __attribute__ ((__packed__)) NLPacketHeader; + +/* Value of 'firstLast' above */ +#define NL_INTERMEDIATE_PACKET 0x0 +#define NL_LAST_PACKET 0x1 +#define NL_FIRST_PACKET 0x2 + +typedef union NLPacket { + /* Network packet header of the first packet (yes, a special case!!!!) */ + struct NLFirstPacketHeader hdrFirst; + /* Network packet header of the following packets (if any) */ + struct NLPacketHeader hdr; + /* Complete network packet (header + data) */ + u_char rawpkt[LL_MTU_MAX]; +} __attribute__ ((__packed__)) NLPacket, *PNLPacket; + +#define HW_VERSION_UNKNOWN -1 +#define HW_VERSION_1 1 +#define HW_VERSION_2 2 + +/* IPW I/O ports */ +#define IOIER 0x00 /* Interrupt Enable Register */ +#define IOIR 0x02 /* Interrupt Source/ACK register */ +#define IODCR 0x04 /* Data Control Register */ +#define IODRR 0x06 /* Data Read Register */ +#define IODWR 0x08 /* Data Write Register */ +#define IOESR 0x0A /* Embedded Driver Status Register */ +#define IORXR 0x0C /* Rx Fifo Register (Host to Embedded) */ +#define IOTXR 0x0E /* Tx Fifo Register (Embedded to Host) */ + +/* I/O ports and bit definitions for version 1 of the hardware */ + +/* IER bits*/ +#define IER_RXENABLED ((u_short) 0x1) +#define IER_TXENABLED ((u_short) 0x2) + +/* ISR bits */ +#define IR_RXINTR ((u_short) 0x1) +#define IR_TXINTR ((u_short) 0x2) + +/* DCR bits */ +#define DCR_RXDONE ((u_short) 0x1) +#define DCR_TXDONE ((u_short) 0x2) +#define DCR_RXRESET ((u_short) 0x4) +#define DCR_TXRESET ((u_short) 0x8) + +/* I/O ports and bit definitions for version 2 of the hardware */ + +typedef struct { + u_short PCCOR; /* Configuration Option Register */ + u_short PCCSR; /* Configuration and Status Register */ + u_short PCPRR; /* Pin Replacemant Register */ + u_short PCSCR; /* Socket and Copy Register */ + u_short PCESR; /* Extendend Status Register */ + u_short PCIOB; /* I/O Base Register */ +} MEMCCR; + +typedef struct { + u_short MemTX_OLD; /* TX Register (R/W) */ + u_short Pad1; + u_short MemRXDone; /* RXDone Register (R/W) */ + u_short Pad2; + u_short MemRX; /* RX Register (R/W) */ + u_short Pad3; + u_short MemPCIntAck; /* PC interrupt Ack Register (W) */ + u_short Pad4; + u_long MemCardPresent; /* Mask for Host to check (R) for CARD_PRESENT_VALUE */ + u_short MemTX_NEW; /* TX2 (new) Register (R/W) */ +} MEMINFREG; + +#define IODMADPR 0x00 /* DMA Data Port Register (R/W) */ + +#define CARD_PRESENT_VALUE ((u_long)0xBEEFCAFEUL) + +#define MEMTX_TX 0x0001 +#define MEMRX_RX 0x0001 +#define MEMRX_RX_DONE 0x0001 +#define MEMRX_PCINTACKK 0x0001 +#define MEMRX_MEMSPURIOUSINT 0x0001 + +#define NL_NUM_OF_PRIORITIES 3 +#define NL_NUM_OF_PROTOCOLS 3 +#define NL_NUM_OF_ADDRESSES NO_OF_IPW_CHANNELS + +typedef struct ipw_assembler_t { + struct ipw_rx_packet_t *packet; +} ipw_assembler_t; + +typedef struct ipw_hardware_t { + unsigned int base_port; + short hwVersion; + unsigned short llMTU; + struct ipw_config_t config; + spinlock_t spinlock; + + int initializing; + int init_loops; + struct timer_list setup_timer; + + int tx_ready; + struct list_head tx_queue[NL_NUM_OF_PRIORITIES]; + /* True if any packets are queued for transmission */ + int tx_queued; + + int rx_bytes_queued; + struct list_head rx_queue; + /* Pool of rx_packet structures that are not currently used. */ + struct list_head rx_pool; + /* True if reception of data is blocked while userspace processes it. */ + int blocking_rx; + /* True if there is RX data ready on the hardware. */ + int rx_ready; + u_short last_memtx_serial; + /* Newer versions of the V2 card firmware + * send serial numbers in the MemTX register, because we had a problem + * on a loaded system with the 'Timer Recovery' logic, which was + * introduced into the card firmware to work around hardware problems + * on certain systems. On these systems (including my IBM Thinkpad + * R50e) the MemTX signal occasionally gets lost by the hardware + * somewhere. 'Timer Recovery' causes the signal to be re-sent if not + * acknowledged after 50 milliseconds. Unfortunately if the CPU is + * slow or heavily loaded, then Timer Recovery can fire, causing the + * driver to get out of synch with the card. (The driver thinks there + * is one more packet available than there really is.) To solve this, + * IPWireless added a serial number to the MemTX signal, which is + * incremented for each packet, but not incremented for Timer Recovery + * re-sends. 'serial_number_detected' is set true when we detect a + * non-zero serial number (indicating the new firmware). Thereafter, + * the driver can safely ignore the Timer Recovery re-sends, and the + * out-of-synch problem is solved. */ + int serial_number_detected; + struct work_struct work_rx; + + /* True if we are to send the set-up data to the hardware. */ + int to_setup; + + /* Card has been removed */ + int removed; + /* Saved irq value when we disable the interrupt. */ + int irq; + /* True if some thread is performing hardware I/O */ + int doing_io; + int in_process_context_loop; + /* True if this driver is shutting down. */ + int shutting_down; + /* Modem control lines */ + unsigned int control_lines[NL_NUM_OF_ADDRESSES]; + ipw_assembler_t packet_assembler[NL_NUM_OF_ADDRESSES]; + + struct tasklet_struct tasklet; + int tasklet_pending; + + /* The handle for the network layer, for the sending of events to it. */ + struct ipw_network_t *network; + MEMINFREG IPWIRELESS_IOMEM *memInfReg; + MEMCCR IPWIRELESS_IOMEM *memCCR; + int bad_interrupt_count; + ipw_reboot_callback_t reboot_callback; + void *reboot_callback_data; + + u_short *MemTX; +} ipw_hardware_t; + +struct ipw_tx_packet_t; + +typedef void (*packet_sent_callback_t) (struct ipw_hardware_t * ipw, + struct ipw_tx_packet_t * packet); + +/* Packet info structure for tx packets. + * Note: not all the fields defined here are required for all protocols */ +typedef struct ipw_tx_packet_t { + struct list_head queue; + /* channel idx + 1 */ + u_char dest_addr; + /* SETUP, CTRL or DATA */ + u_char protocol; + /* Length of data block, which starts at the end of this structure */ + u_short length; + /* Function to call upon packet completion. */ + packet_sent_callback_t packet_sent_callback; + /* Sending state */ + /* Offset of where we've sent up to so far */ + u_long offset; + /* Count of packet fragments, starting at 0 */ + int fragment_count; + + ipw_packet_sent_callback_t external_callback; + void *external_callback_data; +} ipw_tx_packet_t; + +/* Signals from DTE */ +typedef enum ComCtrl_DTESignal { ComCtrl_RTS = 0, ComCtrl_DTR = 1 +} ComCtrl_DTESignal; + +/* Signals from DCE */ +typedef enum ComCtrl_DCESignal { ComCtrl_CTS = 2, ComCtrl_DCD = + 3, ComCtrl_DSR = 4, ComCtrl_RI = 5 +} ComCtrl_DCESignal; + +typedef struct ipw_control_packet_body_t { + + /* ComCtrl_DTESignal or ComCtrl_DCESignal */ + u_char sigNo; + /* ComCtrl_SET(0) or ComCtrl_CLEAR(1) */ + u_char value; +} __attribute__ ((__packed__)) ipw_control_packet_body_t; + +typedef struct ipw_control_packet_t { + struct ipw_tx_packet_t packet; + struct ipw_control_packet_body_t body; +} ipw_control_packet_t; + +typedef struct ipw_rx_packet_t { + struct list_head queue; + unsigned int capacity; + unsigned int length; + unsigned int channelIdx; +} ipw_rx_packet_t; + + +#ifdef IPWIRELESS_STATE_DEBUG +int ipwireless_dump_hardware_state(char *p, ipw_hardware_t * hw) +{ + int idx = 0; + + idx += + sprintf(p + idx, "debug: initializing=%d\n", hw->initializing); + idx += sprintf(p + idx, "debug: tx_ready=%d\n", hw->tx_ready); + idx += sprintf(p + idx, "debug: tx_queued=%d\n", hw->tx_queued); + idx += sprintf(p + idx, "debug: rx_ready=%d\n", hw->rx_ready); + idx += + sprintf(p + idx, "debug: rx_bytes_queued=%d\n", + hw->rx_bytes_queued); + idx += + sprintf(p + idx, "debug: blocking_rx=%d\n", hw->blocking_rx); + idx += sprintf(p + idx, "debug: removed=%d\n", hw->removed); + idx += sprintf(p + idx, "debug: doing_io=%d\n", hw->doing_io); + idx += + sprintf(p + idx, "debug: in_process_context_loop=%d\n", + hw->in_process_context_loop); + idx += + sprintf(p + idx, "debug: tasklet_pending=%d\n", + hw->tasklet_pending); + idx += + sprintf(p + idx, "debug: hardware.shutting_down=%d\n", + hw->shutting_down); + return idx; +} +#endif + + + +char *data_type(const u_char * buf, unsigned length) +{ + NLPacketHeader *hdr = (NLPacketHeader *) buf; + if (length == 0) + return " "; + + if (hdr->firstLast & NL_FIRST_PACKET) { + if (hdr->protocol == TL_PROTOCOLID_COM_DATA) + return "DATA "; + if (hdr->protocol == TL_PROTOCOLID_COM_CTRL) + return "CTRL "; + if (hdr->protocol == TL_PROTOCOLID_SETUP) + return "SETUP"; + return "???? "; + } else + return " "; +} + +#define DUMP_MAX_BYTES 64 + +/** + * Send a fragment of a packet. + */ +static int +do_send_fragment(ipw_hardware_t * hw, const u_char * data, unsigned length) +{ + int i; + +#ifdef TIMING_DIAGNOSTICS + unsigned long start_time = jiffies; +#endif + + /* If length is 0, then there is no work to do - return success. */ + if (length == 0) + return 0; + + /* If length is greater than our MTU, then we fail. */ + if (length > hw->llMTU) + return -1; + + if (ipwireless_cs_debug) { + char buf[DUMP_MAX_BYTES * 4 + 16]; + int i; + buf[0] = 0; + for (i = 0; i < length && i < DUMP_MAX_BYTES; i++) + sprintf(buf + strlen(buf), " %x ", + (unsigned int) data[i]); + if (i > DUMP_MAX_BYTES) + strcat(buf, "..."); + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": send fragment %s%s\n", data_type(data, length), + buf); + } + + if (hw->hwVersion == HW_VERSION_1) { + outw((u_short) length, hw->base_port + IODWR); + + for (i = 0; i < length; i += 2) { + u_short d = data[i]; + if (i + 1 < length) + d |= (((u_short) data[i + 1]) << 8); + outw(d, hw->base_port + IODWR); + } + + outw(DCR_TXDONE, hw->base_port + IODCR); + } else if (hw->hwVersion == HW_VERSION_2) { + outw((u_short) length, hw->base_port + IODMADPR); + + for (i = 0; i < length; i += 2) { + u_short d = data[i]; + if (i + 1 < length) + d |= (((u_short) data[i + 1]) << 8); + outw(d, hw->base_port + IODMADPR); + } + while ((i & 3) != 2) { + outw((u_short) 0xDEAD, hw->base_port + IODMADPR); + i += 2; + } + iowrite16(MEMRX_RX, &hw->memInfReg->MemRX); + } +#ifdef TIMING_DIAGNOSTICS + write_time += (jiffies - start_time); + write_bytes += length + 2; + report_timing(); +#endif + + return 0; +} + +/* Called with spinlock in force. It unlocks, then re-locks it. */ +static int do_send_packet(ipw_hardware_t * hw, ipw_tx_packet_t * packet) +{ + u_short fragment_data_len; + u_short data_left = packet->length - packet->offset; + NLPacket pkt; + u_short header_size; + + spin_unlock_irq(&hw->spinlock); + header_size = + packet->fragment_count == + 0 ? NL_FIRST_PACKET_HEADER_SIZE : + NL_FOLLOWING_PACKET_HEADER_SIZE; + fragment_data_len = (u_short) hw->llMTU - header_size; + if (data_left < fragment_data_len) + fragment_data_len = data_left; + + pkt.hdrFirst.protocol = packet->protocol; + pkt.hdrFirst.address = packet->dest_addr; + pkt.hdrFirst.firstLast = 0; + + /* First packet? */ + if (packet->fragment_count == 0) { + pkt.hdrFirst.firstLast |= NL_FIRST_PACKET; + pkt.hdrFirst.length_lsb = (u_char) packet->length; + pkt.hdrFirst.length_msb = (u_char) (packet->length >> 8); + } + + memcpy(pkt.rawpkt + header_size, + ((u_char *) packet) + sizeof(ipw_tx_packet_t) + + packet->offset, fragment_data_len); + packet->offset += fragment_data_len; + packet->fragment_count++; + + /* Last packet? (May also be first packet.) */ + if (packet->offset == packet->length) + pkt.hdrFirst.firstLast |= NL_LAST_PACKET; + do_send_fragment(hw, pkt.rawpkt, header_size + fragment_data_len); + + /* If this packet has unsent data, then re-queue it. */ + if (packet->offset < packet->length) { + spin_lock_irq(&hw->spinlock); + /* Re-queue it at the head of the highest priority queue so it goes before + * all other packets */ + list_add(&packet->queue, &hw->tx_queue[0]); + } else { + (*packet->packet_sent_callback) (hw, packet); + spin_lock_irq(&hw->spinlock); + } + + return 0; +} + +/* + * Free the specified packet. Can be used as a 'packet_sent_callback' + * to free the packet once it has been sent. + */ +static void free_packet(ipw_hardware_t * hw, + struct ipw_tx_packet_t *packet) +{ + kfree(packet); +} + +/* + * Free the specified packet. Can be used as a 'packet_sent_callback' + * to free the packet once it has been sent. + */ +static void free_packet_and_callback(ipw_hardware_t * hw, + struct ipw_tx_packet_t *packet) +{ + if (packet->external_callback != NULL) { + (*packet->external_callback) (packet->external_callback_data, + packet->length); + } + kfree(packet); +} + +/*! + * If 'packet' is NULL, then this function allocates a new packet, setting its + * length to 0 and ensuring it has the specified minimum amount of free space. + * + * If 'packet' is not NULL, then this function enlarges it if it doesn't + * have the specified minimum amount of free space. + */ +static ipw_rx_packet_t *pool_allocate(ipw_hardware_t * hw, + ipw_rx_packet_t * packet, + int minimum_free_space) +{ + if (packet == NULL) { + /* If this is the first fragment, then we will need to fetch a packet to + * put it in. */ + spin_lock_irq(&hw->spinlock); + /* If we have one in our pool, then pull it out. */ + if (!list_empty(&hw->rx_pool)) { + packet = list_entry(hw->rx_pool.next, ipw_rx_packet_t, queue); + list_del_init(&packet->queue); + spin_unlock_irq(&hw->spinlock); + } + /* Otherwise allocate a new one. */ + else { + static int min_capacity = 256; + int new_capacity; + spin_unlock_irq(&hw->spinlock); + new_capacity = + minimum_free_space > min_capacity ? minimum_free_space : min_capacity; + packet = (ipw_rx_packet_t *) kmalloc(sizeof(ipw_rx_packet_t) + new_capacity, + GFP_ATOMIC); + packet->capacity = new_capacity; + } + packet->length = 0; + } + + /* If this packet does not have sufficient capacity for the data we want to + * add, then make it bigger. */ + if (packet->length + minimum_free_space > packet->capacity) { + ipw_rx_packet_t *old_packet = packet; + packet = (ipw_rx_packet_t *) kmalloc(sizeof(ipw_rx_packet_t) + + old_packet->length + minimum_free_space, + GFP_ATOMIC); + memcpy(packet, old_packet, sizeof(ipw_rx_packet_t) + old_packet->length); + packet->capacity = old_packet->length + minimum_free_space; + kfree(old_packet); + } + + return packet; +} + +static void pool_free(ipw_hardware_t * hw, ipw_rx_packet_t * packet) +{ + static int ticker; + + /* Every now and again we release one instead of pushing it back onto the + * pool. This isn't perfectly efficient, but it prevents us wasting memory. */ + if ((ticker++ & 0x3f) == 0) + kfree(packet); + else + list_add_tail(&packet->queue, &hw->rx_pool); +} + +static void handle_received_DATA_packet(ipw_hardware_t * hw, + unsigned int address, + u_char * data, int length, + int is_last) +{ + unsigned int channelIdx = address - 1; + ipw_assembler_t *assem; + + /* Discard packet if channel index is out of range. */ + if (channelIdx >= NL_NUM_OF_ADDRESSES) { + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": data packet has bad address %d\n", address); + return; + } + + assem = &hw->packet_assembler[channelIdx]; + + /* Create a new packet, or if this assembler already contains one, then + * make larger by 'length' bytes. */ + assem->packet = pool_allocate(hw, assem->packet, length); + assem->packet->channelIdx = channelIdx; + + /* Append this packet data onto existing data. */ + memcpy((u_char *) assem->packet + sizeof(ipw_rx_packet_t) + + assem->packet->length, data, length); + assem->packet->length += length; + + /* If this is the last packet, then send the assembled packet on to the network layer. */ + if (is_last) { + spin_lock_irq(&hw->spinlock); + list_add_tail(&assem->packet->queue, &hw->rx_queue); + hw->rx_bytes_queued += assem->packet->length; + /* Block reception of incoming packets if queue is full. */ + hw->blocking_rx = + hw->rx_bytes_queued >= IPWIRELESS_RX_QUEUE_SIZE; + assem->packet = NULL; + + /* If we are not already inside the process context loop, then delegate + * to it. + */ + if (!hw->in_process_context_loop) { + hw->in_process_context_loop = 1; + spin_unlock_irq(&hw->spinlock); + schedule_work(&hw->work_rx); + } else { + spin_unlock_irq(&hw->spinlock); + } + } +} + +static void do_receive_data_work(struct work_struct *work_rx) +{ + struct ipw_hardware_t *hw = + container_of(work_rx, struct ipw_hardware_t, work_rx); + struct list_head *p, *q; + + spin_lock_irq(&hw->spinlock); + list_for_each_safe(p, q, &hw->rx_queue) { + ipw_rx_packet_t *packet = list_entry(p, ipw_rx_packet_t, queue); + if (hw->shutting_down) + break; + list_del_init(p); + + /* Note: ipwireless_network_packet_received must be called in a process + context (i.e. via schedule_work) because the tty output code can sleep + in the tty_flip_buffer_push call. */ + if (hw->network != NULL) { /* If the network hasn't been disconnected. */ + spin_unlock_irq(&hw->spinlock); + ipwireless_network_packet_received(hw->network, + packet-> channelIdx, + (u_char *) packet + + sizeof (ipw_rx_packet_t), + packet->length); + spin_lock_irq(&hw->spinlock); + } + hw->rx_bytes_queued -= packet->length; + pool_free(hw, packet); + /* Unblock reception of incoming packets if queue is no longer full. */ + hw->blocking_rx = hw->rx_bytes_queued >= IPWIRELESS_RX_QUEUE_SIZE; + if (hw->shutting_down) + break; + + do_io(hw); + } + hw->in_process_context_loop = 0; + spin_unlock_irq(&hw->spinlock); +} + +static void handle_received_CTRL_packet(ipw_hardware_t * hw, + unsigned int address, + u_char * data, int len, + int is_last) +{ + ipw_control_packet_body_t *body = (ipw_control_packet_body_t *) data; + unsigned int channelIdx = address - 1; + unsigned int changed_mask; + + if (len != sizeof(ipw_control_packet_body_t)) { + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": control packet was %d bytes - wrong size!\n", + len); + return; + } + + if (channelIdx >= NL_NUM_OF_ADDRESSES) { + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": control packet has bad address %d\n", address); + return; + } + + switch (body->sigNo) { + case ComCtrl_CTS: + changed_mask = IPW_CONTROL_LINE_CTS; + break; + case ComCtrl_DCD: + changed_mask = IPW_CONTROL_LINE_DCD; + break; + case ComCtrl_DSR: + changed_mask = IPW_CONTROL_LINE_DSR; + break; + case ComCtrl_RI: + changed_mask = IPW_CONTROL_LINE_RI; + break; + default: + changed_mask = 0; + } + + if (changed_mask != 0) { + if (body->value) + hw->control_lines[channelIdx] |= changed_mask; + else + hw->control_lines[channelIdx] &= ~changed_mask; + if (hw->network != NULL) + ipwireless_network_notify_control_line_change(hw->network, + channelIdx, + hw->control_lines[channelIdx], + changed_mask); + } +} + +static void handle_received_packet(ipw_hardware_t * hw, NLPacket * packet, + u_short len) +{ + unsigned int protocol = packet->hdr.protocol; + unsigned int address = packet->hdr.address; + unsigned int header_length; + u_char *data; + unsigned int data_len; + int is_last = packet->hdr.firstLast & NL_LAST_PACKET; + + if (packet->hdr.firstLast & NL_FIRST_PACKET) { + header_length = NL_FIRST_PACKET_HEADER_SIZE; + } else { + header_length = NL_FOLLOWING_PACKET_HEADER_SIZE; + } + data = packet->rawpkt + header_length; + data_len = len - header_length; + switch (protocol) { + case TL_PROTOCOLID_COM_DATA: + handle_received_DATA_packet(hw, address, data, data_len, + is_last); + break; + case TL_PROTOCOLID_COM_CTRL: + handle_received_CTRL_packet(hw, address, data, data_len, + is_last); + break; + case TL_PROTOCOLID_SETUP: + handle_received_SETUP_packet(hw, address, data, data_len, + is_last); + break; + } +} + +/* + * Retrieve a packet from the IPW hardware. + */ +static void do_receive_packet(ipw_hardware_t * hw) +{ + u_short len; + unsigned int i; + u_char pkt[LL_MTU_MAX]; +#ifdef TIMING_DIAGNOSTICS + unsigned long start_time = jiffies; +#endif + + if (hw->hwVersion == HW_VERSION_1) { + len = inw(hw->base_port + IODRR); + if (len > hw->llMTU) { + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": received a packet of %d bytes - longer than the MTU!\n", + len); + outw(DCR_RXDONE | DCR_RXRESET, + hw->base_port + IODCR); + return; + } + + for (i = 0; i < len; i += 2) { + u_short data = inw(hw->base_port + IODRR); + pkt[i] = (u_char) data; + pkt[i + 1] = (u_char) (data >> 8); + } + } else { + len = inw(hw->base_port + IODMADPR); + if (len > hw->llMTU) { + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": received a packet of %d bytes - longer than the MTU!\n", + len); + iowrite16(MEMRX_PCINTACKK, + &hw->memInfReg->MemPCIntAck); + return; + } + + for (i = 0; i < len; i += 2) { + u_short data = inw(hw->base_port + IODMADPR); + pkt[i] = (u_char) data; + pkt[i + 1] = (u_char) (data >> 8); + } + + while ((i & 3) != 2) { + inw(hw->base_port + IODMADPR); + i += 2; + } + } + + if (ipwireless_cs_debug) { + char buf[DUMP_MAX_BYTES * 4 + 16]; + int i; + buf[0] = 0; + for (i = 0; i < len && i < DUMP_MAX_BYTES; i++) + sprintf(buf + strlen(buf), " %x ", + (unsigned int) pkt[i]); + if (i > DUMP_MAX_BYTES) + strcat(buf, "..."); + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": recv fragment %s%s\n", data_type(pkt, len), buf); + } + + handle_received_packet(hw, (NLPacket *) pkt, len); + /* ACK reading */ + if (hw->hwVersion == HW_VERSION_1) { + outw(DCR_RXDONE, hw->base_port + IODCR); + } else { + iowrite16(MEMRX_PCINTACKK, &hw->memInfReg->MemPCIntAck); + } +#ifdef TIMING_DIAGNOSTICS + read_time += (jiffies - start_time); + read_bytes += len + 2; + report_timing(); +#endif +} + +/* + * Send queued packets. Must be called with spin_lock locked. + * May be called either from a interrupt tasklet, or from a process context. + */ +static void do_io(ipw_hardware_t * hw) +{ + int no_data_sent = 0; + + /* If another thread is already performing I/O on the hardware, + * then we need not do it on this thread, so exit. */ + if (hw->doing_io || hw->shutting_down) + return; + + /* Tell other threads we have exclusive access to the hardware. */ + hw->doing_io = 1; + while (1) { + int no_data_sent_last_time = no_data_sent; + no_data_sent = 0; + + /* If we are to send setup data to the hardware... */ + if (hw->to_setup == 1) { + hw->to_setup = 2; + spin_unlock_irq(&hw->spinlock); + do_setup_hardware(hw); + spin_lock_irq(&hw->spinlock); + } else + /* If there is incoming data to be retrieved, retrieve it. */ + if (hw->rx_ready && !hw->blocking_rx) { + hw->rx_ready--; + spin_unlock_irq(&hw->spinlock); + do_receive_packet(hw); + spin_lock_irq(&hw->spinlock); + } else if (hw->tx_queued && hw->tx_ready != 0) { + int another_packet_to_send = 0; + int priority; + + /* If we're initializing, don't send anything of higher priority than + PRIO_SETUP. The network layer therefore need not care about hardware + initialization - any of its stuff will simply be queued until setup + is complete. */ + int priority_limit = (hw->to_setup || + hw-> initializing) ? PRIO_SETUP + 1 : NL_NUM_OF_PRIORITIES; + + hw->tx_ready--; + + no_data_sent = 1; + for (priority = 0; priority < priority_limit; + priority++) + if (!list_empty (&hw->tx_queue[priority])) { + struct list_head *p = hw->tx_queue[priority].next; + + ipw_tx_packet_t *packet = list_entry(p, ipw_tx_packet_t, queue); + list_del_init(p); + + no_data_sent = 0; + + /* do_send_packet() unlocks, then re-locks the spinlock. */ + do_send_packet(hw, packet); + break; + } + + /* Now that we've sent (or not sent) a packet, see if there's any more to + * send after that (include all data, including non-setup data if we are + * setting up. */ + for (priority = 0; priority < NL_NUM_OF_PRIORITIES; + priority++) + if (!list_empty (&hw->tx_queue[priority])) { + another_packet_to_send = 1; + break; + } + + /* If there are no more packets queued to be sent after the present one... */ + if (!another_packet_to_send) { + hw->tx_queued = 0; + } + + /* The logic above would produce an endless loop when hw->to_setup is non-zero, + * and there is non-setup data (>PRIO_SETUP) queued, but no PRIO_SETUP data. + * The if below protects against this. */ + if (no_data_sent && no_data_sent_last_time) + break; + } + /* If we've run out of things to do, then stop looping. */ + else + break; + } + hw->doing_io = 0; +} + +static void ipwireless_do_tasklet(unsigned long hw_) +{ + ipw_hardware_t *hw = (ipw_hardware_t *) hw_; + + spin_lock_irq(&hw->spinlock); + do_io(hw); + hw->tasklet_pending = 0; + spin_unlock_irq(&hw->spinlock); +} + +/*! + * @return true if the card is physically present. + */ +static int is_card_present(ipw_hardware_t * hw) +{ + if (hw->hwVersion == HW_VERSION_1) + return inw(hw->base_port + IOIR) != (u_short) 0xFFFF; + else + return ioread32(&hw->memInfReg->MemCardPresent) == + CARD_PRESENT_VALUE; +} + +static irqreturn_t ipwireless_handle_v1_interrupt(int irq, ipw_hardware_t *hw) +{ + u_short irqn; + u_short ack; + + irqn = inw(hw->base_port + IOIR); + + if (irqn == (u_short) 0xFFFF) { + if (++hw->bad_interrupt_count >= 100) { + /* It is necessary to disable the interrupt at this point, or the kernel + * hangs, interrupting repeatedly forever. */ + hw->irq = irq; + hw->removed = 1; + disable_irq_nosync(irq); + printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME + ": Mr. Fluffy is not happy!\n"); + } + return IRQ_HANDLED; + } else if (irqn != 0) { + ack = 0; + /* Transmit complete. */ + if (irqn & IR_TXINTR) { + hw->tx_ready++; + ack |= IR_TXINTR; + } + + /* Received data */ + if (irqn & IR_RXINTR) { + ack |= IR_RXINTR; + hw->rx_ready++; + } + if (ack != 0) { + outw(ack, hw->base_port + IOIR); + + /* Perform the I/O retrieval in a tasklet, because the ppp_generic + may be called from a tasklet, but not from a hardware interrupt. */ + + if (!hw->tasklet_pending) { + hw->tasklet_pending = 1; + tasklet_schedule(&hw->tasklet); + } + } + return IRQ_HANDLED; + } else + return IRQ_NONE; + +} + +static irqreturn_t ipwireless_handle_v2_v3_interrupt(int irq, ipw_hardware_t *hw) +{ + int tx = 0; + int rx = 0; + int rx_repeat = 0; + u_short memtx = ioread16(hw->MemTX); + u_short memtx_serial; + u_short memrxdone = ioread16(&hw->memInfReg->MemRXDone); + + /* check whether the interrupt was generated by ipwireless card */ + if (!(memtx & MEMTX_TX) && !(memrxdone & MEMRX_RX_DONE)) + return IRQ_NONE; + + /* See if the card is physically present. Note that while it is + * powering up, it appears not to be present. */ + if (ioread32(&hw->memInfReg->MemCardPresent) != CARD_PRESENT_VALUE) { + u_short csr = ioread16(&hw->memCCR->PCCSR); + csr &= 0xfffd; + iowrite16(csr, &hw->memCCR->PCCSR); + return IRQ_HANDLED; + } + + memtx_serial = memtx & (u_short) 0xff00; + if (memtx & MEMTX_TX) { + iowrite16(0, hw->MemTX); + + if (hw->serial_number_detected) { + if (memtx_serial != hw->last_memtx_serial) { + hw->last_memtx_serial = memtx_serial; + hw->rx_ready++; + rx = 1; + } else + rx_repeat = 1; /* Ignore 'Timer Recovery' duplicates. */ + } else { + /* If a non-zero serial number is seen, then enable serial number + * checking. */ + if (memtx_serial != 0) { + hw->serial_number_detected = 1; + printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME + ": MemTX serial number detected\n"); + } + hw->rx_ready++; + rx = 1; + } + } + if (memrxdone & MEMRX_RX_DONE) { + iowrite16(0, &hw->memInfReg->MemRXDone); + hw->tx_ready++; + tx = 1; + } + if (tx) + iowrite16(MEMRX_PCINTACKK, &hw->memInfReg->MemPCIntAck); + + /* Acknowledge interrupt at PCMCIA level. */ + iowrite16(ioread16(&hw->memCCR->PCCSR) & 0xfffd, &hw->memCCR->PCCSR); + + if (tx || rx) { + /* Perform the I/O retrieval in a tasklet, because the ppp_generic + may be called from a tasklet, but not from a hardware interrupt. */ + if (!hw->tasklet_pending) { + hw->tasklet_pending = 1; + tasklet_schedule(&hw->tasklet); + } + } else if (!rx_repeat) { + if (hw->MemTX == &hw->memInfReg->MemTX_NEW) { + printk(KERN_WARNING IPWIRELESS_PCCARD_NAME + ": No valid MemTX value - switching to the old MemTX register address.\n"); + hw->MemTX = &hw->memInfReg->MemTX_OLD; + goto out; + } else { + printk(KERN_WARNING IPWIRELESS_PCCARD_NAME ": spurious interrupt!\n"); + hw->irq = irq; + hw->removed = 1; + disable_irq_nosync(irq); + } + } +out: + return IRQ_HANDLED; +} + +irqreturn_t ipwireless_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + ipw_hardware_t *hw = (ipw_hardware_t *) dev_id; + + if (hw->hwVersion == HW_VERSION_1) + return ipwireless_handle_v1_interrupt(irq, hw); + else + return ipwireless_handle_v2_v3_interrupt(irq, hw); +} + +static void send_packet(ipw_hardware_t * hw, int priority, + ipw_tx_packet_t * packet) +{ + struct list_head *p = &hw->tx_queue[priority]; + + spin_lock_irq(&hw->spinlock); + list_add_tail(&packet->queue, p); + hw->tx_queued = 1; + /* If we happen to have got here as a result of processing from the do_io() + loop, then do_io() will detect this situation and do nothing. */ + do_io(hw); + spin_unlock_irq(&hw->spinlock); +} + +static ipw_tx_packet_t *create_packet(int packet_size, + u_char dest_addr, + u_char protocol, + packet_sent_callback_t + packet_sent_callback) +{ + ipw_tx_packet_t *packet = kmalloc(packet_size, GFP_ATOMIC); + memset(packet, 0, packet_size); + INIT_LIST_HEAD(&packet->queue); + packet->dest_addr = dest_addr; + packet->protocol = protocol; + packet->packet_sent_callback = packet_sent_callback; + packet->length = packet_size - sizeof(ipw_tx_packet_t); + return packet; +} + +void +ipwireless_send_packet(ipw_hardware_t * hw, unsigned int channelIdx, + u_char * data, unsigned int length, + ipw_packet_sent_callback_t callback, + void *callback_data) +{ + ipw_tx_packet_t *packet; + unsigned int flags; + + local_irq_save(flags); + packet = create_packet(sizeof(ipw_tx_packet_t) + length, + (u_char) (channelIdx + 1), + TL_PROTOCOLID_COM_DATA, + free_packet_and_callback); + packet->external_callback = callback; + packet->external_callback_data = callback_data; + memcpy((u_char *) packet + sizeof(ipw_tx_packet_t), data, length); + send_packet(hw, PRIO_DATA, packet); + local_irq_restore(flags); +} + +static void setControlLine(ipw_hardware_t * hw, int gfp_flag, + unsigned int channelIdx, int line, int state) +{ + ipw_control_packet_t *packet = (ipw_control_packet_t *) + create_packet(sizeof(ipw_control_packet_t), + (u_char) (channelIdx + 1), + TL_PROTOCOLID_COM_CTRL, + free_packet); + packet->packet.length = sizeof(ipw_control_packet_body_t); + packet->body.sigNo = (u_char) line; + packet->body.value = (u_char) (state == 0 ? 0 : 1); + send_packet(hw, PRIO_CTRL, &packet->packet); +} + +static void setDTR(ipw_hardware_t * hw, int priority, + unsigned int channelIdx, int state) +{ + if (state != 0) + hw->control_lines[channelIdx] |= IPW_CONTROL_LINE_DTR; + else + hw->control_lines[channelIdx] &= ~IPW_CONTROL_LINE_DTR; + + setControlLine(hw, priority, channelIdx, ComCtrl_DTR, state); +} + +static void setRTS(ipw_hardware_t * hw, int priority, + unsigned int channelIdx, int state) +{ + if (state != 0) + hw->control_lines[channelIdx] |= IPW_CONTROL_LINE_RTS; + else + hw->control_lines[channelIdx] &= ~IPW_CONTROL_LINE_RTS; + + setControlLine(hw, priority, channelIdx, ComCtrl_RTS, state); +} + +void ipwireless_setDTR(ipw_hardware_t * hw, unsigned int channelIdx, + int state) +{ + setDTR(hw, PRIO_CTRL, channelIdx, state); +} + +void ipwireless_setRTS(ipw_hardware_t * hw, unsigned int channelIdx, + int state) +{ + setRTS(hw, PRIO_CTRL, channelIdx, state); +} + +typedef struct ipw_setup_get_version_query_packet_t { + ipw_tx_packet_t packet; + TlSetupGetVersionQry body; +} ipw_setup_get_version_query_packet_t; + +typedef struct ipw_setup_config_packet_t { + ipw_tx_packet_t packet; + TlSetupConfigMsg body; +} ipw_setup_config_packet_t; + +typedef struct ipw_setup_config_done_packet_t { + ipw_tx_packet_t packet; + TlSetupConfigDoneMsg body; +} ipw_setup_config_done_packet_t; + +typedef struct ipw_setup_open_packet_t { + ipw_tx_packet_t packet; + TlSetupOpenMsg body; +} ipw_setup_open_packet_t; + +typedef struct ipw_setup_info_packet_t { + ipw_tx_packet_t packet; + TlSetupInfoMsg body; +} ipw_setup_info_packet_t; + +typedef struct { + ipw_tx_packet_t packet; + TlSetupRebootMsgAck body; +} ipw_setup_reboot_msg_ack_t; + +/* This handles the actual initialization of the card */ +static void __handle_setup_get_version_rsp(ipw_hardware_t *hw) +{ + ipw_setup_config_packet_t *config_packet; + ipw_setup_config_done_packet_t *config_done_packet; + ipw_setup_open_packet_t *open_packet; + ipw_setup_info_packet_t *info_packet; + int port; + unsigned int channelIdx; + + /* generate config packet */ + for (port = 1; port <= NL_NUM_OF_ADDRESSES; port++) { + config_packet = (ipw_setup_config_packet_t *) + create_packet(sizeof(ipw_setup_config_packet_t), + ADDR_SETUP_PROT, + TL_PROTOCOLID_SETUP, + free_packet); + config_packet->packet. length = sizeof(TlSetupConfigMsg); + config_packet->body.sigNo = TL_SETUP_SIGNO_CONFIG_MSG; + config_packet->body.portNo = port; + config_packet->body.prioData = PRIO_DATA; + config_packet->body.prioCtrl = PRIO_CTRL; + send_packet(hw, PRIO_SETUP, &config_packet->packet); + } + config_done_packet = (ipw_setup_config_done_packet_t *) + create_packet(sizeof(ipw_setup_config_done_packet_t), + ADDR_SETUP_PROT, + TL_PROTOCOLID_SETUP, + free_packet); + config_done_packet->packet.length = sizeof(TlSetupConfigDoneMsg); + config_done_packet->body.sigNo = TL_SETUP_SIGNO_CONFIG_DONE_MSG; + send_packet(hw, PRIO_SETUP, &config_done_packet->packet); + + /* generate open packet */ + for (port = 1; port <= NL_NUM_OF_ADDRESSES; port++) { + open_packet = (ipw_setup_open_packet_t *) + create_packet(sizeof(ipw_setup_open_packet_t), + ADDR_SETUP_PROT, + TL_PROTOCOLID_SETUP, + free_packet); + open_packet->packet.length = sizeof(TlSetupOpenMsg); + open_packet->body.sigNo = TL_SETUP_SIGNO_OPEN_MSG; + open_packet->body.portNo = port; + send_packet(hw, PRIO_SETUP, &open_packet->packet); + } + for (channelIdx = 0; channelIdx < NL_NUM_OF_ADDRESSES; channelIdx++) { + setDTR(hw, PRIO_SETUP, channelIdx, + (hw->control_lines[channelIdx] & IPW_CONTROL_LINE_DTR) != 0); + setRTS(hw, PRIO_SETUP, + channelIdx, (hw->control_lines [channelIdx] & IPW_CONTROL_LINE_RTS) != 0); + } + /* From IPWireless official docs: + * There has been an additional message added for NDIS. As the COM + * drivers appear as a modem - async PPP is used, the UE converts + * this back to sync PPP. This is not necessary for NDIS as it receives + * sync PPP - so the NDIS driver sends over one extra message, + * TL_SETUP_SIGNO_INFO_MSG, to indicate that the driver is NDIS not COM. + * If the NDIS driver does not receive a response then it converts the + * PPP frames from sync to async in the driver. This was a temporary step + * during delelopment and is not really tested. If the driver does receive + * a response from the UE which it should then its sends sync frames to the + * UE. Once the UE sees TL_SETUP_SIGNO_INFO_MSG from the driver it will + * bypass the async -> sync PPP conversion. + * + * For NDIS we assume that we are using sync PPP frames, for COM async. This + * driver uses NDIS mode too. We don't bother with translation from async -> sync PPP. + */ + info_packet = (ipw_setup_info_packet_t *) + create_packet(sizeof(ipw_setup_info_packet_t), + ADDR_SETUP_PROT, + TL_PROTOCOLID_SETUP, + free_packet); + info_packet->packet.length = sizeof(TlSetupInfoMsg); + info_packet->body.sigNo = TL_SETUP_SIGNO_INFO_MSG; + info_packet->body.driverType = NDISWAN_DRIVER; + info_packet->body.majorVersion = NDISWAN_DRIVER_MAJOR_VERSION; + info_packet->body.minorVersion = NDISWAN_DRIVER_MINOR_VERSION; + send_packet(hw, PRIO_SETUP, &info_packet->packet); + + /* Initialization is now complete, so we clear the 'to_setup' flag */ + hw->to_setup = 0; + +} + +static void handle_setup_get_version_rsp(ipw_hardware_t *hw, u_char vers_no) +{ + del_timer(&hw->setup_timer); + hw->initializing = 0; + printk(KERN_INFO IPWIRELESS_PCCARD_NAME ": card is ready.\n"); + + if (vers_no == TL_SETUP_VERSION) + __handle_setup_get_version_rsp(hw); + else + printk(KERN_ERR + IPWIRELESS_PCCARD_NAME + ": invalid hardware version no %u\n", + (unsigned int) vers_no); +} + +static void handle_received_SETUP_packet(ipw_hardware_t *hw, unsigned int address, + u_char *data, int len, int is_last) +{ + SETUP_RX_MSG *rx_msg = (SETUP_RX_MSG *) data; + + if (address != ADDR_SETUP_PROT) { + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": setup packet has bad address %d\n", address); + return; + } + + switch (rx_msg->sigNo) { + case TL_SETUP_SIGNO_GET_VERSION_RSP: + if (hw->to_setup) + handle_setup_get_version_rsp(hw, rx_msg->VersRspMsg.version); + break; + + case TL_SETUP_SIGNO_OPEN_MSG: + if (ipwireless_cs_debug) { + unsigned int channelIdx = rx_msg->OpenMsg.portNo - 1; + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": OPEN_MSG [channel %u] reply received\n", channelIdx); + } + break; + + case TL_SETUP_SIGNO_INFO_MSG_ACK: + if (ipwireless_cs_debug) + printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME + ": card successfully configured as driver type NDISWAN\n"); + break; + + case TL_SETUP_SIGNO_REBOOT_MSG: + if (hw->to_setup) { + printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME + ": Ignoring REBOOT message since setup has not completed\n"); + } else { + ipw_setup_reboot_msg_ack_t *packet; + + printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME + ": Acknowledging REBOOT message\n"); + packet = (ipw_setup_reboot_msg_ack_t *) + create_packet(sizeof(ipw_setup_reboot_msg_ack_t), + ADDR_SETUP_PROT, + TL_PROTOCOLID_SETUP, + free_packet); + packet->packet.length = sizeof(TlSetupRebootMsgAck); + packet->body.sigNo = TL_SETUP_SIGNO_REBOOT_MSG_ACK; + send_packet(hw, PRIO_SETUP, &packet->packet); + if (hw->reboot_callback != NULL) + (hw->reboot_callback) (hw->reboot_callback_data); + } + break; + + default: + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": unknown setup message %u received\n", + (unsigned int) rx_msg->sigNo); + } +} + +static void do_setup_hardware(ipw_hardware_t * hw) +{ + ipw_setup_get_version_query_packet_t *ver_packet; + + if (hw->hwVersion == HW_VERSION_1) { + /* Reset RX FIFO */ + outw(DCR_RXRESET, hw->base_port + IODCR); + /* SB: Reset TX FIFO */ + outw(DCR_TXRESET, hw->base_port + IODCR); + + /* Enable TX and RX interrupts. */ + outw(IER_TXENABLED | IER_RXENABLED, hw->base_port + IOIER); + } else { + /* Set INTRACK bit (bit 0), which means we must explicitly acknowledge + * interrupts by clearing bit 2 of PCCSR. */ + u_short csr = ioread16(&hw->memCCR->PCCSR); + csr |= 1; + iowrite16(csr, &hw->memCCR->PCCSR); + } + + ver_packet = (ipw_setup_get_version_query_packet_t *) + create_packet(sizeof(ipw_setup_get_version_query_packet_t), + ADDR_SETUP_PROT, TL_PROTOCOLID_SETUP, + free_packet); + ver_packet->body.sigNo = TL_SETUP_SIGNO_GET_VERSION_QRY; + ver_packet->packet.length = sizeof(TlSetupGetVersionQry); + + /*printk(KERN_EMERG "do_setup_hardware: ver_packet->packet.offset: %d\n", ver_packet->packet.offset);*/ + + send_packet(hw, PRIO_SETUP, &ver_packet->packet); + + /* The response to this version request is handled in handle_received_SETUP_packet */ +} + +static void do_close_hardware(ipw_hardware_t * hw) +{ + unsigned int irqn; + + if (hw->hwVersion == HW_VERSION_1) { + /* Disable TX and RX interrupts. */ + outw(0, hw->base_port + IOIER); + + /* Acknowledge any outstanding interrupt requests */ + irqn = inw(hw->base_port + IOIR); + if (irqn & IR_TXINTR) + outw(IR_TXINTR, hw->base_port + IOIR); + if (irqn & IR_RXINTR) + outw(IR_RXINTR, hw->base_port + IOIR); + } +} + +ipw_hardware_t *ipwireless_hardware_create(struct ipw_config_t *config) +{ + int i; + ipw_hardware_t *hw = + (ipw_hardware_t *) kmalloc(sizeof(struct ipw_hardware_t), + GFP_KERNEL); + if (!hw) + return NULL; + memset(hw, 0, sizeof(struct ipw_hardware_t)); + + hw->initializing = 1; + hw->tx_ready = 1; + memcpy(&hw->config, config, sizeof(ipw_config_t)); + for (i = 0; i < NL_NUM_OF_PRIORITIES; i++) + INIT_LIST_HEAD(&hw->tx_queue[i]); + + hw->rx_bytes_queued = 0; + INIT_LIST_HEAD(&hw->rx_queue); + INIT_LIST_HEAD(&hw->rx_pool); + + spin_lock_init(&hw->spinlock); + tasklet_init(&hw->tasklet, ipwireless_do_tasklet, + (unsigned long) hw); + + init_timer(&hw->setup_timer); + hw->setup_timer.function = ipwireless_setup_timer; + hw->setup_timer.data = (unsigned long) hw; + + INIT_WORK(&hw->work_rx, do_receive_data_work); + + hw->last_memtx_serial = (u_short) 0xffff; + + return hw; +} + +void +ipwireless_init_hardware1(struct ipw_hardware_t *hw, + unsigned int base_port, + void IPWIRELESS_IOMEM * attrMemory, + void IPWIRELESS_IOMEM * commonMemory, + int isV2Card, + ipw_reboot_callback_t reboot_callback, + void *reboot_callback_data) +{ + hw->bad_interrupt_count = 0; + if (hw->removed) { + hw->removed = 0; + enable_irq(hw->irq); + } + hw->base_port = base_port; + hw->hwVersion = isV2Card ? HW_VERSION_2 : HW_VERSION_1; + hw->llMTU = hw->hwVersion == HW_VERSION_1 ? LL_MTU_V1 : LL_MTU_V2; + hw->memCCR = (MEMCCR IPWIRELESS_IOMEM *) ((u_short *) attrMemory + 0x200); + hw->memInfReg = (MEMINFREG IPWIRELESS_IOMEM *) commonMemory; + hw->MemTX = &hw->memInfReg->MemTX_NEW; + hw->reboot_callback = reboot_callback; + hw->reboot_callback_data = reboot_callback_data; +} + +void ipwireless_init_hardware2(struct ipw_hardware_t *hw) +{ + hw->initializing = 1; + hw->init_loops = 0; + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": waiting for card to start up...\n"); + ipwireless_setup_timer((unsigned long) hw); +} + +static void ipwireless_setup_timer(unsigned long data) +{ + struct ipw_hardware_t *hw = (struct ipw_hardware_t *) data; + + hw->init_loops++; + /* Give up after a certain number of retries */ + if (hw->init_loops == TL_SETUP_MAX_VERSION_QRY) { + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": card failed to start up!\n"); + hw->initializing = 0; + } else { + unsigned long flags; + + /* Do not attempt to write to the board if it is not present. */ + if (is_card_present(hw)) { + /* Perform the I/O retrieval in a tasklet, because ppp_generic + * may be called from a tasklet, but not from a hardware interrupt. */ + hw->to_setup = 1; + hw->tx_ready = 1; + spin_lock_irqsave(&hw->spinlock, flags); + if (!hw->tasklet_pending) { + hw->tasklet_pending = 1; + tasklet_schedule(&hw->tasklet); + } + spin_unlock_irqrestore(&hw->spinlock, flags); + } + + hw->setup_timer.expires = + jiffies + (unsigned long) TL_SETUP_VERSION_QRY_TMO *HZ / 1000UL; + add_timer(&hw->setup_timer); + } +} + +/* Stop any interrupts from executing so that, once this function returns, + * other layers of the driver can be sure they won't get any more callbacks. + * Thus must be called on a proper process context. */ +void ipwireless_stop_interrupts(struct ipw_hardware_t *hw) +{ + if (!hw->shutting_down) { + /* Tell everyone we are going down. */ + hw->shutting_down = 1; + del_timer(&hw->setup_timer); + + /* Prevent the hardware from sending any more interrupts */ + do_close_hardware(hw); + } +} + +void ipwireless_hardware_free(ipw_hardware_t * hw) +{ + int i; + struct list_head *p, *q; + + ipwireless_stop_interrupts(hw); + + flush_scheduled_work(); + + for (i = 0; i < NL_NUM_OF_ADDRESSES; i++) + if (hw->packet_assembler[i].packet != NULL) + kfree(hw->packet_assembler[i].packet); + + for (i = 0; i < NL_NUM_OF_PRIORITIES; i++) + list_for_each_safe(p, q, &hw->tx_queue[i]){ + ipw_tx_packet_t *packet = list_entry(p, ipw_tx_packet_t, queue); + list_del_init(p); + free_packet(hw, packet); + } + + list_for_each_safe(p, q, &hw->rx_queue) { + ipw_rx_packet_t *packet = list_entry(p, ipw_rx_packet_t, queue); + list_del_init(p); + kfree(packet); + } + + list_for_each_safe(p, q, &hw->rx_pool) { + ipw_rx_packet_t *packet = list_entry(p, ipw_rx_packet_t, queue); + list_del_init(p); + kfree(packet); + } + kfree(hw); +} + +ipw_config_t *ipwireless_get_config(struct ipw_hardware_t *hw) +{ + return &hw->config; +} + +/* Associate the specified network with this hardware, so it will receive events + * from it. */ +void ipwireless_associate_network(struct ipw_hardware_t *hw, + struct ipw_network_t *network) +{ + hw->network = network; +} diff --git a/drivers/char/pcmcia/ipwireless_cs_hardware.h b/drivers/char/pcmcia/ipwireless_cs_hardware.h new file mode 100644 index 0000000..d75a843 --- /dev/null +++ b/drivers/char/pcmcia/ipwireless_cs_hardware.h @@ -0,0 +1,67 @@ +/* + * IPWireless 3G PCMCIA Network Driver + * + * Original code + * by Stephen Blackheath , + * Ben Martel + * + * Copyrighted as follows: + * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) + * + * Various driver changes and rewrites, port to new kernels + * Copyright (C) 2006-2007 Jiri Kosina + */ + +#ifndef _IPWIRELESS_CS_HARDWARE_H_ +#define _IPWIRELESS_CS_HARDWARE_H_ + +#include +#include +#include + +#define IPWIRELESS_IOMEM __iomem + +#define IPW_CONTROL_LINE_CTS 0x0001 +#define IPW_CONTROL_LINE_DCD 0x0002 +#define IPW_CONTROL_LINE_DSR 0x0004 +#define IPW_CONTROL_LINE_RI 0x0008 +#define IPW_CONTROL_LINE_DTR 0x0010 +#define IPW_CONTROL_LINE_RTS 0x0020 + +struct pt_regs; +struct ipw_config_t; +struct ipw_hardware_t; +struct ipw_network_t; + +struct ipw_hardware_t *ipwireless_hardware_create(struct ipw_config_t *); +void ipwireless_hardware_free(struct ipw_hardware_t *); +irqreturn_t ipwireless_interrupt(int, void *, struct pt_regs *); +void ipwireless_setDTR(struct ipw_hardware_t *, unsigned int, int); +void ipwireless_setRTS(struct ipw_hardware_t *, unsigned int, int); +typedef void (*ipw_packet_sent_callback_t) (void *, unsigned int); +void ipwireless_send_packet(struct ipw_hardware_t *, unsigned int, u_char *, + unsigned int, ipw_packet_sent_callback_t, + void *); + +/* Get the configuration settings for this modem instance. */ +struct ipw_config_t *ipwireless_get_config(struct ipw_hardware_t *); + +/* Associate the specified network with this hardware */ +void ipwireless_associate_network(struct ipw_hardware_t *, struct ipw_network_t *); + +/* Stop any interrupts from executing so that, once this function returns, + * other layers of the driver can be sure they won't get any more callbacks. + * Thus must be called on a proper process context. */ +void ipwireless_stop_interrupts(struct ipw_hardware_t *); + +typedef void (*ipw_reboot_callback_t) (void *); + +void ipwireless_init_hardware1(struct ipw_hardware_t *, unsigned int, + void IPWIRELESS_IOMEM *, void IPWIRELESS_IOMEM *, + int, ipw_reboot_callback_t, void *); + +void ipwireless_init_hardware2(struct ipw_hardware_t *); + +void ipwireless_sleep(u_int tenths); + +#endif diff --git a/drivers/char/pcmcia/ipwireless_cs_main.c b/drivers/char/pcmcia/ipwireless_cs_main.c new file mode 100644 index 0000000..17d0e30 --- /dev/null +++ b/drivers/char/pcmcia/ipwireless_cs_main.c @@ -0,0 +1,524 @@ +/* + * IPWireless 3G PCMCIA Network Driver + * + * Original code + * by Stephen Blackheath , + * Ben Martel + * + * Copyrighted as follows: + * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) + * + * Various driver changes and rewrites, port to new kernels + * Copyright (C) 2006-2007 Jiri Kosina + */ + +#include "ipwireless_cs_hardware.h" +#include "ipwireless_cs_network.h" +#include "ipwireless_cs_main.h" +#include "ipwireless_cs_tty.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static ipw_config_t default_config = { + "*99#" /* phone number */ +}; + +static struct pcmcia_device_id ipw_ids[] = { + PCMCIA_DEVICE_MANF_CARD(0x02f2, 0x0100), + PCMCIA_DEVICE_MANF_CARD(0x02f2, 0x0200), + PCMCIA_DEVICE_NULL +}; + +/* Prototypes */ + +static void ipwireless_detach(struct pcmcia_device *link); + +/* Options configurable through the proc filesystem. */ +__u16 ipwireless_cs_debug = 0; +__u16 ipwireless_cs_vendor = 1; +__u16 ipwireless_cs_loopback = 0; +__u16 ipwireless_cs_out_queue = 1; +static int major = 0; + +#if 0 +MODULE_PARM(debug, "i"); +MODULE_PARM_DESC(debug, "debug (0 or 1) [0]"); +MODULE_PARM(vendor, "i"); +MODULE_PARM_DESC(vendor, "vendor (0 or 1) [0]"); +MODULE_PARM(loopback, "i"); +MODULE_PARM_DESC(loopback, "loopback (0 or 1) [0]"); +#endif +module_param(major, int, 0); +MODULE_PARM_DESC(major, "ttyIPWp major number [0]"); + +static char *drv_name = IPWIRELESS_PCCARD_NAME; +static struct pcmcia_device *dev_list; + +static inline int reset_cor(struct pcmcia_device *link, u_short cor_value) +{ + int i; + conf_reg_t reg = { 0, CS_READ, CISREG_COR, 0 }; + + i = pcmcia_access_configuration_register(link, ®); + + if (i != CS_SUCCESS) { + cs_error(link, AccessConfigurationRegister, i); + return -1; + } + + reg.Action = CS_WRITE; + reg.Value = reg.Value | 0x80; /* SRESET Software reset */ + + i = pcmcia_access_configuration_register(link, ®); + + if (i != CS_SUCCESS) { + cs_error(link, AccessConfigurationRegister, i); + return -1; + } + + reg.Action = CS_WRITE; + reg.Value = cor_value; + + i = pcmcia_access_configuration_register(link, ®); + + if (i != CS_SUCCESS) { + cs_error(link, AccessConfigurationRegister, i); + return -1; + } + + return 0; +} + +/* Executes in process context. */ +static void signalled_reboot_work(struct work_struct *work_reboot) +{ + ipw_dev_t *ipw = container_of(work_reboot, ipw_dev_t, work_reboot); + struct pcmcia_device *link = ipw->link; + int ret = pccard_reset_card(link->socket); + + if (ret != CS_SUCCESS) { + cs_error(link, ResetCard, ret); + } +} + +static void signalled_reboot_callback(void *callback_data) +{ + ipw_dev_t *ipw = (ipw_dev_t *) callback_data; + + /* Delegate to process context. */ + schedule_work(&ipw->work_reboot); +} + +static int config_ipwireless(ipw_dev_t * ipw) +{ + struct pcmcia_device *link = ipw->link; + int ret; + config_info_t conf; + tuple_t tuple; + u_short buf[64]; + cisparse_t parse; + u_short cor_value; + win_req_t reqAM; + win_req_t reqCM; + memreq_t memAM; + memreq_t memCM; + + ipw->isV2Card = 0; + + tuple.Attributes = 0; + tuple.TupleData = (cisdata_t *) buf; + tuple.TupleDataMax = sizeof(buf); + tuple.TupleOffset = 0; + + tuple.DesiredTuple = RETURN_FIRST_TUPLE; + + ret = pcmcia_get_first_tuple(link, &tuple); + + while (ret == 0) { + + ret = pcmcia_get_tuple_data(link, &tuple); + + if (ret != CS_SUCCESS) { + cs_error(link, GetTupleData, ret); + goto exit0; + } + ret = pcmcia_get_next_tuple(link, &tuple); + } + + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + + ret = pcmcia_get_first_tuple(link, &tuple); + + if (ret != CS_SUCCESS) { + cs_error(link, GetFirstTuple, ret); + goto exit0; + } + + ret = pcmcia_get_tuple_data(link, &tuple); + + if (ret != CS_SUCCESS) { + cs_error(link, GetTupleData, ret); + goto exit0; + } + + ret = pcmcia_parse_tuple(link, &tuple, &parse); + + if (ret != CS_SUCCESS) { + cs_error(link, ParseTuple, ret); + goto exit0; + } + + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + link->io.BasePort1 = parse.cftable_entry.io.win[0].base; + link->io.NumPorts1 = parse.cftable_entry.io.win[0].len; + link->io.IOAddrLines = 16; + + link->irq.IRQInfo1 = parse.cftable_entry.irq.IRQInfo1; + link->irq.IRQInfo2 = parse.cftable_entry.irq.IRQInfo2; + + /* 0x40 causes it to generate level mode interrupts. */ + /* 0x04 enables IREQ pin. */ + cor_value = parse.cftable_entry.index | 0x44; + link->conf.ConfigIndex = cor_value; + + /* IRQ and I/O settings */ + tuple.DesiredTuple = CISTPL_CONFIG; + + ret = pcmcia_get_first_tuple(link, &tuple); + + if (ret != CS_SUCCESS) { + cs_error(link, GetFirstTuple, ret); + goto exit0; + } + + ret = pcmcia_get_tuple_data(link, &tuple); + + if (ret != CS_SUCCESS) { + cs_error(link, GetTupleData, ret); + goto exit0; + } + + ret = pcmcia_parse_tuple(link, &tuple, &parse); + + if (ret != CS_SUCCESS) { + cs_error(link, GetTupleData, ret); + goto exit0; + } + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + link->conf.IntType = INT_MEMORY_AND_IO; + + link->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING | IRQ_HANDLE_PRESENT; + link->irq.Handler = ipwireless_interrupt; + link->irq.Instance = ipw->hardware; + + ret = pcmcia_request_io(link, &link->io); + + if (ret != CS_SUCCESS) { + cs_error(link, RequestIO, ret); + goto exit0; + } + + /* memory settings */ + + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + + ret = pcmcia_get_first_tuple(link, &tuple); + + if (ret != CS_SUCCESS) { + cs_error(link, GetFirstTuple, ret); + goto exit1; + } + + ret = pcmcia_get_tuple_data(link, &tuple); + + if (ret != CS_SUCCESS) { + cs_error(link, GetTupleData, ret); + goto exit1; + } + + ret = pcmcia_parse_tuple(link, &tuple, &parse); + + if (ret != CS_SUCCESS) { + cs_error(link, ParseTuple, ret); + goto exit1; + } + + if (parse.cftable_entry.mem.nwin > 0) { + reqCM.Attributes = + WIN_DATA_WIDTH_16 | WIN_MEMORY_TYPE_CM | WIN_ENABLE; + reqCM.Base = parse.cftable_entry.mem.win[0].host_addr; + reqCM.Size = parse.cftable_entry.mem.win[0].len; + if (reqCM.Size < 0x1000) + reqCM.Size = 0x1000; + reqCM.AccessSpeed = 0; + + ret = pcmcia_request_window(&link, &reqCM, &ipw->handleCM); + + if (ret != CS_SUCCESS) { + cs_error(link, RequestWindow, ret); + goto exit1; + } + + memCM.CardOffset = + parse.cftable_entry.mem.win[0].card_addr; + memCM.Page = 0; + + ret = pcmcia_map_mem_page(ipw->handleCM, &memCM); + + if (ret != CS_SUCCESS) { + cs_error(link, MapMemPage, ret); + goto exit1; + } + + ipw->isV2Card = + parse.cftable_entry.mem.win[0].len == 0x100; + + ipw->commonMemory = ioremap(reqCM.Base, reqCM.Size); + + reqAM.Attributes = + WIN_DATA_WIDTH_16 | WIN_MEMORY_TYPE_AM | WIN_ENABLE; + reqAM.Base = 0; + reqAM.Size = 0; /* this used to be 0x1000 */ + reqAM.AccessSpeed = 0; + + ret = pcmcia_request_window(&link, &reqAM, &ipw->handleAM); + + if (ret != CS_SUCCESS) { + cs_error(link, RequestWindow, ret); + goto exit2; + } + + memAM.CardOffset = 0; + memAM.Page = 0; + + ret = pcmcia_map_mem_page(ipw->handleAM, &memAM); + + if (ret != CS_SUCCESS) { + cs_error(link, MapMemPage, ret); + goto exit2; + } + + ipw->attrMemory = ioremap(reqAM.Base, reqAM.Size); + } + + INIT_WORK(&ipw->work_reboot, signalled_reboot_work); + + ipwireless_init_hardware1(ipw->hardware, link->io.BasePort1, + ipw->attrMemory, ipw->commonMemory, + ipw->isV2Card, signalled_reboot_callback, + ipw); + + ret = pcmcia_request_irq(link, &link->irq); + + if (ret != CS_SUCCESS) { + cs_error(link, RequestIRQ, ret); + goto exit3; + } + + /* Look up current Vcc */ + + ret = pcmcia_get_configuration_info(link, &conf); + + if (ret != CS_SUCCESS) { + cs_error(link, GetConfigurationInfo, ret); + goto exit4; + } + + printk(KERN_INFO IPWIRELESS_PCCARD_NAME ": Card type %s\n", + ipw->isV2Card ? "V2/V3" : "V1"); + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": I/O ports 0x%04x-0x%04x, irq %d\n", + (unsigned int) link->io.BasePort1, + (unsigned int) (link->io.BasePort1 + link->io.NumPorts1 - + 1), (unsigned int) link->irq.AssignedIRQ); + if (ipw->attrMemory && ipw->commonMemory) { + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": attr memory 0x%08lx-0x%08lx, common memory 0x%08lx-0x%08lx\n", + (u_long) reqAM.Base, + (u_long) reqAM.Base + (u_long) reqAM.Size - + (u_long) 1, (u_long) reqCM.Base, + (u_long) reqCM.Base + (u_long) reqCM.Size - + (u_long) 1); + } + + ipw->network = ipwireless_network_create(ipw->hardware); + ipw->tty = ipwireless_tty_create(link, ipw->hardware, ipw->network, ipw->nodes); + + ipwireless_init_hardware2(ipw->hardware); + + /* Do the RequestConfiguration last, because it enables interrupts. + * Then we don't get any interrupts before we're ready for them. */ + ret = pcmcia_request_configuration(link, &link->conf); + + if (ret != CS_SUCCESS) { + cs_error(link, RequestConfiguration, ret); + goto exit4; + } + + link->dev_node = &ipw->nodes[0]; + + return 0; + + exit4: + pcmcia_disable_device(link); + exit3: + if (ipw->attrMemory) { + iounmap(ipw->attrMemory); + pcmcia_release_window(ipw->handleAM); + pcmcia_disable_device(link); + } + exit2: + if (ipw->commonMemory) { + iounmap(ipw->commonMemory); + pcmcia_release_window(ipw->handleCM); + } + exit1: + pcmcia_disable_device(link); + exit0: + return -1; +} + +static void release_ipwireless(ipw_dev_t * ipw) +{ + struct pcmcia_device *link = ipw->link; + + pcmcia_disable_device(link); + + if (ipw->commonMemory) + iounmap(ipw->commonMemory); + if (ipw->attrMemory) + iounmap(ipw->attrMemory); + if (ipw->commonMemory) + pcmcia_release_window(ipw->handleCM); + if (ipw->attrMemory) + pcmcia_release_window(ipw->handleAM); + pcmcia_disable_device(link); +} + +/* + * ipwireless_attach() creates an "instance" of the driver, allocating + * local data structures for one device (one interface). The device + * is registered with Card Services. + * + * The dev_link structure is initialized, but we don't actually + * configure the card at this point -- we wait until we receive a + * card insertion event. + */ +static int ipwireless_attach(struct pcmcia_device *link) +{ + ipw_dev_t *ipw; + int ret; + + ipw = (ipw_dev_t *) kmalloc(sizeof(ipw_dev_t), GFP_KERNEL); + memset(ipw, 0, sizeof(ipw_dev_t)); + ipw->link = link; + link->priv = link->irq.Instance = ipw; + + /* Link this device into our device list. */ + link->dev_node = &ipw->nodes[0]; + dev_list = link; + + ipw->hardware = ipwireless_hardware_create(&default_config); + /* RegisterClient will call config_ipwireless */ + + ret = config_ipwireless(ipw); + + if (ret != 0) { + cs_error(link, RegisterClient, ret); + ipwireless_detach(link); + return ret; + } + + return 0; +} + +/* + * This deletes a driver "instance". The device is de-registered with + * Card Services. If it has been released, all local data structures + * are freed. Otherwise, the structures will be freed when the device + * is released. + */ +static void ipwireless_detach(struct pcmcia_device *link) +{ + ipw_dev_t *ipw = (ipw_dev_t *) link->priv; + + release_ipwireless(ipw); + + /* Break the link with Card Services */ + if (link) + pcmcia_disable_device(link); + + if (ipw->tty != NULL) + ipwireless_tty_free(ipw->tty); + if (ipw->network != NULL) + ipwireless_network_free(ipw->network); + if (ipw->hardware != NULL) + ipwireless_hardware_free(ipw->hardware); + kfree(ipw); + +} + +static struct pcmcia_driver me; + +/* + * Module insertion : initialisation of the module. + * Register the card with cardmgr... + */ +static int __init init_ipwireless_cs(void) +{ + int ret; + + printk(KERN_INFO IPWIRELESS_PCCARD_NAME " " + IPWIRELESS_PCMCIA_VERSION " by " IPWIRELESS_PCMCIA_AUTHOR + "\n"); + me.owner = THIS_MODULE; + me.probe = ipwireless_attach; + me.remove = ipwireless_detach; + me.drv.name = drv_name; + me.id_table = ipw_ids; + pcmcia_register_driver(&me); + + ret = ipwireless_tty_init(major); + if (ret != 0) { + pcmcia_unregister_driver(&me); + } + return ret; +} + +/* + * Module removal + */ +static void __exit exit_ipwireless_cs(void) +{ + ipwireless_tty_release(); + + printk(KERN_INFO IPWIRELESS_PCCARD_NAME " " + IPWIRELESS_PCMCIA_VERSION " removed\n"); + + pcmcia_unregister_driver(&me); +} + +module_init(init_ipwireless_cs); +module_exit(exit_ipwireless_cs); + +MODULE_AUTHOR(IPWIRELESS_PCMCIA_AUTHOR); +MODULE_DESCRIPTION(IPWIRELESS_PCCARD_NAME " " IPWIRELESS_PCMCIA_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/pcmcia/ipwireless_cs_main.h b/drivers/char/pcmcia/ipwireless_cs_main.h new file mode 100644 index 0000000..34de274 --- /dev/null +++ b/drivers/char/pcmcia/ipwireless_cs_main.h @@ -0,0 +1,77 @@ +/* + * IPWireless 3G PCMCIA Network Driver + * + * Original code + * by Stephen Blackheath , + * Ben Martel + * + * Copyrighted as follows: + * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) + * + * Various driver changes and rewrites, port to new kernels + * Copyright (C) 2006-2007 Jiri Kosina + */ + +#ifndef _IPWIRELESS_CS_H_ +#define _IPWIRELESS_CS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipwireless_cs_hardware.h" + +#define IPWIRELESS_PCCARD_NAME "ipwireless_cs" +#define IPWIRELESS_PCMCIA_VERSION "1.0.15-jikos1" +#define IPWIRELESS_PCMCIA_AUTHOR "Stephen Blackheath, Ben Martel and Jiri Kosina" + +#define IPWIRELESS_TX_QUEUE_SIZE 262144 +#define IPWIRELESS_RX_QUEUE_SIZE 262144 + +/* If IPWIRELESS_STATE_DEBUG is defined, then ipwireless_cs outputs internal + * state of the driver. */ +#define IPWIRELESS_STATE_DEBUG + +struct ipw_hardware_t; +struct ipw_network_t; +struct ipw_tty_t; + +typedef struct ipw_config_t { + char phone_number[129]; +} ipw_config_t; + +typedef struct ipw_dev_t { + /* Linux PCMCIA structure */ + struct pcmcia_device *link; + int isV2Card; + window_handle_t handleAM; + void IPWIRELESS_IOMEM *attrMemory; + window_handle_t handleCM; + void IPWIRELESS_IOMEM *commonMemory; + dev_node_t nodes[4]; + /* Reference to attribute memory, containing CIS data */ + void *attribute_memory; + + /* Hardware context */ + struct ipw_hardware_t *hardware; + /* Network layer context */ + struct ipw_network_t *network; + /* TTY device context */ + struct ipw_tty_t *tty; + struct work_struct work_reboot; +} ipw_dev_t; + +typedef struct pcmcia_device dev_link_t; + +/* Options configurable through the proc filesystem. */ +extern __u16 ipwireless_cs_debug; +extern __u16 ipwireless_cs_vendor; +extern __u16 ipwireless_cs_loopback; +extern __u16 ipwireless_cs_out_queue; + +#endif diff --git a/drivers/char/pcmcia/ipwireless_cs_network.c b/drivers/char/pcmcia/ipwireless_cs_network.c new file mode 100644 index 0000000..684b616 --- /dev/null +++ b/drivers/char/pcmcia/ipwireless_cs_network.c @@ -0,0 +1,483 @@ +/* + * IPWireless 3G PCMCIA Network Driver + * + * Original code + * by Stephen Blackheath , + * Ben Martel + * + * Copyrighted as follows: + * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) + * + * Various driver changes and rewrites, port to new kernels + * Copyright (C) 2006-2007 Jiri Kosina + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipwireless_cs_network.h" +#include "ipwireless_cs_hardware.h" +#include "ipwireless_cs_main.h" +#include "ipwireless_cs_tty.h" + +/* MAX_OUTGOING_PACKETS_QUEUED: The maximum number of outgoing packets that can + * be queued up at any one time. */ +#define MAX_OUTGOING_PACKETS_QUEUED ipwireless_cs_out_queue + +#define MAX_ASSOCIATED_TTYS 2 + +typedef struct ipw_network_t { + /* Hardware context, used for calls to hardware layer. */ + struct ipw_hardware_t *hardware; + /* Context for kernel 'generic_ppp' functionality */ + struct ppp_channel *ppp_channel; + /* tty context connected with IPW console */ + struct ipw_tty_t *associated_ttys[NO_OF_IPW_CHANNELS][MAX_ASSOCIATED_TTYS]; + /* True if ppp needs waking up once we're ready to xmit */ + int ppp_blocked; + /* Number of packets queued up in hardware module. */ + int outgoing_packets_queued; + /* Spinlock to avoid interrupts during shutdown */ + spinlock_t spinlock; + struct semaphore close_lock; + + /* reply_buffer fields are used for processing replies to AT commands */ + char reply_buffer[512]; + int reply_buffer_len; + int reply_buffer_lfs; + + unsigned int flags; + unsigned int rbits; + u32 xaccm[8]; + u32 raccm; + int mru; + + int shutting_down; + unsigned int ras_control_lines; + + struct work_struct work_go_online; + struct work_struct work_go_offline; +} ipw_network_t; + + +#ifdef IPWIRELESS_STATE_DEBUG +int ipwireless_dump_network_state(char *p, ipw_network_t * network) +{ + int idx = 0; + + idx += + sprintf(p + idx, "debug: ppp_blocked=%d\n", + network->ppp_blocked); + idx += + sprintf(p + idx, "debug: outgoing_packets_queued=%d\n", + network->outgoing_packets_queued); + idx += + sprintf(p + idx, "debug: network.shutting_down=%d\n", + network->shutting_down); + return idx; +} +#endif + + +#define SC_RCV_BITS (SC_RCV_B7_1|SC_RCV_B7_0|SC_RCV_ODDP|SC_RCV_EVNP) + +static void notify_packet_sent(void *callback_data, unsigned int packet_length) +{ + ipw_network_t *network = (ipw_network_t *) callback_data; + unsigned int flags; + + spin_lock_irqsave(&network->spinlock, flags); + network->outgoing_packets_queued--; + if (network->ppp_channel != NULL) { + if (network->ppp_blocked) { + network->ppp_blocked = 0; + spin_unlock_irqrestore(&network->spinlock, flags); + ppp_output_wakeup(network->ppp_channel); + if (ipwireless_cs_debug) + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": ppp unblocked\n"); + } else { + spin_unlock_irqrestore(&network->spinlock, flags); + } + } else { + spin_unlock_irqrestore(&network->spinlock, flags); + } +} + +/* + * Called by the ppp system when it has a packet to send to the hardware. + */ +static int +ipwireless_ppp_start_xmit(struct ppp_channel *ppp_channel, + struct sk_buff *skb) +{ + ipw_network_t *network = (ipw_network_t *) ppp_channel->private; + unsigned int flags; + + spin_lock_irqsave(&network->spinlock, flags); + /* If our outgoing queue is not full... */ + /* Note: We could use CTS for flow control (uncomment the section below), + * but it does not appear to be necessary. The modem may be using its TX + * interrupt for this purpose. */ + if (network->outgoing_packets_queued < MAX_OUTGOING_PACKETS_QUEUED) { + u_char *buf; + static u_char header[] = { 0xff, 0x03 }; + + /* Then start sending... */ + network->outgoing_packets_queued++; + spin_unlock_irqrestore(&network->spinlock, flags); + /* If we have the requested amount of headroom in the skb we were handed, + * then we can add the header efficiently. */ + if (skb_headroom(skb) >= 2) { + memcpy(skb_push(skb, 2), header, 2); + ipwireless_send_packet(network->hardware, + IPW_CHANNEL_RAS, skb->data, + skb->len, + notify_packet_sent, + network); + } + /* Otherwise (rarely) we do it inefficiently. */ + else { + buf = (u_char *) kmalloc(skb->len + 2, GFP_ATOMIC); + memcpy(buf + 2, skb->data, skb->len); + memcpy(buf, header, 2); + ipwireless_send_packet(network->hardware, + IPW_CHANNEL_RAS, buf, + skb->len + 2, + notify_packet_sent, + network); + kfree(buf); + } + kfree_skb(skb); + return 1; + } + /* Otherwise reject the packet, and flag that the ppp system needs to be + unblocked once we are ready to send. */ + else { + /* Note: The ppp_generic system ensures through a spin_lock that + * ppp_output_wakeup() can't be called before this 0 return code is handled + * and the ppp_generic system is blocked. */ + network->ppp_blocked = 1; + spin_unlock_irqrestore(&network->spinlock, flags); + return 0; + } +} + +/* Handle an ioctl call that has come in via ppp. */ +static int ipwireless_ppp_ioctl(struct ppp_channel *ppp_channel, unsigned int cmd, unsigned long arg) +{ + ipw_network_t *network = (ipw_network_t *) ppp_channel->private; + + int err, val; + u32 accm[8]; + + err = -EFAULT; + switch (cmd) { + case PPPIOCGFLAGS: + val = network->flags | network->rbits; + if (put_user(val, (int *) arg)) + break; + err = 0; + break; + case PPPIOCSFLAGS: + if (get_user(val, (int *) arg)) + break; + network->flags = val & ~SC_RCV_BITS; + network->rbits = val & SC_RCV_BITS; + err = 0; + break; + + case PPPIOCGASYNCMAP: + if (put_user(network->xaccm[0], (u32 *) arg)) + break; + err = 0; + break; + case PPPIOCSASYNCMAP: + if (get_user(network->xaccm[0], (u32 *) arg)) + break; + err = 0; + break; + + case PPPIOCGRASYNCMAP: + if (put_user(network->raccm, (u32 *) arg)) + break; + err = 0; + break; + case PPPIOCSRASYNCMAP: + if (get_user(network->raccm, (u32 *) arg)) + break; + err = 0; + break; + + case PPPIOCGXASYNCMAP: + if (copy_to_user + ((void *) arg, network->xaccm, sizeof(network->xaccm))) + break; + err = 0; + break; + case PPPIOCSXASYNCMAP: + if (copy_from_user(accm, (void *) arg, sizeof(accm))) + break; + accm[2] &= ~0x40000000U; /* can't escape 0x5e */ + accm[3] |= 0x60000000U; /* must escape 0x7d, 0x7e */ + memcpy(network->xaccm, accm, sizeof(network->xaccm)); + err = 0; + break; + + case PPPIOCGMRU: + if (put_user(network->mru, (int *) arg)) + break; + err = 0; + break; + case PPPIOCSMRU: + if (get_user(val, (int *) arg)) + break; + if (val < PPP_MRU) + val = PPP_MRU; + network->mru = val; + err = 0; + break; + + default: + err = -ENOTTY; + } + + return err; +} + +static struct ppp_channel_ops ipwireless_ppp_channel_ops = { + ipwireless_ppp_start_xmit, + ipwireless_ppp_ioctl +}; + +static void do_go_online(struct work_struct *work_go_online) +{ + ipw_network_t *network = + container_of(work_go_online, ipw_network_t, work_go_online); + unsigned int flags; + + spin_lock_irqsave(&network->spinlock, flags); + if (network->ppp_channel == NULL) { + struct ppp_channel *channel; + + spin_unlock_irqrestore(&network->spinlock, flags); + channel = (struct ppp_channel *) + kmalloc(sizeof(struct ppp_channel), GFP_KERNEL); + memset(channel, 0, sizeof(struct ppp_channel)); + channel->private = network; + channel->mtu = 16384; /* Wild guess */ + channel->hdrlen = 2; + channel->ops = &ipwireless_ppp_channel_ops; + + /* These accm things are async-PPP related (copied out of ppp_async) + * pppd expects them to appear to work, even though we don't use them. */ + network->flags = 0; + network->rbits = 0; + network->mru = PPP_MRU; + { + int i; + for (i = 0; i < 8; i++) + network->xaccm[i] = 0; + } + network->xaccm[0] = ~0U; + network->xaccm[3] = 0x60000000U; + network->raccm = ~0U; + ppp_register_channel(channel); + spin_lock_irqsave(&network->spinlock, flags); + network->ppp_channel = channel; + } + spin_unlock_irqrestore(&network->spinlock, flags); +} + +static void do_go_offline(struct work_struct *work_go_offline) +{ + ipw_network_t *network = + container_of(work_go_offline, ipw_network_t, work_go_offline); + unsigned int flags; + + down(&network->close_lock); + spin_lock_irqsave(&network->spinlock, flags); + if (network->ppp_channel != NULL) { + struct ppp_channel *channel = network->ppp_channel; + network->ppp_channel = NULL; + spin_unlock_irqrestore(&network->spinlock, flags); + up(&network->close_lock); + ppp_unregister_channel(channel); + } else { + spin_unlock_irqrestore(&network->spinlock, flags); + up(&network->close_lock); + } +} + +void ipwireless_network_notify_control_line_change(ipw_network_t * network, + unsigned int channelIdx, + unsigned int control_lines, + unsigned int changed_mask) +{ + int i; + + if (channelIdx == IPW_CHANNEL_RAS) + network->ras_control_lines = control_lines; + + for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) { + struct ipw_tty_t *tty = + network->associated_ttys[channelIdx][i]; + /* If it's associated with a tty (other than the RAS channel when we're + * online), then send the data to that tty. The RAS channel's data is + * handled above - it always goes through ppp_generic. */ + if (tty != NULL) + ipwireless_tty_notify_control_line_change(tty, + channelIdx, + control_lines, + changed_mask); + } +} + +void ipwireless_network_packet_received(ipw_network_t * network, + unsigned int channelIdx, u_char * data, + unsigned int length) +{ + int i; + unsigned int flags; + + for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) { + struct ipw_tty_t *tty = network->associated_ttys[channelIdx][i]; + /* If it's associated with a tty (other than the RAS channel when we're + * online), then send the data to that tty. The RAS channel's data is + * handled above - it always goes through ppp_generic. */ + if (tty != NULL && channelIdx == IPW_CHANNEL_RAS && + (network->ras_control_lines & IPW_CONTROL_LINE_DCD) != 0 + && ipwireless_tty_is_modem(tty)) { + /* If data came in on the RAS channel and this tty is the modem + * tty, and we are online, then we send it to the PPP layer. */ + down(&network->close_lock); + spin_lock_irqsave(&network->spinlock, flags); + if (network->ppp_channel != NULL) { + struct sk_buff *skb; + + spin_unlock_irqrestore(&network->spinlock, flags); + + /* Send the data to the ppp_generic module. */ + skb = dev_alloc_skb(length + 4); + skb_reserve(skb, 2); + + memcpy(skb_put(skb, length), data, length); + ppp_input(network->ppp_channel, skb); + } else + spin_unlock_irqrestore(&network->spinlock, flags); + up(&network->close_lock); + } + /* Otherwise we send it out the tty. */ + else + ipwireless_tty_received(tty, data, length); + } +} + +ipw_network_t *ipwireless_network_create(struct ipw_hardware_t *hw) +{ + ipw_network_t *network = + (ipw_network_t *) kmalloc(sizeof(ipw_network_t), GFP_ATOMIC); + memset(network, 0, sizeof(ipw_network_t)); + + spin_lock_init(&network->spinlock); + init_MUTEX(&network->close_lock); + + network->hardware = hw; + + INIT_WORK(&network->work_go_online, do_go_online); + INIT_WORK(&network->work_go_offline, do_go_offline); + + ipwireless_associate_network(hw, network); + + return network; +} + +void ipwireless_network_free(ipw_network_t * network) +{ + network->shutting_down = 1; + + /* Physically disconnect the network, and close the ppp channel (in case + this hasn't already been done. */ + ipwireless_ppp_close(network); + flush_scheduled_work(); + + /* Ensure we will receive no more interrupts from the hardware layer. */ + ipwireless_stop_interrupts(network->hardware); + + /* Now that it is safe, we can disassociate ourselves from the hardware layer. */ + ipwireless_associate_network(network->hardware, NULL); + + kfree(network); +} + +void ipwireless_associate_network_tty(struct ipw_network_t *network, + unsigned int channelIdx, + struct ipw_tty_t *tty) +{ + int i; + + for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) + if (network->associated_ttys[channelIdx][i] == NULL) { + network->associated_ttys[channelIdx][i] = tty; + break; + } +} + +void ipwireless_disassociate_network_ttys(struct ipw_network_t *network, + unsigned int channelIdx) +{ + int i; + + for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) + network->associated_ttys[channelIdx][i] = NULL; +} + +void ipwireless_ppp_open(struct ipw_network_t *network) +{ + if (ipwireless_cs_debug) + printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": online\n"); + schedule_work(&network->work_go_online); +} + +/*! + * Explicitly close the ppp link, which should be called if the tty + * is closed down. Must be called from a process context. + */ +void ipwireless_ppp_close(struct ipw_network_t *network) +{ + /* Disconnect from the wireless network. */ + if (ipwireless_cs_debug) + printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": offline\n"); + schedule_work(&network->work_go_offline); +} + +int ipwireless_ppp_channel_index(struct ipw_network_t *network) +{ + int ret = -1; + unsigned int flags; + + spin_lock_irqsave(&network->spinlock, flags); + if (network->ppp_channel != NULL) + ret = ppp_channel_index(network->ppp_channel); + spin_unlock_irqrestore(&network->spinlock, flags); + return ret; +} + +int ipwireless_ppp_unit_number(struct ipw_network_t *network) +{ + int ret = -1; + unsigned int flags; + + spin_lock_irqsave(&network->spinlock, flags); + if (network->ppp_channel != NULL) + ret = ppp_unit_number(network->ppp_channel); + spin_unlock_irqrestore(&network->spinlock, flags); + return ret; +} diff --git a/drivers/char/pcmcia/ipwireless_cs_network.h b/drivers/char/pcmcia/ipwireless_cs_network.h new file mode 100644 index 0000000..920ad5b --- /dev/null +++ b/drivers/char/pcmcia/ipwireless_cs_network.h @@ -0,0 +1,46 @@ +/* + * IPWireless 3G PCMCIA Network Driver + * + * Original code + * by Stephen Blackheath , + * Ben Martel + * + * Copyrighted as follows: + * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) + * + * Various driver changes and rewrites, port to new kernels + * Copyright (C) 2006-2007 Jiri Kosina + */ + +#ifndef _IPWIRELESS_CS_NETWORK_H_ +#define _IPWIRELESS_CS_NETWORK_H_ + +#include + +struct ipw_network_t; +struct ipw_tty_t; +struct ipw_hardware_t; + +/* Definitions of the different channels on the PCMCIA UE */ +#define IPW_CHANNEL_RAS 0 +#define IPW_CHANNEL_DIALLER 1 +#define IPW_CHANNEL_CONSOLE 2 +#define NO_OF_IPW_CHANNELS 5 + +void ipwireless_network_notify_control_line_change(struct ipw_network_t *, unsigned int, + unsigned int, unsigned int); +void ipwireless_network_packet_received(struct ipw_network_t *, unsigned int, u_char *, + unsigned int); +struct ipw_network_t *ipwireless_network_create(struct ipw_hardware_t *); +void ipwireless_network_free(struct ipw_network_t *); +void ipwireless_associate_network_tty(struct ipw_network_t *, unsigned int, struct ipw_tty_t *tty); +void ipwireless_disassociate_network_ttys(struct ipw_network_t *, unsigned int); +void ipwireless_ppp_open(struct ipw_network_t *); + +/* Explicitly close the ppp link, which should be called if the tty + * is closed down. Must be called from a process context.*/ +void ipwireless_ppp_close(struct ipw_network_t *); +int ipwireless_ppp_channel_index(struct ipw_network_t *); +int ipwireless_ppp_unit_number(struct ipw_network_t *); + +#endif diff --git a/drivers/char/pcmcia/ipwireless_cs_setup_protocol.h b/drivers/char/pcmcia/ipwireless_cs_setup_protocol.h new file mode 100644 index 0000000..acc6d4d --- /dev/null +++ b/drivers/char/pcmcia/ipwireless_cs_setup_protocol.h @@ -0,0 +1,118 @@ +/* + * IPWireless 3G PCMCIA Network Driver + * + * Original code + * by Stephen Blackheath , + * Ben Martel + * + * Copyrighted as follows: + * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) + * + * Various driver changes and rewrites, port to new kernels + * Copyright (C) 2006-2007 Jiri Kosina + */ + +#ifndef _IPWIRELESS_CS_SETUP_PROTOCOL_H_ +#define _IPWIRELESS_CS_SETUP_PROTOCOL_H_ + +#include + +/* Version of the setup protocol and transport protocols */ +#define TL_SETUP_VERSION 1 + +#define TL_SETUP_VERSION_QRY_TMO 1000 +#define TL_SETUP_MAX_VERSION_QRY 30 + +/* Message numbers 0-9 are obsoleted. The must not be reused! */ +#define TL_SETUP_SIGNO_GET_VERSION_QRY 10 +#define TL_SETUP_SIGNO_GET_VERSION_RSP 11 +#define TL_SETUP_SIGNO_CONFIG_MSG 12 +#define TL_SETUP_SIGNO_CONFIG_DONE_MSG 13 +#define TL_SETUP_SIGNO_OPEN_MSG 14 +#define TL_SETUP_SIGNO_CLOSE_MSG 15 + +#define TL_SETUP_SIGNO_INFO_MSG 20 +#define TL_SETUP_SIGNO_INFO_MSG_ACK 21 + +#define TL_SETUP_SIGNO_REBOOT_MSG 22 +#define TL_SETUP_SIGNO_REBOOT_MSG_ACK 23 + +/* Syncronous start-messages */ +typedef struct { + u_char sigNo; /* TL_SETUP_SIGNO_GET_VERSION_QRY */ +} __attribute__ ((__packed__)) TlSetupGetVersionQry; + +typedef struct { + u_char sigNo; /* TL_SETUP_SIGNO_GET_VERSION_RSP */ + u_char version; /* TL_SETUP_VERSION */ +} __attribute__ ((__packed__)) TlSetupGetVersionRsp; + +typedef struct { + u_char sigNo; /* TL_SETUP_SIGNO_CONFIG_MSG */ + u_char portNo; + u_char prioData; + u_char prioCtrl; +} __attribute__ ((__packed__)) TlSetupConfigMsg; + +typedef struct { + u_char sigNo; /* TL_SETUP_SIGNO_CONFIG_DONE_MSG */ +} __attribute__ ((__packed__)) TlSetupConfigDoneMsg; + +/* Asyncronous messages */ +typedef struct { + u_char sigNo; /* TL_SETUP_SIGNO_OPEN_MSG */ + u_char portNo; +} __attribute__ ((__packed__)) TlSetupOpenMsg; + +typedef struct { + u_char sigNo; /* TL_SETUP_SIGNO_CLOSE_MSG */ + u_char portNo; +} __attribute__ ((__packed__)) TlSetupCloseMsg; + +/* Driver type - for use in TlSetupInfoMsg.driverType */ +#define COMM_DRIVER 0 +#define NDISWAN_DRIVER 1 +#define NDISWAN_DRIVER_MAJOR_VERSION 2 +#define NDISWAN_DRIVER_MINOR_VERSION 0 + +/* it should not matter when this message comes over as we just store the results and send the ACK. */ +typedef struct { + u_char sigNo; /* TL_SETUP_SIGNO_INFO_MSG */ + u_char driverType; + u_char majorVersion; + u_char minorVersion; +} __attribute__ ((__packed__)) TlSetupInfoMsg; + +typedef struct { + u_char sigNo; /* TL_SETUP_SIGNO_INFO_MSG_ACK */ +} __attribute__ ((__packed__)) TlSetupInfoMsgAck; + +typedef struct { + u_char sigNo; /* TL_SETUP_SIGNO_REBOOT_MSG_ACK */ +} __attribute__ ((__packed__)) TlSetupRebootMsgAck; + +/* Define a union of all the msgs that the driver can receive from the card.*/ +typedef union { + u_char sigNo; + TlSetupGetVersionRsp VersRspMsg; + TlSetupOpenMsg OpenMsg; + TlSetupCloseMsg CloseMsg; + TlSetupInfoMsg InfoMsg; + TlSetupInfoMsgAck InfoMsgAck; +} __attribute__ ((__packed__)) SETUP_RX_MSG, *PSETUP_RX_MSG; + +/* Define a union of all the msgs that the driver can send to the card. + * This will then represent the max msg that can be sent by the driver to the card + */ +typedef union { + u_char sigNo; + TlSetupGetVersionQry QryMsg; + TlSetupConfigMsg ConfigMsg; + TlSetupConfigDoneMsg ConfigDoneMsg; + TlSetupOpenMsg OpenMsg; + TlSetupCloseMsg CloseMsg; + TlSetupInfoMsg InfoMsg; + TlSetupRebootMsgAck RebootMsgAck; +} __attribute__ ((__packed__)) SETUP_TX_MSG, *PSETUP_TX_MSG; + +#endif /* _IPWIRELESS_CS_SETUP_PROTOCOL_H_ */ diff --git a/drivers/char/pcmcia/ipwireless_cs_tty.c b/drivers/char/pcmcia/ipwireless_cs_tty.c new file mode 100644 index 0000000..241164f --- /dev/null +++ b/drivers/char/pcmcia/ipwireless_cs_tty.c @@ -0,0 +1,779 @@ +/* + * IPWireless 3G PCMCIA Network Driver + * + * Original code + * by Stephen Blackheath , + * Ben Martel + * + * Copyrighted as follows: + * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) + * + * Various driver changes and rewrites, port to new kernels + * Copyright (C) 2006-2007 Jiri Kosina + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipwireless_cs_tty.h" +#include "ipwireless_cs_network.h" +#include "ipwireless_cs_hardware.h" +#include "ipwireless_cs_main.h" + +#define IPWIRELESS_PCMCIA_START 0 +#define IPWIRELESS_PCMCIA_MINORS 32 + +#define TTYTYPE_MODEM 0 +#define TTYTYPE_MONITOR 1 +#define TTYTYPE_CONSOLE 2 +#define TTYTYPE_RAS_RAW 3 + +typedef struct ipw_tty_t { + int index; + struct ipw_hardware_t *hardware; + unsigned int channelIdx; + int ttyType; + struct ipw_network_t *network; + struct tty_struct *linux_tty; + int open_count; + struct pcmcia_device *link; + unsigned int control_lines; + struct semaphore sem; /* locks this structure */ + int tx_bytes_queued; + int closing; +} ipw_tty_t; + +static int actual_major; + +static ipw_tty_t *ttys[IPWIRELESS_PCMCIA_MINORS]; + +static char *ttyType_name(int ttyType) +{ + static char *channelNames[] = { + "modem", + "monitor", + "console", + "RAS-raw" + }; + return channelNames[ttyType]; +} + +static void report_registering(ipw_tty_t * tty) +{ + char *iftype = ttyType_name(tty->ttyType); + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": registering %s device ttyIPWp%d\n", iftype, tty->index); +} + +static void report_deregistering(ipw_tty_t * tty) +{ + char *iftype = ttyType_name(tty->ttyType); + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": deregistering %s device ttyIPWp%d\n", iftype, + tty->index); +} + +static ipw_tty_t *get_tty(int minor) +{ + if (minor < IPWIRELESS_PCMCIA_START + || minor >= IPWIRELESS_PCMCIA_START + IPWIRELESS_PCMCIA_MINORS) + return NULL; + else { + int minor_offset = minor - IPWIRELESS_PCMCIA_START; + /* The 'console' channel is only available when 'vendor' mode is enabled. */ + if (!ipwireless_cs_vendor && minor_offset >= 16 + && minor_offset < 24) + return NULL; + + /* The 'ras_raw' channel is only available when 'loopback' mode is enabled. */ + if (!ipwireless_cs_loopback && minor_offset >= 24) + return NULL; + + return ttys[minor_offset]; + } +} + +static int ipw_open(struct tty_struct *linux_tty, struct file *filp) +{ + int minor = linux_tty->index; + ipw_tty_t *tty = get_tty(minor); + + if (tty == NULL) + return -ENODEV; + + down(&tty->sem); + + if (tty->closing) { + up(&tty->sem); + return -ENODEV; + } + if (tty->open_count == 0) + tty->tx_bytes_queued = 0; + + tty->link->open++; + tty->open_count++; + + tty->linux_tty = linux_tty; + linux_tty->driver_data = tty; + linux_tty->low_latency = 1; + + /* Have the console default to RTS and DTR on */ + if (tty->channelIdx == IPW_CHANNEL_CONSOLE) { + ipwireless_setRTS(tty->hardware, tty->channelIdx, 1); + ipwireless_setDTR(tty->hardware, tty->channelIdx, 1); + } + if (tty->ttyType == TTYTYPE_MODEM) + ipwireless_ppp_open(tty->network); + + up(&tty->sem); + + return 0; +} + +static void do_ipw_close(ipw_tty_t * tty) +{ + tty->open_count--; + tty->link->open--; + + if (tty->open_count == 0) { + struct tty_struct *linux_tty = tty->linux_tty; + if (linux_tty != NULL) { + tty->linux_tty = NULL; + linux_tty->driver_data = NULL; + + if (tty->ttyType == TTYTYPE_MODEM) + ipwireless_ppp_close(tty->network); + } + } +} + +static void ipw_hangup(struct tty_struct *linux_tty) +{ + ipw_tty_t *tty = (ipw_tty_t *) linux_tty->driver_data; + if (tty == NULL) + return; + + down(&tty->sem); + if (tty->open_count == 0) { + up(&tty->sem); + return; + } + + do_ipw_close(tty); + + up(&tty->sem); +} + +static void ipw_close(struct tty_struct *linux_tty, struct file *filp) +{ + ipw_hangup(linux_tty); +} + +/* Take data received from hardware, and send it out the tty */ +void +ipwireless_tty_received(struct ipw_tty_t *tty, u_char * data, + unsigned int length) +{ + struct tty_struct *linux_tty; + int i; + int work = 0; + + down(&tty->sem); + linux_tty = tty->linux_tty; + if (linux_tty == NULL) { + up(&tty->sem); + return; + } + + if (!tty->open_count) { + up(&tty->sem); + return; + } + up(&tty->sem); + + + tty_buffer_request_room(linux_tty, length); + for (i = 0; i < length; ++i) { + work += tty_insert_flip_char(linux_tty, data[i], 0); + } + if (work) { + tty_flip_buffer_push(linux_tty); + } +} + +static void ipw_write_packet_sent_callback(void *callback_data, + unsigned int packet_length) +{ + ipw_tty_t *tty = (ipw_tty_t *) callback_data; + + /* Packet has been sent, so we subtract the number of bytes from our + * tally of outstanding TX bytes. */ + tty->tx_bytes_queued -= packet_length; +} + +static int ipw_write(struct tty_struct *linux_tty, + const unsigned char *buf, int count) +{ + ipw_tty_t *tty = (ipw_tty_t *) linux_tty->driver_data; + int room; + + if (tty == NULL) + return -ENODEV; + + down(&tty->sem); + if (!tty->open_count) { + up(&tty->sem); + return -EINVAL; + } + + room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued; + if (room < 0) + room = 0; + /* Don't allow caller to write any more than we have room for */ + if (count > room) + count = room; + + if (count == 0) { + up(&tty->sem); + return 0; + } + + tty->tx_bytes_queued += count; + ipwireless_send_packet(tty->hardware, tty->channelIdx, + (u_char *) buf, count, + ipw_write_packet_sent_callback, tty); + up(&tty->sem); + + return count; +} + +static int ipw_write_room(struct tty_struct *linux_tty) +{ + ipw_tty_t *tty = (ipw_tty_t *) linux_tty->driver_data; + int room; + + if (tty == NULL) + return -ENODEV; + + if (!tty->open_count) + return -EINVAL; + + room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued; + if (room < 0) + room = 0; + return room; +} + +static int ipwireless_get_serial_info(ipw_tty_t * tty, + struct serial_struct __user * + retinfo) +{ + struct serial_struct tmp; + + /* This information is not particularly useful, but providing it stops + * setserial giving an error, and therefore it prevents an ugly + * looking error message in the system log when the user plugs the + * PCCard device in. */ + if (!retinfo) + return (-EFAULT); + memset(&tmp, 0, sizeof(tmp)); + tmp.type = PORT_UNKNOWN; + tmp.line = tty->index; + tmp.port = 0; + tmp.irq = 0; + tmp.flags = 0; + tmp.baud_base = 115200; + tmp.close_delay = 0; + tmp.closing_wait = 0; + tmp.custom_divisor = 0; + tmp.hub6 = 0; + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return (0); +} + +static int ppp_ioctl(struct tty_struct *linux_tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + ipw_tty_t *tty = (ipw_tty_t *) linux_tty->driver_data; + + if (tty == NULL) + return -ENODEV; + + if (!tty->open_count) + return -EINVAL; + + /* A couple of calls that don't do much, but which keep the PCMCIA scripts + * from reporting errors. + */ + switch (cmd) { + case TIOCGSERIAL: + return ipwireless_get_serial_info(tty, + (void __user *) arg); + + case TIOCSSERIAL: + return 0; /* Keeps the PCMCIA scripts happy. */ + } + + if (tty->ttyType == TTYTYPE_MODEM) { + switch (cmd) { + + /* This is used by pppd to find out how to contact the channel through + the /dev/ppp device. + + This ppp thing works in a non-obvious fashion: + pppd talks to a tty that "represents" the channel, and then asks it + (through PPPIOCGCHAN) what ppp channel to use (a ppp_generic concept). + It then communicates to that channel through the ppp_generic system + via the /dev/ppp device. To the user it appears pppd is talking to the + specified tty, but no data is transferred through that tty at all. + */ + case PPPIOCGCHAN: + { + int chan = ipwireless_ppp_channel_index(tty->network); + if (chan < 0) + return -ENODEV; + if (put_user(chan, (int *) arg)) + return -EFAULT; + } + return 0; + + case PPPIOCGUNIT: + { + int unit = ipwireless_ppp_unit_number(tty->network); + if (unit < 0) + return -ENODEV; + if (put_user(unit, (int *) arg)) + return -EFAULT; + } + return 0; + + case TCGETS: + case TCGETA: + return n_tty_ioctl(linux_tty, file, cmd, arg); + + case TCFLSH: + return n_tty_ioctl(linux_tty, file, cmd, arg); + + case FIONREAD: + { + int val = 0; + if (put_user(val, (int *) arg)) + return -EFAULT; + return 0; + } + } + } + + return -ENOIOCTLCMD; +} + +static int ipw_chars_in_buffer(struct tty_struct *linux_tty) +{ + ipw_tty_t *tty = (ipw_tty_t *) linux_tty->driver_data; + + if (tty == NULL) + return -ENODEV; + + if (!tty->open_count) + return -EINVAL; + + return tty->tx_bytes_queued; +} + +static int get_control_lines(ipw_tty_t * tty) +{ + unsigned int my = tty->control_lines; + unsigned int out = 0; + if (my & IPW_CONTROL_LINE_RTS) + out |= TIOCM_RTS; + if (my & IPW_CONTROL_LINE_DTR) + out |= TIOCM_DTR; + if (my & IPW_CONTROL_LINE_CTS) + out |= TIOCM_CTS; + if (my & IPW_CONTROL_LINE_DSR) + out |= TIOCM_DSR; + if (my & IPW_CONTROL_LINE_DCD) + out |= TIOCM_CD; + return out; +} + +static int set_control_lines(ipw_tty_t * tty, unsigned int set, + unsigned int clear) +{ + if (set & TIOCM_RTS) + ipwireless_setRTS(tty->hardware, tty->channelIdx, 1); + if (set & TIOCM_DTR) + ipwireless_setDTR(tty->hardware, tty->channelIdx, 1); + if (clear & TIOCM_RTS) + ipwireless_setRTS(tty->hardware, tty->channelIdx, 0); + if (clear & TIOCM_DTR) + ipwireless_setDTR(tty->hardware, tty->channelIdx, 0); + return 0; +} + +static int ipw_tiocmget(struct tty_struct *linux_tty, struct file *file) +{ + ipw_tty_t *tty = (ipw_tty_t *) linux_tty->driver_data; + + if (tty == NULL) + return -ENODEV; + + if (!tty->open_count) + return -EINVAL; + + return get_control_lines(tty); +} + +static int +ipw_tiocmset(struct tty_struct *linux_tty, struct file *file, + unsigned int set, unsigned int clear) +{ + ipw_tty_t *tty = (ipw_tty_t *) linux_tty->driver_data; + + if (tty == NULL) + return -ENODEV; + + if (!tty->open_count) + return -EINVAL; + + return set_control_lines(tty, set, clear); +} + + +static int +ipw_ioctl(struct tty_struct *linux_tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return ppp_ioctl(linux_tty, file, cmd, arg); +} + +void ipw_flush_buffer(struct tty_struct *tty) +{ + wake_up_interruptible(&tty->write_wait); +} + +static void add_tty(int first, int *j, dev_node_t ** nodesp, + struct pcmcia_device *link, + struct ipw_hardware_t *hardware, + struct ipw_network_t *network, int channelIdx, + int ttyType) +{ + ttys[*j] = (ipw_tty_t *) kmalloc(sizeof(ipw_tty_t), GFP_KERNEL); + memset(ttys[*j], 0, sizeof(ipw_tty_t)); + ttys[*j]->index = *j; + ttys[*j]->hardware = hardware; + ttys[*j]->channelIdx = channelIdx; + ttys[*j]->network = network; + ttys[*j]->ttyType = ttyType; + ttys[*j]->link = link; + init_MUTEX(&ttys[*j]->sem); + ipwireless_associate_network_tty(network, channelIdx, ttys[*j]); + + if (nodesp != NULL) { + if (!first) + (*nodesp)[-1].next = *nodesp; + sprintf((*nodesp)->dev_name, "ttyIPWp%d", *j); + (*nodesp)->major = actual_major; + (*nodesp)->minor = (*j) + IPWIRELESS_PCMCIA_START; + (*nodesp)++; + } + if (get_tty((*j) + IPWIRELESS_PCMCIA_START) == ttys[*j]) + report_registering(ttys[*j]); + (*j) += 8; +} + +struct ipw_tty_t *ipwireless_tty_create(struct pcmcia_device *link, + struct ipw_hardware_t *hardware, + struct ipw_network_t *network, + dev_node_t * nodes) +{ + int i; + for (i = 0; i < 8; i++) { + int allfree = 1; + { + int j; + for (j = i; j < IPWIRELESS_PCMCIA_MINORS; j += 8) + if (ttys[j] != NULL) { + allfree = 0; + break; + } + } + if (allfree) { + int j = i; + add_tty(1, &j, &nodes, link, hardware, network, + IPW_CHANNEL_RAS, TTYTYPE_MODEM); + /* NULL: Don't report the existence of CONSOLE and RAS_RAW channels to the PCMCIA + * subsystem, since they are only there under certain modes. + * This prevents and unpleasant error message appearing in the log when it fails + * to open them. */ + add_tty(0, &j, NULL, link, hardware, network, + IPW_CHANNEL_CONSOLE, TTYTYPE_CONSOLE); + add_tty(0, &j, NULL, link, hardware, network, + IPW_CHANNEL_RAS, TTYTYPE_RAS_RAW); + + return ttys[i]; + } + } + return NULL; +} + +/*! + * Must be called before ipwireless_network_free(). + */ +void ipwireless_tty_free(struct ipw_tty_t *tty) +{ + int j; + struct ipw_network_t *network = ttys[tty->index]->network; + for (j = tty->index; j < IPWIRELESS_PCMCIA_MINORS; j += 8) { + ipw_tty_t *tty = ttys[j]; + if (tty != NULL) { + down(&tty->sem); + if (get_tty(j + IPWIRELESS_PCMCIA_START) == tty) + report_deregistering(tty); + tty->closing = 1; + if (tty->linux_tty != NULL) { + up(&tty->sem); + tty_hangup(tty->linux_tty); + /* Wait till the tty_hangup has really completed. */ + flush_scheduled_work(); + down(&tty->sem); + } + while (tty->open_count) + do_ipw_close(tty); + ipwireless_disassociate_network_ttys(network, + tty-> + channelIdx); + ttys[j] = NULL; + up(&tty->sem); + kfree(tty); + } + } +} + +static struct tty_operations tty_ops = { + .open = ipw_open, + .close = ipw_close, + .hangup = ipw_hangup, + .write = ipw_write, + .write_room = ipw_write_room, + .ioctl = ipw_ioctl, + .chars_in_buffer = ipw_chars_in_buffer, + .tiocmget = ipw_tiocmget, + .tiocmset = ipw_tiocmset, +}; + +struct tty_driver *ipw_tty_driver; + +static int proc_read(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + int i, j; + char *p = page; + int len; +#ifdef IPWIRELESS_STATE_DEBUG + int ipwireless_dump_hardware_state(char *p, + struct ipw_hardware_t *hw); + int ipwireless_dump_network_state(char *p, + struct ipw_network_t *network); +#endif + + if (off == 0) { + p += sprintf(p, "driver: %s\nversion: %s\n\n", + IPWIRELESS_PCCARD_NAME, + IPWIRELESS_PCMCIA_VERSION); + for (i = 0; i < 8; i++) + for (j = i; j < IPWIRELESS_PCMCIA_MINORS; j += 8) + if (ttys[j] != NULL && get_tty (ttys[j]->index + IPWIRELESS_PCMCIA_START) == ttys[j]) + p += sprintf(p, "port: /dev/ttyIPWp%d %s %d\n", + j, ttyType_name (ttys[j]->ttyType), ttys[j]->open_count); +#ifdef IPWIRELESS_STATE_DEBUG + *p++ = '\n'; + for (i = 0; i < 8; i++) + if (ttys[i] != NULL) { + p += ipwireless_dump_hardware_state(p, ttys[i]->hardware); + if (ttys[i]->network != NULL) + p += ipwireless_dump_network_state(p, ttys[i]->network); + break; + } +#endif + } + + len = (p - page); + if (len <= off + count) + *eof = 1; + *start = page + off; + len -= off; + if (len > count) + len = count; + if (len < 0) + len = 0; + return len; +} + +static int proc_write(struct file *file, const char __user * buffer, + unsigned long count, void *data) +{ + char *command = (char *) kmalloc(count + 1, GFP_KERNEL); + int i, lineLen; + ipw_tty_t *could_open[IPWIRELESS_PCMCIA_MINORS]; + + if (copy_from_user(command, buffer, count)) { + kfree(command); + return -EFAULT; + } + command[count] = '\0'; + + for (i = 0; i < IPWIRELESS_PCMCIA_MINORS; ++i) + could_open[i] = get_tty(i + IPWIRELESS_PCMCIA_START); + + for (i = 0; i < count; i += lineLen + 1) { + char *cmd; + lineLen = 0; + while (command[i + lineLen] != '\n' + && command[i + lineLen] != '\0') + lineLen++; + command[i + lineLen] = '\0'; + + cmd = command + i; + + if (strncmp(cmd, "debug=", 6) == 0) { + ipwireless_cs_debug = + (int) simple_strtol(cmd + 6, NULL, 10); + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": set debug=%d\n", ipwireless_cs_debug); + } else if (strncmp(cmd, "vendor=", 7) == 0) { + ipwireless_cs_vendor = + (int) simple_strtol(cmd + 7, NULL, 10); + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": set vendor=%d\n", ipwireless_cs_vendor); + } else if (strncmp(cmd, "loopback=", 9) == 0) { + ipwireless_cs_loopback = + (int) simple_strtol(cmd + 9, NULL, 10); + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": set loopback=%d\n", + ipwireless_cs_loopback); + } else if (strncmp(cmd, "out_queue=", 10) == 0) { + ipwireless_cs_out_queue = + (int) simple_strtol(cmd + 10, NULL, 10); + printk(KERN_INFO IPWIRELESS_PCCARD_NAME + ": set out_queue=%d\n", + ipwireless_cs_out_queue); + } + } + kfree(command); + + /* Report any created or destroyed ttys */ + for (i = 0; i < IPWIRELESS_PCMCIA_MINORS; ++i) { + ipw_tty_t *can_open = get_tty(i + IPWIRELESS_PCMCIA_START); + if (can_open && !could_open[i]) + report_registering(can_open); + if (!can_open && could_open[i]) + report_deregistering(could_open[i]); + } + return count; +} + +int ipwireless_tty_init(int major) +{ + int result; + + ipw_tty_driver = alloc_tty_driver(IPWIRELESS_PCMCIA_MINORS); + if (!ipw_tty_driver) + return -ENOMEM; + + ipw_tty_driver->owner = THIS_MODULE; + ipw_tty_driver->driver_name = IPWIRELESS_PCCARD_NAME; + ipw_tty_driver->name = "ttyIPWp"; + ipw_tty_driver->major = major; /* Major number is automatically allocated. */ + ipw_tty_driver->minor_start = IPWIRELESS_PCMCIA_START; + ipw_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + ipw_tty_driver->subtype = SERIAL_TYPE_NORMAL; + ipw_tty_driver->flags = TTY_DRIVER_REAL_RAW; + ipw_tty_driver->init_termios = tty_std_termios; + ipw_tty_driver->init_termios.c_cflag = + B9600 | CS8 | CREAD | HUPCL | CLOCAL; + tty_set_operations(ipw_tty_driver, &tty_ops); + result = tty_register_driver(ipw_tty_driver); + if (result) { + printk(KERN_ERR IPWIRELESS_PCCARD_NAME + ": failed to register tty driver\n"); + put_tty_driver(ipw_tty_driver); + return result; + } + + actual_major = ipw_tty_driver->major; + + { + struct proc_dir_entry *proc_ent; + proc_ent = + create_proc_entry(IPWIRELESS_PCCARD_NAME, + S_IRUGO | S_IWUSR | S_IWGRP, + &proc_root); + if (proc_ent != NULL) { + proc_ent->read_proc = proc_read; + proc_ent->write_proc = proc_write; + } + } + + return 0; +} + +void ipwireless_tty_release() +{ + int ret, loops = 0; + remove_proc_entry(IPWIRELESS_PCCARD_NAME, &proc_root); + + while (1) { + ret = tty_unregister_driver(ipw_tty_driver); + put_tty_driver(ipw_tty_driver); + if (ret != 0) { + if (loops == 0) + printk(KERN_ERR IPWIRELESS_PCCARD_NAME + ": tty_unregister_driver failed %d - retrying...\n", + ret); + } else + break; + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ / 5); + loops++; + } +} + +/*! + * Return true if this tty represents the modem tty, that is, the one to + * which the ppp subsystem talks when it is online. + */ +int ipwireless_tty_is_modem(struct ipw_tty_t *tty) +{ + return tty->ttyType == TTYTYPE_MODEM; +} + +void +ipwireless_tty_notify_control_line_change(struct ipw_tty_t *tty, + unsigned int channelIdx, + unsigned int control_lines, + unsigned int changed_mask) +{ + /* Update the bits that have changed. */ + /* The effect for the modem port is that the control lines are combined for + * the RAS and DIALLER ports, exactly as in the Macintosh driver. */ + tty->control_lines = (tty->control_lines & ~changed_mask) + | (control_lines & changed_mask); +} + diff --git a/drivers/char/pcmcia/ipwireless_cs_tty.h b/drivers/char/pcmcia/ipwireless_cs_tty.h new file mode 100644 index 0000000..755076e --- /dev/null +++ b/drivers/char/pcmcia/ipwireless_cs_tty.h @@ -0,0 +1,52 @@ +/* + * IPWireless 3G PCMCIA Network Driver + * + * Original code + * by Stephen Blackheath , + * Ben Martel + * + * Copyrighted as follows: + * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) + * + * Various driver changes and rewrites, port to new kernels + * Copyright (C) 2006-2007 Jiri Kosina + */ + +#ifndef _IPWIRELESS_CS_TTY_H_ +#define _IPWIRELESS_CS_TTY_H_ + +#include +#include + +#include +#include +#include +#include + +struct ipw_tty_t; +struct ipw_network_t; +struct ipw_hardware_t; + +/* + * Returns 0 on success. + */ +int ipwireless_tty_init(int); + +void ipwireless_tty_release(void); + +struct ipw_tty_t *ipwireless_tty_create(struct pcmcia_device *, struct ipw_hardware_t *, + struct ipw_network_t *, dev_node_t *); + +/* Must be called before ipwireless_network_free() */ +void ipwireless_tty_free(struct ipw_tty_t *); +void ipwireless_tty_received(struct ipw_tty_t *, u_char *, unsigned int); + +/* + * Return true if this tty represents the modem tty, that is, the one to + * which the ppp subsystem talks when it is online. + */ +int ipwireless_tty_is_modem(struct ipw_tty_t *); +void ipwireless_tty_notify_control_line_change(struct ipw_tty_t *,unsigned int, + unsigned int, unsigned int); + +#endif