Skip to content
forked from makelinux/ldd3

Linux Device Drivers 3 source code with added examples

License

Notifications You must be signed in to change notification settings

fuyajun1983cn/ldd3

 
 

Repository files navigation

源码说明

Tasks [5/5]

  • [X] Porting to linux kernel 3.13
  • [X] Porting to linux kernel 3.14
  • [X] Portng to linux kernel 3.18
  • [X] Porting to linux kernel 4.7
  • [X] Porting to linux kernel 4.8

Source Code说明

驱动说明

scull0 to scull3

每个包含一段内存区域,这段内存是全局的,且是可持久的。

scullpipe0 to scullpipe3

FIFO(First In First Out)设备,像管道一样工作。

scullsingle, scullpriv, sculluid, scullwuid

  • scullsingle 每次只允许一个进程访问该设备
  • scullpriv 对每个虚拟终端,都是私有的。
  • sculluid, scullwuid 可被打开多次, 但一次只能由一个用户打开

scull device的数据结构示意图

./images/layout_of_a_scull_device.png

数据结构定义

/*
 * Representation of scull quantum sets.
 */
struct scull_qset {
        void **data;
        struct scull_qset *next;
};

struct scull_dev {
        struct scull_qset *data;  /* Pointer to first quantum set */
        int quantum;              /* the current quantum size */
        int qset;                 /* the current array size */
        unsigned long size;       /* amount of data stored here */
        unsigned int access_key;  /* used by sculluid and scullpriv */
        struct semaphore sem;     /* mutual exclusion semaphore     */
        struct cdev cdev;         /* Char device structure              */
};

学习笔记

内核模块

调试技术

内核常见的调试项:

  1. CONFIG_DEBUG_KERNEL 打开此项后,就可以使其他的一些调试选项可见。
  2. CONFIG_DEBUG_SLAB 检测内存越界或是未初始化等错误。
  3. CONFIG_DEBUG_PAGEALLOC 当释放时,完整的页会从内核地址空间删除。
  4. CONFIG_DEBUG_SPINLOCK 检测对一个未初始化的spinlock的操作。
  5. CONFIG_DEBUG_SPINLOCK_SLEEP 检测当拥有一个spinlock时,还试图进入sleep的代码。
  6. CONFIG_INIT_DEBUG 检测试图访问__init(或__initdata)标记的代码。
  7. CONFIG_DEBUG_INFO 将完整的调试信息统进内核。特别是想使用gdb调试内核时,非常有用。
  8. CONFIG_MAGIC_SYSRQ 启用”magic SysRq”键。
  9. CONFIG_DEBUG_STACKOVERFLOW
  10. CONFIG_DEBUG_STACK_USAGE 有助于跟踪内核栈溢出的问题。
  11. CONFIG_KALLSYMS 将内核符号信息编译进内核。发生oops时,打印调用栈时可以看到函 数符号信息,而不是一些十六进制的数字。
  12. CONFIG_IKCONFIG
  13. CONFIG_IKCONFIG_PROC 打开后,会将完整的内核配置状态编进内核,这样可以通过/proc/目 录下的一些文件查看。
  14. CONFIG_ACPI_DEBUG ACPI调试信息。
  15. CONFIG_DEBUG_DRIVER 打开驱动核心代码的日志输出。
  16. CONFIG_SCSI_CONSTANTS SCSI驱动调试时使用。
  17. CONFIG_INPUT_EVBUG 打开输入事件的一些输出。调试输入设备驱动时使用
  18. CONFIG_PROFILING 性能调优或跟踪内核Hang住等相关问题。

prink

printk 函数将消息发送到一个环形Buffer:__LOG_BUF_LEN字节长,大 小从4 KB到1 MB大小。可以通过syslog或读取/proc/kmsg来获取该buffer 中的信息。其中,读取/proc/kmsg的方式会消耗掉buffer中的数据。

rate limit

有时,同一个Log信息打印太多,会影响Log分析,可以通过 printk_ratelimit() 来控制一条Log的打印次数。 使用方法一般如下所示:

if (printk_ratelimit( ))
  printk(KERN_NOTICE "The printer is still on fire\n");

