第21章PCI设备驱动之PCI设备驱动结构

21.2 PCI 设备驱动结构

21.2.1 PCI 设备驱动的组成

         PCI 只是一种总线,具体的 PCI 设备可以是字符设备、网络设备、USB 主机控制器等,因此,一个通过 PCI 总线与系统连接的设备的驱动至少包含以下两部分内容。

1)、PCI 驱动。

2)、设备本身的驱动。

        PCI 驱动只是为了辅助设备本身的驱动,它不是目的,而是手段。例如,对于通过 PCI 总线与系统连接的字符设备,则驱动中除要实现 PCI 驱动部分外,其主体仍然是设备作为字符设备本身的驱动,即实现 file_operations 成员函数并注册 cdev。

        在 Linux 内核中,用 pci_driver 结构体来定义 PCI 驱动,该结构体中包含了 PCI 设备的探测/移除、挂起/恢复等函数,其定义如代码清单 21.5 所示。pci_driver起到挂接总线的作用。

代码清单 21.5 pci_driver 结构体

include/linux/pci.h

struct pci_driver {
        struct list_head node;
        const char *name;
        const struct pci_device_id *id_table;   /*不能为 NULL,以便 probe 函数调用*/
        int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id);   /* New device inserted */
        void (*remove) (struct pci_dev *dev);   /* Device removed (NULL if not a hot-plug capable driver) */
        int  (*suspend) (struct pci_dev *dev, pm_message_t state);      /* Device suspended */
        int  (*suspend_late) (struct pci_dev *dev, pm_message_t state);
        int  (*resume_early) (struct pci_dev *dev);
        int  (*resume) (struct pci_dev *dev);                   /* Device woken up */
        void (*shutdown) (struct pci_dev *dev);
        int (*sriov_configure) (struct pci_dev *dev, int num_vfs); /* PF pdev */
        const struct pci_error_handlers *err_handler;
        struct device_driver    driver;
        struct pci_dynids dynids;

};

对 pci_driver 的注册和注销通过如下函数来实现:

int pci_register_driver(struct pci_driver *drv);

void pci_unregister_driver(struct pci_driver *drv);

    pci_driver 的 probe()函数要完成 PCI 设备的初始化及其设备本身身份(字符、TTY、网络等)驱动的注册。当 Linux 内核启动并完成对所有 PCI 设备进行扫描、登录和分配资源等初始化操作的同时,会建立起系统中所有 PCI 设备的拓扑结构,probe()函数将负责硬件的探测工作并保存配置信息。

    以一个 PCI 接口字符设备为例,给出 PCI 设备驱动的完整模板。其一部分代码实现 pci_driver成员函数,一部分代码实现字符设备的 file_operations 成员函数,如代码清单 21.6 所示。

代码清单 21.6 PCI 设备驱动的程序模板

/* 指明该驱动程序适用于哪一些 PCI 设备 */

static struct pci_device_id xxx_pci_tbl [] __initdata = {

             {

                    PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO,

                    PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEMO

             },

             {0,}
 };

MODULE_DEVICE_TABLE(pci, xxx_pci_tbl);

/* 中断处理函数 */
 static void xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
 {
         ...
 }

 /* 字符设备 file_operations open 成员函数 */
 static int xxx_open(struct inode *inode, struct file *file)
 {
             /* 申请中断,注册中断处理程序 */
             request_irq(xxx_irq, &xxx_interrupt, ...));
             ...
 }

/* 字符设备 file_operations ioctl 成员函数 */
 static int xxx_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
 {
             ...
 }

    /* 字符设备 file_operations read、write、mmap 等成员函数 */

 /* 设备文件操作接口 */
 static struct file_operations xxx_fops = {
             owner: THIS_MODULE, /* xxx_fops 所属的设备模块 */
             read: xxx_read, /* 读设备操作*/
             write: xxx_write, /* 写设备操作*/
             ioctl: xxx_ioctl, /* 控制设备操作*/
             mmap: xxx_mmap, /* 内存重映射操作*/
             open: xxx_open, /* 打开设备操作*/
             release: xxx_release /* 释放设备操作*/
 };

/* pci_driver 的 probe 成员函数 */
 static int _ _init xxx_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id)
 {
             pci_enable_device(pci_dev); /* 启动 PCI 设备 */

             /* 读取 PCI 配置信息 */
             iobase = pci_resource_start (pci_dev,1);
              ...
    
             pci_set_master(pci_dev);//设置成总线主 DMA 模式

             pci_request_regions(pci_dev);/* 申请 I/O 资源 */

             /* 注册字符设备 */
             cdev_init(xxx_cdev,&xxx_fops);
             register_chrdev_region(xxx_dev_no, 1, ...);
             cdev_add(xxx_cdev);

             return 0;
 }

 /* pci_driver 的 remove 成员函数 */
 static int _ _init xxx_remove (struct pci_dev *pdev)
 {
         pci_release_regions(pdev);/* 释放 I/O 资源 */
         pci_disable_device (pdev);/* 禁止 PCI 设备 */
         unregister_chrdev_region(xxx_dev_no, 1); /* 释放占用的设备号 */
         cdev_del(&xxx_dev.cdev); /* 注销字符设备 */
         ...
         return 0;
 }

/* 设备模块信息 */
 static struct pci_driver xxx_pci_driver = {
             name: xxx_MODULE_NAME, /* 设备模块名称 */
             id_table: xxx_pci_tbl, /* 能够驱动的设备列表 */
             probe: xxx_probe, /* 查找并初始化设备 */
             remove: xxx_remove /* 卸载设备模块 */
};

static int _ _init xxx_init_module (void)
{

        pci_register_driver(&xxx_pci_driver); // 注册PCI驱动
}

static void _ _exit xxx_cleanup_module (void)
 {
         pci_unregister_driver(&xxx_pci_driver); // 注销PCI驱动
 }

 /*驱动模块加载函数 */
 module_init(xxx_init_module);
 /* 驱动模块卸载函数 */
 module_exit(xxx_cleanup_module);

分析:

        在 PCI 设备驱动中,也需要定义一个 pci_device_id 结构体数组。pci_device_id 用于标识一个 PCI 设备。其成员包括:厂商 ID、设备 ID、子厂商 ID、子设备 ID、类别、类别掩码(类可分为基类、子类)和私有数据。每一个 PCI 设备的驱动程序都有一个 pci_device_id 的数组,用于告诉 PCI 核心自己能够驱动哪些设备,pci_device_id 结构体的定义如代码清单 21.7 所示。

代码清单 21.7 pci_device_id 结构体

include/linux/mod_devicetable.h

struct pci_device_id {
        __u32 vendor, device;           /* Vendor and device ID or PCI_ANY_ID*/
        __u32 subvendor, subdevice;     /* Subsystem ID's or PCI_ANY_ID */
        __u32 class, class_mask;        /* (class,subclass,prog-if) triplet */
        kernel_ulong_t driver_data;     /* Data private to the driver */
};

将代码清单 21.6 中的各个函数进行归类,可得出该驱动的组成,如图 21.4 所示。


图21.4 PCI 字符设备驱动的组成

        图 21.5 所示的树中,树根是主机/PCI 桥树叶是具体的 PCI 设备树叶本身与树枝通过pci_driver 连接,而树叶本身的驱动,读写、控制树叶则需要通过其树叶设备本身所属类设备(字符设备、网络设备等)驱动来完成


图21.5 PCI 设备驱动的组成

        由此看出,对于 USB、PCI 设备这种挂接在总线上的设备,USB、PCI 只是它们的“工作单位”,它们需要向“工作单位”注册(注册 usb_driver、pci_driver),并接收“工作单位”的管理(被调入 probe()、调出 disconnect()/remove()、放假 suspend()/shutdown()、继续上班 resume()等)。但是其本身可能是一个工程师、一个前台或一个经理,因此,做好工程师、前台或经理是其主体工作,这部分对应于字符设备驱动、tty 设备驱动、网络设备驱动等。

