📄 网口程序实例.txt
字号:
CPU将调用elintr中断例程,并带有参数unit即该种网卡的第几块(因为在计算机中,你有可能
装了相同的网卡有几块),elintr的作用是把数据从网卡的数据存储器中读到我们在该网卡初始化时预先分配好
的数据缓冲区中,他调用的函数就只有elread,同样elread也只调用了elget一个函数.elread函数比较简单,就是
调用elget,elget则相对比较复杂一点,涉及到核心内存分配mbuf,mbuf是比较恐怖的东西,正如STEVEN所写的,为
了节约当时"巨大"的4M内存,牺牲了性能搞出了这个mbuf东东,mbuf是必须要弄懂的,虽然在设备驱动程序中调用
他的宏和函数不多,但在后面的IP协议,TCP协议中有不少涉及的地方.
关于数据发送方面和接收差不多,在上层协议放置好数据到mbuf链后,调用el_start函数,该函数把mbuf链中
的数据放置到本块网卡的发送队列缓冲el_pktbuf中,然后再调用el_xmit函数,此函数把发送队列缓冲el_pktbuf
中的数据有传递到网卡的数据存储器中.
*/
#include "opt_inet.h"
#include "opt_ipx.h"
#include <sys/param.h>;
#include <sys/systm.h>;
#include <sys/sockio.h>;
#include <sys/mbuf.h>;
#include <sys/socket.h>;
#include <sys/syslog.h>;
#include <net/ethernet.h>;
#include <net/if.h>;
#include <netinet/in.h>;
#include <netinet/if_ether.h>;
#include <net/bpf.h>;
#include <machine/clock.h>;
#include <i386/isa/isa_device.h>;
#include <i386/isa/if_elreg.h>;/*此头文件是3COM卡的寄存器常量*/
/* 为了调试方便 */
#ifdef EL_DEBUG
#define dprintf(x) printf x /*如果定义了DEBUG调试,则打印到屏幕*/
#else
#define dprintf(x)
#endif
/* softc结构,每种网卡的该结构是不同的,主要是该第一个成员必须是一以太网的共用结构arpcom*/
static struct el_softc {
struct arpcom arpcom; /* 以太网公共部分 */
u_short el_base; /* 基本输入输出地址 */
char el_pktbuf[EL_BUFSIZ]; /* 帧缓冲大小2048 */
} el_softc[NEL]; /*NEL在el.h中定义,即编译时产生的头文件,意思为支持的网卡数*/
/*
看看arpcom结构吧
* 该结构是以太网设备驱动程序和ARP程序所共享.
struct arpcom {
/*
* ifnet 结构必须在此结构的第一个位置.
/
struct ifnet ac_if;
u_char ac_enaddr[6]; /* 以太网硬件地址/
int ac_multicnt; /* 多播地址列表数 /
void *ac_netgraph; /* netgraph 节点信息,即我们所说的PPPoE,也就是ADSL宽带所用到的 /
};
*/
/* 一些函数申明 */
static int el_attach(struct isa_device *);/*第二步:填充相关的数据结构*/
static void el_init(void *); /*不用说,是初始化,在probe,attach之后被调用*/
static int el_ioctl(struct ifnet *,u_long,caddr_t);/*控制网卡的函树指针*/
static int el_probe(struct isa_device *);/*第一步:探测程序.查看是否卡存在和是否在正确的位置.*/
static void el_start(struct ifnet *);/*把数据包从硬件接口输出去*/
static void el_reset(void *);/* 该例程重设接口. 在el_watchdog()中调用*/
static void el_watchdog(struct ifnet *);/*一般该函数用于包在一定时间内没发送出去,就调用他,在
本驱动程序中并不支持该函数,在我的rtl8139说明中有*/
static void el_stop(void *);/*停止接口,在el_ioctl()和el_reset()中调用*/
static int el_xmit(struct el_softc *,int);/*把数据包放到芯片内,发送到以太网上*/
static ointhand2_t elintr;/*中断例程*/
static __inline void elread(struct el_softc *,caddr_t,int);/* 传递包到更高一级协议处理,即ether_input()例程.由elintr()调用 */
static struct mbuf *elget(caddr_t,int,struct ifnet *); /* 从网卡上下载数据包,len是数据的长度,本地以太网头部被分开*/
static __inline void el_hardreset(void *);/* 这是一个子程序,目的是重设硬件.*/
/* isa_driver结构 为 autoconf准备 */
/* isa_driver结构说明:
该结构来之于文件isa_device.h头文件
结构成员:
/*
* 通用的设备驱动程序结构.
*
* 没一设备驱动程序定义一组例程入口,由设置程序在启动时使用.
struct isa_driver {
int (*probe) __P((struct isa_device *idp));
/* 测试设备是否存在
int (*attach) __P((struct isa_device *idp));
/* 为设备设置驱动程序
char *name; /* 设备名称
int sensitive_hw; /* 探测发生有冲突时为真,ISA设备的老毛病
};
*/
struct isa_driver eldriver = {
el_probe, el_attach, "el"
};
/* 探测程序.查看是否卡存在和是否在正确的位置. */
static int
el_probe(struct isa_device *idev)
{
/*
isa_device 是设备的通用结构,该结构说明在isa_device.h头文件中,说明如下:
struct isa_device {
int id_id; /* 设备的 id
struct isa_driver *id_driver; 指向设备的驱动程序结构
int id_iobase; /* 基本IO地址
int id_iosize; /* 基本IO地址的长度
u_int id_irq; /* 中断
int id_drq; /* DMA
caddr_t id_maddr; /* 在总线中的物理IO内存地址(即便要)
int id_msize; /* IO内存的大小
union {
inthand2_t *id_i;
ointhand2_t *id_oi;中断例程指针
} id_iu; /* 中断接口例程
#define id_intr id_iu.id_i
#define id_ointr id_iu.id_oi
int id_unit; /* 在该类设备中是第几个
int id_flags; /* flags
int id_enabled; /* 设备激活了吗
struct isa_device *id_next; /* 在 userconfig()中用于isa_devlist
struct device *id_device;
};
*/
struct el_softc *sc;
u_short base; /* 仅仅为了方便 */
u_char station_addr[ETHER_ADDR_LEN];/*以太网的硬件地址*/
int i;
/* 搜集一些信息 */
sc = &el_softc[idev->;id_unit];/*sc是softc结构,如果你有NEL块el卡的话就有NEL个softc
结构,当然也有可能同时还有其他的xx_softc结构*/
sc->;el_base = idev->;id_iobase;/*该块卡的基本I/O地址*/
base = sc->;el_base;/*有一点多余,只是为了方便下面的引用*/
/* 第一次检查地址,看看基本地址是否在0X280到0X3F0之内 */
if((base < 0x280) || (base >; 0x3f0)) {
printf("el%d: ioaddr must be between 0x280 and 0x3f0\n",
idev->;id_unit);
return(0);
}
/* 现在尝试从PROM中获取地址,看看是否包含了3COM供应商的标识代码.
*/
dprintf(("Probing 3c501 at 0x%x...\n",base));/*在调试时会打印出*/
/* 重置板卡 */
dprintf(("Resetting board...\n"));
outb(base+EL_AC,EL_AC_RESET);/*我们一般定义基地址为0X300,EL_AC=0E,是辅助命令寄存器*/
DELAY(5);/*延迟5毫秒*/
outb(base+EL_AC,0);
dprintf(("Reading station address...\n"));
/* 读硬件地址,共六次 */
for(i=0;i<ETHER_ADDR_LEN;i++) {
outb(base+EL_GPBL,i);
station_addr = inb(base+EL_EAW);/*EL_EAW是该卡的地址口,station_addr是函数内部变量,
下面判断了生产厂家后就没用的*/
}
dprintf(("Address is %6D\n",station_addr, ":"));
/* 如果厂商标识代码正确,那么返回1.
*/
if((station_addr[0] != 0x02) || (station_addr[1] != 0x60)
|| (station_addr[2] != 0x8c)) {
dprintf(("Bad vendor code.\n"));/*3COM厂商此种卡的代码为02608C*/
return(0);
} else {
dprintf(("Vendor code ok.\n"));
/* 把地址拷贝到arpcom结构中 */
bcopy(station_addr,sc->;arpcom.ac_enaddr,ETHER_ADDR_LEN);
return(1);
}
}
/* 这是一个子程序,目的是重设硬件. 在el_init()中调用,在elintr()中调用,产生中断,有溢出发生时调用*/
static __inline void
el_hardreset(xsc)
void *xsc;
{
register struct el_softc *sc = xsc;/*记住在C中,寄存器变量只能有三个,可加快速度*/
register int base;
register int j;
base = sc->;el_base;
/* 第一步,重设板卡,和el_probe中的一样(前面) */
outb(base+EL_AC,EL_AC_RESET);
DELAY(5);
outb(base+EL_AC,0);
/* 又把地址填回去,为什么?没有为什么,就是厂商规定的,一些端口填什么数据时会怎么样,只有厂商知道,我相信,
在同一厂商之间的网卡,交换机,路由器进行秘密通讯是非常可能的,他可以不返回到CPU层*/
for(j=0;j<ETHER_ADDR_LEN;j++)
outb(base+j,sc->;arpcom.ac_enaddr[j]);
}
/* 连接该接口到核心数据结构.被调用时,我们已经知道该卡已经存在在给定的I/O
* 地址,我们还假定中断号是正确的.
*/
static int
el_attach(struct isa_device *idev)
{
struct el_softc *sc;
struct ifnet *ifp;/*该结构是一个巨大的结构,在STEVEN的书中有描述,我也写了一篇*/
u_short base;/*没用上,可以去掉*/
dprintf(("Attaching el%d...\n",idev->;id_unit));
/* 放置一些指针. */
idev->;id_ointr = elintr;/*放置中断例程指针,中断例程在下面*/
sc = &el_softc[idev->;id_unit];/*定位本设备的softc结构指针*/
ifp = &sc->;arpcom.ac_if;/*定位ifnet结构*/
base = sc->;el_base;/*从程序来看,这一句可以去掉,根本没用,因为在该函数中没用到base*/
/* 重设板卡 */
dprintf(("Resetting board...\n"));
el_hardreset(sc);/*该程序在上面*/
/* 初始化ifnet结构,该结构的成员经常用来被ether网子程序,arp,bridge等调用 */
ifp->;if_softc = sc;/*该网卡的IFP(通用接口结构)的专用结构指针(softc结构)*/
ifp->;if_unit = idev->;id_unit;/*第几块网卡*/
ifp->;if_name = "el";/*网络卡的名称*/
ifp->;if_mtu = ETHERMTU;/*1500*/
ifp->;if_output = ether_output;/*以太网的输出子程序指针(不要搞错了,是向IP层
输出,按我们的理解是数据输入了,再转送到上一层协议)*/
ifp->;if_start = el_start;/*把数据包从硬件接口输出去*/
ifp->;if_ioctl = el_ioctl;/*控制网卡的函树指针*/
ifp->;if_watchdog = el_watchdog;/*一般该函数用于包在一定时间内没发送出去,就调用他,在
本驱动程序中并不支持该函数,在我的rtl8139说明中有*/
ifp->;if_init = el_init; /*不用说,是初始化,在probe,attach之后被调用*/
ifp->;if_flags = (IFF_BROADCAST | IFF_SIMPLEX);/*支持广播和单播*/
/* 调用通用以太网初始化例程 */
dprintf(("Attaching interface...\n"));
ether_ifattach(ifp, ETHER_BPF_SUPPORTED);
/*
在if_ethersubr.c中的ether_ifattach例程
void ether_ifattach(ifp, bpf) 调用时,ETHER_BPF_SUPPORTED是BSD的
包过滤器,如果在编译时设置文件没有
打开包过滤器,那么代表0,否则是1
register struct ifnet *ifp;
int bpf;
{
register struct ifaddr *ifa;
register struct sockaddr_dl *sdl;
if_attach(ifp); 此例程在if.c 中
ifp->;if_type = IFT_ETHER;代表以太网
ifp->;if_addrlen = 6;硬件地址长度是6
ifp->;if_hdrlen = 14;包的头长度是6+6+2=14,其中2是协议类型
ifp->;if_mtu = ETHERMTU; 为1500,多此一举,在前面你可看到,已
经填充了.
ifp->;if_resolvemulti = ether_resolvemulti; 以太网解析多播例程指针
if (ifp->;if_baudrate == 0) 波特率
ifp->;if_baudrate = 10000000;
ifa = ifnet_addrs[ifp->;if_index - 1];在ifnet_addrs[]数组中找到本地址指针
KASSERT(ifa != NULL, ("%s: no lladdr!\n", __FUNCTION__));
sdl = (struct sockaddr_dl *)ifa->;ifa_addr; ifa->;ifa_addr在此时指向的是sockaddr_dl结构.
sdl->;sdl_type = IFT_ETHER;
sdl->;sdl_alen = ifp->;if_addrlen;
bcopy((IFP2AC(ifp))->;ac_enaddr, LLADDR(sdl), ifp->;if_addrlen);把硬件地址拷贝到sdl结构中
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -