esoc: Add external soc control framework

External slave soc can be powered on/off and monitored
for power states and crash events.

Change-Id: I9c3317f798b204b754d612a3115dcd71935b5ef2
Signed-off-by: Hanumant Singh <hanumant@codeaurora.org>
This commit is contained in:
Hanumant Singh 2013-11-01 21:46:53 -07:00
parent 40b27baaea
commit 5ad7ffa13d
9 changed files with 962 additions and 0 deletions

View file

@ -114,6 +114,8 @@ source "drivers/edac/Kconfig"
source "drivers/rtc/Kconfig"
source "drivers/esoc/Kconfig"
source "drivers/dma/Kconfig"
source "drivers/dca/Kconfig"

View file

@ -160,5 +160,6 @@ obj-$(CONFIG_NTB) += ntb/
obj-$(CONFIG_MOBICORE_SUPPORT) += gud/
obj-$(CONFIG_CORESIGHT) += coresight/
obj-$(CONFIG_ESOC) += esoc/
obj-$(CONFIG_BIF) += bif/

28
drivers/esoc/Kconfig Normal file
View file

@ -0,0 +1,28 @@
#
# External soc control infrastructure and drivers
#
menuconfig ESOC
bool "External SOCs Control"
help
External SOCs can be powered on and monitored by user
space or kernel drivers. Additionally they can be controlled
to respond to control commands. This framework provides an
interface to track events related to the external slave socs.
if ESOC
config ESOC_DEV
bool "ESOC userspace interface"
help
Say yes here to enable a userspace representation of the control
link. Userspace can register a request engine or a command engine
for the external soc. It can receive event notifications from the
control link.
config ESOC_DEBUG
bool "ESOC debug support"
help
Say yes here to enable debugging support in the ESOC framework
and individual esoc drivers.
endif

6
drivers/esoc/Makefile Normal file
View file

@ -0,0 +1,6 @@
# generic external soc control support
ccflags-$(CONFIG_ESOC_DEBUG) := -DDEBUG
obj-$(CONFIG_ESOC) += esoc_bus.o
obj-$(CONFIG_ESOC_DEV) += esoc_dev.o

150
drivers/esoc/esoc.h Normal file
View file