21.2.2 PCI 骨架程序

        drivers/net/pci-skeleton.c给出一个 PCI 接口网络设备驱动程序的“骨架”,其 pci_driver 中probe()成员函数netdrv_init_one及其调用的netdrv_init_board完成PCI设备初始化及对应的网络设备注册工作,代码清单 21.8 所示为这两个函数的实现。

代码清单 21.8 pci-skeleton 设备驱动的 probe()函数

/* 网络设备的初始化和注册 */

static int __devinit netdrv_init_one (struct pci_dev *pdev,
       const struct pci_device_id *ent)
{
struct net_device *dev = NULL;
struct netdrv_private *tp;
int i, addr_len, option;
void *ioaddr = NULL;
static int board_idx = -1;

/* when built into the kernel, we only print version if device is found */
#ifndef MODULE
static int printed_version;
if (!printed_version++)
printk(version);
#endif

DPRINTK ("ENTER\n");

assert (pdev != NULL);
assert (ent != NULL);

board_idx++;

i = netdrv_init_board (pdev, &dev, &ioaddr);
if (i < 0) {
DPRINTK ("EXIT, returning %d\n", i);
return i;
}

tp = dev->priv;

assert (ioaddr != NULL);
assert (dev != NULL);
assert (tp != NULL);

addr_len = read_eeprom (ioaddr, 0, 8) == 0x8129 ? 8 : 6;
for (i = 0; i < 3; i++)
((u16 *) (dev->dev_addr))[i] =
    le16_to_cpu (read_eeprom (ioaddr, i + 7, addr_len));

/* The Rtl8139-specific entries in the device structure. */
dev->open = netdrv_open;
dev->hard_start_xmit = netdrv_start_xmit;
dev->stop = netdrv_close;
dev->get_stats = netdrv_get_stats;
dev->set_multicast_list = netdrv_set_rx_mode;
dev->do_ioctl = netdrv_ioctl;
dev->tx_timeout = netdrv_tx_timeout;
dev->watchdog_timeo = TX_TIMEOUT;

dev->irq = pdev->irq;
dev->base_addr = (unsigned long) ioaddr;

/* dev->priv/tp zeroed and aligned in alloc_etherdev */
tp = dev->priv;

/* note: tp->chipset set in netdrv_init_board */
tp->drv_flags = PCI_COMMAND_IO | PCI_COMMAND_MEMORY |
PCI_COMMAND_MASTER | NETDRV_CAPS;
tp->pci_dev = pdev;
tp->board = ent->driver_data;
tp->mmio_addr = ioaddr;
spin_lock_init(&tp->lock);

pci_set_drvdata(pdev, dev);

tp->phys[0] = 32;

printk (KERN_INFO "%s: %s at 0x%lx, "
"%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x, "
"IRQ %d\n",
dev->name,
board_info[ent->driver_data].name,
dev->base_addr,
dev->dev_addr[0], dev->dev_addr[1],
dev->dev_addr[2], dev->dev_addr[3],
dev->dev_addr[4], dev->dev_addr[5],
dev->irq);

printk (KERN_DEBUG "%s:  Identified 8139 chip type '%s'\n",
dev->name, rtl_chip_info[tp->chipset].name);

/* Put the chip into low-power mode. */
NETDRV_W8_F (Cfg9346, Cfg9346_Unlock);

/* The lower four bits are the media type. */
option = (board_idx > 7) ? 0 : media[board_idx];
if (option > 0) {
tp->full_duplex = (option & 0x200) ? 1 : 0;
tp->default_port = option & 15;
if (tp->default_port)
tp->medialock = 1;
}

if (tp->full_duplex) {
printk (KERN_INFO
"%s: Media type forced to Full Duplex.\n",
dev->name);
mdio_write (dev, tp->phys[0], MII_ADVERTISE, ADVERTISE_FULL);
tp->duplex_lock = 1;
}

DPRINTK ("EXIT - returning 0\n");
return 0;
}

