您的位置:首页 > 其它

DM9000网卡驱动源码分析系列03 - probe && remove

2015-12-08 08:56 531 查看
static struct dm9000_plat_data *dm9000_parse_dt(struct device *dev)
{
struct dm9000_plat_data *pdata;
struct device_node *np = dev->of_node;
const void *mac_addr;

if (!IS_ENABLED(CONFIG_OF) || !np)
return ERR_PTR(-ENXIO);

pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);

if (of_find_property(np, "davicom,ext-phy", NULL))
pdata->flags |= DM9000_PLATF_EXT_PHY;
if (of_find_property(np, "davicom,no-eeprom", NULL))
pdata->flags |= DM9000_PLATF_NO_EEPROM;

mac_addr = of_get_mac_address(np);
if (mac_addr)
memcpy(pdata->dev_addr, mac_addr, sizeof(pdata->dev_addr));

return pdata;
}

/*
* Search DM9000 board, allocate space and register it
*/
static int dm9000_probe(struct platform_device *pdev)
{
struct dm9000_plat_data *pdata = dev_get_platdata(&pdev->dev);
struct board_info *db;	/* Point a board information structure */
struct net_device *ndev;
const unsigned char *mac_src;
int ret = 0;
int iosize;
int i;
u32 id_val;

if (!pdata) {
pdata = dm9000_parse_dt(&pdev->dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
}

/* Init network device */
ndev = alloc_etherdev(sizeof(struct board_info));
if (!ndev)
return -ENOMEM;

SET_NETDEV_DEV(ndev, &pdev->dev);

dev_dbg(&pdev->dev, "dm9000_probe()\n");

/* setup board info structure */
db = netdev_priv(ndev);

db->dev = &pdev->dev;
db->ndev = ndev;

spin_lock_init(&db->lock);
mutex_init(&db->addr_lock);

INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work);

db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
db->irq_res  = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

if (db->addr_res == NULL || db->data_res == NULL ||
db->irq_res == NULL) {
dev_err(db->dev, "insufficient resources\n");
ret = -ENOENT;
goto out;
}

db->irq_wake = platform_get_irq(pdev, 1);
if (db->irq_wake >= 0) {
dev_dbg(db->dev, "wakeup irq %d\n", db->irq_wake);

ret = request_irq(db->irq_wake, dm9000_wol_interrupt,
IRQF_SHARED, dev_name(db->dev), ndev);
if (ret) {
dev_err(db->dev, "cannot get wakeup irq (%d)\n", ret);
} else {

/* test to see if irq is really wakeup capable */
ret = irq_set_irq_wake(db->irq_wake, 1);
if (ret) {
dev_err(db->dev, "irq %d cannot set wakeup (%d)\n",
db->irq_wake, ret);
ret = 0;
} else {
irq_set_irq_wake(db->irq_wake, 0);
db->wake_supported = 1;
}
}
}

iosize = resource_size(db->addr_res);
db->addr_req = request_mem_region(db->addr_res->start, iosize,
pdev->name);

if (db->addr_req == NULL) {
dev_err(db->dev, "cannot claim address reg area\n");
ret = -EIO;
goto out;
}

db->io_addr = ioremap(db->addr_res->start, iosize);

if (db->io_addr == NULL) {
dev_err(db->dev, "failed to ioremap address reg\n");
ret = -EINVAL;
goto out;
}

iosize = resource_size(db->data_res);
db->data_req = request_mem_region(db->data_res->start, iosize,
pdev->name);

if (db->data_req == NULL) {
dev_err(db->dev, "cannot claim data reg area\n");
ret = -EIO;
goto out;
}

db->io_data = ioremap(db->data_res->start, iosize);

if (db->io_data == NULL) {
dev_err(db->dev, "failed to ioremap data reg\n");
ret = -EINVAL;
goto out;
}

/* fill in parameters for net-dev structure */
ndev->base_addr = (unsigned long)db->io_addr;
ndev->irq	= db->irq_res->start;

/* ensure at least we have a default set of IO routines */
dm9000_set_io(db, iosize);

/* check to see if anything is being over-ridden */
if (pdata != NULL) {
/* check to see if the driver wants to over-ride the
* default IO width */

if (pdata->flags & DM9000_PLATF_8BITONLY)
dm9000_set_io(db, 1);

if (pdata->flags & DM9000_PLATF_16BITONLY)
dm9000_set_io(db, 2);

if (pdata->flags & DM9000_PLATF_32BITONLY)
dm9000_set_io(db, 4);

/* check to see if there are any IO routine
* over-rides */

if (pdata->inblk != NULL)
db->inblk = pdata->inblk;

if (pdata->outblk != NULL)
db->outblk = pdata->outblk;

if (pdata->dumpblk != NULL)
db->dumpblk = pdata->dumpblk;

db->flags = pdata->flags;
}

#ifdef CONFIG_DM9000_FORCE_SIMPLE_PHY_POLL
db->flags |= DM9000_PLATF_SIMPLE_PHY;
#endif

dm9000_reset(db);

/* try multiple times, DM9000 sometimes gets the read wrong */
for (i = 0; i < 8; i++) {
id_val  = ior(db, DM9000_VIDL);
id_val |= (u32)ior(db, DM9000_VIDH) << 8;
id_val |= (u32)ior(db, DM9000_PIDL) << 16;
id_val |= (u32)ior(db, DM9000_PIDH) << 24;

if (id_val == DM9000_ID)
break;
dev_err(db->dev, "read wrong id 0x%08x\n", id_val);
}

if (id_val != DM9000_ID) {
dev_err(db->dev, "wrong id: 0x%08x\n", id_val);
ret = -ENODEV;
goto out;
}

/* Identify what type of DM9000 we are working on */

id_val = ior(db, DM9000_CHIPR);
dev_dbg(db->dev, "dm9000 revision 0x%02x\n", id_val);

switch (id_val) {
case CHIPR_DM9000A:
db->type = TYPE_DM9000A;
break;
case CHIPR_DM9000B:
db->type = TYPE_DM9000B;
break;
default:
dev_dbg(db->dev, "ID %02x => defaulting to DM9000E\n", id_val);
db->type = TYPE_DM9000E;
}

/* dm9000a/b are capable of hardware checksum offload */
if (db->type == TYPE_DM9000A || db->type == TYPE_DM9000B) {
ndev->hw_features = NETIF_F_RXCSUM | NETIF_F_IP_CSUM;
ndev->features |= ndev->hw_features;
}

/* from this point we assume that we have found a DM9000 */

ndev->netdev_ops	= &dm9000_netdev_ops;
ndev->watchdog_timeo	= msecs_to_jiffies(watchdog);
ndev->ethtool_ops	= &dm9000_ethtool_ops;

db->msg_enable       = NETIF_MSG_LINK;
db->mii.phy_id_mask  = 0x1f;
db->mii.reg_num_mask = 0x1f;
db->mii.force_media  = 0;
db->mii.full_duplex  = 0;
db->mii.dev	     = ndev;
db->mii.mdio_read    = dm9000_phy_read;
db->mii.mdio_write   = dm9000_phy_write;

mac_src = "eeprom";

/* 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, ETH_ALEN);
}

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";
}

platform_set_drvdata(pdev, ndev);
ret = register_netdev(ndev);

if (ret == 0)
printk(KERN_INFO "%s: dm9000%c at %p,%p IRQ %d MAC: %pM (%s)\n",
ndev->name, dm9000_type_to_char(db->type),
db->io_addr, db->io_data, ndev->irq,
ndev->dev_addr, mac_src);
return 0;

out:
dev_err(db->dev, "not found (%d).\n", ret);

dm9000_release_board(pdev, db);
free_netdev(ndev);

return ret;
}

static int dm9000_drv_remove(struct platform_device *pdev)
{
struct net_device *ndev = platform_get_drvdata(pdev);

unregister_netdev(ndev);
dm9000_release_board(pdev, netdev_priv(ndev));
free_netdev(ndev);		/* free device structure */

dev_dbg(&pdev->dev, "released and freed device\n");
return 0;
}