@ -0,0 +1,150 @@
/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that 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.
*/
#ifndef __ESOC_H__
#define __ESOC_H__
#include <linux/cdev.h>
#include <linux/completion.h>
#include <linux/esoc_ctrl.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <mach/subsystem_notif.h>
#include <mach/subsystem_restart.h>
#define ESOC_DEV_MAX 4
#define ESOC_NAME_LEN 20
#define ESOC_LINK_LEN 8
struct esoc_clink;
/**
* struct esoc_eng: Engine of the esoc control link
* @handle_clink_req: handle incoming esoc requests.
* @handle_clink_evt: handle for esoc events.
* @esoc_clink: pointer to esoc control link.
*/
struct esoc_eng {
void (*handle_clink_req)(enum esoc_req req,
struct esoc_eng *eng);
void (*handle_clink_evt)(enum esoc_evt evt,
struct esoc_eng *eng);
struct esoc_clink *esoc_clink;
};
/**
* struct esoc_clink: Representation of external esoc device
* @name: Name of the external esoc.
* @link_name: name of the physical link.
* @parent: parent device.
* @dev: device for userspace interface.
* @id: id of the external device.
* @owner: owner of the device.
* @clink_ops: control operations for the control link
* @req_eng: handle for request engine.
* @cmd_eng: handle for command engine.
* @clink_data: private data of esoc control link.
* @compat_data: compat data of esoc driver.
* @subsys_desc: descriptor for subsystem restart
* @subsys_dev: ssr device handle.
*/
struct esoc_clink {
const char *name;
const char *link_name;
struct device *parent;
struct device dev;
unsigned int id;
struct module *owner;
const struct esoc_clink_ops const *clink_ops;
struct esoc_eng *req_eng;
struct esoc_eng *cmd_eng;
spinlock_t notify_lock;
void *clink_data;
void *compat_data;
struct subsys_desc subsys;
struct subsys_device *subsys_dev;
};
/**
* struct esoc_clink_ops: Operations to control external soc
* @cmd_exe: Execute control command
* @get_status: Get current status, or response to previous command
* @notify_esoc: notify external soc of events
*/
struct esoc_clink_ops {
int (*cmd_exe)(enum esoc_cmd cmd, struct esoc_clink *dev);
int (*get_status)(u32 *status, struct esoc_clink *dev);
void (*notify)(enum esoc_notify notify, struct esoc_clink *dev);
};
/**
* struct esoc_compat: Compatibility of esoc drivers.
* @name: esoc link that driver is compatible with.
* @data: driver data associated with esoc clink.
*/
struct esoc_compat {
const char *name;
void *data;
};
/**
* struct esoc_drv: Driver for an esoc clink
* @driver: drivers for esoc.
* @owner: module owner of esoc driver.
* @compat_table: compatible table for driver.
* @compat_entries
* @probe: probe function for esoc driver.
*/
struct esoc_drv {
struct device_driver driver;
struct module *owner;
struct esoc_compat *compat_table;
unsigned int compat_entries;
int (*probe)(struct esoc_clink *esoc_clink);
};
#define to_esoc_clink(d) container_of(d, struct esoc_clink, dev)
#define to_esoc_drv(d) container_of(d, struct esoc_drv, driver)
extern struct bus_type esoc_bus_type;
/* Exported apis */
void esoc_dev_exit(void);
int esoc_dev_init(void);
void esoc_clink_unregister(struct esoc_clink *esoc_dev);
int esoc_clink_register(struct esoc_clink *esoc_dev);
struct esoc_clink *get_esoc_clink(int id);
void put_esoc_clink(struct esoc_clink *esoc_clink);
void *get_esoc_clink_data(struct esoc_clink *esoc);
void set_esoc_clink_data(struct esoc_clink *esoc, void *data);
void esoc_clink_evt_notify(enum esoc_evt, struct esoc_clink *esoc_dev);
void esoc_clink_queue_request(enum esoc_req req, struct esoc_clink *esoc_dev);
void esoc_for_each_dev(void *data, int (*fn)(struct device *dev,
void *data));
int esoc_clink_register_cmd_eng(struct esoc_clink *esoc_clink,
struct esoc_eng *eng);
void esoc_clink_unregister_cmd_eng(struct esoc_clink *esoc_clink,
struct esoc_eng *eng);
int esoc_clink_register_req_eng(struct esoc_clink *esoc_clink,
struct esoc_eng *eng);
void esoc_clink_unregister_req_eng(struct esoc_clink *esoc_clink,
struct esoc_eng *eng);
int esoc_drv_register(struct esoc_drv *driver);
void esoc_set_drv_data(struct esoc_clink *esoc_clink, void *data);
void *esoc_get_drv_data(struct esoc_clink *esoc_clink);
/* ssr operations */
int esoc_clink_register_ssr(struct esoc_clink *esoc_clink);
int esoc_clink_request_ssr(struct esoc_clink *esoc_clink);
void esoc_clink_unregister_ssr(struct esoc_clink *esoc_clink);
#endif

336
drivers/esoc/esoc_bus.c Normal file
View file

