Notes17内核中断,通过IO内存访问外设,pci

Posted 码农编程录

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Notes17内核中断,通过IO内存访问外设,pci相关的知识,希望对你有一定的参考价值。


1.内核中断的使用,顶半部和底半部

gpioout.c
#include <bcm2835.h>
#include<unistd.h>

int main(int argc ,char* argv[])

	int n = atoi(argv[1]);
    bcm2835_init();
	bcm2835_gpio_fsel(21,BCM2835_GPIO_FSEL_OUTP);
	while(n--)
	
		bcm2835_gpio_set(21);
		sleep(1);
		bcm2835_gpio_clr(21);
		sleep(1);
	
	
	return 0;
	

hello.c
/*
request_irq()
free_irq()
typedef irqreturn_t (*irq_handler_t)(int, void *);
enable_irq()
disable_irq()
local_irq_enable()
local_irq_restore()
local_irq_disable()
local_irq_save()
*/

#include<linux/module.h>
#include<linux/gpio.h>
#include<linux/interrupt.h>
#include<linux/proc_fs.h>
#include<linux/uaccess.h>

static struct work_struct work;
unsigned long flags;
void workqueue_fn(struct work_struct *work)			//下半部/底半部

	printk("hello workqueue\\n");


static irqreturn_t irq_handler(int irq,void *dev)	//上半部/顶半部

	static int n=0;
	printk("get irq%d int %d\\n",irq,++n);
	schedule_work(&work);
	return IRQ_HANDLED;


ssize_t hp_write(struct file * filp, const char __user * buff, size_t count, loff_t * f_pos)

	char a;
	get_user(a,buff);
	if(a=='0')
	
		printk("disable irq\\n");
		disable_irq(gpio_to_irq(12));
		//local_irq_disable();
		//local_irq_save(flags);
	
	else
	
		printk("enable irq\\n");
		enable_irq(gpio_to_irq(12));
		//local_irq_enable();
		//local_irq_restore(flags);		
	
	return count;	


struct file_operations hp_ops = 
	.write = hp_write,
;


static int __init hello_init(void)	

	int err;
	printk(KERN_INFO "HELLO LINUX MODULE\\n");
	proc_create("hello_proc",0777,NULL,&hp_ops);
	INIT_WORK(&work,workqueue_fn);
	err = request_irq(gpio_to_irq(12),irq_handler,IRQ_TYPE_EDGE_BOTH,"hello-int",NULL);
    if(err<0)
    
        printk("irq_request failed\\n");
		remove_proc_entry("hello_proc",NULL);
        return err;
    	
	return 0;


static void __exit hello_exit(void)

	printk(KERN_INFO "GOODBYE LINUX\\n");
	free_irq(gpio_to_irq(12),NULL);
	remove_proc_entry("hello_proc",NULL);
	


module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");//许可 GPL、GPL v2、Dual MPL/GPL、Proprietary(专有)等,没有内核会提示
MODULE_AUTHOR("KGZ");		//作者
MODULE_VERSION("V1.0");  	//版本
// makefile
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD       := $(shell pwd)

obj-m	:=hello.o

all:
	make -C $(KERNELDIR) M=$(PWD) modules

clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.mod *.order *.symvers

2.通过IO内存访问外设

hello.c
/*
request_mem_region()
release_mem_region()
ioremap()
iounmap()
ioread32()   ioread8()/ioread16()
iowrite32()  iowrite8()/iowrite16()
*/

#include<linux/module.h>
#include<linux/io.h>

unsigned long gpio_base = 0x3f200000;
int gpio_len =0xb3;
struct timer_list t1;
int tdelay;
uint8_t flag=0;
void timer_fn(struct timer_list *t)

	if(flag)
		iowrite32(ioread32((void *)(gpio_base+0x1c))|(1<<4),(void*)(gpio_base+0x1c));
	else
		iowrite32(ioread32((void *)(gpio_base+0x28))|1<<4,(void*)(gpio_base+0x28));
	flag=!flag;
	mod_timer(&t1,jiffies+msecs_to_jiffies(1000));


static int __init hello_init(void)	

	printk(KERN_INFO "HELLO LINUX MODULE\\n");
	// if (! request_mem_region(gpio_base,gpio_len , "gpio")) 
			// printk(KERN_INFO " can't get I/O mem address 0x%lx\\n",
					// gpio_base);
			// return -ENODEV;
	// 
	gpio_base = (unsigned long)ioremap(gpio_base,gpio_len);
	iowrite32(ioread32((void *)gpio_base)|(1<<12),(void*)gpio_base);  //pin4 output
	printk(KERN_INFO"gpio remap base:0x%lx\\n",gpio_base);
	printk(KERN_INFO"read %x %x %x\\n",ioread8((void *)(gpio_base)),ioread16((void *)(gpio_base)),ioread32((void *)(gpio_base)));
	timer_setup(&t1,timer_fn,0);
	mod_timer(&t1,jiffies+msecs_to_jiffies(1000));
	
	return 0;


