aboutsummaryrefslogblamecommitdiff
path: root/sys/dev/sf/if_sf.c
blob: 5105c706ad24bc6b211563f4a7ac885189ca10a9 (plain) (tree)
1
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
   




























                                                                             

   


                      

                                                                    
                                        
                                                               




                                                






                                                                      

                                                                   







                                                                       


                                                                    

















                                                                     



                                 

                      

                       
                       


                       
                       
                       


                          
 
                    




                         
                         
                            
 


                           

                           
 
                        
 
                            




                                                                    
 

                                  

                                   




                                                               
                                   













                                                                               

  


                               


                                 
                            




                                                
                                       
                                        
                                                       
                                     
                                            
                                                     
                                              
                            
                                              
                                       
                                           


                                                                

                                           

                                               


                                                         
                                                       
             
                                                        

      
                                                      
 


                                                       
                                      
                     
                                                                    
      
 

                                                          
                                                  


                                                           






                                                     

                                                    









                                                                  
                      


                             
             





                                
                                                     
                                                                

                                 
                                                       

                                                         
                                                        
 

                                        
 
                                    
 





                                                                               
 
                     

 

                                            
 
                            



                                                           
                     

 
           
                                                       
 






                                                                               

 




                                                             
          
                                                      
 

                                                   
                                

                        
                                

                                                
                                                                        
                                                
                                                                        
                                                
                                                                        
 
                   






                                                                
          
                                                      
 
                                  

                        
                                
 
                                                      








                                                                            
                   





                                        
          
                                                       
 
 
                                                 
                                



                                                                        
                   


      
          
                                                 
 
                                    
                                  
                                        
 

                                   
                                          
                                                           
                                                  



                              
                           
 


                               
 
                     

 
          
                                                           
 
                                    


                                     


                                                   

                                          
                                                            
                                              


                              
                   

 
           
                               

                                    

                                   














                                                            
                                              





                                                         
 










                                                                   
                                                           









                                                                
                                                           













                                                                 

 
           
                                



                                      

                                                                             
 
                         


                                                   


                                                                   
                                      
 











                                                                              
 
















                                                                           
                 


                                                                      
         



                                           


  

                     
          
                                 

                                    

                                      


                           
 
                                              

                                               
                                                             

                                             



                                  




                               
           
                                                          

                                    
                                     

                           
                    
                                              
 


                                                 
                      

 
          
                                                         
 

                                     
                                     
                                            
 




                                   
                          
                            
                                             







                                                                         
                        
                                                                       

                                            
                                                
                              


                          
                            
                                
                              


                          

                                                                          
                      
                        
                                                           
                     

















                                                                          

                           




























                                                                              
                      
                
                                                        


                      
                       

 
           
                             
 
                          














                                                                          
                                                                      


                                                                          







                                                                       
          
                      

                                   



                                     
 


                                      
 




                                                                         
                                                           

                         

         






                                                                





                                                              
          
                       
 
                                  

                                     
                                    
                                               
                                                      
 
                                   
                         
 
                                                                         
                     


                                                          


                                        
                                  
 










                                                                             
                                 












                                                                            
         


                                                                         
 

















                                                                               
 
                                 
                
                                                                   


                                      
                                                               



                              





















                                                                              
 






                                               
                          

                                                                            


                                     


                          
                                              
 

                                               
                                                                         



                               
                           

                                                              
                                                             



                              
                           
                                                                     
                                                                    
                                 
                                 
                               


                                                          













                                                       
                                                 

                                              
      
          


                                                                 
           
                                                                   
 
                                                               
                                                                            
                                                 

                    
                                                            
                                    

                          

     


                               
                       

 






                                                                     
          
                       


                                     

                                   
                         
 
                     
                                                             

                                           
 
                                                             
                                      
                            
                                  
                            
                              



                                                                  
         
                            
                                                        

                                     
                                
 
                                    
                                                                    
                               
                                                                      


                                                                     
 

                        

                             
                                 
 
                   

 


                                           
 
           
                                                                     
 
                                     
 



                                          

 
          
                                 
 











































                                                                                
 















                                                                          

         
















                                                                          
 
















                                                                          
 















                                                                           
 















                                                                           
 








                                                                           
 









                                                                    
 











                                                                                
 









                                                                               
 








                                                                           
 









                                                                    
 











                                                                                
 




























































































































































































































































































































                                                                               
          





                                         
                                                           




                                                
                     




































                                                                                


                                 


                                                   

                                 
 































































                                                                               
                              
                                         
                            
                           


                                              

         

















                                                                           
                          









                                                                  
           
                             
 

                                         
                                     



                                                
 
                         
 


                                                          
 







































                                                                              
                              







                                                                              

         



                                                        
 



                                                  
                                      

                                                      

 
           
                                       
 








                                                              
                                                


                                                               
                                                       

                             

 
                     
          

                                                        

                                       
                                         
 
                           
                     
                    
 

                                                         
                                  
         
 
                             
                                
                     
                                            
                                     

                                           
                                                                      
                                                
 

                                                              
                                                    




                                                                  
                                                   
                                                                       
                                                                    




                                                                         
                                                                    




                                                                  

                 

                      
                          


                           
           
                  


                                     
                                       
 
                                    

                    







                                                              
 
                         
                     

                                                     
      

                                                       



                                            

                                                         

                                     

                                                                     

                                     

                                                              
                                                    




                                                                  
                                                   














                                                                       
                 

                                                                      




                                          
                                            
                                     
            
                      

 
           






































                                                                                

                                    
 
                                    





                           
                                   
 
                                     
                                     

                                                      
                                  
 
                           
                         
                                              

                    
                                                  



                                                   
                                                                


                                            

                                                                              
 





                                                                        

                                             
                                         
                                                                         




                            





                                                                             
 
          
                         
           
                        
 
                                                


                                          








                                                                        
 








                                             

                                                  










                                                                    
                                                             
                                          

                                          



                                                                      

                                                  





                                                               
                                    











                                                                 


                                                       














                                                                                
 

                                                   
                                                     

                                                    
      




                                                            


                                                    
 










                                                                   
 

                                              
 
                                                   

 
          
                                                   
 

                                      
                                   



                                                          
 
                           
 











                                                                    
                 













                                                                       

         











                                                                       
                 
         
 































                                                                           

 
           
                           

                                    







                             
                                  

                                    

                                        

                           
                           
 

                                                                        

                       






                                                                        
                                                      

                                   







                                                                 
                                                              
                                                             

                              
 
                      



                                                                         
                                            

         






                                                                                
 


                                                                       

 
           
                            
 

                                     
                                     
                                  
 
                           
 
                         
 








                                                                  
 
                                  
                                           
 







                                                
 


                                                    
                                               







                                                                  
                 
         
                                               








                                                                   
                 

         
 












                                                   







                                                                     
   
           
                                    
 
                                     
                                                     
                                  
 
                           
 





































































                                                                            
 


                                                                              
 




















































                                                                                

 
           
                                
 
                                     
 
                           
 



                                                                  
 
                          






                                                                            
 
                           
 
                                            
                                     







                                    
 

                    
                      

                   

 
          
                        




                                    
                    
                    





















                                          
                      

                   
 





















































































































                                                                               
/*-
 * Copyright (c) 1997, 1998, 1999
 *	Bill Paul <wpaul@ctr.columbia.edu>.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Bill Paul.
 * 4. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

/*
 * Adaptec AIC-6915 "Starfire" PCI fast ethernet driver for FreeBSD.
 * Programming manual is available from:
 * http://download.adaptec.com/pdfs/user_guides/aic6915_pg.pdf.
 *
 * Written by Bill Paul <wpaul@ctr.columbia.edu>
 * Department of Electical Engineering
 * Columbia University, New York City
 */
/*
 * The Adaptec AIC-6915 "Starfire" is a 64-bit 10/100 PCI ethernet
 * controller designed with flexibility and reducing CPU load in mind.
 * The Starfire offers high and low priority buffer queues, a
 * producer/consumer index mechanism and several different buffer
 * queue and completion queue descriptor types. Any one of a number
 * of different driver designs can be used, depending on system and
 * OS requirements. This driver makes use of type2 transmit frame
 * descriptors to take full advantage of fragmented packets buffers
 * and two RX buffer queues prioritized on size (one queue for small
 * frames that will fit into a single mbuf, another with full size
 * mbuf clusters for everything else). The producer/consumer indexes
 * and completion queues are also used.
 *
 * One downside to the Starfire has to do with alignment: buffer
 * queues must be aligned on 256-byte boundaries, and receive buffers
 * must be aligned on longword boundaries. The receive buffer alignment
 * causes problems on the strict alignment architecture, where the
 * packet payload should be longword aligned. There is no simple way
 * around this.
 *
 * For receive filtering, the Starfire offers 16 perfect filter slots
 * and a 512-bit hash table.
 *
 * The Starfire has no internal transceiver, relying instead on an
 * external MII-based transceiver. Accessing registers on external
 * PHYs is done through a special register map rather than with the
 * usual bitbang MDIO method.
 *
 * Acesssing the registers on the Starfire is a little tricky. The
 * Starfire has a 512K internal register space. When programmed for
 * PCI memory mapped mode, the entire register space can be accessed
 * directly. However in I/O space mode, only 256 bytes are directly
 * mapped into PCI I/O space. The other registers can be accessed
 * indirectly using the SF_INDIRECTIO_ADDR and SF_INDIRECTIO_DATA
 * registers inside the 256-byte I/O window.
 */

#ifdef HAVE_KERNEL_OPTION_HEADERS
#include "opt_device_polling.h"
#endif

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/endian.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/rman.h>
#include <sys/module.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/sysctl.h>
#include <sys/taskqueue.h>

#include <net/bpf.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/ethernet.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_types.h>
#include <net/if_vlan_var.h>

#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>

#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>

#include <machine/bus.h>

#include <dev/sf/if_sfreg.h>
#include <dev/sf/starfire_rx.h>
#include <dev/sf/starfire_tx.h>

/* "device miibus" required.  See GENERIC if you get errors here. */
#include "miibus_if.h"

MODULE_DEPEND(sf, pci, 1, 1, 1);
MODULE_DEPEND(sf, ether, 1, 1, 1);
MODULE_DEPEND(sf, miibus, 1, 1, 1);

#undef	SF_GFP_DEBUG
#define	SF_CSUM_FEATURES	(CSUM_TCP | CSUM_UDP)
/* Define this to activate partial TCP/UDP checksum offload. */
#undef	SF_PARTIAL_CSUM_SUPPORT

static struct sf_type sf_devs[] = {
	{ AD_VENDORID, AD_DEVICEID_STARFIRE, "Adaptec AIC-6915 10/100BaseTX",
	    AD_SUBSYSID_62011_REV0, "Adaptec ANA-62011 (rev 0) 10/100BaseTX" },
	{ AD_VENDORID, AD_DEVICEID_STARFIRE, "Adaptec AIC-6915 10/100BaseTX",
	    AD_SUBSYSID_62011_REV1, "Adaptec ANA-62011 (rev 1) 10/100BaseTX" },
	{ AD_VENDORID, AD_DEVICEID_STARFIRE, "Adaptec AIC-6915 10/100BaseTX",
	    AD_SUBSYSID_62022, "Adaptec ANA-62022 10/100BaseTX" },
	{ AD_VENDORID, AD_DEVICEID_STARFIRE, "Adaptec AIC-6915 10/100BaseTX",
	    AD_SUBSYSID_62044_REV0, "Adaptec ANA-62044 (rev 0) 10/100BaseTX" },
	{ AD_VENDORID, AD_DEVICEID_STARFIRE, "Adaptec AIC-6915 10/100BaseTX",
	    AD_SUBSYSID_62044_REV1, "Adaptec ANA-62044 (rev 1) 10/100BaseTX" },
	{ AD_VENDORID, AD_DEVICEID_STARFIRE, "Adaptec AIC-6915 10/100BaseTX",
	    AD_SUBSYSID_62020, "Adaptec ANA-62020 10/100BaseFX" },
	{ AD_VENDORID, AD_DEVICEID_STARFIRE, "Adaptec AIC-6915 10/100BaseTX",
	    AD_SUBSYSID_69011, "Adaptec ANA-69011 10/100BaseTX" },
};

static int sf_probe(device_t);
static int sf_attach(device_t);
static int sf_detach(device_t);
static int sf_shutdown(device_t);
static int sf_suspend(device_t);
static int sf_resume(device_t);
static void sf_intr(void *);
static void sf_tick(void *);
static void sf_stats_update(struct sf_softc *);
#ifndef __NO_STRICT_ALIGNMENT
static __inline void sf_fixup_rx(struct mbuf *);
#endif
static int sf_rxeof(struct sf_softc *);
static void sf_txeof(struct sf_softc *);
static int sf_encap(struct sf_softc *, struct mbuf **);
static void sf_start(struct ifnet *);
static void sf_start_locked(struct ifnet *);
static int sf_ioctl(struct ifnet *, u_long, caddr_t);
static void sf_download_fw(struct sf_softc *);
static void sf_init(void *);
static void sf_init_locked(struct sf_softc *);
static void sf_stop(struct sf_softc *);
static void sf_watchdog(struct sf_softc *);
static int sf_ifmedia_upd(struct ifnet *);
static void sf_ifmedia_sts(struct ifnet *, struct ifmediareq *);
static void sf_reset(struct sf_softc *);
static int sf_dma_alloc(struct sf_softc *);
static void sf_dma_free(struct sf_softc *);
static int sf_init_rx_ring(struct sf_softc *);
static void sf_init_tx_ring(struct sf_softc *);
static int sf_newbuf(struct sf_softc *, int);
static void sf_rxfilter(struct sf_softc *);
static int sf_setperf(struct sf_softc *, int, uint8_t *);
static int sf_sethash(struct sf_softc *, caddr_t, int);
#ifdef notdef
static int sf_setvlan(struct sf_softc *, int, uint32_t);
#endif

static uint8_t sf_read_eeprom(struct sf_softc *, int);

static int sf_miibus_readreg(device_t, int, int);
static int sf_miibus_writereg(device_t, int, int, int);
static void sf_miibus_statchg(device_t);
static void sf_link_task(void *, int);
#ifdef DEVICE_POLLING
static int sf_poll(struct ifnet *ifp, enum poll_cmd cmd, int count);
#endif

