how to get struct device's private data in the Linux's chrdev ->open() function?

648 Views Asked by At

I'm a beginnig learner of linux driver, so far I've studied how to write an basic char device driver and platform driver. I'm pricticing on the led example, I want to improve it from basic char device driver model to platform driver model.

In the earlier practice, I define a global int gpio_num[MAX_LED_NUM] array to keep the led's gpio. It's easy because I can indentify the led by device's minor number, and operate the corresponding led by referencing gpio_num[minor];

But in this improvement, I don't want to use global array to keep led's gpio because I can't predict how many leds on the board. So I malloc a structure for every platform device in the probe function to keep their own gpio, and call device_create() to create device node. It seems good so far because how many leds are there, there are how many structures and device nodes in the /dev directory, and there is no global variables.

In order to seperate led operation functions from char device driver, I define the led operaions functions in the platform driver part(driver.c) and pass the function sets to the char device driver part(leds.c). for example , I define the alpha_led_init(struct drv_priv *priv) in the driver.c, and call it from char device's open function(in leds.c) .

in order to call alpha_led_init(struct drv_priv *priv), the open function needs the parameter *priv(the private data of platform device which contains led_gpio). I've pass the private data to the char device by using device_create()'s third parameter. But how can I get it from the open function ? I can't get the struct device *pdev in the open function, so I can't call dev_get_drvdata(pdev) to get the platform device's private data , so there's no way to call alpha_led_init(struct drv_priv *priv).

Is my program model very bad? Any good way to pass platform device's private data to char device ? Any help or advice would be appreciating.

Below is my practicing code, for simplicity, some header files're omitted.

alpha_led.h

#ifndef __ALPHA_LED_H__

#define __ALPHA_LED_H__

#define LED_OFF     (1)
#define LED_ON      (0)
#define LED_MAX_NUM (10)

struct drv_priv
{
    int led_gpio;
};

struct alpha_led_operations
{
    int inited;
    int (*alpha_led_init)(struct drv_priv *pdev);
};

#endif

driver.c

#include "alpha_led.h"

static int led_count;

static int alpha_led_init(struct drv_priv *priv)
{
    int err;
    char name[64];

    if(!priv)
        return -1;

    memset(name, 0, sizeof(name));
    snprintf(name, sizeof(name), "alpha-led-pin-%d", priv->led_gpio);
    err = gpio_request(priv->led_gpio, name);
    if(err)
        return -1;

    err = gpio_direction_output(priv->led_gpio, LED_OFF);
    if(err) {
        gpio_free(priv->led_gpio);
        return -1;
    }

    return 0;
}

static int alpha_led_probe(struct platform_device *pdev)
{
    int err, gpio;
    const char *status = NULL;
    struct drv_priv *priv = NULL;
    struct device_node *np = pdev->dev.of_node;

    if(!np)
        return -1;

    err = of_property_read_string(np, "status", &status);
    if(err || (strcmp(status, "okay") != 0))
        return -1;

    gpio = of_get_named_gpio(np, "led-gpio", 0);
    if(gpio < 0)
        return -1;

    // I malloc a drv_priv structure for every platform device to keep their private data
    priv = devm_kzalloc(&pdev->dev, sizeof(struct drv_priv), GFP_KERNEL);
    if(!priv)
        return -ENOMEM;
    platform_set_drvdata(pdev, priv);

    // for every platform device, the gpio number is their private data.
    priv->led_gpio = gpio;

    // I call self-defined function in leds.c to create device node in /dev directory
    // and pass the platform device's private data(priv) to the device_create()
    return create_led_device_node(led_count++, np->name, priv);
}

static int alpha_led_remove(struct platform_device *pdev)
{
    // get the platform device's private data
    struct drv_priv *priv = platform_get_drvdata(pdev);

    gpio_free(priv->led_gpio);
}

static const struct of_device_id alpha_led_of_match[] = {
    { .compatible = "alientek-alpha,led" },
    {}
};

static struct platform_driver alpha_led_driver = {
    .probe = alpha_led_probe,
    .remove = alpha_led_remove,
    .driver = {
        .name = "alpha-led",
        .of_match_table = alpha_led_of_match,
    }
};

