【driver3】proc文件系统,内存分配,数据类型/移植/对齐,内核中断,通过IO内存访问外设,PCI

news2024/7/30 19:35:37

文章目录

  • 1.创建proc文件系统接口:之前调试内核时都是通过prink打印内核信息,通过dmesg查看输出的信息。新调试方法:利用proc文件系统在pro文件夹下创建接口,读写这个接口就可实现对内核的调试
  • 2.内核内存分配函数:top,free,cat /pro/meminfo查看内存使用情况,cat /pro/slabinfo,cat /pro/buddyinfo,proc/sys/vm/下文件是虚拟内存更详细信息
    • 2.1 hello.c:和硬件有关,需要用到物理地址的,都不能使用vmalloc
  • 3.内核基础数据类型/移植性/数据对齐:页大小为PAGE_SIZE,不要假设4K,保证可移植性
    • 3.1 kdatasize.c:不同的架构(x86_64,arm),基础类型大小可能不同,主要区别在long和指针
    • 3.2 kdataalign.c:数据存储时没有特殊指定会自然对齐:在数据项大小的整数倍的地址处存储数据项,如数据项为short类型即2个字节(32位是4个字节),那就要存在地址能整除2的位置,字节对齐可以提高CPU的访问效率
  • 4.内核中断的使用,顶半部和底半部:使用中断可实现内核和外设的异步处理,提高通讯效率,降低系统功耗
    • 4.1 nmi中断:u8 = unsigned char = uint8_t
    • 4.2 crashdump:BMC作为IIC master可读取OCMCPLD(将GPIO中断信号存储到寄存器,利用逻辑器件的高实时性,帮BMC区分MCERR/IERR)
  • 5.通过IO内存访问外设:有的外设将自己的寄存器映射到了物理内存某个区域,那这个区域叫做io内存区域,linux内核访问这个区域能实现对外设控制和读写
  • 6.PCI设备驱动:pci是一种标准总线可实现块/网络/字符设备驱动,一部分是pci总线部分,另一是设备业务部分(和具体设备有关不讨论)


1.创建proc文件系统接口:之前调试内核时都是通过prink打印内核信息,通过dmesg查看输出的信息。新调试方法:利用proc文件系统在pro文件夹下创建接口,读写这个接口就可实现对内核的调试

/*
struct proc_ops    //pro文件夹下创建接口第一种方式
proc_create()

struct seq_operations   //第二种方式
proc_create_seq()

remove_proc_entry  //移除接口
*/
#include<linux/module.h>
#include<linux/uaccess.h>
#include<linux/string.h>
#define PROC_DEBUG
#ifdef PROC_DEBUG
#include<linux/proc_fs.h>  //传统第一种方式
#include<linux/seq_file.h>  //seq第二种方式
#endif

char * str = "hello proc\n";
#ifdef PROC_DEBUG    //由于proc一般用于调试,通常定义一个宏,将proc对应代码包起来,不需要这调试接口时,就可把这个宏注释掉,这样这个宏包含代码不会编译到内核中了。
int hp_open(struct inode * inode, struct file * filp)
{
	printk(KERN_INFO"open %ld\n",strlen(str));
	return 0;
}

ssize_t hp_read(struct file * filp, char __user * buff, size_t count, loff_t * f_pos)
{
	ssize_t retval=0;
	int n = strlen(str);
	if(*f_pos >= n)
		goto out;
	if(*f_pos + count > n)
		count = n - *f_pos;
	//如上是判断读的内容是否有效
	if(copy_to_user(buff,str,count))  //将字符串str赋值到buff用户空间
	{
		retval = -EFAULT;
		goto out;
	}
	*f_pos += count;
	return count;	
out:
	return retval;
}

struct proc_ops hp_ops = {   //下面 __init函数中proc_create调用hp_ops创建接口文件
	.proc_open = hp_open,
	.proc_read = hp_read,
};

//1111111111111111111111111111111111111111111111111111111111111111111111111111111111111
void * hp_seq_start (struct seq_file *m, loff_t *pos)  //pos表示当前读到哪个位置或写到哪个位置了
{
	printk(KERN_INFO"seq start\n");
	if(*pos >= strlen(str))  //pos指当前读到或写到哪个位置,索引
		return NULL;
	return &str[*pos];  //拿出字符串中字符,将地址返回,这返回值将来作为下面其他函数的v传入
}

void hp_seq_stop(struct seq_file *m, void *v)
{
	printk(KERN_INFO"seq stop\n");  //清除start函数一些工作,start里开辟一些空间或申请一些锁,这里清除
}

void * hp_seq_next (struct seq_file *m, void *v, loff_t *pos)  //改变索引值
{
	printk(KERN_INFO"seq next\n");
	(*pos)++;
	if(*pos >= strlen(str))
		return NULL;
	return &str[*pos];  
}

int hp_seq_show (struct seq_file *m, void *v)
{
	printk(KERN_INFO"seq show\n");
	seq_putc(m,*(char*)v);  //将获得到的字符一个一个打印出
	return 0;
}

const struct seq_operations seq_ops={  //构建这结构体
	.start = hp_seq_start,
	.stop = hp_seq_stop,
	.next = hp_seq_next,
	.show = hp_seq_show,
};
#endif

//11111111111111111111111111111111111111111111111111111111111111111111111111111111111
static int __init hello_init(void)	
{
	printk(KERN_INFO "HELLO LINUX MODULE\n");
#ifdef PROC_DEBUG
	proc_create("hello_proc",0,NULL,&hp_ops);  //第一个参数即显示在pro目录下文件名称,
 //第二个参数默认0只读权限。第三个参数父节点,null默认pro目录。最后一个参数是操作的结构体地址。
	
	proc_create_seq("hello_seq_proc",0,NULL,&seq_ops); //就可在pro目录下创建对应节点
#endif
	return 0;
}

