Grapheme device drivers¶

Laboratory objectives¶

  • sympathize the concepts behind graphic symbol device driver
  • understand the various operations that can be performed on grapheme devices
  • working with waiting queues

Overview¶

In UNIX, hardware devices are accessed past the user through special device files. These files are grouped into the /dev directory, and system calls open , read , write , close , lseek , mmap etc. are redirected by the operating organisation to the device driver associated with the concrete device. The device driver is a kernel component (unremarkably a module) that interacts with a hardware device.

In the UNIX world there are ii categories of device files and thus device drivers: character and cake. This division is done by the speed, volume and way of organizing the data to be transferred from the device to the arrangement and vice versa. In the starting time category, in that location are slow devices, which manage a small amount of data, and access to data does not crave frequent seek queries. Examples are devices such as keyboard, mouse, serial ports, sound carte, joystick. In general, operations with these devices (read, write) are performed sequentially byte by byte. The 2d category includes devices where data volume is large, data is organized on blocks, and search is mutual. Examples of devices that fall into this category are hard drives, cdroms, ram disks, magnetic record drives. For these devices, reading and writing is done at the data block level.

For the two types of device drivers, the Linux kernel offers different APIs. If for graphic symbol devices system calls go directly to device drivers, in instance of cake devices, the drivers practise not piece of work directly with arrangement calls. In the example of cake devices, communication between the user-space and the block device driver is mediated by the file management subsystem and the cake device subsystem. The role of these subsystems is to ready the device commuter's necessary resources (buffers), to go on the recently read data in the cache buffer, and to gild the read and write operations for performance reasons.

Majors and minors¶

In UNIX, the devices traditionally had a unique, fixed identifier associated with them. This tradition is preserved in Linux, although identifiers can exist dynamically allocated (for compatibility reasons, most drivers still apply static identifiers). The identifier consists of two parts: major and modest. The start role identifies the device type (IDE deejay, SCSI disk, serial port, etc.) and the 2d one identifies the device (first disk, second serial port, etc.). Most times, the major identifies the driver, while the pocket-sized identifies each concrete device served by the commuter. In general, a driver will have a major associate and will be responsible for all minors associated with that major.

                  $ ls -la /dev/hda? /dev/ttyS? brw-rw----                  i                  root disk                  iii,                  1                  2004-09-18                  xiv:51 /dev/hda1 brw-rw----                  i                  root disk                  3,                  2                  2004-09-eighteen                  xiv:51 /dev/hda2 crw-rw----                  1                  root dialout                  4,                  64                  2004-09-18                  14:52 /dev/ttyS0 crw-rw----                  1                  root dialout                  4,                  65                  2004-09-xviii                  14:52 /dev/ttyS1                

Every bit tin be seen from the example above, device-type information tin exist found using the ls command. The special graphic symbol files are identified past the c character in the first cavalcade of the control output, and the block type by the character b . In columns 5 and 6 of the effect you can meet the major, respectively the pocket-sized for each device.

Certain major identifiers are statically assigned to devices (in the Documentation/admin-guide/devices.txt file from the kernel sources). When choosing the identifier for a new device, y'all can employ two methods: static (choose a number that does not seem to be used already) or dynamically. In /proc/devices are the loaded devices, along with the major identifier.

To create a device type file, use the mknod command; the command receives the blazon ( block or character ), major and minor of the device ( mknod proper noun type major minor ). Thus, if y'all desire to create a character device named mycdev with the major 42 and pocket-size 0 , apply the control:

                                    # mknod /dev/mycdev c 42 0                

To create the block device with the proper name mybdev with the major 240 and small-scale 0 the control will be:

                                    # mknod /dev/mybdev b 240 0                

Next, nosotros'll refer to character devices every bit drivers.

Data structures for a character device¶

In the kernel, a character-type device is represented past struct cdev , a structure used to annals information technology in the arrangement. About commuter operations use three of import structures: struct file_operations , struct file and struct inode .

struct file_operations

