2005-04-17 02:20:36 +04:00
/*
* Copyright ( c ) 1999 - 2005 Petko Manolov ( petkan @ users . sourceforge . net )
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* ChangeLog :
* . . . . Most of the time spent on reading sources & docs .
* v0 .2 . x First official release for the Linux kernel .
* v0 .3 .0 Beutified and structured , some bugs fixed .
* v0 .3 . x URBifying bulk requests and bugfixing . First relatively
* stable release . Still can touch device ' s registers only
* from top - halves .
* v0 .4 .0 Control messages remained unurbified are now URBs .
* Now we can touch the HW at any time .
* v0 .4 .9 Control urbs again use process context to wait . Argh . . .
* Some long standing bugs ( enable_net_traffic ) fixed .
* Also nasty trick about resubmiting control urb from
* interrupt context used . Please let me know how it
* behaves . Pegasus II support added since this version .
* TODO : suppressing HCD warnings spewage on disconnect .
* v0 .4 .13 Ethernet address is now set at probe ( ) , not at open ( )
* time as this seems to break dhcpd .
* v0 .5 .0 branch to 2.5 . x kernels
* v0 .5 .1 ethtool support added
* v0 .5 .5 rx socket buffers are in a pool and the their allocation
* is out of the interrupt routine .
*/
# include <linux/sched.h>
# include <linux/slab.h>
# include <linux/init.h>
# include <linux/delay.h>
# include <linux/netdevice.h>
# include <linux/etherdevice.h>
# include <linux/ethtool.h>
# include <linux/mii.h>
# include <linux/usb.h>
# include <linux/module.h>
# include <asm/byteorder.h>
# include <asm/uaccess.h>
# include "pegasus.h"
/*
* Version Information
*/
2005-11-15 10:48:23 +03:00
# define DRIVER_VERSION "v0.6.13 (2005 / 11 / 13)"
2005-04-17 02:20:36 +04:00
# define DRIVER_AUTHOR "Petko Manolov <petkan@users.sourceforge.net>"
# define DRIVER_DESC "Pegasus / Pegasus II USB Ethernet driver"
static const char driver_name [ ] = " pegasus " ;
# undef PEGASUS_WRITE_EEPROM
# define BMSR_MEDIA (BMSR_10HALF | BMSR_10FULL | BMSR_100HALF | \
BMSR_100FULL | BMSR_ANEGCAPABLE )
static int loopback = 0 ;
static int mii_mode = 0 ;
2005-11-15 10:55:18 +03:00
static char * devid = NULL ;
2005-04-17 02:20:36 +04:00
static struct usb_eth_dev usb_dev_id [ ] = {
# define PEGASUS_DEV(pn, vid, pid, flags) \
{ . name = pn , . vendor = vid , . device = pid , . private = flags } ,
# include "pegasus.h"
# undef PEGASUS_DEV
2005-11-15 10:55:18 +03:00
{ NULL , 0 , 0 , 0 } ,
2005-04-17 02:20:36 +04:00
{ NULL , 0 , 0 , 0 }
} ;
static struct usb_device_id pegasus_ids [ ] = {
# define PEGASUS_DEV(pn, vid, pid, flags) \
{ . match_flags = USB_DEVICE_ID_MATCH_DEVICE , . idVendor = vid , . idProduct = pid } ,
# include "pegasus.h"
# undef PEGASUS_DEV
2005-11-15 10:55:18 +03:00
{ } ,
2005-04-17 02:20:36 +04:00
{ }
} ;
MODULE_AUTHOR ( DRIVER_AUTHOR ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_LICENSE ( " GPL " ) ;
module_param ( loopback , bool , 0 ) ;
module_param ( mii_mode , bool , 0 ) ;
2005-11-15 10:55:18 +03:00
module_param ( devid , charp , 0 ) ;
2005-04-17 02:20:36 +04:00
MODULE_PARM_DESC ( loopback , " Enable MAC loopback mode (bit 0) " ) ;
MODULE_PARM_DESC ( mii_mode , " Enable HomePNA mode (bit 0),default=MII mode = 0 " ) ;
2005-11-15 10:55:18 +03:00
MODULE_PARM_DESC ( devid , " The format is: 'DEV_name:VendorID:DeviceID:Flags' " ) ;
2005-04-17 02:20:36 +04:00
/* use ethtool to change the level for any given device */
static int msg_level = - 1 ;
module_param ( msg_level , int , 0 ) ;
MODULE_PARM_DESC ( msg_level , " Override default message level " ) ;
MODULE_DEVICE_TABLE ( usb , pegasus_ids ) ;
static int update_eth_regs_async ( pegasus_t * ) ;
/* Aargh!!! I _really_ hate such tweaks */
static void ctrl_callback ( struct urb * urb , struct pt_regs * regs )
{
pegasus_t * pegasus = urb - > context ;
if ( ! pegasus )
return ;
switch ( urb - > status ) {
case 0 :
if ( pegasus - > flags & ETH_REGS_CHANGE ) {
pegasus - > flags & = ~ ETH_REGS_CHANGE ;
pegasus - > flags | = ETH_REGS_CHANGED ;
update_eth_regs_async ( pegasus ) ;
return ;
}
break ;
case - EINPROGRESS :
return ;
case - ENOENT :
break ;
default :
if ( netif_msg_drv ( pegasus ) )
2005-11-15 10:48:23 +03:00
dev_dbg ( & pegasus - > intf - > dev , " %s, status %d \n " ,
2005-04-17 02:20:36 +04:00
__FUNCTION__ , urb - > status ) ;
}
pegasus - > flags & = ~ ETH_REGS_CHANGED ;
wake_up ( & pegasus - > ctrl_wait ) ;
}
static int get_registers ( pegasus_t * pegasus , __u16 indx , __u16 size ,
void * data )
{
int ret ;
char * buffer ;
DECLARE_WAITQUEUE ( wait , current ) ;
buffer = kmalloc ( size , GFP_KERNEL ) ;
if ( ! buffer ) {
if ( netif_msg_drv ( pegasus ) )
dev_warn ( & pegasus - > intf - > dev , " out of memory in %s \n " ,
__FUNCTION__ ) ;
return - ENOMEM ;
}
add_wait_queue ( & pegasus - > ctrl_wait , & wait ) ;
set_current_state ( TASK_UNINTERRUPTIBLE ) ;
while ( pegasus - > flags & ETH_REGS_CHANGED )
schedule ( ) ;
remove_wait_queue ( & pegasus - > ctrl_wait , & wait ) ;
set_current_state ( TASK_RUNNING ) ;
pegasus - > dr . bRequestType = PEGASUS_REQT_READ ;
pegasus - > dr . bRequest = PEGASUS_REQ_GET_REGS ;
pegasus - > dr . wValue = cpu_to_le16 ( 0 ) ;
pegasus - > dr . wIndex = cpu_to_le16p ( & indx ) ;
pegasus - > dr . wLength = cpu_to_le16p ( & size ) ;
pegasus - > ctrl_urb - > transfer_buffer_length = size ;
usb_fill_control_urb ( pegasus - > ctrl_urb , pegasus - > usb ,
usb_rcvctrlpipe ( pegasus - > usb , 0 ) ,
( char * ) & pegasus - > dr ,
buffer , size , ctrl_callback , pegasus ) ;
add_wait_queue ( & pegasus - > ctrl_wait , & wait ) ;
set_current_state ( TASK_UNINTERRUPTIBLE ) ;
/* using ATOMIC, we'd never wake up if we slept */
if ( ( ret = usb_submit_urb ( pegasus - > ctrl_urb , GFP_ATOMIC ) ) ) {
if ( netif_msg_drv ( pegasus ) )
dev_err ( & pegasus - > intf - > dev , " %s, status %d \n " ,
__FUNCTION__ , ret ) ;
goto out ;
}
schedule ( ) ;
out :
remove_wait_queue ( & pegasus - > ctrl_wait , & wait ) ;
memcpy ( data , buffer , size ) ;
kfree ( buffer ) ;
return ret ;
}
static int set_registers ( pegasus_t * pegasus , __u16 indx , __u16 size ,
void * data )
{
int ret ;
char * buffer ;
DECLARE_WAITQUEUE ( wait , current ) ;
buffer = kmalloc ( size , GFP_KERNEL ) ;
if ( ! buffer ) {
if ( netif_msg_drv ( pegasus ) )
dev_warn ( & pegasus - > intf - > dev , " out of memory in %s \n " ,
__FUNCTION__ ) ;
return - ENOMEM ;
}
memcpy ( buffer , data , size ) ;
add_wait_queue ( & pegasus - > ctrl_wait , & wait ) ;
set_current_state ( TASK_UNINTERRUPTIBLE ) ;
while ( pegasus - > flags & ETH_REGS_CHANGED )
schedule ( ) ;
remove_wait_queue ( & pegasus - > ctrl_wait , & wait ) ;
set_current_state ( TASK_RUNNING ) ;
pegasus - > dr . bRequestType = PEGASUS_REQT_WRITE ;
pegasus - > dr . bRequest = PEGASUS_REQ_SET_REGS ;
pegasus - > dr . wValue = cpu_to_le16 ( 0 ) ;
pegasus - > dr . wIndex = cpu_to_le16p ( & indx ) ;
pegasus - > dr . wLength = cpu_to_le16p ( & size ) ;
pegasus - > ctrl_urb - > transfer_buffer_length = size ;
usb_fill_control_urb ( pegasus - > ctrl_urb , pegasus - > usb ,
usb_sndctrlpipe ( pegasus - > usb , 0 ) ,
( char * ) & pegasus - > dr ,
buffer , size , ctrl_callback , pegasus ) ;
add_wait_queue ( & pegasus - > ctrl_wait , & wait ) ;
set_current_state ( TASK_UNINTERRUPTIBLE ) ;
if ( ( ret = usb_submit_urb ( pegasus - > ctrl_urb , GFP_ATOMIC ) ) ) {
if ( netif_msg_drv ( pegasus ) )
dev_err ( & pegasus - > intf - > dev , " %s, status %d \n " ,
__FUNCTION__ , ret ) ;
goto out ;
}
schedule ( ) ;
out :
remove_wait_queue ( & pegasus - > ctrl_wait , & wait ) ;
kfree ( buffer ) ;
return ret ;
}
static int set_register ( pegasus_t * pegasus , __u16 indx , __u8 data )
{
int ret ;
char * tmp ;
DECLARE_WAITQUEUE ( wait , current ) ;
tmp = kmalloc ( 1 , GFP_KERNEL ) ;
if ( ! tmp ) {
if ( netif_msg_drv ( pegasus ) )
dev_warn ( & pegasus - > intf - > dev , " out of memory in %s \n " ,
__FUNCTION__ ) ;
return - ENOMEM ;
}
memcpy ( tmp , & data , 1 ) ;
add_wait_queue ( & pegasus - > ctrl_wait , & wait ) ;
set_current_state ( TASK_UNINTERRUPTIBLE ) ;
while ( pegasus - > flags & ETH_REGS_CHANGED )
schedule ( ) ;
remove_wait_queue ( & pegasus - > ctrl_wait , & wait ) ;
set_current_state ( TASK_RUNNING ) ;
pegasus - > dr . bRequestType = PEGASUS_REQT_WRITE ;
pegasus - > dr . bRequest = PEGASUS_REQ_SET_REG ;
pegasus - > dr . wValue = cpu_to_le16 ( data ) ;
pegasus - > dr . wIndex = cpu_to_le16p ( & indx ) ;
pegasus - > dr . wLength = cpu_to_le16 ( 1 ) ;
pegasus - > ctrl_urb - > transfer_buffer_length = 1 ;
usb_fill_control_urb ( pegasus - > ctrl_urb , pegasus - > usb ,
usb_sndctrlpipe ( pegasus - > usb , 0 ) ,
( char * ) & pegasus - > dr ,
& tmp , 1 , ctrl_callback , pegasus ) ;
add_wait_queue ( & pegasus - > ctrl_wait , & wait ) ;
set_current_state ( TASK_UNINTERRUPTIBLE ) ;
if ( ( ret = usb_submit_urb ( pegasus - > ctrl_urb , GFP_ATOMIC ) ) ) {
if ( netif_msg_drv ( pegasus ) )
dev_err ( & pegasus - > intf - > dev , " %s, status %d \n " ,
__FUNCTION__ , ret ) ;
goto out ;
}
schedule ( ) ;
out :
remove_wait_queue ( & pegasus - > ctrl_wait , & wait ) ;
kfree ( tmp ) ;
return ret ;
}
static int update_eth_regs_async ( pegasus_t * pegasus )
{
int ret ;
pegasus - > dr . bRequestType = PEGASUS_REQT_WRITE ;
pegasus - > dr . bRequest = PEGASUS_REQ_SET_REGS ;
pegasus - > dr . wValue = 0 ;
pegasus - > dr . wIndex = cpu_to_le16 ( EthCtrl0 ) ;
pegasus - > dr . wLength = cpu_to_le16 ( 3 ) ;
pegasus - > ctrl_urb - > transfer_buffer_length = 3 ;
usb_fill_control_urb ( pegasus - > ctrl_urb , pegasus - > usb ,
usb_sndctrlpipe ( pegasus - > usb , 0 ) ,
( char * ) & pegasus - > dr ,
pegasus - > eth_regs , 3 , ctrl_callback , pegasus ) ;
if ( ( ret = usb_submit_urb ( pegasus - > ctrl_urb , GFP_ATOMIC ) ) )
if ( netif_msg_drv ( pegasus ) )
dev_err ( & pegasus - > intf - > dev , " %s, status %d \n " ,
__FUNCTION__ , ret ) ;
return ret ;
}
static int read_mii_word ( pegasus_t * pegasus , __u8 phy , __u8 indx , __u16 * regd )
{
int i ;
__u8 data [ 4 ] = { phy , 0 , 0 , indx } ;
__le16 regdi ;
int ret ;
2005-11-15 10:48:23 +03:00
set_register ( pegasus , PhyCtrl , 0 ) ;
set_registers ( pegasus , PhyAddr , sizeof ( data ) , data ) ;
set_register ( pegasus , PhyCtrl , ( indx | PHY_READ ) ) ;
2005-04-17 02:20:36 +04:00
for ( i = 0 ; i < REG_TIMEOUT ; i + + ) {
ret = get_registers ( pegasus , PhyCtrl , 1 , data ) ;
if ( data [ 0 ] & PHY_DONE )
break ;
}
if ( i < REG_TIMEOUT ) {
ret = get_registers ( pegasus , PhyData , 2 , & regdi ) ;
* regd = le16_to_cpu ( regdi ) ;
2005-11-15 10:48:23 +03:00
return ret ;
2005-04-17 02:20:36 +04:00
}
if ( netif_msg_drv ( pegasus ) )
dev_warn ( & pegasus - > intf - > dev , " fail %s \n " , __FUNCTION__ ) ;
2005-11-15 10:48:23 +03:00
return ret ;
2005-04-17 02:20:36 +04:00
}
static int mdio_read ( struct net_device * dev , int phy_id , int loc )
{
pegasus_t * pegasus = ( pegasus_t * ) netdev_priv ( dev ) ;
u16 res ;
read_mii_word ( pegasus , phy_id , loc , & res ) ;
return ( int ) res ;
}
static int write_mii_word ( pegasus_t * pegasus , __u8 phy , __u8 indx , __u16 regd )
{
int i ;
__u8 data [ 4 ] = { phy , 0 , 0 , indx } ;
int ret ;
data [ 1 ] = ( u8 ) regd ;
data [ 2 ] = ( u8 ) ( regd > > 8 ) ;
2005-11-15 10:48:23 +03:00
set_register ( pegasus , PhyCtrl , 0 ) ;
set_registers ( pegasus , PhyAddr , sizeof ( data ) , data ) ;
set_register ( pegasus , PhyCtrl , ( indx | PHY_WRITE ) ) ;
2005-04-17 02:20:36 +04:00
for ( i = 0 ; i < REG_TIMEOUT ; i + + ) {
ret = get_registers ( pegasus , PhyCtrl , 1 , data ) ;
if ( data [ 0 ] & PHY_DONE )
break ;
}
if ( i < REG_TIMEOUT )
2005-11-15 10:48:23 +03:00
return ret ;
2005-04-17 02:20:36 +04:00
if ( netif_msg_drv ( pegasus ) )
dev_warn ( & pegasus - > intf - > dev , " fail %s \n " , __FUNCTION__ ) ;
2005-11-15 10:48:23 +03:00
return - ETIMEDOUT ;
2005-04-17 02:20:36 +04:00
}
static void mdio_write ( struct net_device * dev , int phy_id , int loc , int val )
{
pegasus_t * pegasus = ( pegasus_t * ) netdev_priv ( dev ) ;
write_mii_word ( pegasus , phy_id , loc , val ) ;
}
static int read_eprom_word ( pegasus_t * pegasus , __u8 index , __u16 * retdata )
{
int i ;
__u8 tmp ;
__le16 retdatai ;
int ret ;
2005-11-15 10:48:23 +03:00
set_register ( pegasus , EpromCtrl , 0 ) ;
set_register ( pegasus , EpromOffset , index ) ;
set_register ( pegasus , EpromCtrl , EPROM_READ ) ;
2005-04-17 02:20:36 +04:00
for ( i = 0 ; i < REG_TIMEOUT ; i + + ) {
ret = get_registers ( pegasus , EpromCtrl , 1 , & tmp ) ;
if ( tmp & EPROM_DONE )
break ;
}
if ( i < REG_TIMEOUT ) {
ret = get_registers ( pegasus , EpromData , 2 , & retdatai ) ;
* retdata = le16_to_cpu ( retdatai ) ;
2005-11-15 10:48:23 +03:00
return ret ;
2005-04-17 02:20:36 +04:00
}
if ( netif_msg_drv ( pegasus ) )
dev_warn ( & pegasus - > intf - > dev , " fail %s \n " , __FUNCTION__ ) ;
2005-11-15 10:48:23 +03:00
return - ETIMEDOUT ;
2005-04-17 02:20:36 +04:00
}
# ifdef PEGASUS_WRITE_EEPROM
static inline void enable_eprom_write ( pegasus_t * pegasus )
{
__u8 tmp ;
int ret ;
2005-11-15 10:48:23 +03:00
get_registers ( pegasus , EthCtrl2 , 1 , & tmp ) ;
set_register ( pegasus , EthCtrl2 , tmp | EPROM_WR_ENABLE ) ;
2005-04-17 02:20:36 +04:00
}
static inline void disable_eprom_write ( pegasus_t * pegasus )
{
__u8 tmp ;
int ret ;
2005-11-15 10:48:23 +03:00
get_registers ( pegasus , EthCtrl2 , 1 , & tmp ) ;
set_register ( pegasus , EpromCtrl , 0 ) ;
set_register ( pegasus , EthCtrl2 , tmp & ~ EPROM_WR_ENABLE ) ;
2005-04-17 02:20:36 +04:00
}
static int write_eprom_word ( pegasus_t * pegasus , __u8 index , __u16 data )
{
int i ;
__u8 tmp , d [ 4 ] = { 0x3f , 0 , 0 , EPROM_WRITE } ;
int ret ;
2005-11-15 10:48:23 +03:00
set_registers ( pegasus , EpromOffset , 4 , d ) ;
2005-04-17 02:20:36 +04:00
enable_eprom_write ( pegasus ) ;
2005-11-15 10:48:23 +03:00
set_register ( pegasus , EpromOffset , index ) ;
set_registers ( pegasus , EpromData , 2 , & data ) ;
set_register ( pegasus , EpromCtrl , EPROM_WRITE ) ;
2005-04-17 02:20:36 +04:00
for ( i = 0 ; i < REG_TIMEOUT ; i + + ) {
ret = get_registers ( pegasus , EpromCtrl , 1 , & tmp ) ;
if ( tmp & EPROM_DONE )
break ;
}
disable_eprom_write ( pegasus ) ;
if ( i < REG_TIMEOUT )
2005-11-15 10:48:23 +03:00
return ret ;
2005-04-17 02:20:36 +04:00
if ( netif_msg_drv ( pegasus ) )
dev_warn ( & pegasus - > intf - > dev , " fail %s \n " , __FUNCTION__ ) ;
2005-11-15 10:48:23 +03:00
return - ETIMEDOUT ;
2005-04-17 02:20:36 +04:00
}
# endif /* PEGASUS_WRITE_EEPROM */
static inline void get_node_id ( pegasus_t * pegasus , __u8 * id )
{
int i ;
__u16 w16 ;
for ( i = 0 ; i < 3 ; i + + ) {
read_eprom_word ( pegasus , i , & w16 ) ;
( ( __le16 * ) id ) [ i ] = cpu_to_le16p ( & w16 ) ;
}
}
static void set_ethernet_addr ( pegasus_t * pegasus )
{
__u8 node_id [ 6 ] ;
get_node_id ( pegasus , node_id ) ;
2005-11-15 10:48:23 +03:00
set_registers ( pegasus , EthID , sizeof ( node_id ) , node_id ) ;
2005-04-17 02:20:36 +04:00
memcpy ( pegasus - > net - > dev_addr , node_id , sizeof ( node_id ) ) ;
}
static inline int reset_mac ( pegasus_t * pegasus )
{
__u8 data = 0x8 ;
int i ;
2005-11-15 10:48:23 +03:00
set_register ( pegasus , EthCtrl1 , data ) ;
2005-04-17 02:20:36 +04:00
for ( i = 0 ; i < REG_TIMEOUT ; i + + ) {
2005-11-15 10:48:23 +03:00
get_registers ( pegasus , EthCtrl1 , 1 , & data ) ;
2005-04-17 02:20:36 +04:00
if ( ~ data & 0x08 ) {
if ( loopback & 1 )
break ;
if ( mii_mode & & ( pegasus - > features & HAS_HOME_PNA ) )
2005-11-15 10:48:23 +03:00
set_register ( pegasus , Gpio1 , 0x34 ) ;
2005-04-17 02:20:36 +04:00
else
2005-11-15 10:48:23 +03:00
set_register ( pegasus , Gpio1 , 0x26 ) ;
set_register ( pegasus , Gpio0 , pegasus - > features ) ;
set_register ( pegasus , Gpio0 , DEFAULT_GPIO_SET ) ;
2005-04-17 02:20:36 +04:00
break ;
}
}
if ( i = = REG_TIMEOUT )
2005-11-15 10:48:23 +03:00
return - ETIMEDOUT ;
2005-04-17 02:20:36 +04:00
if ( usb_dev_id [ pegasus - > dev_index ] . vendor = = VENDOR_LINKSYS | |
usb_dev_id [ pegasus - > dev_index ] . vendor = = VENDOR_DLINK ) {
2005-11-15 10:48:23 +03:00
set_register ( pegasus , Gpio0 , 0x24 ) ;
set_register ( pegasus , Gpio0 , 0x26 ) ;
2005-04-17 02:20:36 +04:00
}
if ( usb_dev_id [ pegasus - > dev_index ] . vendor = = VENDOR_ELCON ) {
__u16 auxmode ;
read_mii_word ( pegasus , 3 , 0x1b , & auxmode ) ;
write_mii_word ( pegasus , 3 , 0x1b , auxmode | 4 ) ;
}
return 0 ;
}
static int enable_net_traffic ( struct net_device * dev , struct usb_device * usb )
{
__u16 linkpart ;
__u8 data [ 4 ] ;
pegasus_t * pegasus = netdev_priv ( dev ) ;
int ret ;
read_mii_word ( pegasus , pegasus - > phy , MII_LPA , & linkpart ) ;
data [ 0 ] = 0xc9 ;
data [ 1 ] = 0 ;
if ( linkpart & ( ADVERTISE_100FULL | ADVERTISE_10FULL ) )
data [ 1 ] | = 0x20 ; /* set full duplex */
if ( linkpart & ( ADVERTISE_100FULL | ADVERTISE_100HALF ) )
data [ 1 ] | = 0x10 ; /* set 100 Mbps */
if ( mii_mode )
data [ 1 ] = 0 ;
data [ 2 ] = ( loopback & 1 ) ? 0x09 : 0x01 ;
memcpy ( pegasus - > eth_regs , data , sizeof ( data ) ) ;
ret = set_registers ( pegasus , EthCtrl0 , 3 , data ) ;
if ( usb_dev_id [ pegasus - > dev_index ] . vendor = = VENDOR_LINKSYS | |
usb_dev_id [ pegasus - > dev_index ] . vendor = = VENDOR_DLINK ) {
u16 auxmode ;
read_mii_word ( pegasus , 0 , 0x1b , & auxmode ) ;
write_mii_word ( pegasus , 0 , 0x1b , auxmode | 4 ) ;
}
2005-11-15 10:48:23 +03:00
return ret ;
2005-04-17 02:20:36 +04:00
}
static void fill_skb_pool ( pegasus_t * pegasus )
{
int i ;
for ( i = 0 ; i < RX_SKBS ; i + + ) {
if ( pegasus - > rx_pool [ i ] )
continue ;
pegasus - > rx_pool [ i ] = dev_alloc_skb ( PEGASUS_MTU + 2 ) ;
/*
* * we give up if the allocation fail . the tasklet will be
* * rescheduled again anyway . . .
*/
if ( pegasus - > rx_pool [ i ] = = NULL )
return ;
pegasus - > rx_pool [ i ] - > dev = pegasus - > net ;
skb_reserve ( pegasus - > rx_pool [ i ] , 2 ) ;
}
}
static void free_skb_pool ( pegasus_t * pegasus )
{
int i ;
for ( i = 0 ; i < RX_SKBS ; i + + ) {
if ( pegasus - > rx_pool [ i ] ) {
dev_kfree_skb ( pegasus - > rx_pool [ i ] ) ;
pegasus - > rx_pool [ i ] = NULL ;
}
}
}
static inline struct sk_buff * pull_skb ( pegasus_t * pegasus )
{
int i ;
struct sk_buff * skb ;
for ( i = 0 ; i < RX_SKBS ; i + + ) {
if ( likely ( pegasus - > rx_pool [ i ] ! = NULL ) ) {
skb = pegasus - > rx_pool [ i ] ;
pegasus - > rx_pool [ i ] = NULL ;
return skb ;
}
}
return NULL ;
}
static void read_bulk_callback ( struct urb * urb , struct pt_regs * regs )
{
pegasus_t * pegasus = urb - > context ;
struct net_device * net ;
int rx_status , count = urb - > actual_length ;
u8 * buf = urb - > transfer_buffer ;
__u16 pkt_len ;
if ( ! pegasus )
return ;
net = pegasus - > net ;
if ( ! netif_device_present ( net ) | | ! netif_running ( net ) )
return ;
switch ( urb - > status ) {
case 0 :
break ;
case - ETIMEDOUT :
if ( netif_msg_rx_err ( pegasus ) )
pr_debug ( " %s: reset MAC \n " , net - > name ) ;
pegasus - > flags & = ~ PEGASUS_RX_BUSY ;
break ;
case - EPIPE : /* stall, or disconnect from TT */
/* FIXME schedule work to clear the halt */
if ( netif_msg_rx_err ( pegasus ) )
printk ( KERN_WARNING " %s: no rx stall recovery \n " ,
net - > name ) ;
return ;
case - ENOENT :
case - ECONNRESET :
case - ESHUTDOWN :
if ( netif_msg_ifdown ( pegasus ) )
pr_debug ( " %s: rx unlink, %d \n " , net - > name , urb - > status ) ;
return ;
default :
if ( netif_msg_rx_err ( pegasus ) )
pr_debug ( " %s: RX status %d \n " , net - > name , urb - > status ) ;
goto goon ;
}
if ( ! count | | count < 4 )
goto goon ;
rx_status = buf [ count - 2 ] ;
if ( rx_status & 0x1e ) {
if ( netif_msg_rx_err ( pegasus ) )
pr_debug ( " %s: RX packet error %x \n " ,
net - > name , rx_status ) ;
pegasus - > stats . rx_errors + + ;
if ( rx_status & 0x06 ) // long or runt
pegasus - > stats . rx_length_errors + + ;
if ( rx_status & 0x08 )
pegasus - > stats . rx_crc_errors + + ;
if ( rx_status & 0x10 ) // extra bits
pegasus - > stats . rx_frame_errors + + ;
goto goon ;
}
if ( pegasus - > chip = = 0x8513 ) {
pkt_len = le32_to_cpu ( * ( __le32 * ) urb - > transfer_buffer ) ;
pkt_len & = 0x0fff ;
pegasus - > rx_skb - > data + = 2 ;
} else {
pkt_len = buf [ count - 3 ] < < 8 ;
pkt_len + = buf [ count - 4 ] ;
pkt_len & = 0xfff ;
pkt_len - = 8 ;
}
2005-09-22 11:49:24 +04:00
/*
* If the packet is unreasonably long , quietly drop it rather than
* kernel panicing by calling skb_put .
*/
if ( pkt_len > PEGASUS_MTU )
goto goon ;
2005-04-17 02:20:36 +04:00
/*
* at this point we are sure pegasus - > rx_skb ! = NULL
* so we go ahead and pass up the packet .
*/
skb_put ( pegasus - > rx_skb , pkt_len ) ;
pegasus - > rx_skb - > protocol = eth_type_trans ( pegasus - > rx_skb , net ) ;
netif_rx ( pegasus - > rx_skb ) ;
pegasus - > stats . rx_packets + + ;
pegasus - > stats . rx_bytes + = pkt_len ;
if ( pegasus - > flags & PEGASUS_UNPLUG )
return ;
spin_lock ( & pegasus - > rx_pool_lock ) ;
pegasus - > rx_skb = pull_skb ( pegasus ) ;
spin_unlock ( & pegasus - > rx_pool_lock ) ;
if ( pegasus - > rx_skb = = NULL )
goto tl_sched ;
goon :
usb_fill_bulk_urb ( pegasus - > rx_urb , pegasus - > usb ,
usb_rcvbulkpipe ( pegasus - > usb , 1 ) ,
pegasus - > rx_skb - > data , PEGASUS_MTU + 8 ,
read_bulk_callback , pegasus ) ;
if ( usb_submit_urb ( pegasus - > rx_urb , GFP_ATOMIC ) ) {
pegasus - > flags | = PEGASUS_RX_URB_FAIL ;
goto tl_sched ;
} else {
pegasus - > flags & = ~ PEGASUS_RX_URB_FAIL ;
}
return ;
tl_sched :
tasklet_schedule ( & pegasus - > rx_tl ) ;
}
static void rx_fixup ( unsigned long data )
{
pegasus_t * pegasus ;
unsigned long flags ;
pegasus = ( pegasus_t * ) data ;
if ( pegasus - > flags & PEGASUS_UNPLUG )
return ;
spin_lock_irqsave ( & pegasus - > rx_pool_lock , flags ) ;
fill_skb_pool ( pegasus ) ;
if ( pegasus - > flags & PEGASUS_RX_URB_FAIL )
if ( pegasus - > rx_skb )
goto try_again ;
if ( pegasus - > rx_skb = = NULL ) {
pegasus - > rx_skb = pull_skb ( pegasus ) ;
}
if ( pegasus - > rx_skb = = NULL ) {
if ( netif_msg_rx_err ( pegasus ) )
printk ( KERN_WARNING " %s: low on memory \n " ,
pegasus - > net - > name ) ;
tasklet_schedule ( & pegasus - > rx_tl ) ;
goto done ;
}
usb_fill_bulk_urb ( pegasus - > rx_urb , pegasus - > usb ,
usb_rcvbulkpipe ( pegasus - > usb , 1 ) ,
pegasus - > rx_skb - > data , PEGASUS_MTU + 8 ,
read_bulk_callback , pegasus ) ;
try_again :
if ( usb_submit_urb ( pegasus - > rx_urb , GFP_ATOMIC ) ) {
pegasus - > flags | = PEGASUS_RX_URB_FAIL ;
tasklet_schedule ( & pegasus - > rx_tl ) ;
} else {
pegasus - > flags & = ~ PEGASUS_RX_URB_FAIL ;
}
done :
spin_unlock_irqrestore ( & pegasus - > rx_pool_lock , flags ) ;
}
static void write_bulk_callback ( struct urb * urb , struct pt_regs * regs )
{
pegasus_t * pegasus = urb - > context ;
struct net_device * net = pegasus - > net ;
if ( ! pegasus )
return ;
if ( ! netif_device_present ( net ) | | ! netif_running ( net ) )
return ;
switch ( urb - > status ) {
case - EPIPE :
/* FIXME schedule_work() to clear the tx halt */
netif_stop_queue ( net ) ;
if ( netif_msg_tx_err ( pegasus ) )
printk ( KERN_WARNING " %s: no tx stall recovery \n " ,
net - > name ) ;
return ;
case - ENOENT :
case - ECONNRESET :
case - ESHUTDOWN :
if ( netif_msg_ifdown ( pegasus ) )
pr_debug ( " %s: tx unlink, %d \n " , net - > name , urb - > status ) ;
return ;
default :
if ( netif_msg_tx_err ( pegasus ) )
pr_info ( " %s: TX status %d \n " , net - > name , urb - > status ) ;
/* FALL THROUGH */
case 0 :
break ;
}
net - > trans_start = jiffies ;
netif_wake_queue ( net ) ;
}
static void intr_callback ( struct urb * urb , struct pt_regs * regs )
{
pegasus_t * pegasus = urb - > context ;
struct net_device * net ;
int status ;
if ( ! pegasus )
return ;
net = pegasus - > net ;
switch ( urb - > status ) {
case 0 :
break ;
case - ECONNRESET : /* unlink */
case - ENOENT :
case - ESHUTDOWN :
return ;
default :
/* some Pegasus-I products report LOTS of data
* toggle errors . . . avoid log spamming
*/
if ( netif_msg_timer ( pegasus ) )
pr_debug ( " %s: intr status %d \n " , net - > name ,
urb - > status ) ;
}
if ( urb - > actual_length > = 6 ) {
u8 * d = urb - > transfer_buffer ;
/* byte 0 == tx_status1, reg 2B */
if ( d [ 0 ] & ( TX_UNDERRUN | EXCESSIVE_COL
| LATE_COL | JABBER_TIMEOUT ) ) {
pegasus - > stats . tx_errors + + ;
if ( d [ 0 ] & TX_UNDERRUN )
pegasus - > stats . tx_fifo_errors + + ;
if ( d [ 0 ] & ( EXCESSIVE_COL | JABBER_TIMEOUT ) )
pegasus - > stats . tx_aborted_errors + + ;
if ( d [ 0 ] & LATE_COL )
pegasus - > stats . tx_window_errors + + ;
}
/* d[5].LINK_STATUS lies on some adapters.
* d [ 0 ] . NO_CARRIER kicks in only with failed TX .
* . . . so monitoring with MII may be safest .
*/
if ( d [ 0 ] & NO_CARRIER )
netif_carrier_off ( net ) ;
else
netif_carrier_on ( net ) ;
/* bytes 3-4 == rx_lostpkt, reg 2E/2F */
pegasus - > stats . rx_missed_errors + = ( ( d [ 3 ] & 0x7f ) < < 8 ) | d [ 4 ] ;
}
status = usb_submit_urb ( urb , SLAB_ATOMIC ) ;
if ( status & & netif_msg_timer ( pegasus ) )
printk ( KERN_ERR " %s: can't resubmit interrupt urb, %d \n " ,
net - > name , status ) ;
}
static void pegasus_tx_timeout ( struct net_device * net )
{
pegasus_t * pegasus = netdev_priv ( net ) ;
if ( netif_msg_timer ( pegasus ) )
printk ( KERN_WARNING " %s: tx timeout \n " , net - > name ) ;
usb_unlink_urb ( pegasus - > tx_urb ) ;
pegasus - > stats . tx_errors + + ;
}
static int pegasus_start_xmit ( struct sk_buff * skb , struct net_device * net )
{
pegasus_t * pegasus = netdev_priv ( net ) ;
int count = ( ( skb - > len + 2 ) & 0x3f ) ? skb - > len + 2 : skb - > len + 3 ;
int res ;
__u16 l16 = skb - > len ;
netif_stop_queue ( net ) ;
( ( __le16 * ) pegasus - > tx_buff ) [ 0 ] = cpu_to_le16 ( l16 ) ;
memcpy ( pegasus - > tx_buff + 2 , skb - > data , skb - > len ) ;
usb_fill_bulk_urb ( pegasus - > tx_urb , pegasus - > usb ,
usb_sndbulkpipe ( pegasus - > usb , 2 ) ,
pegasus - > tx_buff , count ,
write_bulk_callback , pegasus ) ;
if ( ( res = usb_submit_urb ( pegasus - > tx_urb , GFP_ATOMIC ) ) ) {
if ( netif_msg_tx_err ( pegasus ) )
printk ( KERN_WARNING " %s: fail tx, %d \n " ,
net - > name , res ) ;
switch ( res ) {
case - EPIPE : /* stall, or disconnect from TT */
/* cleanup should already have been scheduled */
break ;
case - ENODEV : /* disconnect() upcoming */
break ;
default :
pegasus - > stats . tx_errors + + ;
netif_start_queue ( net ) ;
}
} else {
pegasus - > stats . tx_packets + + ;
pegasus - > stats . tx_bytes + = skb - > len ;
net - > trans_start = jiffies ;
}
dev_kfree_skb ( skb ) ;
return 0 ;
}
static struct net_device_stats * pegasus_netdev_stats ( struct net_device * dev )
{
return & ( ( pegasus_t * ) netdev_priv ( dev ) ) - > stats ;
}
static inline void disable_net_traffic ( pegasus_t * pegasus )
{
int tmp = 0 ;
2005-11-15 10:48:23 +03:00
set_registers ( pegasus , EthCtrl0 , 2 , & tmp ) ;
2005-04-17 02:20:36 +04:00
}
static inline void get_interrupt_interval ( pegasus_t * pegasus )
{
__u8 data [ 2 ] ;
read_eprom_word ( pegasus , 4 , ( __u16 * ) data ) ;
2005-09-22 11:49:24 +04:00
if ( pegasus - > usb - > speed ! = USB_SPEED_HIGH ) {
if ( data [ 1 ] < 0x80 ) {
if ( netif_msg_timer ( pegasus ) )
dev_info ( & pegasus - > intf - > dev , " intr interval "
" changed from %ums to %ums \n " ,
data [ 1 ] , 0x80 ) ;
data [ 1 ] = 0x80 ;
# ifdef PEGASUS_WRITE_EEPROM
write_eprom_word ( pegasus , 4 , * ( __u16 * ) data ) ;
2005-04-17 02:20:36 +04:00
# endif
2005-09-22 11:49:24 +04:00
}
2005-04-17 02:20:36 +04:00
}
pegasus - > intr_interval = data [ 1 ] ;
}
static void set_carrier ( struct net_device * net )
{
pegasus_t * pegasus = netdev_priv ( net ) ;
u16 tmp ;
2005-09-22 11:49:24 +04:00
if ( ! read_mii_word ( pegasus , pegasus - > phy , MII_BMSR , & tmp ) )
2005-04-17 02:20:36 +04:00
return ;
2005-09-22 11:49:24 +04:00
2005-04-17 02:20:36 +04:00
if ( tmp & BMSR_LSTATUS )
netif_carrier_on ( net ) ;
else
netif_carrier_off ( net ) ;
}
static void free_all_urbs ( pegasus_t * pegasus )
{
usb_free_urb ( pegasus - > intr_urb ) ;
usb_free_urb ( pegasus - > tx_urb ) ;
usb_free_urb ( pegasus - > rx_urb ) ;
usb_free_urb ( pegasus - > ctrl_urb ) ;
}
static void unlink_all_urbs ( pegasus_t * pegasus )
{
usb_kill_urb ( pegasus - > intr_urb ) ;
usb_kill_urb ( pegasus - > tx_urb ) ;
usb_kill_urb ( pegasus - > rx_urb ) ;
usb_kill_urb ( pegasus - > ctrl_urb ) ;
}
static int alloc_urbs ( pegasus_t * pegasus )
{
pegasus - > ctrl_urb = usb_alloc_urb ( 0 , GFP_KERNEL ) ;
if ( ! pegasus - > ctrl_urb ) {
return 0 ;
}
pegasus - > rx_urb = usb_alloc_urb ( 0 , GFP_KERNEL ) ;
if ( ! pegasus - > rx_urb ) {
usb_free_urb ( pegasus - > ctrl_urb ) ;
return 0 ;
}
pegasus - > tx_urb = usb_alloc_urb ( 0 , GFP_KERNEL ) ;
if ( ! pegasus - > tx_urb ) {
usb_free_urb ( pegasus - > rx_urb ) ;
usb_free_urb ( pegasus - > ctrl_urb ) ;
return 0 ;
}
pegasus - > intr_urb = usb_alloc_urb ( 0 , GFP_KERNEL ) ;
if ( ! pegasus - > intr_urb ) {
usb_free_urb ( pegasus - > tx_urb ) ;
usb_free_urb ( pegasus - > rx_urb ) ;
usb_free_urb ( pegasus - > ctrl_urb ) ;
return 0 ;
}
return 1 ;
}
static int pegasus_open ( struct net_device * net )
{
pegasus_t * pegasus = netdev_priv ( net ) ;
int res ;
if ( pegasus - > rx_skb = = NULL )
pegasus - > rx_skb = pull_skb ( pegasus ) ;
/*
* * Note : no point to free the pool . it is empty : - )
*/
if ( ! pegasus - > rx_skb )
return - ENOMEM ;
res = set_registers ( pegasus , EthID , 6 , net - > dev_addr ) ;
usb_fill_bulk_urb ( pegasus - > rx_urb , pegasus - > usb ,
usb_rcvbulkpipe ( pegasus - > usb , 1 ) ,
pegasus - > rx_skb - > data , PEGASUS_MTU + 8 ,
read_bulk_callback , pegasus ) ;
if ( ( res = usb_submit_urb ( pegasus - > rx_urb , GFP_KERNEL ) ) ) {
if ( netif_msg_ifup ( pegasus ) )
pr_debug ( " %s: failed rx_urb, %d " , net - > name , res ) ;
goto exit ;
}
usb_fill_int_urb ( pegasus - > intr_urb , pegasus - > usb ,
usb_rcvintpipe ( pegasus - > usb , 3 ) ,
pegasus - > intr_buff , sizeof ( pegasus - > intr_buff ) ,
intr_callback , pegasus , pegasus - > intr_interval ) ;
if ( ( res = usb_submit_urb ( pegasus - > intr_urb , GFP_KERNEL ) ) ) {
if ( netif_msg_ifup ( pegasus ) )
pr_debug ( " %s: failed intr_urb, %d \n " , net - > name , res ) ;
usb_kill_urb ( pegasus - > rx_urb ) ;
goto exit ;
}
if ( ( res = enable_net_traffic ( net , pegasus - > usb ) ) ) {
if ( netif_msg_ifup ( pegasus ) )
pr_debug ( " %s: can't enable_net_traffic() - %d \n " ,
net - > name , res ) ;
res = - EIO ;
usb_kill_urb ( pegasus - > rx_urb ) ;
usb_kill_urb ( pegasus - > intr_urb ) ;
free_skb_pool ( pegasus ) ;
goto exit ;
}
set_carrier ( net ) ;
netif_start_queue ( net ) ;
if ( netif_msg_ifup ( pegasus ) )
pr_debug ( " %s: open \n " , net - > name ) ;
res = 0 ;
exit :
return res ;
}
static int pegasus_close ( struct net_device * net )
{
pegasus_t * pegasus = netdev_priv ( net ) ;
netif_stop_queue ( net ) ;
if ( ! ( pegasus - > flags & PEGASUS_UNPLUG ) )
disable_net_traffic ( pegasus ) ;
tasklet_kill ( & pegasus - > rx_tl ) ;
unlink_all_urbs ( pegasus ) ;
return 0 ;
}
static void pegasus_get_drvinfo ( struct net_device * dev ,
struct ethtool_drvinfo * info )
{
pegasus_t * pegasus = netdev_priv ( dev ) ;
strncpy ( info - > driver , driver_name , sizeof ( info - > driver ) - 1 ) ;
strncpy ( info - > version , DRIVER_VERSION , sizeof ( info - > version ) - 1 ) ;
usb_make_path ( pegasus - > usb , info - > bus_info , sizeof ( info - > bus_info ) ) ;
}
/* also handles three patterns of some kind in hardware */
# define WOL_SUPPORTED (WAKE_MAGIC|WAKE_PHY)
static void
pegasus_get_wol ( struct net_device * dev , struct ethtool_wolinfo * wol )
{
pegasus_t * pegasus = netdev_priv ( dev ) ;
wol - > supported = WAKE_MAGIC | WAKE_PHY ;
wol - > wolopts = pegasus - > wolopts ;
}
static int
pegasus_set_wol ( struct net_device * dev , struct ethtool_wolinfo * wol )
{
pegasus_t * pegasus = netdev_priv ( dev ) ;
u8 reg78 = 0x04 ;
if ( wol - > wolopts & ~ WOL_SUPPORTED )
return - EINVAL ;
if ( wol - > wolopts & WAKE_MAGIC )
reg78 | = 0x80 ;
if ( wol - > wolopts & WAKE_PHY )
reg78 | = 0x40 ;
/* FIXME this 0x10 bit still needs to get set in the chip... */
if ( wol - > wolopts )
pegasus - > eth_regs [ 0 ] | = 0x10 ;
else
pegasus - > eth_regs [ 0 ] & = ~ 0x10 ;
pegasus - > wolopts = wol - > wolopts ;
return set_register ( pegasus , WakeupControl , reg78 ) ;
}
static inline void pegasus_reset_wol ( struct net_device * dev )
{
struct ethtool_wolinfo wol ;
memset ( & wol , 0 , sizeof wol ) ;
( void ) pegasus_set_wol ( dev , & wol ) ;
}
static int
pegasus_get_settings ( struct net_device * dev , struct ethtool_cmd * ecmd )
{
pegasus_t * pegasus ;
if ( in_atomic ( ) )
return 0 ;
pegasus = netdev_priv ( dev ) ;
mii_ethtool_gset ( & pegasus - > mii , ecmd ) ;
return 0 ;
}
static int
pegasus_set_settings ( struct net_device * dev , struct ethtool_cmd * ecmd )
{
pegasus_t * pegasus = netdev_priv ( dev ) ;
return mii_ethtool_sset ( & pegasus - > mii , ecmd ) ;
}
static int pegasus_nway_reset ( struct net_device * dev )
{
pegasus_t * pegasus = netdev_priv ( dev ) ;
return mii_nway_restart ( & pegasus - > mii ) ;
}
static u32 pegasus_get_link ( struct net_device * dev )
{
pegasus_t * pegasus = netdev_priv ( dev ) ;
return mii_link_ok ( & pegasus - > mii ) ;
}
static u32 pegasus_get_msglevel ( struct net_device * dev )
{
pegasus_t * pegasus = netdev_priv ( dev ) ;
return pegasus - > msg_enable ;
}
static void pegasus_set_msglevel ( struct net_device * dev , u32 v )
{
pegasus_t * pegasus = netdev_priv ( dev ) ;
pegasus - > msg_enable = v ;
}
static struct ethtool_ops ops = {
. get_drvinfo = pegasus_get_drvinfo ,
. get_settings = pegasus_get_settings ,
. set_settings = pegasus_set_settings ,
. nway_reset = pegasus_nway_reset ,
. get_link = pegasus_get_link ,
. get_msglevel = pegasus_get_msglevel ,
. set_msglevel = pegasus_set_msglevel ,
. get_wol = pegasus_get_wol ,
. set_wol = pegasus_set_wol ,
} ;
static int pegasus_ioctl ( struct net_device * net , struct ifreq * rq , int cmd )
{
__u16 * data = ( __u16 * ) & rq - > ifr_ifru ;
pegasus_t * pegasus = netdev_priv ( net ) ;
int res ;
switch ( cmd ) {
case SIOCDEVPRIVATE :
data [ 0 ] = pegasus - > phy ;
case SIOCDEVPRIVATE + 1 :
read_mii_word ( pegasus , data [ 0 ] , data [ 1 ] & 0x1f , & data [ 3 ] ) ;
res = 0 ;
break ;
case SIOCDEVPRIVATE + 2 :
if ( ! capable ( CAP_NET_ADMIN ) )
return - EPERM ;
write_mii_word ( pegasus , pegasus - > phy , data [ 1 ] & 0x1f , data [ 2 ] ) ;
res = 0 ;
break ;
default :
res = - EOPNOTSUPP ;
}
return res ;
}
static void pegasus_set_multicast ( struct net_device * net )
{
pegasus_t * pegasus = netdev_priv ( net ) ;
if ( net - > flags & IFF_PROMISC ) {
pegasus - > eth_regs [ EthCtrl2 ] | = RX_PROMISCUOUS ;
if ( netif_msg_link ( pegasus ) )
pr_info ( " %s: Promiscuous mode enabled. \n " , net - > name ) ;
2005-05-25 11:07:04 +04:00
} else if ( net - > mc_count | |
2005-04-17 02:20:36 +04:00
( net - > flags & IFF_ALLMULTI ) ) {
pegasus - > eth_regs [ EthCtrl0 ] | = RX_MULTICAST ;
pegasus - > eth_regs [ EthCtrl2 ] & = ~ RX_PROMISCUOUS ;
if ( netif_msg_link ( pegasus ) )
pr_info ( " %s: set allmulti \n " , net - > name ) ;
} else {
pegasus - > eth_regs [ EthCtrl0 ] & = ~ RX_MULTICAST ;
pegasus - > eth_regs [ EthCtrl2 ] & = ~ RX_PROMISCUOUS ;
}
pegasus - > flags | = ETH_REGS_CHANGE ;
ctrl_callback ( pegasus - > ctrl_urb , NULL ) ;
}
static __u8 mii_phy_probe ( pegasus_t * pegasus )
{
int i ;
__u16 tmp ;
for ( i = 0 ; i < 32 ; i + + ) {
read_mii_word ( pegasus , i , MII_BMSR , & tmp ) ;
if ( tmp = = 0 | | tmp = = 0xffff | | ( tmp & BMSR_MEDIA ) = = 0 )
continue ;
else
return i ;
}
return 0xff ;
}
static inline void setup_pegasus_II ( pegasus_t * pegasus )
{
__u8 data = 0xa5 ;
2005-11-15 10:48:23 +03:00
set_register ( pegasus , Reg1d , 0 ) ;
set_register ( pegasus , Reg7b , 1 ) ;
2005-04-17 02:20:36 +04:00
mdelay ( 100 ) ;
if ( ( pegasus - > features & HAS_HOME_PNA ) & & mii_mode )
2005-11-15 10:48:23 +03:00
set_register ( pegasus , Reg7b , 0 ) ;
2005-04-17 02:20:36 +04:00
else
2005-11-15 10:48:23 +03:00
set_register ( pegasus , Reg7b , 2 ) ;
2005-04-17 02:20:36 +04:00
2005-11-15 10:48:23 +03:00
set_register ( pegasus , 0x83 , data ) ;
get_registers ( pegasus , 0x83 , 1 , & data ) ;
2005-04-17 02:20:36 +04:00
if ( data = = 0xa5 ) {
pegasus - > chip = 0x8513 ;
} else {
pegasus - > chip = 0 ;
}
2005-11-15 10:48:23 +03:00
set_register ( pegasus , 0x80 , 0xc0 ) ;
set_register ( pegasus , 0x83 , 0xff ) ;
set_register ( pegasus , 0x84 , 0x01 ) ;
2005-04-17 02:20:36 +04:00
if ( pegasus - > features & HAS_HOME_PNA & & mii_mode )
2005-11-15 10:48:23 +03:00
set_register ( pegasus , Reg81 , 6 ) ;
2005-04-17 02:20:36 +04:00
else
2005-11-15 10:48:23 +03:00
set_register ( pegasus , Reg81 , 2 ) ;
2005-04-17 02:20:36 +04:00
}
static struct workqueue_struct * pegasus_workqueue = NULL ;
# define CARRIER_CHECK_DELAY (2 * HZ)
static void check_carrier ( void * data )
{
pegasus_t * pegasus = data ;
set_carrier ( pegasus - > net ) ;
if ( ! ( pegasus - > flags & PEGASUS_UNPLUG ) ) {
queue_delayed_work ( pegasus_workqueue , & pegasus - > carrier_check ,
CARRIER_CHECK_DELAY ) ;
}
}
static int pegasus_probe ( struct usb_interface * intf ,
const struct usb_device_id * id )
{
struct usb_device * dev = interface_to_usbdev ( intf ) ;
struct net_device * net ;
pegasus_t * pegasus ;
int dev_index = id - pegasus_ids ;
int res = - ENOMEM ;
usb_get_dev ( dev ) ;
net = alloc_etherdev ( sizeof ( struct pegasus ) ) ;
if ( ! net ) {
dev_err ( & intf - > dev , " can't allocate %s \n " , " device " ) ;
goto out ;
}
pegasus = netdev_priv ( net ) ;
memset ( pegasus , 0 , sizeof ( struct pegasus ) ) ;
pegasus - > dev_index = dev_index ;
init_waitqueue_head ( & pegasus - > ctrl_wait ) ;
if ( ! alloc_urbs ( pegasus ) ) {
dev_err ( & intf - > dev , " can't allocate %s \n " , " urbs " ) ;
goto out1 ;
}
tasklet_init ( & pegasus - > rx_tl , rx_fixup , ( unsigned long ) pegasus ) ;
INIT_WORK ( & pegasus - > carrier_check , check_carrier , pegasus ) ;
pegasus - > intf = intf ;
pegasus - > usb = dev ;
pegasus - > net = net ;
SET_MODULE_OWNER ( net ) ;
net - > open = pegasus_open ;
net - > stop = pegasus_close ;
net - > watchdog_timeo = PEGASUS_TX_TIMEOUT ;
net - > tx_timeout = pegasus_tx_timeout ;
net - > do_ioctl = pegasus_ioctl ;
net - > hard_start_xmit = pegasus_start_xmit ;
net - > set_multicast_list = pegasus_set_multicast ;
net - > get_stats = pegasus_netdev_stats ;
SET_ETHTOOL_OPS ( net , & ops ) ;
pegasus - > mii . dev = net ;
pegasus - > mii . mdio_read = mdio_read ;
pegasus - > mii . mdio_write = mdio_write ;
pegasus - > mii . phy_id_mask = 0x1f ;
pegasus - > mii . reg_num_mask = 0x1f ;
spin_lock_init ( & pegasus - > rx_pool_lock ) ;
pegasus - > msg_enable = netif_msg_init ( msg_level , NETIF_MSG_DRV
| NETIF_MSG_PROBE | NETIF_MSG_LINK ) ;
pegasus - > features = usb_dev_id [ dev_index ] . private ;
get_interrupt_interval ( pegasus ) ;
if ( reset_mac ( pegasus ) ) {
dev_err ( & intf - > dev , " can't reset MAC \n " ) ;
res = - EIO ;
goto out2 ;
}
set_ethernet_addr ( pegasus ) ;
fill_skb_pool ( pegasus ) ;
if ( pegasus - > features & PEGASUS_II ) {
dev_info ( & intf - > dev , " setup Pegasus II specific registers \n " ) ;
setup_pegasus_II ( pegasus ) ;
}
pegasus - > phy = mii_phy_probe ( pegasus ) ;
if ( pegasus - > phy = = 0xff ) {
dev_warn ( & intf - > dev , " can't locate MII phy, using default \n " ) ;
pegasus - > phy = 1 ;
}
pegasus - > mii . phy_id = pegasus - > phy ;
usb_set_intfdata ( intf , pegasus ) ;
SET_NETDEV_DEV ( net , & intf - > dev ) ;
pegasus_reset_wol ( net ) ;
res = register_netdev ( net ) ;
if ( res )
goto out3 ;
queue_delayed_work ( pegasus_workqueue , & pegasus - > carrier_check ,
CARRIER_CHECK_DELAY ) ;
dev_info ( & intf - > dev , " %s, %s, %02x:%02x:%02x:%02x:%02x:%02x \n " ,
net - > name ,
usb_dev_id [ dev_index ] . name ,
net - > dev_addr [ 0 ] , net - > dev_addr [ 1 ] ,
net - > dev_addr [ 2 ] , net - > dev_addr [ 3 ] ,
net - > dev_addr [ 4 ] , net - > dev_addr [ 5 ] ) ;
return 0 ;
out3 :
usb_set_intfdata ( intf , NULL ) ;
free_skb_pool ( pegasus ) ;
out2 :
free_all_urbs ( pegasus ) ;
out1 :
free_netdev ( net ) ;
out :
usb_put_dev ( dev ) ;
return res ;
}
static void pegasus_disconnect ( struct usb_interface * intf )
{
struct pegasus * pegasus = usb_get_intfdata ( intf ) ;
usb_set_intfdata ( intf , NULL ) ;
if ( ! pegasus ) {
dev_dbg ( & intf - > dev , " unregistering non-bound device? \n " ) ;
return ;
}
pegasus - > flags | = PEGASUS_UNPLUG ;
cancel_delayed_work ( & pegasus - > carrier_check ) ;
unregister_netdev ( pegasus - > net ) ;
usb_put_dev ( interface_to_usbdev ( intf ) ) ;
2005-09-22 11:49:24 +04:00
unlink_all_urbs ( pegasus ) ;
2005-04-17 02:20:36 +04:00
free_all_urbs ( pegasus ) ;
free_skb_pool ( pegasus ) ;
if ( pegasus - > rx_skb )
dev_kfree_skb ( pegasus - > rx_skb ) ;
free_netdev ( pegasus - > net ) ;
}
2005-04-19 04:39:22 +04:00
static int pegasus_suspend ( struct usb_interface * intf , pm_message_t message )
2005-04-17 02:20:36 +04:00
{
struct pegasus * pegasus = usb_get_intfdata ( intf ) ;
netif_device_detach ( pegasus - > net ) ;
2005-04-19 04:39:22 +04:00
if ( netif_running ( pegasus - > net ) ) {
cancel_delayed_work ( & pegasus - > carrier_check ) ;
usb_kill_urb ( pegasus - > rx_urb ) ;
usb_kill_urb ( pegasus - > intr_urb ) ;
}
2005-04-17 02:20:36 +04:00
return 0 ;
}
static int pegasus_resume ( struct usb_interface * intf )
{
struct pegasus * pegasus = usb_get_intfdata ( intf ) ;
netif_device_attach ( pegasus - > net ) ;
2005-04-19 04:39:22 +04:00
if ( netif_running ( pegasus - > net ) ) {
pegasus - > rx_urb - > status = 0 ;
pegasus - > rx_urb - > actual_length = 0 ;
2005-04-26 18:43:41 +04:00
read_bulk_callback ( pegasus - > rx_urb , NULL ) ;
2005-04-19 04:39:22 +04:00
pegasus - > intr_urb - > status = 0 ;
pegasus - > intr_urb - > actual_length = 0 ;
2005-04-26 18:43:41 +04:00
intr_callback ( pegasus - > intr_urb , NULL ) ;
2005-04-19 04:39:22 +04:00
queue_delayed_work ( pegasus_workqueue , & pegasus - > carrier_check ,
CARRIER_CHECK_DELAY ) ;
}
2005-04-17 02:20:36 +04:00
return 0 ;
}
static struct usb_driver pegasus_driver = {
. name = driver_name ,
. probe = pegasus_probe ,
. disconnect = pegasus_disconnect ,
. id_table = pegasus_ids ,
. suspend = pegasus_suspend ,
. resume = pegasus_resume ,
} ;
2005-11-15 10:55:18 +03:00
static void parse_id ( char * id )
{
unsigned int vendor_id = 0 , device_id = 0 , flags = 0 , i = 0 ;
char * token , * name = NULL ;
if ( ( token = strsep ( & id , " : " ) ) ! = NULL )
name = token ;
/* name now points to a null terminated string*/
if ( ( token = strsep ( & id , " : " ) ) ! = NULL )
vendor_id = simple_strtoul ( token , NULL , 16 ) ;
if ( ( token = strsep ( & id , " : " ) ) ! = NULL )
device_id = simple_strtoul ( token , NULL , 16 ) ;
flags = simple_strtoul ( id , NULL , 16 ) ;
pr_info ( " %s: new device %s, vendor ID 0x%04x, device ID 0x%04x, flags: 0x%x \n " ,
driver_name , name , vendor_id , device_id , flags ) ;
if ( vendor_id > 0x10000 | | vendor_id = = 0 )
return ;
if ( device_id > 0x10000 | | device_id = = 0 )
return ;
for ( i = 0 ; usb_dev_id [ i ] . name ; i + + ) ;
usb_dev_id [ i ] . name = name ;
usb_dev_id [ i ] . vendor = vendor_id ;
usb_dev_id [ i ] . device = device_id ;
usb_dev_id [ i ] . private = flags ;
pegasus_ids [ i ] . match_flags = USB_DEVICE_ID_MATCH_DEVICE ;
pegasus_ids [ i ] . idVendor = vendor_id ;
pegasus_ids [ i ] . idProduct = device_id ;
}
2005-04-17 02:20:36 +04:00
static int __init pegasus_init ( void )
{
pr_info ( " %s: %s, " DRIVER_DESC " \n " , driver_name , DRIVER_VERSION ) ;
2005-11-15 10:55:18 +03:00
if ( devid )
parse_id ( devid ) ;
2005-04-17 02:20:36 +04:00
pegasus_workqueue = create_singlethread_workqueue ( " pegasus " ) ;
if ( ! pegasus_workqueue )
return - ENOMEM ;
return usb_register ( & pegasus_driver ) ;
}
static void __exit pegasus_exit ( void )
{
destroy_workqueue ( pegasus_workqueue ) ;
usb_deregister ( & pegasus_driver ) ;
}
module_init ( pegasus_init ) ;
module_exit ( pegasus_exit ) ;