📄 usb-char.c
字号:
/*
* (C) Copyright 2000-2001 Extenex Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* usb-char.c
*
* Miscellaneous character device interface for SA1100 USB function
* driver.
*
* Background:
* The SA1100 function driver ported from the Compaq Itsy project
* has an interface, usb-eth.c, to feed network packets over the
* usb wire and into the Linux TCP/IP stack.
*
* This file replaces that one with a simple character device
* interface that allows unstructured "byte pipe" style reads and
* writes over the USB bulk endpoints by userspace programs.
*
* A new define, CONFIG_SA1100_USB_NETLINK, has been created that,
* when set, (the default) causes the ethernet interface to be used.
* When not set, this more pedestrian character interface is linked
* in instead.
*
* Please see linux/Documentation/arm/SA1100/SA1100_USB for details.
*
* ward.willats@extenex.com
*
* To do:
* - Can't dma into ring buffer directly with pci_map/unmap usb_recv
* uses and get bytes out at the same time DMA is going on. Investigate:
* a) changing usb_recv to use alloc_consistent() at client request; or
* b) non-ring-buffer based data structures. In the meantime, I am using
* a bounce buffer. Simple, but wasteful.
*/
#include <linux/module.h>
#include <linux/config.h>
#include <linux/miscdevice.h>
#include <linux/malloc.h>
#include <linux/init.h>
#include <linux/cache.h>
#include <linux/poll.h>
#include <linux/circ_buf.h>
#include <linux/timer.h>
#include <asm/io.h>
#include <asm/semaphore.h>
#include <asm/proc/page.h>
#include <asm/mach-types.h>
#include "usb-char.h"
#include "sa1100_usb.h"
#warning TODO: update config.in to add Etchstone device.
#define machine_is_etchstone1() (1)
#define CONFIG_SA1100_ETCHSTONE1
#ifdef CONFIG_SA1100_EXTENEX1
#error TODO: Do not select Extenex device. Bad things will happen.
#endif
//////////////////////////////////////////////////////////////////////////////
// Driver Options
//////////////////////////////////////////////////////////////////////////////
#define VERSION "0.4"
#define VERBOSITY 0
#if VERBOSITY
# define PRINTK(x, a...) printk (x, ## a)
#else
# define PRINTK(x, a...) /**/
#endif
//////////////////////////////////////////////////////////////////////////////
// Globals - Macros - Enums - Structures
//////////////////////////////////////////////////////////////////////////////
#ifndef MIN
#define MIN( a, b ) ((a)<(b)?(a):(b))
#endif
typedef int bool; enum { false = 0, true = 1 };
static const char pszMe[] = "usbchr: ";
static wait_queue_head_t wq_read;
static wait_queue_head_t wq_write;
static wait_queue_head_t wq_poll;
/* Serialze multiple writers onto the transmit hardware
.. since we sleep the writer during transmit to stay in
.. sync. (Multiple writers don't make much sense, but..) */
static DECLARE_MUTEX( xmit_sem );
// size of usb DATA0/1 packets. 64 is standard maximum
// for bulk transport, though most hosts seem to be able
// to handle larger.
#define TX_PACKET_SIZE 64
#define RX_PACKET_SIZE 64
#define RBUF_SIZE (4*PAGE_SIZE)
static struct wcirc_buf {
char *buf;
int in;
int out;
} rx_ring = { NULL, 0, 0 };
static struct {
unsigned long cnt_rx_complete;
unsigned long cnt_rx_errors;
unsigned long bytes_rx;
unsigned long cnt_tx_timeouts;
unsigned long cnt_tx_errors;
unsigned long bytes_tx;
} charstats;
static char * tx_buf = NULL;
static char * packet_buffer = NULL;
static int sending = 0;
static int usb_ref_count = 0;
static int last_tx_result = 0;
static int last_rx_result = 0;
static int last_tx_size = 0;
static struct timer_list tx_timer;
//////////////////////////////////////////////////////////////////////////////
// Prototypes
//////////////////////////////////////////////////////////////////////////////
static char * what_the_f( int e );
static void free_txrx_buffers( void );
static void twiddle_descriptors( void );
static void free_string_descriptors( void ) ;
static int usbc_open( struct inode *pInode, struct file *pFile );
static void rx_done_callback_packet_buffer( int flag, int size );
static void tx_timeout( unsigned long );
static void tx_done_callback( int flag, int size );
static ssize_t usbc_read( struct file *, char *, size_t, loff_t * );
static ssize_t usbc_write( struct file *, const char *, size_t, loff_t * );
static unsigned int usbc_poll( struct file *pFile, poll_table * pWait );
static int usbc_ioctl( struct inode *pInode, struct file *pFile,
unsigned int nCmd, unsigned long argument );
static int usbc_close( struct inode *pInode, struct file *pFile );
#ifdef CONFIG_SA1100_EXTENEX1
static void extenex_configured_notify_proc( void );
#endif
#ifdef CONFIG_SA1100_ETCHSTONE1
static void etchstone_configured_notify_proc( void );
#endif
//////////////////////////////////////////////////////////////////////////////
// Private Helpers
//////////////////////////////////////////////////////////////////////////////
static char * what_the_f( int e )
{
char * p;
switch( e ) {
case 0:
p = "noErr";
break;
case -ENODEV:
p = "ENODEV - usb not in config state";
break;
case -EBUSY:
p = "EBUSY - another request on the hardware";
break;
case -EAGAIN:
p = "EAGAIN";
break;
case -EINTR:
p = "EINTR - interrupted\n";
break;
case -EPIPE:
p = "EPIPE - zero length xfer\n";
break;
default:
p = "????";
break;
}
return p;
}
static void free_txrx_buffers( void )
{
if ( rx_ring.buf != NULL ) {
kfree( rx_ring.buf );
rx_ring.buf = NULL;
}
if ( packet_buffer != NULL ) {
kfree( packet_buffer );
packet_buffer = NULL;
}
if ( tx_buf != NULL ) {
kfree( tx_buf );
tx_buf = NULL;
}
}
/* twiddle_descriptors()
* It is between open() and start(). Setup descriptors.
*/
static void twiddle_descriptors( void )
{
desc_t * pDesc = sa1100_usb_get_descriptor_ptr();
string_desc_t * pString;
pDesc->b.ep1.wMaxPacketSize = make_word_c( RX_PACKET_SIZE );
pDesc->b.ep1.bmAttributes = USB_EP_BULK;
pDesc->b.ep2.wMaxPacketSize = make_word_c( TX_PACKET_SIZE );
pDesc->b.ep2.bmAttributes = USB_EP_BULK;
if ( machine_is_extenex1() ) {
#ifdef CONFIG_SA1100_EXTENEX1
pDesc->dev.idVendor = make_word_c( 0xC9F );
pDesc->dev.idProduct = 1;
pDesc->dev.bcdDevice = make_word_c( 0x0001 );
pDesc->b.cfg.bmAttributes = USB_CONFIG_SELFPOWERED;
pDesc->b.cfg.MaxPower = 0;
pString = sa1100_usb_kmalloc_string_descriptor( "Extenex" );
if ( pString ) {
sa1100_usb_set_string_descriptor( 1, pString );
pDesc->dev.iManufacturer = 1;
}
pString = sa1100_usb_kmalloc_string_descriptor( "Handheld Theater" );
if ( pString ) {
sa1100_usb_set_string_descriptor( 2, pString );
pDesc->dev.iProduct = 2;
}
pString = sa1100_usb_kmalloc_string_descriptor( "00000000" );
if ( pString ) {
sa1100_usb_set_string_descriptor( 3, pString );
pDesc->dev.iSerialNumber = 3;
}
pString = sa1100_usb_kmalloc_string_descriptor( "HHT Bulk Transfer" );
if ( pString ) {
sa1100_usb_set_string_descriptor( 4, pString );
pDesc->b.intf.iInterface = 4;
}
sa1100_set_configured_callback( extenex_configured_notify_proc );
#endif
}
else if ( machine_is_etchstone1() ) {
#ifdef CONFIG_SA1100_ETCHSTONE1
#define ETCHSTONE_VENDOR_ID 0x0471
#define ETCHSTONE_ESLATE_ID 0x0222
printk( "%s: using vendor=0x%04x, product=0x%04x.\n", pszMe,
ETCHSTONE_VENDOR_ID, ETCHSTONE_ESLATE_ID );
pDesc->dev.idVendor = make_word_c( ETCHSTONE_VENDOR_ID );
pDesc->dev.idProduct = make_word_c( ETCHSTONE_ESLATE_ID );
pDesc->dev.bcdDevice = make_word_c( 0 );
pDesc->b.cfg.bmAttributes = USB_CONFIG_SELFPOWERED;
pDesc->b.cfg.MaxPower = 0;
pString = sa1100_usb_kmalloc_string_descriptor( "Etchstone" );
if ( pString ) {
sa1100_usb_set_string_descriptor( 1, pString );
pDesc->dev.iManufacturer = 1;
}
pString = sa1100_usb_kmalloc_string_descriptor( "eSlate" );
if ( pString ) {
sa1100_usb_set_string_descriptor( 2, pString );
pDesc->dev.iProduct = 2;
}
#warning TODO: USB serial number
printk("%s: using hardcoded 00000000 serial number.\n", pszMe);
pString = sa1100_usb_kmalloc_string_descriptor( "00000000" );
if ( pString ) {
sa1100_usb_set_string_descriptor( 3, pString );
pDesc->dev.iSerialNumber = 3;
}
pString = sa1100_usb_kmalloc_string_descriptor( "HHT Bulk Transfer" );
if ( pString ) {
sa1100_usb_set_string_descriptor( 4, pString );
pDesc->b.intf.iInterface = 4;
}
sa1100_set_configured_callback( etchstone_configured_notify_proc );
#endif
}
}
static void free_string_descriptors( void )
{
if ( machine_is_extenex1() ) {
string_desc_t * pString;
int i;
for( i = 1 ; i <= 4 ; i++ ) {
pString = sa1100_usb_get_string_descriptor( i );
if ( pString )
kfree( pString );
}
}
}
//////////////////////////////////////////////////////////////////////////////
// ASYNCHRONOUS
//////////////////////////////////////////////////////////////////////////////
static void kick_start_rx( void )
{
if ( usb_ref_count ) {
int total_space = CIRC_SPACE( rx_ring.in, rx_ring.out, RBUF_SIZE );
if ( total_space >= RX_PACKET_SIZE ) {
sa1100_usb_recv( packet_buffer, RX_PACKET_SIZE,
rx_done_callback_packet_buffer );
}
}
}
/*
* rx_done_callback_packet_buffer()
* We have completed a DMA xfer into the temp packet buffer.
* Move to ring.
*
* flag values:
* on init, -EAGAIN
* on reset, -EINTR
* on RPE, -EIO
* on short packet -EPIPE
*/
static void
rx_done_callback_packet_buffer( int flag, int size )
{
charstats.cnt_rx_complete++;
if ( flag == 0 || flag == -EPIPE ) {
size_t n;
charstats.bytes_rx += size;
n = CIRC_SPACE_TO_END( rx_ring.in, rx_ring.out, RBUF_SIZE );
n = MIN( n, size );
size -= n;
memcpy( &rx_ring.buf[ rx_ring.in ], packet_buffer, n );
rx_ring.in = (rx_ring.in + n) & (RBUF_SIZE-1);
memcpy( &rx_ring.buf[ rx_ring.in ], packet_buffer + n, size );
rx_ring.in = (rx_ring.in + size) & (RBUF_SIZE-1);
wake_up_interruptible( &wq_read );
wake_up_interruptible( &wq_poll );
last_rx_result = 0;
kick_start_rx();
} else if ( flag != -EAGAIN ) {
charstats.cnt_rx_errors++;
last_rx_result = flag;
wake_up_interruptible( &wq_read );
wake_up_interruptible( &wq_poll );
}
else /* init, start a read */
kick_start_rx();
}
static void tx_timeout( unsigned long unused )
{
printk( "%stx timeout\n", pszMe );
sa1100_usb_send_reset();
charstats.cnt_tx_timeouts++;
}
// on init, -EAGAIN
// on reset, -EINTR
// on TPE, -EIO
static void tx_done_callback( int flags, int size )
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -