本次比较特殊,一次介绍两个Linux子系统,主要原因是和以往的DM9000接口不同,DM9621是USB接口的.
我们先来看一下tiny4412上DM9621的接口特性,如下是DM9621的电路图:
如上图青靛色的部分,它是连接在外接的USB Hub上的,USB Hub的型号是USB4604,电路连接如下:
从上面的电路连接结构可以看出,usb网卡连接在图中青靛色地方,还有两组是扩展USB接口,另外一组排针引出,在途中品红色方框里是USB Hub和芯片连接的引脚,它没有采用USB连接,而是采用了HSIC接口,这种接口和USB接口的信号传输方式不同,USB是差分传输,类似于CAN总线,而HSIC则通过STORBE线和DATA线进行数据的传输.既然友善之臂采用这个接口,那么就在芯片引出后不得不采用USB Hub一类的带有HSIC转USB的器件进行转接,因为两种信号不兼容.但是电缆连接拓扑结构是兼容的.
关于HSIC的一些介绍:
HSIC 是一个两线的源同步穿行接口,使用 240MHz 双倍数据速率来产生 480Mbps的告诉数据速率,和传统 USB 电缆连接拓补结构的主机完全兼容。HSIC 接口不支持中速和低速 USB 转换。
- 480Mbps 高速数据速率
- 源同步串行接口
- 不传输时不耗电
- 最长线路长度位 10cm
- 不支持热插拔
- 信号驱动在 1.2V 标准 LVCMOS 水平
- 为低功耗应用设计
- 不支持高速线性调频协议,HSIC 只能工作在高速状态
- HSIC 可以代替 I2C
- I2C 不够快,而且还需要特殊驱动
- HSIC 允许 USB 软件复用
- PHY 可以复用或适应现存的 PHY 技术
然后,我们看一下Exynos4412 SoC上的USB控制器的架构:
从上面的架构可以看出,CPU的USB控制器是支持1路USB输出和两路HSIC输出的,tiny4412采用了第二组HSIC外接USB Hub.别看这个框架很清晰,涉及到的寄存器特别的多,所以驱动适配器层的驱动程序,一般都是CPU原厂给出的,本次也不例外,我们本次开发驱动主要是驱动的控制器层,这一层是和特定设备相关的,适配器层则是通用的,依照USB协议来完成的.由于本次涉及到的知识比较多,篇幅上面也很长,所以,就不细讲Exynos的USB控制器的架构了.
因为DM9621是USB接口的网卡,所以,驱动程序首先是注册USB驱动,然后在USB驱动的基础上,注册net驱动,所以,我们大致地先讲一下USB子系统,然后再大致讲一下网络子系统,再之后,我们就写一个驱动来测试.
USB子系统:
如上图是USB子系统的框架,我们需要开发的是USB客户驱动程序那一层,关于操作具体地USB控制器寄存器相关的驱动原厂已经提供了,属于USB主机控制器驱动程序那一层,这两层之间靠USB核心层来进行匹配的,我们来讲一下这个流程和上图的组成部分:
1).主体框架:包括USB客户驱动程序,USB核心层驱动程序和USB主机控制器驱动程序.因为USB设备是多种多样的,比如USB蓝牙,USB网卡,USB存储设备,这些不同的USB设备需要不同的USB驱动来完成不同的功能,这些不同的驱动就是USB客户驱动程序,假设USB控制器主机上只有一个,驱动这个USB控制器的驱动也就一个就可以搞定,主要的区别就是USB客户驱动程序,我们插入不同的设备,就需要匹配不同的USB客户驱动程序,这个任务我们专门用USB核心层来完成,USB客户驱动程序和USB主机控制器程序都注册仅USB核心层,然后,根据不同的设备信息匹配不同的设备驱动;
2).USB核心:USB核心由一些基础代码组成,这些基础代码包括结构体和函数定义,共HCD和客户驱动程序使用,同时也间接地使得客户驱动程序与具体的主机控制器无关.
3).驱动不同主机控制器的HCD.
4).用于根集线器(包括物理集成器)的Hub驱动和一个内核辅助线程khubd. khubd监视与该集线器连接的所有端口.系统检测端口状态变化以及配置配置热插拔设备是很消耗时间的事情,而内核提供的基础设施辅助线程就能很好完成这些任务.通常情况下,该线程处于休眠态.当集线器驱动程序检测到USB端口状态变化后,该内核线程被立马唤醒.
5).用于USB客户设备的设备驱动程序.
6).USB文件I同usbfs,它能够让你从用户空间驱动USB设备.
对于端到端的操作,需要USB子系统和许多其他的内核层共同完成.比如要正常使用USB大容量存储设备,需要USB子系统和SCSI驱动程序协同工作,要驱动USB蓝牙键盘,需要USB子系统,蓝牙层,输入子系统和tty层联合完成,而本次,我们讲的就是USB子系统和网络子系统来完成对DM9621的驱动程序.
了解了USB子系统的框架之后,我们来说一下USB的一些特性:
(1)总线速度:
USB传输速度目前常见的有4种,分别是USB1.0, USB1.1, USB2.0和USB3.0,USB1.0传输速度是1.5Mbps,称为低速USB, USB1.1传输速度是12Mbps,称为全速USB, USB2.0传输速度是480Mbps,称为高速USB, USB3.0传输速度是5Gbps,称为超高速USB, USB 2.0基于半双工二线制总线,只能提供单向数据流传输,而USB 3.0采用了对偶单纯形四线制差分信号线,故而支持双向并发数据流传输,这也是新规范速度猛增的关键原因.
(2)主机控制器:
UHCI(Universal Host Controller Interface,通用主机控制器接口):该标准是英特尔提出来的,基于x86的PC机很可能用的就是这个.
OHCI(Open Host Controller Interface,开放主机控制器接口):该标准是康柏和微软等公司提出来的,兼容OHCI的控制器硬件智能程度比UHCI高,所以基于OHCI的HCD(Host Controler Drivers,主机控制器驱动程序)比基于UHCI的HCD更简单.
EHCI(Enhanced Host Controller Interface,增强型主机控制器接口):该主机控制器支持高速的USB2.0设备.为支持低速的USB设备,该控制器通常包含UHCI或OHCI控制器.(Exynos芯片就是EHCI然后包含OHCI).
USB OTG控制器:这类控制器在嵌入式微控制器领域越来越受欢迎.由于采用了OTG控制器,每个通信终端就能充当DRD(Dual-Role Device,双重角色设备).用HNP(Host Negotiation Protocol, 主机沟通协议)初始化设备连接后,这样的设备可以根据功能需要在主机模式和设备模式之间任意切换(Exynos芯片上主机模式采用DMA模式,从机模式采用Slave模式).
主机控制器内嵌了一个叫根集线器的硬件,根集线器是逻辑集线器,多个USB端口共用它,这些端口可以和内部或外部的集线器相连,扩展更多的USB端口,这样级联下来形成树状结构.
(3)传输模式:
•控制传输模式: 用来传送外设和主机之间的控制,状态,配置等信息;
•批量传输模式: 传输大量时延要求不高的数据,比如U盘;
•中断传输模式: 传输数据量小,但对传输时延敏感,要求马上响应,比如键鼠;
•等时传输模式: 传输实时数据,传输速率要预先可知,可能会造成丢包,比如USB声卡.
(4)寻址:
USB设备的每个可寻址的单元称作端点,为每个端点分配地址称作端点地址,每个端点都有与之相关的传输模式,如果一个端点的数据传输模式是批量模式,那这个端点就叫批量端点.地址为0的端点专门用来配置设备,控制管道与它相连,完成设备枚举过程,
每个端点既可以沿上行方向方向发送数据(IN传输),也可以沿下行方向接收数据(OUT传输),IN传输和OUT传输的地址空间是分开的,因此,一个批量传输IN端点和一个批量传输OUT端点可以有相同的地址,所以,USB是7位可寻址的,那么IN和OUT具有相同的地址时,就可以匹配2^7 = 128,由于地址0专门用于控制端点,所以,一个USB接口最大可以连接127个设备(1~127).
关于USB子系统具体的代码架构内容是比较多的,我们就先说这么多吧,篇幅太长了,驱动代码里也有注释信息可以帮助更好地理解,下面再来说一下网络子系统.
如上图是网络子系统的驱动体系结构.分为四个部分,我们来简单地说一下:
1).网络协议接口层想网络层协议提供统一的数据包收发接口,不论上层协议是ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接收数据.这一层的存在使得上层协议独立于具体的设备.
2).网络设备接口层想协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器.实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构.
3).设备驱动功能层的各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,它通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作.
4).网络设备与媒介层是王城数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的函数在物理上驱动.对于Linux系统而言,网络设备和媒介都可以是虚拟的.
下面是Linux网络协议栈示意图:
就先说这么多吧,具体细节请查阅有关资料.下面,我们直接把驱动代码贴出来,我们需要完成的是USB客户驱动程序,并且这个程序属于网络子系统的
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/stddef.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/mii.h>
#include <linux/usb.h>
#include <linux/usb/usbnet.h>
#include <linux/crc32.h>
#include <linux/ctype.h>
#include <linux/skbuff.h>
#include <linux/version.h>
#include <linux/delay.h>
/* control requests */
#define DM_READ_REGS 0x00
#define DM_WRITE_REGS 0x01
#define DM_READ_MEMS 0x02
#define DM_WRITE_REG 0x03
#define DM_WRITE_MEMS 0x05
#define DM_WRITE_MEM 0x07
/* registers */
#define DM_NET_CTRL 0x00
#define DM_RX_CTRL 0x05
#define DM_FLOW_CTRL 0x0a
#define DM_SHARED_CTRL 0x0b
#define DM_SHARED_ADDR 0x0c
#define DM_SHARED_DATA 0x0d /* low + high */
#define DM_EE_PHY_L 0x0d
#define DM_EE_PHY_H 0x0e
#define DM_WAKEUP_CTRL 0x0f
#define DM_PHY_ADDR 0x10 /* 6 bytes */
#define DM_MCAST_ADDR 0x16 /* 8 bytes */
#define DM_GPR_CTRL 0x1e
#define DM_GPR_DATA 0x1f
#define DM_PID 0x2a
#define DM_XPHY_CTRL 0x2e
#define DM_TX_CRC_CTRL 0x31
#define DM_RX_CRC_CTRL 0x32
#define DM_SMIREG 0x91
#define USB_CTRL 0xf4
#define PHY_SPEC_CFG 20
#define DM_TXRX_M 0x5C
#define MD96XX_EEPROM_MAGIC 0x9620
#define DM_MAX_MCAST 64
#define DM_MCAST_SIZE 8
#define DM_EEPROM_LEN 256
#define DM_TX_OVERHEAD 2 /* 2 byte header */
#define DM_RX_OVERHEAD_9601 7 /* 3 byte header + 4 byte crc tail */
#define DM_RX_OVERHEAD 8 /* 4 byte header + 4 byte crc tail */
#define DM_TIMEOUT 1000
#define DM_MODE9621 0x80
#define DM_TX_CS_EN 0 /* Transmit Check Sum Control */
#define DM9621_PHY_ID 1 /* Stone add For kernel read phy register */
struct dm96xx_priv {
int flag_fail_count;
u8 mode_9621;
};
// 设置以太网地址
static unsigned param_addr[6];
static int __init dm9621_set_mac(char *str) {
unsigned char addr[6];
unsigned int val;
int idx = 0;
char *p = str, *end;
while (*p && idx < 6) {
val = simple_strtoul(p, &end, 16);
if (end <= p) {
break;
} else {
addr[idx++] = val;
p = end;
if (*p == ':'|| *p == '-') {
p++;
} else {
break;
}
}
}
if (idx == 6) {
printk("Setup ethernet address to %pM\n", addr);
memcpy(param_addr, addr, 6);
}
return 1;
}
__setup("ethmac=", dm9621_set_mac);
static int dm_read(struct usbnet *dev, u8 reg, u16 length, void *data)
{
// usb_control_msg V.S. usb_submit_urb
// USB_CTRL_SET_TIMEOUT V.S. USB_CTRL_GET_TIMEOUT
return usb_control_msg(dev->udev,
usb_rcvctrlpipe(dev->udev, 0),
DM_READ_REGS,
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
0, reg, data, length, USB_CTRL_SET_TIMEOUT);
}
static int dm_read_reg(struct usbnet *dev, u8 reg, u8 *value)
{
u16 v[2];
int ret;
ret = dm_read(dev, reg, 2, v);
*value = (u8)(v[0] & 0xff);
return ret;
}
static int dm_write(struct usbnet *dev, u8 reg, u16 length, void *data)
{
return usb_control_msg(dev->udev,
usb_sndctrlpipe(dev->udev, 0),
DM_WRITE_REGS,
USB_DIR_OUT | USB_TYPE_VENDOR |USB_RECIP_DEVICE,
0, reg, data, length, USB_CTRL_SET_TIMEOUT);
}
static int dm_write_reg(struct usbnet *dev, u8 reg, u8 value)
{
return usb_control_msg(dev->udev,
usb_sndctrlpipe(dev->udev, 0),
DM_WRITE_REG,
USB_DIR_OUT | USB_TYPE_VENDOR |USB_RECIP_DEVICE,
value, reg, NULL, 0, USB_CTRL_SET_TIMEOUT);
}
static void dm_write_async_callback(struct urb *urb)
{
struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)urb->context;
int status = urb->status;
if (status < 0)
printk(KERN_DEBUG "dm_write_async_callback() failed with %d\n", status);
kfree(req);
usb_free_urb(urb);
}
static void dm_write_async_helper(struct usbnet *dev, u8 reg, u8 value,
u16 length, void *data)
{
struct usb_ctrlrequest *req;
struct urb *urb;
int status;
urb = usb_alloc_urb(0, GFP_ATOMIC);
if (!urb) {
printk("Error allocating URB in dm_write_async_helper!\n");
return;
}
req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC);
if (!req) {
printk("Failed to allocate memory for control request\n");
usb_free_urb(urb);
return;
}
req->bRequestType = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE;
req->bRequest = length ? DM_WRITE_REGS : DM_WRITE_REG;
req->wValue = cpu_to_le16(value);
req->wIndex = cpu_to_le16(reg);
req->wLength = cpu_to_le16(length);
usb_fill_control_urb(urb, dev->udev,
usb_sndctrlpipe(dev->udev, 0),
(void *)req, data, length,
dm_write_async_callback, req);
status = usb_submit_urb(urb, GFP_ATOMIC);
if (status < 0) {
netdev_err(dev->net, "Error submitting the control message: status=%d\n",
status);
kfree(req);
usb_free_urb(urb);
}
}
static void dm_write_reg_async(struct usbnet *dev, u8 reg, u8 value)
{
printk("dm_write_reg_async() reg=0x%02x value=0x%02x\n", reg, value);
dm_write_async_helper(dev, reg, value, 0, NULL);
}
static void dm_write_async(struct usbnet *dev, u8 reg, u16 length, void *data)
{
printk("dm_write_async() reg=0x%02x length=%d\n", reg, length);
dm_write_async_helper(dev, reg, 0, length, data);
}
static int dm_write_shared_word(struct usbnet *dev, int phy, u8 reg, __le16 value)
{
int ret, i;
mutex_lock(&dev->phy_mutex);
ret = dm_write(dev, DM_SHARED_DATA, 2, &value);
if (ret < 0)
goto out;
dm_write_reg(dev, DM_SHARED_ADDR, phy ? (reg | 0x40) : reg);
if (!phy)
dm_write_reg(dev, DM_SHARED_CTRL, 0x10);
dm_write_reg(dev, DM_SHARED_CTRL, phy ? 0x0a : 0x12);
dm_write_reg(dev, DM_SHARED_CTRL, 0x10);
for (i = 0; i < DM_TIMEOUT; i++) {
u8 tmp;
udelay(1);
ret = dm_read_reg(dev, DM_SHARED_CTRL, &tmp);
if (ret < 0)
goto out;
/* ready */
if ((tmp & 1) == 0)
break;
}
if (i == DM_TIMEOUT) {
printk("%s write timed out!\n", phy ? "phy" : "eeprom");
ret = -EIO;
goto out;
}
dm_write_reg(dev, DM_SHARED_CTRL, 0x0);
out:
mutex_unlock(&dev->phy_mutex);
return ret;
}
static int dm_read_shared_word(struct usbnet *dev, int phy, u8 reg, __le16 *value)
{
u16 v[2];
int ret, i;
mutex_lock(&dev->phy_mutex);
dm_write_reg(dev, DM_SHARED_ADDR, phy ? (reg | 0x40) : reg);
dm_write_reg(dev, DM_SHARED_CTRL, phy ? 0xc : 0x4);
for (i = 0; i < DM_TIMEOUT; i++) {
u8 tmp;
udelay(1);
ret = dm_read_reg(dev, DM_SHARED_CTRL, &tmp);
if (ret < 0)
goto out;
/* ready */
if ((tmp & 1) == 0)
break;
}
if (i == DM_TIMEOUT) {
printk("%s read timed out!\n", phy ? "phy" : "eeprom");
ret = -EIO;
goto out;
}
dm_write_reg(dev, DM_SHARED_CTRL, 0x0);
ret = dm_read(dev, DM_SHARED_DATA, 2, v);
*value = v[0];
out:
mutex_unlock(&dev->phy_mutex);
return ret;
}
static int dm9621_mdio_read(struct net_device *netdev, int phy_id, int loc)
{
struct usbnet *dev = netdev_priv(netdev);
__le16 res;
dm_read_shared_word(dev, phy_id, loc, &res);
return le16_to_cpu(res);
}
static void dm9621_mdio_write(struct net_device *netdev, int phy_id, int loc, int val)
{
struct usbnet *dev = netdev_priv(netdev);
__le16 res = cpu_to_le16(val);
int mdio_val;
dm_write_shared_word(dev, phy_id, loc, res);
mdelay(1);
mdio_val = dm9621_mdio_read(netdev, phy_id, loc);
}
/***************************************************************************************************/
static void dm9621_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *info)
{
/* Inherit standard device info */
usbnet_get_drvinfo(net, info);
info->eedump_len = DM_EEPROM_LEN;
}
static u32 dm9621_get_link(struct net_device *net)
{
struct usbnet *dev = netdev_priv(net);
return mii_link_ok(&dev->mii);
}
static int dm_write_eeprom_word(struct usbnet *dev, int phy, u8 offset, u8 value)
{
int ret, i;
u8 reg,dloc,tmp_H,tmp_L;
__le16 eeword;
//devwarn(dev, "offset = 0x%x value = 0x%x ", offset, value);
/* hank: from offset to determin eeprom word register location,reg */
reg = (offset >> 1) & 0xff;
/* hank: high/low byte by odd/even of offset */
dloc = (offset & 0x01) ? DM_EE_PHY_H : DM_EE_PHY_L;
/* retrieve high and low byte from the corresponding reg*/
ret = dm_read_shared_word(dev, 0, reg, &eeword);
//devwarn(dev, "reg = 0x%x dloc = 0x%x eeword = 0x%4x", reg, dloc, eeword);
tmp_H = (eeword & 0xff);
tmp_L = (eeword >> 8);
printk("tmp_L = 0x%2x tmp_H = 0x%2x eeword = 0x%4x\n", tmp_L, tmp_H, eeword);
/* determine new high and low byte */
if (offset & 0x01) {
tmp_L = value;
} else {
tmp_H = value;
}
//printk("updated new: tmp_L =0x%2x tmp_H =0x%2x\n", tmp_L,tmp_H);
mutex_lock(&dev->phy_mutex);
/* hank: write low byte data first to eeprom reg */
// dm_write(dev, (offset & 0x01)? DM_EE_PHY_H:DM_EE_PHY_L, 1, &value);
dm_write(dev,DM_EE_PHY_L, 1, &tmp_H);
/* high byte will be zero */
//(offset & 0x01)? (value = eeword << 8):(value = eeword >> 8);
/* write the not modified 8 bits back to its origional high/low byte reg */
dm_write(dev,DM_EE_PHY_H, 1, &tmp_L);
if (ret < 0)
goto out;
/* hank : write word location to reg 0x0c */
ret = dm_write_reg(dev, DM_SHARED_ADDR, reg);
if (!phy)
dm_write_reg(dev, DM_SHARED_CTRL, 0x10);
dm_write_reg(dev, DM_SHARED_CTRL, 0x12);
dm_write_reg(dev, DM_SHARED_CTRL, 0x10);
for (i = 0; i < DM_TIMEOUT; i++) {
u8 tmp;
udelay(1);
ret = dm_read_reg(dev, DM_SHARED_CTRL, &tmp);
if (ret < 0)
goto out;
/* ready */
if ((tmp & 1) == 0)
break;
}
if (i == DM_TIMEOUT) {
printk("%s write timed out!\n", phy ? "phy" : "eeprom");
ret = -EIO;
goto out;
}
//dm_write_reg(dev, DM_SHARED_CTRL, 0x0);
out:
mutex_unlock(&dev->phy_mutex);
return ret;
}
static int dm_read_eeprom_word(struct usbnet *dev, u8 offset, void *value)
{
return dm_read_shared_word(dev, 0, offset, value);
}
static int dm9621_get_eeprom_len(struct net_device *dev)
{
return DM_EEPROM_LEN;
}
static int dm9621_get_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, u8 * data)
{
struct usbnet *dev = netdev_priv(net);
__le16 *ebuf = (__le16 *) data;
int i;
/* access is 16bit */
if ((eeprom->offset % 2) || (eeprom->len % 2))
return -EINVAL;
for (i = 0; i < eeprom->len / 2; i++) {
if (dm_read_eeprom_word(dev, eeprom->offset / 2 + i, &ebuf[i]) < 0)
return -EINVAL;
}
return 0;
}
static int