可以通过如下两种方法修改 printk_ratelimit() 的行为:

  1. 修改”/proc/sys/kernel/printk_ratelimit” 重新enable消息打印前, 等待的时间。
  2. “/proc/sys/kernel/printk_ratelimit_burst”: 在启用rate_limit前, 允许同一份Log打印的次数。

打印设备号

int print_dev_t(char *buffer, dev_t dev);
char *format_dev_t(char *buffer, dev_t dev);

字符设备

同步与竞态

休眠与唤醒

  • 基本调用步骤:
    1. 初始化一个等待队列头:

      init_waitqueue_head(&ret->wait_queue);

      注: 判断队列是否为空: waitqueue_active(...) , 返回false即表示队列为空.

    2. 等待某个条件发生:

      wait_event(...)wait_event_timeout(...)

    3. 唤醒队列

      wake_up_all(...)

  • 手动睡眠
    1. 初始化一个等待队列项
      DEFINE_WAIT(my_wait);
              

      或者

      wait_queue_t my_wait;
      init_wait(&my_wait);          
              
    2. 将等待队列项加入到队列中
      void prepare_to_wait(wait_queue_head_t *queue,
                           wait_queue_t *wait,
                           int state);
              
    3. 调用 prepare_to_wait 后,可以调用 schedule()
    4. 当schedule返回,执行清理工作。
      void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);
              

计时器与延时

  • 内核变量——Jiffies jiffies与struct timeval, struct timespec之间的转换:
    #include <linux/time.h>
    unsigned long timespec_to_jiffies(struct timespec *value);
    void jiffies_to_timespec(unsigned long jiffies, struct timespec *value);
    unsigned long timeval_to_jiffies(struct timeval *value);
    void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);
        

    读取64-bit的jiffies值:

    #include <linux/jiffies.h>
    u64 get_jiffies_64(void);
        

    turn a wall-clock time into a jiffies value:

    #include <linux/time.h>
    unsigned long mktime (unsigned int year, unsigned int mon,
                          unsigned int day, unsigned int hour,
                          unsigned int min, unsigned int sec);
        

    获取绝对时间:

    #include <linux/time.h>
    void do_gettimeofday(struct timeval *tv);
    
    //获取当前时间
    #include <linux/time.h>
    struct timespec current_kernel_time(void);
        
  • 延迟运行
    1. Busy Waiting

      The HZ symbol specifies the number of clock ticks generated per second.

      while (time_before(jiffies, j1))
        cpu_relax( );
              
    2. schedule
      while (time_before(jiffies, j1)) {
        schedule( );
      }
              
    3. Timeouts
      #include <linux/wait.h>
      long wait_event_timeout(wait_queue_head_t q, condition, long timeout);
      long wait_event_interruptible_timeout(wait_queue_head_t q,
                                            condition, long timeout);
      
      #include <linux/sched.h>
      signed long schedule_timeout(signed long timeout);
      set_current_state(TASK_INTERRUPTIBLE);
      schedule_timeout (delay);
              
    4. 短延时
      //busy waiting
      #include <linux/delay.h>
      void ndelay(unsigned long nsecs);
      void udelay(unsigned long usecs);
      void mdelay(unsigned long msecs);
      
      //no busy waiting
      void msleep(unsigned int millisecs);
      unsigned long msleep_interruptible(unsigned int millisecs);
      void ssleep(unsigned int seconds)
              
    5. 内核定时器与延时
      #include <linux/timer.h>
      struct timer_list {
      /* ... */
      unsigned long expires;
      void (*function)(unsigned long);
        unsigned long data;
      };
      void init_timer(struct timer_list *timer);
      struct timer_list TIMER_INITIALIZER(_function, _expires, _data);
      void add_timer(struct timer_list * timer);
      int del_timer(struct timer_list * timer);
      int mod_timer(struct timer_list *timer, unsigned long expires);//update timer
      //Works like del_timer, but also guarantees that when it returns, the timer
      //function is not running on any CPU.
      int del_timer_sync(struct timer_list *timer);
      /*
        Returns true or false to indicate whether the timer is currently scheduled to run
      by reading one of the opaque fields of the structure.
      */
      int timer_pending(const struct timer_list * timer);
              
    6. 下半部机制之微线程

      数据结构:

      #include <linux/interrupt.h>
      struct tasklet_struct {
        /* ... */
        void (*func)(unsigned long);
        unsigned long data;
      };
      void tasklet_init(struct tasklet_struct *t,
      void (*func)(unsigned long), unsigned long data);
      DECLARE_TASKLET(name, func, data);
      DECLARE_TASKLET_DISABLED(name, func, data);
              
    7. 下半部机制之工作队列
      struct workqueue_struct *create_workqueue(const char *name);//为每个CPU创建一个内核线程
      struct workqueue_struct *create_singlethread_workqueue(const char *name);//只创建一个内核线程
      
      // work_struct
      DECLARE_WORK(name, void (*function)(void *), void *data);
      INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
      PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data); //修改work_struct
      
      //submit work
      int queue_work(struct workqueue_struct *queue, struct work_struct *work);
      int queue_delayed_work(struct workqueue_struct *queue,
                             struct work_struct *work, unsigned long delay);
      //cancel pending workqueue entry
      int cancel_delayed_work(struct work_struct *work);
      
      //make sure the work function is not running
      //anywhere in the system after cancel_delayed_work returns 0
      void flush_workqueue(struct workqueue_struct *queue);
      
      //get rid of a workqueue
      void destroy_workqueue(struct workqueue_struct *queue);
              
    8. 共享工作队列

      大部分情况下,我们不需要创建自己的工作队列,可以将工作项提交 到默认的工作队列中。

      int schedule_work(struct work_struct *work);
      
      //submit work
      int schedule_delayed_work(struct work_struct *work, unsigned long delay);
      
      //cancel delayed work
      int cancel_delayed_work(struct work_struct *work);
      
      //flush the shared workqueue
      void flush_scheduled_work(void);
              

内存分配

与硬件通信

内核数据结构

网络驱动

snull驱动设计说明

  • it simulates conversations with real remote hosts in order to better demonstrate the task of writing a network driver.
  • it supports only IP traffic.
  • The snull module creates two interfaces.
  • How a host sees its interfaces

    ./images/snull.png

  • possible configuration

    /etc/networks

    networkip
    snullnet0192.168.0.0
    snullnet1192.168.1.0

    /etc/hosts

    iphost
    192.168.0.1local0
    192.168.0.2remote0
    192.168.1.2local1
    192.168.1.1remote1

网络驱动基本知识

  • struct net_device 描述一个网络接口。头文件: <linux/netdevice.h>
    struct net_device *alloc_netdev(int sizeof_priv,
                                    const char *name,
                                    void (*setup)(struct net_device *));
        
  • 注册网络设备
    for (i = 0; i < 2; i++)
      if ((result = register_netdev(snull_devs[i])))
        printk("snull: error %i registering device \"%s\"\n",
               result, snull_devs[i]->name);
        
  • netif_start_queue/netif_stop_queue 标记Driver是否可以传输数据包。
    /*
      If you must disable packet transmission from anywhere other than your hard_start_xmit
      function (in response to a reconfiguration request, perhaps), the function you want to
      use is:
    */
    void netif_tx_disable(struct net_device *dev);
    
    /*
      This function is just like netif_start_queue, except that it also pokes the networking
    system to make it start transmitting packets again.
    */
    void netif_wake_queue(struct net_device *dev);
        
  • 数据传输与接收 当可以传输数据时,内核会调用 ndo_start_transmit 将数据放到队 列中。
    netif_rx(skb);//hand off the socket buffer to the upper layers.
    netif_receive_skb(skb); //feed packets to the kernel, used in poll mode
        
  • chagnes in link state
    void netif_carrier_off(struct net_device *dev);
    void netif_carrier_on(struct net_device *dev);
    int netif_carrier_ok(struct net_device *dev);
        
  • ioctl

    Any ioctl command that is not recognized by the protocol layer is passed to the device layer. These device-related ioctl commands accept a third argument from user space, a struct ifreq * .

NAPI

API
  1. netif_napi_add() 把网络设备与napi虚拟设备绑定
  2. 使能和禁用
    • netif_enalbe()
    • netif_disable()
  3. 调度NAPI netif_schedule()