As mentioned above, the character device drivers receive unaltered system calls made by users over device-type files. Consequently, implementation of a character device driver means implementing the arrangement calls specific to files: open up , close , read , write , lseek , mmap , etc. These operations are described in the fields of the struct file_operations structure:

                                        #include                    <linux/fs.h>                                        struct                    file_operations                    {                    struct                    module                    *                    owner                    ;                    loff_t                    (                    *                    llseek                    )                    (                    struct                    file                    *                    ,                    loff_t                    ,                    int                    );                    ssize_t                    (                    *                    read                    )                    (                    struct                    file                    *                    ,                    char                    __user                    *                    ,                    size_t                    ,                    loff_t                    *                    );                    ssize_t                    (                    *                    write                    )                    (                    struct                    file                    *                    ,                    const                    char                    __user                    *                    ,                    size_t                    ,                    loff_t                    *                    );                    [...]                    long                    (                    *                    unlocked_ioctl                    )                    (                    struct                    file                    *                    ,                    unsigned                    int                    ,                    unsigned                    long                    );                    [...]                    int                    (                    *                    open                    )                    (                    struct                    inode                    *                    ,                    struct                    file                    *                    );                    int                    (                    *                    flush                    )                    (                    struct                    file                    *                    ,                    fl_owner_t                    id                    );                    int                    (                    *                    release                    )                    (                    struct                    inode                    *                    ,                    struct                    file                    *                    );                    [...]                  

It tin can be noticed that the signature of the office differs from the system call that the user uses. The operating system sits between the user and the device commuter to simplify implementation in the device commuter.

open does not receive the parameter path or the various parameters that control the file opening mode. Similarly, read , write , release , ioctl , lseek practice not receive as a parameter a file descriptor. Instead, these routines receive as parameters two structures: file and inode . Both structures represent a file, but from dissimilar perspectives.

Most parameters for the presented operations have a directly pregnant:
  • file and inode identifies the device type file;
  • size is the number of bytes to exist read or written;
  • offset is the displacement to be read or written (to be updated accordingly);
  • user_buffer user buffer from which it reads / writes;
  • whence is the fashion to seek (the position where the search functioning starts);
  • cmd and arg are the parameters sent by the users to the ioctl call (IO command).

inode and file structures¶

An inode represents a file from the signal of view of the file organization. Attributes of an inode are the size, rights, times associated with the file. An inode uniquely identifies a file in a file system.

The file construction is still a file, only closer to the user's point of view. From the attributes of the file structure nosotros list: the inode, the file name, the file opening attributes, the file position. All open files at a given time take associated a file structure.

To understand the differences between inode and file, we will apply an analogy from object-oriented programming: if we consider a course inode, then the files are objects, that is, instances of the inode class. Inode represents the static epitome of the file (the inode has no state), while the file represents the dynamic image of the file (the file has country).

Returning to device drivers, the two entities have almost always standard ways of using: the inode is used to determine the major and minor of the device on which the operation is performed, and the file is used to make up one's mind the flags with which the file was opened, just also to salvage and access (later) private information.

The file construction contains, among many fields:

  • f_mode , which specifies read ( FMODE_READ ) or write ( FMODE_WRITE );
  • f_flags , which specifies the file opening flags ( O_RDONLY , O_NONBLOCK , O_SYNC , O_APPEND , O_TRUNC , etc.);
  • f_op , which specifies the operations associated with the file (pointer to the file_operations construction );
  • private_data , a pointer that can be used by the developer to store device-specific data; The pointer volition exist initialized to a retentiveness location assigned past the programmer.
  • f_pos , the offset within the file

The inode structure contains, amidst many data, an i_cdev field, which is a arrow to the structure that defines the grapheme device (when the inode corresponds to a character device).

Implementation of operations¶

To implement a device driver, it is recommended that you create a structure that contains information most the device, information used in the module. In the case of a driver for a graphic symbol device, the construction will contain a cdev structure field to refer to the device. The following instance uses the struct my_device_data:

                                    #include                  <linux/fs.h>                                    #include                  <linux/cdev.h>                                    struct                  my_device_data                  {                  struct                  cdev                  cdev                  ;                  /* my data starts here */                  //...                  };                  static                  int                  my_open                  (                  struct                  inode                  *                  inode                  ,                  struct                  file                  *                  file                  )                  {                  struct                  my_device_data                  *                  my_data                  ;                  my_data                  =                  container_of                  (                  inode                  ->                  i_cdev                  ,                  struct                  my_device_data                  ,                  cdev                  );                  file                  ->                  private_data                  =                  my_data                  ;                  //...                  }                  static                  int                  my_read                  (                  struct                  file                  *                  file                  ,                  char                  __user                  *                  user_buffer                  ,                  size_t                  size                  ,                  loff_t                  *                  showtime                  )                  {                  struct                  my_device_data                  *                  my_data                  ;                  my_data                  =                  (                  struct                  my_device_data                  *                  )                  file                  ->                  private_data                  ;                  //...                  }                

