ARM-Linux驱动--DMA驱动分析(一)

硬件平台:FL2440 (s3c2440)

内核版本:2.6.35

主机平台:Ubuntu 11.04

内核版本:2.6.39

原创作品,转载请标明出处http://blog.csdn.net/yming0221/article/details/6645821

1、DMA的功能和工作原理这里就不多说了,可以查看s3c2440的手册

2、在正式分析DMA驱动之前,我们先来看一下DMA的注册和初始化过程

系统设备:(翻译自源码注释)

系统设备和系统模型有点不同,它不需要动态绑定驱动,不能被探测(probe),不归结为任何的系统总线,所以要区分对待。对待系统设备我们仍然要有设备驱动的观念,因为我们需要对设备进行基本的操作。

定义系统设备,在./arch/arm/mach-s3c2440/s3c244x.c中

/* 定义系统设备类 */
struct sysdev_class s3c2440_sysclass = {
	.name		= "s3c2440-core",
	.suspend	= s3c244x_suspend,
	.resume		= s3c244x_resume
};
注册系统设备类,在真正注册设备之前,确保已经注册了初始化了的系统设备类

static int __init s3c2440_core_init(void)
{
	return sysdev_class_register(&s3c2440_sysclass);
}

下面就是系统设备类的注册函数,在./drivers/base/sys.c中

int sysdev_class_register(struct sysdev_class *cls)
{
	int retval;

	pr_debug("Registering sysdev class '%s'\n", cls->name);

	INIT_LIST_HEAD(&cls->drivers);
	memset(&cls->kset.kobj, 0x00, sizeof(struct kobject));
	cls->kset.kobj.parent = &system_kset->kobj;
	cls->kset.kobj.ktype = &ktype_sysdev_class;
	cls->kset.kobj.kset = system_kset;

	retval = kobject_set_name(&cls->kset.kobj, "%s", cls->name);
	if (retval)
		return retval;

	retval = kset_register(&cls->kset);
	if (!retval && cls->attrs)
		retval = sysfs_create_files(&cls->kset.kobj,
					    (const struct attribute **)cls->attrs);
	return retval;
}

/* 定义DMA系统设备驱动 */
static struct sysdev_driver s3c2440_dma_driver = {
	.add	= s3c2440_dma_add,/* 添加add函数 */
};
下面是add函数,就是调用三个函数

static int __init s3c2440_dma_add(struct sys_device *sysdev)
{
	s3c2410_dma_init();
	s3c24xx_dma_order_set(&s3c2440_dma_order);
	return s3c24xx_dma_init_map(&s3c2440_dma_sel);
}
注册DMA驱动到系统设备

static int __init s3c2440_dma_init(void)
{
	return sysdev_driver_register(&s3c2440_sysclass, &s3c2440_dma_driver);
}
下面就是系统设备驱动的注册函数

/**
 *	sysdev_driver_register - Register auxillary driver
 *	@cls:	Device class driver belongs to.
 *	@drv:	Driver.
 *
 *	@drv is inserted into @cls->drivers to be
 *	called on each operation on devices of that class. The refcount
 *	of @cls is incremented.
 */

int sysdev_driver_register(struct sysdev_class *cls, struct sysdev_driver *drv)
{
	int err = 0;

	if (!cls) {
		WARN(1, KERN_WARNING "sysdev: invalid class passed to "
			"sysdev_driver_register!\n");
		return -EINVAL;
	}

	/* Check whether this driver has already been added to a class. */
	if (drv->entry.next && !list_empty(&drv->entry))
		WARN(1, KERN_WARNING "sysdev: class %s: driver (%p) has already"
			" been registered to a class, something is wrong, but "
			"will forge on!\n", cls->name, drv);

	mutex_lock(&sysdev_drivers_lock);
	if (cls && kset_get(&cls->kset)) {
		list_add_tail(&drv->entry, &cls->drivers);/* 将设备驱动添加到系统设备类的链表中 */

		/* If devices of this class already exist, tell the driver */
		if (drv->add) {
			struct sys_device *dev;
			list_for_each_entry(dev, &cls->kset.list, kobj.entry)
				drv->add(dev);
		}
	} else {
		err = -EINVAL;
		WARN(1, KERN_ERR "%s: invalid device class\n", __func__);
	}
	mutex_unlock(&sysdev_drivers_lock);
	return err;
}
在./arch/arm/mach-s3c2440/s3c2440.c中定义s3c2440的系统设备和注册

static struct sys_device s3c2440_sysdev = {
	.cls		= &s3c2440_sysclass,/* 定义系统设备的所属系统设备类,用于系统设备注册到指定设备类 */
};
/* S3C2440初始化 */
int __init s3c2440_init(void)
{
	printk("S3C2440: Initialising architecture\n");

	s3c24xx_gpiocfg_default.set_pull = s3c_gpio_setpull_1up;
	s3c24xx_gpiocfg_default.get_pull = s3c_gpio_getpull_1up;

	/* change irq for watchdog */

	s3c_device_wdt.resource[1].start = IRQ_S3C2440_WDT;
	s3c_device_wdt.resource[1].end   = IRQ_S3C2440_WDT;

	/* register our system device for everything else */

	return sysdev_register(&s3c2440_sysdev);/* 注册s3c2440的系统设备 */
}
接下来是系统设备的注册函数

/**
 *	sysdev_register - add a system device to the tree
 *	@sysdev:	device in question
 *
 */
 /* 系统设备的注册 */
int sysdev_register(struct sys_device *sysdev)
{
	int error;
	struct sysdev_class *cls = sysdev->cls;/* 所属的系统设备类 */

	if (!cls)
		return -EINVAL;

	pr_debug("Registering sys device of class '%s'\n",
		 kobject_name(&cls->kset.kobj));

	/* initialize the kobject to 0, in case it had previously been used */
	memset(&sysdev->kobj, 0x00, sizeof(struct kobject));

	/* Make sure the kset is set */
	sysdev->kobj.kset = &cls->kset;

	/* Register the object */
	error = kobject_init_and_add(&sysdev->kobj, &ktype_sysdev, NULL,
				     "%s%d", kobject_name(&cls->kset.kobj),
				     sysdev->id);

	if (!error) {
		struct sysdev_driver *drv;

		pr_debug("Registering sys device '%s'\n",
			 kobject_name(&sysdev->kobj));

		mutex_lock(&sysdev_drivers_lock);
		/* Generic notification is implicit, because it's that
		 * code that should have called us.
		 */

		/* Notify class auxillary drivers */
		list_for_each_entry(drv, &cls->drivers, entry) {
			if (drv->add)
				drv->add(sysdev);/* 遍历该设备所属同一个设备类的所有设备,并执行相应的add函数 */
		}
		mutex_unlock(&sysdev_drivers_lock);
		kobject_uevent(&sysdev->kobj, KOBJ_ADD);
	}

	return error;
}
那DMA系统设备驱动中的add函数中到底是什么呢?

(1)首先看第一个函数int __init s3c2410_dma_init(void),在./arch/arm/plat-s3c24xx/dma.c

int __init s3c2410_dma_init(void)
{
	return s3c24xx_dma_init(4, IRQ_DMA0, 0x40);
}
实际上就是初始化DMA为4通道,设置中断号,设置寄存器的覆盖范围

下面是该函数的实现

int __init s3c24xx_dma_init(unsigned int channels, unsigned int irq,
			    unsigned int stride)/* 参数分别为通道个数、中断号、寄存器的覆盖范围 */
{
	struct s3c2410_dma_chan *cp;/* 通道的结构体表示 */
	int channel;
	int ret;

	printk("S3C24XX DMA Driver, Copyright 2003-2006 Simtec Electronics\n");

	dma_channels = channels;

	dma_base = ioremap(S3C24XX_PA_DMA, stride * channels);
	if (dma_base == NULL) {
		printk(KERN_ERR "dma failed to remap register block\n");
		return -ENOMEM;
	}
	
	/* 分配DMA告诉缓冲区 */
	dma_kmem = kmem_cache_create("dma_desc",
				     sizeof(struct s3c2410_dma_buf), 0,
				     SLAB_HWCACHE_ALIGN,
				     s3c2410_dma_cache_ctor);

	if (dma_kmem == NULL) {
		printk(KERN_ERR "dma failed to make kmem cache\n");
		ret = -ENOMEM;
		goto err;
	}

	for (channel = 0; channel < channels;  channel++) {
		cp = &s3c2410_chans[channel];

		memset(cp, 0, sizeof(struct s3c2410_dma_chan));

		/* dma channel irqs are in order.. */
		cp->number = channel;
		cp->irq    = channel + irq;
		cp->regs   = dma_base + (channel * stride);

		/* point current stats somewhere */
		cp->stats  = &cp->stats_store;
		cp->stats_store.timeout_shortest = LONG_MAX;

		/* basic channel configuration */

		cp->load_timeout = 1<<18;

		printk("DMA channel %d at %p, irq %d\n",
		       cp->number, cp->regs, cp->irq);
	}

	return 0;
	
/* 异常处理 */
 err:
	kmem_cache_destroy(dma_kmem);
	iounmap(dma_base);
	dma_base = NULL;
	return ret;
}

(2)然后是函数s3c24xx_dma_order_set(&s3c2440_dma_order);

int __init s3c24xx_dma_order_set(struct s3c24xx_dma_order *ord)
{
	struct s3c24xx_dma_order *nord = dma_order;

	if (nord == NULL)
		nord = kmalloc(sizeof(struct s3c24xx_dma_order), GFP_KERNEL);

	if (nord == NULL) {
		printk(KERN_ERR "no memory to store dma channel order\n");
		return -ENOMEM;
	}

	dma_order = nord;
	memcpy(nord, ord, sizeof(struct s3c24xx_dma_order));
	return 0;
}
我们注意到函数中使用了kmalloc给结构体重新分配了内存,这是由于__initdata修饰的变量表示初始化用的变量,初始化完毕后空间自动释放,所以需要将其存储起来。

(3)最后一个函数s3c24xx_dma_init_map(&s3c2440_dma_sel)

该函数功能是建立DMA源与硬件通道的映射图

int __init s3c24xx_dma_init_map(struct s3c24xx_dma_selection *sel)
{
	struct s3c24xx_dma_map *nmap;
	size_t map_sz = sizeof(*nmap) * sel->map_size;
	int ptr;

	nmap = kmalloc(map_sz, GFP_KERNEL);
	if (nmap == NULL)
		return -ENOMEM;

	memcpy(nmap, sel->map, map_sz);
	memcpy(&dma_sel, sel, sizeof(*sel));

	dma_sel.map = nmap;

	for (ptr = 0; ptr < sel->map_size; ptr++)
		s3c24xx_dma_check_entry(nmap+ptr, ptr);

	return 0;
}
这里的kmalloc函数的作用同上面的作用一样。

注:由于内核实在是太深了,这里只是表面上按流程大体了解了子同设备的注册和系统设备驱动的注册以及DMA设备的注册和初始化,函数中有很多细节有待进一步研究。

你可能感兴趣的:(linux)