memcpy函数用法_吐血整理--Linux字符驱动中的常用函数

今天先吐到这里。。


编写字符驱动的时候,我们大都是从现成的驱动中去查询函数的用法,所以不需要去记住那些函数的用法。

但是我们还是要记住一些基本的函数名,否则也就无法展开驱动的编写或者搜寻它们的用法:

  1. module_init(); module_exit();
  2. register_chrdev(); unregister_chrdev(); --上古版本
  3. 设备号申请:alloc_chrdev_region(); register_chrdev_region(); unregister_chrdev_region();
  4. 设备号处理:MAJOR(); MINOR(); MKDEV();
  5. 字符设备注册结构体:cdev; cdev_init(); cdev_add(); cdev_del();
  6. 内部操作:memcpy();
  7. 内外交互:copy_to_user(); copy_from_user();
  8. ioremap(); iounmap(); //采用设备树以后大多都使用of_iomap()
  9. 内存读函数:readb(); readw(); readl(); /* 8bit, 16bit, 32bit */
  10. 内存写函数:writeb(); writew(); writel(); /* 8bit, 16bit, 32bit */自动创建设备节点函数:
  11. class_create(); class_destroy();
  12. device_create(); device_destroy();
  13. 声明设备树中的节点: device_node
  14. GPIO使用:1:of_get_named_gpio() 2:gpio_request /gpio_free 3:gpio_direction_input/output 4: gpio_get_value() 5: gpio_set_value()
  15. 声明一个定时器结构体:timer_list
    初始化定时器:init_timer(), 未设置周期时不会激活定时器
    定时器结构体 .function = ?_function; 指定定时器回调函数
    定时器结构体 .data = 回调函数传入的参数
    定时器结构体 .expires=定时器超时时间,即周期
    开启定时器:timer_add()
    删除定时器:del_timer_sync() ://等待所有处理器用完该定时器后删除
    立即删除定时器: del_timer() :
    修改定时器:mod_timer() //修改定时器会立即激活定时器
    时间转换: msecs_to_jiffies()Note: 与硬件定时器不同的是,使用内核定时器不需要我们去做寄存器初始化,另外内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。

66284031e766f38b072fa95de8d5d4ab.png
内核短延时函数
  1. 中断服务函数指针的声明: irqreturn_t (*handler)(int, void *);
    1. 中断申请: request_irq() // 可能会导致睡眠,所以不能用在禁止睡眠的代码段中
      request_threaded_irq() -- 申请线程化的中断
      从设备树获取中断号:irq_of_parse_and_map() // 如果使用GPIO的话可以使用函数 gpio_to_irq()
      释放中断: free_irq()
  2. 定义原子变量:atomic_t a ;

df854c82023c8c198c4e8f146e4c0261.png


另外还有原子位操作, 但是并没有相应的变量结构,而是通过几个API函数直接对内存进行操作。

840c5b60f2c40246713733549f20faf4.png
  1. 声明自旋锁: spinlock_t
    初始化自旋锁: spin_lock_init()
    对于临界区的访问:
    在线程中可以用:
    获取锁: spin_lock_irqsave()
    释放锁: spin_unlock_irqrestore()
    在中断服务函数中可以用:
    获取锁: spin_lock()
    释放锁: spin_unlock()
    下半部中可以用:
    获取锁: spin_lock_bh()
    释放锁: spin_unlock_bh()
    另外,
    基于自旋锁,还衍生出了其他特定场合使用的锁,这些锁在驱动中用的不多,但是在 Linux 内核中常被用到。如:读写锁和顺序锁。
  2. struct semaphore sem; //定义信号量
    sema_init(&sem, 1); //初始化信号
    down(&sem);
    //操作临界区
    up(&sem);

b21cb57671ae58b644157932f90f12f8.png
  1. 互斥体等同于值为1的二值信号量,当互斥访问时优先使用,如下图

f1f56786304b8a8d95ea180dbcc94c37.png
  1. 声明等待队列体:wait_queue_head_t
    初始化等待队列头:init_waitqueue_head()
    // 上面这两个可以用 DECLARE_WAIT_QUEUE_HEAD() 这一个函数来代替。
    定义等待队列: DECLARE_WAITQUEUE()
    添加等待队列到等待队列头:add_wait_queue() //紧接着
    设置任务状态:_set_current_state()
    进行任务切换,也就是让当前进程睡眠:schedule(),紧接着进行唤醒信号判断,(因为进程被唤醒后,会从睡眠点继续往下执行,我们需要判断是不是由信号唤醒的)
    signal_pending(current),是信号唤醒返回真,则退出函数(不去读取值)否则返回假,即中断唤醒,就去读取值。
    主动唤醒: wake_up_interruptible():只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程 wake_up() : 都能唤醒
    等待事件然后自动唤醒:

09a43f45c4a49b3cd6b258c6d27ff60b.png


设置任务状态:_set_current_state()
将进程移除等待队列: remove_wait_queue()

poll机制:
当应用程序调用 select 或 poll 函数来对驱动程序进行非阻塞访问的时候,驱动程序 file_operations 操作集中的 poll 函数就会执行。我们在编写驱动程序的时候需要提供对应的 poll 函 数。
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)

bb06737bb74aabf3e301c7d77985cc9e.png

我们需要在驱动程序的 poll 函数中调用 poll_wait 函数,poll_wait 函数不会引起阻塞,只是 将应用程序添加到 poll_table 中,poll_wait 函数原型如下: void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

异步通知


在设备结构体中添加异步结构体指针: struct fasync_struct *async_queue
实现操作集中的fasync函数: int (*fasync) (int fd, struct file *filp, int on)
xxx_fasync 在fasync函数中只需要调用fasync_helper()来初始化fasync_struct 结构体 指针就行了
当应用程序通过“fcntl(fd, F_SETFL, flags | FASYNC)”改变 fasync 标记的时候,驱动程序 file_operations 操作集中的 fasync 函数就会执行。
关闭驱动文件的时候需要在 file_operations 操作集中的 release 函数中释放 fasync_struct,即调用xxx_fasync(-1, flip, 0)
向应用程序发送信号:kill_fasync()

无设备树platform驱动:
文件:xxxdevice.c

  • 实现 platform_device结构体的成员变量(主要就是.name 和 resource 以及 resource size )
  • 在模块的__init函数里调用platform_device_register()
  • 在模块的__exit函数里调用 platform_device_unregister()

文件:xxxdevice.c

  • 实现file_operations结构体,
  • 实现platform_driver结构体成员变量
.probe中,用platform_ get_resource函数给声明的resource结构赋值,
(再加上无platform时__init的函数内容:  resource内存映射,设备号、cdev、class、device)

.remove函数中,取消映射,注销,释放... (无platform时__exit的函数内容)
  • 在模块的__init函数里调用platform_driver_register()
  • 在模块的__exit函数里调用 platform_driver_unregister()

设备树下的platform驱动:

  • 实现file_operations结构体
  • 实现platform_driver结构体成员变量
.probe中,使用of函数获取节点以及节点的gpio,(再加上无platform时__init的函数内容: 
 resource内存映射,设备号、cdev、class、device) 

.remove函数中,取消映射,注销,释放... (无platform时__exit的函数内容)
  • 在模块的__init函数里调用platform_driver_register()
  • 在模块的__exit函数里调用 platform_driver_unregister()

MISC 驱动:

//MISC 设备驱动的主设备号都为 10,不同的设备使用不同的从设备号。

  • 实现miscdevice结构体(也就是设置里面的.minor 、.name和.fops,ops跟我们以往写的一样,这里赋给这个结构体,用以一步注册)
  • 在probe函数中使用 misc_register(&上一步搞的结构体),一步register、init&add
  • 在remove函数中使用 misc_deregister(&上一步搞的结构体),一步delete, unregister, destroy
  • 使用 ls /sys/class/misc 查看我们注册的设备,ls /dev/xxx -l 可以看到设备的详细信息

input子系统:

input设备的本质是字符设备,,INPUT_MAJOR 定义 在 include/uapi/linux/major.h中,为13

  • 向设备结构体中添加input_dev结构体指针
  • 使用input_allocate_device()来给声明的input_dev结构体赋值
  • 使用set_bit()函数设置input_dev结构体的evbit变量,事件类型 //产生哪类事件?
  • 使用set_bit()函数设置input_dev结构体的keybit变量 ,事件码//产生该类事件的哪一个
  • 在模块的__init函数里调用input_register_device(),步register、init&add,也不需要再实现fops
  • 事件发生以后要使用input_event ( ) 函数来上报事件并使用input_sync()函数来同步事件
  • 在__exit函数里面调用 input_unregister_device() 以及input_free_device() 这两个函数来代替之前的一系列unregister,del,destory 函数
  • 当我们向 Linux 内核成功注册 input_dev 设备以后,会在/dev/input 目录下生成一 个名为“eventX(X=0….n)”的文件,这个/dev/input/eventX 就是对应的 input 设备文件
  • Linux 内核会使用 input_event 结构体来表示输入事件,所以在编写测试程序时,需要创建一个input_event结构体来保存读入的信息,并从该结构体中取出我们需要的成员变量值。
  • 另外也可以直接使用 hexdump 命令来查看 /dev/input/eventX的 文件内容,内容将以 input_event 类型的原始事件数据显示出来。

