blob: 441b4d9af92571da20d640e1f0f3dd543da3a57c [file] [log] [blame]
/*
* Driver data test interface
*
* Copyright (C) 2016 Luis R. Rodriguez <mcgrof@kernel.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of copyleft-next (version 0.3.1 or later) as published
* at http://copyleft-next.org/.
*
* This module provides an interface to trigger and test the driver data API
* through a series of configurations and a few triggers. This driver
* lacks any extra dependencies, and will not normally be loaded by the
* system unless explicitly requested by name. You can also build this
* driver into your kernel.
*
* Although all configurations are already written for and will be supported
* for this test driver, ideally we should strive to see what mechanisms we
* can put in place to instead automatically generate this sort of test
* interface, test cases, and infer results. Its a simple enough interface that
* should hopefully enable more exploring in this area.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/completion.h>
#include <linux/drvdata.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/async.h>
#include <linux/delay.h>
#include <linux/vmalloc.h>
/* Used for the fallback default to test against */
#define TEST_DRVDATA "test-drvdata.bin"
/*
* For device allocation / registration
*/
static DEFINE_MUTEX(reg_dev_mutex);
static LIST_HEAD(reg_test_devs);
/*
* num_test_devs actually represents the *next* ID of the next
* device we will allow to create.
*/
int num_test_devs;
/**
* test_config - represents configuration for the drvdata API
*
* @name: the name of the primary drvdata file to look for
* @default_name: a fallback example, used to test the optional callback
* mechanism.
* @async: true if you want to trigger an async request. This will use
* drvdata_request_async(). If false the synchronous call will
* be used, drvdata_request().
* @optional: whether or not the drvdata is optional refer to the
* struct drvdata_reg_params @optional field for more information.
* @keep: whether or not we wish to free the drvdata on our own, refer to
* the struct drvdata_req_params @keep field for more information.
* @enable_opt_cb: whether or not the optional callback should be set
* on a trigger. There is no equivalent setting on the struct
* drvdata_req_params as this is implementation specific, and in
* in drvdata API its explicit if you had defined an optional call
* back for your descriptor with either DRVDATA_SYNC_OPT_CB() or
* DRVDATA_ASYNC_OPT_CB(). Since the params are in a const we have
* no option but to use a flag and two const structs to decide which
* one we should use.
* @test_result: a test may use this to collect the result from the call
* of the drvdata_request_async() or drvdata_request() calls used in their
* tests. Note that for async calls this typically will be a successful
* result (0) unless of course you've used bogus parameters, or the system
* is out of memory. Tests against the callbacks can only be
* implementation specific, so we don't test for that for now but it may
* make sense to build tests cases against a series of semantically
* similar family of callbacks that generally represents usage in the
* kernel. Synchronous calls return bogus error checks against the
* parameters as well, but also return the result of the work from the
* callbacks. You can therefore rely on sync calls if you really want to
* test for the callback results as well. Errors you can expect:
*
* API specific:
*
* 0: success for sync, for async it means request was sent
* -EINVAL: invalid parameters or request
* -ENOENT: files not found
*
* System environment:
*
* -ENOMEM: memory pressure on system
* -ENODEV: out of number of devices to test
*
* The ordering of elements in this struct must match the exact order of the
* elements in the ATTRIBUTE_GROUPS(test_dev_config), this is done to know
* what corresponding field each device attribute configuration entry maps
* to what struct member on test_alloc_dev_attrs().
*/
struct test_config {
char *name;
char *default_name;
bool async;
bool optional;
bool keep;
bool enable_opt_cb;
int test_result;
};
/**
* test_drvdata_private - private device driver drvdata representation
*
* @size: size of the data copied, in bytes
* @data: the actual data we copied over from drvdata
* @written: true if a callback managed to copy data over to the device
* successfully. Since different callbacks are used for this purpose
* having the data written does not necessarily mean a test case
* completed successfully. Each tests case has its own specific
* goals.
*
* Private representation of buffer where we put the device system data */
struct test_drvdata_private {
size_t size;
u8 *data;
bool written;
};
/**
* drvdata_test_device - test device to help test drvdata
*
* @dev_idx: unique ID for test device
* @config: this keeps the device's own configuration. Instead of creating
* different triggers for all possible test cases we can think of in
* kernel, we expose a set possible device attributes for tuning the
* drvdata API and we to let you tune them in userspace. We then just
* provide one trigger.
* @test_drvdata: internal private representation of a storage area
* a driver might typically use to stuff firmware / drvdata.
* @misc_dev: we use a misc device under the hood
* @dev: pointer to misc_dev's own struct device
* @drvdata_mutex: for access into the @drvdata, the fake storage location for
* the system data we copy.
* @config_mutex:
* @trigger_mutex: all triggers are mutually exclusive when testing. To help
* enable testing you can create a different device, each device has its
* own set of protections, mimicking real devices.
* list: needed to be part of the reg_test_devs
*/
struct drvdata_test_device {
int dev_idx;
struct test_config config;
struct test_drvdata_private test_drvdata;
struct miscdevice misc_dev;
struct device *dev;
struct mutex drvdata_mutex;
struct mutex config_mutex;
struct mutex trigger_mutex;
struct list_head list;
};
static struct miscdevice *dev_to_misc_dev(struct device *dev)
{
return dev_get_drvdata(dev);
}
static
struct drvdata_test_device *misc_dev_to_test_dev(struct miscdevice *misc_dev)
{
return container_of(misc_dev, struct drvdata_test_device, misc_dev);
}
static struct drvdata_test_device *dev_to_test_dev(struct device *dev)
{
struct miscdevice *misc_dev;
misc_dev = dev_to_misc_dev(dev);
return misc_dev_to_test_dev(misc_dev);
}
static ssize_t test_fw_misc_read(struct file *f, char __user *buf,
size_t size, loff_t *offset)
{
struct miscdevice *misc_dev = f->private_data;
struct drvdata_test_device *test_dev = misc_dev_to_test_dev(misc_dev);
struct test_drvdata_private *test_drvdata = &test_dev->test_drvdata;
ssize_t rc = 0;
mutex_lock(&test_dev->drvdata_mutex);
if (test_drvdata->written)
rc = simple_read_from_buffer(buf, size, offset,
test_drvdata->data,
test_drvdata->size);
mutex_unlock(&test_dev->drvdata_mutex);
return rc;
}
static const struct file_operations test_fw_fops = {
.owner = THIS_MODULE,
.read = test_fw_misc_read,
};
static void free_test_drvdata(struct test_drvdata_private *test_drvdata)
{
kfree(test_drvdata->data);
test_drvdata->data = NULL;
test_drvdata->size = 0;
test_drvdata->written = false;
}
static int test_load_drvdata(struct drvdata_test_device *test_dev,
const struct drvdata *drvdata)
{
struct test_drvdata_private *test_drvdata = &test_dev->test_drvdata;
int ret = 0;
if (!drvdata)
return -ENOENT;
mutex_lock(&test_dev->drvdata_mutex);
free_test_drvdata(test_drvdata);
test_drvdata->data = kzalloc(drvdata->size, GFP_KERNEL);
if (!test_drvdata->data) {
ret = -ENOMEM;
goto out;
}
memcpy(test_drvdata->data, drvdata->data, drvdata->size);
test_drvdata->size = drvdata->size;
test_drvdata->written = true;
dev_info(test_dev->dev, "loaded: %zu\n", test_drvdata->size);
out:
mutex_unlock(&test_dev->drvdata_mutex);
return ret;
}
static int sync_found_cb(void *context, const struct drvdata *drvdata)
{
struct drvdata_test_device *test_dev = context;
int ret;
ret = test_load_drvdata(test_dev, drvdata);
if (ret)
dev_info(test_dev->dev, "unable to write drvdata: %d\n", ret);
return ret;
}
static ssize_t config_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
int len = 0;
mutex_lock(&test_dev->config_mutex);
len += snprintf(buf, PAGE_SIZE,
"Custom trigger configuration for: %s\n",
dev_name(dev));
if (config->default_name)
len += snprintf(buf+len, PAGE_SIZE,
"default name:\t%s\n",
config->default_name);
else
len += snprintf(buf+len, PAGE_SIZE,
"default name:\tEMTPY\n");
if (config->name)
len += snprintf(buf+len, PAGE_SIZE,
"name:\t\t%s\n", config->name);
else
len += snprintf(buf+len, PAGE_SIZE,
"name:\t\tEMPTY\n");
len += snprintf(buf+len, PAGE_SIZE,
"type:\t\t%s\n",
config->async ? "async" : "sync");
len += snprintf(buf+len, PAGE_SIZE,
"optional:\t%s\n",
config->optional ? "true" : "false");
len += snprintf(buf+len, PAGE_SIZE,
"enable_opt_cb:\t%s\n",
config->enable_opt_cb ? "true" : "false");
len += snprintf(buf+len, PAGE_SIZE,
"keep:\t\t%s\n",
config->keep ? "true" : "false");
mutex_unlock(&test_dev->config_mutex);
return len;
}
static DEVICE_ATTR_RO(config);
static int config_load_data(struct drvdata_test_device *test_dev,
const struct drvdata *drvdata)
{
struct test_config *config = &test_dev->config;
int ret;
ret = test_load_drvdata(test_dev, drvdata);
if (ret) {
if (!config->optional)
dev_info(test_dev->dev, "unable to write drvdata\n");
}
if (config->keep) {
release_drvdata(drvdata);
drvdata = NULL;
}
return ret;
}
static int config_req_default(struct drvdata_test_device *test_dev)
{
struct test_config *config = &test_dev->config;
int rc;
/*
* Note: we don't chain config->optional here, we make this
* fallback file a requirement. It doesn't make much sense to test
* chaining further as the optional callback is implementation
* specific, by testing it once we test it for any possible
* chains. We provide this as an example of what people can do
* and use a default non-optional fallback.
*/
const struct drvdata_req_params req_params = {
DRVDATA_DEFAULT_SYNC(sync_found_cb, test_dev),
};
if (config->async)
dev_info(test_dev->dev,
"loading default fallback '%s' using sync request now\n",
config->default_name);
else
dev_info(test_dev->dev,
"loading default fallback '%s'\n",
config->default_name);
rc = drvdata_request(config->default_name,
&req_params, test_dev->dev);
if (rc)
dev_info(test_dev->dev,
"load of default '%s' failed: %d\n",
config->default_name, rc);
return rc;
}
/*
* This is the default sync fallback callback, as a fallback this
* then uses a sync request.
*/
static int config_sync_req_default_cb(void *context)
{
struct drvdata_test_device *test_dev = context;
int rc;
rc = config_req_default(test_dev);
return rc;
/* Leave all the error checking for the main caller */
}
/*
* This is the default config->async fallback callback, as a fallback this
* then uses a sync request.
*/
static void config_async_req_default_cb(void *context)
{
struct drvdata_test_device *test_dev = context;
config_req_default(test_dev);
/* Leave all the error checking for the main caller */
}
static int config_sync_req_cb(void *context,
const struct drvdata *drvdata)
{
struct drvdata_test_device *test_dev = context;
return config_load_data(test_dev, drvdata);
}
static int trigger_config_sync(struct drvdata_test_device *test_dev)
{
struct test_config *config = &test_dev->config;
int rc;
const struct drvdata_req_params req_params_default = {
DRVDATA_DEFAULT_SYNC(config_sync_req_cb, test_dev),
.optional = config->optional,
.keep = config->keep,
};
const struct drvdata_req_params req_params_opt_cb = {
DRVDATA_DEFAULT_SYNC(config_sync_req_cb, test_dev),
DRVDATA_SYNC_OPT_CB(config_sync_req_default_cb, test_dev),
.optional = config->optional,
.keep = config->keep,
};
const struct drvdata_req_params *req_params;
if (config->enable_opt_cb)
req_params = &req_params_opt_cb;
else
req_params= &req_params_default;
rc = drvdata_request(config->name, req_params, test_dev->dev);
if (rc)
dev_err(test_dev->dev, "sync load of '%s' failed: %d\n",
config->name, rc);
return rc;
}
static void config_async_req_cb(const struct drvdata *drvdata,
void *context)
{
struct drvdata_test_device *test_dev = context;
config_load_data(test_dev, drvdata);
}
static int trigger_config_async(struct drvdata_test_device *test_dev)
{
struct test_config *config = &test_dev->config;
int rc;
const struct drvdata_req_params req_params_default = {
DRVDATA_DEFAULT_ASYNC(config_async_req_cb, test_dev),
.sync_reqs.mode = config->async ?
DRVDATA_ASYNC : DRVDATA_SYNC,
.optional = config->optional,
.keep = config->keep,
};
const struct drvdata_req_params req_params_opt_cb = {
DRVDATA_DEFAULT_ASYNC(config_async_req_cb, test_dev),
DRVDATA_ASYNC_OPT_CB(config_async_req_default_cb, test_dev),
.sync_reqs.mode = config->async ?
DRVDATA_ASYNC : DRVDATA_SYNC,
.optional = config->optional,
.keep = config->keep,
};
const struct drvdata_req_params *req_params;
async_cookie_t async_cookie;
if (config->enable_opt_cb)
req_params = &req_params_opt_cb;
else
req_params = &req_params_default;
rc = drvdata_request_async(config->name, req_params,
test_dev->dev, &async_cookie);
if (rc) {
dev_err(test_dev->dev, "async load of '%s' failed: %d\n",
config->name, rc);
goto out;
}
drvdata_synchronize_request(async_cookie);
out:
return rc;
}
static ssize_t
trigger_config_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_drvdata_private *test_drvdata = &test_dev->test_drvdata;
struct test_config *config = &test_dev->config;
int rc;
mutex_lock(&test_dev->trigger_mutex);
mutex_lock(&test_dev->config_mutex);
dev_info(dev, "loading '%s'\n", config->name);
if (config->async)
rc = trigger_config_async(test_dev);
else
rc = trigger_config_sync(test_dev);
config->test_result = rc;
if (rc)
goto out;
if (test_drvdata->written) {
dev_info(dev, "loaded: %zu\n", test_drvdata->size);
rc = count;
} else {
dev_err(dev, "failed to load firmware\n");
rc = -ENODEV;
}
out:
mutex_unlock(&test_dev->config_mutex);
mutex_unlock(&test_dev->trigger_mutex);
return rc;
}
static DEVICE_ATTR_WO(trigger_config);
/*
* XXX: move to kstrncpy() once merged.
*
* Users should use kfree_const() when freeing these.
*/
static int __kstrncpy(char **dst, const char *name, size_t count, gfp_t gfp)
{
*dst = kstrndup(name, count, gfp);
if (!*dst)
return -ENOSPC;
return count;
}
static int config_copy_name(struct test_config *config,
const char *name,
size_t count)
{
return __kstrncpy(&config->name, name, count, GFP_KERNEL);
}
static int config_copy_default_name(struct test_config *config,
const char *name,
size_t count)
{
return __kstrncpy(&config->default_name, name, count, GFP_KERNEL);
}
static void __drvdata_config_free(struct test_config *config)
{
kfree_const(config->name);
config->name = NULL;
kfree_const(config->default_name);
config->default_name = NULL;
}
static void drvdata_config_free(struct drvdata_test_device *test_dev)
{
struct test_config *config = &test_dev->config;
mutex_lock(&test_dev->config_mutex);
__drvdata_config_free(config);
mutex_unlock(&test_dev->config_mutex);
}
static int __drvdata_config_init(struct test_config *config)
{
int ret;
ret = config_copy_name(config, TEST_DRVDATA, strlen(TEST_DRVDATA));
if (ret < 0)
goto out;
ret = config_copy_default_name(config, TEST_DRVDATA,
strlen(TEST_DRVDATA));
if (ret < 0)
goto out;
config->async = false;
config->optional = false;
config->keep = false;
config->enable_opt_cb = false;
config->test_result = 0;
out:
return ret;
}
int drvdata_config_init(struct drvdata_test_device *test_dev)
{
struct test_config *config = &test_dev->config;
int ret;
mutex_lock(&test_dev->config_mutex);
ret = __drvdata_config_init(config);
mutex_unlock(&test_dev->config_mutex);
return ret;
}
static ssize_t config_name_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
int rc;
mutex_lock(&test_dev->config_mutex);
rc = config_copy_name(config, buf, count);
mutex_unlock(&test_dev->config_mutex);
return rc;
}
/*
* As per sysfs_kf_seq_show() the buf is max PAGE_SIZE.
*/
static ssize_t config_test_show_str(struct mutex *config_mutex,
char *dst,
char *src)
{
int len;
mutex_lock(config_mutex);
len = snprintf(dst, PAGE_SIZE, "%s\n", src);
mutex_unlock(config_mutex);
return len;
}
static ssize_t config_name_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
return config_test_show_str(&test_dev->config_mutex, buf,
config->name);
}
static DEVICE_ATTR(config_name, 0644, config_name_show, config_name_store);
static ssize_t config_default_name_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
int rc;
mutex_lock(&test_dev->config_mutex);
rc = config_copy_default_name(config, buf, count);
mutex_unlock(&test_dev->config_mutex);
return rc;
}
static ssize_t config_default_name_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
return config_test_show_str(&test_dev->config_mutex, buf,
config->default_name);
}
static DEVICE_ATTR(config_default_name, 0644, config_default_name_show,
config_default_name_store);
static ssize_t reset_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
int ret;
mutex_lock(&test_dev->trigger_mutex);
mutex_lock(&test_dev->drvdata_mutex);
free_test_drvdata(&test_dev->test_drvdata);
mutex_unlock(&test_dev->drvdata_mutex);
mutex_lock(&test_dev->config_mutex);
__drvdata_config_free(config);
ret = __drvdata_config_init(config);
if (ret < 0) {
ret = -ENOMEM;
dev_err(dev, "could not alloc settings for config trigger: %d\n",
ret);
goto out;
}
dev_info(dev, "reset\n");
ret = count;
out:
mutex_unlock(&test_dev->config_mutex);
mutex_unlock(&test_dev->trigger_mutex);
return ret;
}
static DEVICE_ATTR_WO(reset);
/*
* XXX: consider a soluton to generalize drivers to specify their own
* mutex, adding it to dev core after this gets merged. This may not
* be important for once-in-a-while system tuning parameters, but if
* we want to enable fuzz testing, this is really important.
*
* It may make sense to just have a "struct device configuration mutex"
* for these sorts of things, although there is difficulty in that we'd
* need dynamically allocated attributes for that. Its the same reason
* why we ended up not using the provided standard device attribute
* bool, int interfaces.
*/
static int test_dev_config_update_bool(struct drvdata_test_device *test_dev,
const char *buf, size_t size,
bool *config)
{
int ret;
mutex_lock(&test_dev->config_mutex);
if (strtobool(buf, config) < 0)
ret = -EINVAL;
else
ret = size;
mutex_unlock(&test_dev->config_mutex);
return ret;
}
static ssize_t test_dev_config_show_bool(struct drvdata_test_device *test_dev,
char *buf,
bool config)
{
bool val;
mutex_lock(&test_dev->config_mutex);
val = config;
mutex_unlock(&test_dev->config_mutex);
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
static int test_dev_config_update_int(struct drvdata_test_device *test_dev,
const char *buf, size_t size,
int *config)
{
char *end;
long new = simple_strtol(buf, &end, 0);
if (end == buf || new > INT_MAX || new < INT_MIN)
return -EINVAL;
mutex_lock(&test_dev->config_mutex);
*(int *)config = new;
mutex_unlock(&test_dev->config_mutex);
/* Always return full write size even if we didn't consume all */
return size;
}
static ssize_t test_dev_config_show_int(struct drvdata_test_device *test_dev,
char *buf,
int config)
{
int val;
mutex_lock(&test_dev->config_mutex);
val = config;
mutex_unlock(&test_dev->config_mutex);
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t config_async_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
return test_dev_config_update_bool(test_dev, buf, count,
&config->async);
}
static ssize_t config_async_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
return test_dev_config_show_bool(test_dev, buf, config->async);
}
static DEVICE_ATTR(config_async, 0644, config_async_show, config_async_store);
static ssize_t config_optional_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
return test_dev_config_update_bool(test_dev, buf, count,
&config->optional);
}
static ssize_t config_optional_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
return test_dev_config_show_bool(test_dev, buf, config->optional);
}
static DEVICE_ATTR(config_optional, 0644, config_optional_show,
config_optional_store);
static ssize_t config_keep_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
return test_dev_config_update_bool(test_dev, buf, count,
&config->keep);
}
static ssize_t config_keep_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
return test_dev_config_show_bool(test_dev, buf, config->keep);
}
static DEVICE_ATTR(config_keep, 0644, config_keep_show, config_keep_store);
static ssize_t config_enable_opt_cb_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
return test_dev_config_update_bool(test_dev, buf, count,
&config->enable_opt_cb);
}
static ssize_t config_enable_opt_cb_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
return test_dev_config_show_bool(test_dev, buf,
config->enable_opt_cb);
}
static DEVICE_ATTR(config_enable_opt_cb, 0644,
config_enable_opt_cb_show,
config_enable_opt_cb_store);
static ssize_t test_result_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
return test_dev_config_update_int(test_dev, buf, count,
&config->test_result);
}
static ssize_t test_result_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
struct test_config *config = &test_dev->config;
return test_dev_config_show_int(test_dev, buf, config->test_result);
}
static DEVICE_ATTR(test_result, 0644, test_result_show, test_result_store);
#define DRVDATA_DEV_ATTR(name) &dev_attr_##name.attr
static struct attribute *test_dev_attrs[] = {
DRVDATA_DEV_ATTR(trigger_config),
DRVDATA_DEV_ATTR(config),
DRVDATA_DEV_ATTR(reset),
DRVDATA_DEV_ATTR(config_name),
DRVDATA_DEV_ATTR(config_default_name),
DRVDATA_DEV_ATTR(config_async),
DRVDATA_DEV_ATTR(config_optional),
DRVDATA_DEV_ATTR(config_keep),
DRVDATA_DEV_ATTR(config_enable_opt_cb),
DRVDATA_DEV_ATTR(test_result),
NULL,
};
ATTRIBUTE_GROUPS(test_dev);
void free_test_dev_drvdata(struct drvdata_test_device *test_dev)
{
kfree_const(test_dev->misc_dev.name);
test_dev->misc_dev.name = NULL;
vfree(test_dev);
test_dev = NULL;
drvdata_config_free(test_dev);
}
void unregister_test_dev_drvdata(struct drvdata_test_device *test_dev)
{
dev_info(test_dev->dev, "removing interface\n");
misc_deregister(&test_dev->misc_dev);
kfree(&test_dev->misc_dev.name);
free_test_dev_drvdata(test_dev);
}
struct drvdata_test_device *alloc_test_dev_drvdata(int idx)
{
int rc;
struct drvdata_test_device *test_dev;
struct miscdevice *misc_dev;
test_dev = vzalloc(sizeof(struct drvdata_test_device));
if (!test_dev) {
pr_err("Cannot alloc test_dev\n");
goto err_out;
}
mutex_init(&test_dev->drvdata_mutex);
mutex_init(&test_dev->config_mutex);
mutex_init(&test_dev->trigger_mutex);
rc = drvdata_config_init(test_dev);
if (rc < 0) {
pr_err("Cannot alloc drvdata_config_init()\n");
goto err_out_free;
}
test_dev->dev_idx = idx;
misc_dev = &test_dev->misc_dev;
misc_dev->minor = MISC_DYNAMIC_MINOR;
misc_dev->name = kasprintf(GFP_KERNEL, "test_drvdata%d", idx);
if (!misc_dev->name) {
pr_err("Cannot alloc misc_dev->name\n");
goto err_out_free_config;
}
misc_dev->fops = &test_fw_fops;
misc_dev->groups = test_dev_groups;
return test_dev;
err_out_free_config:
__drvdata_config_free(&test_dev->config);
err_out_free:
kfree(test_dev);
err_out:
return NULL;
}
static int register_test_dev_drvdata(void)
{
struct drvdata_test_device *test_dev = NULL;
int rc = -ENODEV;
mutex_lock(&reg_dev_mutex);
/* int should suffice for number of devices, test for wrap */
if (unlikely(num_test_devs + 1) < 0) {
pr_err("reached limit of number of test devices\n");
goto out;
}
test_dev = alloc_test_dev_drvdata(num_test_devs);
if (!test_dev) {
rc = -ENOMEM;
goto out;
}
rc = misc_register(&test_dev->misc_dev);
if (rc) {
pr_err("could not register misc device: %d\n", rc);
free_test_dev_drvdata(test_dev);
return rc;
}
test_dev->dev = test_dev->misc_dev.this_device;
list_add_tail(&test_dev->list, &reg_test_devs);
dev_info(test_dev->dev, "interface ready\n");
num_test_devs++;
mutex_unlock(&reg_dev_mutex);
out:
return rc;
}
static int __init test_drvdata_init(void)
{
int rc;
rc = register_test_dev_drvdata();
if (rc)
pr_err("Cannot add first test drvdata device\n");
return rc;
}
late_initcall(test_drvdata_init);
static void __exit test_drvdata_exit(void)
{
struct drvdata_test_device *test_dev, *tmp;
mutex_lock(&reg_dev_mutex);
list_for_each_entry_safe(test_dev, tmp, &reg_test_devs, list) {
list_del(&test_dev->list);
unregister_test_dev_drvdata(test_dev);
}
mutex_unlock(&reg_dev_mutex);
}
module_exit(test_drvdata_exit);
MODULE_AUTHOR("Luis R. Rodriguez <mcgrof@kernel.org>");
MODULE_LICENSE("GPL");