使用说明
  1. 不同的网卡驱动都会为网络设备定义自己的结构体。有的是自己的结 构体中即包含 net_device 和 napi,有的是把自己的结构体放到 net_device 的priv部分。
  2. 实现NAPI的轮询函数
    rx_pool(struct napi_struct *napi, int weigh)
        
    /*
      @param wight: 一次允许处理的报文最大数量
    **/
    int rx_pool(struct napi_struct *napi, int weigh)
    {
      int rx_cnt = 0;
      struct sk_buff *skb;
    
      while (rx_cnt < weigh) {
        skb = netdev_alloc_skb();
        copy_skb_from_hw(skb);
        rx_cnt++;
        netif_receive_skb(skb); /*送内核协议栈处理*/
      }
    
      if (rx_cnt < weigh) {
        /* 处理完了所有包,使能中断 */
        napi_complete(napi);
        enable_rx_irq();
      }
      return rx_cnt;
    }
        
  3. 把网络设备与NAPI相关联
    netif_napi_add(netdev, napi, rx_pool, weigh);
        
  4. 注册网卡的收包中断
    request_irq(irq, &rx_irq_handle, 0, netdev->name, netdev);
        

    在处理函数中,

    static irqreturn_t rx_irq_handle(int irq, void *data)
    {
      struct net_device *netdev = data;
      struct napi_struct *napi = netdev_priv(netdev)->napi;
      disable_rx_irq();
      napi_schedule(napi);
    }
        

PCI驱动

how a PCI driver can find its hardware and gain access to it.

PCI寻址

Each PCI peripheral is identified by a bus number, a device number and a function number. In Linux, it support domain. domain(16bits) + bus(8bits) + device(5bits) + function(3bits)

每当一个外围设备都会响应三种地址的查询:

  1. Memory Locations
  2. I/O ports
  3. Configuration Registers

其中对于处于同一个PCI总线上的设备,前两个地址是一样的.

三个或五个PCI寄存器标识一个设备: vendorID, deviceID,class. additional registers: subsystem vendorID, subsystem deviceID.

struct pci_device_id {
  __u32 vendor;
  __u32 device;
  //Vendor ID and device ID, can be PCI_ANY_ID if a driver can handle any vendor or device Id.

  __u32 subvendor;
  __u32 subdevice;
  //Subsystem vendor and subsystem device id

  __u32 class;
  __u32 class_mask;
  //device class such as "parallel", "ehternet", can be PCI_ANY_ID if a driver can handle any type of subsystem ID.

  kernel_ulong_t driver_data;
  //hold information to differentiate between different devices if it wants to .
};

MODULE_DEVICE_TABLE

this macro is used to export pci_device_id structure to user space to allow the hotplug and module loading.

注策PCI设备

static struct pci_driver pci_driver = {
  .name = "pci_skel",
  .id_table = ids,
  .probe = probe,
  .remove = remove,
};

static __init pci_skel_init(void)
{
  return pci_register_driver(&pci_driver);
}

Enabling the PCI device

before a driver can access any device resource(I/O region or interrupt), the driver must call the pci_enable_device .

Accessing the configuration space

int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
int pci_read_config_word(struct pci_dev *dev, int where, u16*val);
int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);

int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);
int pci_write_config_word(struct pci_dev *dev, int where, u16 val);
int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);


static unsigned char skel_get_revision(struct pci_dev *dev)
{
  u8 revision;

  pci_read_config_byte(dev, PCI_REVISION_ID, &revision);
  return revision;
}

Accessing IO/Memory Resource

unsigned long pci_resource_start(struct pci_dev *dev, int bar);
unsigned long pci_resource_end(struct pci_dev *dev, int bar);
unsigned long pci_resource_flags(struct pci_dev *dev, int bar);

Resource flags are: IORESOURCE_IO , IORESOURCE_MEM : If the associated I/O region exists, one and only one of these flags is set.

IORESOURCE_PREFETCH, IORESOURCE_READONLY : those flags tell whether a Memory region is prefetchable and/or write protected. the latter flag is never set for PCI resources.

Interrupt

result = pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &myirq);
if (result) {
  /* deal with error */
 }

☛ TODO USB驱动

☛ TODO SDIO驱动

引用

README

About

Linux Device Drivers 3 source code with added examples

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • C 88.7%
  • C++ 3.7%
  • Makefile 3.5%
  • Shell 2.4%
  • Objective-C 1.7%