@ -0,0 +1,336 @@
/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that 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.
*/
#include <linux/idr.h>
#include <linux/slab.h>
#include "esoc.h"
static DEFINE_IDA(esoc_ida);
/* SYSFS */
static ssize_t
esoc_name_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return snprintf(buf, ESOC_NAME_LEN, "%s", to_esoc_clink(dev)->name);
}
static ssize_t
esoc_link_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return snprintf(buf, ESOC_LINK_LEN, "%s",
to_esoc_clink(dev)->link_name);
}
static struct device_attribute esoc_clink_attrs[] = {
__ATTR_RO(esoc_name),
__ATTR_RO(esoc_link),
__ATTR_NULL,
};
static int esoc_bus_match(struct device *dev, struct device_driver *drv)
{
struct esoc_clink *esoc_clink = to_esoc_clink(dev);
return !memcmp(esoc_clink->name, drv->name,
strlen(drv->name));
}
static int esoc_bus_probe(struct device *dev)
{
int ret;
struct esoc_clink *esoc_clink = to_esoc_clink(dev);
struct esoc_drv *esoc_drv = to_esoc_drv(dev->driver);
ret = esoc_drv->probe(esoc_clink);
if (ret) {
pr_err("failed to probe %s dev\n", esoc_clink->name);
return ret;
}
return 0;
}
struct bus_type esoc_bus_type = {
.name = "esoc",
.match = esoc_bus_match,
.dev_attrs = esoc_clink_attrs,
};
EXPORT_SYMBOL(esoc_bus_type);
struct device esoc_bus = {
.init_name = "esoc-bus"
};
EXPORT_SYMBOL(esoc_bus);
/* bus accessor */
static void esoc_clink_release(struct device *dev)
{
struct esoc_clink *esoc_clink = to_esoc_clink(dev);
ida_simple_remove(&esoc_ida, esoc_clink->id);
kfree(esoc_clink);
}
static int esoc_clink_match_id(struct device *dev, void *id)
{
struct esoc_clink *esoc_clink = to_esoc_clink(dev);
int *esoc_id = (int *)id;
if (esoc_clink->id == *esoc_id) {
if (!try_module_get(esoc_clink->owner))
return 0;
return 1;
}
return 0;
}
void esoc_for_each_dev(void *data, int (*fn)(struct device *dev, void *))
{
int ret;
ret = bus_for_each_dev(&esoc_bus_type, NULL, data, fn);
return;
}
EXPORT_SYMBOL(esoc_for_each_dev);
struct esoc_clink *get_esoc_clink(int id)
{
struct esoc_clink *esoc_clink;
struct device *dev;
dev = bus_find_device(&esoc_bus_type, NULL, &id, esoc_clink_match_id);
if (IS_ERR(dev))
return NULL;
esoc_clink = to_esoc_clink(dev);
return esoc_clink;
}
EXPORT_SYMBOL(get_esoc_clink);
void put_esoc_clink(struct esoc_clink *esoc_clink)
{
module_put(esoc_clink->owner);
}
EXPORT_SYMBOL(put_esoc_clink);
/* ssr operations */
int esoc_clink_register_ssr(struct esoc_clink *esoc_clink)
{
int ret;
int len;
char *subsys_name;
len = strlen("esoc") + sizeof(esoc_clink->id);
subsys_name = kzalloc(len, GFP_KERNEL);
if (IS_ERR(subsys_name))
return PTR_ERR(subsys_name);
snprintf(subsys_name, len, "esoc%d", esoc_clink->id);
esoc_clink->subsys.name = subsys_name;
esoc_clink->subsys.dev = &esoc_clink->dev;
esoc_clink->subsys_dev = subsys_register(&esoc_clink->subsys);
if (IS_ERR(esoc_clink->subsys_dev)) {
dev_err(&esoc_clink->dev, "failed to register ssr node\n");
ret = PTR_ERR(esoc_clink->subsys_dev);
goto subsys_err;
}
return 0;
subsys_err:
kfree(subsys_name);
return ret;
}
EXPORT_SYMBOL(esoc_clink_register_ssr);
void esoc_clink_unregister_ssr(struct esoc_clink *esoc_clink)
{
subsys_unregister(esoc_clink->subsys_dev);
kfree(esoc_clink->subsys.name);
}
EXPORT_SYMBOL(esoc_clink_unregister_ssr);
int esoc_clink_request_ssr(struct esoc_clink *esoc_clink)
{
subsystem_restart_dev(esoc_clink->subsys_dev);
return 0;
}
EXPORT_SYMBOL(esoc_clink_request_ssr);
/* bus operations */
void esoc_clink_evt_notify(enum esoc_evt evt, struct esoc_clink *esoc_clink)
{
unsigned long flags;
spin_lock_irqsave(&esoc_clink->notify_lock, flags);
if (esoc_clink->req_eng && esoc_clink->req_eng->handle_clink_evt)
esoc_clink->req_eng->handle_clink_evt(evt, esoc_clink->req_eng);
if (esoc_clink->cmd_eng && esoc_clink->cmd_eng->handle_clink_evt)
esoc_clink->cmd_eng->handle_clink_evt(evt, esoc_clink->cmd_eng);
spin_unlock_irqrestore(&esoc_clink->notify_lock, flags);
}
EXPORT_SYMBOL(esoc_clink_evt_notify);
void *get_esoc_clink_data(struct esoc_clink *esoc)
{
return esoc->clink_data;
}
EXPORT_SYMBOL(get_esoc_clink_data);
void set_esoc_clink_data(struct esoc_clink *esoc, void *data)
{
esoc->clink_data = data;
}
EXPORT_SYMBOL(set_esoc_clink_data);
void esoc_clink_queue_request(enum esoc_req req, struct esoc_clink *esoc_clink)
{
unsigned long flags;
struct esoc_eng *req_eng;
spin_lock_irqsave(&esoc_clink->notify_lock, flags);
if (esoc_clink->req_eng != NULL) {
req_eng = esoc_clink->req_eng;
req_eng->handle_clink_req(req, req_eng);
}
spin_unlock_irqrestore(&esoc_clink->notify_lock, flags);
}
EXPORT_SYMBOL(esoc_clink_queue_request);
void esoc_set_drv_data(struct esoc_clink *esoc_clink, void *data)
{
dev_set_drvdata(&esoc_clink->dev, data);
}
EXPORT_SYMBOL(esoc_set_drv_data);
void *esoc_get_drv_data(struct esoc_clink *esoc_clink)
{
return dev_get_drvdata(&esoc_clink->dev);
}
EXPORT_SYMBOL(esoc_get_drv_data);
/* bus registration functions */
void esoc_clink_unregister(struct esoc_clink *esoc_clink)
{
if (get_device(&esoc_clink->dev) != NULL) {
device_unregister(&esoc_clink->dev);
put_device(&esoc_clink->dev);
}
}
EXPORT_SYMBOL(esoc_clink_unregister);
int esoc_clink_register(struct esoc_clink *esoc_clink)
{
int id, err;
struct device *dev;
if (!esoc_clink->name || !esoc_clink->link_name ||
!esoc_clink->clink_ops) {
dev_err(esoc_clink->parent, "invalid esoc arguments\n");
return -EINVAL;
}
id = ida_simple_get(&esoc_ida, 0, ESOC_DEV_MAX, GFP_KERNEL);
if (id < 0) {
err = id;
goto exit_ida;
}
esoc_clink->id = id;
dev = &esoc_clink->dev;
dev->bus = &esoc_bus_type;
dev->release = esoc_clink_release;
if (!esoc_clink->parent)
dev->parent = &esoc_bus;
else
dev->parent = esoc_clink->parent;
dev_set_name(dev, "esoc%d", id);
err = device_register(dev);
if (err) {
dev_err(esoc_clink->parent, "esoc device register failed\n");
goto exit_ida;
}
spin_lock_init(&esoc_clink->notify_lock);
return 0;
exit_ida:
ida_simple_remove(&esoc_ida, id);
pr_err("unable to register %s, err = %d\n", esoc_clink->name, err);
return err;
}
EXPORT_SYMBOL(esoc_clink_register);
int esoc_clink_register_req_eng(struct esoc_clink *esoc_clink,
struct esoc_eng *eng)
{
if (esoc_clink->req_eng)
return -EBUSY;
if (!eng->handle_clink_req)
return -EINVAL;
esoc_clink->req_eng = eng;
eng->esoc_clink = esoc_clink;
return 0;
}
EXPORT_SYMBOL(esoc_clink_register_req_eng);
int esoc_clink_register_cmd_eng(struct esoc_clink *esoc_clink,
struct esoc_eng *eng)
{
if (esoc_clink->cmd_eng)
return -EBUSY;
esoc_clink->cmd_eng = eng;
eng->esoc_clink = esoc_clink;
return 0;
}
EXPORT_SYMBOL(esoc_clink_register_cmd_eng);
void esoc_clink_unregister_req_eng(struct esoc_clink *esoc_clink,
struct esoc_eng *eng)
{
esoc_clink->req_eng = NULL;
}
EXPORT_SYMBOL(esoc_clink_unregister_req_eng);
void esoc_clink_unregister_cmd_eng(struct esoc_clink *esoc_clink,
struct esoc_eng *eng)
{
esoc_clink->cmd_eng = NULL;
}
EXPORT_SYMBOL(esoc_clink_unregister_cmd_eng);
int esoc_drv_register(struct esoc_drv *driver)
{
int ret;
driver->driver.bus = &esoc_bus_type;
driver->driver.probe = esoc_bus_probe;
ret = driver_register(&driver->driver);
if (ret)
return ret;
return 0;
}
EXPORT_SYMBOL(esoc_drv_register);
static int __init esoc_init(void)
{
int ret;
ret = device_register(&esoc_bus);
if (ret) {
pr_err("esoc bus device register fail\n");
return ret;
}
ret = bus_register(&esoc_bus_type);
if (ret) {
pr_err("esoc bus register fail\n");
return ret;
}
pr_debug("esoc bus registration done\n");
return 0;
}
subsys_initcall(esoc_init);
MODULE_LICENSE("GPL v2");

