I am working on parallel port driver. Now i had seen the methods to get interrupt from parallel port.
by one of them,
First make 4th pin of control reg 1(IRQ).
then make nACK low.
so i make a switch in between data pin 8 and nACK. so, if i write some data which has msb 1 then it will be interrupted, if that switch is on. Now i have a problem. If i disconnected that switch and again connect then it will not give me interrupt.
So, how can i do that thing by which i got interrupt by means of switch is connect or not.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/parport.h>
#include <asm/uaccess.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/errno.h>
#include <asm/irq.h>
#include <linux/kthread.h>
#define DEVICE_NAME "parlelport"
struct pardevice *pdev;
static int dummy;
int ret;
static irqreturn_t recv_handler(int irq, void *dev_id)
{
printk("we inside if isr");
return 0;
}
int led_open(struct inode *inode, struct file *file)
{
printk("1\n");
printk("Device File Opened\n");
char byte1;
byte1=inb(0x37A);
printk("%d \n",byte1);
return 0;
}
ssize_t led_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
{
printk("2\n");
char byte=inb(0x37A);
printk("%d",byte);
byte = byte | 0x10; // 0x10= 00010000, 4th pin of CTRL reg
outb(byte, 0x37A); //which enable IRQ
char kbuf;
copy_from_user(&kbuf, buf, 1);
parport_claim_or_block(pdev); /* Claim the port */
parport_write_data(pdev->port, kbuf); /* Write to the device */
//parport_release (pdev);
return count;
}
int led_release(struct inode *inode, struct file *file)
{
printk("3\n");
printk("Device File Released\n");
char byte;
byte=inb(0x37A);
printk("%d", byte);
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
};
static int led_preempt(void *handle)
{
printk("4\n");
return 1;
}
static void led_attach(struct parport *port)
{
printk("5\n");
pdev = parport_register_device(port, DEVICE_NAME, led_preempt, NULL, NULL, 0, NULL);
printk("Port attached\n");
char byte1;
byte1=inb(0x37A);
printk("%d \n",byte1);
}
static void led_detach(struct parport *port)
{
printk("6\n");
parport_unregister_device (pdev);
printk("Port Deattached\n");
}
static struct parport_driver led_driver = {
.name= "led",
.attach = led_attach,
.detach = led_detach,
};
int __init led_init(void)
{
printk("7\n");
if (register_chrdev(89, DEVICE_NAME, &led_fops))
{
printk("Can't register device\n");
return -1;
}
char byte=inb(0x37A);
printk("%d",byte);
byte = byte | 0x10;
outb(byte, 0x37A);
char byte1;
byte1=inb(0x37A);
printk("%d %d \n",byte,byte1);
parport_register_driver(&led_driver);
ret= request_irq(7, recv_handler, IRQF_SHARED, "parlelport", &dummy);
printk("%d",ret);
return 0;
}
void __exit led_cleanup(void)
{
printk("8\n");
unregister_chrdev(89, DEVICE_NAME);
if(!ret)
free_irq(7, &dummy);
parport_unregister_driver(&led_driver);
printk("LED Driver unregistered.\n");
return;
}
module_init(led_init);
module_exit(led_cleanup);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Vikrant Patel");
Test.c file
int main()
{
int fd=open("/dev/parlelport",O_RDWR);
char byte;
printf("Enter Value to send on parallel port");
scanf("%c",&byte);
printf("Byte value is %c\n",byte);
if(write(fd,&byte,sizeof(char)))
{
printf("\nSuccessfully written on port");
}
getchar();
getchar();
close(fd);
}
I got it.
First make a thread
put the Enable IRQ code in that thread
so it will continuously execute it
whenever i connect pins at my hardware then it will be interrupted.
check this code for your ref.
#include <linux/module.h>
#include <linux/parport.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>
#define DEVICE_NAME "parlelport"
#define DATA 0x378
#define STATUS 0x379
#define CONTROL 0x37A
struct pardevice *pdev;
struct task_struct *ts1, *ts2;
int dummy;
char buf1='1',buf2='2';
char byte='0';
int thread1(void *data)
{
while(1)
{
outb(byte, CONTROL); /* 0x30 = 0011 0000 , makes IRQ pin(5th bit) enable */
printk("Thread1\n");
parport_claim_or_block(pdev); /* Claim the port */
parport_write_data(pdev->port, buf1); /* Write to the device */
parport_release(pdev); /* Release the port */
msleep(4000);
if (kthread_should_stop())
break;
}
return 0;
}
int thread2(void *data)
{
while(1)
{
outb(byte,CONTROL); /* 0x30 = 0011 0000 , makes IRQ pin(5th bit) enable */
printk("Thread2\n");
parport_claim_or_block(pdev); /* Claim the port */
parport_write_data(pdev->port, buf2); /* Write to the device */
parport_release(pdev); /* Release the port */
msleep(4000);
if (kthread_should_stop())
break;
}
return 0;
}
int led_open(struct inode *inode, struct file *file)
{
printk("Device File Opened\n");
ts1=kthread_run(thread1,NULL,"kthread"); /* Initiation of thread 1 */
msleep(2000);
ts2=kthread_run(thread2,NULL,"kthread"); /* Initiation of thread 2 */
return 0;
}
ssize_t led_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
{
return count;
}
int led_release(struct inode *inode, struct file *file)
{
printk("Device File Released\n");
kthread_stop(ts1);
kthread_stop(ts2);
buf1='1';
buf2='2';
outb_p(0x00,DATA);
return 0;
}
static irqreturn_t recv_handler(int irq, void *unused)
{
printk("we inside of isr");
buf1= buf1 ^ 0x7F;
buf2= buf2 ^ 0x7F;
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
};
static int led_preempt(void *handle)
{
return 1;
}
static void led_attach(struct parport *port)
{
pdev = parport_register_device(port, DEVICE_NAME, led_preempt, NULL, NULL, 0, NULL);
printk("Port attached\n");
}
static void led_detach(struct parport *port)
{
parport_unregister_device (pdev);
printk("Port Deattached\n");
}
static struct parport_driver led_driver = {
.name= "led",
.attach = led_attach,
.detach = led_detach,
};
int __init led_init(void)
{
/*Register our ISR with the kernel for PARALLEL_IRQ */
if (request_irq(7, recv_handler, IRQF_SHARED, DEVICE_NAME ,&dummy))
{
printk("Registering ISR failed\n");
return -ENODEV;
}
/*Register Character Device Driver at 89 Major number*/
if (register_chrdev(89, DEVICE_NAME, &led_fops))
{
printk("Can't register device\n");
return -1;
}
/*Register parallel port driver with parport structure led_driver*/
parport_register_driver(&led_driver);
return 0;
}
void __exit led_cleanup(void)
{
unregister_chrdev(89, DEVICE_NAME); /* Unregister char driver */
free_irq(7, &dummy); /* Free the ISR from IRQ7 */
parport_unregister_driver(&led_driver); /* Unregister the parallel port driver */
printk("LED Driver unregistered.\n");
return;
}
module_init(led_init);
module_exit(led_cleanup);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Vikrant Patel");
Related
I have built a custom hardware configuration in Vivado for Xilinx SoC board, and used petalinux to create a custom driver to control the hardware logic. It seems like after running insmod command, the driver is never initialized and the ->probe() function is not called.
I am new to this domain, and wondering if anyone ran into a similar issue and has some pointers on where and what to check in order to see where the issue is. Any advice would be very helpful!
Running the dmesg command after inserting the driver into the kernel:
root#plzwork3:/proc/device-tree/amba_pl#0/simpleMultiplier#a0000000# dmesg
[ 3351.680317] <1>Hello module world.
[ 3351.683735] <1>Module parameters were (0xdeadbeef) and "default"
Device Tree entity created by Vivado:
/ {
amba_pl: amba_pl#0 {
#address-cells = <2>;
#size-cells = <2>;
compatible = "simple-bus";
ranges ;
simpleMultiplier_0: simpleMultiplier#a0000000 {
clock-names = "axiliteregport_aclk";
clocks = <&zynqmp_clk 71>;
compatible = "xlnx,simpleMultiplier-1.0";
reg = <0x0 0xa0000000 0x0 0x10000>;
xlnx,axiliteregport-addr-width = <0x4>;
xlnx,axiliteregport-data-width = <0x20>;
};
};
};
Device driver code snippets:
#ifdef CONFIG_OF
static struct of_device_id simpmod_of_match[] = {
{ .compatible = "xlnx,simpleMultiplier-1.0", },
{ /* end of list */ },
};
Full device driver code:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/fs.h>
/* Standard module information, edit as appropriate */
MODULE_LICENSE("GPL");
MODULE_AUTHOR
("Xilinx Inc.");
MODULE_DESCRIPTION("simpmod - loadable module template generated by petalinux-create -t modules");
#define DRIVER_NAME "simpmod"
#define CLASS_NAME "CLASS_TUT"
static char ker_buf[100];
static int currLen = 0;
// Parts of the math operation (2,2,+) is 2+2
static int operand_1 = 0;
static int operand_2 = 0;
static int result = 0;
static struct class *driver_class = NULL;
static dev_t first;
static struct cdev c_dev; // Global variable for the character device
static struct device *ourDevice;
// Pointer to the IP registers
volatile unsigned int *regA;
volatile unsigned int *regB;
volatile unsigned int *regC;
// Structure to hold device specific data
struct simpmod_local {
int irq;
unsigned long mem_start;
unsigned long mem_end;
void __iomem *base_addr;
};
// Character callbacks prototype
static int dev_open(struct inode *inod, struct file *fil);
static ssize_t dev_read(struct file *fil, char *buf, size_t len, loff_t *off);
static ssize_t dev_write(struct file *fil, const char *buf, size_t len, loff_t *off);
static int dev_release(struct inode *inod, struct file *fil);
static struct file_operations fops = {
.read=dev_read,
.write=dev_write,
.open=dev_open,
.release=dev_release,
};
static irqreturn_t simpmod_irq(int irq, void *lp){
printk("simpmod interrupt\n");
return IRQ_HANDLED;
}
static int simpmod_probe(struct platform_device *pdev){
struct resource *r_irq; /* Interrupt resources */
struct resource *r_mem; /* IO mem resources */
struct device *dev = &pdev->dev;
static struct simpmod_local *lp = NULL;
int rc = 0;
printk("Device Tree Probing\n");
// Get data of type IORESOURCE_MEM(reg-addr) from the device-tree
// Other types defined here:
// http://lxr.free-electrons.com/source/include/linux/ioport.h#L33
r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r_mem) {
dev_err(dev, "invalid address\n");
return -ENODEV;
}
// Allocate memory (continuous physical)to hold simpmod_local struct
lp = (struct simpmod_local *) kmalloc(sizeof(struct simpmod_local), GFP_KERNEL);
if (!lp) {
printk("Cound not allocate simpmod device\n");
return -ENOMEM;
}
dev_set_drvdata(dev, lp);
// Save data on simpmod_local strucutre
lp->mem_start = r_mem->start;
lp->mem_end = r_mem->end;
// Ask the kernel the memory region defined on the device-tree and
// prevent other drivers to overlap on this region
// This is needed before the ioremap
if (!request_mem_region(lp->mem_start,
lp->mem_end - lp->mem_start + 1,
DRIVER_NAME)) {
dev_err(dev, "Couldn't lock memory region at %p\n",
(void *)lp->mem_start);
rc = -EBUSY;
goto error1;
}
// Get an virtual address from the device physical address with a
// range size: lp->mem_end - lp->mem_start + 1
lp->base_addr = ioremap(lp->mem_start, lp->mem_end - lp->mem_start + 1);
if (!lp->base_addr) {
dev_err(dev, "simpmod: Could not allocate iomem\n");
rc = -EIO;
goto error2;
}
// ****************** NORMAL Device diver *************************
// register a range of char device numbers
if (alloc_chrdev_region(&first, 0, 1, "Leonardo") < 0){
printk(KERN_ALERT "alloc_chrdev_region failed\n");
return -1;
}
// Create class (/sysfs)
driver_class = class_create(THIS_MODULE, CLASS_NAME);
if (driver_class == NULL) {
printk(KERN_ALERT "Create class failed\n");
unregister_chrdev_region(first, 1);
return -1;
}
ourDevice = device_create(driver_class, NULL, first, NULL, "tutDevice");
if ( ourDevice == NULL){
printk(KERN_ALERT "Create device failed\n");
class_destroy(driver_class);
unregister_chrdev_region(first, 1);
return -1;
}
// Create a character device /dev/tutDevice
cdev_init(&c_dev, &fops);
if (cdev_add(&c_dev, first, 1) == -1){
printk(KERN_ALERT "Create character device failed\n");
device_destroy(driver_class, first);
class_destroy(driver_class);
unregister_chrdev_region(first, 1);
return -1;petalinux-config -c rootfs
}
// Create the attribute file on /sysfs/class/CLASS_TUT/ called
// parCrtl and isBusy
// Get data of type IORESOURCE_IRQ(interrupt) from the device-tree
r_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!r_irq) {
printk("no IRQ found\n");
printk("simpmod at 0x%08x mapped to 0x%08x\n",
(unsigned int __force)lp->mem_start,
(unsigned int __force)lp->base_addr);
// Configuring pointers to the IP registers
regA = (unsigned int __force)lp->base_addr + 0x10;
regB = (unsigned int __force)lp->base_addr + 0x18;
regC = (unsigned int __force)lp->base_addr + 0x26;
printk("regA: 0x%08x\n",(unsigned int)regA);
printk("regB: 0x%08x\n",(unsigned int)regB);
printk("regC: 0x%08x\n",(unsigned int)regC);
return 0;
}
lp->irq = r_irq->start;
rc = request_irq(lp->irq, &simpmod_irq, 0, DRIVER_NAME, lp);
if (rc) {
dev_err(dev, "testmodule: Could not allocate interrupt %d.\n",
lp->irq);
goto error3;
}
return 0;
error3:
free_irq(lp->irq, lp);
error2:
release_mem_region(lp->mem_start, lp->mem_end - lp->mem_start + 1);
error1:
kfree(lp);
dev_set_drvdata(dev, NULL);
return rc;
}
static int simpmod_remove(struct platform_device *pdev){
struct device *dev = &pdev->dev;
struct simpmod_local *lp = dev_get_drvdata(dev);
free_irq(lp->irq, lp);
release_mem_region(lp->mem_start, lp->mem_end - lp->mem_start + 1);
kfree(lp);
dev_set_drvdata(dev, NULL);
return 0;
}
// Indicate which type of hardware we handle on this case(simpleAlu-1.0)
#ifdef CONFIG_OF
static struct of_device_id simpmod_of_match[] = {
{ .compatible = "xlnx,simpleMultiplier-1.0", },
{ /* end of list */ },
};
MODULE_DEVICE_TABLE(of, simpmod_of_match);
#else
# define simpmod_of_match
#endif
static struct platform_driver simpmod_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = simpmod_of_match,
},
.probe = simpmod_probe,
.remove = simpmod_remove,
};
static int __init simpmod_init(void)
{
printk("<1>Simple device driver.\n");
printk("Hussam was here, and he init the module\n");
return platform_driver_register(&simpmod_driver);
}
static void __exit simpmod_exit(void)
{
platform_driver_unregister(&simpmod_driver);
printk(KERN_ALERT "Goodbye module world.\n");
}
static int dev_open(struct inode *inod, struct file *fil){
printk(KERN_ALERT "Character device opened\n");
return 0;
}
// Just send to the user a string with the value of result
static ssize_t dev_read(struct file *fil, char *buf, size_t len, loff_t *off){
// Return the result only once (otherwise a simple cat will loop)
// Copy from kernel space to user space
printk(KERN_ALERT "Reading device rx: %d\n",(int)len);
int n = sprintf(ker_buf, "%d\n", *regC);
// Copy back to user the result (to,from,size)
copy_to_user(buf,ker_buf,n);
printk(KERN_ALERT "Returning %s rx: %d\n",ker_buf,n);
return n;
}
// Parse the input stream ex: "50,2,*" to some operand variables.
static ssize_t dev_write(struct file *fil, const char *buf, size_t len, loff_t *off){
// Get data from user space to kernel space
copy_from_user(ker_buf,buf,len);
sscanf (ker_buf,"%d,%d,%c",&operand_1,&operand_2);
ker_buf[len] = 0;
// Change the IP registers to the parsed operands (on rega and regb)
*regA = (unsigned int)operand_1;
*regB = (unsigned int)operand_2;
printk(KERN_ALERT "Receiving math operation <%d %d>\n",operand_1,operand_2);
return len;
}
static int dev_release(struct inode *inod, struct file *fil){
printk(KERN_ALERT "Device closed\n");
return 0;
}
module_init(simpmod_init);
module_exit(simpmod_exit);
the problem is, that the (compatible) names in your device tree does not match with those in your code:
Device Tree
compatible = "simple-bus";
Code:
#ifdef CONFIG_OF
static struct of_device_id simpmod_of_match[] = {
{ .compatible = "xlnx,simpleMultiplier-1.0", },
{ /* end of list */ },
};
Put your device tree:
compatible = "xlnx,simpleMultiplier-1.0";
Both entries should match!
Now your "probe" function should be called!
BR
Andreas
your of_match table section in the driver code:
// Indicate which type of hardware we handle on this case(simpleAlu-1.0)
#ifdef CONFIG_OF
static struct of_device_id simpmod_of_match[] = {
{ .compatible = "xlnx,simpleMultiplier-1.0", },
{ /* end of list */ },
};
MODULE_DEVICE_TABLE(of, simpmod_of_match);
#else
# define simpmod_of_match
#endif
should be like this:
static struct of_device_id simpmod_of_match[] = {
{ .compatible = "xlnx,simpleMultiplier-1.0", },
{ /* end of list */ },
};
MODULE_DEVICE_TABLE(of, simpmod_of_match);
and make sure the compatible line strings have to match in the pl.dtsi and of_match table
I'm having troubles with the tiny tty driver found in the book Linux Device Drivers. I had to adopt the code slightly to fit my requirements, so kicked out any code that was not relevant (see code below).
I use a kernel thread that writes "hello world" to the TTY layer. If I open the device file in a terminal using the cat command, I receive the intended string.
But I'm facing two issues:
Why is tiny_write(...) called whenever tty_insert_flip_char(...) is
called in my kernel thread (tiny_thread)? Shouldn't tiny_write(...) function be called only if writing to the device file? How to distinguish in this function if it is called during a read or write operation?
Why do I get an error if using echo on the device file?
echo test > /dev/tiny_tty
bash: echo: write error: Invalid argument
The driver is running on the Raspberry Pi kernel 4.9.56-v7.
Thanks a lot!
Regards,
Thomas
UPDATE: The first issue is (partly) solved using the solution in tty_flip_buffer_push() sends data back to itself. Is there a way to do this directly in the device driver, so no interaction is required by the user?
/*
* Tiny TTY driver
*
* Base on tiny tty driver from Greg Kroah-Hartman
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
#include <asm/uaccess.h>
#include <linux/kthread.h>
#include <linux/jiffies.h>
#define USE_SIMULATOR
#define DELAY_TIME HZ * 2 /* 2 seconds per character */
#define TINY_TTY_MAJOR 240 /* experimental range */
#define TINY_TTY_MINORS 1 /* only have 4 devices */
#if defined(USE_SIMULATOR)
static struct task_struct *thread_id;
static wait_queue_head_t wq_thread;
static DECLARE_COMPLETION(on_exit);
#endif /* USE_SIMULATOR */
struct tiny_serial {
struct tty_struct *tty; /* pointer to the tty for this device */
int open_count; /* number of times this port has been opened */
struct semaphore sem; /* locks this structure */
};
static struct tiny_serial *tiny_serial; /* initially all NULL */
#if defined(USE_SIMULATOR)
static int tiny_thread(void *thread_data)
{
unsigned int timeoutMs;
struct tiny_serial *tiny = (struct tiny_serial*)thread_data;
struct tty_struct *tty;
struct tty_port *port;
char buf[] = "hello world\n";
int i = 0;
allow_signal(SIGTERM);
pr_info("%s\n", __func__);
tty = tiny->tty;
port = tty->port;
while(kthread_should_stop() == 0)
{
timeoutMs = 1000;
timeoutMs = wait_event_interruptible_timeout(wq_thread, (timeoutMs==0), msecs_to_jiffies(timeoutMs));
if(timeoutMs == -ERESTARTSYS)
{
pr_info("%s - signal break\n", __func__);
up(&tiny->sem);
break;
}
pr_info("%s\n", __func__);
down(&tiny->sem);
if(tiny)
{
for (i = 0; i < strlen(buf); ++i)
{
if (!tty_buffer_request_room(tty->port, 1))
tty_flip_buffer_push(tty->port);
tty_insert_flip_char(tty->port, buf[i], TTY_NORMAL);
}
tty_flip_buffer_push(tty->port);
}
up(&tiny->sem);
}
complete_and_exit(&on_exit, 0);
}
#endif /* USE_SIMULATOR */
static int tiny_open(struct tty_struct *tty, struct file *file)
{
pr_info("%s\n", __func__);
/* initialize the pointer in case something fails */
tty->driver_data = NULL;
/* get the serial object associated with this tty pointer */
if(tiny_serial == NULL) {
/* first time accessing this device, let's create it */
tiny_serial = kmalloc(sizeof(*tiny_serial), GFP_KERNEL);
if (!tiny_serial)
return -ENOMEM;
sema_init(&tiny_serial->sem, 1);
tiny_serial->open_count = 0;
}
down(&tiny_serial->sem);
/* save our structure within the tty structure */
tty->driver_data = tiny_serial;
tiny_serial->tty = tty;
++tiny_serial->open_count;
if (tiny_serial->open_count == 1) {
/* this is the first time this port is opened */
/* do any hardware initialization needed here */
#if defined(USE_SIMULATOR)
if(thread_id == NULL)
thread_id = kthread_create(tiny_thread, (void*)tiny_serial, "tiny_thread");
wake_up_process(thread_id);
#endif /* USE_SIMULATOR */
}
up(&tiny_serial->sem);
return 0;
}
static void do_close(struct tiny_serial *tiny)
{
pr_info("%s\n", __func__);
down(&tiny->sem);
if (!tiny->open_count) {
/* port was never opened */
goto exit;
}
--tiny->open_count;
if (tiny->open_count <= 0) {
/* The port is being closed by the last user. */
/* Do any hardware specific stuff here */
#if defined(USE_SIMULATOR)
/* shut down our timer and free the memory */
if(thread_id)
{
kill_pid(task_pid(thread_id), SIGTERM, 1);
wait_for_completion(&on_exit);
thread_id = NULL;
}
#endif /* USE_SIMULATOR */
}
exit:
up(&tiny->sem);
}
static void tiny_close(struct tty_struct *tty, struct file *file)
{
struct tiny_serial *tiny = tty->driver_data;
pr_info("%s\n", __func__);
if (tiny)
do_close(tiny);
}
static int tiny_write(struct tty_struct *tty,
const unsigned char *buffer, int count)
{
struct tiny_serial *tiny = tty->driver_data;
int i;
int retval = -EINVAL;
if (!tiny)
return -ENODEV;
down(&tiny->sem);
if (!tiny->open_count)
/* port was not opened */
goto exit;
/* fake sending the data out a hardware port by
* writing it to the kernel debug log.
*/
printk(KERN_DEBUG "%s - ", __FUNCTION__);
for (i = 0; i < count; ++i)
{
printk("%02x ", buffer[i]);
}
printk("\n");
exit:
up(&tiny->sem);
return retval;
}
static int tiny_write_room(struct tty_struct *tty)
{
struct tiny_serial *tiny = tty->driver_data;
int room = -EINVAL;
pr_info("%s\n", __func__);
if (!tiny)
return -ENODEV;
down(&tiny->sem);
if (!tiny->open_count) {
/* port was not opened */
goto exit;
}
/* calculate how much room is left in the device */
room = 255;
exit:
up(&tiny->sem);
return room;
}
static void tiny_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
{
pr_info("%s\n", __func__);
}
static int tiny_install(struct tty_driver *driver, struct tty_struct *tty)
{
int retval = -ENOMEM;
pr_info("%s\n", __func__);
tty->port = kmalloc(sizeof *tty->port, GFP_KERNEL);
if (!tty->port)
goto err;
tty_init_termios(tty);
driver->ttys[0] = tty;
tty_port_init(tty->port);
tty_buffer_set_limit(tty->port, 8192);
tty_driver_kref_get(driver);
tty->count++;
return 0;
err:
pr_info("%s - err\n", __func__);
kfree(tty->port);
return retval;
}
static struct tty_operations serial_ops = {
.open = tiny_open,
.close = tiny_close,
.write = tiny_write,
.write_room = tiny_write_room,
.set_termios = tiny_set_termios,
.install = tiny_install,
};
static struct tty_driver *tiny_tty_driver;
static int __init tiny_init(void)
{
int retval;
pr_info("%s\n", __func__);
#if defined(USE_SIMULATOR)
init_waitqueue_head(&wq_thread);
thread_id = NULL;
#endif /* USE_SIMULATOR */
/* allocate the tty driver */
tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS);
if (!tiny_tty_driver)
return -ENOMEM;
/* initialize the tty driver */
tiny_tty_driver->owner = THIS_MODULE;
tiny_tty_driver->driver_name = "tiny_tty";
tiny_tty_driver->name = "tiny_tty";
tiny_tty_driver->major = TINY_TTY_MAJOR,
tiny_tty_driver->type = TTY_DRIVER_TYPE_SYSTEM,
tiny_tty_driver->subtype = SYSTEM_TYPE_CONSOLE,
tiny_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_UNNUMBERED_NODE,
tiny_tty_driver->init_termios = tty_std_termios;
tiny_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
tty_set_operations(tiny_tty_driver, &serial_ops);
/* register the tty driver */
retval = tty_register_driver(tiny_tty_driver);
if (retval) {
printk(KERN_ERR "failed to register tiny tty driver");
put_tty_driver(tiny_tty_driver);
return retval;
}
tty_register_device(tiny_tty_driver, 0, NULL);
//tiny_install(tiny_tty_driver, tiny_table[0]->tty);
return retval;
}
static void __exit tiny_exit(void)
{
pr_info("%s\n", __func__);
#if defined(USE_SIMULATOR)
if(thread_id)
{
/* shut down our timer and free the memory */
kill_pid(task_pid(thread_id), SIGTERM, 1);
wait_for_completion(&on_exit);
}
#endif /* USE_SIMULATOR */
tty_unregister_device(tiny_tty_driver, 0);
tty_unregister_driver(tiny_tty_driver);
if (tiny_serial) {
/* close the port */
while (tiny_serial->open_count)
do_close(tiny_serial);
if(tiny_serial->tty->port)
{
kfree(tiny_serial->tty->port);
tiny_serial->tty->port = NULL;
}
kfree(tiny_serial);
tiny_serial = NULL;
}
}
module_init(tiny_init);
module_exit(tiny_exit);
MODULE_AUTHOR("");
MODULE_DESCRIPTION("Tiny TTY driver");
MODULE_LICENSE("GPL");
The post is a bit dated already, but I've stumbled upon same issues and decided to share the solution:
you can turn off echo by setting appropriate flags in termios struct:
tiny_tty_driver->init_termios.c_lflag &= ~ECHO;
this is because tiny_write always returns -EINVAL, set retval = count; before return to fix this.
I am running example "Driver for the Parallel LED Board (led.c)" in the book "Essential Linux Device Drivers". One problem is that led_attach() is never called.
This link http://www.spinics.net/lists/newbies/msg38087.html talks the same topic.
"you do first register a class_device with the device name "led" (class_device_create). After that the kernel knows that there is a device called "led". When you register the led_driver, his name is also "led", so the kernel matches the two and call your attach function of your led_driver struct."
I do use "led" as device name and led_driver name, and the module is named led.ko too. However, the led_attach() is not called any way.
Here is my code:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/parport.h>
#include <asm/uaccess.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#define DEVICE_NAME "led"
static dev_t dev_number; /* Allotted device number */
static struct class *led_class; /* Class to which this device belongs */
struct cdev led_cdev; /* Associated cdev */
struct pardevice *pdev; /* Parallel port device */
/* LED open */
int led_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "(pid:%d, cmd:%s)led_open(major: %d, minor: %d)\n",
current->pid, current->comm, imajor(inode), iminor(inode));
return 0;
}
/* Write to the LED */
ssize_t led_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
{
printk(KERN_INFO "(pid:%d, cmd:%s)led_write(count: %d, *ppos: %lld)\n",
current->pid, current->comm, count, *ppos);
return count;
}
/* Release the device */
int led_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "(pid:%d, cmd:%s)led_release()\n", current->pid, current->comm);
return 0;
}
/* File Operations */
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
};
static int led_preempt(void *handle)
{
printk(KERN_INFO "(pid:%d, cmd:%s)led_preempt()\n", current->pid, current->comm);
return 1;
}
/* Parport attach method */
static void led_attach(struct parport *port)
{
printk(KERN_INFO "(pid:%d, cmd:%s)led_attach()\n", current->pid, current->comm);
/* Register the parallel LED device with parport */
pdev = parport_register_device(port, DEVICE_NAME,
led_preempt, NULL,
NULL, 0, NULL);
if (pdev == NULL) printk("Bad register\n");
}
/* Parport detach method */
static void led_detach(struct parport *port)
{
printk(KERN_INFO "(pid:%d, cmd:%s)led_detach() Port Detached\n", current->pid, current->comm);
/* Do nothing */
parport_unregister_device(pdev);
}
/* Parport driver operations */
static struct parport_driver led_driver = {
.name = DEVICE_NAME,
.attach = led_attach,
.detach = led_detach,
};
/* Driver Initialization */
int __init led_init(void)
{
printk("led_init()\n");
/* Request dynamic allocation of a device major number */
if (alloc_chrdev_region(&dev_number, 0, 1, DEVICE_NAME) < 0) {
printk(KERN_DEBUG "Can't register device\n");
return -1;
}
/* Create the led class */
led_class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(led_class)) printk("Bad class create\n");
/* Connect the file operations with the cdev */
cdev_init(&led_cdev, &led_fops);
led_cdev.owner = THIS_MODULE;
/* Connect the major/minor number to the cdev */
if (cdev_add(&led_cdev, dev_number, 1)) {
printk("Bad cdev add\n");
return 1;
}
//class_device_create(led_class, NULL, dev_number,
device_create(led_class, NULL, dev_number, NULL, DEVICE_NAME);
/* Register this driver with parport */
// int parport_register_driver (struct parport_driver * drv);
if (parport_register_driver(&led_driver)) {
printk(KERN_ERR "Bad Parport Register\n");
return -EIO;
}
return 0;
}
/* Driver Exit */
void __exit led_cleanup(void)
{
printk("led_cleanup()\n");
//void parport_unregister_driver (struct parport_driver * arg);
parport_unregister_driver(&led_driver);
//class_device_destroy(led_class, MKDEV(MAJOR(dev_number), 0));
device_destroy(led_class, dev_number);
cdev_del(&led_cdev);
class_destroy(led_class);
unregister_chrdev_region(dev_number, 1);
return;
}
module_init(led_init);
module_exit(led_cleanup);
MODULE_LICENSE("Dual BSD/GPL");
Now we know the answer based on your replies:
Your LED driver depends on parport.ko to interface your driver to generic parallel port. And parport.ko depends on parport_pc.ko to actually detect/attach a "PC styled parallel port". Since you disabled parport_pc, the parallel port will not be detected, and attach event will not be passed to you.
I am trying lessons on USB http://www.linuxforu.com/2011/12/data-transfers-to-from-usb-devices/ and stuck with the problem - while reading, usb_bulk_msg returns error 22 - Invalid argument. Write operation succeeds.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/usb.h>
#define MIN(a,b) (((a) <= (b)) ? (a) : (b))
#define BULK_EP_OUT 0x01
#define BULK_EP_IN 0x82
#define MAX_PKT_SIZE 512
static struct usb_device *device;
static struct usb_class_driver class;
static unsigned char bulk_buf[MAX_PKT_SIZE];
static int pen_open(struct inode *i, struct file *f)
{
return 0;
}
static int pen_close(struct inode *i, struct file *f)
{
return 0;
}
static ssize_t pen_read(struct file *f, char __user *buf, size_t cnt, loff_t *off)
{
int retval;
int read_cnt;
/* Read the data from the bulk endpoint */
retval = usb_bulk_msg(device, usb_rcvbulkpipe(device, BULK_EP_IN),
bulk_buf, MAX_PKT_SIZE, &read_cnt, 5000);
if (retval)
{
printk(KERN_ERR "Bulk message returned %d\n", retval);
return retval;
}
if (copy_to_user(buf, bulk_buf, MIN(cnt, read_cnt)))
{
return -EFAULT;
}
return MIN(cnt, read_cnt);
}
static ssize_t pen_write(struct file *f, const char __user *buf, size_t cnt, loff_t *off)
{
int retval;
int wrote_cnt = MIN(cnt, MAX_PKT_SIZE);
if (copy_from_user(bulk_buf, buf, MIN(cnt, MAX_PKT_SIZE)))
{
return -EFAULT;
}
/* Write the data into the bulk endpoint */
retval = usb_bulk_msg(device, usb_sndbulkpipe(device, BULK_EP_OUT),
bulk_buf, MIN(cnt, MAX_PKT_SIZE), &wrote_cnt, 5000);
if (retval)
{
printk(KERN_ERR "Bulk message returned %d\n", retval);
return retval;
}
return wrote_cnt;
}
static struct file_operations fops =
{
.open = pen_open,
.release = pen_close,
.read = pen_read,
.write = pen_write,
};
static int pen_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
int retval;
device = interface_to_usbdev(interface);
class.name = "usb/pen%d";
class.fops = &fops;
if ((retval = usb_register_dev(interface, &class)) < 0)
{
/* Something prevented us from registering this driver */
err("Not able to get a minor for this device.");
}
else
{
printk(KERN_INFO "Minor obtained: %d\n", interface->minor);
}
return retval;
}
static void pen_disconnect(struct usb_interface *interface)
{
usb_deregister_dev(interface, &class);
}
/* Table of devices that work with this driver */
static struct usb_device_id pen_table[] =
{
{ USB_DEVICE(0x058F, 0x6387) },
{} /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, pen_table);
static struct usb_driver pen_driver =
{
.name = "pen_driver",
.probe = pen_probe,
.disconnect = pen_disconnect,
.id_table = pen_table,
};
static int __init pen_init(void)
{
int result;
/* Register this driver with the USB subsystem */
if ((result = usb_register(&pen_driver)))
{
err("usb_register failed. Error number %d", result);
}
return result;
}
static void __exit pen_exit(void)
{
/* Deregister this driver with the USB subsystem */
usb_deregister(&pen_driver);
}
module_init(pen_init);
module_exit(pen_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com>");
MODULE_DESCRIPTION("USB Pen Device Driver");
There could be many possible reasons for this error but in my case it happened that the BULK_EP address was wrong. I recommend setting up your endpoint addresses in the probe function rather than hard-coding them. Feel free to refer the below code to setup bulk endpoint addresses.
static void
set_bulk_address (
struct my_device *dev,
struct usb_interface *interface)
{
struct usb_endpoint_descriptor *endpoint;
struct usb_host_interface *iface_desc;
int i;
iface_desc = interface->cur_altsetting;
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
endpoint = &iface_desc->endpoint[i].desc;
/* check for bulk endpoint */
if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
== USB_ENDPOINT_XFER_BULK){
/* bulk in */
if(endpoint->bEndpointAddress & USB_DIR_IN) {
dev->bulk_in_add = endpoint->bEndpointAddress;
dev->bulk_in_size = endpoint->wMaxPacketSize;
dev->bulk_in_buffer = kmalloc(dev->bulk_in_size,
GFP_KERNEL);
if (!dev->bulk_in_buffer)
print("Could not allocate bulk buffer");
}
/* bulk out */
else
dev->bulk_out_add = endpoint->bEndpointAddress;
}
}
}
As you may notice, I have defined my own device struct to hold the endpoint information. Here is my struct definition
struct my_device {
struct usb_device *udev; /* usb device for this device */
struct usb_interface *interface; /* interface for this device */
unsigned char minor; /* minor value */
unsigned char * bulk_in_buffer; /* the buffer to in data */
size_t bulk_in_size; /* the size of the in buffer */
__u8 bulk_in_add; /* bulk in endpoint address */
__u8 bulk_out_add; /* bulk out endpoint address */
struct kref kref; /* module references counter */
};
The error is coming because you have to mention the correct bulk endpoints as per your usb device not the ones given in the example code.
For that you can either check /sys/kernel/debug/usb/devices file or /proc/bus/usb/devices.
In the file check the section containing your device's vendorId and productId and in that section check the E segment for endpoints.
In that segment the one with (I) will be BULK_EP_IN value and the one with (O) will be the value for BULK_EP_OUT.
Let's say the buffer is allocated using a page based scheme. One way to implement mmap would be to use remap_pfn_range but LDD3 says this does not work for conventional memory. It appears we can work around this by marking the page(s) reserved using SetPageReserved so that it gets locked in memory. But isn't all kernel memory already non-swappable i.e. already reserved? Why the need to set the reserved bit explicitly?
Does this have something to do with pages allocated from HIGH_MEM?
The simplest way to map a set of pages from the kernel in your mmap method is to use the fault handler to map the pages. Basically you end up with something like:
static int my_mmap(struct file *filp, struct vm_area_struct *vma)
{
vma->vm_ops = &my_vm_ops;
return 0;
}
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = nonseekable_open,
.mmap = my_mmap,
.llseek = no_llseek,
};
(where the other file operations are whatever your module needs). Also in my_mmap you do whatever range checking etc. is needed to validate the mmap parameters.
Then the vm_ops look like:
static int my_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
vmf->page = my_page_at_index(vmf->pgoff);
get_page(vmf->page);
return 0;
}
static const struct vm_operations_struct my_vm_ops = {
.fault = my_fault
}
where you just need to figure out for a given vma / vmf passed to your fault function which page to map into userspace. This depends on exactly how your module works. For example, if you did
my_buf = vmalloc_user(MY_BUF_SIZE);
then the page you use would be something like
vmalloc_to_page(my_buf + (vmf->pgoff << PAGE_SHIFT));
But you could easily create an array and allocate a page for each entry, use kmalloc, whatever.
[just noticed that my_fault is a slightly amusing name for a function]
Minimal runnable example and userland test
Kernel module:
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h> /* min */
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/slab.h>
static const char *filename = "lkmc_mmap";
enum { BUFFER_SIZE = 4 };
struct mmap_info {
char *data;
};
/* After unmap. */
static void vm_close(struct vm_area_struct *vma)
{
pr_info("vm_close\n");
}
/* First page access. */
static vm_fault_t vm_fault(struct vm_fault *vmf)
{
struct page *page;
struct mmap_info *info;
pr_info("vm_fault\n");
info = (struct mmap_info *)vmf->vma->vm_private_data;
if (info->data) {
page = virt_to_page(info->data);
get_page(page);
vmf->page = page;
}
return 0;
}
/* After mmap. TODO vs mmap, when can this happen at a different time than mmap? */
static void vm_open(struct vm_area_struct *vma)
{
pr_info("vm_open\n");
}
static struct vm_operations_struct vm_ops =
{
.close = vm_close,
.fault = vm_fault,
.open = vm_open,
};
static int mmap(struct file *filp, struct vm_area_struct *vma)
{
pr_info("mmap\n");
vma->vm_ops = &vm_ops;
vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
vma->vm_private_data = filp->private_data;
vm_open(vma);
return 0;
}
static int open(struct inode *inode, struct file *filp)
{
struct mmap_info *info;
pr_info("open\n");
info = kmalloc(sizeof(struct mmap_info), GFP_KERNEL);
pr_info("virt_to_phys = 0x%llx\n", (unsigned long long)virt_to_phys((void *)info));
info->data = (char *)get_zeroed_page(GFP_KERNEL);
memcpy(info->data, "asdf", BUFFER_SIZE);
filp->private_data = info;
return 0;
}
static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
struct mmap_info *info;
ssize_t ret;
pr_info("read\n");
if ((size_t)BUFFER_SIZE <= *off) {
ret = 0;
} else {
info = filp->private_data;
ret = min(len, (size_t)BUFFER_SIZE - (size_t)*off);
if (copy_to_user(buf, info->data + *off, ret)) {
ret = -EFAULT;
} else {
*off += ret;
}
}
return ret;
}
static ssize_t write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
struct mmap_info *info;
pr_info("write\n");
info = filp->private_data;
if (copy_from_user(info->data, buf, min(len, (size_t)BUFFER_SIZE))) {
return -EFAULT;
} else {
return len;
}
}
static int release(struct inode *inode, struct file *filp)
{
struct mmap_info *info;
pr_info("release\n");
info = filp->private_data;
free_page((unsigned long)info->data);
kfree(info);
filp->private_data = NULL;
return 0;
}
static const struct file_operations fops = {
.mmap = mmap,
.open = open,
.release = release,
.read = read,
.write = write,
};
static int myinit(void)
{
proc_create(filename, 0, NULL, &fops);
return 0;
}
static void myexit(void)
{
remove_proc_entry(filename, NULL);
}
module_init(myinit)
module_exit(myexit)
MODULE_LICENSE("GPL");
GitHub upstream.
Userland test:
#define _XOPEN_SOURCE 700
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /* uintmax_t */
#include <string.h>
#include <sys/mman.h>
#include <unistd.h> /* sysconf */
/* Format documented at:
* https://github.com/torvalds/linux/blob/v4.9/Documentation/vm/pagemap.txt
*/
typedef struct {
uint64_t pfn : 54;
unsigned int soft_dirty : 1;
unsigned int file_page : 1;
unsigned int swapped : 1;
unsigned int present : 1;
} PagemapEntry;
/* Parse the pagemap entry for the given virtual address.
*
* #param[out] entry the parsed entry
* #param[in] pagemap_fd file descriptor to an open /proc/pid/pagemap file
* #param[in] vaddr virtual address to get entry for
* #return 0 for success, 1 for failure
*/
int pagemap_get_entry(PagemapEntry *entry, int pagemap_fd, uintptr_t vaddr)
{
size_t nread;
ssize_t ret;
uint64_t data;
nread = 0;
while (nread < sizeof(data)) {
ret = pread(pagemap_fd, ((uint8_t*)&data) + nread, sizeof(data),
(vaddr / sysconf(_SC_PAGE_SIZE)) * sizeof(data) + nread);
nread += ret;
if (ret <= 0) {
return 1;
}
}
entry->pfn = data & (((uint64_t)1 << 54) - 1);
entry->soft_dirty = (data >> 54) & 1;
entry->file_page = (data >> 61) & 1;
entry->swapped = (data >> 62) & 1;
entry->present = (data >> 63) & 1;
return 0;
}
/* Convert the given virtual address to physical using /proc/PID/pagemap.
*
* #param[out] paddr physical address
* #param[in] pid process to convert for
* #param[in] vaddr virtual address to get entry for
* #return 0 for success, 1 for failure
*/
int virt_to_phys_user(uintptr_t *paddr, pid_t pid, uintptr_t vaddr)
{
char pagemap_file[BUFSIZ];
int pagemap_fd;
snprintf(pagemap_file, sizeof(pagemap_file), "/proc/%ju/pagemap", (uintmax_t)pid);
pagemap_fd = open(pagemap_file, O_RDONLY);
if (pagemap_fd < 0) {
return 1;
}
PagemapEntry entry;
if (pagemap_get_entry(&entry, pagemap_fd, vaddr)) {
return 1;
}
close(pagemap_fd);
*paddr = (entry.pfn * sysconf(_SC_PAGE_SIZE)) + (vaddr % sysconf(_SC_PAGE_SIZE));
return 0;
}
enum { BUFFER_SIZE = 4 };
int main(int argc, char **argv)
{
int fd;
long page_size;
char *address1, *address2;
char buf[BUFFER_SIZE];
uintptr_t paddr;
if (argc < 2) {
printf("Usage: %s <mmap_file>\n", argv[0]);
return EXIT_FAILURE;
}
page_size = sysconf(_SC_PAGE_SIZE);
printf("open pathname = %s\n", argv[1]);
fd = open(argv[1], O_RDWR | O_SYNC);
if (fd < 0) {
perror("open");
assert(0);
}
printf("fd = %d\n", fd);
/* mmap twice for double fun. */
puts("mmap 1");
address1 = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (address1 == MAP_FAILED) {
perror("mmap");
assert(0);
}
puts("mmap 2");
address2 = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (address2 == MAP_FAILED) {
perror("mmap");
return EXIT_FAILURE;
}
assert(address1 != address2);
/* Read and modify memory. */
puts("access 1");
assert(!strcmp(address1, "asdf"));
/* vm_fault */
puts("access 2");
assert(!strcmp(address2, "asdf"));
/* vm_fault */
strcpy(address1, "qwer");
/* Also modified. So both virtual addresses point to the same physical address. */
assert(!strcmp(address2, "qwer"));
/* Check that the physical addresses are the same.
* They are, but TODO why virt_to_phys on kernel gives a different value? */
assert(!virt_to_phys_user(&paddr, getpid(), (uintptr_t)address1));
printf("paddr1 = 0x%jx\n", (uintmax_t)paddr);
assert(!virt_to_phys_user(&paddr, getpid(), (uintptr_t)address2));
printf("paddr2 = 0x%jx\n", (uintmax_t)paddr);
/* Check that modifications made from userland are also visible from the kernel. */
read(fd, buf, BUFFER_SIZE);
assert(!memcmp(buf, "qwer", BUFFER_SIZE));
/* Modify the data from the kernel, and check that the change is visible from userland. */
write(fd, "zxcv", 4);
assert(!strcmp(address1, "zxcv"));
assert(!strcmp(address2, "zxcv"));
/* Cleanup. */
puts("munmap 1");
if (munmap(address1, page_size)) {
perror("munmap");
assert(0);
}
puts("munmap 2");
if (munmap(address2, page_size)) {
perror("munmap");
assert(0);
}
puts("close");
close(fd);
return EXIT_SUCCESS;
}
GitHub upstream.
Tested on kernel 5.4.3.
Though the pages are reserved via a kernel driver, it is meant to be accessed via user space. As a result, the PTE (page table entries) do not know if the pfn belongs to user space or kernel space (even though they are allocated via kernel driver).
This is why they are marked with SetPageReserved.