static int __devinit netdrv_init_board (struct pci_dev *pdev,
struct net_device **dev_out,
void **ioaddr_out)
{
void *ioaddr = NULL;
struct net_device *dev;
struct netdrv_private *tp;
int rc, i;
u32 pio_start, pio_end, pio_flags, pio_len;
unsigned long mmio_start, mmio_end, mmio_flags, mmio_len;
u32 tmp;

DPRINTK ("ENTER\n");

assert (pdev != NULL);
assert (ioaddr_out != NULL);

*ioaddr_out = NULL;
*dev_out = NULL;

/* dev zeroed in alloc_etherdev */
dev = alloc_etherdev (sizeof (*tp));
if (dev == NULL) {
dev_err(&pdev->dev, "unable to alloc new ethernet\n");
DPRINTK ("EXIT, returning -ENOMEM\n");
return -ENOMEM;
}
SET_MODULE_OWNER(dev);
SET_NETDEV_DEV(dev, &pdev->dev);
tp = dev->priv;

/* enable device (incl. PCI PM wakeup), and bus-mastering */
rc = pci_enable_device (pdev);
if (rc)
goto err_out;

pio_start = pci_resource_start (pdev, 0);
pio_end = pci_resource_end (pdev, 0);
pio_flags = pci_resource_flags (pdev, 0);
pio_len = pci_resource_len (pdev, 0);

mmio_start = pci_resource_start (pdev, 1);
mmio_end = pci_resource_end (pdev, 1);
mmio_flags = pci_resource_flags (pdev, 1);
mmio_len = pci_resource_len (pdev, 1);

/* set this immediately, we need to know before
* we talk to the chip directly */
DPRINTK("PIO region size == 0x%02X\n", pio_len);
DPRINTK("MMIO region size == 0x%02lX\n", mmio_len);

/* make sure PCI base addr 0 is PIO */
if (!(pio_flags & IORESOURCE_IO)) {
dev_err(&pdev->dev, "region #0 not a PIO resource, aborting\n");
rc = -ENODEV;
goto err_out;
}

/* make sure PCI base addr 1 is MMIO */
if (!(mmio_flags & IORESOURCE_MEM)) {
dev_err(&pdev->dev, "region #1 not an MMIO resource, aborting\n");
rc = -ENODEV;
goto err_out;
}

/* check for weird/broken PCI region reporting */
if ((pio_len < NETDRV_MIN_IO_SIZE) ||
    (mmio_len < NETDRV_MIN_IO_SIZE)) {
dev_err(&pdev->dev, "Invalid PCI region size(s), aborting\n");
rc = -ENODEV;
goto err_out;
}

rc = pci_request_regions (pdev, MODNAME);
if (rc)
goto err_out;

pci_set_master (pdev);

#ifdef USE_IO_OPS
ioaddr = (void *) pio_start;
#else
/* ioremap MMIO region */
ioaddr = ioremap (mmio_start, mmio_len);
if (ioaddr == NULL) {
dev_err(&pdev->dev, "cannot remap MMIO, aborting\n");
rc = -EIO;
goto err_out_free_res;
}
#endif /* USE_IO_OPS */

/* Soft reset the chip. */
NETDRV_W8 (ChipCmd, (NETDRV_R8 (ChipCmd) & ChipCmdClear) | CmdReset);

/* Check that the chip has finished the reset. */
for (i = 1000; i > 0; i--)
if ((NETDRV_R8 (ChipCmd) & CmdReset) == 0)
break;
else
udelay (10);

/* Bring the chip out of low-power mode. */
/* <insert device-specific code here> */

#ifndef USE_IO_OPS
/* sanity checks -- ensure PIO and MMIO registers agree */
assert (inb (pio_start+Config0) == readb (ioaddr+Config0));
assert (inb (pio_start+Config1) == readb (ioaddr+Config1));
assert (inb (pio_start+TxConfig) == readb (ioaddr+TxConfig));
assert (inb (pio_start+RxConfig) == readb (ioaddr+RxConfig));
#endif /* !USE_IO_OPS */

/* identify chip attached to board */
tmp = NETDRV_R8 (ChipVersion);
for (i = ARRAY_SIZE (rtl_chip_info) - 1; i >= 0; i--)
if (tmp == rtl_chip_info[i].version) {
tp->chipset = i;
goto match;
}

/* if unknown chip, assume array element #0, original RTL-8139 in this case */
dev_printk (KERN_DEBUG, &pdev->dev,
"unknown chip version, assuming RTL-8139\n");
dev_printk (KERN_DEBUG, &pdev->dev, "TxConfig = 0x%lx\n",
NETDRV_R32 (TxConfig));
tp->chipset = 0;


match:
DPRINTK ("chipset id (%d) == index %d, '%s'\n",
tmp,
tp->chipset,
rtl_chip_info[tp->chipset].name);


rc = register_netdev (dev);
if (rc)
goto err_out_unmap;


DPRINTK ("EXIT, returning 0\n");
*ioaddr_out = ioaddr;
*dev_out = dev;
return 0;

err_out_unmap:
#ifndef USE_IO_OPS
iounmap(ioaddr);
err_out_free_res:
#endif
pci_release_regions (pdev);
err_out:
free_netdev (dev);
DPRINTK ("EXIT, returning %d\n", rc);
return rc;
}