static uint32_t csr_read_4(struct sf_softc *, int);
static void csr_write_4(struct sf_softc *, int, uint32_t);
static void sf_txthresh_adjust(struct sf_softc *);
static int sf_sysctl_stats(SYSCTL_HANDLER_ARGS);
static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int, int);
static int sysctl_hw_sf_int_mod(SYSCTL_HANDLER_ARGS);

static device_method_t sf_methods[] = {
	/* Device interface */
	DEVMETHOD(device_probe,		sf_probe),
	DEVMETHOD(device_attach,	sf_attach),
	DEVMETHOD(device_detach,	sf_detach),
	DEVMETHOD(device_shutdown,	sf_shutdown),
	DEVMETHOD(device_suspend,	sf_suspend),
	DEVMETHOD(device_resume,	sf_resume),

	/* bus interface */
	DEVMETHOD(bus_print_child,	bus_generic_print_child),
	DEVMETHOD(bus_driver_added,	bus_generic_driver_added),

	/* MII interface */
	DEVMETHOD(miibus_readreg,	sf_miibus_readreg),
	DEVMETHOD(miibus_writereg,	sf_miibus_writereg),
	DEVMETHOD(miibus_statchg,	sf_miibus_statchg),

	{ NULL, NULL }
};

static driver_t sf_driver = {
	"sf",
	sf_methods,
	sizeof(struct sf_softc),
};

static devclass_t sf_devclass;

DRIVER_MODULE(sf, pci, sf_driver, sf_devclass, 0, 0);
DRIVER_MODULE(miibus, sf, miibus_driver, miibus_devclass, 0, 0);

#define SF_SETBIT(sc, reg, x)	\
	csr_write_4(sc, reg, csr_read_4(sc, reg) | (x))

#define SF_CLRBIT(sc, reg, x)				\
	csr_write_4(sc, reg, csr_read_4(sc, reg) & ~(x))

static uint32_t
csr_read_4(struct sf_softc *sc, int reg)
{
	uint32_t		val;

	if (sc->sf_restype == SYS_RES_MEMORY)
		val = CSR_READ_4(sc, (reg + SF_RMAP_INTREG_BASE));
	else {
		CSR_WRITE_4(sc, SF_INDIRECTIO_ADDR, reg + SF_RMAP_INTREG_BASE);
		val = CSR_READ_4(sc, SF_INDIRECTIO_DATA);
	}

	return (val);
}

static uint8_t
sf_read_eeprom(struct sf_softc *sc, int reg)
{
	uint8_t		val;

	val = (csr_read_4(sc, SF_EEADDR_BASE +
	    (reg & 0xFFFFFFFC)) >> (8 * (reg & 3))) & 0xFF;

	return (val);
}

static void
csr_write_4(struct sf_softc *sc, int reg, uint32_t val)
{

	if (sc->sf_restype == SYS_RES_MEMORY)
		CSR_WRITE_4(sc, (reg + SF_RMAP_INTREG_BASE), val);
	else {
		CSR_WRITE_4(sc, SF_INDIRECTIO_ADDR, reg + SF_RMAP_INTREG_BASE);
		CSR_WRITE_4(sc, SF_INDIRECTIO_DATA, val);
	}
}

/*
 * Copy the address 'mac' into the perfect RX filter entry at
 * offset 'idx.' The perfect filter only has 16 entries so do
 * some sanity tests.
 */
static int
sf_setperf(struct sf_softc *sc, int idx, uint8_t *mac)
{

	if (idx < 0 || idx > SF_RXFILT_PERFECT_CNT)
		return (EINVAL);

	if (mac == NULL)
		return (EINVAL);

	csr_write_4(sc, SF_RXFILT_PERFECT_BASE +
	    (idx * SF_RXFILT_PERFECT_SKIP) + 0, mac[5] | (mac[4] << 8));
	csr_write_4(sc, SF_RXFILT_PERFECT_BASE +
	    (idx * SF_RXFILT_PERFECT_SKIP) + 4, mac[3] | (mac[2] << 8));
	csr_write_4(sc, SF_RXFILT_PERFECT_BASE +
	    (idx * SF_RXFILT_PERFECT_SKIP) + 8, mac[1] | (mac[0] << 8));

	return (0);
}

/*
 * Set the bit in the 512-bit hash table that corresponds to the
 * specified mac address 'mac.' If 'prio' is nonzero, update the
 * priority hash table instead of the filter hash table.
 */
static int
sf_sethash(struct sf_softc *sc, caddr_t	mac, int prio)
{
	uint32_t		h;

	if (mac == NULL)
		return (EINVAL);

	h = ether_crc32_be(mac, ETHER_ADDR_LEN) >> 23;

	if (prio) {
		SF_SETBIT(sc, SF_RXFILT_HASH_BASE + SF_RXFILT_HASH_PRIOOFF +
		    (SF_RXFILT_HASH_SKIP * (h >> 4)), (1 << (h & 0xF)));
	} else {
		SF_SETBIT(sc, SF_RXFILT_HASH_BASE + SF_RXFILT_HASH_ADDROFF +
		    (SF_RXFILT_HASH_SKIP * (h >> 4)), (1 << (h & 0xF)));
	}

	return (0);
}

#ifdef notdef
/*
 * Set a VLAN tag in the receive filter.
 */
static int
sf_setvlan(struct sf_softc *sc, int idx, uint32_t vlan)
{

	if (idx < 0 || idx >> SF_RXFILT_HASH_CNT)
		return (EINVAL);

	csr_write_4(sc, SF_RXFILT_HASH_BASE +
	    (idx * SF_RXFILT_HASH_SKIP) + SF_RXFILT_HASH_VLANOFF, vlan);

	return (0);
}
#endif

static int
sf_miibus_readreg(device_t dev, int phy, int reg)
{
	struct sf_softc		*sc;
	int			i;
	uint32_t		val = 0;

	sc = device_get_softc(dev);

	for (i = 0; i < SF_TIMEOUT; i++) {
		val = csr_read_4(sc, SF_PHY_REG(phy, reg));
		if ((val & SF_MII_DATAVALID) != 0)
			break;
	}

	if (i == SF_TIMEOUT)
		return (0);

	val &= SF_MII_DATAPORT;
	if (val == 0xffff)
		return (0);

	return (val);
}

static int
sf_miibus_writereg(device_t dev, int phy, int reg, int val)
{
	struct sf_softc		*sc;
	int			i;
	int			busy;

	sc = device_get_softc(dev);

	csr_write_4(sc, SF_PHY_REG(phy, reg), val);

	for (i = 0; i < SF_TIMEOUT; i++) {
		busy = csr_read_4(sc, SF_PHY_REG(phy, reg));
		if ((busy & SF_MII_BUSY) == 0)
			break;
	}

	return (0);
}

static void
sf_miibus_statchg(device_t dev)
{
	struct sf_softc		*sc;

	sc = device_get_softc(dev);
	taskqueue_enqueue(taskqueue_swi, &sc->sf_link_task);
}

static void
sf_link_task(void *arg, int pending)
{
	struct sf_softc		*sc;
	struct mii_data		*mii;
	struct ifnet		*ifp;
	uint32_t		val;

	sc = (struct sf_softc *)arg;

	SF_LOCK(sc);

	mii = device_get_softc(sc->sf_miibus);
	ifp = sc->sf_ifp;
	if (mii == NULL || ifp == NULL ||
	    (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) {
		SF_UNLOCK(sc);
		return;
	}

	if (mii->mii_media_status & IFM_ACTIVE) {
		if (IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE)
			sc->sf_link = 1;
	} else
		sc->sf_link = 0;

	val = csr_read_4(sc, SF_MACCFG_1);
	val &= ~SF_MACCFG1_FULLDUPLEX;
	val &= ~(SF_MACCFG1_RX_FLOWENB | SF_MACCFG1_TX_FLOWENB);
	if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) {
		val |= SF_MACCFG1_FULLDUPLEX;
		csr_write_4(sc, SF_BKTOBKIPG, SF_IPGT_FDX);
#ifdef notyet
		/* Configure flow-control bits. */
		if ((IFM_OPTIONS(sc->sc_mii->mii_media_active) &
		    IFM_ETH_RXPAUSE) != 0)
			val |= SF_MACCFG1_RX_FLOWENB;
		if ((IFM_OPTIONS(sc->sc_mii->mii_media_active) &
		    IFM_ETH_TXPAUSE) != 0)
			val |= SF_MACCFG1_TX_FLOWENB;
#endif
	} else
		csr_write_4(sc, SF_BKTOBKIPG, SF_IPGT_HDX);

	/* Make sure to reset MAC to take changes effect. */
	csr_write_4(sc, SF_MACCFG_1, val | SF_MACCFG1_SOFTRESET);
	DELAY(1000);
	csr_write_4(sc, SF_MACCFG_1, val);

	val = csr_read_4(sc, SF_TIMER_CTL);
	if (IFM_SUBTYPE(mii->mii_media_active) == IFM_100_TX)
		val |= SF_TIMER_TIMES_TEN;
	else
		val &= ~SF_TIMER_TIMES_TEN;
	csr_write_4(sc, SF_TIMER_CTL, val);

	SF_UNLOCK(sc);
}

static void
sf_rxfilter(struct sf_softc *sc)
{
	struct ifnet		*ifp;
	int			i;
	struct ifmultiaddr	*ifma;
	uint8_t			dummy[ETHER_ADDR_LEN] = { 0, 0, 0, 0, 0, 0 };
	uint32_t		rxfilt;

	ifp = sc->sf_ifp;

	/* First zot all the existing filters. */
	for (i = 1; i < SF_RXFILT_PERFECT_CNT; i++)
		sf_setperf(sc, i, dummy);
	for (i = SF_RXFILT_HASH_BASE; i < (SF_RXFILT_HASH_MAX + 1);
	    i += sizeof(uint32_t))
		csr_write_4(sc, i, 0);

	rxfilt = csr_read_4(sc, SF_RXFILT);
	rxfilt &= ~(SF_RXFILT_PROMISC | SF_RXFILT_ALLMULTI | SF_RXFILT_BROAD);
	if ((ifp->if_flags & IFF_BROADCAST) != 0)
		rxfilt |= SF_RXFILT_BROAD;
	if ((ifp->if_flags & IFF_ALLMULTI) != 0 ||
	    (ifp->if_flags & IFF_PROMISC) != 0) {
		if ((ifp->if_flags & IFF_PROMISC) != 0)
			rxfilt |= SF_RXFILT_PROMISC;
		if ((ifp->if_flags & IFF_ALLMULTI) != 0)
			rxfilt |= SF_RXFILT_ALLMULTI;
		goto done;
	}

	/* Now program new ones. */
	i = 1;
	IF_ADDR_LOCK(ifp);
	TAILQ_FOREACH_REVERSE(ifma, &ifp->if_multiaddrs, ifmultihead,
	    ifma_link) {
		if (ifma->ifma_addr->sa_family != AF_LINK)
			continue;
		/*
		 * Program the first 15 multicast groups
		 * into the perfect filter. For all others,
		 * use the hash table.
		 */
		if (i < SF_RXFILT_PERFECT_CNT) {
			sf_setperf(sc, i,
			    LLADDR((struct sockaddr_dl *)ifma->ifma_addr));
			i++;
			continue;
		}

		sf_sethash(sc,
		    LLADDR((struct sockaddr_dl *)ifma->ifma_addr), 0);
	}
	IF_ADDR_UNLOCK(ifp);

done:
	csr_write_4(sc, SF_RXFILT, rxfilt);
}

/*
 * Set media options.
 */
static int
sf_ifmedia_upd(struct ifnet *ifp)
{
	struct sf_softc		*sc;
	struct mii_data		*mii;
	int			error;

	sc = ifp->if_softc;
	SF_LOCK(sc);

	mii = device_get_softc(sc->sf_miibus);
	if (mii->mii_instance) {
		struct mii_softc        *miisc;
		LIST_FOREACH(miisc, &mii->mii_phys, mii_list)
			mii_phy_reset(miisc);
	}
	error = mii_mediachg(mii);
	SF_UNLOCK(sc);

	return (error);
}

/*
 * Report current media status.
 */
static void
sf_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr)
{
	struct sf_softc		*sc;
	struct mii_data		*mii;

	sc = ifp->if_softc;
	SF_LOCK(sc);
	mii = device_get_softc(sc->sf_miibus);

	mii_pollstat(mii);
	ifmr->ifm_active = mii->mii_media_active;
	ifmr->ifm_status = mii->mii_media_status;
	SF_UNLOCK(sc);
}

static int
sf_ioctl(struct ifnet *ifp, u_long command, caddr_t data)
{
	struct sf_softc		*sc;
	struct ifreq		*ifr;
	struct mii_data		*mii;
	int			error, mask;

	sc = ifp->if_softc;
	ifr = (struct ifreq *)data;
	error = 0;

	switch (command) {
	case SIOCSIFFLAGS:
		SF_LOCK(sc);
		if (ifp->if_flags & IFF_UP) {
			if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) {
				if ((ifp->if_flags ^ sc->sf_if_flags) &
				    (IFF_PROMISC | IFF_ALLMULTI))
					sf_rxfilter(sc);
			} else {
				if (sc->sf_detach == 0)
					sf_init_locked(sc);
			}
		} else {
			if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0)
				sf_stop(sc);
		}
		sc->sf_if_flags = ifp->if_flags;
		SF_UNLOCK(sc);
		break;
	case SIOCADDMULTI:
	case SIOCDELMULTI:
		SF_LOCK(sc);
		sf_rxfilter(sc);
		SF_UNLOCK(sc);
		break;
	case SIOCGIFMEDIA:
	case SIOCSIFMEDIA:
		mii = device_get_softc(sc->sf_miibus);
		error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command);
		break;
	case SIOCSIFCAP:
		mask = ifr->ifr_reqcap ^ ifp->if_capenable;
#ifdef DEVICE_POLLING
		if ((mask & IFCAP_POLLING) != 0) {
			if ((ifr->ifr_reqcap & IFCAP_POLLING) != 0) {
				error = ether_poll_register(sf_poll, ifp);
				if (error != 0)
					break;
				SF_LOCK(sc);
				/* Disable interrupts. */
				csr_write_4(sc, SF_IMR, 0);
				ifp->if_capenable |= IFCAP_POLLING;
				SF_UNLOCK(sc);
			} else {
				error = ether_poll_deregister(ifp);
				/* Enable interrupts. */
				SF_LOCK(sc);
				csr_write_4(sc, SF_IMR, SF_INTRS);
				ifp->if_capenable &= ~IFCAP_POLLING;
				SF_UNLOCK(sc);
			}
		}
#endif /* DEVICE_POLLING */
		if ((mask & IFCAP_TXCSUM) != 0) {
			if ((IFCAP_TXCSUM & ifp->if_capabilities) != 0) {
				SF_LOCK(sc);
				ifp->if_capenable ^= IFCAP_TXCSUM;
				if ((IFCAP_TXCSUM & ifp->if_capenable) != 0) {
					ifp->if_hwassist |= SF_CSUM_FEATURES;
					SF_SETBIT(sc, SF_GEN_ETH_CTL,
					    SF_ETHCTL_TXGFP_ENB);
				} else {
					ifp->if_hwassist &= ~SF_CSUM_FEATURES;
					SF_CLRBIT(sc, SF_GEN_ETH_CTL,
					    SF_ETHCTL_TXGFP_ENB);
				}
				SF_UNLOCK(sc);
			}
		}
		if ((mask & IFCAP_RXCSUM) != 0) {
			if ((IFCAP_RXCSUM & ifp->if_capabilities) != 0) {
				SF_LOCK(sc);
				ifp->if_capenable ^= IFCAP_RXCSUM;
				if ((IFCAP_RXCSUM & ifp->if_capenable) != 0)
					SF_SETBIT(sc, SF_GEN_ETH_CTL,
					    SF_ETHCTL_RXGFP_ENB);
				else
					SF_CLRBIT(sc, SF_GEN_ETH_CTL,
					    SF_ETHCTL_RXGFP_ENB);
				SF_UNLOCK(sc);
			}
		}
		break;
	default:
		error = ether_ioctl(ifp, command, data);
		break;
	}

	return (error);
}

static void
sf_reset(struct sf_softc *sc)
{
	int		i;

	csr_write_4(sc, SF_GEN_ETH_CTL, 0);
	SF_SETBIT(sc, SF_MACCFG_1, SF_MACCFG1_SOFTRESET);
	DELAY(1000);
	SF_CLRBIT(sc, SF_MACCFG_1, SF_MACCFG1_SOFTRESET);

	SF_SETBIT(sc, SF_PCI_DEVCFG, SF_PCIDEVCFG_RESET);

	for (i = 0; i < SF_TIMEOUT; i++) {
		DELAY(10);
		if (!(csr_read_4(sc, SF_PCI_DEVCFG) & SF_PCIDEVCFG_RESET))
			break;
	}

	if (i == SF_TIMEOUT)
		device_printf(sc->sf_dev, "reset never completed!\n");

	/* Wait a little while for the chip to get its brains in order. */
	DELAY(1000);
}

/*
 * Probe for an Adaptec AIC-6915 chip. Check the PCI vendor and device
 * IDs against our list and return a device name if we find a match.
 * We also check the subsystem ID so that we can identify exactly which
 * NIC has been found, if possible.
 */
static int
sf_probe(device_t dev)
{
	struct sf_type		*t;
	uint16_t		vid;
	uint16_t		did;
	uint16_t		sdid;
	int			i;

	vid = pci_get_vendor(dev);
	did = pci_get_device(dev);
	sdid = pci_get_subdevice(dev);

	t = sf_devs;
	for (i = 0; i < sizeof(sf_devs) / sizeof(sf_devs[0]); i++, t++) {
		if (vid == t->sf_vid && did == t->sf_did) {
			if (sdid == t->sf_sdid) {
				device_set_desc(dev, t->sf_sname);
				return (BUS_PROBE_DEFAULT);
			}
		}
	}

	if (vid == AD_VENDORID && did == AD_DEVICEID_STARFIRE) {
		/* unkown subdevice */
		device_set_desc(dev, sf_devs[0].sf_name);
		return (BUS_PROBE_DEFAULT);
	}

	return (ENXIO);
}

/*
 * Attach the interface. Allocate softc structures, do ifmedia
 * setup and ethernet/BPF attach.
 */
static int
sf_attach(device_t dev)
{
	int			i;
	struct sf_softc		*sc;
	struct ifnet		*ifp;
	uint32_t		reg;
	int			rid, error = 0;
	uint8_t			eaddr[ETHER_ADDR_LEN];

	sc = device_get_softc(dev);
	sc->sf_dev = dev;

	mtx_init(&sc->sf_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK,
	    MTX_DEF);
	callout_init_mtx(&sc->sf_co, &sc->sf_mtx, 0);
	TASK_INIT(&sc->sf_link_task, 0, sf_link_task, sc);

	/*
	 * Map control/status registers.
	 */
	pci_enable_busmaster(dev);

	/*
	 * Prefer memory space register mapping over I/O space as the
	 * hardware requires lots of register access to get various
	 * producer/consumer index during Tx/Rx operation. However this
	 * requires large memory space(512K) to map the entire register
	 * space.
	 */
	sc->sf_rid = PCIR_BAR(0);
	sc->sf_restype = SYS_RES_MEMORY;
	sc->sf_res = bus_alloc_resource_any(dev, sc->sf_restype, &sc->sf_rid,
	    RF_ACTIVE);
	if (sc->sf_res == NULL) {
		reg = pci_read_config(dev, PCIR_BAR(0), 4);
		if ((reg & PCIM_BAR_MEM_64) == PCIM_BAR_MEM_64)
			sc->sf_rid = PCIR_BAR(2);
		else
			sc->sf_rid = PCIR_BAR(1);
		sc->sf_restype = SYS_RES_IOPORT;
		sc->sf_res = bus_alloc_resource_any(dev, sc->sf_restype,
		    &sc->sf_rid, RF_ACTIVE);
		if (sc->sf_res == NULL) {
			device_printf(dev, "couldn't allocate resources\n");
			mtx_destroy(&sc->sf_mtx);
			return (ENXIO);
		}
	}
	if (bootverbose)
		device_printf(dev, "using %s space register mapping\n",
		    sc->sf_restype == SYS_RES_MEMORY ? "memory" : "I/O");

	reg = pci_read_config(dev, PCIR_CACHELNSZ, 1);
	if (reg == 0) {
		/*
		 * If cache line size is 0, MWI is not used at all, so set
		 * reasonable default. AIC-6915 supports 0, 4, 8, 16, 32
		 * and 64.
		 */
		reg = 16;
		device_printf(dev, "setting PCI cache line size to %u\n", reg);
		pci_write_config(dev, PCIR_CACHELNSZ, reg, 1);
	} else {
		if (bootverbose)
			device_printf(dev, "PCI cache line size : %u\n", reg);
	}
	/* Enable MWI. */
	reg = pci_read_config(dev, PCIR_COMMAND, 2);
	reg |= PCIM_CMD_MWRICEN;
	pci_write_config(dev, PCIR_COMMAND, reg, 2);

	/* Allocate interrupt. */
	rid = 0;
	sc->sf_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
	    RF_SHAREABLE | RF_ACTIVE);

	if (sc->sf_irq == NULL) {
		device_printf(dev, "couldn't map interrupt\n");
		error = ENXIO;
		goto fail;
	}

	SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
	    OID_AUTO, "stats", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
	    sf_sysctl_stats, "I", "Statistics");

	SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
		SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
		OID_AUTO, "int_mod", CTLTYPE_INT | CTLFLAG_RW,
		&sc->sf_int_mod, 0, sysctl_hw_sf_int_mod, "I",
		"sf interrupt moderation");
	/* Pull in device tunables. */
	sc->sf_int_mod = SF_IM_DEFAULT;
	error = resource_int_value(device_get_name(dev), device_get_unit(dev),
	    "int_mod", &sc->sf_int_mod);
	if (error == 0) {
		if (sc->sf_int_mod < SF_IM_MIN ||
		    sc->sf_int_mod > SF_IM_MAX) {
			device_printf(dev, "int_mod value out of range; "
			    "using default: %d\n", SF_IM_DEFAULT);
			sc->sf_int_mod = SF_IM_DEFAULT;
		}
	}

	/* Reset the adapter. */
	sf_reset(sc);

	/*
	 * Get station address from the EEPROM.
	 */
	for (i = 0; i < ETHER_ADDR_LEN; i++)
		eaddr[i] =
		    sf_read_eeprom(sc, SF_EE_NODEADDR + ETHER_ADDR_LEN - i);

	/* Allocate DMA resources. */
	if (sf_dma_alloc(sc) != 0) {
		error = ENOSPC;
		goto fail;
	}

	sc->sf_txthresh = SF_MIN_TX_THRESHOLD;

	ifp = sc->sf_ifp = if_alloc(IFT_ETHER);
	if (ifp == NULL) {
		device_printf(dev, "can not allocate ifnet structure\n");
		error = ENOSPC;
		goto fail;
	}

	/* Do MII setup. */
	if (mii_phy_probe(dev, &sc->sf_miibus, sf_ifmedia_upd,
	    sf_ifmedia_sts)) {
		device_printf(dev, "MII without any phy!\n");
		error = ENXIO;
		goto fail;
	}

	ifp->if_softc = sc;
	if_initname(ifp, device_get_name(dev), device_get_unit(dev));
	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
	ifp->if_ioctl = sf_ioctl;
	ifp->if_start = sf_start;
	ifp->if_init = sf_init;
	IFQ_SET_MAXLEN(&ifp->if_snd, SF_TX_DLIST_CNT - 1);
	ifp->if_snd.ifq_drv_maxlen = SF_TX_DLIST_CNT - 1;
	IFQ_SET_READY(&ifp->if_snd);
	/*
	 * With the help of firmware, AIC-6915 supports
	 * Tx/Rx TCP/UDP checksum offload.
	 */
	ifp->if_hwassist = SF_CSUM_FEATURES;
	ifp->if_capabilities = IFCAP_HWCSUM;

	/*
	 * Call MI attach routine.
	 */
	ether_ifattach(ifp, eaddr);

	/* VLAN capability setup. */
	ifp->if_capabilities |= IFCAP_VLAN_MTU;
	ifp->if_capenable = ifp->if_capabilities;
#ifdef DEVICE_POLLING
	ifp->if_capabilities |= IFCAP_POLLING;
#endif
	/*
	 * Tell the upper layer(s) we support long frames.
	 * Must appear after the call to ether_ifattach() because
	 * ether_ifattach() sets ifi_hdrlen to the default value.
	 */
	ifp->if_data.ifi_hdrlen = sizeof(struct ether_vlan_header);

	/* Hook interrupt last to avoid having to lock softc */
	error = bus_setup_intr(dev, sc->sf_irq, INTR_TYPE_NET | INTR_MPSAFE,
	    NULL, sf_intr, sc, &sc->sf_intrhand);

	if (error) {
		device_printf(dev, "couldn't set up irq\n");
		ether_ifdetach(ifp);
		goto fail;
	}

fail:
	if (error)
		sf_detach(dev);

	return (error);
}

/*
 * Shutdown hardware and free up resources. This can be called any
 * time after the mutex has been initialized. It is called in both
 * the error case in attach and the normal detach case so it needs
 * to be careful about only freeing resources that have actually been
 * allocated.
 */
static int
sf_detach(device_t dev)
{
	struct sf_softc		*sc;
	struct ifnet		*ifp;

	sc = device_get_softc(dev);
	ifp = sc->sf_ifp;

#ifdef DEVICE_POLLING
	if (ifp != NULL && ifp->if_capenable & IFCAP_POLLING)
		ether_poll_deregister(ifp);
#endif

	/* These should only be active if attach succeeded */
	if (device_is_attached(dev)) {
		SF_LOCK(sc);
		sc->sf_detach = 1;
		sf_stop(sc);
		SF_UNLOCK(sc);
		callout_drain(&sc->sf_co);
		taskqueue_drain(taskqueue_swi, &sc->sf_link_task);
		if (ifp != NULL)
			ether_ifdetach(ifp);
	}
	if (sc->sf_miibus) {
		device_delete_child(dev, sc->sf_miibus);
		sc->sf_miibus = NULL;
	}
	bus_generic_detach(dev);

	if (sc->sf_intrhand != NULL)
		bus_teardown_intr(dev, sc->sf_irq, sc->sf_intrhand);
	if (sc->sf_irq != NULL)
		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sf_irq);
	if (sc->sf_res != NULL)
		bus_release_resource(dev, sc->sf_restype, sc->sf_rid,
		    sc->sf_res);

	sf_dma_free(sc);
	if (ifp != NULL)
		if_free(ifp);

	mtx_destroy(&sc->sf_mtx);

	return (0);
}

struct sf_dmamap_arg {
	bus_addr_t		sf_busaddr;
};

static void
sf_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error)
{
	struct sf_dmamap_arg	*ctx;

	if (error != 0)
		return;
	ctx = arg;
	ctx->sf_busaddr = segs[0].ds_addr;
}

static int
sf_dma_alloc(struct sf_softc *sc)
{
	struct sf_dmamap_arg	ctx;
	struct sf_txdesc	*txd;
	struct sf_rxdesc	*rxd;
	bus_addr_t		lowaddr;
	bus_addr_t		rx_ring_end, rx_cring_end;
	bus_addr_t		tx_ring_end, tx_cring_end;
	int			error, i;

	lowaddr = BUS_SPACE_MAXADDR;

again:
	/* Create parent DMA tag. */
	error = bus_dma_tag_create(
	    bus_get_dma_tag(sc->sf_dev),	/* parent */
	    1, 0,			/* alignment, boundary */
	    lowaddr,			/* lowaddr */
	    BUS_SPACE_MAXADDR,		/* highaddr */
	    NULL, NULL,			/* filter, filterarg */
	    BUS_SPACE_MAXSIZE_32BIT,	/* maxsize */
	    0,				/* nsegments */
	    BUS_SPACE_MAXSIZE_32BIT,	/* maxsegsize */
	    0,				/* flags */
	    NULL, NULL,			/* lockfunc, lockarg */
	    &sc->sf_cdata.sf_parent_tag);
	if (error != 0) {
		device_printf(sc->sf_dev, "failed to create parent DMA tag\n");
		goto fail;
	}
	/* Create tag for Tx ring. */
	error = bus_dma_tag_create(sc->sf_cdata.sf_parent_tag,/* parent */
	    SF_RING_ALIGN, 0, 		/* alignment, boundary */
	    BUS_SPACE_MAXADDR,		/* lowaddr */
	    BUS_SPACE_MAXADDR,		/* highaddr */
	    NULL, NULL,			/* filter, filterarg */
	    SF_TX_DLIST_SIZE,		/* maxsize */
	    1,				/* nsegments */
	    SF_TX_DLIST_SIZE,		/* maxsegsize */
	    0,				/* flags */
	    NULL, NULL,			/* lockfunc, lockarg */
	    &sc->sf_cdata.sf_tx_ring_tag);
	if (error != 0) {
		device_printf(sc->sf_dev, "failed to create Tx ring DMA tag\n");
		goto fail;
	}

	/* Create tag for Tx completion ring. */
	error = bus_dma_tag_create(sc->sf_cdata.sf_parent_tag,/* parent */
	    SF_RING_ALIGN, 0, 		/* alignment, boundary */
	    BUS_SPACE_MAXADDR,		/* lowaddr */
	    BUS_SPACE_MAXADDR,		/* highaddr */
	    NULL, NULL,			/* filter, filterarg */
	    SF_TX_CLIST_SIZE,		/* maxsize */
	    1,				/* nsegments */
	    SF_TX_CLIST_SIZE,		/* maxsegsize */
	    0,				/* flags */
	    NULL, NULL,			/* lockfunc, lockarg */
	    &sc->sf_cdata.sf_tx_cring_tag);
	if (error != 0) {
		device_printf(sc->sf_dev,
		    "failed to create Tx completion ring DMA tag\n");
		goto fail;
	}

	/* Create tag for Rx ring. */
	error = bus_dma_tag_create(sc->sf_cdata.sf_parent_tag,/* parent */
	    SF_RING_ALIGN, 0,		/* alignment, boundary */
	    BUS_SPACE_MAXADDR,		/* lowaddr */
	    BUS_SPACE_MAXADDR,		/* highaddr */
	    NULL, NULL,			/* filter, filterarg */
	    SF_RX_DLIST_SIZE,		/* maxsize */
	    1,				/* nsegments */
	    SF_RX_DLIST_SIZE,		/* maxsegsize */
	    0,				/* flags */
	    NULL, NULL,			/* lockfunc, lockarg */
	    &sc->sf_cdata.sf_rx_ring_tag);
	if (error != 0) {
		device_printf(sc->sf_dev,
		    "failed to create Rx ring DMA tag\n");
		goto fail;
	}

	/* Create tag for Rx completion ring. */
	error = bus_dma_tag_create(sc->sf_cdata.sf_parent_tag,/* parent */
	    SF_RING_ALIGN, 0,		/* alignment, boundary */
	    BUS_SPACE_MAXADDR,		/* lowaddr */
	    BUS_SPACE_MAXADDR,		/* highaddr */
	    NULL, NULL,			/* filter, filterarg */
	    SF_RX_CLIST_SIZE,		/* maxsize */
	    1,				/* nsegments */
	    SF_RX_CLIST_SIZE,		/* maxsegsize */
	    0,				/* flags */
	    NULL, NULL,			/* lockfunc, lockarg */
	    &sc->sf_cdata.sf_rx_cring_tag);
	if (error != 0) {
		device_printf(sc->sf_dev,
		    "failed to create Rx completion ring DMA tag\n");
		goto fail;
	}

	/* Create tag for Tx buffers. */
	error = bus_dma_tag_create(sc->sf_cdata.sf_parent_tag,/* parent */
	    1, 0,			/* alignment, boundary */
	    BUS_SPACE_MAXADDR,		/* lowaddr */
	    BUS_SPACE_MAXADDR,		/* highaddr */
	    NULL, NULL,			/* filter, filterarg */
	    MCLBYTES * SF_MAXTXSEGS,	/* maxsize */
	    SF_MAXTXSEGS,		/* nsegments */
	    MCLBYTES,			/* maxsegsize */
	    0,				/* flags */
	    NULL, NULL,			/* lockfunc, lockarg */
	    &sc->sf_cdata.sf_tx_tag);
	if (error != 0) {
		device_printf(sc->sf_dev, "failed to create Tx DMA tag\n");
		goto fail;
	}

	/* Create tag for Rx buffers. */
	error = bus_dma_tag_create(sc->sf_cdata.sf_parent_tag,/* parent */
	    SF_RX_ALIGN, 0,		/* alignment, boundary */
	    BUS_SPACE_MAXADDR,		/* lowaddr */
	    BUS_SPACE_MAXADDR,		/* highaddr */
	    NULL, NULL,			/* filter, filterarg */
	    MCLBYTES,			/* maxsize */
	    1,				/* nsegments */
	    MCLBYTES,			/* maxsegsize */
	    0,				/* flags */
	    NULL, NULL,			/* lockfunc, lockarg */
	    &sc->sf_cdata.sf_rx_tag);
	if (error != 0) {
		device_printf(sc->sf_dev, "failed to create Rx DMA tag\n");
		goto fail;
	}

	/* Allocate DMA'able memory and load the DMA map for Tx ring. */
	error = bus_dmamem_alloc(sc->sf_cdata.sf_tx_ring_tag,
	    (void **)&sc->sf_rdata.sf_tx_ring, BUS_DMA_WAITOK |
	    BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc->sf_cdata.sf_tx_ring_map);
	if (error != 0) {
		device_printf(sc->sf_dev,
		    "failed to allocate DMA'able memory for Tx ring\n");
		goto fail;
	}

	ctx.sf_busaddr = 0;
	error = bus_dmamap_load(sc->sf_cdata.sf_tx_ring_tag,
	    sc->sf_cdata.sf_tx_ring_map, sc->sf_rdata.sf_tx_ring,
	    SF_TX_DLIST_SIZE, sf_dmamap_cb, &ctx, 0);
	if (error != 0 || ctx.sf_busaddr == 0) {
		device_printf(sc->sf_dev,
		    "failed to load DMA'able memory for Tx ring\n");
		goto fail;
	}
	sc->sf_rdata.sf_tx_ring_paddr = ctx.sf_busaddr;

	/*
	 * Allocate DMA'able memory and load the DMA map for Tx completion ring.
	 */
	error = bus_dmamem_alloc(sc->sf_cdata.sf_tx_cring_tag,
	    (void **)&sc->sf_rdata.sf_tx_cring, BUS_DMA_WAITOK |
	    BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc->sf_cdata.sf_tx_cring_map);
	if (error != 0) {
		device_printf(sc->sf_dev,
		    "failed to allocate DMA'able memory for "
		    "Tx completion ring\n");
		goto fail;
	}

	ctx.sf_busaddr = 0;
	error = bus_dmamap_load(sc->sf_cdata.sf_tx_cring_tag,
	    sc->sf_cdata.sf_tx_cring_map, sc->sf_rdata.sf_tx_cring,
	    SF_TX_CLIST_SIZE, sf_dmamap_cb, &ctx, 0);
	if (error != 0 || ctx.sf_busaddr == 0) {
		device_printf(sc->sf_dev,
		    "failed to load DMA'able memory for Tx completion ring\n");
		goto fail;
	}
	sc->sf_rdata.sf_tx_cring_paddr = ctx.sf_busaddr;

	/* Allocate DMA'able memory and load the DMA map for Rx ring. */
	error = bus_dmamem_alloc(sc->sf_cdata.sf_rx_ring_tag,
	    (void **)&sc->sf_rdata.sf_rx_ring, BUS_DMA_WAITOK |
	    BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc->sf_cdata.sf_rx_ring_map);
	if (error != 0) {
		device_printf(sc->sf_dev,
		    "failed to allocate DMA'able memory for Rx ring\n");
		goto fail;
	}

	ctx.sf_busaddr = 0;
	error = bus_dmamap_load(sc->sf_cdata.sf_rx_ring_tag,
	    sc->sf_cdata.sf_rx_ring_map, sc->sf_rdata.sf_rx_ring,
	    SF_RX_DLIST_SIZE, sf_dmamap_cb, &ctx, 0);
	if (error != 0 || ctx.sf_busaddr == 0) {
		device_printf(sc->sf_dev,
		    "failed to load DMA'able memory for Rx ring\n");
		goto fail;
	}
	sc->sf_rdata.sf_rx_ring_paddr = ctx.sf_busaddr;

	/*
	 * Allocate DMA'able memory and load the DMA map for Rx completion ring.
	 */
	error = bus_dmamem_alloc(sc->sf_cdata.sf_rx_cring_tag,
	    (void **)&sc->sf_rdata.sf_rx_cring, BUS_DMA_WAITOK |
	    BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc->sf_cdata.sf_rx_cring_map);
	if (error != 0) {
		device_printf(sc->sf_dev,
		    "failed to allocate DMA'able memory for "
		    "Rx completion ring\n");
		goto fail;
	}

	ctx.sf_busaddr = 0;
	error = bus_dmamap_load(sc->sf_cdata.sf_rx_cring_tag,
	    sc->sf_cdata.sf_rx_cring_map, sc->sf_rdata.sf_rx_cring,
	    SF_RX_CLIST_SIZE, sf_dmamap_cb, &ctx, 0);
	if (error != 0 || ctx.sf_busaddr == 0) {
		device_printf(sc->sf_dev,
		    "failed to load DMA'able memory for Rx completion ring\n");
		goto fail;
	}
	sc->sf_rdata.sf_rx_cring_paddr = ctx.sf_busaddr;

	/*
	 * Tx desciptor ring and Tx completion ring should be addressed in
	 * the same 4GB space. The same rule applys to Rx ring and Rx
	 * completion ring. Unfortunately there is no way to specify this
	 * boundary restriction with bus_dma(9). So just try to allocate
	 * without the restriction and check the restriction was satisfied.
	 * If not, fall back to 32bit dma addressing mode which always
	 * guarantees the restriction.
	 */
	tx_ring_end = sc->sf_rdata.sf_tx_ring_paddr + SF_TX_DLIST_SIZE;
	tx_cring_end = sc->sf_rdata.sf_tx_cring_paddr + SF_TX_CLIST_SIZE;
	rx_ring_end = sc->sf_rdata.sf_rx_ring_paddr + SF_RX_DLIST_SIZE;
	rx_cring_end = sc->sf_rdata.sf_rx_cring_paddr + SF_RX_CLIST_SIZE;
	if ((SF_ADDR_HI(sc->sf_rdata.sf_tx_ring_paddr) !=
	    SF_ADDR_HI(tx_cring_end)) ||
	    (SF_ADDR_HI(sc->sf_rdata.sf_tx_cring_paddr) !=
	    SF_ADDR_HI(tx_ring_end)) ||
	    (SF_ADDR_HI(sc->sf_rdata.sf_rx_ring_paddr) !=
	    SF_ADDR_HI(rx_cring_end)) ||
	    (SF_ADDR_HI(sc->sf_rdata.sf_rx_cring_paddr) !=
	    SF_ADDR_HI(rx_ring_end))) {
		device_printf(sc->sf_dev,
		    "switching to 32bit DMA mode\n");
		sf_dma_free(sc);
		/* Limit DMA address space to 32bit and try again. */
		lowaddr = BUS_SPACE_MAXADDR_32BIT;
		goto again;
	}

	/* Create DMA maps for Tx buffers. */
	for (i = 0; i < SF_TX_DLIST_CNT; i++) {
		txd = &sc->sf_cdata.sf_txdesc[i];
		txd->tx_m = NULL;
		txd->ndesc = 0;
		txd->tx_dmamap = NULL;
		error = bus_dmamap_create(sc->sf_cdata.sf_tx_tag, 0,
		    &txd->tx_dmamap);
		if (error != 0) {
			device_printf(sc->sf_dev,
			    "failed to create Tx dmamap\n");
			goto fail;
		}
	}
	/* Create DMA maps for Rx buffers. */
	if ((error = bus_dmamap_create(sc->sf_cdata.sf_rx_tag, 0,
	    &sc->sf_cdata.sf_rx_sparemap)) != 0) {
		device_printf(sc->sf_dev,
		    "failed to create spare Rx dmamap\n");
		goto fail;
	}
	for (i = 0; i < SF_RX_DLIST_CNT; i++) {
		rxd = &sc->sf_cdata.sf_rxdesc[i];
		rxd->rx_m = NULL;
		rxd->rx_dmamap = NULL;
		error = bus_dmamap_create(sc->sf_cdata.sf_rx_tag, 0,
		    &rxd->rx_dmamap);
		if (error != 0) {
			device_printf(sc->sf_dev,
			    "failed to create Rx dmamap\n");
			goto fail;
		}
	}

fail:
	return (error);
}