static void __exit hello_exit(void)
{
#ifdef PROC_DEBUG
	remove_proc_entry("hello_proc",NULL);  //第二个参数是父节点
	remove_proc_entry("hello_seq_proc",NULL);
#endif
	printk(KERN_INFO "GOODBYE LINUX\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("KGZ");
MODULE_VERSION("V1.0"); 

rmmod就没有/proc/下接口了,"hello proc"有11个字符(连空格和换行符),所以show next调用了11次,最后一次返回null会调用stop。cat又会再调用一次,调start后直接返回null到stop。
在这里插入图片描述

2.内核内存分配函数:top,free,cat /pro/meminfo查看内存使用情况,cat /pro/slabinfo,cat /pro/buddyinfo,proc/sys/vm/下文件是虚拟内存更详细信息

2.1 hello.c:和硬件有关,需要用到物理地址的,都不能使用vmalloc

/*
1.如下两个一般千字节以下空间
kmalloc()	 分配空间不清0
kzalloc()    分配空间并清0 
kfree()

2.如下对于某些应用需要频繁分配或释放固定大小空间,如下可提前创建一个高速缓冲区,从高速缓冲区中分配空间,这样运行速度会快,内存使用效率也会高
struct kmem_cache	  //slab分配器/专用高速缓存  速度快 利用率高
kmem_cache_create()   //创建高速缓冲区,返回地址保存在上面一行的结构指针中,然后可调用下行函数分配空间,使用完后,free释放
kmem_cache_alloc()			
kmem_cache_free()
kmem_cache_destroy()  //清除高速缓冲区

3.大块内存
__get_free_page()	//按页分配,单独一页
__get_free_pages()  //多页
get_zeroed_page()   //清0
free_page()
free_pages()

vmalloc() / vfree()	 //分配的虚拟地址连续,物理地址不连续,效率不高,用在分配大的连续的、只在软件中使用的、用于缓存的内存区域, 和硬件有关和需用到物理地址的都不能用
*/
#include<linux/module.h>
#include<linux/slab.h>
#include<linux/gfp.h>  //按页分配__get_free_page(),包含在slab.h中
#include<linux/vmalloc.h>
char * kmlcp;
struct kmem_cache *h_cache;
char * kmemcp;
char * frpgp;
char * vmlcp;

static int  hello_init(void)	
{
	printk(KERN_INFO "HELLO LINUX MODULE\n");
//111111111111111111111111111111111111 1
	kmlcp = kmalloc(1024,GFP_KERNEL); //第一个参数:分配空间的大小,第二个参数:常用flag有GFP_KERNEL(kmalloc可休眠)和GFP_ATOMIC(kmalloc不可休眠),中断中分配空间用GFP_ATOMIC
	if(!kmlcp)  //不休眠可能会失败,所以这里判断下
	{
		return -ENOMEM;
	}
	printk(KERN_INFO"kmalloc get addr:%p\n",kmlcp); //分配成功,将地址打印出

//111111111111111111111111111111111111 2
	h_cache = kmem_cache_create("h_cache",512,0,SLAB_HWCACHE_ALIGN|SLAB_POISON,NULL);
	if(!h_cache)
	{
		kfree(kmlcp);
		return -ENOMEM;
	}
	kmemcp = kmem_cache_alloc(h_cache,GFP_KERNEL); //第一个参数就是kmem_cache_create返回发的地址
	if(!kmemcp)
	{
		//清除分配的资源
		return -ENOMEM;
	}
	printk(KERN_INFO"kmem_cache get addr:%p\n",kmemcp);

//111111111111111111111111111111111111 3
	frpgp =(void *) __get_free_pages(GFP_KERNEL,0);  //第二个参数是页面数,以2为底的对数值,0:1 1:2 2:4 3:8 ,填的0分配1个页,填的1分配2个页,填的2分配4个页
	if(!frpgp)
	{
		//清除分配的资源
		return -ENOMEM;
	}
	printk(KERN_INFO"free pages get addr:%p\n",frpgp);

//111111111111111111111111111111111111 4
	vmlcp = vmalloc(PAGE_SIZE<<4);   //大空间,大于一个页,这里分配16个页空间
	if(!vmlcp)
	{
		//清除分配的资源
		return -ENOMEM;
	}
	printk(KERN_INFO"vmalloc get addr:%p\n",vmlcp);
	return 0;
}

static void __exit hello_exit(void)
{
	printk(KERN_INFO "GOODBYE LINUX\n");
// 1	
	kfree(kmlcp);
// 2
	kmem_cache_free(h_cache,kmemcp);
	kmem_cache_destroy(h_cache);
// 3
	free_pages((unsigned long)frpgp,0);
// 4 
	vfree(vmlcp);
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("KGZ");
MODULE_VERSION("V1.0");

在这里插入图片描述

3.内核基础数据类型/移植性/数据对齐:页大小为PAGE_SIZE,不要假设4K,保证可移植性

3.1 kdatasize.c:不同的架构(x86_64,arm),基础类型大小可能不同,主要区别在long和指针

/*
	linux内核基础数据类型分三大类:C标准(int,long,char等),linux内核特有大小确定(u32,u16等),特定内核对象(pid_t,ssize_t,size_t等)
	
	由于不同平台数据类型大小有区别,要考虑程序可移植性:-Wall(编译时使用这个标志会检查所有不兼容的问题),消除所有警告就可保证程序可移植性。如果编译器支持uint32_t,则不使用u32,使用uint32_t等标准类型

	基础数据类型除了不同大小外还有存储方式不同,有的系统是大端存储方式,有的是小端,内核提供如下函数进行转换:	
	        cpu_to_le32()     le32_to_cpu() (小端32位转换为cpu存储类型)
			cpu_to_be32()     be32_to_cpu()  (大....)
			......
			htonl() (host主机转换为network的long类型)   ntohl() (network转主机)
			htons()	(.................short..)      			              ntohs()
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/utsname.h>
#include <linux/errno.h>

static void data_cleanup(void)
{
	/* never called */
}

int data_init(void)
{
	ssize_t n=90888;

	printk("arch   Size:  char  short  int  long   ptr long-long "
		" u8 u16 u32 u64\n");

	printk("%-12s  %3i   %3i   %3i   %3i   %3i   %3i      "
		"%3i %3i %3i %3i\n",
		init_uts_ns.name.machine,
		(int)sizeof(char), (int)sizeof(short), (int)sizeof(int),
		(int)sizeof(long),
		(int)sizeof(void *), (int)sizeof(long long), (int)sizeof(__u8),
		(int)sizeof(__u16), (int)sizeof(__u32), (int)sizeof(__u64));
		
	printk("%i, %li, %i, %li\n",(int)sizeof(pid_t),(long)current->pid,(int)sizeof(ssize_t),(long)n);
	
	printk("le32:%x be32:%x htonl:%x ntohl:%x\n",	cpu_to_le32(0x1234abcd),
													cpu_to_be32(0x1234abcd),
													htonl(0x1234abcd),
													ntohl(0x1234abcd));
	return -ENODEV;
}

module_init(data_init);
module_exit(data_cleanup);
MODULE_LICENSE("Dual BSD/GPL");

如下host是小端,network网络存储都是大端。
在这里插入图片描述

3.2 kdataalign.c:数据存储时没有特殊指定会自然对齐:在数据项大小的整数倍的地址处存储数据项,如数据项为short类型即2个字节(32位是4个字节),那就要存在地址能整除2的位置,字节对齐可以提高CPU的访问效率

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/utsname.h>
#include <linux/errno.h>

struct c   {char c;  char      t;} c; // 如果不对齐,第二个成员地址减去结构体地址都=1(第一个成员char)
struct s   {char c;  short     t;} s;
struct i   {char c;  int       t;} i;
struct l   {char c;  long      t;} l;
struct ll  {char c;  long long t;} ll;  //long long是8个字节
struct p   {char c;  void *    t;} p;
struct u1b {char c;  __u8      t;} u1b;
struct u2b {char c;  __u16     t;} u2b;
struct u4b {char c;  __u32     t;} u4b;
struct u8b {char c;  __u64     t;} u8b;

struct {
	u16 id;
	u8  a;
	u64 lun;
	u16 reserved1;
	u32 reserved2;
}__attribute__((packed)) scsi;  // 属性:不用对齐,每个数据紧挨着

struct {
	u16 id;
	u8  a;
	u64 lun;
	u16 reserved1;
	u32 reserved2;
} scsi1;

static void data_cleanup(void)
{
	/* never called */
}

static int data_init(void)
{
	printk("arch  Align:  char  short  int  long   ptr long-long "
		" u8 u16 u32 u64\n");
	printk("%-12s  %3i   %3i   %3i   %3i   %3i   %3i      "
		"%3i %3i %3i %3i\n",
		init_uts_ns.name.machine,
		(int)((void *)(&c.t)   - (void *)&c),   //第二个成员地址 - 结构体地址 = 地址差即偏移的字节数
		(int)((void *)(&s.t)   - (void *)&s),
		(int)((void *)(&i.t)   - (void *)&i),
		(int)((void *)(&l.t)   - (void *)&l),
		(int)((void *)(&p.t)   - (void *)&p),
		(int)((void *)(&ll.t)  - (void *)&ll),
		(int)((void *)(&u1b.t) - (void *)&u1b),
		(int)((void *)(&u2b.t) - (void *)&u2b),
		(int)((void *)(&u4b.t) - (void *)&u4b),
		(int)((void *)(&u8b.t) - (void *)&u8b));

	// 如下打印对齐和不对齐的每个成员地址
	printk("packed %i unpacked %i\n",(int)sizeof(scsi),(int)sizeof(scsi1));
	printk("      id		      a		       lun	        reserved1	 reserved2\n");
	printk("scsi  %lx %lx %lx %lx %lx",(unsigned long)&scsi.id,(unsigned long)&scsi.a,(unsigned long)&scsi.lun,(unsigned long)&scsi.reserved1,(unsigned long)&scsi.reserved2);
	printk("scsi1 %lx %lx %lx %lx %lx\n",(unsigned long)&scsi1.id,(unsigned long)&scsi1.a,(unsigned long)&scsi1.lun,(unsigned long)&scsi1.reserved1,(unsigned long)&scsi1.reserved2);
	
	return -ENODEV;
}

module_init(data_init);
module_exit(data_cleanup);
MODULE_LICENSE("Dual BSD/GPL");

x86_64 / unpacked / scsi1一行都是对齐的,char在地址0上,short在地址2上(因为1不能被2整除),
在这里插入图片描述
在这里插入图片描述

4.内核中断的使用,顶半部和底半部:使用中断可实现内核和外设的异步处理,提高通讯效率,降低系统功耗

// hello.c
/*
request_irq()  //申请中断,申请成功后就可以使用这个中断,一旦中断触发就会调用注册的回调函数
free_irq()
typedef irqreturn_t (*irq_handler_t)(int, void *); //中断回调函数,第一个参数是中断号int类型
enable_irq() //打开指定的中断 
disable_irq() 
*/
#include<linux/module.h>
#include<linux/gpio.h>  //测试中断需用外设,用到了树莓派的gpio
#include<linux/interrupt.h> //中断
#include<linux/proc_fs.h>  //用到了proc文件系统
#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; //此处退出后,workqueue_fn就会运行
}

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')  //给proc文件写入的是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 = { //proc_create接口,通过proc控制中断开闭
	.write = hp_write,
};

