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");