linux四层概念模型:应用层,传输层,网际层,网络接口层
网卡设备驱动工作与网络接口层
what we care
net_device
{
name
base_addr//IO 基地址
irq //中断号
net_device_ops
{
netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb,
struct net_device *dev);//发送数据
}
}
申请net_dev : alloc_etherdev(sizeof_priv)
注册 :register_netdev(struct net_device * dev)
注销 :unregister_netdev(struct net_device * dev)
//发送数据
(*ndo_start_xmit) (struct sk_buff *skb,struct net_device *dev);
接收:将网卡芯片收到的数据copy到DDRAM,形成一个struct sk_buff的结构,然后调用
netif_rx(...),驱动程序中的接收工作便完成了
sk_buff(驱动层发送,提交数据基本单元)
释放: dev_kfree_skb(...)
申请: dev_alloc_skb(...)
试着写一个虚拟网卡测试程序:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <mach/gpio.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/irq.h>
#include <asm/bitops.h>
static struct net_device *virnetdev = NULL;
/******** int (*ndo_init)(struct net_device *dev)******/
static int vir_netdev_init(struct net_device *dev)
{
printk("enter net dev init\n");
// initial set
ether_setup(dev);
strcpy(dev->name,"vir_net");
return 0;
}
/******** int (*ndo_open)(struct net_device *dev)*******/
static int vir_netdev_open(struct net_device *dev)
{
printk("enter net dev open\n");
return 0;
}
/******** netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev) ********/
static netdev_tx_t vir_netdev_xmit(struct sk_buff *skb, struct net_device *dev)
{
printk("enter net dev xmit\n");
//send data
// free buff
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
static struct net_device_ops vir_netdev_ops =
{
.ndo_init = vir_netdev_init,
.ndo_open = vir_netdev_open,
.ndo_start_xmit = vir_netdev_xmit,
};
int __init virnet_init(void)
{
// 1.alloc net device
virnetdev =alloc_etherdev(0);
// 2. full in the dev
virnetdev->netdev_ops =&vir_netdev_ops;
// 3 .register
register_netdev(virnetdev);
return 0;
}
void __exit virnet_exit(void)
{
unregister_netdev(virnetdev);
free_netdev(virnetdev);
}
module_init(virnet_init);
module_exit(virnet_exit);
MODULE_LICENSE("GPL");
运行效果:
初始时只有一块网卡设备eth0.
安装模块后:
vir_net网卡显示了出来
配置IP地址
ping命令测试.ping 命令并没有进入到该虚拟网卡的发送函数中,因为没有打印调试信息.
DM9000A网卡驱动移植要点:
其实当做上面的实验时,因为有用到NFS文件系统,本身内核中就已经将DM9000A的网卡驱动移植好了,但一直没有拿出来做笔记。
DM9000A
1) 10/100M 自适应
2) 8bit/16 bit 模式可配置
3) 内部有16k sram
4) 片选CS#(低电平有效.default:low, pin37)
5) CMD管脚(low:index port,high:data port pin 32)
6) INT 中断管脚 pin34
7) EECK (pin 20,high: int pin active low ,else int pin active high)
8) EECS (pin 21, EECS 如果被拉高,则bus工作于8bit mode,否则工作于16bit)
9) DM9000A每个寄存器的意义?
10)如何往DM9000A的寄存器里面写入/读取数据?REGTCR:TCR(0x02)写入0x55
index:0x02 data:0x55
1.保证片选: 基地址0x88000000 ,cs拉低
将0x80000000送到地址总线上去,此时bit2=0;
2.cmd 为低(连接到addr2上):传送0x02
volatile unsigned int *p =0x88000000
*p=0x02
cmd 为高:传送0x55
volatile unsigned int *p =0x88000004
*p=0x55
11)数据收发:chapter 9
a)将要发送的数据通过DMA方式写入TX SRAM
b)将要发送数据长度写入0xfc,0xfd
c)control reg bit 1
3)硬件原理图
1)DM9000A 的SD0-SD15连接到CPU的数据总线上
2)CMD管脚连接到 cpu的地址总线的ADDR bus的bit2
3)CS连接到CPU的xm0_csn1上 bank1:(0x8800_0000 -0x8FFF_FFFF)DM9000挂在cpu的bank1上,当你的cpu的地址总线上出现的值在 rang(0x8800_0000 -0x8FFF_FFFF)
都会导致 xm0_csn1变为低电平,也就是选中DM9000芯片
4) INT —EINT7
5) EECK low : 中断高电平有效
6) EECS low :16bit mode
网卡的驱动是基于linux平台总线设备模型的,一般情况下,这类驱动不需要修改对应的driver本身,只需要修改对应的resource结构和platform_data即可。当然要弄清楚这部分怎么修改,本身也是比较耗时的事情,尤其是弄懂其中的意义。当然需要阅读芯片手册,开发板的原理图,s5pv210的芯片手册,内核中关于dm9000a的驱动代码来分析。
//S5PV210_PA_SROM_BANK1 Bank1 base addr
static struct resource smdkv210_dm9000_resources[] = {
[0] = DEFINE_RES_MEM(S5PV210_PA_SROM_BANK1, 1),//write index addr
[1] = DEFINE_RES_MEM(S5PV210_PA_SROM_BANK1 + 4, 4),//write data
[2] = DEFINE_RES_NAMED(IRQ_EINT(7), 1, NULL, IORESOURCE_IRQ \
| IORESOURCE_IRQ_HIGHLEVEL),
};
static struct dm9000_plat_data smdkv210_dm9000_platdata = {
.flags = DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM,
/* try reading the node address from the attached EEPROM ,the address for dm9000 read its mac or others use,we dont use it*/
.dev_addr = { 0x00, 0x09, 0xc0, 0xff, 0xec, 0x48 },
};
static void __init smdkv210_dm9000_init(void)
{
unsigned int tmp;
gpio_request(S5PV210_MP01(1), "nCS1");
s3c_gpio_cfgpin(S5PV210_MP01(1), S3C_GPIO_SFN(2));
gpio_free(S5PV210_MP01(1));
tmp = (5 << S5P_SROM_BCX__TACC__SHIFT);
//S5P_SROM_BC1:Specifies the SROM Bank1 control register
//set the BC1 Access cycle: tacc 5 clock
__raw_writel(tmp, S5P_SROM_BC1);
//S5P_SROM_BW:Specifies the SROM Bus width & wait control
tmp = __raw_readl(S5P_SROM_BW);
//just get the bit : bit7-bit4,other bit clear to 0
tmp &= (S5P_SROM_BW__CS_MASK << S5P_SROM_BW__NCS1__SHIFT);
//set bank1 16 bit mode
tmp |= (1 << S5P_SROM_BW__NCS1__SHIFT);
__raw_writel(tmp, S5P_SROM_BW);
}
这边用到两个reg的设定,具体参考:
DM9000a内部有一个eeprom用于存放一些固定的信息,比如说mac地址。这边我们并未用这个.我们在配置它的platform_data时,也强制配置成no eeprom的模式,所以下一行的 dev ddr也就不用care,以为这是大致用来操作它本身的
eeprom的,没用到,自然可以不关心。所以写到这里,我们已经可以正常使用网卡收发数据了。当然能进入nfs文件系统本身就说明网卡的驱动是OK的.
/* try reading the node address from the attached EEPROM */
for (i = 0; i < 6; i += 2)
dm9000_read_eeprom(db, i / 2, ndev->dev_addr+i);
if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) {
mac_src = "platform data";
memcpy(ndev->dev_addr, pdata->dev_addr, 6);
}
if (!is_valid_ether_addr(ndev->dev_addr)) {
/* try reading from mac */
mac_src = "chip";
for (i = 0; i < 6; i++)
ndev->dev_addr[i] = ior(db, i+DM9000_PAR);
}
if (!is_valid_ether_addr(ndev->dev_addr)) {
dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please "
"set using ifconfig\n", ndev->name);
eth_hw_addr_random(ndev);
mac_src = "random";
}
实际上翻阅dm9000a的代码,它调用dev_adr是用来操作dm9000的eeprom的。
关于驱动,因为是基于平台总线的,我们移植时需要关系的只是对应的 资源列表和platform_data平台数据。这些都是与硬件连接相关的最底层配置。反而网卡driver那边一般是不需要配置的。这就是平台驱动,设备,总线模型开发驱动的好处.