几经波折,在开发板上终于可以使用网络了。Linux内核可以通过网络挂接网络文件系统了。首先感谢Internet,Google等帮助过我的工具,还要感谢各位嵌友的无私奉献。在移植的过程中尤其感激weibing的博客文章cs8900移植linux-2.6.19.2,根据他的文章使cs8900
成功跑起来。此文章可以在http://weibing.blogbus.com/logs/4467465.html找到。
在解释网络驱动前,先说说自己的硬件配置:
1. 处理器为s3c2410
2. 网络芯片cs8900a
3. cs8900a映射到s3c2410的bank3空间
4. cs8900a占用int9号中断
5. Linux内核版本为2.6.20
一、初始化阶段
网络初始化被调用的路径为:
init->do_basic_setup->do_initcalls->net_olddevs_init->ethif_probe2->probe_list2->cs89x0_probe->cs89x0_probe1
真是不容易啊,终于进到cs89x0_probe1了,在这里开始探测和初始化cs8900了。下面就按照这个顺序来说明网络驱动第一阶段的工作。注意:这里的调用顺序是将cs8900驱动编入内核所产生的,如果将cs8900驱动选为模块,这个路径:init->do_basic_setup->do_initcalls->net_olddevs_init->ethif_probe2->probe_list2也会执行。
1.1 init函数
我们知道当start_kernel函数完成后就会启动init进程执行,在真正的应用程序init进程(如busybox的/sbin/init)之前,Linux还需要执行一些初始化操作。init的代码可以在\init\main.c中找到,它的代码如下:
static int init(void * unused)
{
lock_kernel();
…… //省略多cpu的初始化代码先
do_basic_setup(); //我们所关注的初始化函数
……
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace(); //挂接根文件系统
}
……
free_initmem(); //释放初始化代码的空间
unlock_kernel();
…… //这几段没看懂
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) //检查控制台
//console是否存在
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
……//这几段没看懂
if (ramdisk_execute_command) { //运行ramdisk_execute_command指定的init用户进程
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
……
if (execute_command) { //判断在启动时是否指定了init参数,如果指定,
//此值将赋给execute_command
run_init_process(execute_command); //开始执行用户init进程,如果成功将不会
//返回。
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
//如果没有指定init启动参数,则查找下面的目录init进程,如果找到则不会返回
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
//如果上面的程序都出错,则打印下面的信息,如果内核找到init进程,
//则程序不会指向到此处
panic("No init found. Try passing init= option to kernel.");
}
1.2 do_basic_setup函数
在这里我们最关心的是do_basic_setup函数,顾名思义该函数的功能就是“做基本设置”,它的实现代码也在\init\main.c中。do_basic_setup()完成外设及其驱动程序的加载和初始化。该函数代码如下所示:
static void __init do_basic_setup(void)
{
/* drivers will send hotplug events */
init_workqueues(); //初始化工作队列
usermodehelper_init(); //初始化khelper内核线程,还没弄清楚
driver_init(); //初始化内核的设备管理架构需要的数据结构,
//很复杂,以后在谈这部分。
#ifdef CONFIG_SYSCTL
sysctl_init(); //没搞懂
#endif
do_initcalls(); //重点函数,初始化的主要工作就靠它了
}
1.3 do_ initcalls函数
do_initcalls函数将会调用内核中所有的初始化函数,它的代码同样在\init\main.c中。do_initcalls函数调用其他初始化函数相当简洁,它的关键代码如下所示:
initcall_t *call;
for (call = __initcall_start; call < __initcall_end; call++) {
……
result = (*call)();
……
}
简洁归简洁,但这段代码是什么意思呢?这说来就话长了,最重要的应该是先了解Linux处理初始化的大体思想,由于Linux有很多部分需要初始化,每个部分都有自己的初始化函数,如果按照常理一个一个的调用未免显得冗长,而且也不便于扩展。那么Linux是怎么处理的呢?首先,Linux将各个部分的初始化函数编译到一个块内存区中,当初始化完了以后释放这块内存区,这就是init函数中free_initmem所要做的事。然后,再在另外的内存区中存放一些函数指针,让每个指针指向一个初始化函数。然后在do_initcalls中依次根据这些指针调用初始化函数。
上面一段就是Linux实现初始化的大体思想,下面我们看看它最终是怎么实现的。首先要了解的是__define_initcall宏,该宏的定义在\ include\linux\init.h中,它的原型如下所示:
#define __define_initcall(level,fn,id) static initcall_t __initcall_##fn##id __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
__define_initcall宏有三个参数,level表示初始化函数的级别,level值的大小觉得了调用顺序,level越小越先被调用,fn就是具体的初始化函数,id简单标识初始化函数,现在还没找到有什么用^_^。__define_initcall的功能为,首先声明一个initcall_t类型的函数指针__initcall_##fn##id,initcall_t的原型为:
typedef int (*initcall_t)(void);
该类型可简单理解为函数指针类型^_^。然后,让该函数指针指向fn。最后,通过编译器的编译参数将此指针放到指定的空间".initcall" level ".init"中,__attribute_used向编译器说明这段代码有用,即使在没用到的时候,编译器也不会警告。__attribute__的__section__参数表示该段代码放入什么内存区域中,也即指定编译到什么地方,编译参数更详细的地方可以查阅GCC文档,在gcc官方网站中能找到各个版本的手册。这样说来还是比较抽象,下面举个例子来说明:
假如有初始化函数init_foolish函数,现在使用__define_initcall宏向内核加入该函数。假如调用方式如下:
__define_initcall("0",init_foolish,1);
那么,__define_initcall宏首先申请一个initcall_t类型的函数指针__initcall_init_foolish1(注意替换关系),且使该指针指向了init_foolish,函数指针__initcall_init_foolish1被放到.initcall.0.init内存区域中,这个标志在连接时会用到。
有了上面的基础知识,现在回到do_initcalls函数中,首先注意到是__initcall_start和__initcall_end,它们的作用就是界定了存放初始化函数指针区域的起始地址,也即从__initcall_start开始到__initcall_end结束的区域中存放了指向各个初始化函数的函数指针。换句话说,只要某段程序代码从__initcall_start开始依次调用函数指针,那么就可以完成各个部分的初始化工作,这显得十分优雅而且便于扩充,再看看do_initcalls,它何尝不是如此呢。这里还有一个有用的技巧就是__initcall_start和__initcall_end的原型是initcall_t型的数组,以后可以使用这种技巧^_^。
现在我们知道了do_initcalls函数的实现原理,那么到底它调用了多少初始化函数呢?我们怎样才能知道呢?根据上面的分析,我们知道所有的初始化函数的指针都放在__initcall_start和__initcall_end区域期间,而函数指针与它指向的函数之间又有固定的关系,如上面的例子,初始化函数名为init_foolish,指向它的函数指针就是__initcall_init_foolish1,即在此函数加上前缀__initcall_和一个数字后缀,反之,从函数指针也可推出初始化函数名。有了这两个信息,我们就可以很方便的找个初始化函数。怎么找呢??首先打开Linux完后产生的System.map文件,然后找到__initcall_start和__initcall_end字符串,你会发现它们之间有很多类似于__initcall_xxx1这样的符号,这些符号就是我们需要的函数指针了,这样就可推出初始化函数的名字。比如,我们这里需要的函数指针__initcall_net_olddevs_init6,按照上面的名字规则,很容易推出它所指向的初始化函数名字是net_olddevs_init。
得到了初始化函数的名字又怎么样呢?又不知道它在哪个文件里,不要着急!请打开你的浏览器登陆网站,然后选择Linux版本和架构,然后可以搜索我们想要的信息。比如我输入net_olddevs_init,然后我就会得到该函数所在文件的相关信息。
1.4 net_olddevs_init函数
我们知道net_olddevs_init函数在do_initcalls函数中被调用并执行,那么它到底要做什么呢?看看实现代码就知道了,它的实现代码可以在\drivers\net\Space.c中找到。对于网络驱动部分的主要实现代码如下:
static int __init net_olddevs_init(void){
……
int num;
for (num = 0; num < 8; ++num)
ethif_probe2(num);
……
}
这段代码就不用讲解了吧,嘿嘿!就是调用了8次ethif_probe2,赶快去看看ethif_probe2长什么样子。
1.5 ethif_probe2函数
先看看该函数的实现代码,该代码也在\drivers\net\Space.c文件中。
static void __init ethif_probe2(int unit)
{
unsigned long base_addr = netdev_boot_base("eth", unit); // 由于ethif_probe2被
//net_olddevs_init调用了8次,
// 所以unit的值为0~7,也即在这里可以注册eth0~eth7八个网络设备
if (base_addr == 1)
return;
(void)( probe_list2(unit, m68k_probes, base_addr == 0) &&
probe_list2(unit, eisa_probes, base_addr == 0) &&
probe_list2(unit, mca_probes, base_addr == 0) &&
probe_list2(unit, isa_probes, base_addr == 0) &&
probe_list2(unit, parport_probes, base_addr == 0));
}
该函数首先调用netdev_boot_base所给的设备是否已经向内核注册,如果已注册netdev_boot_base返回1,随后推出ethif_probe2。如果设备没注册,则又调用函数probe_list2四次,每次传递的传输不同,注意到每次传递的第二个参数不同,这个参数也是相当重要的,这里拿isa_probes参数为例说明,因为这个参数与cs89x0_probe有关,isa_probes的定义也在\drivers\net\Space.c中,它的样子形如:
static struct devprobe2 isa_probes[] __initdata = {
……
#ifdef CONFIG_SEEQ8005
{seeq8005_probe, 0},
#endif
#ifdef CONFIG_CS89x0
{cs89x0_probe, 0},
#endif
#ifdef CONFIG_AT1700
{at1700_probe, 0},
#endif
{NULL, 0},
……
};
如果把cs8900的驱动选为非编译进内核,那么它的探测函数cs89x0_probe就不会存在于isa_probes数组中,所以在初始阶段就不能被调用。从上面的代码可以知道devprobe2类型至少包括两个域,至少一个域为函数指针,看看它的原型如下:
struct devprobe2 {
struct net_device *(*probe)(int unit); //函数指针,指向探测函数
int status; /* non-zero if autoprobe has failed */
};
下面看看probe_list2函数是怎么表演的。
1.6 ethif_probe2函数
对于ethif_probe2函数也没有什么需要说明的,它的主要任务是依次调用devprobe2类型的probe域指向的函数。他的实现代码同样在\drivers\net\Space.c中,它的关键代码如下:
static int __init probe_list2(int unit, struct devprobe2 *p, int autoprobe)
{
struct net_device *dev;
for (; p->probe; p++) {
……
dev = p->probe(unit);
……
}
……
}
1.7 cs89x0_probe函数
从该函数起,真正开始执行与cs8900驱动初始化程序,该函数在\drivers\net\cs89x0.c文件实现。下面依次解释该函数。
struct net_device * __init cs89x0_probe(int unit)
{
struct net_device *dev = alloc_etherdev(sizeof(struct net_local)); //该函数申请一个net_device+
//sizeof(struct net_local)的空间,net_local是cs8900驱动的私有数据空间。
unsigned *port;
int err = 0;
int irq;
int io;
if (!dev)
return ERR_PTR(-ENODEV);
sprintf(dev->name, "eth%d", unit); //初始化dev->name域
netdev_boot_setup_check(dev); //检查是否给定了启动参数,如果给定了
//启动参数,此函数将初始化dev的irq、
//base_addr、mem_start和mem_end域。
io = dev->base_addr; //io实际实质cs8900所占地址空间的起始地址,
//此地址为虚拟地址
irq = dev->irq;
if (net_debug)
printk("cs89x0:cs89x0_probe(0x%x)\n", io);
//下面根据io的值调用cs89x0_probe1函数
if (io > 0x1ff) {/* Check a single specified location. */ //此段没搞懂,由于没给
//启动参数,这里也不会执行
err = cs89x0_probe1(dev, io, 0);
} else if (io != 0) { /* Don't probe at all. */
err = -ENXIO;
} else {
for (port = netcard_portlist; *port; port++) {// netcard_portlist为unsigned int型数组,在cs89x0.c文件中定
//义,里面列出了cs8900可能占用空间的起始地址,这些地址
//将在cs89x0_probe1函数中用于向内核申请。
if (cs89x0_probe1(dev, *port, 0) == 0) // cs89x0_probe1探测成功就返回0
break;
dev->irq = irq;
}
if (!*port)
err = -ENODEV;
}
if (err)
goto out;
return dev;
out:
free_netdev(dev); //表示探测失败,这里就释放dev的空间,随后打印些消息
printk(KERN_WARNING "cs89x0: no cs8900 or cs8920 detected. Be sure to disable PnP with SETUP\n");
return ERR_PTR(err);
}
从上面的程序清单可以看到该函数还没有真正的开始探测cs8900,实质的探测工作是让cs89x0_probe1完成的。在解释cs89x0_probe1之前先提一下网络驱动程序中非常重要的一些函数。内核需要一个数据结构来管理或者描述每个网络驱动程序,这个数据类型就是struct net_device,该数据类型包括很多域,详细的解释可以参见《Linux 设备驱动程序》一书中的描述,也可以参见源代码(在\include\linux\netdevice.h中,源码中也有详细的注解)。内核为了编程方便特地实现了函数alloc_netdev来完成对net_device的空间分配。那么alloc_etherdev函数主要针对以太网在alloc_netdev基础上封装的一个函数,它除了申请net_device空间外,还会初始化net_device的相关域。
1.8 cs89x0_probe1函数
对于该函数完成了最终的网络芯片cs8900的探测工作,里面涉及了一些芯片硬件的操作,看这个源码之前应该对cs8900a芯片比较熟悉,或者在读的时候把它的芯片manual打开。这函数的代码很长,大约有300多行,但是它没有什么特别的技巧,只要认真阅读,最多半天就能搞明了^_^,下面给出该函数在ARM架构下,且没开DMA情况下的注解。
static int __init cs89x0_probe1(struct net_device *dev, int ioaddr, int modular)
{
struct net_local *lp = netdev_priv(dev); //dev 空间已经在cs89x0_probe中申请成功,
//这里lp从dev中得到自己的私有数据,也即net_local数据域的起始地址,
//netdev_priv 函数为网络驱动中得到私有数据的标准函数,当然也可以直接
//使用dev->priv,但不鼓励这种做法。
//下面申请些局部变量
static unsigned version_printed;
int i;
int tmp;
unsigned rev_type = 0;
int eeprom_buff[CHKSUM_LEN];
int retval;
SET_MODULE_OWNER(dev); // 设置模块的属于者,该宏定义在
//include\linux\netdevice文件中,实际为do{}while(0)
/* Initialize the device structure. */
if (!modular) { //这里的modular为0,由cs89x0_probe传入
memset(lp, 0, sizeof(*lp)); //将lp填充为0
spin_lock_init(&lp->lock); //初始化自旋锁,自旋锁用于保护dev结构的互斥访问
#ifndef MODULE //在make menuconfig时确定,表示是否将网络驱动编译为模块。
#if ALLOW_DMA //是否启用了DMA
if (g_cs89x0_dma) {
lp->use_dma = 1;
lp->dma = g_cs89x0_dma;
lp->dmasize = 16; /* Could make this an option... */
}
#endif
lp->force = g_cs89x0_media__force;
#endif
}
......
/* Grab the region so we can find another board if autoIRQ fails. */
/* WTF is going on here? */
// request_region函数向内核注册io地址空间,这里NETCARD_IO_EXTENT=16
// 所以可以看出cs8900工作在I/O模式。cs8900在memory模式需要映射4k空间
if (request_region(ioaddr & ~3, NETCARD_IO_EXTENT, DRV_NAME)==NULL) {
printk(KERN_ERR "%s: request_region(0x%x, 0x%x) failed\n",
DRV_NAME, ioaddr, NETCARD_IO_EXTENT);
retval = -EBUSY;
goto out1;
}
......
/* if they give us an odd I/O address, then do ONE write to
the address port, to get it back to address zero, where we
expect to find the EISA signature word. An IO with a base of 0x3
will skip the test for the ADD_PORT. */
//下面这段代码比较费解,不是说代码的意思不好解释,而是为什么只在寄地址
//才检查呢?根据数据手册的说明“The CS8900A reads 3000h from IObase+0Ah after
//the reset, until the software writes a non-zero value at IObase+0Ah. The
//3000h value can be used as part of the CS8900A signature when the system
//scans for the CS8900A.”从这段话可知,这只能作为扫描到cd8900存在部分的依据;
//从后面的代码中可以看到,还需要确定cs8900的ID号后才能真正确保cs8900存在。
if (ioaddr & 1) {
if (net_debug > 1)
printk(KERN_INFO "%s: odd ioaddr 0x%x\n", dev->name, ioaddr);
if ((ioaddr & 2) != 2)
if ((readword(ioaddr & ~3, ADD_PORT) & ADD_MASK) != ADD_SIG) {
printk(KERN_ERR "%s: bad signature 0x%x\n",
dev->name, readword(ioaddr & ~3, ADD_PORT));
retval = -ENODEV;
goto out2;
}
}
ioaddr &= ~3;
printk(KERN_DEBUG "PP_addr at %x[%x]: 0x%x\n",
ioaddr, ADD_PORT, readword(ioaddr, ADD_PORT));
writeword(ioaddr, ADD_PORT, PP_ChipID); //这里表示扫描到cs8900,
//按照数据手册写0
//下面这段代码确定cs8900的EISA ID号是否为0x630E。这里DATA_PORT=0x0C是
//cs8900的数据口,CHIP_EISA_ID_SIG=0x630E,0x630E为Crystal公司在EISA的注册
//号。通过下面的检查以后就真正确定了cs8900存在,硬件电路ok。
//以及向内核注册的端口地址ok。
tmp = readword(ioaddr, DATA_PORT);
if (tmp != CHIP_EISA_ID_SIG) {
printk(KERN_DEBUG "%s: incorrect signature at %x[%x]: 0x%x!="
CHIP_EISA_ID_SIG_STR "\n",
dev->name, ioaddr, DATA_PORT, tmp);
retval = -ENODEV;
goto out2;
}
/* Fill in the 'dev' fields. */
dev->base_addr = ioaddr;
/* get the chip type */
rev_type = readreg(dev, PRODUCT_ID_ADD);//rev_type=0x0a00,这个值是实际
//测试出来的,但根据cs8900A的数据手册,该值应该是0x0700。???
lp->chip_type = rev_type &~ REVISON_BITS;
lp->chip_revision = ((rev_type & REVISON_BITS) >> 8) + 'A';
//执行上面赋值后lp->chip_type=0x0,lp->chip_revision=0x4b。注意这里的加法运算
//rev_type & REVISON_BITS)>>8=0x0a,这个0x0a是数字,'A'转换成十
//六进制后为0x41,所以,0x41+0x0a=0x4b,0x4b在ascii码中对应的字母为'K'
/* Check the chip type and revision in order to set the correct send command
CS8920 revision C and CS8900 revision F can use the faster send. */
lp->send_cmd = TX_AFTER_381; //默认每次传输381字节,
//根据数据手册可以传输的字节
//选项有5、381、1021、all四个,但这里的驱动不支持1021字节的选项。
if (lp->chip_type == CS8900 && lp->chip_revision >= 'F')//此条件满足
lp->send_cmd = TX_NOW;//选择每次传输5字节
if (lp->chip_type != CS8900 && lp->chip_revision >= 'C')
lp->send_cmd = TX_NOW;
if (net_debug && version_printed++ == 0)
printk(version);
printk(KERN_INFO "%s: cs89%c0%s rev %c found at %#3lx ",
dev->name,
lp->chip_type==CS8900?'0':'2',
lp->chip_type==CS8920M?"M":"",
lp->chip_revision,
dev->base_addr);//按照上面的分析,这里打印的应该形如:
//cs89x0.c: v2.4.3-pre1 Russell Nelson ,
//Andrew Morton eth0: cs8900 rev K found at 0xf4000300
reset_chip(dev); //重新复位cs8900a
/* Here we read the current configuration of the chip. If there
is no Extended EEPROM then the idea is to not disturb the chip
configuration, it should have been correctly setup by automatic
EEPROM read on reset. So, if the chip says it read the EEPROM
the driver will always do *something* instead of complain that
adapter_cnf is 0. */
......
//以下代码一直到printk(KERN_INFO "cs89x0 media %s%s%s",功能为
//从EEPROM中读出配置信息,并填充dev结构的相关域。
if ((readreg(dev, PP_SelfST) & (EEPROM_OK | EEPROM_PRESENT)) ==
(EEPROM_OK|EEPROM_PRESENT)) {//读取SelfST寄存器,并判断EEPROM
//是否存在若存在,则判断是否读取操作成功。上述条件满足,
//则读出EEPROM中的配置信息填充dev相关域。
/* Load the MAC. */
for (i=0; i < ETH_ALEN/2; i++) {//读取以太网地址
unsigned int Addr;
Addr = readreg(dev, PP_IA+i*2);
dev->dev_addr[i*2] = Addr & 0xFF;
dev->dev_addr[i*2+1] = Addr >> 8;
}
/* Load the Adapter Configuration.
Note: Barring any more specific information from some
other source (ie EEPROM+Schematics), we would not know
how to operate a 10Base2 interface on the AUI port.
However, since we do read the status of HCB1 and use
settings that always result in calls to control_dc_dc(dev,0)
a BNC interface should work if the enable pin
(dc/dc converter) is on HCB1. It will be called AUI
however. */
lp->adapter_cnf = 0;
i = readreg(dev, PP_LineCTL); //读取LineCTL寄存器,
//确定MAC配置和物理接口
/* Preserve the setting of the HCB1 pin. */
if ((i & (HCB1 | HCB1_ENBL)) == (HCB1 | HCB1_ENBL))
lp->adapter_cnf |= A_CNF_DC_DC_POLARITY;
/* Save the sqelch bit */
if ((i & LOW_RX_SQUELCH) == LOW_RX_SQUELCH)
lp->adapter_cnf |= A_CNF_EXTND_10B_2 | A_CNF_LOW_RX_SQUELCH;
/* Check if the card is in 10Base-t only mode */
if ((i & (AUI_ONLY | AUTO_AUI_10BASET)) == 0)
lp->adapter_cnf |= A_CNF_10B_T | A_CNF_MEDIA_10B_T;
/* Check if the card is in AUI only mode */
if ((i & (AUI_ONLY | AUTO_AUI_10BASET)) == AUI_ONLY)
lp->adapter_cnf |= A_CNF_AUI | A_CNF_MEDIA_AUI;
/* Check if the card is in Auto mode. */
if ((i & (AUI_ONLY | AUTO_AUI_10BASET)) == AUTO_AUI_10BASET)
lp->adapter_cnf |= A_CNF_AUI | A_CNF_10B_T |
A_CNF_MEDIA_AUI | A_CNF_MEDIA_10B_T | A_CNF_MEDIA_AUTO;
if (net_debug > 1)
printk(KERN_INFO "%s: PP_LineCTL=0x%x, adapter_cnf=0x%x\n",
dev->name, i, lp->adapter_cnf);
/* IRQ. Other chips already probe, see below. */
if (lp->chip_type == CS8900)
lp->isa_config = readreg(dev, PP_CS8900_ISAINT) & INT_NO_MASK;
printk( "[Cirrus EEPROM] ");
}
printk("\n");
/* First check to see if an EEPROM is attached. */
......//以下检查EEPROM的相关信息
if ((readreg(dev, PP_SelfST) & EEPROM_PRESENT) == 0)//是否EEPROM存在
printk(KERN_WARNING "cs89x0: No EEPROM, relying on command line....\n");
else if (get_eeprom_data(dev, START_EEPROM_DATA,CHKSUM_LEN,eeprom_buff) < 0) {
//读取RRPROM失败
printk(KERN_WARNING "\ncs89x0: EEPROM read failed, relying on command line.\n");
} else if (get_eeprom_cksum(START_EEPROM_DATA,CHKSUM_LEN,eeprom_buff) < 0) {
/* Check if the chip was able to read its own configuration starting
at 0 in the EEPROM*/
if ((readreg(dev, PP_SelfST) & (EEPROM_OK | EEPROM_PRESENT)) !=
(EEPROM_OK|EEPROM_PRESENT))
printk(KERN_WARNING "cs89x0: Extended EEPROM checksum bad and no Cirrus EEPROM, relying on command line\n");
} else {
/* This reads an extended EEPROM that is not documented
in the CS8900 datasheet. 扩展配置*/
/* get transmission control word but keep the autonegotiation bits */
if (!lp->auto_neg_cnf) lp->auto_neg_cnf = eeprom_buff[AUTO_NEG_CNF_OFFSET/2];
/* Store adapter configuration */
if (!lp->adapter_cnf) lp->adapter_cnf = eeprom_buff[ADAPTER_CNF_OFFSET/2];
/* Store ISA configuration */
lp->isa_config = eeprom_buff[ISA_CNF_OFFSET/2];
dev->mem_start = eeprom_buff[PACKET_PAGE_OFFSET/2] << 8;
/* eeprom_buff has 32-bit ints, so we can't just memcpy it */
/* store the initial memory base address */
for (i = 0; i < ETH_ALEN/2; i++) {
dev->dev_addr[i*2] = eeprom_buff[i];
dev->dev_addr[i*2+1] = eeprom_buff[i] >> 8;
}
if (net_debug > 1)
printk(KERN_DEBUG "%s: new adapter_cnf: 0x%x\n",
dev->name, lp->adapter_cnf);
}
/* allow them to force multiple transceivers. If they force multiple, autosense */
{
int count = 0;
if (lp->force & FORCE_RJ45) {lp->adapter_cnf |= A_CNF_10B_T; count++; }
if (lp->force & FORCE_AUI) {lp->adapter_cnf |= A_CNF_AUI; count++; }
if (lp->force & FORCE_BNC) {lp->adapter_cnf |= A_CNF_10B_2; count++; }
if (count > 1) {lp->adapter_cnf |= A_CNF_MEDIA_AUTO; }
else if (lp->force & FORCE_RJ45){lp->adapter_cnf |= A_CNF_MEDIA_10B_T; }
else if (lp->force & FORCE_AUI) {lp->adapter_cnf |= A_CNF_MEDIA_AUI; }
else if (lp->force & FORCE_BNC) {lp->adapter_cnf |= A_CNF_MEDIA_10B_2; }
}
if (net_debug > 1)
printk(KERN_DEBUG "%s: after force 0x%x, adapter_cnf=0x%x\n",
dev->name, lp->force, lp->adapter_cnf);
/* FIXME: We don't let you set dc-dc polarity or low RX squelch from the command line: add it here */
/* FIXME: We don't let you set the IMM bit from the command line: add it to lp->auto_neg_cnf here */
/* FIXME: we don't set the Ethernet address on the command line. Use
ifconfig IFACE hw ether AABBCCDDEEFF */
printk(KERN_INFO "cs89x0 media %s%s%s",//如果没有EEPROM,将打印单个空格
(lp->adapter_cnf & A_CNF_10B_T)?"RJ-45,":"",
(lp->adapter_cnf & A_CNF_AUI)?"AUI,":"",
(lp->adapter_cnf & A_CNF_10B_2)?"BNC,":"");
lp->irq_map = 0xffff;
/* If this is a CS8900 then no pnp soft */
if (lp->chip_type != CS8900 &&
/* Check if the ISA IRQ has been set */
(i = readreg(dev, PP_CS8920_ISAINT) & 0xff,
(i != 0 && i < CS8920_NO_INTS))) {//非cs8900芯片
if (!dev->irq)
dev->irq = i;
} else {
i = lp->isa_config & INT_NO_MASK;//由于没有EEPROM,所以lp->isa_config=0
if (lp->chip_type == CS8900) {
/* Translate the IRQ using the IRQ mapping table. */
if (i >= sizeof(cs8900_irq_map)/sizeof(cs8900_irq_map[0]))
//sizeof(cs8900_irq_map)/sizeof(cs8900_irq_map[0])求cs8900_irq_map数据元个数
printk("\ncs89x0: invalid ISA interrupt number %d\n", i);
else
i = cs8900_irq_map[i];//i保存了中断号
lp->irq_map = CS8900_IRQ_MAP; /* fixed IRQ map for CS8900 */
} else {
int irq_map_buff[IRQ_MAP_LEN/2];
if (get_eeprom_data(dev, IRQ_MAP_EEPROM_DATA,
IRQ_MAP_LEN/2,
irq_map_buff) >= 0) {
if ((irq_map_buff[0] & 0xff) == PNP_IRQ_FRMT)
lp->irq_map = (irq_map_buff[0]>>8) | (irq_map_buff[1] << 8);
}
}
if (!dev->irq)
dev->irq = i;//填充dev->irq,按照前面的定义该值为53
}
printk(" IRQ %d", dev->irq);
#if ALLOW_DMA
if (lp->use_dma) {
get_dma_channel(dev);
printk(", DMA %d", dev->dma);
}
else
#endif
{
printk(", programmed I/O");
}
/* print the ethernet address. */
printk(", MAC");
for (i = 0; i < ETH_ALEN; i++)
{
printk("%c%02x", i ? ':' : ' ', dev->dev_addr[i]);
}
//指定相关cs8900支持的相关操作
dev->open = net_open; //打开接口,该函数应该注册所有的系统资源
dev->stop = net_close; //停止接口,该函数执行的操作与open相反
dev->tx_timeout = net_timeout; //传输超时时,将调用此函数
dev->watchdog_timeo= HZ; //在网络层确定传输超时,调用tx_timeout前的最小延时
dev->hard_start_xmit = net_send_packet; //该方法初始化数据包传输。完整的数据包在sk_buffer中
dev->get_stats = net_get_stats; //获得接口的统计信息
dev->set_multicast_list = set_multicast_list; //当组播列表发生改变,或者设备标志发
//生改变时,将调用该方法
dev->set_mac_address = set_mac_address; //设置硬件的地址
#ifdef CONFIG_NET_POLL_CONTROLLER
dev->poll_controller = net_poll_controller; //该方法在进制中断的情况下,
//要求驱动程序在接口上检查事件。它被用于特定的内核网络中,比如远程控制台
//和内核网络调试。
#endif
printk("\n");
if (net_debug)
printk("cs89x0_probe1() successful\n");
retval = register_netdev(dev);//向内核注册cs8900驱动程序
if (retval)
goto out3;
return 0;
out3:
writeword(dev->base_addr, ADD_PORT, PP_ChipID);
out2:
release_region(ioaddr & ~3, NETCARD_IO_EXTENT);
out1:
return retval;
}
1.9 一些问题总结
这里没有讲解cs8900驱动的移植过程,需要移植的朋友可以参见前面提到的weibing的博客文章。这里需要补充的是很多朋友在移植成功了以后,发现内核会打印出如下的消息:
cs89x0_probe1() successful
cs89x0:cs89x0_probe(0x0)
cs8900a: request_region(0xf4000300, 0x10) failed
cs89x0: no cs8900 or cs8920 detected. Be sure to disable PnP with SETUP
该消息的很奇怪,先是说cs89x0_peobe1成功,后面又提示说失败,而且没有影响网络驱动的功能,这时为什么呢?回忆在net_olddevs_init函数时,它调用了8次ethif_probe2函数,也就是说cs89x0_peobe1不被调用了一次,第一次成功了,后面的肯定会失败,如果按照这种思路,那应该会打印7次失败信息,而这里只有一次,不解ing!这个问题也可以简单的解决,我采用了下面的方法解决此问题,判断cs89x0_probe的参数是否大于0,如果大于0就直接退出,这使得cs89x0_probe函数只正常执行一次,这样处理以后就没有提示失败的信息。
To be continued……
------ anmnmnly
------ 2007.11.30