1.8139too网卡设备简介
一个PCI设备,总共有三个地址空间:内存,端口和设备。内存和端口事实上是同一个内容的不合拜望路径罢了。PCI设备的设备空间是标准化的,每个PCI设备的设备空间的前64个字节的含义差不多上一样的。但各个设备的内存和端口空间是完全不一样的。在硬件概念上,设备的I/O内存和I/O端口空间没有差别,事实上差不多上设备拥用的一些读写存放器。 8139too网卡拥有256字节的读写存放器空间。它的全部构造如下: /* Symbolic offsets to registers. */ enum RTL8139_registers { MAC0 = 0, /* Ethernet hardware address. */ MAR0 = 8, /* Multicast filter. */ TxStatus0 = 0x10, /* Transmit status (Four 32bit registers). */ TxAddr0 = 0x20, /* Tx descriptors (also four 32bit). */ RxBuf = 0x30, 小额信贷运作与管理 ChipCmd = 0x37,
RxBufPtr = 0x38,
RxBufAddr = 0x3A,
IntrMask = 0x3C,
IntrStatus = 0x3E,
TxConfig = 0x40,
RxConfig = 0x44,
Timer = 0x48, /* A general-purpose counter. */
RxMissed = 0x4C, /* 24 bits valid, write clears. */
Cfg9346 = 0x50,
Config0 = 0x51,
Config1 = 0x52,
FlashReg = 0x54,
MediaStatus = 0x58,
Config3 = 0x59,
Config4 = 0x5A, /* absent on RTL-8139A */
HltClk = 0x5B,
MultiIntr = 0x5C,
TxSummary = 0x60,
BasicModeCtrl = 0x62,
BasicModeStatus = 0x64,
NWayAdvert = 0x66,
NWayLPAR = 0x68,
NWayExpansion = 0x6A,
/* Undocumented registers, but required for proper operation. */
FIFOTMS = 0x70, /* FIFO Control and test. */
CSCR = 0x74, /* Chip Status and Configuration Register. */
PARA78 = 0x78,
PARA7c = 0x7c, /* Magic transceiver parameter register. */
Config5 = 0xD8, /* absent on RTL-8139A */
};
每个存放器都有它专门的含义和用处。举个例子(我们假设应用I/O内存的方法,ioaddr为设备内存映射在CPU内存地址空间的肇端地址,下述代码能读取板卡的版本号):
#define HW_REVID(b30, b29, b28, b27, b26, b23, b22) \
农业生产结构 (b30<<30 | b29<<29 | b28<<28 | b27<<27 | b26<<26 | b23<<23 | b22<<22)
#define HW_REVID_MASK HW_REVID(1, 1, 1, 1, 1, 1, 1)
/* identify chip attached to board */国家企业信用公示信息系统(全国)
version = ioread32( ioaddr + TxConfig ) & HW_REVID_MASK;
在我的电脑上,version读出来的值的0x74400000,经由比对,板卡名称为:RTL-8100B/8139D。
下面是全部系例版本号的表格:
enum chip_flags {
HasHltClk = (1 << 0),
HasLWake = (1 << 1),
};
/* directly indexed by chip_t, above */
const static struct {
const char *name;
u32 version; /* from RTL8139C/RTL8139D docs */
u32 flags;
} rtl_chip_info[] = {
{ "RTL-8139", HW_REVID(1, 0, 0, 0, 0, 0, 0),HasHltClk,},
{ "RTL-8139 rev K", HW_REVID(1, 1, 0, 0, 0, 0, 0),HasHltClk,},
{ "RTL-8139A", HW_REVID(1, 1, 1, 0, 0, 0, 0),HasHltClk,},
{ "RTL-8139A rev G",HW_REVID(1, 1, 1, 0, 0, 1, 0),HasHltClk,},
{ "RTL-8139B", HW_REVID(1, 1, 1, 1, 0, 0, 0),HasLWake, },
{ "RTL-8130", HW_REVID(1, 1, 1, 1, 1, 0, 0),HasLWake, },
{ "RTL-8139C", HW_REVID(1, 1, 1, 0, 1, 0, 0),HasLWake, },
{ "RTL-8100", HW_REVID(1, 1, 1, 1, 0, 1, 0),HasLWake, },
{ "RTL-8100B/8139D",HW_REVID(1, 1, 1, 0, 1, 0, 1),HasLWake, },
{ "RTL-8101", HW_REVID(1, 1, 1, 0, 1, 1, 1),HasLWake, },
};
在那个表格中,RTL_8139B以下(包含RTL_8139B)的板卡都属于较新的版本。新老版本之间有不合的唤醒方法。先看新版本的:
#define LWAKE 0x10
#define Cfg1_PM_Enable 0x01
u8 new_tmp8, tmp8;
enum Config4Bits {
LWPTN = (1 << 2), /* not on 8139, 8139A */江都市实验小学
};
enum Cfg9346Bits {
Cfg9346_Lock = 0x00,
Cfg9346_Unlock = 0xC0,
};
new_tmp8 = tmp8 = ioread8( ioaddr + Config1 );
if( (rtl_chip_info[tp->chipset].flags & HasLWake) && (tmp8 & LWAKE))
new_tmp8 &= ~LWAKE;
new_tmp8 |= Cfg1_PM_Enable;
if (new_tmp8 != tmp8) {
iowrite8( Cfg9346_Unlock, ioaddr + Cfg9346 );万向联轴器
iowrite8( tmp8, ioaddr + Config1 );
iowrite8( Cfg9346_Lock, ioaddr + Cfg9346 );
}
if (rtl_chip_info[tp->chipset].flags & HasLWake) {
tmp8 = ioread8( ioaddr + Config4 );
if (tmp8 & LWPTN) {
iowrite8( Cfg9346_Unlock, ioaddr + Cfg9346 );
iowrite8( tmp8 & ~LWPTN, ioaddr + Config4 );
iowrite8( Cfg9346_Lock, ioaddr + Cfg9346 );
}
}
全然的一个流程是:假如板卡版本本身支撑了HasLWake,而Config1中读出的值带有LWAKE,把Config1的值写回,并把Config4 中的LWPTN去除。而我的网卡中从Config1, Config4读取的值分别为0x8d, 0x88,因此,无需做任何操作。
下面是旧版本的唤醒方法:
enum Config1Bits {
Cfg1_PM_Enable = 0x01,
Cfg1_VPD_Enable = 0x02,
Cfg1_PIO = 0x04,
Cfg1_MMIO = 0x08,
LWAKE = 0x10, /* not on 8139, 8139A */
Cfg1_Driver_Load = 0x20,
Cfg1_LED0 = 0x40,
Cfg1_LED1 = 0x80,
锁流光 SLEEP = (1 << 1), /* only on 8139, 8139A */
PWRDN = (1 << 0), /* only on 8139, 8139A */
};
tmp8 = ioread8( ioaddr + Config1 );
tmp8 &= ~(SLEEP | PWRDN);
iowrite8( tmp8, ioaddr + Config1 );
下面是一个板卡的复位操作:
enum ChipCmdBits {
CmdReset = 0x10,
CmdRxEnb = 0x08,
CmdTxEnb = 0x04,
RxBufEmpty = 0x01,
};
static void rtl8139_chip_reset (void __iomem *ioaddr)
{
int i;
/* Soft reset the chip. */
iowrite8( CmdReset, ioaddr + ChipCmd );
/* Check that the chip has finished the reset. */
for (i = 1000; i > 0; i--) {
barrier();
if ((ioread8( ioaddr + ChipCmd ) & CmdReset) == 0)
break;
udelay (10);
}
}
写一个CmdReset敕令到ChipCmd地位,等待该敕令消掉,即可。
关于网卡的存放器操作,还有一些,再进行过程中碰到时再介绍。
2. 收集设备的初始化
3.一个简单的收集流量统计法度榜样
4. net_device构造体详解
构造体net_device代表了一个收集设备接口,它是我们明白得收集设备驱动法度榜样的关键。那个地点,我们选择个中的一些重要成员,一一作具体分析,并结合以太网设备,看看Linux内核是若何为以太网设备供给构造体中某些成员的缺省值的。 在Linux内核源代码中是如许为那个构造体作注释的:实际上,那个构造体是一个专门大年夜的缺点,它把I/O数据和更高层的数据混淆在一路,同时它几乎必须明白INET模块中的每个数据构造。 毫无疑问,这是一个巨型构造体。但我们为编写收集设备驱动法度榜样,只须要明白得个中的一部分,下面选择个中的一些作分析,并给出以太网设备的缺省值。 unsigned short flags;void (*set_multicast_list)(struct net_device *dev); 这是一个接口标记,包含了专门多值的位掩码。在以太网的缺省初始化函数中,该标记 被设置为:IFF_BROADCAST|IFF_MULTICAST,表示以太网卡是可广播的,同时是能够或许进行组播发送的。别的,该标记接口还有一些只读标记,如IFF_UP,当接口被激活并能够开端传输数据包时,内核设置该标记。而IFF_PROMISC被设置或清除时,会调用set_multicast_list函数通知板卡上的硬件过滤器。
unsigned short hard_header_len;
unsigned short type;
hard_header_len是硬件头的长度,在以太网设备的初始化函数中,该成员被赋为ETH_HLEN,即以太网头的长度,该值为14,下面是以太网头的定义:
struct ethhdr {
unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
unsigned char h_source[ETH_ALEN]; /* source ether addr */
unsigned short h_proto; /* packet type ID field */
} __attribute__((packed));
ETH_ALEN被定义为6,即以太网MAC地址的长度。h_proto储存type的值。type是接口
的硬件类型,以太网设备的初始化函数中将其赋值为ARPHRD_ETHER,即10Mb以太网。
int (*hard_header) (struct sk_buff *skb, struct net_device *dev,
unsigned short type, void *daddr,void *saddr,
unsigned len);
该函数在数据被传输之前被调用,它依照先前检索到的源和目标地址建立硬件头。以太网设备的默认函数是eth_header:
int eth_header(struct sk_buff *skb, struct net_device *dev,
unsigned short type, void *daddr,
void *saddr, unsigned len)
{
struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN);
/*
* Set the protocol type. For a packet of
* type ETH_P_802_3 we put the length
* in here instead. It is up to the 802.2
* layer to carry protocol information.
*/
if(type!=ETH_P_802_3)
eth->h_proto = htons(type);
else
eth->h_proto = htons(len);
//Set the source hardware address.
if(!saddr)
saddr = dev->dev_addr;
memcpy(eth->h_source,saddr,dev->addr_len);
//Anyway, the loopback-device should never
//use
if (dev->flags & (IFF_LOOPBACK|IFF_NOARP)){
memset(eth->h_dest, 0, dev->addr_len);
return ETH_HLEN;
}
if(daddr){
memcpy(eth->h_dest,daddr,dev->addr_len);
return ETH_HLEN;
}
return -ETH_HLEN;
}
它依照传入的参数建立以太网头,假如传入的源地址为空,则应用设备的地址作为源地址。与之相干的还有一个int (*rebuild_header)(struct sk_buff *skb)函数,在以太网设备中,它是用于在ARP地址解析完成今后,从新建立以太网头,主假如更新目标MAC地址,
因为在前一次建立时,因为没有经由 ARP协定,有可能目标MAC地址是缺点的(未完,待续)。
5. net_device构造体详解(续)
int (*set_mac_address)(struct net_device *dev, void *addr);
改变网卡的mac地址。实际上,专门多硬件设备接口全然不支撑这种功能,就算支撑,也须要硬件厂商供给的对象修改EPROM中的硬件地址。一样我们在某一操作体系下所谓的修改MAC地址,只是修改了操作体系在安装网卡时从网卡EPROM中读出来的值,假如体系被重装,则MAC地址又复原为本来的值。以太网设备的默认函数是eth_mac_addr,它只是简单地修改了dev->dev_addr的值。
int (*hard_header_cache)(struct neighbour *neigh,struct hh_cache *hh);
void (*header_cache_update)(struct hh_cache *hh,struct net_device *dev,
unsigned char * haddr);
int (*hard_header_parse)(struct sk_buff *skb,unsigned char *haddr);
这三个函数在以太网设备接口中都有默认的值,hard_header_cache把硬件地址缓存到struct hh_cache中;header_cache_update在地址产生变更中,更新hh_cache构造中的目标地址;而 hard_header_parse则从skb中的数据包中获得源地址,并将其复制到位于haddr的缓冲区。
int (*change_mtu)(struct net_device *dev, int new_mtu);
下面是以太网设备接口的change_mtu的实现:
static int eth_change_mtu(struct net_device *dev, int new_mtu)
{
if ((new_mtu < 68) || (new_mtu > 1500))
return -EINVAL;
dev->mtu = new_mtu;
return 0;
}
在net_device中,还有专门多收集驱动法度榜样必须涉及的重要成员,跟着我们的8139too网卡驱动法度榜样的进一步深刻,在涉及时再作具体分析