//11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
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); //初始化一个work,将底半部操作放在工作队列中去执行
	err = request_irq(gpio_to_irq(12),irq_handler,IRQ_TYPE_EDGE_BOTH,"hello-int",NULL);
//上行第一个参数:通过gpio号12获取中断号。第二个参数:中断回调函数irq_handler。第三个参数:触发方式:上升沿下降沿都会触发中断
//第四个参数:hello-int名称会在proc文件系统中显示,第五个参数:指针参数,这个参数会在中断触发后通过irq_handler中一个参数传入
    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); //释放request_irq申请的中断
	remove_proc_entry("hello_proc",NULL);
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("KGZ");
MODULE_VERSION("V1.0");

如下中断号,cpu0核心中断次数,cpu1…,中断控制器pinctrl-bcm2835。
在这里插入图片描述

// 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);  //树莓派21号引脚和之前树莓派12号引脚硬件连在一起,这样可通过控制21号引脚高低电平触发12号引脚中断
	while(n--)
	{
		bcm2835_gpio_set(21);  //高电平
		sleep(1);
		bcm2835_gpio_clr(21);  //低电平
		sleep(1);
	}
	return 0;
}

如下循环一次(参数是1)中触发两次中断,上升沿一次,下降沿一次。
在这里插入图片描述
如下dmesg先是中断回调,再是工作队列。cat /proc/interrupts看出在CPU0上发生2次中断。
在这里插入图片描述
如下关闭中断(非0是开中断),再次运行./gpioout 1,dmesg看没有信息更新。
在这里插入图片描述