A structure like my_device_data will contain the data associated with a device. The cdev field ( cdev blazon) is a grapheme-type device and is used to record it in the arrangement and identify the device. The pointer to the cdev member can be found using the i_cdev field of the inode construction (using the container_of macro). In the private_data field of the file structure, information can be stored at open which is so bachelor in the read , write , release , etc. routines.

Registration and unregistration of character devices¶

The registration/unregistration of a device is made by specifying the major and small-scale. The dev_t type is used to continue the identifiers of a device (both major and minor) and can be obtained using the MKDEV macro.

For the static assignment and unallocation of device identifiers, the register_chrdev_region and unregister_chrdev_region functions are used:

                                    #include                  <linux/fs.h>                                    int                  register_chrdev_region                  (                  dev_t                  first                  ,                  unsigned                  int                  count                  ,                  char                  *                  proper name                  );                  void                  unregister_chrdev_region                  (                  dev_t                  offset                  ,                  unsigned                  int                  count                  );                

Information technology is recommended that device identifiers be dynamically assigned to the alloc_chrdev_region office.

Below sequence reserves my_minor_count devices, starting with my_major major and my_first_minor minor (if the max value for minor is exceeded, move to the adjacent major):

                                    #include                  <linux/fs.h>                                    ...                  err                  =                  register_chrdev_region                  (                  MKDEV                  (                  my_major                  ,                  my_first_minor                  ),                  my_minor_count                  ,                  "my_device_driver"                  );                  if                  (                  err                  !=                  0                  )                  {                  /* study error */                  return                  err                  ;                  }                  ...                

After assigning the identifiers, the character device will have to be initialized ( cdev_init ) and the kernel will take to be notified( cdev_add ). The cdev_add function must exist called only later the device is ready to receive calls. Removing a device is done using the cdev_del function.

                                    #include                  <linux/cdev.h>                                    void                  cdev_init                  (                  struct                  cdev                  *                  cdev                  ,                  struct                  file_operations                  *                  fops                  );                  int                  cdev_add                  (                  struct                  cdev                  *                  dev                  ,                  dev_t                  num                  ,                  unsigned                  int                  count                  );                  void                  cdev_del                  (                  struct                  cdev                  *                  dev                  );                

The post-obit sequence registers and initializes MY_MAX_MINORS devices:

                                    #include                  <linux/fs.h>                                    #include                  <linux/cdev.h>                                    #define MY_MAJOR       42                  #ascertain MY_MAX_MINORS  v                  struct                  my_device_data                  {                  struct                  cdev                  cdev                  ;                  /* my data starts here */                  //...                  };                  struct                  my_device_data                  devs                  [                  MY_MAX_MINORS                  ];                  const                  struct                  file_operations                  my_fops                  =                  {                  .                  owner                  =                  THIS_MODULE                  ,                  .                  open                  =                  my_open                  ,                  .                  read                  =                  my_read                  ,                  .                  write                  =                  my_write                  ,                  .                  release                  =                  my_release                  ,                  .                  unlocked_ioctl                  =                  my_ioctl                  };                  int                  init_module                  (                  void                  )                  {                  int                  i                  ,                  err                  ;                  err                  =                  register_chrdev_region                  (                  MKDEV                  (                  MY_MAJOR                  ,                  0                  ),                  MY_MAX_MINORS                  ,                  "my_device_driver"                  );                  if                  (                  err                  !=                  0                  )                  {                  /* report error */                  return                  err                  ;                  }                  for                  (                  i                  =                  0                  ;                  i                  <                  MY_MAX_MINORS                  ;                  i                  ++                  )                  {                  /* initialize devs[i] fields */                  cdev_init                  (                  &                  devs                  [                  i                  ].                  cdev                  ,                  &                  my_fops                  );                  cdev_add                  (                  &                  devs                  [                  i                  ].                  cdev                  ,                  MKDEV                  (                  MY_MAJOR                  ,                  i                  ),                  1                  );                  }                  return                  0                  ;                  }                

While the following sequence deletes and unregisters them:

                                    void                  cleanup_module                  (                  void                  )                  {                  int                  i                  ;                  for                  (                  i                  =                  0                  ;                  i                  <                  MY_MAX_MINORS                  ;                  i                  ++                  )                  {                  /* release devs[i] fields */                  cdev_del                  (                  &                  devs                  [                  i                  ].                  cdev                  );                  }                  unregister_chrdev_region                  (                  MKDEV                  (                  MY_MAJOR                  ,                  0                  ),                  MY_MAX_MINORS                  );                  }                

Note

initialization of the struct my_fops used the initialization of members by proper name, defined in C99 standard (see designated initializers and the file_operations structure). Structure members who do not explicitly appear in this initialization will be fix to the default value for their type. For example, afterward the initialization above, my_fops.mmap will exist NULL.

Admission to the address space of the process¶

A driver for a device is the interface between an application and hardware. As a result, nosotros often accept to access user-infinite information. Accessing it can non be done directly (by de-referencing a user-space pointer). Direct admission of a user-space pointer can lead to incorrect behavior (depending on architecture, a user-infinite pointer may not be valid or mapped to kernel-space), a kernel oops (the user-mode arrow tin refer to a non-resident retentiveness area) or security issues. Proper access to user-space data is done by calling the macros / functions below:

                                    #include                  <asm/uaccess.h>                                    put_user                  (                  type                  val                  ,                  blazon                  *                  accost                  );                  get_user                  (                  type                  val                  ,                  blazon                  *                  address                  );                  unsigned                  long                  copy_to_user                  (                  void                  __user                  *                  to                  ,                  const                  void                  *                  from                  ,                  unsigned                  long                  n                  );                  unsigned                  long                  copy_from_user                  (                  void                  *                  to                  ,                  const                  void                  __user                  *                  from                  ,                  unsigned                  long                  n                  );                

All macros / functions return 0 in case of success and another value in case of fault and have the following roles:

  • put_user shop the value val to user-space address address ; Type can be one on eight, 16, 32, 64 bit (the maximum supported type depends on the hardware platform);
  • get_user counterpart to the previous function, only that val will be set to a value identical to the value at the user-space address given by address;
  • copy_to_user copies n bytes from the kernel-space, from the accost referenced by from in user-space to the address referenced by to ;
  • copy_from_user copies north bytes from user-space from the accost referenced by from in kernel-space to the address referenced by to .

A common department of code that works with these functions is:

                                    #include                  <asm/uaccess.h>                                    /*                                      * Copy at well-nigh size bytes to user infinite.                                      * Return ''0'' on success and some other value on error.                                      */                  if                  (                  copy_to_user                  (                  user_buffer                  ,                  kernel_buffer                  ,                  size                  ))                  render                  -                  EFAULT                  ;                  else                  render                  0                  ;                

Open and release¶

The open part performs the initialization of a device. In about cases, these operations refer to initializing the device and filling in specific information (if it is the first open up call). The release function is almost releasing device-specific resource: unlocking specific data and closing the device if the final call is close.

In most cases, the open function will have the following structure:

                                    static                  int                  my_open                  (                  struct                  inode                  *                  inode                  ,                  struct                  file                  *                  file                  )                  {                  struct                  my_device_data                  *                  my_data                  =                  container_of                  (                  inode                  ->                  i_cdev                  ,                  struct                  my_device_data                  ,                  cdev                  );                  /* validate access to device */                  file                  ->                  private_data                  =                  my_data                  ;                  /* initialize device */                  ...                  return                  0                  ;                  }                

A problem that occurs when implementing the open function is access command. Sometimes a device needs to be opened one time at a time; More specifically, practice not allow the second open before the release. To implement this brake, you cull a way to handle an open telephone call for an already open device: it can return an error ( -EBUSY ), cake open calls until a release functioning, or close downwardly the device before do the open.

At the user-space call of the open and close functions on the device, telephone call my_open and my_release in the driver. An instance of a user-space telephone call:

                                    int                  fd                  =                  open                  (                  "/dev/my_device"                  ,                  O_RDONLY                  );                  if                  (                  fd                  <                  0                  )                  {                  /* handle error */                  }                  /* exercise work */                  //..                  shut                  (                  fd                  );                

