使用seq_file读取特定寄存器:设备驱动程序

时间:2016-04-20 17:59:25

标签: c linux-device-driver

我有一个非常简单的修改驱动程序来写入特定的寄存器。我传递了值和我打算写入的寄存器。 IE:0x00000008 2,读写为写入8(base_addr + 2 * 4)。

这很有效,因为驱动程序会自动接收输入缓冲区。

现在因为为了阅读我必须使用seq_file我不知道如何传递我想要读取的寄存器...我正在使用的增强设备驱动程序发布在下面。

有没有办法传递寄存器值然后使用ioread32(base_addr + register)?如何在此函数中使用copy_from_user,其中我的唯一输入是seq_file?

用法示例(将ioread32更改为base_addr + 2):

$ sudo echo "0x00001233 2" > /proc/accelerator                                  
$ cat /proc/accelerator                                                         
0x1233
$ 

Dev Driver Code:

#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/uaccess.h>    /* Needed for copy_from_user */
#include <asm/io.h>         /* Needed for IO Read/Write Functions */
#include <linux/proc_fs.h>  /* Needed for Proc File System Functions */
#include <linux/seq_file.h> /* Needed for Sequence File Operations */
#include <linux/platform_device.h>  /* Needed for Platform Driver Functions */
#include <linux/slab.h> /*for kmalloc and kfree */
#include <linux/vmalloc.h>

/* Define Driver Name */
#define DRIVER_NAME "accelerator"


unsigned long *base_addr;   /* Vitual Base Address */
struct resource *res;       /* Device Resource Structure */
unsigned long remap_size;   /* Device Memory Size */