static void __exit hello_exit(void)

	printk(KERN_INFO "GOODBYE LINUX\\n");
	//release_mem_region(gpio_base,gpio_len);
	del_timer(&t1);
	iounmap((void *)gpio_base);
	

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");//许可 GPL、GPL v2、Dual MPL/GPL、Proprietary(专有)等,没有内核会提示
MODULE_AUTHOR("KGZ");		//作者
MODULE_VERSION("V1.0");  	//版本

3.pci

pci是一种标准总线,基于它可以实现块设备,网络设备,字符设备。实现PCI设备驱动:一个部分是pci总线部分,另一个是设备业务部分。如下是pci总线树形结构示意图,两个id用来匹配pci设备和驱动。

如下是PCI设备的配置寄存器值。每个PCI设备中都有一个配置区域,这个区域保存了PCI设备信息。下图是前64字节内容,前64字节内容是标准化的。

// pci_skel.c
/*
struct pci_device_id	 构造一个数组包含驱动支持的所有设备
PCI_DEVICE()      这个宏通过vendor-id和device-id填充上面pci_device_id结构体内容
PCI_DEVICE_CLASS()    通过class类填充pci_device_id结构体内容
MODULE_DEVICE_TABLE()    上面填充好结构体构造的数组后,调用MODULE_DEVICE_TABLE()宏,导出pci_device_id结构体到用户空间,使热插拔和模块装载系统知道什么模块针对什么硬件设备

struct pci_driver   利用这结构体将驱动注册到内核中
pci_register_driver()		注册
pci_unregister_driver()		注销

在读取pci设备的配置寄存器或io空间,io地址时,需要如下调用
pci_enable_device()      激活/初始化pci设备,比如唤醒设备、读写配置信息等
pci_disable_device()

如下内核提供一系列函数读取pci设备配置信息
pci_read_config_byte()   8位
pci_read_config_word()   16位
pci_read_config_dword()   32位
pci_resource_start()	获取区域信息(bar info) pci支持6个区域(io端口/io内存)
pci_resource_end()   io空间结束地址
pci_resource_flags()   io空间标志信息

pci_request_regions()	获得io空间地址后,调用这行函数申请这片区域,跟request_mem_region()一样
pci_release_regions()

pci_ioremap_bar()	物理地址映射到虚拟地址空间,跟ioremap一样,作了必要的检查
pci_set_drvdata()	设置驱动私有数据
pci_get_drvdata()	获取驱动私有数据
*/
#include <linux/module.h>
#include <linux/pci.h>

struct pci_card    //私有数据

   //端口读写变量
   resource_size_t io;     //io空间起始地址
   long range,flags;       //空间大小,标志
   void __iomem *ioaddr;    //地址被映射后虚拟地址
   int irq;     //pci设备中断号
;


static struct pci_device_id ids[] =   //里面包含这驱动支持的所有pci设备
	 PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x100e) ,   //第一个参数:厂商号。第二个参数:设备id
	 PCI_DEVICE(PCI_VENDOR_ID_INTEL,PCI_DEVICE_ID_INTEL_80332_0) ,
	 0,   //最后一组是0,表示结束
;
MODULE_DEVICE_TABLE(pci, ids); //导出到用户空间:第一个参数:总线类型。第二个参数:上面数组名称。

//
void skel_get_configs(struct pci_dev *dev)

	uint8_t val1;
	uint16_t val2;
	uint32_t val4;

	pci_read_config_word(dev,PCI_VENDOR_ID, &val2);
	printk("vendorID:%x",val2);
	pci_read_config_word(dev,PCI_DEVICE_ID, &val2);
	printk("deviceID:%x",val2);
	pci_read_config_byte(dev, PCI_REVISION_ID, &val1);
	printk("revisionID:%x",val1);
	pci_read_config_dword(dev,PCI_CLASS_REVISION, &val4);
	printk("class:%x",val4);

///

/* 设备中断服务*/
static irqreturn_t mypci_interrupt(int irq, void *dev_id)

   struct pci_card *mypci = (struct pci_card *)dev_id;
   printk("irq = %d,mypci_irq = %d\\n",irq,mypci->irq);
   return IRQ_HANDLED;