通过module_platform_driver(dm9000_driver);把驱动放到platform总线上
如果有dm9000设备出现,就会调用probe函数,反之,则调用remove函数,下面我们来看看probe函数的实现

static struct dm9000_plat_data *dm9000_parse_dt(struct device *dev)
{
    struct dm9000_plat_data *pdata;
    struct device_node *np = dev->of_node;
    const void *mac_addr;

    if (!IS_ENABLED(CONFIG_OF) || !np)
        return ERR_PTR(-ENXIO);

    pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
    if (!pdata)
        return ERR_PTR(-ENOMEM);

    if (of_find_property(np, "davicom,ext-phy", NULL))
        pdata->flags |= DM9000_PLATF_EXT_PHY;
    if (of_find_property(np, "davicom,no-eeprom", NULL))
        pdata->flags |= DM9000_PLATF_NO_EEPROM;

    mac_addr = of_get_mac_address(np);
    if (mac_addr)
        memcpy(pdata->dev_addr, mac_addr, sizeof(pdata->dev_addr));

    return pdata;
}

/*
* Search DM9000 board, allocate space and register it
*/
static int dm9000_probe(struct platform_device *pdev)
{
struct dm9000_plat_data *pdata = dev_get_platdata(&pdev->dev);
struct board_info *db;	/* Point a board information structure */
struct net_device *ndev;
const unsigned char *mac_src;
int ret = 0;
int iosize;
int i;
u32 id_val;

if (!pdata) {
pdata = dm9000_parse_dt(&pdev->dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
}
通过通用的途径来获取设备数据,因为匹配操作是platform执行的,那么总线告诉驱动有匹配的设备的时候也会提供设备的信息
那么信息就是通过dev_get_platdata函数来获取的,如果获取不到这个信息就自己通过设备树获取,

从dm9000_parse_dt函数来看,并没有获取很多的信息,只是获取flags,还有网卡的mac地址

那些以of开头的函数是设备树提供的API

/* Init network device */
ndev = alloc_etherdev(sizeof(struct board_info));
if (!ndev)
return -ENOMEM;

SET_NETDEV_DEV(ndev, &pdev->dev);

dev_dbg(&pdev->dev, "dm9000_probe()\n");

/* setup board info structure */
db = netdev_priv(ndev);

db->dev = &pdev->dev;
db->ndev = ndev;

spin_lock_init(&db->lock);
mutex_init(&db->addr_lock);

INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work);
首先分配空间alloc_etherdev函数是alloc_netdev的封装,参数就是netdev_priv返回的指针指向的大小
SET_NETDEV_DEV宏的定义如下

#define SET_NETDEV_DEV(net, pdev) ((net)->dev.parent = (pdev))

通过netdev_priv获取私有数据后, 初始化一些值,自旋锁,及互斥锁

INIT_DELAYED_WORK宏的作用是设置

db->phy_poll.work.func = dm9000_poll_work;

这个delay_work的作用就是延迟执行,通过调用schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)