/* Write operation for /proc/accelerator
 * -----------------------------------
 *  When user cat a string to /proc/accelerator file, the string will be stored in
 *  const char __user *buf. This function will copy the string from user
 *  space into kernel space, and change it to an unsigned long value.
 *  It will then write the value to the register of accelerator controller,
 *  and turn on the corresponding LEDs eventually.
 */
 static ssize_t proc_accelerator_write(struct file *file, const char __user * buf,
                  size_t count, loff_t * ppos)
  {
      //Allocate
      char * myaddr_phrase;
      char * pEnd;
      char *buffer = vzalloc(count);
      myaddr_phrase = buffer;
      u32 myaddr_value;
       u32 myreg_value;

      //Copy Data 
      if (count < 22) {
          if (copy_from_user(myaddr_phrase, buf, count))
              return -EFAULT;

         //myaddr_phrase[count] = '\0';
     printk("count = %d\n", count);
     printk("%s\n",myaddr_phrase);
      }
    // Use strtol to parse input
    /* http://www.cplusplus.com/reference/cstdlib/strtol/ */
         myaddr_value = simple_strtoul(myaddr_phrase, &pEnd, 0);
     printk("myaddr_value = %08x\n", myaddr_value);
     pEnd = strsep(&myaddr_phrase," ");
     myreg_value = simple_strtoul(myaddr_phrase,&pEnd ,0);
     printk("myreg_value = %08x\n", myreg_value);
     printk("final_value = %08x\n", (base_addr + (myreg_value)));
     printk("mult_val = %08x\n", (myreg_value));
     wmb();
     iowrite32(myaddr_value, (base_addr + (myreg_value)));
     return count;
 }

 /* Callback function when opening file /proc/accelerator
  * ------------------------------------------------------
  *  Read the register value of accelerator controller, print the value to
  *  the sequence file struct seq_file *p. In file open operation for /proc/accelerator
  *  this callback function will be called first to fill up the seq_file,
  *  and seq_read function will print whatever in seq_file to the terminal.
  */
 static int proc_accelerator_show(struct seq_file *p, void *v)
 {
     u32 accelerator_value;
     accelerator_value = ioread32(base_addr+2);
     seq_printf(p, "0x%x\n", accelerator_value);
     return 0;
 }

 /* Open function for /proc/accelerator
   * ------------------------------------
   *  When user want to read /proc/accelerator (i.e. cat /proc/accelerator), the open function 
   *  will be called first. In the open function, a seq_file will be prepared and the 
   *  status of accelerator will be filled into the seq_file by proc_accelerator_show function.
   *
   *p 69
    int (*open) (struct inode *, struct file *);
    Though this is always the first operation performed on the device file, the driver
    is not required to declare a corresponding method. If this entry is NULL, opening
    the device always succeeds, but your driver isn’t notified.

    Open described on p76
   */
 static int proc_accelerator_open(struct inode *inode, struct file *file)
  {
      unsigned int size = 16;
      char *buf;
      struct seq_file *m;
      int res;

      buf = (char *)kmalloc(size * sizeof(char), GFP_KERNEL);
      if (!buf)
          return -ENOMEM;

      res = single_open(file, proc_accelerator_show, NULL);

      if (!res) {
          m = file->private_data;
          m->buf = buf;
          m->size = size;
      } else {
          kfree(buf);
      }

      return res;
  }

  /* File Operations for /proc/accelerator */
  static const struct file_operations proc_accelerator_operations = {
      .open = proc_accelerator_open,
      .read =  seq_read,
      .write = proc_accelerator_write,
      .llseek = seq_lseek,
      .release = single_release
  };
  /*
int (*open) (struct inode *, struct file *);
Though this is always the first operation performed on the device file, the driver
is not required to declare a corresponding method. If this entry is NULL, opening
the device always succeeds, but your driver isn’t notified.

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
Used to retrieve data from the device. A null pointer in this position causes the
readsystem call to fail with-EINVAL(“Invalid argument”). A nonnegative return
value represents the number of bytes successfully read (the return value is a
“signed size” type, usually the native integer type for the target platform)

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
Sends data to the device. IfNULL, -EINVALis returned to the program calling the
writesystem call. The return value, if nonnegative, represents the number of
bytes successfully written.

loff_t (*llseek) (struct file *, loff_t, int);
Thellseek method is used to change the current read/write position in a file, and
the new position is returned as a (positive) return value. Theloff_tparameter is
a “long offset” and is at least 64 bits wide even on 32-bit platforms. Errors are
signaled by a negative return value. If this function pointer isNULL, seek calls will
modify the position counter in thefilestructure (described in the section “The
file Structure”) in potentially unpredictable ways.

int (*release) (struct inode *, struct file *);
This operation is invoked when thefilestructure is being released. Likeopen,
releasecan beNULL.
*
  */


  /* Shutdown function for accelerator
   * -----------------------------------
   *  Before accelerator shutdown, turn-off all the leds
  */
 static void accelerator_shutdown(struct platform_device *pdev)
 {
    iowrite32(0, base_addr);
 }

 /* Remove function for accelerator
  * ----------------------------------
  *  When accelerator module is removed, turn off all the leds first,
  *  release virtual address and the memory region requested.
  */
 static int accelerator_remove(struct platform_device *pdev)
 {
     accelerator_shutdown(pdev);

     /* Remove /proc/accelerator entry */
     remove_proc_entry(DRIVER_NAME, NULL);

     /* Release mapped virtual address */
     iounmap(base_addr);

     /* Release the region */
     release_mem_region(res->start, remap_size);

     return 0;
 }

 /* Device Probe function for accelerator
  * ------------------------------------
  *  Get the resource structure from the information in device tree.
  *  request the memory region needed for the controller, and map it into
  *  kernel virtual memory space. Create an entry under /proc file system
  *  and register file operations for that entry.
  */
 static int accelerator_probe(struct platform_device *pdev)
 {

     struct proc_dir_entry *accelerator_proc_entry;
     int ret = 0;
    printk(KERN_ALERT "Probing\n");
     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
     if (!res) {
         dev_err(&pdev->dev, "No memory resource\n");
         return -ENODEV;
     }

     remap_size = res->end - res->start + 1;
     if (!request_mem_region(res->start, remap_size, pdev->name)) {
         dev_err(&pdev->dev, "Cannot request IO\n");
         return -ENXIO;
     }

     base_addr = ioremap(res->start, remap_size);
     if (base_addr == NULL) {
         dev_err(&pdev->dev, "Couldn't ioremap memory at 0x%08lx\n",
             (unsigned long)res->start);
         ret = -ENOMEM;
         goto err_release_region;
     }

     accelerator_proc_entry = proc_create(DRIVER_NAME, 0, NULL,
                        &proc_accelerator_operations);
     if (accelerator_proc_entry == NULL) {
         dev_err(&pdev->dev, "Couldn't create proc entry\n");
         ret = -ENOMEM;
         goto err_create_proc_entry;
     }

     printk(KERN_INFO DRIVER_NAME " probed at VA 0x%08lx\n",
            (unsigned long) base_addr);
     printk(KERN_ALERT "Goodbye, probe\n"); 

     return 0;

  err_create_proc_entry:
     iounmap(base_addr);
  err_release_region:
     release_mem_region(res->start, remap_size);

     return ret;
 }

 /* device match table to match with device node in device tree */
 /*
 https://lwn.net/Articles/448502/
 */
 static const struct of_device_id accelerator_of_match[] = {
     {.compatible = "PCA,bitSplitter"},
     {},
 };

 MODULE_DEVICE_TABLE(of, accelerator_of_match);

 /* platform driver structure for accelerator driver */
 /*
 Platform devices are represented by the struct, and is found in <linux/platform_device.h>
 at minimum probe() and remove() must be supplied, the others have to do with power management 
 https://lwn.net/Articles/448499/
 */
 static struct platform_driver accelerator_driver = {
     .driver = {
            .name = DRIVER_NAME,
            .owner = THIS_MODULE,
            .of_match_table = accelerator_of_match},
     .probe = accelerator_probe,
     .remove = accelerator_remove,
     .shutdown = accelerator_shutdown
};

 /* Register accelerator platform driver */
 /*
   Helper macro for drivers that don't do
 * anything special in module init/exit.  This eliminates a lot of
 * boilerplate.  Each module may only use this macro once, and
 * calling it replaces module_init() and module_exit()
 *
 * Platform drivers are for HW that will not dynamically come and go into a Linux system, 
 * such as the video and audio controllers in a tablet.  In makes sense to statically pull 
 * in those code necessary through the __initcall magic discussed above.  
 *
 * http://henryomd.blogspot.com/2014/11/linux-kernel-startup.html
 */
 module_platform_driver(accelerator_driver);

 /* Module Informations */
 /*
 Discussed in 2.6 Preliminaries 
 */
 MODULE_AUTHOR("Digilent, Inc.");
 MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION(DRIVER_NAME ": accelerator driver (Simple Version)");
 MODULE_ALIAS(DRIVER_NAME);

0 个答案:

没有答案