Read and write¶

The read and write operations are reaching the device driver as a upshot of a userspace program calling the read or write organization calls:

                                    if                  (                  read                  (                  fd                  ,                  buffer                  ,                  size                  )                  <                  0                  )                  {                  /* handle error */                  }                  if                  (                  write                  (                  fd                  ,                  buffer                  ,                  size                  )                  <                  0                  )                  {                  /* handle mistake */                  }                

The read and write functions transfer data between the device and the user-space: the read part reads the data from the device and transfers it to the user-infinite, while writing reads the user-space data and writes information technology to the device. The buffer received every bit a parameter is a user-space pointer, which is why it is necessary to use the copy_to_user or copy_from_user functions.

The value returned past read or write can be:

  • the number of bytes transferred; if the returned value is less than the size parameter (the number of bytes requested), and then it means that a fractional transfer was made. Well-nigh of the time, the user-space app calls the organisation call (read or write) office until the required data number is transferred.
  • 0 to marker the stop of the file in the example of read ; if write returns the value 0 then it means that no byte has been written and that no mistake has occurred; In this example, the user-space application retries the write telephone call.
  • a negative value indicating an error code.

To perform a data transfer consisting of several partial transfers, the following operations should exist performed:

  • transfer the maximum number of possible bytes between the buffer received every bit a parameter and the device (writing to the device/reading from the device will be done from the kickoff received as a parameter);
  • update the offset received equally a parameter to the position from which the next read / write data will begin;
  • return the number of bytes transferred.

The sequence below shows an instance for the read role that takes into account the internal buffer size, user buffer size and the offset:

                                    static                  int                  my_read                  (                  struct                  file                  *                  file                  ,                  char                  __user                  *                  user_buffer                  ,                  size_t                  size                  ,                  loff_t                  *                  offset                  )                  {                  struct                  my_device_data                  *                  my_data                  =                  (                  struct                  my_device_data                  *                  )                  file                  ->                  private_data                  ;                  ssize_t                  len                  =                  min                  (                  my_data                  ->                  size                  -                  *                  offset                  ,                  size                  );                  if                  (                  len                  <=                  0                  )                  render                  0                  ;                  /* read data from my_data->buffer to user buffer */                  if                  (                  copy_to_user                  (                  user_buffer                  ,                  my_data                  ->                  buffer                  +                  *                  offset                  ,                  len                  ))                  return                  -                  EFAULT                  ;                  *                  offset                  +=                  len                  ;                  return                  len                  ;                  }                

The images below illustrate the read operation and how data is transferred betwixt the userspace and the commuter:

  1. when the driver has enough data bachelor (starting with the Showtime position) to accurately transfer the required size (SIZE) to the user.
  2. when a smaller corporeality is transferred than required.

../_images/read.png ../_images/read2.png

Nosotros can look at the read operation implemented by the driver equally a response to a userpace read request. In this instance, the commuter is responsible for advancing the outset according to how much it reads and returning the read size (which may be less than what is required).

The construction of the write function is similar:

                                    static                  int                  my_write                  (                  struct                  file                  *                  file                  ,                  const                  char                  __user                  *                  user_buffer                  ,                  size_t                  size                  ,                  loff_t                  *                  offset                  )                  {                  struct                  my_device_data                  *                  my_data                  =                  (                  struct                  my_device_data                  *                  )                  file                  ->                  private_data                  ;                  ssize_t                  len                  =                  min                  (                  my_data                  ->                  size                  -                  *                  offset                  ,                  size                  );                  if                  (                  len                  <=                  0                  )                  render                  0                  ;                  /* read information from user buffer to my_data->buffer */                  if                  (                  copy_from_user                  (                  my_data                  ->                  buffer                  +                  *                  offset                  ,                  user_buffer                  ,                  len                  ))                  return                  -                  EFAULT                  ;                  *                  start                  +=                  len                  ;                  return                  len                  ;                  }                

The write performance will answer to a write asking from userspace. In this case, depending on the maximum driver capacity (MAXSIZ), information technology can write more or less than the required size.

../_images/write.png ../_images/write2.png

ioctl¶