static void
sf_dma_free(struct sf_softc *sc)
{
	struct sf_txdesc	*txd;
	struct sf_rxdesc	*rxd;
	int			i;

	/* Tx ring. */
	if (sc->sf_cdata.sf_tx_ring_tag) {
		if (sc->sf_cdata.sf_tx_ring_map)
			bus_dmamap_unload(sc->sf_cdata.sf_tx_ring_tag,
			    sc->sf_cdata.sf_tx_ring_map);
		if (sc->sf_cdata.sf_tx_ring_map &&
		    sc->sf_rdata.sf_tx_ring)
			bus_dmamem_free(sc->sf_cdata.sf_tx_ring_tag,
			    sc->sf_rdata.sf_tx_ring,
			    sc->sf_cdata.sf_tx_ring_map);
		sc->sf_rdata.sf_tx_ring = NULL;
		sc->sf_cdata.sf_tx_ring_map = NULL;
		bus_dma_tag_destroy(sc->sf_cdata.sf_tx_ring_tag);
		sc->sf_cdata.sf_tx_ring_tag = NULL;
	}
	/* Tx completion ring. */
	if (sc->sf_cdata.sf_tx_cring_tag) {
		if (sc->sf_cdata.sf_tx_cring_map)
			bus_dmamap_unload(sc->sf_cdata.sf_tx_cring_tag,
			    sc->sf_cdata.sf_tx_cring_map);
		if (sc->sf_cdata.sf_tx_cring_map &&
		    sc->sf_rdata.sf_tx_cring)
			bus_dmamem_free(sc->sf_cdata.sf_tx_cring_tag,
			    sc->sf_rdata.sf_tx_cring,
			    sc->sf_cdata.sf_tx_cring_map);
		sc->sf_rdata.sf_tx_cring = NULL;
		sc->sf_cdata.sf_tx_cring_map = NULL;
		bus_dma_tag_destroy(sc->sf_cdata.sf_tx_cring_tag);
		sc->sf_cdata.sf_tx_cring_tag = NULL;
	}
	/* Rx ring. */
	if (sc->sf_cdata.sf_rx_ring_tag) {
		if (sc->sf_cdata.sf_rx_ring_map)
			bus_dmamap_unload(sc->sf_cdata.sf_rx_ring_tag,
			    sc->sf_cdata.sf_rx_ring_map);
		if (sc->sf_cdata.sf_rx_ring_map &&
		    sc->sf_rdata.sf_rx_ring)
			bus_dmamem_free(sc->sf_cdata.sf_rx_ring_tag,
			    sc->sf_rdata.sf_rx_ring,
			    sc->sf_cdata.sf_rx_ring_map);
		sc->sf_rdata.sf_rx_ring = NULL;
		sc->sf_cdata.sf_rx_ring_map = NULL;
		bus_dma_tag_destroy(sc->sf_cdata.sf_rx_ring_tag);
		sc->sf_cdata.sf_rx_ring_tag = NULL;
	}
	/* Rx completion ring. */
	if (sc->sf_cdata.sf_rx_cring_tag) {
		if (sc->sf_cdata.sf_rx_cring_map)
			bus_dmamap_unload(sc->sf_cdata.sf_rx_cring_tag,
			    sc->sf_cdata.sf_rx_cring_map);
		if (sc->sf_cdata.sf_rx_cring_map &&
		    sc->sf_rdata.sf_rx_cring)
			bus_dmamem_free(sc->sf_cdata.sf_rx_cring_tag,
			    sc->sf_rdata.sf_rx_cring,
			    sc->sf_cdata.sf_rx_cring_map);
		sc->sf_rdata.sf_rx_cring = NULL;
		sc->sf_cdata.sf_rx_cring_map = NULL;
		bus_dma_tag_destroy(sc->sf_cdata.sf_rx_cring_tag);
		sc->sf_cdata.sf_rx_cring_tag = NULL;
	}
	/* Tx buffers. */
	if (sc->sf_cdata.sf_tx_tag) {
		for (i = 0; i < SF_TX_DLIST_CNT; i++) {
			txd = &sc->sf_cdata.sf_txdesc[i];
			if (txd->tx_dmamap) {
				bus_dmamap_destroy(sc->sf_cdata.sf_tx_tag,
				    txd->tx_dmamap);
				txd->tx_dmamap = NULL;
			}
		}
		bus_dma_tag_destroy(sc->sf_cdata.sf_tx_tag);
		sc->sf_cdata.sf_tx_tag = NULL;
	}
	/* Rx buffers. */
	if (sc->sf_cdata.sf_rx_tag) {
		for (i = 0; i < SF_RX_DLIST_CNT; i++) {
			rxd = &sc->sf_cdata.sf_rxdesc[i];
			if (rxd->rx_dmamap) {
				bus_dmamap_destroy(sc->sf_cdata.sf_rx_tag,
				    rxd->rx_dmamap);
				rxd->rx_dmamap = NULL;
			}
		}
		if (sc->sf_cdata.sf_rx_sparemap) {
			bus_dmamap_destroy(sc->sf_cdata.sf_rx_tag,
			    sc->sf_cdata.sf_rx_sparemap);
			sc->sf_cdata.sf_rx_sparemap = 0;
		}
		bus_dma_tag_destroy(sc->sf_cdata.sf_rx_tag);
		sc->sf_cdata.sf_rx_tag = NULL;
	}

	if (sc->sf_cdata.sf_parent_tag) {
		bus_dma_tag_destroy(sc->sf_cdata.sf_parent_tag);
		sc->sf_cdata.sf_parent_tag = NULL;
	}
}