static int __init platform_driver_led_init(void)
{
    int rc;
    struct alpha_led_operations *ops;

    rc = platform_driver_register(&alpha_led_driver);

    // pass the lower led control functions to leds.c
    ops = get_alpha_led_ops();
    ops->alpha_led_init = alpha_led_init;
    ops->inited = 1;

    return 0;
}

static void __exit platform_driver_led_exit(void)
{
    platform_driver_unregister(&alpha_led_driver);
}

module_init(platform_driver_led_init);
module_exit(platform_driver_led_exit);

MODULE_AUTHOR("David");
MODULE_LICENSE("GPL");

leds.c

#include "alpha_led.h"

#define LED_DEV_NAME ("alpha-led")
#define LED_CLASS_NAME ("alpha-led-class")

static int led_major;
static struct cdev led_cdev;
static struct class *led_class;

static int led_open(struct inode *inode, struct file *filp);
static int led_close(struct inode *inode, struct file *filp);

static struct alpha_led_operations alpha_led_ops;

static const struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_close,
};

static int led_open(struct inode *inode, struct file *filp)
{
    int err, minor;

    if(!inode || !filp)
        return -1;

    if(!alpha_led_ops.inited)
        return -1;
    if(!alpha_led_ops.alpha_led_init)
        return -1;

    //Question: here I want to call alpha_led_init(struct drv_priv *priv) defined in the driver.c
    //But how can I get the parameter priv ? I know I have set it in the device_create(), but how can I fetch it here?
    //Or Am writing a very bad platform driver model?
    return alpha_led_ops.alpha_led_init(...);
}


static int led_close(struct inode *inode, struct file *filp)
{
    return 0;
}

static int __init chrdev_led_init(void)
{
    dev_t devid;
    int i, rc, major, minor;
    struct device *pdev;

    if (led_major) {
        devid = MKDEV(led_major, 0);
        rc = register_chrdev_region(devid, LED_MAX_NUM, LED_DEV_NAME);
    } else {
        rc = alloc_chrdev_region(&devid, 0, LED_MAX_NUM, LED_DEV_NAME);
        led_major = MAJOR(devid);
    }
    if(rc < 0)
        goto chrdev_failed;

    
    cdev_init(&led_cdev, &led_fops);
    rc = cdev_add(&led_cdev, devid, LED_MAX_NUM);
    if(rc < 0)
        goto cdev_failed;

    led_class = class_create(THIS_MODULE, LED_CLASS_NAME);
    if(IS_ERR(led_class))
        goto class_failed;

    return 0;

class_failed:
    cdev_del(&led_cdev);
cdev_failed:
    unregister_chrdev_region(devid, LED_MAX_NUM);
chrdev_failed:
    return -1;
}

static void __exit chrdev_led_exit(void)
{
    class_destroy(led_class);
    cdev_del(&led_cdev);
    unregister_chrdev_region(MKDEV(led_major, 0), LED_MAX_NUM);
}

int create_led_device_node(int minor, const char *name, void *priv)
{
    struct device *dev = NULL;

    if(minor >= LED_MAX_NUM)
        return NULL;
    
    //device_create take the platform device's private data(priv) as it's own private data.
    if(name)
        dev = device_create(led_class, NULL, MKDEV(led_major, minor), priv, "%s", name);
    else
        dev = device_create(led_class, NULL, MKDEV(led_major, minor), priv, "led-%d", minor);

    if(!dev)
        return -1;

    return 0;
}

void destroy_led_device_node(int minor)
{
    device_destroy(led_class, MKDEV(led_major, minor));
}

struct alpha_led_operations * get_alpha_led_ops(void)
{
    return &alpha_led_ops;
}

EXPORT_SYMBOL(create_led_device_node);
EXPORT_SYMBOL(destroy_led_device_node);
EXPORT_SYMBOL(get_alpha_led_ops);

module_init(chrdev_led_init);
module_exit(chrdev_led_exit);

MODULE_AUTHOR("David");
MODULE_LICENSE("GPL");
0

There are 0 best solutions below