您的位置:首页 > 理论基础 > 计算机网络

linux网络设备应用与驱动编程学习4——模板与实例(A)

2011-09-27 10:24 806 查看
源码来自lpc32xx_mii.c

1. 模块初始化卸载

static int __init lpc32xx_net_init(void)

{

return platform_driver_register(&lpc32xx_net_driver);

}

static void __exit lpc32xx_net_cleanup(void)

{

platform_driver_unregister(&lpc32xx_net_driver);

}

2. 平台驱动相关方法

static struct platform_driver lpc32xx_net_driver = {

.probe = lpc32xx_net_drv_probe,

.remove = __devexit_p(lpc32xx_net_drv_remove),

.suspend = lpc32xx_net_drv_suspend,

.resume = lpc32xx_net_drv_resume,

.driver = {

.name = MODNAME,

},

};

3. probe方法分析

static int lpc32xx_net_drv_probe(struct platform_device *pdev)

{

struct resource *res;

struct net_device *ndev;

struct netdata_local *pldat;

struct phy_device *phydev;

dma_addr_t dma_handle;

int irq, ret;

第一步:从平台上获取资源信息

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

irq = platform_get_irq(pdev, 0);

/*static struct resource net_resources[] = {

[0] = {

.start = ETHERNET_BASE,

.end = ETHERNET_BASE + SZ_4K - 1,

.flags = IORESOURCE_MEM,

},

[1] = {

.start = IRQ_ETHERNET,

.end = IRQ_ETHERNET,

.flags = IORESOURCE_IRQ,

},

};*/

if ((!res) || (irq < 0) || (irq >= NR_IRQS))

{

dev_err(&pdev->dev, "error getting resources.\n");

ret = -ENXIO;

goto err_exit;

}

第二步:分配和初始化net_device结构,这一步也可放在模块初始化中完成

ndev = alloc_etherdev(sizeof(struct netdata_local));

/*alloc_etherdev函数最终调用alloc_netdev_mq(sizeof_priv, "eth%d", ether_setup, queue_count);函数*/

if (!ndev) {

dev_err(&pdev->dev, "could not allocate device.\n");

ret = -ENOMEM;

goto err_exit;

}

SET_NETDEV_DEV(ndev, &pdev->dev);

/*将ndev的父设备指向平台设备。即将ndev设备挂到平台设备表里*/

pldat = netdev_priv(ndev);

//获得ndev的私有指针,指针内的成员由驱动作者自己定义。

pldat->pdev = pdev;

pldat->ndev = ndev;

spin_lock_init(&pldat->lock);

/* Save resources */

pldat->net_region_start = res->start;

pldat->net_region_size = res->end - res->start + 1;

ndev->irq = irq;

//第三步:申请硬件资源

/* Get clock for the device */

pldat->clk = clk_get(&ndev->dev, "net_ck");

if (IS_ERR(pldat->clk)) {

ret = PTR_ERR(pldat->clk);

goto err_out_free_dev;

}

/*以上为初始化私有结构体*/

/* Enable network clock */

__lpc32xx_net_clock_enable(pldat, 1);

//使能时钟

/* Map IO space */

pldat->net_base = ioremap(pldat->net_region_start, pldat->net_region_size);

if (!pldat->net_base)

{

dev_err(&pdev->dev, "failed to map registers, aborting.\n");

ret = -ENOMEM;

goto err_out_disable_clocks;

}

//将网卡物理空间动态映射到内核空间。

ret = request_irq(ndev->irq, __lpc32xx_eth_interrupt, 0,

ndev->name, ndev);

if (ret) {

printk(KERN_ERR

"%s: Unable to request IRQ %d (error %d)\n",

ndev->name, ndev->irq, ret);

goto err_out_iounmap;

}

//申请中断

//第四步:设备操作接口初始化

/* Fill in the fields of the device structure with ethernet values. */

ether_setup(ndev);

/*ndev申请完之后并没有初始化,ether_setup()函数就是完成ndev有关于以太网确定成员的初始化*/

/*probe的以上部分完成了网络设备驱动的“网络设备接口层”的工作,以下对设备操作函数的具体实现便是“设备驱动功能层”的事情,即网络设备驱动的主体工作*/

/* Setup driver functions */

ndev->open = lpc32xx_net_open;

ndev->stop = lpc32xx_net_close;

//设备打开与关闭时调用

ndev->hard_start_xmit = lpc32xx_net_hard_start_xmit;

//设备数据发送时调用

ndev->tx_timeout = lpc32xx_net_timeout;

//发送超时调用

ndev->watchdog_timeo = msecs_to_jiffies(watchdog);

//设定超时时间,单位jiffies

ndev->set_multicast_list = lpc32xx_net_set_multicast_list;

/*当设备的组播列表改变或设备标志改变时调用*/

ndev->ethtool_ops = &lpc32xx_net_ethtool_ops;

//结构体中的成员用于更改或报告网络设备的设置

ndev->do_ioctl = &lpc32xx_net_ioctl;

//设备特定的I/O控制

#ifdef CONFIG_NET_POLL_CONTROLLER

ndev->poll_controller = lpc32xx_net_poll_controller;

//支持纯粹的netconsole(用于kgdb调试),它以轮询方式接收数据包

#endif

ndev->base_addr = pldat->net_region_start;

// 继续初始化ndev:虚拟基地址

//第五步:其它及DMA初始化

/* Save board specific configuration */

pldat->ncfg = (struct lpc32xx_net_cfg *) pdev->dev.platform_data;

/* .platform_data = &lpc32xx_netdata,

struct lpc32xx_net_cfg lpc32xx_netdata =

{

.get_mac_addr = &return_mac_address,

.phy_irq = -1,

.phy_mask = 0xFFFFFFF0,

};

*/

if (pldat->ncfg == NULL)

{

printk(KERN_INFO "%s : WARNING: No board MAC address provided\n",

ndev->name);

pldat->ncfg = &__lpc32xx_local_net_config;

}

/* Get size of DMA buffers/descriptors region */

pldat->dma_buff_size = (ENET_TX_DESC + ENET_RX_DESC) * (ENET_MAXF_SIZE +

sizeof(struct txrx_desc_t) + sizeof(struct rx_status_t));

/*计算DMA缓冲区所需要的空间,DMA空间包括帧片断(描述符)数组大小,状态大小,帧片断数量*/

#if defined(CONFIG_MACH_LPC32XX_IRAM_FOR_NET)

pldat->dma_buff_base_v = (u32) io_p2v(IRAM_BASE);

dma_handle = (dma_addr_t) IRAM_BASE;

//初始化DMA缓冲区的基地址

#else

pldat->dma_buff_size += 4096; /* Allows room for alignment */

/* Align on the next highest page entry size */

pldat->dma_buff_size &= 0Xfffff000;

pldat->dma_buff_size += 0X00001000;

//如果DMA缓冲区不在内部RAM中,则进行页对齐

/* Allocate a chunk of memory for the DMA ethernet buffers and descriptors */

pldat->dma_buff_base_v = (u32) dma_alloc_coherent(&pldat->pdev->dev, pldat->dma_buff_size,

&dma_handle, GFP_KERNEL);

#endif

//申请一致性缓冲区,初始化DMA缓冲区的基地址

if (pldat->dma_buff_base_v == (u32) NULL)

{

dev_err(&pdev->dev, "error getting DMA region.\n");

ret = -ENOMEM;

goto err_out_free_irq;

}

pldat->dma_buff_base_p = (u32) dma_handle;

#ifdef NET_DEBUG

printk(KERN_INFO "Ethernet net MAC resources\n");

printk(KERN_INFO "IO address start :0x%08x\n", (u32) pldat->net_region_start);

printk(KERN_INFO "IO address size :%d\n", (u32) pldat->net_region_size);

printk(KERN_INFO "IO address (mapped) :0x%08x\n", (u32) pldat->net_base);

printk(KERN_INFO "IRQ number :%d\n", ndev->irq);

printk(KERN_INFO "DMA buffer size :%d\n", pldat->dma_buff_size);

printk(KERN_INFO "DMA buffer P address :0x%08x\n", pldat->dma_buff_base_p);

printk(KERN_INFO "DMA buffer V address :0x%08x\n", pldat->dma_buff_base_v);

#endif

/* Get the board MAC address */

if (pldat->ncfg->get_mac_addr != NULL)

{

ret = pldat->ncfg->get_mac_addr(ndev->dev_addr);

//在探测阶段先随便指定一个mac完成初始化

if (ret)

{

/* Mac address load error */

goto err_out_dma_unmap;

}

}

if (!is_valid_ether_addr(ndev->dev_addr))

{

printk(KERN_INFO "%s: Invalid ethernet MAC address. Please "

"set using ifconfig\n", ndev->name);

}

第六步:以太网控制器相关

/* Reset the ethernet controller */

__lpc32xx_eth_reset(pldat);

//__lpc32xx_eth_reset()函数是一些读写寄存器构成

/* then shut everything down to save power */

__lpc32xx_net_shutdown(pldat);

/* Set default parameters */

pldat->msg_enable = NETIF_MSG_LINK;

/* Force an MII interface reset and clock setup */

__lpc32xx_mii_mngt_reset(pldat);

/* Force default PHY interface setup in chip, this will probably be

changed by the PHY driver */

pldat->link = 0;

pldat->speed = 100;

pldat->duplex = DUPLEX_FULL;

__lpc32xx_params_setup(pldat);

//__lpc32xx_params_setup()函数就是根据pldat的speed,duplex设置完成相应寄存器设置。

ret = register_netdev(ndev);

//以上代码主要就是完成了ndev及其私有指针pldat指向结构的部分初始化

if (ret) {

dev_err(&pdev->dev, "Cannot register net device, aborting.\n");

goto err_out_dma_unmap;

}

platform_set_drvdata(pdev, ndev);

//将ndev作为pdev->drvdata,方便pdev与ndev之间结构信息共享

if (lpc32xx_mii_init(pldat) != 0) {

goto err_out_unregister_netdev;

}

// lpc32xx_mii_init()完成mii接口的初始化见“lpc32xx_mii_init()函数分析”

printk(KERN_INFO "%s: LPC32XX mac at 0x%08lx irq %d\n",

ndev->name, ndev->base_addr, ndev->irq);

//最后初始化了一个总线设备。下面的是一些错误处理。

phydev = pldat->phy_dev;

printk(KERN_INFO "%s: attached PHY driver [%s] "

"(mii_bus:phy_addr=%s, irq=%d)\n",

ndev->name, phydev->drv->name, phydev->dev.bus_id, phydev->irq);

return 0;

err_out_unregister_netdev:

platform_set_drvdata(pdev, NULL);

unregister_netdev(ndev);

err_out_dma_unmap:

dma_free_coherent(&pldat->pdev->dev, pldat->dma_buff_size,

(void *) pldat->dma_buff_base_v, (dma_addr_t) pldat->dma_buff_base_p);

err_out_free_irq:

free_irq(ndev->irq, ndev);

err_out_iounmap:

iounmap(pldat->net_base);

err_out_disable_clocks:

clk_disable(pldat->clk);

clk_put(pldat->clk);

err_out_free_dev:

free_netdev(ndev);

err_exit:

printk("%s: not found (%d).\n", MODNAME, ret);

return ret;

}