In addition to read and write operations, a driver needs the ability to perform certain physical device control tasks. These operations are accomplished by implementing a ioctl function. Initially, the ioctl system telephone call used Big Kernel Lock. That's why the call was gradually replaced with its unlocked version chosen unlocked_ioctl . You can read more on LWN: http://lwn.net/Manufactures/119652/

                                    static                  long                  my_ioctl                  (                  struct                  file                  *                  file                  ,                  unsigned                  int                  cmd                  ,                  unsigned                  long                  arg                  );                

cmd is the command sent from user-space. If a value is being sent from the user-space call, it can be accessed directly. If a buffer is fetched, the arg value will be a arrow to it, and must exist accessed through the copy_to_user or copy_from_user .

Before implementing the ioctl function, the numbers corresponding to the commands must be chosen. One method is to choose consecutive numbers starting at 0, only it is recommended to employ _IOC(dir, type, nr, size) macrodefinition to generate ioctl codes. The macrodefinition parameters are as follows:

  • dir represents the data transfer ( _IOC_NONE , _IOC_READ , _IOC_WRITE ).
  • blazon represents the magic number ( Documentation/ioctl/ioctl-number.txt );
  • nr is the ioctl code for the device;
  • size is the size of the transferred information.

The following example shows an implementation for a ioctl role:

                                    #include                  <asm/ioctl.h>                                    #define MY_IOCTL_IN _IOC(_IOC_WRITE, 'k', ane, sizeof(my_ioctl_data))                  static                  long                  my_ioctl                  (                  struct                  file                  *                  file                  ,                  unsigned                  int                  cmd                  ,                  unsigned                  long                  arg                  )                  {                  struct                  my_device_data                  *                  my_data                  =                  (                  struct                  my_device_data                  *                  )                  file                  ->                  private_data                  ;                  my_ioctl_data                  mid                  ;                  switch                  (                  cmd                  )                  {                  example                  MY_IOCTL_IN                  :                  if                  (                  copy_from_user                  (                  &                  mid                  ,                  (                  my_ioctl_data                  *                  )                  arg                  ,                  sizeof                  (                  my_ioctl_data                  ))                  )                  return                  -                  EFAULT                  ;                  /* process information and execute command */                  break                  ;                  default                  :                  return                  -                  ENOTTY                  ;                  }                  return                  0                  ;                  }                

At the user-space call for the ioctl function, the my_ioctl function of the driver volition exist chosen. An case of such a user-space telephone call:

                                    if                  (                  ioctl                  (                  fd                  ,                  MY_IOCTL_IN                  ,                  buffer                  )                  <                  0                  )                  {                  /* handle mistake */                  }                

Waiting queues¶

It is often necessary for a thread to wait for an operation to finish, merely information technology is desirable that this wait is not busy-waiting. Using waiting queues nosotros can block a thread until an event occurs. When the condition is satisfied, elsewhere in the kernel, in another procedure, in an interrupt or deferrable work, we will wake-up the process.

A waiting queue is a listing of processes that are waiting for a specific outcome. A queue is defined with the wait_queue_head_t type and can be used by the functions/macros:

                                    #include                  <linux/wait.h>                                    DECLARE_WAIT_QUEUE_HEAD                  (                  wq_name                  );                  void                  init_waitqueue_head                  (                  wait_queue_head_t                  *                  q                  );                  int                  wait_event                  (                  wait_queue_head_t                  q                  ,                  int                  condition                  );                  int                  wait_event_interruptible                  (                  wait_queue_head_t                  q                  ,                  int                  condition                  );                  int                  wait_event_timeout                  (                  wait_queue_head_t                  q                  ,                  int                  status                  ,                  int                  timeout                  );                  int                  wait_event_interruptible_timeout                  (                  wait_queue_head_t                  q                  ,                  int                  condition                  ,                  int                  timeout                  );                  void                  wake_up                  (                  wait_queue_head_t                  *                  q                  );                  void                  wake_up_interruptible                  (                  wait_queue_head_t                  *                  q                  );                

