linux怎么解绑端口,Linux硬件设备访问(混杂设备)(国嵌学习笔记)

一、mmap设备操作

1>mmap系统调用

void* mmap(void* addr, size_t len, int prot, int flags, int fd,off_t offset)

内存映射函数mmap,负责把文件内容映射到进程的虚拟内存空间,通过这段内存的读取和修改,来实

现对文件的读取和修改,而不需要再调用read,write等操作。

3f7d3723e1698cb51a9f4fd7389d7b69.png

参数:

addr:指定映射的起始地址(一般不指定,设为NULL由系统指定)

length:映射到内存的文件长度。

prot:映射区的保护方式,可以是:

PROT_EXEC:映射区可被执行

PROT_READ:映射区可被读取

PROT_WRITE:映射区可被写入。

flags:映射区的特性,可以是:

MAP_SHARED:写入映射区的数据会复制回文件,且允许其他映射该文件的进程共享。

MAP_PRIVATE:对映射区的写入擦做会产生一个映射区的复制(copy-on-write),对此区域

所做的修改不会写回原文件。

fd:由open返回的文件描述符,代表要映射的文件。

offset:以文件开始处的偏移量,必须是分页大小的整数倍,通常为0,表示从文件头开始映射。

2>解除映射

int munmap(void* start, size_t length)

功能:

取消参数start所指向的映射内存,参数length表示欲取消的内存大小。

返回值:

解除成功返回0,否则返回-1,错误原因存于errno中。

3>mmap系统调用的使用

/****************mmap.c*******************/

#include

#include

#include

#include

#include

#include

#include

#include

#define MEMDEV_SIZE 4096

int main(void)

{

int fd, ret;

char buf[MEMDEV_SIZE];

char *pmdev;

if (-1==(fd=open ("/dev/memdev0", O_RDWR))) {

printf("open dev0 error\n");

_exit(EXIT_FAILURE);

}

printf("\nTest for mmap(): ...\n");

//sleep(30);

pmdev = (char*)mmap(NULL, MEMDEV_SIZE, PROT_READ|PROT_WRITE,

MAP_SHARED, fd, 0);

printf("mmap() return: pmdev=%p\n", pmdev);

sleep(20);

printf("begin to use pmdev ...\n");

bzero(pmdev, MEMDEV_SIZE);

strcpy(buf, "Test for mmap() -- write by write()");

printf("[use write()] write to mdev: %s\n", buf);

ret = write(fd, buf, strlen(buf));

printf("[use pmdev] content in mdev: %s\n", pmdev);

bzero(buf, MEMDEV_SIZE);

strcpy(buf, "write through pmdev, read by read()");

printf("[use pmdev] store to mdev(offset=%d): %s\n", ret, buf);

strcat(pmdev, buf);

ret = read(fd, buf, strlen(buf));

buf[ret] = 0;

printf("[use read()] read from mdev: %s\n", buf);

//sleep(30);

_exit(EXIT_SUCCESS);

}

4>虚拟内存区域

1、虚拟内存区域是进程的虚拟地址空间中的一个同质区间,即具有同样特性的连续地址范围。

一个进程的内存映像由下面几部分组成:程序代码、数据、BSS和栈区域,以及内存映射的区域。一

个进程的内存区域可以通过查看/proc/pid/maps.

每一行的域为:

start end perm offset major:minor inode

起始虚拟地址 结束虚拟地址 读写执行权限 被映射部分在文件中的起始地址 主次设备号 索引结点

2、Linux内核使用结构vm_area_struct()来描述虚拟内存区域,其中主要成

员如下:unsigned long vm_start:虚拟内存区域起始地址 unsigned long vm_end :虚拟内存区域

结束地址 unsigned long vm_flags:该区域的标记。如VM_IO和VM_RESERVED。VM_IO将该VMA标记为内

存映射的IO区域,VM_IO会阻止系统将将该区域包含在进程的存放转存(core dump)中,

VM_RESERVED标志内存区域不能被换出。

5>mmap设备操作

映射一个设备是指把用户空间的一段地址关联到设备内存上。当程序读写这段用户空间的地址

时,它实际上是在访问设备。mmap方法是file_oprations结构的成员,在mmap系统调用发出时被调

用。在此之前,内核已经完成了很多工作,mmap设备方法所需要做的就是建立虚拟地址到物理地址的

页表。int(*mmap)(struct file*,struct vm_area_struct*).

mmap建立页表有两种方法:

1、使用remap_pfn_range一次建立所有页表;

2、使用nopage VMA方法每次建立一个页表。

构造页表的函数原型:

int remap_pfn_range(struct vm_area_struct* vma,unsigned long addr,unsigned long pfn,unsigned long size,pgprot_t prot)

参数:

vma:虚拟内存区域指针

vir_addr:虚拟地址的起始值

pfn:要映射的物理地址所在的物理页帧号,可将物理地址>>PAGE_SHIFT得到。

size:要映射的区域大小

prot:VMA的保护属性。

实现实例:

int memdev_mmap(struct file* filp,struct vm_area_struct* vma)

{

vma->vm_flags |= VM_IO;

vma->vm_flags |= VM_RESERVED ;

if(remap_pfn_range(vma,vma->vm_start,virt_to_phys(dev->data)>>PAGE_SHIFT,size,vma->vm_page_prot))

return -EAGAIN;

return 0;

}

二、硬件访问

1>寄存器和内存

寄存器和RAM的主要不同在于寄存器操作有副作用(side effect或边际效果):读取某个地址时

可能导致该地址内容发生变化,比如很多设备的中断状态寄存器只要一读取,便自动清零。

2>内存与I/O

在X86处理器中存在I/O空间的概念,I/O空间是相对内存空间而言的,他们是彼此独立的地址空

间,在32位的X86系统中,I/O空间大小为64K,内存空间大小为4G。X86支持内存空间、I/O空间,

ARM、MIPS、PowerPC只支持内存空间。

3>IO端口和IO内存

IO端口:当一个寄存器或内存位于IO空间时,称其为IO端口。

IO内存:当一个寄存器或内存位于内存空间时,称其为IO内存。

4>操作I/O端口

对I/O端口的操作需按如下步骤完成:1.申请、2.访问、3.释放。

1、内核提供了一套函数来驱动申请它需要的I/O端口,其中核心函数是:

struct resource* request_region(unsigned long first,unsigned long n,const char* name)

这个函数告诉内核,你要使用从first开始的n个端口,name参数是设备的名字,如果申请成功,返回

是非NULL,申请失败,返回NULL。系统中端口的分配情况记录在/proc/ioports中展示。如果不能分

配需要的端口,可以来这里查看谁在使用。

2、I/O端口可分为8位,16位,32位端口。Linux内核头文件(体系依赖的头文件)定义了下列内联函数来访问I/O端口:

unsigned inb(unsigned port)    读字节端口(8位宽)。

void outb(unsigned char byte,unsigned port) 写字节端口(8位)。

unsigned inw(unsigned port)

void outw(unsigned short word,unsigned port)   存取16-位端口。

unsigned inl(unsigned port)

void outl(unsigned longword,unsigned port)   存取32-位端口。

3、当用完一组I/O端口(通常在驱动卸载时),应使用如下函数把他们返还系统:

void release_region(unsigned long start, unsigned long n)

5>操作I/O内存

1、申请:内核提供了一套函数来允许驱动申请它需要的I/O内存,其中核心的函数是:

struct resource* request_mem_region(unsigned long start,unsigned long len , char* name)

这个函数申请一个从start开始,长度为len字节的内存区。如果成功,返回非NULL;否则返回NULL,

所有已经在使用的I/O内存在/proc/iomem中列出。

2、映射:在访问I/O内存之前,必须进行物理地址到虚拟地址的映射,ioremap函数具有此功能:

void* ioremap(unsigned long phys_addr,unsigned long size)

3、访问:访问I/O内存的正确方法是通过一系列内核提供的函数:

读I/O内存读,使用下列之一:

unsigned ioread8(void* addr)

unsigned ioread16(void* addr)

unsigned ioread32(void* addr)

写I/O内存,使用下列之一:

void iowrite8(u8 value, void* addr)

void iowrite16(u16 value, void* addr)

void iowrite32(u32 value, void* addr)

4:释放:I/O内存不在需要使用时应当释放,步骤如下:

1、void iounmap(void* addr):解除映射

2、void release_mem_region(unsigned long start,unsigned long len)

三、混杂设备驱动

1>定义:在Linux系统中,存在一类字符设备,它们共享一个主设备号(10),但次设备号不同,我

们称这类设备为混杂设备(miscdevice)。所有的混杂设备形成一个链表,对设备访问时内核根据次

设备号查找到相应的miscdevice设备。

2>设备描述

Linux内核使用struct miscdevice来描述一个混杂设备。

struct miscdevice{

int minor;/*次设备号*/

const char* name;/*设备名*/

const struct file_operations* fops;/*文件操作*/

struct list_head list;

struct device* parent;

struct device* this_device;

};

3>设备注册

Linux内核使用misc_register函数来注册一个混杂设备驱动。