分析:

        probe()函数完成网络设备的初始化和注册pci_driver 的 remove()成员函数完成相反的工作,即注销网络设备,如代码清单 21.9 所示。

代码清单 21.9 pci-skeleton 设备驱动的 remove()函数

static void __devexit netdrv_remove_one (struct pci_dev *pdev)
{
struct net_device *dev = pci_get_drvdata (pdev);
struct netdrv_private *np;

DPRINTK ("ENTER\n");

assert (dev != NULL);

np = dev->priv;
assert (np != NULL);
unregister_netdev (dev);

#ifndef USE_IO_OPS
iounmap (np->mmio_addr);
#endif /* !USE_IO_OPS */

pci_release_regions (pdev);
free_netdev (dev);
pci_set_drvdata (pdev, NULL);
pci_disable_device (pdev);
DPRINTK ("EXIT\n");
}

        /drivers/net/pci-skeleton.c 中定义的 pci_device_id 结构体数组及 MODULE_DEVICE_TABLE导出代码如清单 21.10 所示。

代码清单 21.10 PCI 设备驱动的 pci_device_id 数组及 MODULE_DEVICE_TABLE

static struct pci_device_id netdrv_pci_tbl[] = {
{0x10ec, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x10ec, 0x8138, PCI_ANY_ID, PCI_ANY_ID, 0, 0, NETDRV_CB },
{0x1113, 0x1211, PCI_ANY_ID, PCI_ANY_ID, 0, 0, SMC1211TX },
/* {0x1113, 0x1211, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MPX5030 },*/
{0x1500, 0x1360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, DELTA8139 },
{0x4033, 0x1360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, ADDTRON8139 },
{0,}
};
MODULE_DEVICE_TABLE (pci, netdrv_pci_tbl);

除此之外,pci-skeleton 的主体即是完成网络设备驱动相关的工作。

总结:

        PCI 设备驱动只是字符设备、tty 设备、网络设备、音频设备等与系统的一个接口,因此,驱动由两部分组成,一部分是 PCI 相关部分,另一部分是设备本身所属类驱动。PCI 驱动的核心数据结构是 pci_driver,在 probe()成员函数中将申请资源并注册对应的字符设备、tty 设备、网络设备、音频设备等,而 remove()成员函数中将释放资源并注销对应的字符设备、tty 设备、网络设备、音频设备等。


  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值