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初始化)