#include <linux/workqueue.h>

struct workqueue_struct *my_workqueue;  // 创建一个工作队列, 下面my_workqueue =
struct work_struct my_work;  // 声明工作项结构
static void my_work_handler(struct work_struct *work) {..}  // 定义工作项回调函数

int init_module(void)
{
    my_workqueue = create_workqueue("my workqueue");  // 创建 工作队列
    INIT_WORK(&my_work, my_work_handler);  // 初始化 工作项
    int ret = queue_work(my_workqueue, &my_work);  // 将 工作项 提交到 工作队列
    if (ret)
    {
        printk("Failed to queue work\n");
        return ret;
    }
    return 0;
}

4.1 nmi中断:u8 = unsigned char = uint8_t

// nmi.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kmod.h>
#include <linux/interrupt.h>

#define NMI_ADDRESS  0xfd6d0000
#define NMI_READ    0x864

void __iomem *io_mem;
#define CMD_CRASH_DUMP          "/usr/local/bin/log.sh"

static struct workqueue_struct *g_mca_wq = NULL;
static struct work_struct g_mca_dwq;

static void read_log(struct work_struct *work)
{
    char cmd_path[] = CMD_CRASH_DUMP;
    char *cmd_argv[] = {cmd_path, NULL, NULL};
    char *cmd_envp[] = {NULL};
    printk(KERN_INFO "into read NMI\n");
    call_usermodehelper(cmd_path, cmd_argv, cmd_envp, UMH_WAIT_PROC);
}

static int Handling_functions(unsigned int cmd, struct pt_regs *regs)
{
    /*
    int value;
    value = ioread32(io_mem + NMI_READ);
    printk(KERN_INFO "Read NMI STATUS %x\n",value);
    */
    printk(KERN_INFO "Handing NMI\n");
    queue_work(g_mca_wq, &g_mca_dwq);
    return NMI_DONE;
}

static int register_nmi_handler_Processing(void)
{
	// 如下不可屏蔽中断,NMI_UNKNOWN硬件触发类型标识符参考arch/x86/include/asm/nmi.h文件
    register_nmi_handler(NMI_UNKNOWN,Handling_functions,0,"nmi_handler");  // request_irq可屏蔽中断
    return 0;
}

///
static int __init nmi_init_hua(void)
{
    int ret;
    io_mem = ioremap(NMI_ADDRESS,0x1000); //给ioread32用
    if (!io_mem) {
            printk(KERN_ERR "Failed to map GPIO %p to memory\n", io_mem);
            return -1;
    }
    ret = register_nmi_handler_Processing();
    if (ret) {
        return ret;
    } 
    g_mca_wq = create_workqueue("Nmi workqueue");
    if (g_mca_wq == NULL) {
        printk(KERN_ERR "%s: error creating CPU error workqueue\n", __func__);
        return -1;
    }
    INIT_WORK(&g_mca_dwq, read_log);
    printk(KERN_INFO "loaded NMI_test successfully\n");
    return 0;
}

static void __exit nmi_exit_hua(void)
{
    unregister_nmi_handler(NMI_UNKNOWN, "nmi_handler");
    destroy_workqueue(g_mca_wq);
    iounmap(io_mem);
    printk(KERN_INFO "unloaded NMI_test successfully\n");
}

module_init(nmi_init_hua);
module_exit(nmi_exit_hua);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");
MODULE_DESCRIPTION("NMI handler driver");
# Makefile
obj-m := gpiodrv.o
KERNELDIR ?= /home_a/alibmc/build/tmp/work/obmc_hq-fb-linux-gnueabi/linux-aspeed/4.1.51-r1/build #/usr/lib/modules/5.10.7/build
PWD ?= $(shell pwd)
all:
	make -C $(KERNELDIR) M=$(PWD) modules
	rm -f *.o *.symvers *.order *.mod.* *.ko.*