380
drivers/esoc/esoc_dev.c Normal file
View file

@ -0,0 +1,380 @@
/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that 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.
*/
#include <linux/kfifo.h>
#include <linux/list.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include "esoc.h"
/**
* struct esoc_udev: Userspace char interface
* @dev: interface device.
* @req_fifio: fifo for clink requests.
* @req_wait: signal availability of request from clink
* @req_fifo_lock: serialize access to req fifo
* @evt_fito: fifo for clink events
* @evt_wait: signal availablity of clink event
* @evt_fifo_lock: serialize access to event fifo
* @list: entry in esoc dev list.
* @clink: reference to contorl link
*/
struct esoc_udev {
struct device *dev;
struct kfifo req_fifo;
wait_queue_head_t req_wait;
spinlock_t req_fifo_lock;
struct kfifo evt_fifo;
wait_queue_head_t evt_wait;
spinlock_t evt_fifo_lock;
struct list_head list;
struct esoc_clink *clink;
};
/**
* struct esoc_uhandle: Userspace handle of esoc
* @esoc_clink: esoc control link.
* @eng: esoc engine for commands/ requests.
* @esoc_udev: user interface device.
*/
struct esoc_uhandle {
struct esoc_clink *esoc_clink;
struct esoc_eng eng;
struct esoc_udev *esoc_udev;
};
#define ESOC_MAX_MINOR 256
#define ESOC_MAX_REQ 8
#define ESOC_MAX_EVT 4
static LIST_HEAD(esoc_udev_list);
static DEFINE_SPINLOCK(esoc_udev_list_lock);
struct class *esoc_class;
static int esoc_major;
static struct esoc_udev *get_free_esoc_udev(struct esoc_clink *esoc_clink)
{
struct esoc_udev *esoc_udev;
int err;
if (esoc_clink->id > ESOC_MAX_MINOR) {
pr_err("too many esoc devices\n");
return ERR_PTR(-ENODEV);
}
esoc_udev = kzalloc(sizeof(*esoc_udev), GFP_KERNEL);
if (!esoc_udev)
return ERR_PTR(-ENOMEM);
err = kfifo_alloc(&esoc_udev->req_fifo, (sizeof(u32)) * ESOC_MAX_REQ,
GFP_KERNEL);
if (err) {
pr_err("unable to allocate request fifo for %s\n",
esoc_clink->name);
goto req_fifo_fail;
}
err = kfifo_alloc(&esoc_udev->req_fifo, (sizeof(u32)) * ESOC_MAX_EVT,
GFP_KERNEL);
if (err) {
pr_err("unable to allocate evt fifo for %s\n",
esoc_clink->name);
goto evt_fifo_fail;
}
init_waitqueue_head(&esoc_udev->req_wait);
init_waitqueue_head(&esoc_udev->evt_wait);
spin_lock_init(&esoc_udev->req_fifo_lock);
spin_lock_init(&esoc_udev->evt_fifo_lock);
esoc_udev->clink = esoc_clink;
spin_lock(&esoc_udev_list_lock);
list_add_tail(&esoc_udev->list, &esoc_udev_list);
spin_unlock(&esoc_udev_list_lock);
return esoc_udev;
evt_fifo_fail:
kfifo_free(&esoc_udev->req_fifo);
req_fifo_fail:
kfree(esoc_udev);
return ERR_PTR(-ENODEV);
}
static void return_esoc_udev(struct esoc_udev *esoc_udev)
{
spin_lock(&esoc_udev_list_lock);
list_del(&esoc_udev->list);
spin_unlock(&esoc_udev_list_lock);
kfifo_free(&esoc_udev->req_fifo);
kfifo_free(&esoc_udev->evt_fifo);
kfree(esoc_udev);
}
static struct esoc_udev *esoc_udev_get_by_minor(unsigned index)
{
struct esoc_udev *esoc_udev;
spin_lock(&esoc_udev_list_lock);
list_for_each_entry(esoc_udev, &esoc_udev_list, list) {
if (esoc_udev->clink->id == index)
goto found;
}
esoc_udev = NULL;
found:
spin_unlock(&esoc_udev_list_lock);
return esoc_udev;
}
void esoc_udev_handle_clink_req(enum esoc_req req, struct esoc_eng *eng)
{
int err;
u32 clink_req;
struct esoc_clink *esoc_clink = eng->esoc_clink;
struct esoc_udev *esoc_udev = esoc_udev_get_by_minor(esoc_clink->id);
if (!esoc_udev)
return;
clink_req = (u32)req;
err = kfifo_in_spinlocked(&esoc_udev->req_fifo, &clink_req,
sizeof(clink_req),
&esoc_udev->req_fifo_lock);
if (err != sizeof(clink_req)) {
pr_err("unable to queue request for %s\n", esoc_clink->name);
return;
}
wake_up_interruptible(&esoc_udev->req_wait);
}
void esoc_udev_handle_clink_evt(enum esoc_evt evt, struct esoc_eng *eng)
{
int err;
u32 clink_evt;
struct esoc_clink *esoc_clink = eng->esoc_clink;
struct esoc_udev *esoc_udev = esoc_udev_get_by_minor(esoc_clink->id);
if (!esoc_udev)
return;
clink_evt = (u32)evt;
err = kfifo_in_spinlocked(&esoc_udev->evt_fifo, &clink_evt,
sizeof(clink_evt),
&esoc_udev->evt_fifo_lock);
if (err != sizeof(clink_evt)) {
pr_err("unable to queue event for %s\n", esoc_clink->name);
return;
}
wake_up_interruptible(&esoc_udev->evt_wait);
}
static long esoc_dev_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
int err;
u32 esoc_cmd , status, req, evt;
struct esoc_uhandle *uhandle = file->private_data;
struct esoc_udev *esoc_udev = uhandle->esoc_udev;
struct esoc_clink *esoc_clink = uhandle->esoc_clink;
const struct esoc_clink_ops const *clink_ops = esoc_clink->clink_ops;
void __user *uarg = (void __user *)arg;
switch (cmd) {
case ESOC_REG_REQ_ENG:
err = esoc_clink_register_req_eng(esoc_clink, &uhandle->eng);
if (err)
return err;
break;
case ESOC_REG_CMD_ENG:
err = esoc_clink_register_cmd_eng(esoc_clink, &uhandle->eng);
if (err)
return err;
break;
case ESOC_CMD_EXE:
if (esoc_clink->cmd_eng != &uhandle->eng)
return -EACCES;
get_user(esoc_cmd, (u32 __user *)arg);
return clink_ops->cmd_exe(esoc_cmd, esoc_clink);
case ESOC_WAIT_FOR_REQ:
if (esoc_clink->req_eng != &uhandle->eng)
return -EACCES;
err = wait_event_interruptible(esoc_udev->req_wait,
!kfifo_is_empty(&esoc_udev->req_fifo));
if (!err) {
err = kfifo_out_spinlocked(&esoc_udev->req_fifo, &req,
sizeof(req),
&esoc_udev->req_fifo_lock);
if (err != sizeof(req)) {
pr_err("read from clink %s req q failed\n",
esoc_clink->name);
return -EIO;
}
put_user(req, (unsigned long __user *)uarg);
}
return err;
break;
case ESOC_NOTIFY:
get_user(esoc_cmd, (u32 __user *)arg);
clink_ops->notify(esoc_cmd, esoc_clink);
break;
case ESOC_GET_STATUS:
err = clink_ops->get_status(&status, esoc_clink);
if (err)
return err;
put_user(status, (unsigned long __user *)uarg);
break;
case ESOC_WAIT_FOR_CRASH:
err = wait_event_interruptible(esoc_udev->evt_wait,
!kfifo_is_empty(&esoc_udev->evt_fifo));
if (!err) {
err = kfifo_out_spinlocked(&esoc_udev->evt_fifo, &evt,
sizeof(evt),
&esoc_udev->evt_fifo_lock);
if (err != sizeof(evt)) {
pr_err("read from clink %s evt q failed\n",
esoc_clink->name);
return -EIO;
}
put_user(evt, (unsigned long __user *)uarg);
}
return err;
break;
default:
return -EINVAL;
};
return 0;
}
static int esoc_dev_open(struct inode *inode, struct file *file)
{
struct esoc_uhandle *uhandle;
struct esoc_udev *esoc_udev;
struct esoc_clink *esoc_clink;
struct esoc_eng *eng;
unsigned int minor = iminor(inode);
esoc_udev = esoc_udev_get_by_minor(minor);
esoc_clink = get_esoc_clink(esoc_udev->clink->id);
uhandle = kzalloc(sizeof(*uhandle), GFP_KERNEL);
if (!uhandle) {
pr_err("failed to allocate memory for uhandle\n");
put_esoc_clink(esoc_clink);
return -ENOMEM;
}
uhandle->esoc_udev = esoc_udev;
uhandle->esoc_clink = esoc_clink;
eng = &uhandle->eng;
eng->handle_clink_req = esoc_udev_handle_clink_req;
eng->handle_clink_evt = esoc_udev_handle_clink_evt;
file->private_data = uhandle;
return 0;
}
static int esoc_dev_release(struct inode *inode, struct file *file)
{
struct esoc_clink *esoc_clink;
struct esoc_uhandle *uhandle = file->private_data;
esoc_clink = uhandle->esoc_clink;
put_esoc_clink(esoc_clink);
kfree(uhandle);
return 0;
}
static const struct file_operations esoc_dev_fops = {
.owner = THIS_MODULE,
.open = esoc_dev_open,
.unlocked_ioctl = esoc_dev_ioctl,
.release = esoc_dev_release,
};
int esoc_clink_add_device(struct device *dev, void *dummy)
{
struct esoc_udev *esoc_udev;
struct esoc_clink *esoc_clink = to_esoc_clink(dev);
esoc_udev = get_free_esoc_udev(esoc_clink);
if (IS_ERR(esoc_udev))
return PTR_ERR(esoc_udev);
esoc_udev->dev = device_create(esoc_class, &esoc_clink->dev,
MKDEV(esoc_major, esoc_clink->id),
esoc_clink, "esoc-%d", esoc_clink->id);
if (IS_ERR(esoc_udev->dev)) {
pr_err("failed to create user device\n");
goto dev_err;
}
return 0;
dev_err:
return_esoc_udev(esoc_udev);
return -ENODEV;
}
int esoc_clink_del_device(struct device *dev, void *dummy)
{
struct esoc_udev *esoc_udev;
struct esoc_clink *esoc_clink = to_esoc_clink(dev);
esoc_udev = esoc_udev_get_by_minor(esoc_clink->id);
if (!esoc_udev)
return 0;
return_esoc_udev(esoc_udev);
device_destroy(esoc_class, MKDEV(esoc_major, esoc_clink->id));
return_esoc_udev(esoc_udev);
return 0;
}
static int esoc_dev_notifier_call(struct notifier_block *nb,
unsigned long action,
void *data)
{
struct device *dev = data;
switch (action) {
case BUS_NOTIFY_ADD_DEVICE:
return esoc_clink_add_device(dev, NULL);
case BUS_NOTIFY_DEL_DEVICE:
return esoc_clink_del_device(dev, NULL);
};
return 0;
}
static struct notifier_block esoc_dev_notifier = {
.notifier_call = esoc_dev_notifier_call,
};
int __init esoc_dev_init(void)
{
int ret = 0;
esoc_class = class_create(THIS_MODULE, "esoc-dev");
if (IS_ERR(esoc_class)) {
pr_err("coudn't create class");
return PTR_ERR(esoc_class);
}
esoc_major = register_chrdev(0, "esoc", &esoc_dev_fops);
if (esoc_major < 0) {
pr_err("failed to allocate char dev\n");
ret = esoc_major;
goto class_unreg;
}
ret = bus_register_notifier(&esoc_bus_type, &esoc_dev_notifier);
if (ret)
goto chrdev_unreg;
esoc_for_each_dev(NULL, esoc_clink_add_device);
return ret;
chrdev_unreg:
unregister_chrdev(esoc_major, "esoc");
class_unreg:
class_destroy(esoc_class);
return 0;
}
void __exit esoc_dev_exit(void)
{
bus_unregister_notifier(&esoc_bus_type, &esoc_dev_notifier);
class_destroy(esoc_class);
unregister_chrdev(esoc_major, "esoc-dev");
}
MODULE_LICENSE("GPLv2");
module_init(esoc_dev_init);
module_exit(esoc_dev_exit);

