我有一个非常简单的修改驱动程序来写入特定的寄存器。我传递了值和我打算写入的寄存器。 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);