The roles of the macros / functions in a higher place are:

  • init_waitqueue_head() initializes the queue; to initialize the queue at compile time, you can use the DECLARE_WAIT_QUEUE_HEAD macro;
  • wait_event() and wait_event_interruptible() adds the current thread to the queue while the condition is false, sets it to TASK_UNINTERRUPTIBLE or TASK_INTERRUPTIBLE and calls the scheduler to schedule a new thread; Waiting will be interrupted when some other thread will call the wake_up function;
  • wait_event_timeout() and wait_event_interruptible_timeout() take the same effect as the above functions, only waiting can exist interrupted at the end of the timeout received as a parameter;
  • wake_up() puts all threads off from country TASK_INTERRUPTIBLE and TASK_UNINTERRUPTIBLE in TASK_RUNNING status; Remove these threads from the queue;
  • wake_up_interruptible() same action, but but threads with TASK_INTERRUPTIBLE status are woken upwards.

A simple example is that of a thread waiting to change the value of a flag. The initializations are done past the sequence:

                                    #include                  <linux/sched.h>                                    wait_queue_head_t                  wq                  ;                  int                  flag                  =                  0                  ;                  init_waitqueue_head                  (                  &                  wq                  );                

A thread volition wait for the flag to be inverse to a value other than zero:

                                    wait_event_interruptible                  (                  wq                  ,                  flag                  !=                  0                  );                

While another thread will change the flag value and wake upward the waiting threads:

                                    flag                  =                  1                  ;                  wake_up_interruptible                  (                  &                  wq                  );                

Exercises¶

Important

To solve exercises, you demand to perform these steps:

  • prepare skeletons from templates
  • build modules
  • re-create modules to the VM
  • starting time the VM and test the module in the VM.

The electric current lab name is device_drivers. Run across the exercises for the job name.

The skeleton lawmaking is generated from total source examples located in tools/labs/templates . To solve the tasks, start by generating the skeleton lawmaking for a consummate lab:

                    tools/labs $ make make clean tools/labs $                    LABS                    =<lab proper name> make skels                  

Yous tin too generate the skeleton for a single job, using

                    tools/labs $                    LABS                    =<lab name>/<task name> make skels                  

Once the skeleton drivers are generated, build the source:

Then, copy the modules and start the VM:

                    tools/labs $ brand copy tools/labs $ brand kick                  

The modules are placed in /habitation/root/skels/device_drivers/<task_name>.

Alternatively, we can copy files via scp, in guild to avoid restarting the VM. For boosted details about connecting to the VM via the network, delight check Connecting to the Virtual Car.

Review the Exercises section for more than detailed information.

Warning

Earlier starting the exercises or generating the skeletons, please run git pull inside the Linux repo, to make sure you take the latest version of the exercises.

If you lot take local changes, the pull control volition fail. Bank check for local changes using git condition . If you want to keep them, run git stash before pull and git stash pop after. To discard the changes, run git reset --hard master .

If y'all already generated the skeleton earlier git pull you volition need to generate it once again.

0. Intro¶

Using LXR find the definitions of the following symbols in the Linux kernel:

  • struct file
  • struct file_operations
  • generic_ro_fops
  • vfs_read()

1. Annals/unregister¶

The driver volition control a single device with the MY_MAJOR major and MY_MINOR minor (the macros defined in the kernel/so2_cdev.c file).

  1. Create /dev/so2_cdev grapheme device node using mknod.

    Hint

    Read Majors and minors department in the lab.

  2. Implement the registration and deregistration of the device with the name so2_cdev , respectively in the init and exit module functions. Implement TODO 1.

  3. Display, using pr_info , a bulletin subsequently the registration and unregistration operations to confirm that they were successful. So load the module into the kernel:

    And come across character devices in /proc/devices :

                                $ true cat /proc/devices                            |                            less                          

    Identify the device blazon registered with major 42 . Note that /proc/devices contains just the device types (major) but not the bodily devices (i.due east. minors).

    Note

    Entries in /dev are non created by loading the module. These can exist created in two ways:

    • manually, using the mknod control equally we did above.
    • automatically using udev daemon
  4. Unload the kernel module

2. Register an already registered major¶

Modify MY_MAJOR and then that information technology points to an already used major number.

Hint

Come across /proc/devices to get an already assigned major.

See errno-base.h and effigy out what does the mistake code mean. Render to the initial configuration of the module.

three. Open and close¶

Run cat /dev/so2_cdev to read data from our char device. Reading does not piece of work because the commuter does not have the open up part implemented. Follow comments marked with TODO 2 and implement them.

  1. Initialize your device
    • add together a cdev struct field to so2_device_data structure.
    • Read the section Registration and unregistration of character devices in the lab.
  2. Implement the open up and release functions in the driver.
  3. Display a message in the open up and release functions.
  4. Read again /dev/so2_cdev file. Follow the letters displayed by the kernel. We however get an mistake considering read function is not yet implemented.