总结一下lpc32xx_net_drv_probe()函数:首先根据平台设备的resource结构获得空间和中断信息,并利用这些作息初始化申请的net_device结构体,再向内核申请这些资源。再次,填充ndev的设备操作函数成员,让内核得到一些控制网络传输的方法。接着,根据芯片特点,申请了DMA缓冲区,初始化了mac。而后便是初始化以太网控制器及其与phy的数据交互接口mii,最后是一些错误处理。可以说一个probe方法完成了整个网络设备驱动的构架工作。

lpc32xx_mii_init()函数分析

static int lpc32xx_mii_init(struct netdata_local *pldat)

{

int err = -ENXIO, i;

/* Setup MII mode */

#if defined (MAC_LPC32XX_MII_SUPPORT)

__raw_writel(COMMAND_PASSRUNTFRAME, ENET_COMMAND(pldat->net_base));

#else

__raw_writel((COMMAND_PASSRUNTFRAME | COMMAND_RMII),

ENET_COMMAND(pldat->net_base));

__raw_writel(SUPP_RESET_RMII, ENET_SUPP(pldat->net_base));

#endif

pldat->mii_bus.name = "LPC32XX_mii_bus";

pldat->mii_bus.read = &lpc32xx_mdio_read;

pldat->mii_bus.write = &lpc32xx_mdio_write;

pldat->mii_bus.reset = &lpc32xx_mdio_reset;

snprintf(pldat->mii_bus.id, MII_BUS_ID_SIZE, "%x", pldat->pdev->id);

pldat->mii_bus.priv = pldat;

pldat->mii_bus.dev = &pldat->ndev->dev;

pldat->mii_bus.phy_mask = 0xFFFFFFF0;

/*在plat的结构中,mii_bus是一种PHY设备挂接的总线.该总线介于mac于phy之间,以上是它的初始化。该总线提供了read,write,reset方法,有点像字符设备中fop提供的方法*/

if (pldat->ncfg)

{

pldat->mii_bus.phy_mask = pldat->ncfg->phy_mask;

}

pldat->mii_bus.irq = kmalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL);

