2005-04-17 02:20:36 +04:00
/*
* Amiga Linux / 68 k 8390 based PCMCIA Ethernet Driver for the Amiga 1200
*
* ( C ) Copyright 1997 Alain Malek
* ( Alain . Malek @ cryogen . com )
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* This program is based on
*
* ne . c : A general non - shared - memory NS8390 ethernet driver for linux
* Written 1992 - 94 by Donald Becker .
*
* 8390. c : A general NS8390 ethernet driver core for linux .
* Written 1992 - 94 by Donald Becker .
*
* cnetdevice : A Sana - II ethernet driver for AmigaOS
* Written by Bruce Abbott ( bhabbott @ inhb . co . nz )
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file COPYING in the main directory of the Linux
* distribution for more details .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/errno.h>
# include <linux/pci.h>
# include <linux/init.h>
# include <linux/delay.h>
# include <linux/netdevice.h>
# include <linux/etherdevice.h>
2006-01-10 05:37:15 +03:00
# include <linux/jiffies.h>
2005-04-17 02:20:36 +04:00
# include <asm/system.h>
# include <asm/io.h>
# include <asm/setup.h>
# include <asm/amigaints.h>
# include <asm/amigahw.h>
# include <asm/amigayle.h>
# include <asm/amipcmcia.h>
# include "8390.h"
/* ---- No user-serviceable parts below ---- */
# define DRV_NAME "apne"
# define NE_BASE (dev->base_addr)
# define NE_CMD 0x00
# define NE_DATAPORT 0x10 /* NatSemi-defined port window offset. */
# define NE_RESET 0x1f /* Issue a read to reset, a write to clear. */
# define NE_IO_EXTENT 0x20
# define NE_EN0_ISR 0x07
# define NE_EN0_DCFG 0x0e
# define NE_EN0_RSARLO 0x08
# define NE_EN0_RSARHI 0x09
# define NE_EN0_RCNTLO 0x0a
# define NE_EN0_RXCR 0x0c
# define NE_EN0_TXCR 0x0d
# define NE_EN0_RCNTHI 0x0b
# define NE_EN0_IMR 0x0f
# define NE1SM_START_PG 0x20 /* First page of TX buffer */
# define NE1SM_STOP_PG 0x40 /* Last page +1 of RX ring */
# define NESM_START_PG 0x40 /* First page of TX buffer */
# define NESM_STOP_PG 0x80 /* Last page +1 of RX ring */
struct net_device * __init apne_probe ( int unit ) ;
static int apne_probe1 ( struct net_device * dev , int ioaddr ) ;
static int apne_open ( struct net_device * dev ) ;
static int apne_close ( struct net_device * dev ) ;
static void apne_reset_8390 ( struct net_device * dev ) ;
static void apne_get_8390_hdr ( struct net_device * dev , struct e8390_pkt_hdr * hdr ,
int ring_page ) ;
static void apne_block_input ( struct net_device * dev , int count ,
struct sk_buff * skb , int ring_offset ) ;
static void apne_block_output ( struct net_device * dev , const int count ,
const unsigned char * buf , const int start_page ) ;
static irqreturn_t apne_interrupt ( int irq , void * dev_id , struct pt_regs * regs ) ;
static int init_pcmcia ( void ) ;
/* IO base address used for nic */
# define IOBASE 0x300
/*
use MANUAL_CONFIG and MANUAL_OFFSET for enabling IO by hand
you can find the values to use by looking at the cnet . device
config file example ( the default values are for the CNET40BC card )
*/
/*
# define MANUAL_CONFIG 0x20
# define MANUAL_OFFSET 0x3f8
# define MANUAL_HWADDR0 0x00
# define MANUAL_HWADDR1 0x12
# define MANUAL_HWADDR2 0x34
# define MANUAL_HWADDR3 0x56
# define MANUAL_HWADDR4 0x78
# define MANUAL_HWADDR5 0x9a
*/
static const char version [ ] =
" apne.c:v1.1 7/10/98 Alain Malek (Alain.Malek@cryogen.ch) \n " ;
static int apne_owned ; /* signal if card already owned */
struct net_device * __init apne_probe ( int unit )
{
struct net_device * dev ;
# ifndef MANUAL_CONFIG
char tuple [ 8 ] ;
# endif
int err ;
if ( apne_owned )
return ERR_PTR ( - ENODEV ) ;
if ( ! ( AMIGAHW_PRESENT ( PCMCIA ) ) )
return ERR_PTR ( - ENODEV ) ;
printk ( " Looking for PCMCIA ethernet card : " ) ;
/* check if a card is inserted */
if ( ! ( PCMCIA_INSERTED ) ) {
printk ( " NO PCMCIA card inserted \n " ) ;
return ERR_PTR ( - ENODEV ) ;
}
dev = alloc_ei_netdev ( ) ;
if ( ! dev )
return ERR_PTR ( - ENOMEM ) ;
if ( unit > = 0 ) {
sprintf ( dev - > name , " eth%d " , unit ) ;
netdev_boot_setup_check ( dev ) ;
}
SET_MODULE_OWNER ( dev ) ;
/* disable pcmcia irq for readtuple */
pcmcia_disable_irq ( ) ;
# ifndef MANUAL_CONFIG
if ( ( pcmcia_copy_tuple ( CISTPL_FUNCID , tuple , 8 ) < 3 ) | |
( tuple [ 2 ] ! = CISTPL_FUNCID_NETWORK ) ) {
printk ( " not an ethernet card \n " ) ;
/* XXX: shouldn't we re-enable irq here? */
free_netdev ( dev ) ;
return ERR_PTR ( - ENODEV ) ;
}
# endif
printk ( " ethernet PCMCIA card inserted \n " ) ;
if ( ! init_pcmcia ( ) ) {
/* XXX: shouldn't we re-enable irq here? */
free_netdev ( dev ) ;
return ERR_PTR ( - ENODEV ) ;
}
if ( ! request_region ( IOBASE , 0x20 , DRV_NAME ) ) {
free_netdev ( dev ) ;
return ERR_PTR ( - EBUSY ) ;
}
err = apne_probe1 ( dev , IOBASE ) ;
if ( err ) {
release_region ( IOBASE , 0x20 ) ;
free_netdev ( dev ) ;
return ERR_PTR ( err ) ;
}
err = register_netdev ( dev ) ;
if ( ! err )
return dev ;
pcmcia_disable_irq ( ) ;
free_irq ( IRQ_AMIGA_PORTS , dev ) ;
pcmcia_reset ( ) ;
release_region ( IOBASE , 0x20 ) ;
free_netdev ( dev ) ;
return ERR_PTR ( err ) ;
}
static int __init apne_probe1 ( struct net_device * dev , int ioaddr )
{
int i ;
unsigned char SA_prom [ 32 ] ;
int wordlength = 2 ;
const char * name = NULL ;
int start_page , stop_page ;
# ifndef MANUAL_HWADDR0
int neX000 , ctron ;
# endif
static unsigned version_printed ;
if ( ei_debug & & version_printed + + = = 0 )
printk ( version ) ;
printk ( " PCMCIA NE*000 ethercard probe " ) ;
/* Reset card. Who knows what dain-bramaged state it was left in. */
{ unsigned long reset_start_time = jiffies ;
outb ( inb ( ioaddr + NE_RESET ) , ioaddr + NE_RESET ) ;
while ( ( inb ( ioaddr + NE_EN0_ISR ) & ENISR_RESET ) = = 0 )
2006-01-10 05:37:15 +03:00
if ( time_after ( jiffies , reset_start_time + 2 * HZ / 100 ) ) {
2005-04-17 02:20:36 +04:00
printk ( " not found (no reset ack). \n " ) ;
return - ENODEV ;
}
outb ( 0xff , ioaddr + NE_EN0_ISR ) ; /* Ack all intr. */
}
# ifndef MANUAL_HWADDR0
/* Read the 16 bytes of station address PROM.
We must first initialize registers , similar to NS8390_init ( eifdev , 0 ) .
We can ' t reliably read the SAPROM address without this .
( I learned the hard way ! ) . */
{
struct { unsigned long value , offset ; } program_seq [ ] = {
{ E8390_NODMA + E8390_PAGE0 + E8390_STOP , NE_CMD } , /* Select page 0*/
{ 0x48 , NE_EN0_DCFG } , /* Set byte-wide (0x48) access. */
{ 0x00 , NE_EN0_RCNTLO } , /* Clear the count regs. */
{ 0x00 , NE_EN0_RCNTHI } ,
{ 0x00 , NE_EN0_IMR } , /* Mask completion irq. */
{ 0xFF , NE_EN0_ISR } ,
{ E8390_RXOFF , NE_EN0_RXCR } , /* 0x20 Set to monitor */
{ E8390_TXOFF , NE_EN0_TXCR } , /* 0x02 and loopback mode. */
{ 32 , NE_EN0_RCNTLO } ,
{ 0x00 , NE_EN0_RCNTHI } ,
{ 0x00 , NE_EN0_RSARLO } , /* DMA starting at 0x0000. */
{ 0x00 , NE_EN0_RSARHI } ,
{ E8390_RREAD + E8390_START , NE_CMD } ,
} ;
for ( i = 0 ; i < sizeof ( program_seq ) / sizeof ( program_seq [ 0 ] ) ; i + + ) {
outb ( program_seq [ i ] . value , ioaddr + program_seq [ i ] . offset ) ;
}
}
for ( i = 0 ; i < 32 /*sizeof(SA_prom)*/ ; i + = 2 ) {
SA_prom [ i ] = inb ( ioaddr + NE_DATAPORT ) ;
SA_prom [ i + 1 ] = inb ( ioaddr + NE_DATAPORT ) ;
if ( SA_prom [ i ] ! = SA_prom [ i + 1 ] )
wordlength = 1 ;
}
/* At this point, wordlength *only* tells us if the SA_prom is doubled
up or not because some broken PCI cards don ' t respect the byte - wide
request in program_seq above , and hence don ' t have doubled up values .
These broken cards would otherwise be detected as an ne1000 . */
if ( wordlength = = 2 )
for ( i = 0 ; i < 16 ; i + + )
SA_prom [ i ] = SA_prom [ i + i ] ;
if ( wordlength = = 2 ) {
/* We must set the 8390 for word mode. */
outb ( 0x49 , ioaddr + NE_EN0_DCFG ) ;
start_page = NESM_START_PG ;
stop_page = NESM_STOP_PG ;
} else {
start_page = NE1SM_START_PG ;
stop_page = NE1SM_STOP_PG ;
}
neX000 = ( SA_prom [ 14 ] = = 0x57 & & SA_prom [ 15 ] = = 0x57 ) ;
ctron = ( SA_prom [ 0 ] = = 0x00 & & SA_prom [ 1 ] = = 0x00 & & SA_prom [ 2 ] = = 0x1d ) ;
/* Set up the rest of the parameters. */
if ( neX000 ) {
name = ( wordlength = = 2 ) ? " NE2000 " : " NE1000 " ;
} else if ( ctron ) {
name = ( wordlength = = 2 ) ? " Ctron-8 " : " Ctron-16 " ;
start_page = 0x01 ;
stop_page = ( wordlength = = 2 ) ? 0x40 : 0x20 ;
} else {
printk ( " not found. \n " ) ;
return - ENXIO ;
}
# else
wordlength = 2 ;
/* We must set the 8390 for word mode. */
outb ( 0x49 , ioaddr + NE_EN0_DCFG ) ;
start_page = NESM_START_PG ;
stop_page = NESM_STOP_PG ;
SA_prom [ 0 ] = MANUAL_HWADDR0 ;
SA_prom [ 1 ] = MANUAL_HWADDR1 ;
SA_prom [ 2 ] = MANUAL_HWADDR2 ;
SA_prom [ 3 ] = MANUAL_HWADDR3 ;
SA_prom [ 4 ] = MANUAL_HWADDR4 ;
SA_prom [ 5 ] = MANUAL_HWADDR5 ;
name = " NE2000 " ;
# endif
dev - > base_addr = ioaddr ;
/* Install the Interrupt handler */
i = request_irq ( IRQ_AMIGA_PORTS , apne_interrupt , SA_SHIRQ , DRV_NAME , dev ) ;
if ( i ) return i ;
for ( i = 0 ; i < ETHER_ADDR_LEN ; i + + ) {
printk ( " %2.2x " , SA_prom [ i ] ) ;
dev - > dev_addr [ i ] = SA_prom [ i ] ;
}
printk ( " \n %s: %s found. \n " , dev - > name , name ) ;
ei_status . name = name ;
ei_status . tx_start_page = start_page ;
ei_status . stop_page = stop_page ;
ei_status . word16 = ( wordlength = = 2 ) ;
ei_status . rx_start_page = start_page + TX_PAGES ;
ei_status . reset_8390 = & apne_reset_8390 ;
ei_status . block_input = & apne_block_input ;
ei_status . block_output = & apne_block_output ;
ei_status . get_8390_hdr = & apne_get_8390_hdr ;
dev - > open = & apne_open ;
dev - > stop = & apne_close ;
# ifdef CONFIG_NET_POLL_CONTROLLER
dev - > poll_controller = ei_poll ;
# endif
NS8390_init ( dev , 0 ) ;
pcmcia_ack_int ( pcmcia_get_intreq ( ) ) ; /* ack PCMCIA int req */
pcmcia_enable_irq ( ) ;
apne_owned = 1 ;
return 0 ;
}
static int
apne_open ( struct net_device * dev )
{
ei_open ( dev ) ;
return 0 ;
}
static int
apne_close ( struct net_device * dev )
{
if ( ei_debug > 1 )
printk ( " %s: Shutting down ethercard. \n " , dev - > name ) ;
ei_close ( dev ) ;
return 0 ;
}
/* Hard reset the card. This used to pause for the same period that a
8390 reset command required , but that shouldn ' t be necessary . */
static void
apne_reset_8390 ( struct net_device * dev )
{
unsigned long reset_start_time = jiffies ;
init_pcmcia ( ) ;
if ( ei_debug > 1 ) printk ( " resetting the 8390 t=%ld... " , jiffies ) ;
outb ( inb ( NE_BASE + NE_RESET ) , NE_BASE + NE_RESET ) ;
ei_status . txing = 0 ;
ei_status . dmaing = 0 ;
/* This check _should_not_ be necessary, omit eventually. */
while ( ( inb ( NE_BASE + NE_EN0_ISR ) & ENISR_RESET ) = = 0 )
2006-01-10 05:37:15 +03:00
if ( time_after ( jiffies , reset_start_time + 2 * HZ / 100 ) ) {
2005-04-17 02:20:36 +04:00
printk ( " %s: ne_reset_8390() did not complete. \n " , dev - > name ) ;
break ;
}
outb ( ENISR_RESET , NE_BASE + NE_EN0_ISR ) ; /* Ack intr. */
}
/* Grab the 8390 specific header. Similar to the block_input routine, but
we don ' t need to be concerned with ring wrap as the header will be at
the start of a page , so we optimize accordingly . */
static void
apne_get_8390_hdr ( struct net_device * dev , struct e8390_pkt_hdr * hdr , int ring_page )
{
int nic_base = dev - > base_addr ;
int cnt ;
char * ptrc ;
short * ptrs ;
/* This *shouldn't* happen. If it does, it's the last thing you'll see */
if ( ei_status . dmaing ) {
printk ( " %s: DMAing conflict in ne_get_8390_hdr "
" [DMAstat:%d][irqlock:%d][intr:%d]. \n " ,
dev - > name , ei_status . dmaing , ei_status . irqlock , dev - > irq ) ;
return ;
}
ei_status . dmaing | = 0x01 ;
outb ( E8390_NODMA + E8390_PAGE0 + E8390_START , nic_base + NE_CMD ) ;
outb ( ENISR_RDC , nic_base + NE_EN0_ISR ) ;
outb ( sizeof ( struct e8390_pkt_hdr ) , nic_base + NE_EN0_RCNTLO ) ;
outb ( 0 , nic_base + NE_EN0_RCNTHI ) ;
outb ( 0 , nic_base + NE_EN0_RSARLO ) ; /* On page boundary */
outb ( ring_page , nic_base + NE_EN0_RSARHI ) ;
outb ( E8390_RREAD + E8390_START , nic_base + NE_CMD ) ;
if ( ei_status . word16 ) {
ptrs = ( short * ) hdr ;
for ( cnt = 0 ; cnt < ( sizeof ( struct e8390_pkt_hdr ) > > 1 ) ; cnt + + )
* ptrs + + = inw ( NE_BASE + NE_DATAPORT ) ;
} else {
ptrc = ( char * ) hdr ;
for ( cnt = 0 ; cnt < sizeof ( struct e8390_pkt_hdr ) ; cnt + + )
* ptrc + + = inb ( NE_BASE + NE_DATAPORT ) ;
}
outb ( ENISR_RDC , nic_base + NE_EN0_ISR ) ; /* Ack intr. */
ei_status . dmaing & = ~ 0x01 ;
le16_to_cpus ( & hdr - > count ) ;
}
/* Block input and output, similar to the Crynwr packet driver. If you
are porting to a new ethercard , look at the packet driver source for hints .
The NEx000 doesn ' t share the on - board packet memory - - you have to put
the packet out through the " remote DMA " dataport using outb . */
static void
apne_block_input ( struct net_device * dev , int count , struct sk_buff * skb , int ring_offset )
{
int nic_base = dev - > base_addr ;
char * buf = skb - > data ;
char * ptrc ;
short * ptrs ;
int cnt ;
/* This *shouldn't* happen. If it does, it's the last thing you'll see */
if ( ei_status . dmaing ) {
printk ( " %s: DMAing conflict in ne_block_input "
" [DMAstat:%d][irqlock:%d][intr:%d]. \n " ,
dev - > name , ei_status . dmaing , ei_status . irqlock , dev - > irq ) ;
return ;
}
ei_status . dmaing | = 0x01 ;
outb ( E8390_NODMA + E8390_PAGE0 + E8390_START , nic_base + NE_CMD ) ;
outb ( ENISR_RDC , nic_base + NE_EN0_ISR ) ;
outb ( count & 0xff , nic_base + NE_EN0_RCNTLO ) ;
outb ( count > > 8 , nic_base + NE_EN0_RCNTHI ) ;
outb ( ring_offset & 0xff , nic_base + NE_EN0_RSARLO ) ;
outb ( ring_offset > > 8 , nic_base + NE_EN0_RSARHI ) ;
outb ( E8390_RREAD + E8390_START , nic_base + NE_CMD ) ;
if ( ei_status . word16 ) {
ptrs = ( short * ) buf ;
for ( cnt = 0 ; cnt < ( count > > 1 ) ; cnt + + )
* ptrs + + = inw ( NE_BASE + NE_DATAPORT ) ;
if ( count & 0x01 ) {
buf [ count - 1 ] = inb ( NE_BASE + NE_DATAPORT ) ;
}
} else {
ptrc = ( char * ) buf ;
for ( cnt = 0 ; cnt < count ; cnt + + )
* ptrc + + = inb ( NE_BASE + NE_DATAPORT ) ;
}
outb ( ENISR_RDC , nic_base + NE_EN0_ISR ) ; /* Ack intr. */
ei_status . dmaing & = ~ 0x01 ;
}
static void
apne_block_output ( struct net_device * dev , int count ,
const unsigned char * buf , const int start_page )
{
int nic_base = NE_BASE ;
unsigned long dma_start ;
char * ptrc ;
short * ptrs ;
int cnt ;
/* Round the count up for word writes. Do we need to do this?
What effect will an odd byte count have on the 8390 ?
I should check someday . */
if ( ei_status . word16 & & ( count & 0x01 ) )
count + + ;
/* This *shouldn't* happen. If it does, it's the last thing you'll see */
if ( ei_status . dmaing ) {
printk ( " %s: DMAing conflict in ne_block_output. "
" [DMAstat:%d][irqlock:%d][intr:%d] \n " ,
dev - > name , ei_status . dmaing , ei_status . irqlock , dev - > irq ) ;
return ;
}
ei_status . dmaing | = 0x01 ;
/* We should already be in page 0, but to be safe... */
outb ( E8390_PAGE0 + E8390_START + E8390_NODMA , nic_base + NE_CMD ) ;
outb ( ENISR_RDC , nic_base + NE_EN0_ISR ) ;
/* Now the normal output. */
outb ( count & 0xff , nic_base + NE_EN0_RCNTLO ) ;
outb ( count > > 8 , nic_base + NE_EN0_RCNTHI ) ;
outb ( 0x00 , nic_base + NE_EN0_RSARLO ) ;
outb ( start_page , nic_base + NE_EN0_RSARHI ) ;
outb ( E8390_RWRITE + E8390_START , nic_base + NE_CMD ) ;
if ( ei_status . word16 ) {
ptrs = ( short * ) buf ;
for ( cnt = 0 ; cnt < count > > 1 ; cnt + + )
outw ( * ptrs + + , NE_BASE + NE_DATAPORT ) ;
} else {
ptrc = ( char * ) buf ;
for ( cnt = 0 ; cnt < count ; cnt + + )
outb ( * ptrc + + , NE_BASE + NE_DATAPORT ) ;
}
dma_start = jiffies ;
while ( ( inb ( NE_BASE + NE_EN0_ISR ) & ENISR_RDC ) = = 0 )
2006-01-10 05:37:15 +03:00
if ( time_after ( jiffies , dma_start + 2 * HZ / 100 ) ) { /* 20ms */
2005-04-17 02:20:36 +04:00
printk ( " %s: timeout waiting for Tx RDC. \n " , dev - > name ) ;
apne_reset_8390 ( dev ) ;
NS8390_init ( dev , 1 ) ;
break ;
}
outb ( ENISR_RDC , nic_base + NE_EN0_ISR ) ; /* Ack intr. */
ei_status . dmaing & = ~ 0x01 ;
return ;
}
static irqreturn_t apne_interrupt ( int irq , void * dev_id , struct pt_regs * regs )
{
unsigned char pcmcia_intreq ;
if ( ! ( gayle . inten & GAYLE_IRQ_IRQ ) )
return IRQ_NONE ;
pcmcia_intreq = pcmcia_get_intreq ( ) ;
if ( ! ( pcmcia_intreq & GAYLE_IRQ_IRQ ) ) {
pcmcia_ack_int ( pcmcia_intreq ) ;
return IRQ_NONE ;
}
if ( ei_debug > 3 )
printk ( " pcmcia intreq = %x \n " , pcmcia_intreq ) ;
pcmcia_disable_irq ( ) ; /* to get rid of the sti() within ei_interrupt */
ei_interrupt ( irq , dev_id , regs ) ;
pcmcia_ack_int ( pcmcia_get_intreq ( ) ) ;
pcmcia_enable_irq ( ) ;
return IRQ_HANDLED ;
}
# ifdef MODULE
static struct net_device * apne_dev ;
int init_module ( void )
{
apne_dev = apne_probe ( - 1 ) ;
if ( IS_ERR ( apne_dev ) )
return PTR_ERR ( apne_dev ) ;
return 0 ;
}
void cleanup_module ( void )
{
unregister_netdev ( apne_dev ) ;
pcmcia_disable_irq ( ) ;
free_irq ( IRQ_AMIGA_PORTS , apne_dev ) ;
pcmcia_reset ( ) ;
release_region ( IOBASE , 0x20 ) ;
free_netdev ( apne_dev ) ;
}
# endif
static int init_pcmcia ( void )
{
u_char config ;
# ifndef MANUAL_CONFIG
u_char tuple [ 32 ] ;
int offset_len ;
# endif
u_long offset ;
pcmcia_reset ( ) ;
pcmcia_program_voltage ( PCMCIA_0V ) ;
pcmcia_access_speed ( PCMCIA_SPEED_250NS ) ;
pcmcia_write_enable ( ) ;
# ifdef MANUAL_CONFIG
config = MANUAL_CONFIG ;
# else
/* get and write config byte to enable IO port */
if ( pcmcia_copy_tuple ( CISTPL_CFTABLE_ENTRY , tuple , 32 ) < 3 )
return 0 ;
config = tuple [ 2 ] & 0x3f ;
# endif
# ifdef MANUAL_OFFSET
offset = MANUAL_OFFSET ;
# else
if ( pcmcia_copy_tuple ( CISTPL_CONFIG , tuple , 32 ) < 6 )
return 0 ;
offset_len = ( tuple [ 2 ] & 0x3 ) + 1 ;
offset = 0 ;
while ( offset_len - - ) {
offset = ( offset < < 8 ) | tuple [ 4 + offset_len ] ;
}
# endif
out_8 ( GAYLE_ATTRIBUTE + offset , config ) ;
return 1 ;
}
MODULE_LICENSE ( " GPL " ) ;