553 lines
11 KiB
C
553 lines
11 KiB
C
/*
|
|
* eventlib.c
|
|
*
|
|
* Copyright (c) 2017-2018, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/types.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/crc32.h>
|
|
|
|
#include <linux/keventlib.h>
|
|
|
|
#include "eventlib.h"
|
|
|
|
#define KEVENTLIB_VERSION "0.2"
|
|
|
|
#define EVENTLIB_SYSFS_DIR_NAME "eventlib"
|
|
#define EVENTLIB_SYSFS_TEST_FILE_NAME "test"
|
|
#define EVENTLIB_SYSFS_EVENTS_FILE_NAME "events"
|
|
#define EVENTLIB_SYSFS_SCHEMA_FILE_NAME "schema"
|
|
|
|
#define EVENTLIB_TEST_SHM_SIZE (PAGE_SIZE)
|
|
|
|
#define EVENTLIB_MAX_PROVIDERS 256
|
|
#define EVENTLIB_TEST_DATA_SIZE 0x10
|
|
|
|
struct eventlib_provider_info {
|
|
struct kobject *kobj;
|
|
|
|
struct bin_attribute attr;
|
|
struct bin_attribute attr_schema;
|
|
|
|
void *data;
|
|
size_t data_size;
|
|
|
|
struct eventlib_ctx el_ctx;
|
|
|
|
void *w2r;
|
|
size_t w2r_size;
|
|
|
|
int id;
|
|
struct list_head list;
|
|
|
|
char *schema;
|
|
size_t schema_size;
|
|
};
|
|
|
|
static struct eventlib_module {
|
|
struct kobject *kobj_root;
|
|
|
|
struct list_head providers;
|
|
atomic_t nr_providers;
|
|
|
|
spinlock_t lock;
|
|
|
|
int test_id;
|
|
} ctx;
|
|
|
|
struct eventlib_work_data {
|
|
struct work_struct work;
|
|
struct eventlib_provider_info *provider;
|
|
};
|
|
|
|
#define EVENTLIB_TEST_SAMPLE_MAGIC 0x11223344
|
|
struct eventlib_test_sample {
|
|
uint32_t magic;
|
|
uint32_t size;
|
|
uint32_t crc;
|
|
} __attribute__((__packed__));
|
|
|
|
static int is_initialized;
|
|
|
|
static int keventlib_init(struct eventlib_provider_info *info)
|
|
{
|
|
int ret;
|
|
struct eventlib_ctx *el_ctx = &info->el_ctx;
|
|
|
|
info->w2r = info->data;
|
|
info->w2r_size = info->data_size;
|
|
|
|
pr_debug("w2r: %p, size: %#zx\n", info->w2r, info->w2r_size);
|
|
|
|
memset(el_ctx, 0, sizeof(*el_ctx));
|
|
|
|
el_ctx->direction = EVENTLIB_DIRECTION_WRITER;
|
|
el_ctx->w2r_shm = info->w2r;
|
|
el_ctx->w2r_shm_size = (uint32_t)info->w2r_size;
|
|
el_ctx->r2w_shm = NULL;
|
|
el_ctx->r2w_shm_size = 0;
|
|
el_ctx->flags = 0;
|
|
|
|
ret = eventlib_init(el_ctx);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
sysfs_mmap(struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *attr, struct vm_area_struct *vma)
|
|
{
|
|
unsigned long vm_size, pfn;
|
|
|
|
struct eventlib_provider_info *info =
|
|
container_of(attr, struct eventlib_provider_info, attr);
|
|
|
|
vm_size = vma->vm_end - vma->vm_start;
|
|
vma->vm_private_data = filp->private_data;
|
|
|
|
pr_debug("%s: vma: %#lx - %#lx (%#lx)\n",
|
|
__func__, vma->vm_start, vma->vm_end, vm_size);
|
|
|
|
if (vm_size != attr->size)
|
|
return -EINVAL;
|
|
|
|
if (!info->data)
|
|
return -ENOMEM;
|
|
|
|
pfn = virt_to_phys(info->data) >> PAGE_SHIFT;
|
|
|
|
if (remap_pfn_range(vma, vma->vm_start, pfn,
|
|
vma->vm_end - vma->vm_start,
|
|
vma->vm_page_prot))
|
|
return -EAGAIN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
sysfs_schema_read(struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *attr,
|
|
char *buf, loff_t off, size_t len)
|
|
{
|
|
struct eventlib_provider_info *info =
|
|
container_of(attr, struct eventlib_provider_info, attr_schema);
|
|
|
|
if (info->schema == NULL)
|
|
return -ENOENT;
|
|
|
|
if (len > info->schema_size - off)
|
|
len = info->schema_size - off;
|
|
|
|
memcpy(buf, info->schema + off, len);
|
|
|
|
return len;
|
|
}
|
|
|
|
static int
|
|
create_sysfs_entry(struct eventlib_provider_info *info,
|
|
const char *name)
|
|
{
|
|
int ret;
|
|
struct bin_attribute *attr = &info->attr;
|
|
|
|
info->kobj = kobject_create_and_add(name, ctx.kobj_root);
|
|
if (info->kobj == NULL) {
|
|
pr_err("Unable to create sysfs directory: %s\n", name);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
sysfs_bin_attr_init(attr);
|
|
|
|
attr->attr.name = EVENTLIB_SYSFS_EVENTS_FILE_NAME;
|
|
attr->attr.mode = 0444;
|
|
attr->mmap = sysfs_mmap;
|
|
attr->read = NULL;
|
|
attr->size = info->data_size;
|
|
|
|
ret = sysfs_create_bin_file(info->kobj, attr);
|
|
if (ret) {
|
|
pr_err("Unable to create sysfs file: %s\n",
|
|
attr->attr.name);
|
|
kobject_put(info->kobj);
|
|
return ret;
|
|
}
|
|
|
|
if (info->schema) {
|
|
struct bin_attribute *attr_schema = &info->attr_schema;
|
|
|
|
sysfs_bin_attr_init(attr_schema);
|
|
|
|
attr_schema->attr.name = EVENTLIB_SYSFS_SCHEMA_FILE_NAME;
|
|
attr_schema->attr.mode = 0444;
|
|
attr_schema->mmap = NULL;
|
|
attr_schema->read = sysfs_schema_read;
|
|
attr_schema->write = NULL;
|
|
attr_schema->size = info->schema_size;
|
|
|
|
ret = sysfs_create_bin_file(info->kobj, attr_schema);
|
|
if (ret) {
|
|
pr_err("Unable to create sysfs file: %s\n",
|
|
attr_schema->attr.name);
|
|
kobject_put(info->kobj);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void remove_sysfs_entry(struct eventlib_provider_info *info)
|
|
{
|
|
sysfs_remove_bin_file(info->kobj, &info->attr);
|
|
if (info->schema)
|
|
sysfs_remove_bin_file(info->kobj, &info->attr_schema);
|
|
|
|
kobject_put(info->kobj);
|
|
|
|
}
|
|
|
|
static int is_id_free(int id)
|
|
{
|
|
struct eventlib_provider_info *info;
|
|
|
|
list_for_each_entry(info, &ctx.providers, list) {
|
|
if (id == info->id)
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int get_free_id(void)
|
|
{
|
|
int id;
|
|
|
|
for (id = 0; id < EVENTLIB_MAX_PROVIDERS; id++) {
|
|
if (is_id_free(id))
|
|
return id;
|
|
}
|
|
|
|
return -EMFILE;
|
|
}
|
|
|
|
static int
|
|
provider_init(struct eventlib_provider_info *info,
|
|
size_t size, const char *name,
|
|
const char *schema, size_t schema_size)
|
|
{
|
|
int ret = 0, id;
|
|
|
|
info->data = NULL;
|
|
info->data_size = 0;
|
|
|
|
info->w2r = NULL;
|
|
info->w2r_size = 0;
|
|
|
|
if (size == 0 || !is_power_of_2(size))
|
|
return -EINVAL;
|
|
|
|
info->data = (void *)__get_free_pages(GFP_KERNEL, get_order(size));
|
|
if (!info->data)
|
|
return -ENOMEM;
|
|
|
|
memset(info->data, 0, size);
|
|
info->data_size = size;
|
|
|
|
if (schema && schema_size > 0) {
|
|
info->schema_size = schema_size;
|
|
|
|
info->schema = kmalloc(info->schema_size, GFP_KERNEL);
|
|
if (!info->schema)
|
|
return -ENOMEM;
|
|
|
|
memcpy(info->schema, schema, schema_size);
|
|
} else {
|
|
info->schema = NULL;
|
|
info->schema_size = 0;
|
|
}
|
|
|
|
ret = create_sysfs_entry(info, name);
|
|
if (ret < 0)
|
|
goto err_free;
|
|
|
|
ret = keventlib_init(info);
|
|
if (ret < 0)
|
|
goto err_sysfs;
|
|
|
|
INIT_LIST_HEAD(&info->list);
|
|
|
|
spin_lock(&ctx.lock);
|
|
|
|
id = get_free_id();
|
|
if (id < 0) {
|
|
pr_err("Too many providers: > %d\n", EVENTLIB_MAX_PROVIDERS);
|
|
ret = -EMFILE;
|
|
goto err_get_id;
|
|
}
|
|
|
|
info->id = id;
|
|
|
|
list_add_tail(&info->list, &ctx.providers);
|
|
atomic_inc(&ctx.nr_providers);
|
|
|
|
spin_unlock(&ctx.lock);
|
|
|
|
return 0;
|
|
|
|
err_get_id:
|
|
spin_unlock(&ctx.lock);
|
|
eventlib_close(&info->el_ctx);
|
|
|
|
err_sysfs:
|
|
remove_sysfs_entry(info);
|
|
|
|
err_free:
|
|
if (info->schema) {
|
|
kfree(info->schema);
|
|
info->schema = NULL;
|
|
}
|
|
|
|
free_pages((unsigned long)info->data, get_order(size));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct eventlib_provider_info *
|
|
find_provider_info(int id)
|
|
{
|
|
struct eventlib_provider_info *info;
|
|
|
|
list_for_each_entry(info, &ctx.providers, list) {
|
|
if (id == info->id)
|
|
return info;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
__free_provider(struct work_struct *work)
|
|
{
|
|
struct eventlib_work_data *wd =
|
|
container_of(work, struct eventlib_work_data, work);
|
|
|
|
struct eventlib_provider_info *info = wd->provider;
|
|
|
|
remove_sysfs_entry(info);
|
|
|
|
if (info->schema)
|
|
kfree(info->schema);
|
|
|
|
kfree(info);
|
|
kfree(wd);
|
|
|
|
if (atomic_dec_and_test(&ctx.nr_providers))
|
|
kobject_put(ctx.kobj_root);
|
|
}
|
|
|
|
static void free_provider(struct eventlib_provider_info *info)
|
|
{
|
|
struct eventlib_work_data *wd;
|
|
|
|
eventlib_close(&info->el_ctx);
|
|
|
|
free_pages((unsigned long)info->data,
|
|
get_order(info->data_size));
|
|
|
|
list_del(&info->list);
|
|
|
|
wd = kmalloc(sizeof(*wd), GFP_ATOMIC);
|
|
if (!wd)
|
|
return;
|
|
|
|
wd->provider = info;
|
|
INIT_WORK(&wd->work, __free_provider);
|
|
schedule_work(&wd->work);
|
|
}
|
|
|
|
static void unregister_all_providers(void)
|
|
{
|
|
struct eventlib_provider_info *info, *next;
|
|
|
|
spin_lock(&ctx.lock);
|
|
list_for_each_entry_safe(info, next, &ctx.providers, list)
|
|
free_provider(info);
|
|
spin_unlock(&ctx.lock);
|
|
}
|
|
|
|
int keventlib_write(int id, void *data, size_t size, uint32_t type, uint64_t ts)
|
|
{
|
|
int err = 0;
|
|
struct eventlib_provider_info *info;
|
|
|
|
pr_debug("%s: size: %#zx\n", __func__, size);
|
|
|
|
spin_lock(&ctx.lock);
|
|
|
|
info = find_provider_info(id);
|
|
if (!info) {
|
|
err = -ENOENT;
|
|
goto err_out;
|
|
}
|
|
|
|
if (!info->data) {
|
|
err = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
|
|
eventlib_write(&info->el_ctx, 0, type, ts, data, size);
|
|
|
|
err_out:
|
|
spin_unlock(&ctx.lock);
|
|
return err;
|
|
}
|
|
EXPORT_SYMBOL(keventlib_write);
|
|
|
|
int keventlib_register(size_t size, const char *name,
|
|
const char *schema, size_t schema_size)
|
|
{
|
|
int ret;
|
|
struct eventlib_provider_info *info;
|
|
|
|
if (!is_initialized) {
|
|
pr_warn("keventlib is not initialized\n");
|
|
return -EACCES;
|
|
}
|
|
|
|
info = kmalloc(sizeof(*info), GFP_KERNEL);
|
|
if (!info)
|
|
return -ENOMEM;
|
|
|
|
ret = provider_init(info, size, name, schema, schema_size);
|
|
if (ret < 0) {
|
|
kfree(info);
|
|
return ret;
|
|
}
|
|
|
|
return info->id;
|
|
}
|
|
EXPORT_SYMBOL(keventlib_register);
|
|
|
|
void keventlib_unregister(int id)
|
|
{
|
|
struct eventlib_provider_info *info;
|
|
|
|
if (!is_initialized) {
|
|
pr_warn("keventlib is not initialized\n");
|
|
return;
|
|
}
|
|
|
|
spin_lock(&ctx.lock);
|
|
|
|
info = find_provider_info(id);
|
|
if (!info) {
|
|
pr_err("Unregistered provider: %d\n", id);
|
|
spin_unlock(&ctx.lock);
|
|
return;
|
|
}
|
|
|
|
free_provider(info);
|
|
|
|
spin_unlock(&ctx.lock);
|
|
}
|
|
EXPORT_SYMBOL(keventlib_unregister);
|
|
|
|
static void put_test_data(int id)
|
|
{
|
|
int i;
|
|
uint8_t value = 0;
|
|
uint64_t ts = 0;
|
|
uint8_t *data;
|
|
struct eventlib_test_sample *s;
|
|
uint8_t buffer[sizeof(struct eventlib_test_sample) +
|
|
EVENTLIB_TEST_DATA_SIZE];
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
s = (struct eventlib_test_sample *)buffer;
|
|
data = (uint8_t *)(s + 1);
|
|
|
|
s->magic = EVENTLIB_TEST_SAMPLE_MAGIC;
|
|
s->size = EVENTLIB_TEST_DATA_SIZE;
|
|
memset(data, value++, s->size);
|
|
s->crc = crc32(~0U, data, s->size) ^ 0xffffffff;
|
|
|
|
keventlib_write(ctx.test_id, buffer,
|
|
sizeof(buffer), 10 + id, ts++);
|
|
}
|
|
}
|
|
|
|
static int __init
|
|
eventlib_module_init(void)
|
|
{
|
|
int ret;
|
|
|
|
if (is_initialized)
|
|
return -ENOMEM;
|
|
|
|
atomic_set(&ctx.nr_providers, 0);
|
|
|
|
INIT_LIST_HEAD(&ctx.providers);
|
|
spin_lock_init(&ctx.lock);
|
|
|
|
ctx.kobj_root = kobject_create_and_add(EVENTLIB_SYSFS_DIR_NAME,
|
|
kernel_kobj);
|
|
if (ctx.kobj_root == NULL) {
|
|
pr_err("Unable to create sysfs directory: %s\n",
|
|
EVENTLIB_SYSFS_DIR_NAME);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
is_initialized = 1;
|
|
|
|
ret = keventlib_register(EVENTLIB_TEST_SHM_SIZE,
|
|
EVENTLIB_SYSFS_TEST_FILE_NAME,
|
|
NULL, 0);
|
|
if (ret < 0) {
|
|
kobject_put(ctx.kobj_root);
|
|
is_initialized = 0;
|
|
return ret;
|
|
}
|
|
|
|
ctx.test_id = ret;
|
|
put_test_data(ctx.test_id);
|
|
|
|
pr_info("keventlib is initialized, test id: %d\n", ctx.test_id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit
|
|
eventlib_module_exit(void)
|
|
{
|
|
unregister_all_providers();
|
|
pr_info("keventlib is uninitialized\n");
|
|
}
|
|
|
|
subsys_initcall(eventlib_module_init);
|
|
module_exit(eventlib_module_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Nvidia Ltd");
|
|
MODULE_DESCRIPTION("Kernel Eventlib");
|
|
MODULE_VERSION(KEVENTLIB_VERSION);
|