第二个参数delay的解释是:@delay: number of jiffies to wait or 0 for immediate execution, 既多少个jiffies后执行func,如果是0就立即执行

static irqreturn_t dm9000_wol_interrupt(int irq, void *dev_id)
{
    struct net_device *dev = dev_id;
    struct board_info *db = netdev_priv(dev);
    unsigned long flags;
    unsigned nsr, wcr;

    spin_lock_irqsave(&db->lock, flags);

    nsr = ior(db, DM9000_NSR);
    wcr = ior(db, DM9000_WCR);

    dev_dbg(db->dev, "%s: NSR=0x%02x, WCR=0x%02x\n", __func__, nsr, wcr);

    if (nsr & NSR_WAKEST) {
        /* clear, so we can avoid */
        iow(db, DM9000_NSR, NSR_WAKEST);

        if (wcr & WCR_LINKST)
            dev_info(db->dev, "wake by link status change\n");
        if (wcr & WCR_SAMPLEST)
            dev_info(db->dev, "wake by sample packet\n");
        if (wcr & WCR_MAGICST)
            dev_info(db->dev, "wake by magic packet\n");
        if (!(wcr & (WCR_LINKST | WCR_SAMPLEST | WCR_MAGICST)))
            dev_err(db->dev, "wake signalled with no reason? "
                "NSR=0x%02x, WSR=0x%02x\n", nsr, wcr);
    }

    spin_unlock_irqrestore(&db->lock, flags);

    return (nsr & NSR_WAKEST) ? IRQ_HANDLED : IRQ_NONE;
}
/** 中断处理函数,看代码就是打印出被唤醒的原因,因为网卡状态改变,包来了,或是信号等原因被唤醒 */