I2C 设备驱动:
(I2C适配器驱动是SOC厂商的job)

  • 没有设备树的时候,要实现i2c_board_info结构体(也就是实现type--也就是名字,和addr--设备地址)
  • 有设备树时,直接在i2c节点添加子节点type@address,属性reg和compatible也就是设置type和addr
  • 在设备结构体里面,声明void *private_data ,它将指向I2C设备对应的i2c_client。
  • 实现i2c_driver结构体
.probe中,使.private_data = client //client是probe函数传递进来的i2c_client结构体类型的指针。
client结构体里面含有我们的I2C设备地址等信息(在初始化i2c_msg结构体的时候,需要用到client里面保存的
设备地址信息)
(再加上无platform时__init的函数内容:  设备号、cdev、class、device) 
.remove函数中,取消映射,注销,释放... (无platform时__exit的函数内容)
  • 实现file_operations结构体
在读写i2c设备寄存器的函数中,使用i2c_msg 结构体,将其成员变量赋值后,作为参数传递给i2c_transfer函
数
  • 在模块的__init函数里调用i2c_add_driver()
  • 在__exit函数里面调用 i2c_del_driver()

//这里构造了i2c_msg结构体,然后使用i2c_transfer函数来实现传输,我们也可以使用Documentation中更加推荐的SMBUS一系列函数(属于i2c_transfer的子函数),直接使用里面封装好的接口函数,就不用那么麻烦的去自己构建i2c_msg结构体了。

//如果设备树修改正确的话,会在 /sys/bus/i2c/devices 目录下看到设备的子目录

SPI 设备驱动:

(SPI适配器驱动是SOC厂商的job)

  • 没有设备树的时候,要实现spi_board_info结构体(type--也就是名字,addr)
  • 有设备树时,直接在spi节点添加子节点type@address,属性reg和compatible也就是设置type和addr
  • 在设备结构体里面,声明void *private_data ,它将指向spi设备对应的spi_device。
  • 实现spi_driver结构体
.probe中,
    1.无platform时__init的函数内容:  设备号、cdev、class、device) 
    2.使用of函数获取设备树中的片选gpio信息,并设置gpio为高电平,(在读写函数的头部,拉低引脚以选中)
    3.初始化spi参数 (调用spi_setup函数)
    4.使.private_data = spi //( probe 函数会向驱动提供当前 SPI 设备对应的 spi_device,
      因此在 probe 函数中设置 private_data 为 probe 函数传递进来的 spi_device 参数。 
    5.初始化你的spi硬件设备,

.remove函数中,取消映射,注销,释放... (无platform时__exit的函数内容)
  • 实现file_operations结构体
在读写SPI设备寄存器的函数中,
    1.首先将片选引脚拉低,函数结尾要恢复至高电平
    2.使用kzalloc函数申请一个spi_transfer结构体,然后初始化它的tx_buf或者rx_buf和数据长度len
    3.声明一个spi_message结构体,并使用spi_message_init函数进行初始化
    4.使用spi_message_add_tail函数将 spi_transfer 添加到 spi_message 
    5.使用spi_sync函数同步传输/spi_async异步传输
  • 在模块的__init函数里调用 spi_register_driver
  • 在__exit函数里面调用 spi_unregister_driver

多点触摸屏驱动(Type B):

以I2C设备驱动为框架,在其probe函数中申请并设置input子系统:

    1. 哪类事件?EV_ABS(必须有),BTN_TOUCH(必须有)
    2. 哪些值? ABS_MT_POSITION_X/Y
    3. 初始化slots:  input_mt_init_slots()
    4. 注册申请的input_dev, input_register_device()

在中断服务函数中上报坐标信息:

    1. input_mt_slot() //Type B 函数,用于产生 ABS_MT_SLOT 事件
    2. input_mt_report_slot_state() //Type B 函数,用于产生 ABS_MT_TRACKING_ID 和 
       ABS_MT_TOOL_TYPE 事件,ABS_MT_TRACKING_ID 事 件 给 slot 关联一个 ABS_MT_TRACKING_ID 
       ABS_MT_TOOL_TYPE 事件指定触摸类型(是笔还是手指等)
    3. input_report_abs()  //A,B 通用,此函数上报触摸点坐标信息
    4. input_mt_report_pointer_emulation() //获取当前触摸点数,然后与我们设置的最大触摸点数
       量比较
    4. 前面三步是根据Type A时序来循环上报所有坐标,最后使用input_sync()来发送SYN_REPORT事件

在remove函数中上释放资源,

当卸载驱动的时候 xxx_remove 函数就会执行,
我们在此函数释放一些资源并调用 input_unregister_device 来释放掉前面添加到内核中的 input_dev。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值