View file

@ -114,6 +114,7 @@ header-y += elfcore.h
header-y += epm_adc.h
header-y += errno.h
header-y += errqueue.h
header-y += esoc_ctrl.h
header-y += ethtool.h
header-y += eventpoll.h
header-y += fadvise.h

View file

@ -0,0 +1,58 @@
#ifndef _UAPI_ESOC_CTRL_H_
#define _UAPI_ESOC_CTRL_H_
#define ESOC_CODE 0xCC
#define ESOC_CMD_EXE _IOW(ESOC_CODE, 1, u32)
#define ESOC_WAIT_FOR_REQ _IOR(ESOC_CODE, 2, u32)
#define ESOC_NOTIFY _IOW(ESOC_CODE, 3, u32)
#define ESOC_GET_STATUS _IOR(ESOC_CODE, 4, u32)
#define ESOC_WAIT_FOR_CRASH _IOR(ESOC_CODE, 6, u32)
#define ESOC_REG_REQ_ENG _IO(ESOC_CODE, 7)
#define ESOC_REG_CMD_ENG _IO(ESOC_CODE, 8)
enum esoc_evt {
ESOC_RUN_STATE = 0x1,
ESOC_UNEXPECTED_RESET,
ESOC_ERR_FATAL,
ESOC_IN_DEBUG,
ESOC_BOOT_FAIL,
};
enum esoc_cmd {
ESOC_PWR_ON = 1,
ESOC_PWR_OFF,
ESOC_RESET,
ESOC_PREPARE_DEBUG,
ESOC_EXE_DEBUG,
ESOC_EXIT_DEBUG,
};
enum esoc_notify {
ESOC_IMG_XFER_DONE = 1,
ESOC_IMG_XFER_RETRY,
ESOC_IMG_XFER_FAIL,
ESOC_UPGRADE_AVAILABLE,
ESOC_DEBUG_DONE,
ESOC_DEBUG_FAIL,
};
enum esoc_req {
ESOC_REQ_IMG = 1,
ESOC_REQ_DEBUG,
};
#ifdef __KERNEL__
/**
* struct esoc_handle: Handle for clients of esoc
* @name: name of the external soc.
* @link: link of external soc.
* @id: id of external soc.
*/
struct esoc_handle {
const char *name;
const char *link;
unsigned int id;
};
#endif
#endif