static int probe(struct pci_dev *dev, const struct pci_device_id *id)

	int retval = 0;
	struct pci_card *mypci;
	printk("probe func\\n"); 
	if(pci_enable_device(dev))   //激活pci设备
	
		printk (KERN_ERR "IO Error.\\n");
		return -EIO;
	
	mypci = kmalloc(sizeof(struct pci_card),GFP_KERNEL);  //私有数据分配一空间
   if(!mypci)
   
      printk("In %s,kmalloc err!",__func__);
      return -ENOMEM;
   

   mypci->irq = dev->irq;  //给私有数据终端号赋值,内核启动时扫描pci设备,给pci设备分配中断号获取基本信息 
   if(mypci->irq < 0)
   
      printk("IRQ is %d, it's invalid!\\n",mypci->irq);
      goto out_mypci;
   

   mypci->io = pci_resource_start(dev, 0);
   mypci->range = pci_resource_end(dev, 0) - mypci->io + 1;  //结束地址-开始地址+1
   mypci->flags = pci_resource_flags(dev,0); //获取区域0标志,这标志会指示这区域是io内存还是io端口
   printk("start %llx %lx %lx\\n",mypci->io,mypci->range,mypci->flags);
   printk("PCI base addr 0 is io%s.\\n",(mypci->flags & IORESOURCE_MEM)? "mem":"port"); //判断是io内存还是io端口

  //retval=request_mem_region(mypci->io,mypci->range, "pci_skel");
   retval = pci_request_regions(dev,"pci_skel"); //要操作这内存区域,首先要分配
   if(retval)
   
      printk("PCI request regions err!\\n");
      goto out_mypci;
   
   mypci->ioaddr = pci_ioremap_bar(dev,0);  //分配成功,就将物理地址映射到内核的虚拟地址中
   //mypci->ioaddr = ioremap(mypci->io,mypci->range);  这里变量的类型与函数参数的类型必须一致,否则会出错
   if(!mypci->ioaddr)
   
      printk("ioremap err!\\n");
      retval = -ENOMEM;
      goto out_regions;
   
   //申请中断IRQ并设定中断服务子函数并绑定中断号
   retval = request_irq(mypci->irq, mypci_interrupt, IRQF_SHARED, "pci_skel", mypci);
   if(retval)
   
      printk (KERN_ERR "Can't get assigned IRQ %d.\\n",mypci->irq);
      goto out_iounmap;
   
   pci_set_drvdata(dev,mypci);  //将私有数据保存到pci设备结构体中
   printk("Probe succeeds.PCIE ioport addr start at %llX, mypci->ioaddr is 0x%p,interrupt No. %d.\\n",mypci->io,mypci->ioaddr,mypci->irq);
 	skel_get_configs(dev); //测试读写配置空间
   return 0;
  
out_iounmap:
	iounmap(mypci->ioaddr);
out_regions:
	pci_release_regions(dev);
out_mypci:
	kfree(mypci);
	return retval;


//当probe函数结束后就拿到了pci设备io空间地址,之后业务逻辑代码操作这io地址进行
/
/* 移除PCI设备 */
static void remove(struct pci_dev *dev)

   struct pci_card *mypci = pci_get_drvdata(dev);  //获得私有数据
   free_irq (mypci->irq, mypci);  //释放中断号
   iounmap(mypci->ioaddr);  //取消地址映射
   //release_mem_region(mypci->io,mypci->range);
   pci_release_regions(dev);  //释放申请的空间
   kfree(mypci);   //释放私有数据
   pci_disable_device(dev);  //关闭pci设备
   printk("Device is removed successfully.\\n");



static struct pci_driver pci_driver = 
	.name = "pci_skel",     //一般和模块名称一样即本文件名称
	.id_table = ids,     //支持的所有设备结构体名称
	.probe = probe,     //当内核检测到和驱动匹配后会调用
	.remove = remove,
;

static int __init pci_skel_init(void)

	printk("HELLO PCI\\n");
	return pci_register_driver(&pci_driver);  //上面结构体地址


static void __exit pci_skel_exit(void)

	printk("GOODBYE PCI\\n");
	pci_unregister_driver(&pci_driver);  //注销驱动程序


MODULE_LICENSE("GPL");
module_init(pci_skel_init);
module_exit(pci_skel_exit);

以上是关于Notes17内核中断,通过IO内存访问外设,pci的主要内容,如果未能解决你的问题,请参考以下文章

C2内核模块,分配设备号,字符驱动,/设备节点,设备读写,/同步和互斥,ioctl函数,进程休眠,时间和延时,延缓,/proc文件系统,内存分配,数据类型,/内核中断,通过IO内存访问外设

[架构之路-37]:目标系统 - 系统软件 - Linux OS硬件设备驱动必须熟悉的六大工作机制之:内存与IO访问中断定时与延时

[架构之路-37]:目标系统 - 系统软件 - Linux OS硬件设备驱动必须熟悉的六大工作机制之:内存与IO访问中断定时与延时

王爽《汇编语言》第三版 第十五章 外中断

[架构之路-120]-《软考-系统架构设计师》-计算机体系结构 -2- 一文了解ARM SOC体系结构原理(CPU工作原理指令内存中断堆栈IO初始化)

第15章 外中断