if (!pldat->mii_bus.irq) {

err = -ENOMEM;

goto err_out;

}

for (i = 0; i < PHY_MAX_ADDR; i++)

{

pldat->mii_bus.irq[i] = PHY_POLL;

}

/*申请了一片断区域,并初始化。PHY_MAX_ADDR代表总线能接受的设备数量

一个设备将来对就这里申请的一个中断位置*/

platform_set_drvdata(pldat->ndev, &pldat->mii_bus);

//像这样的函数,个人以为,只为作指针引用方便

if (mdiobus_register(&pldat->mii_bus))

{

goto err_out_free_mdio_irq;

}

if (lpc32xx_mii_probe(pldat->ndev) != 0)

{

goto err_out_unregister_bus;

}

return 0;

err_out_unregister_bus:

mdiobus_unregister(&pldat->mii_bus);

err_out_free_mdio_irq:

kfree(pldat->mii_bus.irq);

err_out:

return err;

}

追踪(mdiobus_register(&pldat->mii_bus)

int mdiobus_register(struct mii_bus *bus)

{

int i;

int err = 0;

if (NULL == bus || NULL == bus->name ||

NULL == bus->read ||

NULL == bus->write)

return -EINVAL;

//检查总线是否被初始化

mutex_init(&bus->mdio_lock);

if (bus->reset)

bus->reset(bus);

for (i = 0; i < PHY_MAX_ADDR; i++) {

struct phy_device *phydev;

if (bus->phy_mask & (1 << i)) {

bus->phy_map[i] = NULL;

continue;

}

phydev = get_phy_device(bus, i);

if (IS_ERR(phydev))

return PTR_ERR(phydev);

/* There's a PHY at this address

* We need to set:

* 1) IRQ

* 2) bus_id

* 3) parent

* 4) bus

* 5) mii_bus

* And, we need to register it */

if (phydev) {

phydev->irq = bus->irq[i];

phydev->dev.parent = bus->dev;

/*pldat->mii_bus.dev = &pldat->ndev->dev; 这说明phydev的父设备是ndev,即物理层PHY设备的父设备为MAC设备*/

phydev->dev.bus = &mdio_bus_type;

snprintf(phydev->dev.bus_id, BUS_ID_SIZE, PHY_ID_FMT, bus->id, i);

phydev->bus = bus;

/* Run all of the fixups for this PHY */

phy_scan_fixups(phydev);

err = device_register(&phydev->dev);

if (err) {

printk(KERN_ERR "phy %d failed to register\n",

i);

phy_device_free(phydev);

phydev = NULL;

}

}

bus->phy_map[i] = phydev;

}

pr_info("%s: probed\n", bus->name);

return err;

}

EXPORT_SYMBOL(mdiobus_register);

/*经过以上源代码分析,可以看出mdiobus_register()函数为总线上所有设备进行了设置,并注册进了设备模型。从名子上看是总线注册,实际是总线上的设备注册。*/

追踪lpc32xx_mii_probe()

static int lpc32xx_mii_probe(struct net_device *ndev)

{

struct netdata_local *pldat = netdev_priv(ndev);

struct phy_device *phydev = NULL;

int phy_addr;

/* find the first phy */

for (phy_addr = 0; phy_addr < PHY_MAX_ADDR; phy_addr++)

{

if (pldat->mii_bus.phy_map[phy_addr])

{

phydev = pldat->mii_bus.phy_map[phy_addr];

break;

}

}

if (!phydev)

{

printk (KERN_ERR "%s: no PHY found\n", ndev->name);

return -1;

}

/* Attach to the PHY */

#if defined (MAC_LPC32XX_MII_SUPPORT)

phydev = phy_connect(ndev, phydev->dev.bus_id,

&lpc32xx_handle_link_change, 0, PHY_INTERFACE_MODE_MII);

#else

phydev = phy_connect(ndev, phydev->dev.bus_id,

&lpc32xx_handle_link_change, 0, PHY_INTERFACE_MODE_RMII);

#endif

if (IS_ERR(phydev))

{

printk(KERN_ERR "%s: Could not attach to PHY\n", ndev->name);

return PTR_ERR(phydev);

}

/* mask with MAC supported features */

phydev->supported &= PHY_BASIC_FEATURES;

phydev->advertising = phydev->supported;

pldat->link = 0;

pldat->speed = 0;

pldat->duplex = -1;

pldat->phy_dev = phydev;

return 0;

}

lpc32xx_mii_probe()完成了这样一个事情:找到第一个phy设备,然后根据内核配置,选用MII接口或RMII接口,之后再进行简单的配置。这个探测方法完成的是phy设备的探测。

总结一下:

lpc32xx_mii_init()函数完成的是mii接口的初始化,包括注册注册mii_bus总线上的设备,然后根据找到总线上第一个phy设备进行一些初始化设置。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