clean:
	make -C $(KERNELDIR) M=$(PWD) clean
	rm -f *.o *.ko *.symvers *.order *.mod.* *.ko.*

4.2 crashdump:BMC作为IIC master可读取OCMCPLD(将GPIO中断信号存储到寄存器,利用逻辑器件的高实时性,帮BMC区分MCERR/IERR)

在这里插入图片描述
如下mceer低触发后16个bclk(150ns)后会拉高,bmc只监控绿线gpio低电平触发中断:0-1。

#define     FM_CPU_MSMI_CATERR_LVT3_N         841   // 和oFM_CPU_CATERR_PLD_LVT3_N是一个信号

在这里插入图片描述
如下ierr低触发后一直是低(中间有一次拉低,所以打印出mceer和ierr),低电平触发:0-1-0。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如下注错ierr:iintel手册ICX EDSVol2 574942 R1-5.pdf
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如下注错mcerr:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.通过IO内存访问外设:有的外设将自己的寄存器映射到了物理内存某个区域,那这个区域叫做io内存区域,linux内核访问这个区域能实现对外设控制和读写

// hello.c
/*
request_mem_region()   //访问外设前需要先申请这片io内存区域
release_mem_region()
ioremap()   //io内存区域(上行申请的)是物理地址,内核使用的是虚拟地址,ioremap将物理地址映射为虚拟地址
iounmap()
ioread32()/ioread8()/ioread16()  //读取io内存 //硬件是树莓派,四字节对齐地址读写的话8/16/32位都能读到正常值
iowrite32()/iowrite8()/iowrite16()
*/
#include<linux/module.h>
#include<linux/io.h>

unsigned long gpio_base = 0x3f200000;  //树莓派gpio基地址
int gpio_len =0xb3;  //寄存器范围
struct timer_list t1;  //内核定时器,让1s开一次灯,1s关一次灯
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)); //1c寄存器将gpio置为高电平
	else
		iowrite32(ioread32((void *)(gpio_base+0x28))|1<<4,(void*)(gpio_base+0x28)); //28寄存器将gpio置为低电平
	flag=!flag;
	mod_timer(&t1,jiffies+msecs_to_jiffies(1000));  //gpio4接了一个led灯,以1s频率亮灭
}

//11111111111111111111111111111111111111111111111111111111111111111111111111111111
static int __init hello_init(void)	
{
	printk(KERN_INFO "HELLO LINUX MODULE\n");
	// if (! request_mem_region(gpio_base,gpio_len , "gpio")) {  //理论上先申请这片区域,不过树莓派已经将这片区域申请好了,可通过cat /proc/iomem了解i/o内存分配情况(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第一个参数是写的值,第二个参数是写的地址
	iowrite32(ioread32((void *)gpio_base)|(1<<12),(void*)gpio_base);  //这一整行代码意思是将pin4设置为输出,具体寄存器含义下载树莓派芯片手册查看
	printk(KERN_INFO"gpio remap base:0x%lx\n",gpio_base); //打印地址
	//如下gpio地址是4字节对齐的,可以用如下8 16 32读, 如果将gpio_base+1,+2,+3就不对了
	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)); //设置溢出时间1s	
	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");  	//版本

在这里插入图片描述
如下make,insmod,打印的地址是虚拟地址,8位读到的是0,16位读到的是1900。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.PCI设备驱动:pci是一种标准总线可实现块/网络/字符设备驱动,一部分是pci总线部分,另一是设备业务部分(和具体设备有关不讨论)

在这里插入图片描述
如下是PCI设备的配置寄存器值:每个PCI设备中都有一个配置区域,这个区域保存了PCI设备信息,下图是前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内存),获取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_device_id里面包含这驱动支持的所有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;
}

//11111111111111111111111111111111111111111111111111111111111111111111111
static int probe(struct pci_dev *dev, const struct pci_device_id *id)  // 第一个参数pci设备结构体,第二个参数匹配的pci_device_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); //获得区域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);  //分配成功,就将物理地址映射到内核的虚拟地址中,作用同下行,不过pci.h提供pci_ioremap_bar就用这个
   //mypci->ioaddr = ioremap(mypci->io,mypci->range);  这里变量的类型与函数参数的类型必须一致,否则会出错
   if(!mypci->ioaddr)
   {
      printk("ioremap err!\n");
      retval = -ENOMEM;
      goto out_regions;
   }
   //申请中断IRQ并给中断号绑定中断服务子函数pci_ioremap_bar
   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地址进行