static int
sf_init_rx_ring(struct sf_softc *sc)
{
	struct sf_ring_data	*rd;
	int			i;

	sc->sf_cdata.sf_rxc_cons = 0;

	rd = &sc->sf_rdata;
	bzero(rd->sf_rx_ring, SF_RX_DLIST_SIZE);
	bzero(rd->sf_rx_cring, SF_RX_CLIST_SIZE);

	for (i = 0; i < SF_RX_DLIST_CNT; i++) {
		if (sf_newbuf(sc, i) != 0)
			return (ENOBUFS);
	}

	bus_dmamap_sync(sc->sf_cdata.sf_rx_cring_tag,
	    sc->sf_cdata.sf_rx_cring_map,
	    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
	bus_dmamap_sync(sc->sf_cdata.sf_rx_ring_tag,
	    sc->sf_cdata.sf_rx_ring_map,
	    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

	return (0);
}

static void
sf_init_tx_ring(struct sf_softc *sc)
{
	struct sf_ring_data	*rd;
	int			i;

	sc->sf_cdata.sf_tx_prod = 0;
	sc->sf_cdata.sf_tx_cnt = 0;
	sc->sf_cdata.sf_txc_cons = 0;

	rd = &sc->sf_rdata;
	bzero(rd->sf_tx_ring, SF_TX_DLIST_SIZE);
	bzero(rd->sf_tx_cring, SF_TX_CLIST_SIZE);
	for (i = 0; i < SF_TX_DLIST_CNT; i++) {
		rd->sf_tx_ring[i].sf_tx_ctrl = htole32(SF_TX_DESC_ID);
		sc->sf_cdata.sf_txdesc[i].tx_m = NULL;
		sc->sf_cdata.sf_txdesc[i].ndesc = 0;
	}
	rd->sf_tx_ring[i].sf_tx_ctrl |= htole32(SF_TX_DESC_END);

	bus_dmamap_sync(sc->sf_cdata.sf_tx_ring_tag,
	    sc->sf_cdata.sf_tx_ring_map,
	    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
	bus_dmamap_sync(sc->sf_cdata.sf_tx_cring_tag,
	    sc->sf_cdata.sf_tx_cring_map,
	    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
}

/*
 * Initialize an RX descriptor and attach an MBUF cluster.
 */
static int
sf_newbuf(struct sf_softc *sc, int idx)
{
	struct sf_rx_rdesc	*desc;
	struct sf_rxdesc	*rxd;
	struct mbuf		*m;
	bus_dma_segment_t	segs[1];
	bus_dmamap_t		map;
	int			nsegs;

	m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
	if (m == NULL)
		return (ENOBUFS);
	m->m_len = m->m_pkthdr.len = MCLBYTES;
	m_adj(m, sizeof(uint32_t));

	if (bus_dmamap_load_mbuf_sg(sc->sf_cdata.sf_rx_tag,
	    sc->sf_cdata.sf_rx_sparemap, m, segs, &nsegs, 0) != 0) {
		m_freem(m);
		return (ENOBUFS);
	}
	KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs));

	rxd = &sc->sf_cdata.sf_rxdesc[idx];
	if (rxd->rx_m != NULL) {
		bus_dmamap_sync(sc->sf_cdata.sf_rx_tag, rxd->rx_dmamap,
		    BUS_DMASYNC_POSTREAD);
		bus_dmamap_unload(sc->sf_cdata.sf_rx_tag, rxd->rx_dmamap);
	}
	map = rxd->rx_dmamap;
	rxd->rx_dmamap = sc->sf_cdata.sf_rx_sparemap;
	sc->sf_cdata.sf_rx_sparemap = map;
	bus_dmamap_sync(sc->sf_cdata.sf_rx_tag, rxd->rx_dmamap,
	    BUS_DMASYNC_PREREAD);
	rxd->rx_m = m;
	desc = &sc->sf_rdata.sf_rx_ring[idx];
	desc->sf_addr = htole64(segs[0].ds_addr);

	return (0);
}

#ifndef __NO_STRICT_ALIGNMENT
static __inline void
sf_fixup_rx(struct mbuf *m)
{
        int			i;
        uint16_t		*src, *dst;

	src = mtod(m, uint16_t *);
	dst = src - 1;

	for (i = 0; i < (m->m_len / sizeof(uint16_t) + 1); i++)
		*dst++ = *src++;

	m->m_data -= ETHER_ALIGN;
}
#endif

/*
 * The starfire is programmed to use 'normal' mode for packet reception,
 * which means we use the consumer/producer model for both the buffer
 * descriptor queue and the completion descriptor queue. The only problem
 * with this is that it involves a lot of register accesses: we have to
 * read the RX completion consumer and producer indexes and the RX buffer
 * producer index, plus the RX completion consumer and RX buffer producer
 * indexes have to be updated. It would have been easier if Adaptec had
 * put each index in a separate register, especially given that the damn
 * NIC has a 512K register space.
 *
 * In spite of all the lovely features that Adaptec crammed into the 6915,
 * it is marred by one truly stupid design flaw, which is that receive
 * buffer addresses must be aligned on a longword boundary. This forces
 * the packet payload to be unaligned, which is suboptimal on the x86 and
 * completely unuseable on the Alpha. Our only recourse is to copy received
 * packets into properly aligned buffers before handing them off.
 */
static int
sf_rxeof(struct sf_softc *sc)
{
	struct mbuf		*m;
	struct ifnet		*ifp;
	struct sf_rxdesc	*rxd;
	struct sf_rx_rcdesc	*cur_cmp;
	int			cons, eidx, prog, rx_npkts;
	uint32_t		status, status2;

	SF_LOCK_ASSERT(sc);

	ifp = sc->sf_ifp;
	rx_npkts = 0;

	bus_dmamap_sync(sc->sf_cdata.sf_rx_ring_tag,
	    sc->sf_cdata.sf_rx_ring_map,
	    BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
	bus_dmamap_sync(sc->sf_cdata.sf_rx_cring_tag,
	    sc->sf_cdata.sf_rx_cring_map,
	    BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);

	/*
	 * To reduce register access, directly read Receive completion
	 * queue entry.
	 */
	eidx = 0;
	prog = 0;
	for (cons = sc->sf_cdata.sf_rxc_cons; ; SF_INC(cons, SF_RX_CLIST_CNT)) {
		cur_cmp = &sc->sf_rdata.sf_rx_cring[cons];
		status = le32toh(cur_cmp->sf_rx_status1);
		if (status == 0)
			break;
#ifdef DEVICE_POLLING
		if ((ifp->if_capenable & IFCAP_POLLING) != 0) {
			if (sc->rxcycles <= 0)
				break;
			sc->rxcycles--;
		}
#endif
		prog++;
		eidx = (status & SF_RX_CMPDESC_EIDX) >> 16;
		rxd = &sc->sf_cdata.sf_rxdesc[eidx];
		m = rxd->rx_m;

		/*
		 * Note, if_ipackets and if_ierrors counters
		 * are handled in sf_stats_update().
		 */
		if ((status & SF_RXSTAT1_OK) == 0) {
			cur_cmp->sf_rx_status1 = 0;
			continue;
		}

		if (sf_newbuf(sc, eidx) != 0) {
			ifp->if_iqdrops++;
			cur_cmp->sf_rx_status1 = 0;
			continue;
		}

		/* AIC-6915 supports TCP/UDP checksum offload. */
		if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) {
			status2 = le32toh(cur_cmp->sf_rx_status2);
			/*
			 * Sometimes AIC-6915 generates an interrupt to
			 * warn RxGFP stall with bad checksum bit set
			 * in status word. I'm not sure what conditioan
			 * triggers it but recevied packet's checksum
			 * was correct even though AIC-6915 does not
			 * agree on this. This may be an indication of
			 * firmware bug. To fix the issue, do not rely
			 * on bad checksum bit in status word and let
			 * upper layer verify integrity of received
			 * frame.
			 * Another nice feature of AIC-6915 is hardware
			 * assistance of checksum calculation by
			 * providing partial checksum value for received
			 * frame. The partial checksum value can be used
			 * to accelerate checksum computation for
			 * fragmented TCP/UDP packets. Upper network
			 * stack already takes advantage of the partial
			 * checksum value in IP reassembly stage. But
			 * I'm not sure the correctness of the partial
			 * hardware checksum assistance as frequent
			 * RxGFP stalls are seen on non-fragmented
			 * frames. Due to the nature of the complexity
			 * of checksum computation code in firmware it's
			 * possible to see another bug in RxGFP so
			 * ignore checksum assistance for fragmented
			 * frames. This can be changed in future.
			 */
			if ((status2 & SF_RXSTAT2_FRAG) == 0) {
				if ((status2 & (SF_RXSTAT2_TCP |
				    SF_RXSTAT2_UDP)) != 0) {
					if ((status2 & SF_RXSTAT2_CSUM_OK)) {
						m->m_pkthdr.csum_flags =
						    CSUM_DATA_VALID |
						    CSUM_PSEUDO_HDR;
						m->m_pkthdr.csum_data = 0xffff;
					}
				}
			}
#ifdef SF_PARTIAL_CSUM_SUPPORT
			else if ((status2 & SF_RXSTAT2_FRAG) != 0) {
				if ((status2 & (SF_RXSTAT2_TCP |
				    SF_RXSTAT2_UDP)) != 0) {
					if ((status2 & SF_RXSTAT2_PCSUM_OK)) {
						m->m_pkthdr.csum_flags =
						    CSUM_DATA_VALID;
						m->m_pkthdr.csum_data =
						    (status &
						    SF_RX_CMPDESC_CSUM2);
					}
				}
			}
#endif
		}

		m->m_pkthdr.len = m->m_len = status & SF_RX_CMPDESC_LEN;
#ifndef	__NO_STRICT_ALIGNMENT
		sf_fixup_rx(m);
#endif
		m->m_pkthdr.rcvif = ifp;

		SF_UNLOCK(sc);
		(*ifp->if_input)(ifp, m);
		SF_LOCK(sc);
		rx_npkts++;

		/* Clear completion status. */
		cur_cmp->sf_rx_status1 = 0;
	}

	if (prog > 0) {
		sc->sf_cdata.sf_rxc_cons = cons;
		bus_dmamap_sync(sc->sf_cdata.sf_rx_ring_tag,
		    sc->sf_cdata.sf_rx_ring_map,
		    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
		bus_dmamap_sync(sc->sf_cdata.sf_rx_cring_tag,
		    sc->sf_cdata.sf_rx_cring_map,
		    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

		/* Update Rx completion Q1 consumer index. */
		csr_write_4(sc, SF_CQ_CONSIDX,
		    (csr_read_4(sc, SF_CQ_CONSIDX) & ~SF_CQ_CONSIDX_RXQ1) |
		    (cons & SF_CQ_CONSIDX_RXQ1));
		/* Update Rx descriptor Q1 ptr. */
		csr_write_4(sc, SF_RXDQ_PTR_Q1,
		    (csr_read_4(sc, SF_RXDQ_PTR_Q1) & ~SF_RXDQ_PRODIDX) |
		    (eidx & SF_RXDQ_PRODIDX));
	}
	return (rx_npkts);
}

/*
 * Read the transmit status from the completion queue and release
 * mbufs. Note that the buffer descriptor index in the completion
 * descriptor is an offset from the start of the transmit buffer
 * descriptor list in bytes. This is important because the manual
 * gives the impression that it should match the producer/consumer
 * index, which is the offset in 8 byte blocks.
 */
static void
sf_txeof(struct sf_softc *sc)
{
	struct sf_txdesc	*txd;
	struct sf_tx_rcdesc	*cur_cmp;
	struct ifnet		*ifp;
	uint32_t		status;
	int			cons, idx, prod;

	SF_LOCK_ASSERT(sc);

	ifp = sc->sf_ifp;

	bus_dmamap_sync(sc->sf_cdata.sf_tx_cring_tag,
	    sc->sf_cdata.sf_tx_cring_map,
	    BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);

	cons = sc->sf_cdata.sf_txc_cons;
	prod = (csr_read_4(sc, SF_CQ_PRODIDX) & SF_TXDQ_PRODIDX_HIPRIO) >> 16;
	if (prod == cons)
		return;

	for (; cons != prod; SF_INC(cons, SF_TX_CLIST_CNT)) {
		cur_cmp = &sc->sf_rdata.sf_tx_cring[cons];
		status = le32toh(cur_cmp->sf_tx_status1);
		if (status == 0)
			break;
		switch (status & SF_TX_CMPDESC_TYPE) {
		case SF_TXCMPTYPE_TX:
			/* Tx complete entry. */
			break;
		case SF_TXCMPTYPE_DMA:
			/* DMA complete entry. */
			idx = status & SF_TX_CMPDESC_IDX;
			idx = idx / sizeof(struct sf_tx_rdesc);
			/*
			 * We don't need to check Tx status here.
			 * SF_ISR_TX_LOFIFO intr would handle this.
			 * Note, if_opackets, if_collisions and if_oerrors
			 * counters are handled in sf_stats_update().
			 */
			txd = &sc->sf_cdata.sf_txdesc[idx];
			if (txd->tx_m != NULL) {
				bus_dmamap_sync(sc->sf_cdata.sf_tx_tag,
				    txd->tx_dmamap,
				    BUS_DMASYNC_POSTWRITE);
				bus_dmamap_unload(sc->sf_cdata.sf_tx_tag,
				    txd->tx_dmamap);
				m_freem(txd->tx_m);
				txd->tx_m = NULL;
			}
			sc->sf_cdata.sf_tx_cnt -= txd->ndesc;
			KASSERT(sc->sf_cdata.sf_tx_cnt >= 0,
			    ("%s: Active Tx desc counter was garbled\n",
			    __func__));
			txd->ndesc = 0;
			ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
			break;
		default:
			/* It should not happen. */
			device_printf(sc->sf_dev,
			    "unknown Tx completion type : 0x%08x : %d : %d\n",
			    status, cons, prod);
			break;
		}
		cur_cmp->sf_tx_status1 = 0;
	}

	sc->sf_cdata.sf_txc_cons = cons;
	bus_dmamap_sync(sc->sf_cdata.sf_tx_cring_tag,
	    sc->sf_cdata.sf_tx_cring_map,
	    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

	if (sc->sf_cdata.sf_tx_cnt == 0)
		sc->sf_watchdog_timer = 0;

	/* Update Tx completion consumer index. */
	csr_write_4(sc, SF_CQ_CONSIDX,
	    (csr_read_4(sc, SF_CQ_CONSIDX) & 0xffff) |
	    ((cons << 16) & 0xffff0000));
}

static void
sf_txthresh_adjust(struct sf_softc *sc)
{
	uint32_t		txfctl;

	device_printf(sc->sf_dev, "Tx underrun -- ");
	if (sc->sf_txthresh < SF_MAX_TX_THRESHOLD) {
		txfctl = csr_read_4(sc, SF_TX_FRAMCTL);
		/* Increase Tx threshold 256 bytes. */
		sc->sf_txthresh += 16;
		if (sc->sf_txthresh > SF_MAX_TX_THRESHOLD)
			sc->sf_txthresh = SF_MAX_TX_THRESHOLD;
		txfctl &= ~SF_TXFRMCTL_TXTHRESH;
		txfctl |= sc->sf_txthresh;
		printf("increasing Tx threshold to %d bytes\n",
		    sc->sf_txthresh * SF_TX_THRESHOLD_UNIT);
		csr_write_4(sc, SF_TX_FRAMCTL, txfctl);
	} else
		printf("\n");
}

#ifdef DEVICE_POLLING
static int
sf_poll(struct ifnet *ifp, enum poll_cmd cmd, int count)
{
	struct sf_softc		*sc;
	uint32_t		status;
	int			rx_npkts;

	sc = ifp->if_softc;
	rx_npkts = 0;
	SF_LOCK(sc);

	if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) {
		SF_UNLOCK(sc);
		return (rx_npkts);
	}

	sc->rxcycles = count;
	rx_npkts = sf_rxeof(sc);
	sf_txeof(sc);
	if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd))
		sf_start_locked(ifp);

	if (cmd == POLL_AND_CHECK_STATUS) {
		/* Reading the ISR register clears all interrrupts. */
		status = csr_read_4(sc, SF_ISR);

		if ((status & SF_ISR_ABNORMALINTR) != 0) {
			if ((status & SF_ISR_STATSOFLOW) != 0)
				sf_stats_update(sc);
			else if ((status & SF_ISR_TX_LOFIFO) != 0)
				sf_txthresh_adjust(sc);
			else if ((status & SF_ISR_DMAERR) != 0) {
				device_printf(sc->sf_dev,
				    "DMA error, resetting\n");
				sf_init_locked(sc);
			} else if ((status & SF_ISR_NO_TX_CSUM) != 0) {
				sc->sf_statistics.sf_tx_gfp_stall++;
#ifdef	SF_GFP_DEBUG
				device_printf(sc->sf_dev,
				    "TxGFP is not responding!\n");
#endif
			} else if ((status & SF_ISR_RXGFP_NORESP) != 0) {
				sc->sf_statistics.sf_rx_gfp_stall++;
#ifdef	SF_GFP_DEBUG
				device_printf(sc->sf_dev,
				    "RxGFP is not responding!\n");
#endif
			}
		}
	}

	SF_UNLOCK(sc);
	return (rx_npkts);
}
#endif /* DEVICE_POLLING */

static void
sf_intr(void *arg)
{
	struct sf_softc		*sc;
	struct ifnet		*ifp;
	uint32_t		status;

	sc = (struct sf_softc *)arg;
	SF_LOCK(sc);

	if (sc->sf_suspended != 0)
		goto done_locked;

	/* Reading the ISR register clears all interrrupts. */
	status = csr_read_4(sc, SF_ISR);
	if (status == 0 || status == 0xffffffff ||
	    (status & SF_ISR_PCIINT_ASSERTED) == 0)
		goto done_locked;

	ifp = sc->sf_ifp;
#ifdef DEVICE_POLLING
	if ((ifp->if_capenable & IFCAP_POLLING) != 0)
		goto done_locked;
#endif
	if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
		goto done_locked;

	/* Disable interrupts. */
	csr_write_4(sc, SF_IMR, 0x00000000);

	for (; (status & SF_INTRS) != 0;) {
		if ((status & SF_ISR_RXDQ1_DMADONE) != 0)
			sf_rxeof(sc);

		if ((status & (SF_ISR_TX_TXDONE | SF_ISR_TX_DMADONE |
		    SF_ISR_TX_QUEUEDONE)) != 0)
			sf_txeof(sc);

		if ((status & SF_ISR_ABNORMALINTR) != 0) {
			if ((status & SF_ISR_STATSOFLOW) != 0)
				sf_stats_update(sc);
			else if ((status & SF_ISR_TX_LOFIFO) != 0)
				sf_txthresh_adjust(sc);
			else if ((status & SF_ISR_DMAERR) != 0) {
				device_printf(sc->sf_dev,
				    "DMA error, resetting\n");
				sf_init_locked(sc);
				break;
			} else if ((status & SF_ISR_NO_TX_CSUM) != 0) {
				sc->sf_statistics.sf_tx_gfp_stall++;
#ifdef	SF_GFP_DEBUG
				device_printf(sc->sf_dev,
				    "TxGFP is not responding!\n");
#endif
			}
			else if ((status & SF_ISR_RXGFP_NORESP) != 0) {
				sc->sf_statistics.sf_rx_gfp_stall++;
#ifdef	SF_GFP_DEBUG
				device_printf(sc->sf_dev,
				    "RxGFP is not responding!\n");
#endif
			}
		}
		/* Reading the ISR register clears all interrrupts. */
		status = csr_read_4(sc, SF_ISR);
	}

	/* Re-enable interrupts. */
	csr_write_4(sc, SF_IMR, SF_INTRS);

	if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd))
		sf_start_locked(ifp);
done_locked:
	SF_UNLOCK(sc);
}

static void
sf_download_fw(struct sf_softc *sc)
{
	uint32_t gfpinst;
	int i, ndx;
	uint8_t *p;

	/*
	 * A FP instruction is composed of 48bits so we have to
	 * write it with two parts.
	 */
	p = txfwdata;
	ndx = 0;
	for (i = 0; i < sizeof(txfwdata) / SF_GFP_INST_BYTES; i++) {
		gfpinst = p[2] << 24 | p[3] << 16 | p[4] << 8 | p[5];
		csr_write_4(sc, SF_TXGFP_MEM_BASE + ndx * 4, gfpinst);
		gfpinst = p[0] << 8 | p[1];
		csr_write_4(sc, SF_TXGFP_MEM_BASE + (ndx + 1) * 4, gfpinst);
		p += SF_GFP_INST_BYTES;
		ndx += 2;
	}
	if (bootverbose)
		device_printf(sc->sf_dev, "%d Tx instructions downloaded\n", i);

	p = rxfwdata;
	ndx = 0;
	for (i = 0; i < sizeof(rxfwdata) / SF_GFP_INST_BYTES; i++) {
		gfpinst = p[2] << 24 | p[3] << 16 | p[4] << 8 | p[5];
		csr_write_4(sc, SF_RXGFP_MEM_BASE + (ndx * 4), gfpinst);
		gfpinst = p[0] << 8 | p[1];
		csr_write_4(sc, SF_RXGFP_MEM_BASE + (ndx + 1) * 4, gfpinst);
		p += SF_GFP_INST_BYTES;
		ndx += 2;
	}
	if (bootverbose)
		device_printf(sc->sf_dev, "%d Rx instructions downloaded\n", i);
}

static void
sf_init(void *xsc)
{
	struct sf_softc		*sc;

	sc = (struct sf_softc *)xsc;
	SF_LOCK(sc);
	sf_init_locked(sc);
	SF_UNLOCK(sc);
}

