From: Stefan E. <se...@us...> - 2003-03-18 11:12:32
|
Update of /cvsroot/blob/blob/src/blob In directory sc8-pr-cvs1:/tmp/cvs-serv23193 Added Files: smc9196.c Log Message: - first working version of a smc9xxx ethernet "driver" for BLOB. Basically a stripped-down version of drivers/net/smc9194.c. Implements readether()/writeether() wrappers to use with Russell King's arp/tftp/bootp code. --- NEW FILE: smc9196.c --- /* * smc9196.c: Low-level ethernet polling functions * * Most of this code was taken from linux/drivers/net/smc9194.c * Original Author: * Erik Stahlman ( er...@vt... ) * contributors: * Arnaldo Carvalho de Melo <ac...@co...> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include <util.h> #include <time.h> #include <errno.h> #include <serial.h> #include <command.h> #include <net/smc9196.h> /********************************************************************** * defines */ #define SMC_DEBUG #ifdef SMC_DEBUG # define DBG( x, args... ) if ( dbg>=x ) printf( args ) #else # define DBG( x, args... ) ; #endif #define SMC_BASE (0x18000000) #define SMC_SELECT_BANK( base, x ) smc_outw( x, base, BANK_SELECT ) #define SMC_DELAY() msleep(1); #define SMC_SET_INT(base, x) \ { \ smc_outb((x), base, INT_MASK); \ } /* this enables an interrupt in the interrupt mask register */ #define SMC_ENABLE_INT(x) \ { \ u8 mask; \ mask = smc_inb(ioaddr, INT_MASK); \ mask |= (x); \ smc_outb(mask, ioaddr, INT_MASK); \ } #define SMC_INTERRUPT_MASK (IM_EPH_INT | IM_RX_OVRN_INT | IM_RCV_INT) /* Duplex, half or full. */ #define DUPLEX_HALF 0x00 #define DUPLEX_FULL 0x01 /* Which connector port. */ #define PORT_TP 0x00 #define PORT_AUI 0x01 /********************************************************************** * globals */ u8 hwaddress[6] = { 2, 3, 4, 5, 6, 7 }; /********************************************************************** * module globals */ static int dbg = 1; static u32 smc_chip_base = 0; static u8 pkt[1024]; /********************************************************************** * smc memory access functions */ static inline u8 smc_inb( u32 base, int reg ) { return *((volatile u8 *)(base+reg*4)); } static inline void smc_ins( u32 ioaddr, int reg, u8 *dest, u16 size ) { while( size-- ) *dest++ = smc_inb( ioaddr, reg ); } static inline unsigned short smc_inw(unsigned base, int r) { unsigned char b1, b2; msleep(1); b1 = smc_inb(base, r); msleep(1); b2 = smc_inb(base, r+1); return (b2 << 8) | b1; } static inline void smc_outb( u8 val, u32 base, int r ) { *((volatile u8 *)(base+r*4)) = val; } static inline void smc_outw( u16 val, u32 base, int r ) { smc_outb(val&0xff, base, r); smc_outb(val>>8, base, r+1); } static inline void smc_outl( u32 val, u32 base, int r ) { smc_outb(val, base, r); smc_outb(val>>8, base, r+1); smc_outb(val>>16, base, r+2); smc_outb(val>>24, base, r+3); } static inline void smc_outs( u32 ioaddr, int reg, u8 *dest, u16 size ) { while( size-- ) smc_outb( *dest++, ioaddr, reg ); } static void hexdump( u8 *address, size_t len ) { u8 *endaddress = address + len, *start=address; u8 *tmpaddress; u8 value; /* print it */ for ( ; address < endaddress; address += 0x10) { printf( "0x%08x: ", address-start ); for (tmpaddress = address; tmpaddress < address + 0x10; tmpaddress += 1) { value = *tmpaddress; printf( "%02x ", value ); } for (tmpaddress = address; tmpaddress < address + 0x10; tmpaddress++) { value = *tmpaddress; if ((value >= ' ') && (value <= '~')) serial_write(value); else serial_write('.'); } serial_write('\n'); } } /********************************************************************** * static functions */ static int smc_probe_chip( u32 base ) { u16 bank; bank = smc_inw( base, BANK_SELECT ); if ( (bank & 0xff00) != 0x3300 ) { DBG( 1, "%s: probe 1 failed\n", __FUNCTION__ ); return -EINVAL; } smc_outw( 0, base, BANK_SELECT ); bank = smc_inw( base, BANK_SELECT ); if ( (bank & 0xff00) != 0x3300 ) { DBG( 1, "%s: probe 2 failed\n", __FUNCTION__ ); return -EINVAL; } DBG( 1, "%s: probe done\n", __FUNCTION__ ); return 0; } static int smc_hw_reset( u32 base ) { u8 ecor, ecsr; u32 addr = base | ( 1<<25 ); /* ATTRIBUTE SPACE */ /* * Reset the device. We must disable IRQs around this. */ ecor = smc_inb(addr, ECOR) & ~ECOR_RESET; smc_outb(ecor | ECOR_RESET, addr, ECOR); msleep(1); /* * The device will ignore all writes to the enable bit while * reset is asserted, even if the reset bit is cleared in the * same write. Must clear reset first, then enable the device. */ smc_outb(ecor, addr, ECOR); smc_outb(ecor | ECOR_ENABLE, addr, ECOR); /* * Force byte mode. */ ecsr = smc_inb(addr, ECSR); smc_outb( ecsr | ECSR_IOIS8, addr, ECSR); DBG( 1, "%s: reset done\n", __FUNCTION__ ); return 0; } /* . Function: smc_reset(struct net_device *dev) . Purpose: . This sets the SMC91xx chip to its normal state, hopefully from whatever . mess that any other DOS driver has put it in. . . Maybe I should reset more registers to defaults in here? SOFTRESET should . do that for me. . . Method: . 1. send a SOFT RESET . 2. wait for it to finish . 3. enable autorelease mode . 4. reset the memory management unit . 5. clear all interrupts . */ static void smc_reset(u32 ioaddr) { /* This resets the registers mostly to defaults, but doesn't affect EEPROM. That seems unnecessary */ SMC_SELECT_BANK( ioaddr, 0); smc_outw(RCR_SOFTRESET, ioaddr, RCR); /* this should pause enough for the chip to be happy */ SMC_DELAY(); /* Set the transmit and receive configuration registers to default values */ smc_outw(RCR_CLEAR, ioaddr, RCR); smc_outw(TCR_CLEAR, ioaddr, TCR); /* set the control register to automatically release successfully transmitted packets, to make the best use out of our limited memory */ SMC_SELECT_BANK( ioaddr, 1); smc_outw(smc_inw(ioaddr, CONTROL) | CTL_AUTO_RELEASE, ioaddr, CONTROL); /* Reset the MMU */ SMC_SELECT_BANK( ioaddr, 2); smc_outw(MC_RESET, ioaddr, MMU_CMD); /* Note: It doesn't seem that waiting for the MMU busy is needed here, but this is a place where future chipsets _COULD_ break. Be wary of issuing another MMU command right after this */ SMC_SET_INT( ioaddr, 0); DBG( 1, "%s: sw reset done.\n", __FUNCTION__ ); } /* * This is responsible for setting the chip appropriately * for the interface type. This should only be called while * the interface is up and running. */ static void smc_set_port(int port, int duplex, u32 ioaddr ) { u16 val; SMC_SELECT_BANK( ioaddr, 1); val = smc_inw(ioaddr, CONFIG); switch (port) { case PORT_TP: DBG( 1, "%s: select TP\n", __FUNCTION__ ); val &= ~CFG_AUI_SELECT; break; case PORT_AUI: DBG( 1, "%s: select AUI\n", __FUNCTION__ ); val |= CFG_AUI_SELECT; break; } smc_outw(val, ioaddr, CONFIG); SMC_SELECT_BANK( ioaddr, 0); val = smc_inw(ioaddr, TCR); switch (duplex) { case DUPLEX_HALF: DBG( 1, "%s: select half duplex\n", __FUNCTION__ ); val &= ~TCR_FDSE; break; case DUPLEX_FULL: DBG( 1, "%s: select full duplex\n", __FUNCTION__ ); val |= TCR_FDSE; break; } smc_outw(val, ioaddr, TCR); } static int smc_get_ethaddr( u32 ioaddr, u8 dev_addr[6] ) { int i; SMC_SELECT_BANK( ioaddr, 1); for ( i=0; i<6; i+=2 ) { u16 address; address = smc_inw(ioaddr, ADDR0 + i); dev_addr[i + 1] = address >> 8; dev_addr[i] = address & 0xFF; } DBG( 1, "%s: got eth addr %02x:%02x:%02x:%02x:%02x:%02x\n", __FUNCTION__, dev_addr[0], dev_addr[1], dev_addr[2], dev_addr[3], dev_addr[4], dev_addr[5] ); return 0; } static int smc_set_ethaddr( u32 ioaddr, u8 dev_addr[6] ) { int i; SMC_SELECT_BANK( ioaddr, 1); for ( i=0; i<6; i+=2 ) { u16 address; address = dev_addr[i+1]<<8 | dev_addr[i]; smc_outw(address, ioaddr, ADDR0 + i); } DBG( 1, "%s: set eth addr to %02x:%02x:%02x:%02x:%02x:%02x\n", __FUNCTION__, dev_addr[0], dev_addr[1], dev_addr[2], dev_addr[3], dev_addr[4], dev_addr[5] ); return 0; } /* . Function: smc_enable . Purpose: let the chip talk to the outside work . Method: . 1. Enable the transmitter . 2. Enable the receiver . 3. Enable interrupts */ static void smc_enable(u32 ioaddr) { SMC_SELECT_BANK( ioaddr, 0); /* see the header file for options in TCR/RCR NORMAL*/ smc_outw(TCR_NORMAL, ioaddr, TCR); smc_outw(RCR_NORMAL, ioaddr, RCR); /* now, enable interrupts */ SMC_SELECT_BANK( ioaddr, 2); SMC_SET_INT( ioaddr, SMC_INTERRUPT_MASK); //SMC_SET_INT( ioaddr, 0); DBG( 1, "%s: chip enabled.\n", __FUNCTION__ ); } /*------------------------------------------------------------- . . smc_rcv - receive a packet from the card . . There is (at least) a packet waiting to be read from . chip-memory. . . o Read the status . o If an error, record it . o otherwise, read in the packet -------------------------------------------------------------- */ static int smc_rcv( u32 ioaddr, u8 *data, u16 size ) { int ret = 0; int packet_number; u16 status; u16 packet_length; /* assume bank 2 */ packet_number = smc_inw(ioaddr, FIFO_PORTS); if (packet_number & FP_RXEMPTY) { /* we got called , but nothing was on the FIFO */ DBG( 2, "%s: WARNING: smc_rcv with nothing on FIFO.\n", __FUNCTION__); /* don't need to restore anything */ ret = -1; goto done; } /* start reading from the start of the packet */ smc_outw(PTR_READ | PTR_RCV | PTR_AUTOINC, ioaddr, POINTER); /* First two words are status and packet_length */ status = smc_inw(ioaddr, DATA_1); packet_length = smc_inw(ioaddr, DATA_1); packet_length &= 0x07ff; /* mask off top bits */ DBG( 5, "RCV: STATUS %4x LENGTH %4x\n", status, packet_length); /* . the packet length contains 3 extra words : . status, length, and an extra word with an odd byte . */ packet_length -= 6; if (status & RS_ERRORS) { /* error ... */ printf( "%s: RCV error\n", __FUNCTION__ ); ret = -1; goto done; } /* do stuff to make a new packet */ /* read one extra byte */ if (status & RS_ODDFRAME) packet_length++; /* ! This should work without alignment, but it could be ! in the worse case */ if ( packet_length > size ) { printf( "%s: packet (%d) too large for buffer (%d).\n", __FUNCTION__, packet_length, size ); ret = -1; goto done; } smc_ins(ioaddr, DATA_1, data, packet_length); //print_packet(data, packet_length); ret = packet_length; done: /* error or good, tell the card to get rid of this packet */ smc_outw(MC_RELEASE, ioaddr, MMU_CMD); return ret; } /* . Function: smc_hardware_send_packet(struct net_device *) . Purpose: . This sends the actual packet to the SMC9xxx chip. . . Algorithm: . First, see if a saved_skb is available. . (this should NOT be called if there is no 'saved_skb' . Now, find the packet number that the chip allocated . Point the data pointers at it in memory . Set the length word in the chip's memory . Dump the packet to chip memory . Check if a last byte is needed (odd length packet) . if so, set the control flag right . Tell the card to send it . Enable the transmit interrupt, so I know if it failed . Free the kernel data if I actually sent it. */ static void smc_hardware_send_packet( u32 ioaddr, u8 *buf, u16 length ) { u16 lastword; u8 packet_no; DBG( 5, "%s( %p, %p, %d)\n", __FUNCTION__, (void *)ioaddr, (void *)buf, length ); /* If I get here, I _know_ there is a packet slot waiting for me */ packet_no = smc_inb(ioaddr, PNR_ARR + 1); if (packet_no & 0x80) { printf("%s: Memory allocation failed.\n", __FUNCTION__ ); return; } DBG( 5, "packet_no=%d\n", packet_no ); /* we have a packet address, so tell the card to use it */ smc_outb(packet_no, ioaddr, PNR_ARR); /* point to the beginning of the packet */ smc_outw(PTR_AUTOINC, ioaddr, POINTER); DBG( 5, "%s: Trying to xmit packet of length %d\n", __FUNCTION__, length); //print_packet(buf, length); /* send the packet length (+6 for status, length and ctl byte) and the status word (set to zeros) */ smc_outl((length + 6) << 16, ioaddr, DATA_1); /* send the actual data . I _think_ it's faster to send the longs first, and then . mop up by sending the last word. It depends heavily . on alignment, at least on the 486. Maybe it would be . a good idea to check which is optimal? But that could take . almost as much time as is saved? */ smc_outs(ioaddr, DATA_1, buf, length); /* Send the last byte, if there is one. */ if ((length & 1) == 0) lastword = 0; else lastword = 0x2000 | buf[length - 1]; smc_outw(lastword, ioaddr, DATA_1); /* enable the interrupts */ SMC_ENABLE_INT(IM_TX_INT | IM_TX_EMPTY_INT); /* and let the chipset deal with it */ smc_outw(MC_ENQUEUE, ioaddr, MMU_CMD); DBG( 5, "%s: Sent packet of length %d\n", __FUNCTION__, length); return; } /* . Function: smc_wait_to_send_packet(struct sk_buff * skb, struct net_device *) . Purpose: . Attempt to allocate memory for a packet, if chip-memory is not . available, then tell the card to generate an interrupt when it . is available. . . Algorithm: . . o if the saved_skb is not currently null, then drop this packet . on the floor. This should never happen, because of TBUSY. . o if the saved_skb is null, then replace it with the current packet, . o See if I can sending it now. . o (NO): Enable interrupts and let the interrupt handler deal with it. . o (YES):Send it now. */ static int smc_send(u32 ioaddr, u8 *data, u16 length ) { unsigned short numPages; u16 time_out; DBG( 5, "%s( %p, %p, %d)\n", __FUNCTION__, (void *)ioaddr, (void *)data, length ); /* ** The MMU wants the number of pages to be the number of 256 bytes ** 'pages', minus 1 (since a packet can't ever have 0 pages :)) ** ** Pkt size for allocating is data length +6 (for additional status words, ** length and ctl!) If odd size last byte is included in this header. */ numPages = ((length & 0xfffe) + 6) / 256; DBG( 5, "numPages=%d\n", numPages ); if (numPages > 7) { printf("%s: Far too big packet error.\n", __FUNCTION__); return -1; } /* now, try to allocate the memory */ SMC_SELECT_BANK( ioaddr, 2); smc_outw(MC_ALLOC | numPages, ioaddr, MMU_CMD); time_out = 10; do { u16 status; status = smc_inb(ioaddr, INTERRUPT); DBG( 5, "status=%d\n", status ); if (status & IM_ALLOC_INT) { /* acknowledge the interrupt */ smc_outb(IM_ALLOC_INT, ioaddr, INTERRUPT); break; } //msleep(1); } while (-- time_out); if (!time_out) { printf("%s: memory allocation time out.\n", __FUNCTION__ ); return -1; } /* or YES! I can send the packet now.. */ smc_hardware_send_packet(ioaddr, data, length ); return 0; } /********************************************************************** * test command */ static int smctest( int argc, char *argv[] ) { u32 base = SMC_BASE; u8 hw_addr_set[6] = { 2,3,4,5,6,7 }; //u8 hw_addr_set[6] = { 0x00, 0x50, 0x7f, 0x01, 0xcf, 0x0f }; u8 hw_addr[6]; u8 serverip[4] = { 192, 168, 1, 1 }; u8 clientip[4] = { 192, 168, 1, 191 }; int i; static const unsigned char arp_txpacket[] = { /* destination address */ /* source address */ 0x08, 0x06, /* proto */ /* arp */ 0x00, 0x01, /* hardware format: ethernet */ 0x08, 0x00, /* protocol format: ip */ 0x06, /* hardware length */ 0x04, /* protocol length */ 0x00, 0x01 /* arp request */ }; static unsigned char arp_tx[64]; unsigned char *p = arp_tx; if (smc_init(base)) return -EINVAL; set_ethaddr( hw_addr_set ); get_ethaddr( hw_addr ); printf( "eth hw addr %02x:%02x:%02x:%02x:%02x:%02x\n", hw_addr[0], hw_addr[1], hw_addr[2], hw_addr[3], hw_addr[4], hw_addr[5] ); memset (p, 0, sizeof (arp_tx)); memset (p, 0xff, 6); memcpy (p + 12, arp_txpacket, sizeof (arp_txpacket)); memcpy (p + 22, hw_addr, 6); memcpy (p + 28, clientip, 4); memcpy (p + 38, serverip, 4); i=1024; while (i-- ) { //msleep( 100 ); if (!(i & (~0xf ))) printf( "." ); writeether( p, 60 ); } i=1024; while (i-- ) { int pkt_size; pkt_size = readether( pkt, 1024 ); if ( pkt_size > 0 ) { printf( "[%02d]", pkt_size ); } else { printf( "." ); } } printf( "\n" ); return 0; } static char smctesthelp[] = "test smc 91c96 ethernet chip\n"; __commandlist(smctest, "smctest", smctesthelp); /********************************************************************** * exported functions */ int smc_init(u32 base) { DBG( 1, "%s(%08x)\n", __FUNCTION__, base ); smc_hw_reset(base); if ( smc_probe_chip(base) ) return -EINVAL; smc_reset(base); smc_enable(base); smc_set_port( PORT_TP, DUPLEX_HALF, base ); smc_set_ethaddr( base, hwaddress ); smc_chip_base = base; return 0; } int writeether( u8 *buf, int size ) { if ( !smc_chip_base ) return -EINVAL; memcpy( buf+6, hwaddress, 6 ); #ifdef SMC_DEBUG printf( "%s: tx packet (%d bytes):\n", __FUNCTION__, size ); hexdump( buf, size ); #endif return smc_send( smc_chip_base, buf, size ); } int readether( u8 *buf, int size ) { int ret = 0; u8 status; u8 mask = 0xff; if ( !smc_chip_base ) { ret = -EINVAL; goto done; } //mask = smc_inb(smc_chip_base, INT_MASK); /* clear all interrupts */ //SMC_SET_INT( smc_chip_base, 0); /* read the status flag, and mask it */ status = smc_inb(smc_chip_base, INTERRUPT) & mask; if (!status) { ret = 0; } else if ( status & IM_RCV_INT ) { ret = smc_rcv( smc_chip_base, buf, size ); #ifdef SMC_DEBUG printf( "%s: rx packet (%d bytes):\n", __FUNCTION__, size ); hexdump( buf, size ); #endif } /* restore state register */ //SMC_SELECT_BANK( smc_chip_base, 2); //SMC_SET_INT(smc_chip_base, mask); done: return ret; } int set_ethaddr( u8 hw_addr[6] ) { if ( !smc_chip_base ) return -EINVAL; return smc_set_ethaddr( smc_chip_base, hw_addr ); } int get_ethaddr( u8 hw_addr[6] ) { if ( !smc_chip_base ) return -EINVAL; return smc_get_ethaddr( smc_chip_base, hw_addr ); } |