db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
db->irq_res  = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

if (db->addr_res == NULL || db->data_res == NULL ||
db->irq_res == NULL) {
dev_err(db->dev, "insufficient resources\n");
ret = -ENOENT;
goto out;
}

db->irq_wake = platform_get_irq(pdev, 1);
if (db->irq_wake >= 0) {
dev_dbg(db->dev, "wakeup irq %d\n", db->irq_wake);

ret = request_irq(db->irq_wake, dm9000_wol_interrupt,
IRQF_SHARED, dev_name(db->dev), ndev);
if (ret) {
dev_err(db->dev, "cannot get wakeup irq (%d)\n", ret);
} else {

/* test to see if irq is really wakeup capable */
ret = irq_set_irq_wake(db->irq_wake, 1);
if (ret) {
dev_err(db->dev, "irq %d cannot set wakeup (%d)\n",
db->irq_wake, ret);
ret = 0;
} else {
irq_set_irq_wake(db->irq_wake, 0);
db->wake_supported = 1;
}
}
}

iosize = resource_size(db->addr_res);
db->addr_req = request_mem_region(db->addr_res->start, iosize,
pdev->name);

if (db->addr_req == NULL) {
dev_err(db->dev, "cannot claim address reg area\n");
ret = -EIO;
goto out;
}

db->io_addr = ioremap(db->addr_res->start, iosize);

if (db->io_addr == NULL) {
dev_err(db->dev, "failed to ioremap address reg\n");
ret = -EINVAL;
goto out;
}

iosize = resource_size(db->data_res);
db->data_req = request_mem_region(db->data_res->start, iosize,
pdev->name);

if (db->data_req == NULL) {
dev_err(db->dev, "cannot claim data reg area\n");
ret = -EIO;
goto out;
}

db->io_data = ioremap(db->data_res->start, iosize);

if (db->io_data == NULL) {
dev_err(db->dev, "failed to ioremap data reg\n");
ret = -EINVAL;
goto out;
}

/* fill in parameters for net-dev structure */
ndev->base_addr = (unsigned long)db->io_addr;
ndev->irq	= db->irq_res->start;
这一块代码初始化设备的IO内存,还有中断号,首先通过platform_get_resource函数获取到resource资源

wake那一块代码不是很清楚什么作用,

在linux kernel中,调用enable_irq_wake函数,可以将一个irq具有唤醒系统的功能,即把系统从低功耗模式中唤醒

static inline int enable_irq_wake(unsigned int irq)  

{  

    return irq_set_irq_wake(irq, 1);  

}

Linux内核提供了一组函数以申请和释放IO内存的范围,这里的申请表明该驱动要访问这片区域,它不会做任何内存映射的动作

通过调用request_mem_region函数来申请,通过调用release_mem_region来释放,将IO内存返回给系统

在内核中访问IO内存之前,需首先使用ioremap函数将设备所处的物理地址映射到虚拟地址上,不再使用IO后,通过iounmap释放
/* ensure at least we have a default set of IO routines */
dm9000_set_io(db, iosize);

/* check to see if anything is being over-ridden */
if (pdata != NULL) {
/* check to see if the driver wants to over-ride the
* default IO width */

if (pdata->flags & DM9000_PLATF_8BITONLY)
dm9000_set_io(db, 1);

if (pdata->flags & DM9000_PLATF_16BITONLY)
dm9000_set_io(db, 2);

if (pdata->flags & DM9000_PLATF_32BITONLY)
dm9000_set_io(db, 4);

/* check to see if there are any IO routine
* over-rides */

if (pdata->inblk != NULL)
db->inblk = pdata->inblk;

if (pdata->outblk != NULL)
db->outblk = pdata->outblk;

if (pdata->dumpblk != NULL)
db->dumpblk = pdata->dumpblk;

db->flags = pdata->flags;
}
IO内存已经申请且映射好了,我们需要封装一些函数来实现对IO内存的读写操作

in, out是从驱动的角度看数据的流入,流出,in是指从网卡到驱动,out是指从驱动到网卡,dump就是把垃圾数据dump掉

要根据网卡的特点注册不同的函数,因为如果网卡支持一次4字节读写,那么就不应该一次读写1个或2个字节,以提高效率

#ifdef CONFIG_DM9000_FORCE_SIMPLE_PHY_POLL
db->flags |= DM9000_PLATF_SIMPLE_PHY;
#endif

dm9000_reset(db);

/* try multiple times, DM9000 sometimes gets the read wrong */
for (i = 0; i < 8; i++) {
id_val  = ior(db, DM9000_VIDL);
id_val |= (u32)ior(db, DM9000_VIDH) << 8;
id_val |= (u32)ior(db, DM9000_PIDL) << 16;
id_val |= (u32)ior(db, DM9000_PIDH) << 24;

if (id_val == DM9000_ID)
break;
dev_err(db->dev, "read wrong id 0x%08x\n", id_val);
}

if (id_val != DM9000_ID) {
dev_err(db->dev, "wrong id: 0x%08x\n", id_val);
ret = -ENODEV;
goto out;
}

/* Identify what type of DM9000 we are working on */

id_val = ior(db, DM9000_CHIPR);
dev_dbg(db->dev, "dm9000 revision 0x%02x\n", id_val);

switch (id_val) {
case CHIPR_DM9000A:
db->type = TYPE_DM9000A;
break;
case CHIPR_DM9000B:
db->type = TYPE_DM9000B;
break;
default:
dev_dbg(db->dev, "ID %02x => defaulting to DM9000E\n", id_val);
db->type = TYPE_DM9000E;
}

/* dm9000a/b are capable of hardware checksum offload */
if (db->type == TYPE_DM9000A || db->type == TYPE_DM9000B) {
ndev->hw_features = NETIF_F_RXCSUM | NETIF_F_IP_CSUM;
ndev->features |= ndev->hw_features;
}
获取设备的网卡类型,同样是dm9000设备,也有dm9000a, dm9000b, dm9000e, 不同的型号之间有些功能的支持也不同

比如dm9000a, dm9000b支持校验和操作,减轻上层负担
static void dm9000_reset(struct board_info *db)
{
dev_dbg(db->dev, "resetting device\n");

/* Reset DM9000, see DM9000 Application Notes V1.22 Jun 11, 2004 page 29
* The essential point is that we have to do a double reset, and the
* instruction is to set LBK into MAC internal loopback mode.
*/
iow(db, DM9000_NCR, NCR_RST | NCR_MAC_LBK);
udelay(100); /* Application note says at least 20 us */
if (ior(db, DM9000_NCR) & 1)
dev_err(db->dev, "dm9000 did not respond to first reset\n");

iow(db, DM9000_NCR, 0);
iow(db, DM9000_NCR, NCR_RST | NCR_MAC_LBK);
udelay(100);
if (ior(db, DM9000_NCR) & 1)
dev_err(db->dev, "dm9000 did not respond to second reset\n");
}
网卡的重置,都是对寄存器的操作,可以参考DM9000_Application_Notes_Ver_1_22.pdf
下载地址:http://download.csdn.net/detail/shuimuyq/9336125