int misc_register(struct miscdevice* misc)

4>设备资源

设备资源使用struct resource来描述

struct resource{

resource_size_t start; //资源的起始物理地址

resource_size_t end; //资源的结束物理地址

const char* name;//资源的名称

unsigned long flags;//资源的类型,比如MEM。IO,IRQ类型

struct resource *parent,*sibling,*child;//资源链表指针

}

四、LED驱动程序设计

1>上拉/下拉电阻

上拉是将不确定的信号通过一个电阻与电源相连,固定在高电平。下拉是将不确定的信号通过一

个电阻与地相连,固定在低电平。上拉是对器件注入电流,下拉是输出电流。当一个接有上拉电阻的

I/O端口设为输入状态时,它的常态为高电平,可用于检测低电平的输入。

点击(此处)折叠或打开

#ifndef __LED_H__

#define __LED_H__

#include

#define DEVICE_NAME "up6410_led"

/*幻数*/

#define led_IOC_MAGIC '1'

/*led命令*/

#define LED_IOCGETDAT _IOR(led_IOC_MAGIC,1,int)

#define LED_IOCSETDAT _IOW(led_IOC_MAGIC,2,int)

#define LED_IOC_MAXNR    2

#endif

点击(此处)折叠或打开

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include "led.h"

unsigned long GPIOM_VA_BASE;

#define GPIOM_CON_VA    GPIO_VA_BASE

#define GPIOM_DAT_VA    GPIO_VA_BASE + 0x4

#define GPIOM_PUD_VA    GPIO_VA_BASE + 0x8

#define GPIO_VA_BASE     0x7f008820

struct resource ok6410_led_resource = {

.name = "led io_mem",

.strat = GPIO_PA_BASE,

.end = GPIO_PA_BASE + 0xc,

.flags = IORESOURCE_MEM,

};

static void ok6410_led_pin_setup(void)

{

unsigned long start = ok6410_led_resource.start;

unsigned long size = ok6410_led_resource.end - start;

unsigned long tmp;

/*申请IO内存*/

request_mem_region(start, size,ok6410_led_resource,name);

/*映射IO内存*/

GPIOM_VA_BASE = (unsigned long)ioremap(start, size);

printk("<1>[GPIOM_VA_BASE = 0x%lx]\n",GPIO_VA_BASE);

/*访问*/

tmp = readl(GPIOM_CON_VA);

tmp = (tmp & ~(0xffffU))|(0x1111U);//设置IO端口为输出功能

writel(tmp,GPIO_CON_VA);

tmp = readl(GPIOM_DAT_VA);

tmp |= 0xf;

writel(tmp, GPIOM_DAT_VA);

}

static unsigned long ok6410_led_getdat()

{

return (readl(GPIOM_DAT_VA)&0xF);

}

static void ok6410_led_setdat(int dat)

{

unsigned long tmp;

tmp = readl(GPIOM_DAT_VA);

tmp = (tmp & ~0xF) | (dat&0xF);

writel(tmp, GPIOM_DAT_VA);

}

static long led_ioctl(struct file* filp,unsigned long cmd,unsigned long arg)

{

int ioarg, ret;

if(_IOC_TYPE(cmd) != LED_IOC_MAGIC)

return -EINVAL;

if(_IOC_NR(cmd) > LED_IOC_MAXNR)

return -EINVAL;

switch(cmd){

case LED_IOCGETDAT:

ioarg = ok6410_led_getdat();

ret = put_user(ioarg,(int*)arg);

break;

case LED_IOCSETDAT:

ret = get_user(ioarg,(int*)arg);

ok6410_led_setdat(ioarg);

break;

default:

return -EINVAL;

}

return ret;

}

static void ok6410_led_pin_release(void)

{

/*解除映射*/

iounmap((void*)GPIOM_VA_BASE);

release_mem_region(ok6410_led_resource.start,ok6410_led_resource.end - ok6410_led_resource.start);

}

staitc struct file_operations dev_fops = {

.owner = THIS_MODULE,

.unlocked_ioctl = led_ioctl,

}

struct miscdevice misc = {

.minor = MISC_DYNAMIC_MINOR,

.name = DEVICE_NAME,

.fops = &dev_fops,

}

static int __init dev_init(void)

{

int ret;

ok_6410_led_pin_setup();//设置引脚功能

ret = misc_register(&misc);//混杂设备注册

return ret;

}

static void __exit dec_exit(void)

{

ok6410_led_pin_release();//释放引脚

misc_deregister(&misc);//注销设备

}

module_init(dev_init);

module_exit(dev_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("XIAHAIJUN");

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值