Annotation

The image of a device driver'due south operations is in the file_operations structure. Read Open and release department.

four. Access restriction¶

Restrict access to the device with atomic variables, so that a single process tin can open the device at a time. The rest will receive the "device busy" error ( -EBUSY ). Restricting access will be done in the open office displayed by the driver. Follow comments marked with TODO 3 and implement them.

  1. Add an atomic_t variable to the device structure.
  2. Initialize the variable at module initialization.
  3. Use the variable in the open function to restrict access to the device. We recommend using atomic_cmpxchg() .
  4. Reset the variable in the release function to retrieve admission to the device.
  5. To test your deployment, you lot'll need to simulate a long-term use of your device. To simulate a sleep, phone call the scheduler at the end of the device opening:
                    set_current_state(TASK_INTERRUPTIBLE)                    ;                    schedule_timeout(                    grand                    )                    ;                  

Note

The reward of the atomic_cmpxchg function is that it tin can check the former value of the variable and set it up to a new value, all in one atomic operation. Read more details about atomic_cmpxchg An example of use is hither.

5. Read operation¶

Implement the read part in the driver. Follow comments marked with TODO 4 and implement them.

  1. Go on a buffer in so2_device_data structure initialized with the value of MESSAGE macro. Initializing this buffer will be washed in module init function.
  2. At a read call, copy the contents of the kernel infinite buffer into the user space buffer.
    • Utilise the copy_to_user() office to copy data from kernel space to user space.
    • Ignore the size and kickoff parameters at this fourth dimension. You lot can assume that the buffer in user space is big enough. You do not need to check the validity of the size argument of the read office.
    • The value returned by the read call is the number of bytes transmitted from the kernel infinite buffer to the user space buffer.
  3. After implementation, examination using cat /dev/so2_cdev .

Notation

The command cat /dev/so2_cdev does non finish (use Ctrl+C). Read the read and write sections and Access to the address space of the process If you desire to display the first value use a construction of the course: pr_info("Offset: %lld \north", *offset) ; The data type loff_t (used by first ) is a typedef for long long int.

The cat command reads to the end of the file, and the stop of the file is signaled by returning the value 0 in the read. Thus, for a correct implementation, you volition demand to update and utilise the offset received as a parameter in the read function and render the value 0 when the user has reached the cease of the buffer.

Change the driver and so that the cat commands ends:

  1. Use the size parameter.
  2. For every read, update the offset parameter accordingly.
  3. Ensure that the read part returns the number of bytes that were copied into the user buffer.

Annotation

By dereferencing the offset parameter it is possible to read and move the electric current position in the file. Its value needs to exist updated every fourth dimension a read is done successfully.

vi. Write operation¶

Add the power to write a message into kernel buffer to replace the predefined message. Implement the write function in the driver. Follow comments marked with TODO 5

Ignore the get-go parameter at this fourth dimension. You tin presume that the commuter buffer is large enough. You lot practice not need to bank check the validity of the write function size argument.

Note

The paradigm of a device driver's operations is in the file_operations structure. Test using commands:

                                            repeat                      "arpeggio"> /dev/so2_cdev true cat /dev/so2_cdev                    

Read the read and write sections and Access to the address space of the process

7. ioctl operation¶

For this practice, nosotros want to add the ioctl MY_IOCTL_PRINT to display the message from the IOCTL_MESSAGE macro in the commuter. Follow the comments marked with TODO 6

For this:

  1. Implement the ioctl function in the driver.
  2. We need to use user/so2_cdev_test.c to call the ioctl function with the appropriate parameters.
  3. To test, we will utilize an user-space plan ( user/so2_cdev_test.c ) which will call the ioctl function with the required arguments.

Annotation

The macro MY_IOCTL_PRINT is defined in the file include/so2_cdev.h , which is shared betwixt the kernel module and the user-space program.

Read the ioctl section in the lab.

Annotation

The userspace code is compiled automatically at make build and copied at make re-create .

Considering we need to compile the plan for qemu motorcar which is 32 chip, if your host is 64 bit and so you need to install gcc-multilib package.