【模块概述】
EthernetSwitch 模块中包含 5个 10/100Mbps高速以太网接口和 5个 10/100双模式以太网收发器(PHY)。模块可通过配置使用 VLAN 功能,支持 16个802.1Q VLAN和 基于端口的VLAN划分,VLAN ID的可分配范围为0~4095。
【寄存器概述】
内置交换芯片模块的寄存器分 3大类:全局控制寄存器、端口控制寄存器、端口数据计数器。寄存器地址映射表见下图:
【数据读写】
1、交换芯片模块(Ethernet Switch)的各寄存器通过 GE0 模块的“MII Address”寄存器和“MII Control”寄存器进行访问。
2、GE0 模块作为 MDIO 通讯的主机,交换芯片模块作为MDIO 通信的从机。
3、使用 MDC/MDIO 接口对交换芯片内部寄存器和 MII寄存器进行访问。访问片内集成 PHY 中的 MII寄存器时,需要使用如下格式的数据帧:
其中 OP 字段表示操作码。读数据时设置为 10,写数据时设置为 01。另外,因为交换芯片模块的内部寄存器长度均为 32位,但 MDIO协议的数据字段长度只有 16位,所以读写一个寄存器需要进行 2 个周期的 MDIO通信。
【代码分析】
在源文件 athrs_emac_adapter.c 中对接口函数表进行初始化。依据设备型号的不同,读写交换芯片模块寄存器的函数也有所区别。比如在代码中可以发现,对于 XXXX 这款设备,对寄存器读写的操作交给了 athrs_get_sw_reg() 和 athrs_set_sw_reg() 这 2 个函数。关键代码如下:
INT32 Register_athrs_emac_driver(VOID)
{
……
athrsEmacDrv.Ops.Enable_vport= athrs_emac_Enable_vport;
athrsEmacDrv.Ops.mii_read= athrs_emac_mii_read;
athrsEmacDrv.Ops.mii_write= athrs_emac_mii_write;
athrsEmacDrv.Ops.IntfFuncTbl[SWETH_GET_PORT_STAT] = athrs_get_port_stat;
……
#if defined(CONFIG_PRODUCT_ATHEROS_XXXX) // XXXX表示产品型号
athrsEmacDrv.Ops.IntfFuncTbl[SWETH_SET_SWITCH_REG]= athrs_set_sw_reg;
athrsEmacDrv.Ops.IntfFuncTbl[SWETH_GET_SWITCH_REG]= athrs_get_sw_reg;
athrsEmacDrv.Ops.IntfFuncTbl[SWETH_GET_SWITCH_PORT_STAT] = athrsget_sw_port_statistic;
athrsEmacDrv.Ops.IntfFuncTbl[SWETH_GET_SWITCH_VLAN_PORT_MAP] = athrs_sw_vlan_port_ctl;
athrsEmacDrv.Ops.IntfFuncTbl[SWETH_GET_SWITCH_VLAN_CONFIG] = athrs_sw_vlan_port_config;
#endif
ret= RegisterEmacDrv(&athrsEmacDrv);
……
}
对于接口函数表的初始化完成了,接下来应该关心在工程的哪个地方对这些方法进行了调用。利用 SourceInsight 的强大搜索功能对她们追根溯源,就可以在源文件 sweth_core.c 中找到对于交换芯片各接口的抽象层函数的封装,上述函数表中的各函数就在这些抽象层函数中被调用。阅读这些函数我们可以看到各寄存器的读写细节。举个例子:
static INT32 hal_set_phy_reg(structEthObj_s *pEthObj, BYTE Func_id, union SwEthIntfFuncArg_u *pFunc_arg,SwEthIntfFuncPtr *pDrvIntfFuncTbl)
{
if (pDrvIntfFuncTbl[Func_id] != NULL){
return pDrvIntfFuncTbl[Func_id](pEthObj->Obj_id,pEthObj->Emac, pEthObj->Phy, pFunc_arg);
}else{
return sweth_mii_write(pFunc_arg->PhyReg.MacId,pFunc_arg->PhyReg.PhyAddr, pFunc_arg->PhyReg.RegAddr,pFunc_arg->PhyReg.RegVal);
}
}
这个函数的封装很简单,先判断是否在接口函数表中存在对应的操作函数,如果有就调用函数表中的该函数,若没有就使用 sweth_mii_write() 函数进行写寄存器操作。
同时,对于交换芯片模块的初始化配置操作也是在该源文件中的函数里实现的。该函数将初始化操作分为 2 部分——创建对象、创建接口函数。入口点函数如下:
static int __init sweth_init(void)
{
INT32 ret;
……
ret = CreateSwEthObjs(); // 创建对象
if (ret != 0)
{
……
DeleteSwEthObjs();
return -1;
}
ret = CreateSwEthIntf(); // 创建接口函数
……
return 0;
}
【VLAN功能】
对于配置 VLAN 功能,我们有一个必要不充分条件需要先搞清楚,那就是 VLAN 是什么?这里只简单介绍一下: VLAN是一种将物理网络在逻辑上划分成几个不同子网的技术,通过在网络数据帧里插入VLAN ID 字段可以把数据包分流到对应的子网中,也可以依此对各逻辑子网进行数据隔离。知道这些就足以学习AR7240芯片上的VLAN配置,更详细的信息可以在网络上搜索到。
在 XXXX 设备上,配置VLAN 的操作代码位于源文件 vlan_igmp.c 中。我会在《AR7240_VLAN 学习笔记》中详细描述 VLAN 的配置过程。
【备注】
源文件 sweth_core.c 的编写非常符合Linux内核模块编程的风格且注释详细,可以作为内核模块编程的参考规范。
文件中不仅可以看到Linux内核模块中标准的 module_init(sweth_init)、module_exit(sweth_exit)、int __init sweth_init()、void __exit sweth_exit()、EXPORT_SYMBOL() 这些模块初始化入口、函数和符号表操作,在文件末尾我们还可以看到常被开发者省略掉的 MODULE_DESCRIPTIONG() 以及 MODULE_AUTHOR() 声明。模块描述有助于读者理解该模块的用途功能,而作者声明除了让开发者获得内心的满足之外更重要的是能在代码出问题时让用户第一时间知道应该联系谁。建议在作者声明里填写自己的常用邮箱。