static void
sf_init_locked(struct sf_softc *sc)
{
	struct ifnet		*ifp;
	struct mii_data		*mii;
	uint8_t			eaddr[ETHER_ADDR_LEN];
	bus_addr_t		addr;
	int			i;

	SF_LOCK_ASSERT(sc);
	ifp = sc->sf_ifp;
	mii = device_get_softc(sc->sf_miibus);

	sf_stop(sc);
	/* Reset the hardware to a known state. */
	sf_reset(sc);

	/* Init all the receive filter registers */
	for (i = SF_RXFILT_PERFECT_BASE;
	    i < (SF_RXFILT_HASH_MAX + 1); i += sizeof(uint32_t))
		csr_write_4(sc, i, 0);

	/* Empty stats counter registers. */
	for (i = SF_STATS_BASE; i < (SF_STATS_END + 1); i += sizeof(uint32_t))
		csr_write_4(sc, i, 0);

	/* Init our MAC address. */
	bcopy(IF_LLADDR(sc->sf_ifp), eaddr, sizeof(eaddr));
	csr_write_4(sc, SF_PAR0,
	    eaddr[2] << 24 | eaddr[3] << 16 | eaddr[4] << 8 | eaddr[5]);
	csr_write_4(sc, SF_PAR1, eaddr[0] << 8 | eaddr[1]);
	sf_setperf(sc, 0, eaddr);

	if (sf_init_rx_ring(sc) == ENOBUFS) {
		device_printf(sc->sf_dev,
		    "initialization failed: no memory for rx buffers\n");
		return;
	}

	sf_init_tx_ring(sc);

	/*
	 * 16 perfect address filtering.
	 * Hash only multicast destination address, Accept matching
	 * frames regardless of VLAN ID.
	 */
	csr_write_4(sc, SF_RXFILT, SF_PERFMODE_NORMAL | SF_HASHMODE_ANYVLAN);

	/*
	 * Set Rx filter.
	 */
	sf_rxfilter(sc);

	/* Init the completion queue indexes. */
	csr_write_4(sc, SF_CQ_CONSIDX, 0);
	csr_write_4(sc, SF_CQ_PRODIDX, 0);

	/* Init the RX completion queue. */
	addr = sc->sf_rdata.sf_rx_cring_paddr;
	csr_write_4(sc, SF_CQ_ADDR_HI, SF_ADDR_HI(addr));
	csr_write_4(sc, SF_RXCQ_CTL_1, SF_ADDR_LO(addr) & SF_RXCQ_ADDR);
	if (SF_ADDR_HI(addr) != 0)
		SF_SETBIT(sc, SF_RXCQ_CTL_1, SF_RXCQ_USE_64BIT);
	/* Set RX completion queue type 2. */
	SF_SETBIT(sc, SF_RXCQ_CTL_1, SF_RXCQTYPE_2);
	csr_write_4(sc, SF_RXCQ_CTL_2, 0);

	/*
	 * Init RX DMA control.
	 * default RxHighPriority Threshold,
	 * default RxBurstSize, 128bytes.
	 */
	SF_SETBIT(sc, SF_RXDMA_CTL,
	    SF_RXDMA_REPORTBADPKTS |
	    (SF_RXDMA_HIGHPRIO_THRESH << 8) |
	    SF_RXDMA_BURST);

	/* Init the RX buffer descriptor queue. */
	addr = sc->sf_rdata.sf_rx_ring_paddr;
	csr_write_4(sc, SF_RXDQ_ADDR_HI, SF_ADDR_HI(addr));
	csr_write_4(sc, SF_RXDQ_ADDR_Q1, SF_ADDR_LO(addr));

	/* Set RX queue buffer length. */
	csr_write_4(sc, SF_RXDQ_CTL_1,
	    ((MCLBYTES  - sizeof(uint32_t)) << 16) |
	    SF_RXDQCTL_64BITBADDR | SF_RXDQCTL_VARIABLE);

	if (SF_ADDR_HI(addr) != 0)
		SF_SETBIT(sc, SF_RXDQ_CTL_1, SF_RXDQCTL_64BITDADDR);
	csr_write_4(sc, SF_RXDQ_PTR_Q1, SF_RX_DLIST_CNT - 1);
	csr_write_4(sc, SF_RXDQ_CTL_2, 0);

	/* Init the TX completion queue */
	addr = sc->sf_rdata.sf_tx_cring_paddr;
	csr_write_4(sc, SF_TXCQ_CTL, SF_ADDR_LO(addr) & SF_TXCQ_ADDR);
	if (SF_ADDR_HI(addr) != 0)
		SF_SETBIT(sc, SF_TXCQ_CTL, SF_TXCQ_USE_64BIT);

	/* Init the TX buffer descriptor queue. */
	addr = sc->sf_rdata.sf_tx_ring_paddr;
	csr_write_4(sc, SF_TXDQ_ADDR_HI, SF_ADDR_HI(addr));
	csr_write_4(sc, SF_TXDQ_ADDR_HIPRIO, 0);
	csr_write_4(sc, SF_TXDQ_ADDR_LOPRIO, SF_ADDR_LO(addr));
	csr_write_4(sc, SF_TX_FRAMCTL,
	    SF_TXFRMCTL_CPLAFTERTX | sc->sf_txthresh);
	csr_write_4(sc, SF_TXDQ_CTL,
	    SF_TXDMA_HIPRIO_THRESH << 24 |
	    SF_TXSKIPLEN_0BYTES << 16 |
	    SF_TXDDMA_BURST << 8 |
	    SF_TXBUFDESC_TYPE2 | SF_TXMINSPACE_UNLIMIT);
	if (SF_ADDR_HI(addr) != 0)
		SF_SETBIT(sc, SF_TXDQ_CTL, SF_TXDQCTL_64BITADDR);

	/* Set VLAN Type register. */
	csr_write_4(sc, SF_VLANTYPE, ETHERTYPE_VLAN);

	/* Set TxPause Timer. */
	csr_write_4(sc, SF_TXPAUSETIMER, 0xffff);

	/* Enable autopadding of short TX frames. */
	SF_SETBIT(sc, SF_MACCFG_1, SF_MACCFG1_AUTOPAD);
	SF_SETBIT(sc, SF_MACCFG_2, SF_MACCFG2_AUTOVLANPAD);
	/* Make sure to reset MAC to take changes effect. */
	SF_SETBIT(sc, SF_MACCFG_1, SF_MACCFG1_SOFTRESET);
	DELAY(1000);
	SF_CLRBIT(sc, SF_MACCFG_1, SF_MACCFG1_SOFTRESET);

	/* Enable PCI bus master. */
	SF_SETBIT(sc, SF_PCI_DEVCFG, SF_PCIDEVCFG_PCIMEN);

	/* Load StarFire firmware. */
	sf_download_fw(sc);

	/* Intialize interrupt moderation. */
	csr_write_4(sc, SF_TIMER_CTL, SF_TIMER_IMASK_MODE | SF_TIMER_TIMES_TEN |
	    (sc->sf_int_mod & SF_TIMER_IMASK_INTERVAL));

#ifdef DEVICE_POLLING
	/* Disable interrupts if we are polling. */
	if ((ifp->if_capenable & IFCAP_POLLING) != 0)
		csr_write_4(sc, SF_IMR, 0x00000000);
	else
#endif
	/* Enable interrupts. */
	csr_write_4(sc, SF_IMR, SF_INTRS);
	SF_SETBIT(sc, SF_PCI_DEVCFG, SF_PCIDEVCFG_INTR_ENB);

	/* Enable the RX and TX engines. */
	csr_write_4(sc, SF_GEN_ETH_CTL,
	    SF_ETHCTL_RX_ENB | SF_ETHCTL_RXDMA_ENB |
	    SF_ETHCTL_TX_ENB | SF_ETHCTL_TXDMA_ENB);

	if ((ifp->if_capenable & IFCAP_TXCSUM) != 0)
		SF_SETBIT(sc, SF_GEN_ETH_CTL, SF_ETHCTL_TXGFP_ENB);
	else
		SF_CLRBIT(sc, SF_GEN_ETH_CTL, SF_ETHCTL_TXGFP_ENB);
	if ((ifp->if_capenable & IFCAP_RXCSUM) != 0)
		SF_SETBIT(sc, SF_GEN_ETH_CTL, SF_ETHCTL_RXGFP_ENB);
	else
		SF_CLRBIT(sc, SF_GEN_ETH_CTL, SF_ETHCTL_RXGFP_ENB);

	sc->sf_link = 0;
	mii_mediachg(mii);

	ifp->if_drv_flags |= IFF_DRV_RUNNING;
	ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;

	callout_reset(&sc->sf_co, hz, sf_tick, sc);
}

static int
sf_encap(struct sf_softc *sc, struct mbuf **m_head)
{
	struct sf_txdesc	*txd;
	struct sf_tx_rdesc	*desc;
	struct mbuf		*m;
	bus_dmamap_t		map;
	bus_dma_segment_t	txsegs[SF_MAXTXSEGS];
	int			error, i, nsegs, prod, si;
	int			avail, nskip;

	SF_LOCK_ASSERT(sc);

	m = *m_head;
	prod = sc->sf_cdata.sf_tx_prod;
	txd = &sc->sf_cdata.sf_txdesc[prod];
	map = txd->tx_dmamap;
	error = bus_dmamap_load_mbuf_sg(sc->sf_cdata.sf_tx_tag, map,
	    *m_head, txsegs, &nsegs, BUS_DMA_NOWAIT);
	if (error == EFBIG) {
		m = m_collapse(*m_head, M_DONTWAIT, SF_MAXTXSEGS);
		if (m == NULL) {
			m_freem(*m_head);
			*m_head = NULL;
			return (ENOBUFS);
		}
		*m_head = m;
		error = bus_dmamap_load_mbuf_sg(sc->sf_cdata.sf_tx_tag,
		    map, *m_head, txsegs, &nsegs, BUS_DMA_NOWAIT);
		if (error != 0) {
			m_freem(*m_head);
			*m_head = NULL;
			return (error);
		}
	} else if (error != 0)
		return (error);
	if (nsegs == 0) {
		m_freem(*m_head);
		*m_head = NULL;
		return (EIO);
	}

	/* Check number of available descriptors. */
	avail = (SF_TX_DLIST_CNT - 1) - sc->sf_cdata.sf_tx_cnt;
	if (avail < nsegs) {
		bus_dmamap_unload(sc->sf_cdata.sf_tx_tag, map);
		return (ENOBUFS);
	}
	nskip = 0;
	if (prod + nsegs >= SF_TX_DLIST_CNT) {
		nskip = SF_TX_DLIST_CNT - prod - 1;
		if (avail < nsegs + nskip) {
			bus_dmamap_unload(sc->sf_cdata.sf_tx_tag, map);
			return (ENOBUFS);
		}
	}

	bus_dmamap_sync(sc->sf_cdata.sf_tx_tag, map, BUS_DMASYNC_PREWRITE);

	si = prod;
	for (i = 0; i < nsegs; i++) {
		desc = &sc->sf_rdata.sf_tx_ring[prod];
		desc->sf_tx_ctrl = htole32(SF_TX_DESC_ID |
		    (txsegs[i].ds_len & SF_TX_DESC_FRAGLEN));
		desc->sf_tx_reserved = 0;
		desc->sf_addr = htole64(txsegs[i].ds_addr);
		if (i == 0 && prod + nsegs >= SF_TX_DLIST_CNT) {
			/* Queue wraps! */
			desc->sf_tx_ctrl |= htole32(SF_TX_DESC_END);
			prod = 0;
		} else
			SF_INC(prod, SF_TX_DLIST_CNT);
	}
	/* Update producer index. */
	sc->sf_cdata.sf_tx_prod = prod;
	sc->sf_cdata.sf_tx_cnt += nsegs + nskip;

	desc = &sc->sf_rdata.sf_tx_ring[si];
	/* Check TDP/UDP checksum offload request. */
	if ((m->m_pkthdr.csum_flags & SF_CSUM_FEATURES) != 0)
		desc->sf_tx_ctrl |= htole32(SF_TX_DESC_CALTCP);
	desc->sf_tx_ctrl |=
	    htole32(SF_TX_DESC_CRCEN | SF_TX_DESC_INTR | (nsegs << 16));

	txd->tx_dmamap = map;
	txd->tx_m = m;
	txd->ndesc = nsegs + nskip;

	return (0);
}

static void
sf_start(struct ifnet *ifp)
{
	struct sf_softc		*sc;

	sc = ifp->if_softc;
	SF_LOCK(sc);
	sf_start_locked(ifp);
	SF_UNLOCK(sc);
}

static void
sf_start_locked(struct ifnet *ifp)
{
	struct sf_softc		*sc;
	struct mbuf		*m_head;
	int			enq;

	sc = ifp->if_softc;
	SF_LOCK_ASSERT(sc);

	if ((ifp->if_drv_flags & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) !=
	    IFF_DRV_RUNNING || sc->sf_link == 0)
		return;

	/*
	 * Since we don't know when descriptor wrap occurrs in advance
	 * limit available number of active Tx descriptor counter to be
	 * higher than maximum number of DMA segments allowed in driver.
	 */
	for (enq = 0; !IFQ_DRV_IS_EMPTY(&ifp->if_snd) &&
	    sc->sf_cdata.sf_tx_cnt < SF_TX_DLIST_CNT - SF_MAXTXSEGS; ) {
		IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head);
		if (m_head == NULL)
			break;
		/*
		 * Pack the data into the transmit ring. If we
		 * don't have room, set the OACTIVE flag and wait
		 * for the NIC to drain the ring.
		 */
		if (sf_encap(sc, &m_head)) {
			if (m_head == NULL)
				break;
			IFQ_DRV_PREPEND(&ifp->if_snd, m_head);
			ifp->if_drv_flags |= IFF_DRV_OACTIVE;
			break;
		}

		enq++;
		/*
		 * If there's a BPF listener, bounce a copy of this frame
		 * to him.
		 */
		ETHER_BPF_MTAP(ifp, m_head);
	}

	if (enq > 0) {
		bus_dmamap_sync(sc->sf_cdata.sf_tx_ring_tag,
		    sc->sf_cdata.sf_tx_ring_map,
		    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
		/* Kick transmit. */
		csr_write_4(sc, SF_TXDQ_PRODIDX,
		    sc->sf_cdata.sf_tx_prod * (sizeof(struct sf_tx_rdesc) / 8));

		/* Set a timeout in case the chip goes out to lunch. */
		sc->sf_watchdog_timer = 5;
	}
}

static void
sf_stop(struct sf_softc *sc)
{
	struct sf_txdesc	*txd;
	struct sf_rxdesc	*rxd;
	struct ifnet		*ifp;
	int			i;

	SF_LOCK_ASSERT(sc);

	ifp = sc->sf_ifp;

	ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
	sc->sf_link = 0;
	callout_stop(&sc->sf_co);
	sc->sf_watchdog_timer = 0;

	/* Reading the ISR register clears all interrrupts. */
	csr_read_4(sc, SF_ISR);
	/* Disable further interrupts. */
	csr_write_4(sc, SF_IMR, 0);

	/* Disable Tx/Rx egine. */
	csr_write_4(sc, SF_GEN_ETH_CTL, 0);

	csr_write_4(sc, SF_CQ_CONSIDX, 0);
	csr_write_4(sc, SF_CQ_PRODIDX, 0);
	csr_write_4(sc, SF_RXDQ_ADDR_Q1, 0);
	csr_write_4(sc, SF_RXDQ_CTL_1, 0);
	csr_write_4(sc, SF_RXDQ_PTR_Q1, 0);
	csr_write_4(sc, SF_TXCQ_CTL, 0);
	csr_write_4(sc, SF_TXDQ_ADDR_HIPRIO, 0);
	csr_write_4(sc, SF_TXDQ_CTL, 0);

	/*
	 * Free RX and TX mbufs still in the queues.
	 */
	for (i = 0; i < SF_RX_DLIST_CNT; i++) {
		rxd = &sc->sf_cdata.sf_rxdesc[i];
		if (rxd->rx_m != NULL) {
			bus_dmamap_sync(sc->sf_cdata.sf_rx_tag,
			    rxd->rx_dmamap, BUS_DMASYNC_POSTREAD);
			bus_dmamap_unload(sc->sf_cdata.sf_rx_tag,
			    rxd->rx_dmamap);
			m_freem(rxd->rx_m);
			rxd->rx_m = NULL;
		}
        }
	for (i = 0; i < SF_TX_DLIST_CNT; i++) {
		txd = &sc->sf_cdata.sf_txdesc[i];
		if (txd->tx_m != NULL) {
			bus_dmamap_sync(sc->sf_cdata.sf_tx_tag,
			    txd->tx_dmamap, BUS_DMASYNC_POSTWRITE);
			bus_dmamap_unload(sc->sf_cdata.sf_tx_tag,
			    txd->tx_dmamap);
			m_freem(txd->tx_m);
			txd->tx_m = NULL;
			txd->ndesc = 0;
		}
        }
}

static void
sf_tick(void *xsc)
{
	struct sf_softc		*sc;
	struct mii_data		*mii;

	sc = xsc;
	SF_LOCK_ASSERT(sc);
	mii = device_get_softc(sc->sf_miibus);
	mii_tick(mii);
	sf_stats_update(sc);
	sf_watchdog(sc);
	callout_reset(&sc->sf_co, hz, sf_tick, sc);
}

/*
 * Note: it is important that this function not be interrupted. We
 * use a two-stage register access scheme: if we are interrupted in
 * between setting the indirect address register and reading from the
 * indirect data register, the contents of the address register could
 * be changed out from under us.
 */
static void
sf_stats_update(struct sf_softc *sc)
{
	struct ifnet		*ifp;
	struct sf_stats		now, *stats, *nstats;
	int			i;

	SF_LOCK_ASSERT(sc);

	ifp = sc->sf_ifp;
	stats = &now;

	stats->sf_tx_frames =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_TX_FRAMES);
	stats->sf_tx_single_colls =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_TX_SINGLE_COL);
	stats->sf_tx_multi_colls =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_TX_MULTI_COL);
	stats->sf_tx_crcerrs =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_TX_CRC_ERRS);
	stats->sf_tx_bytes =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_TX_BYTES);
	stats->sf_tx_deferred =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_TX_DEFERRED);
	stats->sf_tx_late_colls =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_TX_LATE_COL);
	stats->sf_tx_pause_frames =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_TX_PAUSE);
	stats->sf_tx_control_frames =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_TX_CTL_FRAME);
	stats->sf_tx_excess_colls =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_TX_EXCESS_COL);
	stats->sf_tx_excess_defer =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_TX_EXCESS_DEF);
	stats->sf_tx_mcast_frames =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_TX_MULTI);
	stats->sf_tx_bcast_frames =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_TX_BCAST);
	stats->sf_tx_frames_lost =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_TX_FRAME_LOST);
	stats->sf_rx_frames =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_FRAMES);
	stats->sf_rx_crcerrs =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_CRC_ERRS);
	stats->sf_rx_alignerrs =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_ALIGN_ERRS);
	stats->sf_rx_bytes =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_BYTES);
	stats->sf_rx_pause_frames =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_PAUSE);
	stats->sf_rx_control_frames =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_CTL_FRAME);
	stats->sf_rx_unsup_control_frames =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_UNSUP_FRAME);
	stats->sf_rx_giants =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_GIANTS);
	stats->sf_rx_runts =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_RUNTS);
	stats->sf_rx_jabbererrs =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_JABBER);
	stats->sf_rx_fragments =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_FRAGMENTS);
	stats->sf_rx_pkts_64 =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_64);
	stats->sf_rx_pkts_65_127 =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_65_127);
	stats->sf_rx_pkts_128_255 =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_128_255);
	stats->sf_rx_pkts_256_511 =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_256_511);
	stats->sf_rx_pkts_512_1023 =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_512_1023);
	stats->sf_rx_pkts_1024_1518 =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_1024_1518);
	stats->sf_rx_frames_lost =
	    csr_read_4(sc, SF_STATS_BASE + SF_STATS_RX_FRAME_LOST);
	/* Lower 16bits are valid. */
	stats->sf_tx_underruns =
	    (csr_read_4(sc, SF_STATS_BASE + SF_STATS_TX_UNDERRUN) & 0xffff);

	/* Empty stats counter registers. */
	for (i = SF_STATS_BASE; i < (SF_STATS_END + 1); i += sizeof(uint32_t))
		csr_write_4(sc, i, 0);

	ifp->if_opackets += (u_long)stats->sf_tx_frames;

	ifp->if_collisions += (u_long)stats->sf_tx_single_colls +
	    (u_long)stats->sf_tx_multi_colls;

	ifp->if_oerrors += (u_long)stats->sf_tx_excess_colls +
	    (u_long)stats->sf_tx_excess_defer +
	    (u_long)stats->sf_tx_frames_lost;

	ifp->if_ipackets += (u_long)stats->sf_rx_frames;

	ifp->if_ierrors += (u_long)stats->sf_rx_crcerrs +
	    (u_long)stats->sf_rx_alignerrs +
	    (u_long)stats->sf_rx_giants +
	    (u_long)stats->sf_rx_runts +
	    (u_long)stats->sf_rx_jabbererrs +
	    (u_long)stats->sf_rx_frames_lost;

	nstats = &sc->sf_statistics;

	nstats->sf_tx_frames += stats->sf_tx_frames;
	nstats->sf_tx_single_colls += stats->sf_tx_single_colls;
	nstats->sf_tx_multi_colls += stats->sf_tx_multi_colls;
	nstats->sf_tx_crcerrs += stats->sf_tx_crcerrs;
	nstats->sf_tx_bytes += stats->sf_tx_bytes;
	nstats->sf_tx_deferred += stats->sf_tx_deferred;
	nstats->sf_tx_late_colls += stats->sf_tx_late_colls;
	nstats->sf_tx_pause_frames += stats->sf_tx_pause_frames;
	nstats->sf_tx_control_frames += stats->sf_tx_control_frames;
	nstats->sf_tx_excess_colls += stats->sf_tx_excess_colls;
	nstats->sf_tx_excess_defer += stats->sf_tx_excess_defer;
	nstats->sf_tx_mcast_frames += stats->sf_tx_mcast_frames;
	nstats->sf_tx_bcast_frames += stats->sf_tx_bcast_frames;
	nstats->sf_tx_frames_lost += stats->sf_tx_frames_lost;
	nstats->sf_rx_frames += stats->sf_rx_frames;
	nstats->sf_rx_crcerrs += stats->sf_rx_crcerrs;
	nstats->sf_rx_alignerrs += stats->sf_rx_alignerrs;
	nstats->sf_rx_bytes += stats->sf_rx_bytes;
	nstats->sf_rx_pause_frames += stats->sf_rx_pause_frames;
	nstats->sf_rx_control_frames += stats->sf_rx_control_frames;
	nstats->sf_rx_unsup_control_frames += stats->sf_rx_unsup_control_frames;
	nstats->sf_rx_giants += stats->sf_rx_giants;
	nstats->sf_rx_runts += stats->sf_rx_runts;
	nstats->sf_rx_jabbererrs += stats->sf_rx_jabbererrs;
	nstats->sf_rx_fragments += stats->sf_rx_fragments;
	nstats->sf_rx_pkts_64 += stats->sf_rx_pkts_64;
	nstats->sf_rx_pkts_65_127 += stats->sf_rx_pkts_65_127;
	nstats->sf_rx_pkts_128_255 += stats->sf_rx_pkts_128_255;
	nstats->sf_rx_pkts_256_511 += stats->sf_rx_pkts_256_511;
	nstats->sf_rx_pkts_512_1023 += stats->sf_rx_pkts_512_1023;
	nstats->sf_rx_pkts_1024_1518 += stats->sf_rx_pkts_1024_1518;
	nstats->sf_rx_frames_lost += stats->sf_rx_frames_lost;
	nstats->sf_tx_underruns += stats->sf_tx_underruns;
}

static void
sf_watchdog(struct sf_softc *sc)
{
	struct ifnet		*ifp;

	SF_LOCK_ASSERT(sc);

	if (sc->sf_watchdog_timer == 0 || --sc->sf_watchdog_timer)
		return;

	ifp = sc->sf_ifp;

	ifp->if_oerrors++;
	if (sc->sf_link == 0) {
		if (bootverbose)
			if_printf(sc->sf_ifp, "watchdog timeout "
			   "(missed link)\n");
	} else
		if_printf(ifp, "watchdog timeout, %d Tx descs are active\n",
		    sc->sf_cdata.sf_tx_cnt);

	sf_init_locked(sc);

	if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd))
		sf_start_locked(ifp);
}

static int
sf_shutdown(device_t dev)
{
	struct sf_softc		*sc;

	sc = device_get_softc(dev);

	SF_LOCK(sc);
	sf_stop(sc);
	SF_UNLOCK(sc);

	return (0);
}

static int
sf_suspend(device_t dev)
{
	struct sf_softc		*sc;

	sc = device_get_softc(dev);

	SF_LOCK(sc);
	sf_stop(sc);
	sc->sf_suspended = 1;
	bus_generic_suspend(dev);
	SF_UNLOCK(sc);

	return (0);
}

static int
sf_resume(device_t dev)
{
	struct sf_softc		*sc;
	struct ifnet		*ifp;

	sc = device_get_softc(dev);

	SF_LOCK(sc);
	bus_generic_resume(dev);
	ifp = sc->sf_ifp;
	if ((ifp->if_flags & IFF_UP) != 0)
		sf_init_locked(sc);

	sc->sf_suspended = 0;
	SF_UNLOCK(sc);

	return (0);
}

static int
sf_sysctl_stats(SYSCTL_HANDLER_ARGS)
{
	struct sf_softc		*sc;
	struct sf_stats		*stats;
	int			error;
	int			result;

	result = -1;
	error = sysctl_handle_int(oidp, &result, 0, req);

	if (error != 0 || req->newptr == NULL)
		return (error);

	if (result != 1)
		return (error);

	sc = (struct sf_softc *)arg1;
	stats = &sc->sf_statistics;

	printf("%s statistics:\n", device_get_nameunit(sc->sf_dev));
	printf("Transmit good frames : %ju\n",
	    (uintmax_t)stats->sf_tx_frames);
	printf("Transmit good octets : %ju\n",
	    (uintmax_t)stats->sf_tx_bytes);
	printf("Transmit single collisions : %u\n",
	    stats->sf_tx_single_colls);
	printf("Transmit multiple collisions : %u\n",
	    stats->sf_tx_multi_colls);
	printf("Transmit late collisions : %u\n",
	    stats->sf_tx_late_colls);
	printf("Transmit abort due to excessive collisions : %u\n",
	    stats->sf_tx_excess_colls);
	printf("Transmit CRC errors : %u\n",
	    stats->sf_tx_crcerrs);
	printf("Transmit deferrals : %u\n",
	    stats->sf_tx_deferred);
	printf("Transmit abort due to excessive deferrals : %u\n",
	    stats->sf_tx_excess_defer);
	printf("Transmit pause control frames : %u\n",
	    stats->sf_tx_pause_frames);
	printf("Transmit control frames : %u\n",
	    stats->sf_tx_control_frames);
	printf("Transmit good multicast frames : %u\n",
	    stats->sf_tx_mcast_frames);
	printf("Transmit good broadcast frames : %u\n",
	    stats->sf_tx_bcast_frames);
	printf("Transmit frames lost due to internal transmit errors : %u\n",
	    stats->sf_tx_frames_lost);
	printf("Transmit FIFO underflows : %u\n",
	    stats->sf_tx_underruns);
	printf("Transmit GFP stalls : %u\n", stats->sf_tx_gfp_stall);
	printf("Receive good frames : %ju\n",
	    (uint64_t)stats->sf_rx_frames);
	printf("Receive good octets : %ju\n",
	    (uint64_t)stats->sf_rx_bytes);
	printf("Receive CRC errors : %u\n",
	    stats->sf_rx_crcerrs);
	printf("Receive alignment errors : %u\n",
	    stats->sf_rx_alignerrs);
	printf("Receive pause frames : %u\n",
	    stats->sf_rx_pause_frames);
	printf("Receive control frames : %u\n",
	    stats->sf_rx_control_frames);
	printf("Receive control frames with unsupported opcode : %u\n",
	    stats->sf_rx_unsup_control_frames);
	printf("Receive frames too long : %u\n",
	    stats->sf_rx_giants);
	printf("Receive frames too short : %u\n",
	    stats->sf_rx_runts);
	printf("Receive frames jabber errors : %u\n",
	    stats->sf_rx_jabbererrs);
	printf("Receive frames fragments : %u\n",
	    stats->sf_rx_fragments);
	printf("Receive packets 64 bytes : %ju\n",
	    (uint64_t)stats->sf_rx_pkts_64);
	printf("Receive packets 65 to 127 bytes : %ju\n",
	    (uint64_t)stats->sf_rx_pkts_65_127);
	printf("Receive packets 128 to 255 bytes : %ju\n",
	    (uint64_t)stats->sf_rx_pkts_128_255);
	printf("Receive packets 256 to 511 bytes : %ju\n",
	    (uint64_t)stats->sf_rx_pkts_256_511);
	printf("Receive packets 512 to 1023 bytes : %ju\n",
	    (uint64_t)stats->sf_rx_pkts_512_1023);
	printf("Receive packets 1024 to 1518 bytes : %ju\n",
	    (uint64_t)stats->sf_rx_pkts_1024_1518);
	printf("Receive frames lost due to internal receive errors : %u\n",
	    stats->sf_rx_frames_lost);
	printf("Receive GFP stalls : %u\n", stats->sf_rx_gfp_stall);

	return (error);
}

static int
sysctl_int_range(SYSCTL_HANDLER_ARGS, int low, int high)
{
	int error, value;

	if (!arg1)
		return (EINVAL);
	value = *(int *)arg1;
	error = sysctl_handle_int(oidp, &value, 0, req);
	if (error || !req->newptr)
		return (error);
	if (value < low || value > high)
		return (EINVAL);
	*(int *)arg1 = value;

	return (0);
}

static int
sysctl_hw_sf_int_mod(SYSCTL_HANDLER_ARGS)
{

	return (sysctl_int_range(oidp, arg1, arg2, req, SF_IM_MIN, SF_IM_MAX));
}