static void remove(struct pci_dev *dev) //移除PCI设备,清除在prob函数中做的工作
{
   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");
}

//11111111111111111111111111111111111111111111111111111111111111111111
static struct pci_driver pci_driver = {
	.name = "pci_skel",   //一般和模块名称一样即本文件名称
	.id_table = ids,     //支持的所有设备结构体数组的名称
	.probe = 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);

如下网卡驱动在设备启动时就加载了,需先将模块驱动移除rmmod。显示probe func说明调用了probe函数,发现了匹配的设备。mypci->ioaddr是映射后的虚拟地址。
在这里插入图片描述
lspci:列出系统中所有pci的简略信息,总线:设备.功能
在这里插入图片描述
如下还有一个pci网卡信息。cat /pro/bus/pci/devices也会列出pci设备详细信息。
在这里插入图片描述
如下进入目录得到pci设备信息文件。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1646295.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

spring ioc 容器加载过程 refresh() 方法详解

IOC 加载过程 从 new ClassPathXmlApplicationContext开始 ApplicationContext context new ClassPathXmlApplicationContext("classpath:application.xml");ClassPathXmlApplicationContext类构造方法 public ClassPathXmlApplicationContext(String[] configLo…

自动驾驶融合定位:IMU内参模型及标定

自动驾驶融合定位&#xff1a;IMU内参模型及标定 一、 概述 标定的本质是参数辨识。首先明确哪些参数可辨识&#xff0c;其次弄清怎样辨识。 参数包括陀螺仪和加速度计各自的零偏、标度因数、安装误差。 辨识就比较丰富了&#xff0c;如果让各位先不局限于标定任务&#xf…

数据分析——业务指标分析

业务指标分析 前言一、业务指标分析的定义二、业务问题构建问题构建的要求 三、业务问题的识别在识别问题的阶段对于企业内部收益者的补充 四、竞争者分析竞争者分析的内容竞争者分析目的案例 五、市场机会识别好的市场机会必须满足的条件市场机会案例 六、风险控制数据分析师常…

【Git实战】如何将本地仓库推送至Github(windows版)?

最近使用Go语言开发项目&#xff0c;想寻找位操作相关的工具包。找了一圈没有合适的&#xff0c;因此自己写了一个。又想将其推送到Github上&#xff0c;中间逢山开路&#xff0c;遇水搭桥&#xff0c;终于成功将本地Git仓库和Github进行了关联。现将我的方法公之于众&#xff…

网页html版面分析-- BeauifulSoup(python 文档解析提取)

介绍 BeauifulSoup 是一个可以从HTML或XML 文件中提取数据的python库&#xff1b;它能通过转换器实现惯用的文档导航、查找、修改文档的方式。 BeauifulSoup是一个基于re开发的解析库&#xff0c;可以提供一些强大的解析功能&#xff1b;使用BeauifulSoup 能够提高提取数据的效…

国产最强多模态大模型Step Fun-1V,究竟有多好用?

前言&#xff1a; 2023年是大模型的元年&#xff0c;随着ChatGPT的爆火&#xff0c;将大模型带入了公众的视野 &#xff0c;国内也随之掀起了百模大战的浪潮。在这股浪潮的推动下&#xff0c;诸多科技公司&#xff0c;乃至大厂都争相推出自己的大模型产品&#xff0c;希望能够…

华为 二层交换机与防火墙连通上网实验

防火墙是一种网络安全设备&#xff0c;用于监控和控制网络流量。它可以帮助防止未经授权的访问&#xff0c;保护网络免受攻击和恶意软件感染。防火墙可以根据预定义的规则过滤流量&#xff0c;例如允许或阻止特定IP地址或端口的流量。它也可以检测和阻止恶意软件、病毒和其他威…

element-plus upload查看图片后不展示press delete to remove?

直接上全局样式就行&#xff1a; :deep(.el-upload-list__item .el-icon--close-tip) {display: none !important; }

灌溉机器人 状压dp

灌溉机器人 题目描述 农田灌溉是一项十分费体力的农活&#xff0c;特别是大型的农田。小明想为农民伯伯们减轻农作负担&#xff0c;最近在研究一款高科技——灌溉机器人。它可以在远程电脑控制下&#xff0c;给农田里的作物进行灌溉。 现在有一片 N 行 M 列的农田。农田的土…

【数据结构】有关环形链表题目的总结

文章目录 引入 - 快慢指针思考 - 快慢指针行走步数进阶 - 寻找环形链表的头 引入 - 快慢指针 141-环形链表 - Leetcode 关于这道题&#xff0c;大家可以利用快慢指针&#xff0c;一个每次走两步&#xff0c;一个每次走一步&#xff0c;只要他们有一次相撞了就代表说这是一个链…

【吃透Java手写】SpringBoot-简易版-源码解析

【吃透Java手写】SpringBoot-简易版-源码解析 1 SpringbootDemo2 准备工作2.1 Springboot-my2.1.1 依赖2.1.2 SpringBootApplication2.1.3 SJBSpringApplication2.1.3.1 run方法 2.2 Springboot-user2.2.1 依赖2.2.2 UserController2.2.3 UserApplication 2.3 分析run方法的逻辑…

13 【PS作图】人物绘画理论-脸型

三庭五眼 三庭&#xff1a;脸的长度比例 &#xff08;1&#xff09;发际线到眉毛 &#xff08;2&#xff09;眉毛到鼻底 &#xff08;3&#xff09;鼻底到下巴 三个部分大致为三等分 五眼&#xff1a;脸的宽度比例 以眼睛长度为单位&#xff0c;把脸的宽度分成五等分&#x…

为什么很多人不推荐你用JWT?

为什么很多人不推荐你用JWT? 如果你经常看一些网上的带你做项目的教程&#xff0c;你就会发现 有很多的项目都用到了JWT。那么他到底安全吗&#xff1f;为什么那么多人不推荐你去使用。这个文章将会从全方面的带你了解JWT 以及他的优缺点。 什么是JWT? 这个是他的官网JSON…

搜索算法系列之四(斐波那契)

以下算法被验证过&#xff0c;如有什么问题或有补充的欢迎留言。 前言 斐波那契数列&#xff0c;又称黄金分割数列&#xff0c;是由意大利数学家&#xff08;Leonardo Fibonacci&#xff09;在1202年提出的。这个数列的递推关系是F(0)1&#xff0c;F(1)1&#xff0c;F(n)F(n-…

微搭低代码入门05文件的上传和下载

目录 1 创建数据源2 创建应用3 创建页面4 设置导航功能5 文件上传6 文件下载总结 小程序中&#xff0c;我们通常会有文件的上传和下载的需&#xff0c;在微搭中&#xff0c;文件是存放在云存储中&#xff0c;每一个文件都会有一个唯一的fileid&#xff0c;我们本篇就介绍如何通…

强化学习玩flappy_bird

强化学习玩flappy_bird&#xff08;代码解析&#xff09; 游戏地址&#xff1a;https://flappybird.io/ 该游戏的规则是&#xff1a; 点击屏幕则小鸟立即获得向上速度。 不点击屏幕则小鸟受重力加速度影响逐渐掉落。 小鸟碰到地面会死亡&#xff0c;碰到水管会死亡。&#…

vscode连接服务器的docker步骤

进入容器之后&#xff0c;操作方式与本地windows系统操作逻辑一样&#xff1b;容器内部结构都能任意查看和使用&#xff0c;创建文件及编写python脚本都可以直接使用vs code编辑器进行编辑和调试&#xff0c;从而避免使用命令行及vim编辑文件&#xff0c;非常直观且方便~

4. RedHat认证-进程管理

4. RedHat认证-进程管理 1.进程概念 进程就是正在运行中的程序或者命令 每一个进程都是运行的实体&#xff0c;都有自己的地址空间&#xff0c;并占有一定的资源空间 程序消耗的是磁盘资源、进程消耗的是内存和CPU资源 进程会占用四类资源&#xff08;CPU 、内存、磁盘、网…

python爬虫(一)之 抓取极氪网站汽车文章

极氪汽车文章爬虫 闲来没事&#xff0c;将极氪网站的汽车文章吃干抹尽&#xff0c;全部抓取到本地&#xff0c;还是有点小小的难度。不能抓取太快&#xff0c;太快容易被封禁IP&#xff0c;不过就算被封了问题也不大&#xff0c;大不了重启路由器&#xff0c;然后你的IP里面又…

i.MX 6ULL 裸机 IAR 环境安装

一. IAR 的安装请自行搜索 二. 使用最新版本的 IAR&#xff0c;需要修改 SDK 1. 在 SDK 的 core_ca7.h 加上 #include "intrinsics.h" /* IAR Intrinsics */ 2. debug 时需要修改每个工程下的 ddr_init.jlinkscript&#xff0c;参考链接 Solved: How to conn…