/* from this point we assume that we have found a DM9000 */
ndev->netdev_ops	= &dm9000_netdev_ops;
ndev->watchdog_timeo	= msecs_to_jiffies(watchdog);
ndev->ethtool_ops	= &dm9000_ethtool_ops;

db->msg_enable       = NETIF_MSG_LINK;
db->mii.phy_id_mask  = 0x1f;
db->mii.reg_num_mask = 0x1f;
db->mii.force_media  = 0;
db->mii.full_duplex  = 0;
db->mii.dev	     = ndev;
db->mii.mdio_read    = dm9000_phy_read;
db->mii.mdio_write   = dm9000_phy_write;

mac_src = "eeprom";

/* 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, ETH_ALEN);
}

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";
}

platform_set_drvdata(pdev, ndev);
ret = register_netdev(ndev);

if (ret == 0)
printk(KERN_INFO "%s: dm9000%c at %p,%p IRQ %d MAC: %pM (%s)\n",
ndev->name, dm9000_type_to_char(db->type),
db->io_addr, db->io_data, ndev->irq,
ndev->dev_addr, mac_src);
return 0;

out:
dev_err(db->dev, "not found (%d).\n", ret);

dm9000_release_board(pdev, db);
free_netdev(ndev);

return ret;
}
设置/注册netdev_ops ethtool_ops, netdev_ops是设备支持的操作的实现, ethtool_ops是ethtool工具支持的操作的实现

watchdog_timeo是发送超时设置

mii(Medium Independent Interface (10M/100M)) 网卡设备对ioctl的支持,很多都是已经实现了,不需要重写

接下来是获取网卡地址,首先读取eeprom(Electrically Erasable Programmable Read-Only Memory电可擦可编程只读存储器)

如果读不到则去platform总线获取网卡地址,再读不到就去设备IO内存读取网卡地址,再读不到就随机生成网卡地址并警告
static unsigned char dm9000_type_to_char(enum dm9000_type type)
{
switch (type) {
case TYPE_DM9000E: return 'e';
case TYPE_DM9000A: return 'a';
case TYPE_DM9000B: return 'b';
}

return '?';
}

/* dm9000_release_board
*
* release a board, and any mapped resources
*/
static void dm9000_release_board(struct platform_device *pdev, struct board_info *db)
{
/* unmap our resources */

iounmap(db->io_addr);
iounmap(db->io_data);

/* release the resources */

if (db->data_req)
release_resource(db->data_req);
kfree(db->data_req);

if (db->addr_req)
release_resource(db->addr_req);
kfree(db->addr_req);
}

static int dm9000_drv_remove(struct platform_device *pdev)
{
struct net_device *ndev = platform_get_drvdata(pdev);

unregister_netdev(ndev);
dm9000_release_board(pdev, netdev_priv(ndev));
free_netdev(ndev);		/* free device structure */

dev_dbg(&pdev->dev, "released and freed device\n");
return 0;
}
这几个函数都很短,很好理解,dm9000_type_to_char根据设备类型,返回对应字母,id_val --> db->type

dm9000_release_board函数是在probe出错,或者remove时被调用,被映射的资源都应该释放

有一点不理解的是:db->data_req,db->addr_req都是通过request_mem_region申请的,为什么不是release_mem_region释放

dm9000_drv_remove函数在设备被移除时调用,就是probe的反操作,顺序要注意,在probe时register_netdev是在最后被调用

调用后就以为着内核可以使用这个设备了,在remove时unregister_netdev要在最开始被调用
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: