Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I initialize the attribute group correctly for a platform driver?

Tags:

linux

driver

EDIT #4: I figured out that I shouldn't be assigning the attr_groups to the groups field in the driver field of the struct platform_driver structure. Look for the comment /* WRONGO: should not be assigned here. */ below.

I haven't figured out where I should be assigning it yet ...


My code for a platform driver manages to cause a Kernel "OOPS" when reading a sysfs attribute because of a NULL reference. I'm sure it is a simple oversight in the following code, but I can't see it:

...

static int samples_per_frame = SAMPLE_CHANNEL_COUNT;

DEVICE_INT_ATTR(samples_per_frame, S_IRUGO | S_IWUSR, samples_per_frame);

static struct attribute *attrs[] = {
    &dev_attr_samples_per_frame.attr.attr, NULL,
};

static struct attribute_group attr_group = {
    .attrs = attrs,
};

static const struct attribute_group *attr_groups[] = {
    &attr_group, NULL,
};

static struct platform_driver platform = {
    .remove = my_remove,
    .probe = my_probe,
    .driver = {
        .name = DRIVER_NAME,
        .owner = THIS_MODULE,
        .groups = attr_groups, /* WRONGO: should not be assigned here. */
        .of_match_table = of_match,
    },
};

...

which, compiles cleanly (Linux 3.12), and when the module is loaded, the install and probe run smoothly, the attribute appears in the sysfs, yet when I try to read it, a kernel "oops" ensues.

/sys/bus/platform/drivers/my_driver# ls
ff250000.my_driver   samples_per_frame  uevent
/sys/bus/platform/drivers/my_driver#
/sys/bus/platform/drivers/my_driver# cat samples_per_frame
Unable to handle kernel NULL pointer dereference at virtual address 00000000
pgd = bf1e0000
[00000000] *pgd=3f1d8831, *pte=00000000, *ppte=00000000
Internal error: Oops: 17 [#1] SMP ARM
Modules linked in: my_driver(O)
CPU: 1 PID: 194 Comm: cat Tainted: G           O 3.12.0 #6
task: bf054000 ti: bf194000 task.ti: bf194000
PC is at device_show_int+0x2c/0x38
LR is at drv_attr_show+0x2c/0x38
pc : [<80308f58>]    lr : [<8030b6fc>]    psr: a0000013
sp : bf195ee8  ip : 00000d58  fp : bf195ef4
r10: bf1d1380  r9 : 7e946c20  r8 : bf195f78
r7 : bf1d13d8  r6 : bf1d13c0  r5 : bfbc9e00  r4 : 8055d520
r3 : 00000000  r2 : 80620d58  r1 : 00001000  r0 : bf190000
Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
Control: 10c5387d  Table: 3f1e004a  DAC: 00000015
Process cat (pid: 194, stack limit = 0xbf194248)
Stack: (0xbf195ee8 to 0xbf196000)
5ee0:                   bf195f04 bf195ef8 8030b6fc 80308f38 bf195f44 bf195f08
5f00: 80166118 8030b6dc 8076ed30 bf14b000 bfbd8d80 00001000 bfbd8d88 bfbd8d80
5f20: 00001000 7e946c20 bf195f78 00000000 bf194000 00001000 bf195f74 bf195f48
5f40: 8010722c 80166084 801061e4 801118c0 00000000 00000000 bfbd8d80 7e946c20
5f60: 00000000 00001000 bf195fa4 bf195f78 8010741c 80107190 00000000 00000000
5f80: 00001000 7e946c20 00000003 00000003 8000ec88 00000000 00000000 bf195fa8
5fa0: 8000ea40 801073dc 00001000 7e946c20 00000003 7e946c20 00001000 00000000
5fc0: 00001000 7e946c20 00000003 00000003 00000001 00000001 00000001 00000003
5fe0: 00000000 7e946bfc 0001079d 76f5e1cc 40000010 00000003 00000000 00000000
[<80308f58>] (device_show_int+0x2c/0x38) from [<8030b6fc>] (drv_attr_show+0x2c/0x38)
[<8030b6fc>] (drv_attr_show+0x2c/0x38) from [<80166118>] (sysfs_read_file+0xa0/0x144)
[<80166118>] (sysfs_read_file+0xa0/0x144) from [<8010722c>] (vfs_read+0xa8/0x148)
[<8010722c>] (vfs_read+0xa8/0x148) from [<8010741c>] (SyS_read+0x4c/0x80)
[<8010741c>] (SyS_read+0x4c/0x80) from [<8000ea40>] (ret_fast_syscall+0x0/0x30)
Code: e5913010 e1a0200c e3a01a01 e3482062 (e5933000)
---[ end trace 5cdf9dda0d86a7db ]---
Segmentation fault

Why am I getting a null reference?


EDIT #1:

If I replace the DEVICE_INT_ATTR() macro with DEVICE_ATTR() and provide my own show() & store() functions, it works fine.


EDIT #2:

It appears the DEVICE_INT_ATTR() macro sets the var field to NULL. hmmm....


EDIT #3: When I create the struct dev_ext_attribute structure with DEVICE_INT_ATTR() macro, I can dump the variables. That is; the addresses of structure, the function pointers and the address of the integer I'm trying to expose in sysfs are all correct when viewed with printk() in my probe function, but when my home spun show() function looks at the structure, the struture is at a different address and the var field is NULL.

like image 399
Jamie Avatar asked Nov 10 '22 05:11

Jamie


1 Answers

The problem arose from assigning the attr_groups structure here:

static struct platform_driver platform = {
    .remove = my_remove,
    .probe = my_probe,
    .driver = {
        .name = DRIVER_NAME,
        .owner = THIS_MODULE,
        .groups = attr_groups, /* WRONGO: should not be assigned here. */
        .of_match_table = of_match,
    },
};

By doing it in struct class instead, everything works as expected:

static int __init cz_tdm_init(void)
{
    int ret;
    pr_info("MODNAME=%s", KBUILD_MODNAME);
    /* Dynamically allocate a major number and a
     * range of DEV_MINOR_NUMBER_COUNT (1) minor numbers. */
    if ((ret = alloc_chrdev_region(&first, 0, DEV_MINOR_NUMBER_COUNT, DRIVER_NAME)) < 0) {
        ret = -ENODEV;
        goto exit;
    }
    /* Add the char device to the system. */
    cdev_init(&cdev, &fops);
    if ((ret = cdev_add(&cdev, first, DEV_MINOR_NUMBER_COUNT)) < 0) {
        ret = -ENODEV;
        goto exit;
    }
    resources.cdev = 1;
    /* Create a class entry in sysfs */
    if ((class = class_create(THIS_MODULE, "coveloz")) == NULL) {
        pr_err("Couldn't create 'struct class' structure.");
        ret = -ENODEV;
        goto exit;
    }
    class->dev_groups = attr_groups; /* RIGHTO */

    /* Create the /dev/ file system entry */
    /* return value ignored: there's a 'struct class' to 'struct device' mapping */
    if (device_create(class, NULL, first, NULL, DRIVER_NAME) == NULL) {
        pr_err("Couldn't create entry in '/dev/' file system.");
        ret = -ENODEV;
        goto exit;
    }
like image 110
Jamie Avatar answered Nov 15 '22 04:11

Jamie