Question

I have been writing a lcd kernel driver for a LCD module. All was going well, I can write to the display, create a /dev/lcd node that I can write into and it will display the results on the screen. I thought using the llseek fops callback to position the cursor on the lcd would be good, this way I could use rewind fseek etc. However it is not working as I expected, below is a summary of what I am seeing:

The relevant lines of code from the driver side are:

loff_t lcd_llseek(struct file *filp, loff_t off, int whence)
{
    switch (whence) {
        case 0: // SEEK_SET
            if (off > 4*LINE_LENGTH || off < 0) {
                printk(KERN_ERR "unsupported SEEK_SET offset %llx\n", off);
                return -EINVAL;
            }
            lcd_gotoxy(&lcd, off, 0, WHENCE_ABS);
            break;
        case 1: // SEEK_CUR 
            if (off > 4*LINE_LENGTH || off < -4*LINE_LENGTH) {
                printk(KERN_ERR "unsupported SEEK_CUR offset %llx\n", off);
                return -EINVAL;
            }
            lcd_gotoxy(&lcd, off, 0, WHENCE_REL);
            break;
        case 2: // SEEK_END (not supported, hence fall though)
        default:
            // how did we get here !
            printk(KERN_ERR "unsupported seek operation\n");
            return -EINVAL;
    }
    filp->f_pos = lcd.pos;
    printk(KERN_INFO "lcd_llseek complete\n");
    return lcd.pos;
}

int lcd_open(struct inode *inode, struct file *filp)
{
    if (!atomic_dec_and_test(&lcd_available)) {
        atomic_inc(&lcd_available);
        return -EBUSY; // already open
    }

    return 0;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .write = lcd_write,
    .llseek = lcd_llseek,
    .open = lcd_open,
    .release = lcd_release,
};

int lcd_init(void)
{
    ...

    // allocate a new dev number (this can be dynamic or
    // static if passed in as a module param)
    if (major) {
        devno = MKDEV(major, 0);
        ret = register_chrdev_region(devno, 1, MODULE_NAME);
    } else {
        ret = alloc_chrdev_region(&devno, 0, 1, MODULE_NAME);
        major = MAJOR(devno);
    }
    if (ret < 0) {
        printk(KERN_ERR "alloc_chrdev_region failed\n");
        goto fail;
    }

    // create a dummy class for the lcd
    cl = class_create(THIS_MODULE, "lcd");
    if (IS_ERR(cl)) {
        printk(KERN_ERR "class_simple_create for class lcd failed\n");
        goto fail1;
    }

    // create cdev interface
    cdev_init(&cdev, &fops);
    cdev.owner = THIS_MODULE;
    ret = cdev_add(&cdev, devno, 1);
    if (ret) {
        printk(KERN_ERR "cdev_add failed\n");
        goto fail2;
    }

    // create /sys/lcd/fplcd/dev so udev will add our device to /dev/fplcd
    device = device_create(cl, NULL, devno, NULL, "lcd");
    if (IS_ERR(device)) {
        printk(KERN_ERR "device_create for fplcd failed\n");
        goto fail3;
    }
    ...
}

To test the lseek call I have the following unit test:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#define log(msg, ...) fprintf(stdout, __FILE__ ":%s():[%d]:" msg, __func__, __LINE__, __VA_ARGS__)

int lcd;

void test(void)
{
    int k;

    // a lot of hello's
    log("hello world test\n",1);
    if (lseek(lcd, 0, SEEK_CUR) == -1) {
        log("failed to seek\n", 1);
    }
}

int main(int argc, char **argv)
{
    lcd = open("/dev/lcd", O_WRONLY);
    if (lcd == -1) {
        perror("unable to open lcd");
        exit(EXIT_FAILURE);
    }

    test();

    close(lcd);
    return 0;
}

The files are cross compiled like so:

~/Workspace/ts4x00/lcd-module$ cat Makefile 
obj-m += fls_lcd.o

all:
    make -C $(KPATH) M=$(PWD) modules
    $(CROSS_COMPILE)gcc -g -fPIC $(CFLAGS) lcd_unit_test.c -o lcd_unit_test

clean:
    make -C $(KPATH) M=$(PWD) clean
    rm -rf lcd_unit_test
~/Workspace/ts4x00/lcd-module$ make CFLAGS+="-march=armv4 -ffunction-sections -fdata-sections"
make -C ~/Workspace/ts4x00/linux-2.6.29 M=~/Workspace/ts4x00/lcd-module modules
make[1]: Entering directory `~/Workspace/ts4x00/linux-2.6.29'
  CC [M]  ~/Workspace/ts4x00/lcd-module/fls_lcd.o
~/Workspace/ts4x00/lcd-module/fls_lcd.c:443: warning: 'lcd_entry_mode' defined but not used
  Building modules, stage 2.
  MODPOST 1 modules
  CC      ~/Workspace/ts4x00/lcd-module/fls_lcd.mod.o
  LD [M]  ~/Workspace/ts4x00/lcd-module/fls_lcd.ko
make[1]: Leaving directory `~/Workspace/ts4x00/linux-2.6.29'
~/Workspace/ts4x00/arm-2008q3/bin/arm-none-linux-gnueabi-gcc -g -fPIC -march=armv4 -ffunction-sections -fdata-sections lcd_unit_test.c -o lcd_unit_test

This is the output of running the driver with the unit test is:

root@ts4700:~/devel# insmod ./fls_lcd.ko 
root@ts4700:~/devel# ./lcd_unit_test 
lcd_unit_test.c:test():[61]:hello world test
lcd_unit_test.c:test():[63]:failed to seek
root@ts4700:~/devel# dmesg
FLS LCD driver started
unsupported SEEK_SET offset bf0a573c

I cannot figure out why the parameters are being mucked up so badly on the kernel side, I tried to SEEK_CUR to position 0 and in the driver I get a SEEK_SET (no matter what I put in the unit test) and a crazy big number for off?

Does anyone know what is going on please ?

btw I am compiling for kernel 2.6.29 on a arm dev kit

Was it helpful?

Solution

OK sorry guys after trying to debug this all last night it comes down to compiling against the wrong kernel (I had KPATH left to a different config of the kernel than was on the sdcard)

sorry for wasting everyones time, but hopefully if someone is seeing what looks like a crazy stack in their kernel driver this might set them straight.

oh and thanks for all the help :)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top