blob: a572274e6dcff1f5a1820525f5f249074fb4745c [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/mutex.h>
#include <linux/completion.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/list.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/jiffies.h>
#include <linux/compat.h>
#include <linux/amlogic/media/codec_mm/codec_mm.h>
#include "resourcemanage.h"
#define RESMAM_ENABLE_JSON (1)
#ifdef RESMAM_ENABLE_JSON
#include "cjson.h"
#endif
#define DEVICE_NAME "amresource_mgr"
#define DEVICE_CLASS_NAME "resource_mgr"
#define QUERY_SECURE_BUFFER (1)
#define QUERY_NO_SECURE_BUFFER (0)
#define RESMAN_VERSION (3)
#define SINGLE_SIZE (64)
#define EXT_MAX_SIZE (64 * 1024)
#define EXTCONFIGNAME "/vendor/etc/resman.json"
#define EXTCONFIGNAMELIX "/etc/resman.json"
struct {
int id;
char *name;
} resources_map[] = {
{RESMAN_ID_VFM_DEFAULT, "vfm_default"},
{RESMAN_ID_AMVIDEO, "amvideo"},
{RESMAN_ID_PIPVIDEO, "videopip"},
{RESMAN_ID_SEC_TVP, "tvp"},
{RESMAN_ID_TSPARSER, "tsparser"},
{RESMAN_ID_CODEC_MM, "codec_mm"},
{RESMAN_ID_ADC_PLL, "adc_pll"},
{RESMAN_ID_DECODER, "decoder"},
{RESMAN_ID_HWC, "hwc"},
{RESMAN_ID_DMX, "dmx"},
{RESMAN_ID_DI, "di"},
};
enum RESMAN_STATUS {
RESMAN_STATUS_INITED,
RESMAN_STATUS_ACQUIRED,
RESMAN_STATUS_PREEMPTED
};
/**
* struct resman_event - event list node
* @list: list entry
* @type: event type, see RESMAN_EVENT
*/
struct resman_event {
struct list_head list;
__u32 type;
};
/**
* struct resman_node - allocated resource node
* @slist: session list that own same resource
* @rlist: resource list that own by same session
* @pending_release: pending session for release
* @session_ptr: session owner
* @resource_ptr: resource owner
* @s: node data
*/
struct resman_node {
struct list_head slist;
struct list_head rlist;
struct completion pending_release;
void *session_ptr;
void *resource_ptr;
union {
struct {
char type;
} toggle;
struct {
__u32 score;
bool secure;
} codec_mm;
struct {
__u32 use;
} capacity;
} s;
};
/**
* struct resman_session - session data
* @id: session id
* @list: sessions list
* @resources: allocated resource list
* @events: events list
* @app_name: name of app
* @status: session status, see RESMAN_STATUS
* @comm: proc comm name
* @pid: thread id
* @tgid: pid
* @lock: lock of session
* @wq_event: wait queue for poll
* @app_type: type of session, see RESMAN_APP
*/
struct resman_session {
int id;
struct list_head list;
struct list_head resources;
struct list_head events;
char app_name[32];
int status;
char comm[TASK_COMM_LEN];
pid_t pid;
pid_t tgid;
struct mutex lock; /*session lock*/
wait_queue_head_t wq_event;
int app_type;
};
/**
* struct resman_resource - resource data
* @list: resource list
* @sessions: list of session that acquired this resource
* the first one always the oldest one
* @lock: resource lock
* @wq_release: wait queue for release
* @wq_acquire: wait queue for acquire
* @pending_acquire: pending session for acquire
* @id: resource id
* @name: name of resource
* @type: resource tye, see RESMAN_TYPE
* @acquire: hook of acquire
* @release: hook of release
* @value: value of resource
* @d: resource data
*
*/
struct resman_resource {
struct list_head list;
struct list_head sessions;
struct mutex lock; /*resource lock*/
wait_queue_head_t wq_release;
wait_queue_head_t wq_acquire;
atomic_t pending_acquire;
int id;
char name[32];
__u32 type;
bool (*acquire)(struct resman_session *sess,
struct resman_resource *resource,
struct resman_node *node,
bool preempt,
__u32 timeout,
char *arg);
void (*release)(struct resman_session *sess,
struct resman_resource *resource,
struct resman_node *node);
int value;
union {
/**
* @avail: max available value for counter
*/
struct {
__u32 avail;
} counter;
/**
* @total: total score
* @uhd: how much score per uhd instance
* @fhd: how much score per fhd instance
* @secure: max secure decoder count
*/
struct {
__u32 total;
__u32 uhd;
__u32 fhd;
__u32 secure;
atomic_t counter_secure;
atomic_t counter_nonsecure;
} codec_mm;
/**
* @total: max available capacity size
*/
struct {
__u32 total;
} capacity;
} d;
};
static int resman_debug = 1;
static int preempt_timeout_ms = 2500;
static struct list_head sessions_head;
static struct list_head resources_head;
static DEFINE_MUTEX(sessions_lock);
static DEFINE_MUTEX(resource_lock);
static dev_t resman_devno;
static int sess_id = 1;
static struct cdev *resman_cdev;
static struct class *resman_class;
static char *resman_configs;
static bool extloaded;
module_param(resman_debug, int, 0644);
module_param(preempt_timeout_ms, int, 0644);
#define dprintk(level, fmt, arg...) \
do { \
if (resman_debug >= (level)) \
pr_info("resman: " fmt, ## arg); \
} while (0)
/**
* resman_parser_kv() - parse key value pair
*
* @str: input string, must rw
* @k: key name
* @v: value to store
*
* parse key value pair like "foo:1"
*
* Return: true for success of false
*/
static inline bool resman_parser_kv(char *str, const char *k, __u32 *v)
{
bool ret = false;
char *sk, *sv;
sk = strsep(&str, ":");
sv = str;
if (k && v && sk && sv && !strcmp(sk, k) && !kstrtou32(sv, 0, v))
ret = true;
if (!ret)
dprintk(2, "parse %s failed %s\n", k, str);
return ret;
}
static struct resman_resource *resman_find_resource_by_id(int id)
{
struct resman_resource *ret = NULL;
struct list_head *pos, *tmp;
list_for_each_safe(pos, tmp, &resources_head) {
ret = list_entry(pos, struct resman_resource, list);
if (ret->id == id)
return ret;
}
return NULL;
}
static struct resman_resource *resman_find_resource_by_name(const char *name)
{
struct resman_resource *ret = NULL;
struct list_head *pos, *tmp;
list_for_each_safe(pos, tmp, &resources_head) {
ret = list_entry(pos, struct resman_resource, list);
if (!strcmp(name, ret->name))
return ret;
}
return NULL;
}
static struct resman_node *resman_find_node_by_resource_id
(struct resman_session *sess, int id)
{
struct resman_node *ret = NULL;
struct resman_resource *resource = NULL;
struct list_head *pos, *tmp;
list_for_each_safe(pos, tmp, &sess->resources) {
ret = list_entry(pos, struct resman_node, rlist);
resource = (struct resman_resource *)ret->resource_ptr;
if (resource->id == id)
return ret;
}
return NULL;
}
static int resman_count_codec_mm_session(bool secure)
{
struct resman_resource *resource;
int count = 0;
resource = resman_find_resource_by_id(RESMAN_ID_CODEC_MM);
if (!resource)
return 0;
if (secure)
count = atomic_read(&resource->d.codec_mm.counter_secure);
else
count = atomic_read(&resource->d.codec_mm.counter_nonsecure);
return count;
}
static bool resman_acquire_resource(struct resman_session *sess,
struct resman_resource *resource,
bool preempt,
__u32 timeout,
const char *arg_ro)
{
bool ret = false;
struct resman_node *node;
char *arg_rw = NULL;
if (arg_ro)
arg_rw = kstrdup(arg_ro, GFP_KERNEL);
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (atomic_read(&resource->pending_acquire) > 0)
wait_event_interruptible(resource->wq_acquire,
atomic_read(&resource->pending_acquire) == 0);
mutex_lock(&resource->lock);
dprintk(1, "%d acquire [%s], preempt %s\n",
sess->id,
resource->name,
preempt ? "yes" : "no");
if (resource->acquire(sess, resource, node, preempt, timeout, arg_rw)) {
list_add_tail(&node->rlist, &sess->resources);
list_add_tail(&node->slist, &resource->sessions);
init_completion(&node->pending_release);
node->session_ptr = (void *)sess;
node->resource_ptr = (void *)resource;
ret = true;
sess->status = RESMAN_STATUS_ACQUIRED;
dprintk(1, "%d acquire [%s] success\n",
sess->id,
resource->name);
} else {
dprintk(1, "%d acquire [%s] failed\n",
sess->id, resource->name);
}
mutex_unlock(&resource->lock);
kfree(arg_rw);
return ret;
}
static void resman_release_resource(struct resman_node *node)
{
struct resman_session *sess = node->session_ptr;
struct resman_resource *resource = node->resource_ptr;
if (!mutex_trylock(&resource->lock)) {
while (!completion_done(&node->pending_release))
complete(&node->pending_release);
mutex_lock(&resource->lock);
}
resource->release(sess, resource, node);
list_del(&node->rlist);
list_del(&node->slist);
dprintk(1, "%d release [%s]\n",
sess->id,
resource->name);
wake_up_interruptible_all(&resource->wq_release);
mutex_unlock(&resource->lock);
kfree(node);
}
static struct resman_session *resman_open_session(void)
{
struct resman_session *sess = NULL;
sess = kzalloc(sizeof(*sess), GFP_KERNEL);
if (!sess)
return NULL;
INIT_LIST_HEAD(&sess->resources);
INIT_LIST_HEAD(&sess->events);
mutex_init(&sess->lock);
init_waitqueue_head(&sess->wq_event);
strcpy(sess->comm, current->comm);
sess->tgid = current->tgid;
sess->pid = current->pid;
sess->status = RESMAN_STATUS_INITED;
sess->id = sess_id++;
mutex_lock(&sessions_lock);
list_add_tail(&sess->list, &sessions_head);
mutex_unlock(&sessions_lock);
return sess;
}
static void resman_close_session(struct resman_session *sess)
{
struct list_head *pos, *tmp;
struct resman_node *node;
struct resman_event *event;
mutex_lock(&sess->lock);
wake_up_interruptible_all(&sess->wq_event);
list_for_each_safe(pos, tmp, &sess->resources) {
node = list_entry(pos, struct resman_node, rlist);
resman_release_resource(node);
}
list_for_each_safe(pos, tmp, &sess->events) {
event = list_entry(pos, struct resman_event, list);
list_del(&event->list);
kfree(event);
}
mutex_unlock(&sess->lock);
mutex_lock(&sessions_lock);
list_del(&sess->list);
mutex_unlock(&sessions_lock);
kfree(sess);
}
static void resman_send_event(struct resman_session *sess, __u32 type)
{
struct resman_event *event;
event = kzalloc(sizeof(*event), GFP_KERNEL);
if (!event)
return;
event->type = type;
list_add_tail(&event->list, &sess->events);
}
static bool resman_preempt_session(struct resman_session *curr,
struct resman_node *node)
{
bool ret = false;
bool need_unlock = true;
struct resman_session *sess;
if (!node)
goto beach;
sess = node->session_ptr;
if (sess->status != RESMAN_STATUS_ACQUIRED)
goto beach;
if (!mutex_trylock(&sess->lock)) {
/*other session may waiting to release this resource*/
if (wait_for_completion_interruptible_timeout(&node->pending_release,
usecs_to_jiffies(10000)) <= 0)
goto beach;
need_unlock = false;
}
dprintk(1, "%d preempting %d %s\n",
curr->id,
sess->id,
sess->app_name);
sess->status = RESMAN_STATUS_PREEMPTED;
resman_send_event(sess, RESMAN_EVENT_PREEMPT);
wake_up_interruptible(&sess->wq_event);
if (need_unlock)
mutex_unlock(&sess->lock);
ret = true;
beach:
return ret;
}
/**
* resman_counter_preempt() - preempt for counter type
*
* @curr: current session data
* @resource: resource data
* Return: true for success or false
*/
static bool resman_counter_preempt(struct resman_session *curr,
struct resman_resource *resource)
{
bool ret = false;
struct resman_node *node, *selected = NULL;
struct resman_session *sess;
struct list_head *pos, *tmp;
list_for_each_safe(pos, tmp, &resource->sessions) {
node = list_entry(pos, struct resman_node, slist);
sess = node->session_ptr;
if (sess->status == RESMAN_STATUS_PREEMPTED) {
selected = node;
break;
}
}
if (!selected)
selected = list_first_entry_or_null(&resource->sessions,
struct resman_node, slist);
if (resman_preempt_session(curr, selected))
ret = true;
return ret;
}
/**
* resman_counter_acquire() - acquire counter resource
*
* @sess: session data
* @resource: resource data
* @node: node data
* @preempt: preempt or not
* @timeout: time out
* @arg: arg rw
*
* acquire a counter resource,
* Return: true for success or false
*/
static bool resman_counter_acquire(struct resman_session *sess,
struct resman_resource *resource,
struct resman_node *node,
bool preempt,
__u32 timeout,
char *arg)
{
bool ret = false;
long remain;
if (resource->value >= resource->d.counter.avail) {
if (preempt) {
if (!timeout)
timeout = preempt_timeout_ms;
if (!resman_counter_preempt(sess, resource))
timeout = 0;
}
if (timeout) {
atomic_inc(&resource->pending_acquire);
mutex_unlock(&resource->lock);
dprintk(1, "%d acquire wait\n", sess->id);
remain = wait_event_interruptible_timeout(resource->wq_release,
(resource->value <
resource->d.counter.avail),
msecs_to_jiffies(timeout));
dprintk(1, "%d acquire wait return %ld\n",
sess->id,
remain);
mutex_lock(&resource->lock);
atomic_dec(&resource->pending_acquire);
wake_up_interruptible_all(&resource->wq_acquire);
}
}
if (resource->value < resource->d.counter.avail) {
resource->value++;
ret = true;
}
return ret;
}
/**
* resman_counter_release() - release counter resource
*
* @sess: session data
* @resource: resource data
* @node: node data
*/
static void resman_counter_release(struct resman_session *sess,
struct resman_resource *resource,
struct resman_node *node)
{
if (resource->value > 0)
resource->value--;
}
/**
* resman_toggle_acquire() - acquire toggle resource
*
* @sess: session data
* @resource: resource data
* @node: node data
* @preempt: preempt or not
* @timeout: time out
* @arg: arg rw
* Return: true for success or false
*/
static bool resman_toggle_acquire(struct resman_session *sess,
struct resman_resource *resource,
struct resman_node *node,
bool preempt,
__u32 timeout,
char *arg)
{
bool ret = false;
if (arg) {
switch (*arg) {
case '+':
if (resource->value >= 0) {
resource->value++;
node->s.toggle.type = '+';
ret = true;
}
break;
case '-':
if (resource->value <= 0) {
resource->value--;
node->s.toggle.type = '-';
ret = true;
}
break;
default:
break;
}
}
return ret;
}
/**
* resman_toggle_release() - resman_toggle_release
*
* @sess: session data
* @resource: resource data
* @node: node data
*/
static void resman_toggle_release(struct resman_session *sess,
struct resman_resource *resource,
struct resman_node *node)
{
switch (node->s.toggle.type) {
case '+':
if (resource->value > 0)
resource->value--;
break;
case '-':
if (resource->value < 0)
resource->value++;
break;
default:
break;
}
node->s.toggle.type = 0;
}
/**
* resman_tvp_preempt() - resman_tvp_preempt
*
* @curr: current session data
* Return: true for success or false
*/
static bool resman_tvp_preempt(struct resman_session *curr)
{
bool ret = false;
struct resman_resource *resource;
struct resman_node *node, *selected = NULL;
struct resman_session *sess;
struct list_head *pos, *tmp;
resource = resman_find_resource_by_id(RESMAN_ID_CODEC_MM);
if (!resource)
return false;
/*Find the oldest non-secure decoder instance*/
list_for_each_safe(pos, tmp, &resource->sessions) {
node = list_entry(pos, struct resman_node, slist);
sess = node->session_ptr;
if (sess == curr || node->s.codec_mm.secure)
continue;
selected = node;
break;
}
if (resman_preempt_session(curr, selected))
ret = true;
return ret;
}
/**
* resman_tvp_acquire() - resman_tvp_acquire
*
* @sess: session data
* @resource: resource data
* @node: node data
* @preempt: preempt or not
* @timeout: time out
* @arg: arg rw
* Return: true for success or false
*/
static bool resman_tvp_acquire(struct resman_session *sess,
struct resman_resource *resource,
struct resman_node *node,
bool preempt,
__u32 timeout,
char *arg)
{
bool ret = false;
bool again = false;
long remain;
int size = 0;
int flags = 1;
int count;
if (!resman_parser_kv(arg, "size", (__u32 *)&size)) {
if (!strcmp(arg, "uhd"))
flags = 2;
}
do {
count = resman_count_codec_mm_session(false);
if (!codec_mm_enable_tvp(size, flags)) {
resource->value++;
ret = true;
break;
}
/*If TVP alloc failed, reclaim non-secure decoder instance*/
if (!preempt || !count)
break;
if (!timeout)
timeout = preempt_timeout_ms;
if (!resman_tvp_preempt(sess)) {
again = false;
timeout = 0;
} else {
again = true;
}
if (timeout) {
atomic_inc(&resource->pending_acquire);
mutex_unlock(&resource->lock);
dprintk(1, "%d acquire wait\n", sess->id);
remain = wait_event_interruptible_timeout(resource->wq_release,
(resman_count_codec_mm_session(false)
< count),
msecs_to_jiffies(timeout));
dprintk(1, "%d acquire wait return %ld\n",
sess->id,
remain);
mutex_lock(&resource->lock);
atomic_dec(&resource->pending_acquire);
wake_up_interruptible_all(&resource->wq_acquire);
}
} while (again);
return ret;
}
/**
* resman_tvp_release() - resman_tvp_release
*
* @sess: session data
* @resource: resource data
* @node: node data
*/
static void resman_tvp_release(struct resman_session *sess,
struct resman_resource *resource,
struct resman_node *node)
{
if (resource->value > 0) {
codec_mm_disable_tvp();
resource->value--;
}
}
/**
* resman_codec_mm_preempt() - resman_codec_mm_preempt
*
* @curr: current session data
* @resource: resource data
* @score: acquired score
* @secure: secure or not
* Return: true for success or false
*/
static bool resman_codec_mm_preempt(struct resman_session *curr,
struct resman_resource *resource,
__u32 score,
bool secure)
{
bool ret = false;
int secure_count;
struct resman_node *node, *selected;
struct resman_session *sess;
struct list_head *pos, *tmp;
__u32 new_score;
secure_count = atomic_read(&resource->d.codec_mm.counter_secure);
/*Reclaim secure instance if exceed limitation*/
list_for_each_safe(pos, tmp, &resource->sessions) {
if (!secure || secure_count < resource->d.codec_mm.secure)
break;
node = list_entry(pos, struct resman_node, slist);
sess = node->session_ptr;
if (node->s.codec_mm.secure) {
if (resman_preempt_session(curr, node))
ret = true;
secure_count--;
}
}
do {
selected = NULL;
/*Calculate the available score*/
new_score = resource->value;
list_for_each_safe(pos, tmp, &resource->sessions) {
node = list_entry(pos, struct resman_node, slist);
sess = node->session_ptr;
if (sess->status == RESMAN_STATUS_PREEMPTED)
new_score -= node->s.codec_mm.score;
}
if (new_score + score <= resource->d.codec_mm.total)
break;
/*Find the oldest session not preempted*/
list_for_each_safe(pos, tmp, &resource->sessions) {
node = list_entry(pos, struct resman_node, slist);
sess = node->session_ptr;
if (sess->status == RESMAN_STATUS_ACQUIRED) {
selected = node;
break;
}
}
if (resman_preempt_session(curr, selected)) {
new_score -= selected->s.codec_mm.score;
ret = true;
}
} while (selected &&
(new_score + score > resource->d.codec_mm.total));
return ret;
}
static bool resman_codec_mm_enough(struct resman_resource *resource,
__u32 score,
bool secure)
{
bool enough = true;
if (!secure && (codec_mm_get_free_size() >> 20 < score)) {
enough = false;
dprintk(2, "free size 0x%x\n", codec_mm_get_free_size());
} else if (secure && ((codec_mm_get_tvp_free_size() + codec_mm_get_free_size()) >> 20)
< score) {
enough = false;
dprintk(2, "free size 0x%x\n", codec_mm_get_tvp_free_size() +
+ codec_mm_get_free_size());
} else if (resource->value + score > resource->d.codec_mm.total)
enough = false;
return enough;
}
/**
* resman_codec_mm_acquire() - resman_codec_mm_acquire
*
* @sess: session data
* @resource: resource data
* @node: node data
* @preempt: preempt or not
* @timeout: time out
* @arg: arg rw
* Return: true for success or false
*/
static bool resman_codec_mm_acquire(struct resman_session *sess,
struct resman_resource *resource,
struct resman_node *node,
bool preempt,
__u32 timeout,
char *arg)
{
bool ret = false;
long remain;
__u32 score = resource->d.codec_mm.fhd;
bool secure = false;
char *opt;
while ((opt = strsep(&arg, ","))) {
if (!strncmp(opt, "size", 4))
resman_parser_kv(opt, "size", &score);
else if (!strcmp(opt, "single"))
/*single mode, 64M codec mm size is enough*/
score = SINGLE_SIZE;
else if (!strcmp(opt, "uhd"))
score = resource->d.codec_mm.uhd;
else if (!strcmp(opt, "secure"))
secure = true;
}
dprintk(1, "%d score %d secure %s\n",
sess->id,
score,
secure ? "yes" : "no");
if (!resman_codec_mm_enough(resource, score, secure)) {
if (preempt) {
if (!timeout)
timeout = preempt_timeout_ms;
if (!resman_codec_mm_preempt(sess, resource,
score, secure))
timeout = 0;
}
if (timeout) {
atomic_inc(&resource->pending_acquire);
mutex_unlock(&resource->lock);
dprintk(1, "%d acquire wait\n", sess->id);
remain = wait_event_interruptible_timeout(resource->wq_release,
resman_codec_mm_enough(resource,
score,
secure),
msecs_to_jiffies(timeout));
dprintk(1, "%d acquire wait return %ld\n",
sess->id,
remain);
mutex_lock(&resource->lock);
atomic_dec(&resource->pending_acquire);
wake_up_interruptible_all(&resource->wq_acquire);
}
}
if (resman_codec_mm_enough(resource, score, secure)) {
node->s.codec_mm.score = score;
node->s.codec_mm.secure = secure;
resource->value += score;
if (secure)
atomic_inc(&resource->d.codec_mm.counter_secure);
else
atomic_inc(&resource->d.codec_mm.counter_nonsecure);
ret = true;
}
return ret;
}
/**
* resman_codec_mm_release() - resman_codec_mm_release
*
* @sess: session data
* @resource: resource data
* @node: node data
*/
static void resman_codec_mm_release(struct resman_session *sess,
struct resman_resource *resource,
struct resman_node *node)
{
if (node->s.codec_mm.secure)
atomic_dec(&resource->d.codec_mm.counter_secure);
else
atomic_dec(&resource->d.codec_mm.counter_nonsecure);
if (resource->value >= node->s.codec_mm.score)
resource->value -= node->s.codec_mm.score;
}
/**
* void resman_codec_mm_probe() - probe codec mm parameters
*
* @resource: resorce context
*/
static void resman_codec_mm_probe(struct resman_resource *resource)
{
int total_bytes;
int tvp_fhd, tvp_uhd;
codec_mm_get_default_tvp_size(&tvp_fhd, &tvp_uhd);
total_bytes = codec_mm_get_total_size();
if (total_bytes >= tvp_fhd + tvp_uhd)
resource->d.codec_mm.secure = 2;
else
resource->d.codec_mm.secure = 1;
resource->d.codec_mm.total = total_bytes >> 20;
resource->d.codec_mm.uhd = tvp_uhd >> 20;
resource->d.codec_mm.fhd = tvp_fhd >> 20;
}
static bool resman_capacity_enough(struct resman_resource *resource,
__u32 size)
{
bool enough = true;
if ((resource->value + size) > resource->d.capacity.total)
enough = false;
dprintk(2, "[%s] value 0x%x score 0x%x total 0x%x\n",
resource->name, resource->value,
size, resource->d.capacity.total);
return enough;
}
/**
* resman_capacity_preempt() - resman_capacity_preempt
*
* @curr: current session data
* @resource: resource data
* @size: need resman to release resource size
* Return: true for success or false
*/
static bool resman_capacity_preempt(struct resman_session *curr,
struct resman_resource *resource,
__u32 size)
{
bool ret = false;
struct resman_node *node, *selected;
struct resman_session *sess;
struct list_head *pos, *tmp;
__u32 used_size;
do {
selected = NULL;
/*Calculate the available size*/
used_size = resource->value;
list_for_each_safe(pos, tmp, &resource->sessions) {
node = list_entry(pos, struct resman_node, slist);
sess = node->session_ptr;
if (sess->status == RESMAN_STATUS_PREEMPTED)
used_size -= node->s.capacity.use;
}
dprintk(2, "used_size 0x%x size 0x%x\n", used_size, size);
if ((used_size + size) <= resource->d.capacity.total)
break;
/*Find the oldest session not preempted*/
list_for_each_safe(pos, tmp, &resource->sessions) {
node = list_entry(pos, struct resman_node, slist);
sess = node->session_ptr;
if (sess->status == RESMAN_STATUS_ACQUIRED) {
selected = node;
break;
}
}
if (resman_preempt_session(curr, selected)) {
used_size -= selected->s.capacity.use;
ret = true;
}
} while (selected &&
(used_size + size > resource->d.capacity.total));
return ret;
}
/**
* resman_capacity_acquire() - resman_capacity_acquire
*
* @sess: session data
* @resource: resource data
* @node: node data
* @preempt: preempt or not
* @timeout: time out
* @arg: arg rw
* Return: true for success or false
*/
static bool resman_capacity_acquire(struct resman_session *sess,
struct resman_resource *resource,
struct resman_node *node,
bool preempt,
__u32 timeout,
char *arg)
{
bool ret = false;
long remain;
__u32 size = 0;
char *opt;
while ((opt = strsep(&arg, ","))) {
if (!strncmp(opt, "size", 4))
resman_parser_kv(opt, "size", &size);
}
if (size <= 0) {
dprintk(0, "%d size %d parameter format:size:X!\n", sess->id, size);
return ret;
}
if (!resman_capacity_enough(resource, size)) {
if (preempt) {
if (!timeout)
timeout = preempt_timeout_ms;
if (!resman_capacity_preempt(sess, resource, size))
timeout = 0;
}
if (timeout) {
atomic_inc(&resource->pending_acquire);
mutex_unlock(&resource->lock);
dprintk(1, "%d acquire wait\n", sess->id);
remain =
wait_event_interruptible_timeout(resource->wq_release,
resman_capacity_enough(resource, size),
msecs_to_jiffies(timeout));
dprintk(1, "%d acquire wait return %ld\n",
sess->id, remain);
mutex_lock(&resource->lock);
atomic_dec(&resource->pending_acquire);
wake_up_interruptible_all(&resource->wq_acquire);
}
}
if (resman_capacity_enough(resource, size)) {
node->s.capacity.use = size;
resource->value += size;
ret = true;
}
return ret;
}
/**
* resman_codec_mm_release() - resman_codec_mm_release
*
* @sess: session data
* @resource: resource data
* @node: node data
*/
static void resman_capacity_release(struct resman_session *sess,
struct resman_resource *resource,
struct resman_node *node)
{
if (resource->value >= node->s.capacity.use)
resource->value -= node->s.capacity.use;
}
static bool resman_create_resource(const char *name,
__u32 type,
char *arg)
{
struct resman_resource *resource = NULL, *restmp = NULL;
char *opt;
int i;
char r0 = 0;
char r1 = 0;
char r2 = 0;
char r3 = 0;
resource = kzalloc(sizeof(*resource), GFP_KERNEL);
if (!resource || !name)
goto error;
resource->id = -1;
for (i = 0; i < ARRAY_SIZE(resources_map) && resources_map[i].name;
i++) {
if (!strcmp(resources_map[i].name, name)) {
restmp = resman_find_resource_by_id(resources_map[i].id);
if (restmp) {
dprintk(3, "notice: [%s] exist\n", name);
kfree(resource);
resource = restmp;
} else {
resource->id = resources_map[i].id;
strncpy(resource->name, resources_map[i].name,
sizeof(resource->name));
resource->name[sizeof(resource->name) - 1] = 0;
}
break;
}
}
if (i >= ARRAY_SIZE(resources_map)) {
if (strlen(name) < 4) {
dprintk(0, "At least 4 letters are required for resource name\n");
goto error;
}
r0 = name[0];
r1 = name[1];
r2 = name[2];
r3 = name[3];
resource->id = (r0 << 24) | (r1 << 16) | (r2 << 8) | r3;
restmp = resman_find_resource_by_id(resource->id);
if (restmp) {
dprintk(3, "notice: [%s] exist\n", name);
kfree(resource);
resource = restmp;
} else {
strncpy(resource->name, name, strlen(name));
dprintk(3, "ext resource id[%d] name %s\n",
resource->id, resource->name);
}
}
if (resource->id < 0) {
dprintk(0, "%s error, [%s] invalid\n", __func__, name);
goto error;
}
switch (type) {
default:
dprintk(0, "%s error, unkonw type\n", __func__);
goto error;
case RESMAN_TYPE_COUNTER:
resource->acquire = resman_counter_acquire;
resource->release = resman_counter_release;
if (!resman_parser_kv(arg, "avail",
&resource->d.counter.avail)) {
goto error;
}
break;
case RESMAN_TYPE_TOGGLE:
resource->acquire = resman_toggle_acquire;
resource->release = resman_toggle_release;
break;
case RESMAN_TYPE_TVP:
resource->acquire = resman_tvp_acquire;
resource->release = resman_tvp_release;
break;
case RESMAN_TYPE_CODEC_MM:
resource->acquire = resman_codec_mm_acquire;
resource->release = resman_codec_mm_release;
opt = strsep(&arg, ",");
if (!resman_parser_kv(opt, "total",
&resource->d.codec_mm.total)) {
goto error;
}
if (resource->d.codec_mm.total) {
opt = strsep(&arg, ",");
if (!resman_parser_kv(opt, "uhd",
&resource->d.codec_mm.uhd)) {
goto error;
}
opt = strsep(&arg, ",");
if (!resman_parser_kv(opt, "fhd",
&resource->d.codec_mm.fhd)) {
goto error;
}
opt = strsep(&arg, ",");
if (!resman_parser_kv(opt, "secure",
&resource->d.codec_mm.secure)) {
goto error;
}
} else {
resman_codec_mm_probe(resource);
}
atomic_set(&resource->d.codec_mm.counter_secure, 0);
atomic_set(&resource->d.codec_mm.counter_nonsecure, 0);
break;
case RESMAN_TYPE_CAPACITY_SIZE:
resource->acquire = resman_capacity_acquire;
resource->release = resman_capacity_release;
opt = strsep(&arg, ",");
if (!resman_parser_kv(opt, "total",
&resource->d.capacity.total)) {
goto error;
}
break;
}
resource->type = type;
if (!restmp) {//resource exist just need update the parameter
INIT_LIST_HEAD(&resource->sessions);
mutex_init(&resource->lock);
list_add_tail(&resource->list, &resources_head);
init_waitqueue_head(&resource->wq_release);
init_waitqueue_head(&resource->wq_acquire);
atomic_set(&resource->pending_acquire, 0);
}
return true;
error:
kfree(resource);
return false;
}
static void resman_destroy_resource(struct resman_resource *resource)
{
if (list_empty(&resource->sessions)) {
list_del(&resource->list);
kfree(resource);
}
}
static void all_resource_uninit(void)
{
struct resman_resource *resource;
struct resman_node *node;
struct list_head *pos, *tmp;
struct list_head *pos1, *tmp1;
mutex_lock(&resource_lock);
kfree(resman_configs);
resman_configs = NULL;
list_for_each_safe(pos, tmp, &resources_head) {
resource = list_entry(pos, struct resman_resource, list);
list_for_each_safe(pos1, tmp1, &resource->sessions) {
node = list_entry(pos, struct resman_node, slist);
resman_release_resource(node);
}
resman_destroy_resource(resource);
}
mutex_unlock(&resource_lock);
}
#ifdef RESMAM_ENABLE_JSON
/**
* resman_config_from_json() - parse resource config
*
* @buf: config string
*
* Parse config
* The config is a json file that contains various of resource config arrays
* Every resource config contains resource name and type and optional args
*
* config_string
* { "resman_config": [
* {
* "name": "resname1",
* "type": type1,
* "args": "args1"
* },
* {
* "name": "resname2",
* "type": type1,
* "args": "args1"
* }
* ]
* }
*
* Return: true for success or false
*/
static bool resman_config_from_json(char *buf)
{
bool ret = true;
char *configs = kstrdup(buf, GFP_KERNEL);
char *newconfig = NULL;
struct cjson *root = NULL, *arrs_node = NULL;
struct cjson *node = NULL, *name_node = NULL, *type_node = NULL, *args_node = NULL;
if (!configs | !buf) {
dprintk(0, "read %s\n", buf);
return false;
}
if (configs[strlen(configs) - 1] == '\n')
configs[strlen(configs) - 1] = 0;
root = cjson_parse(configs);
if (root == 0) {
dprintk(0, "json null or has error, please check the json file:[ %s]\n", configs);
ret = false;
goto error;
}
arrs_node = cjson_getobjectitem(root, "resman_config");
if (arrs_node == 0) {
ret = false;
goto error;
}
mutex_lock(&resource_lock);
if (arrs_node->type == cjson_array) {
node = arrs_node->child;
while (node) {
name_node = cjson_getobjectitem(node, "name");
if (!name_node) {
ret = false;
mutex_unlock(&resource_lock);
goto error;
}
dprintk(3, "%s:%s\n", name_node->string, name_node->valuestring);
type_node = cjson_getobjectitem(node, "type");
if (!type_node) {
ret = false;
mutex_unlock(&resource_lock);
goto error;
}
dprintk(3, "type:%d\n", type_node->valueint);
args_node = cjson_getobjectitem(node, "args");
if (args_node)
dprintk(3, "args:%s\n", args_node->valuestring);
else
dprintk(3, "args = NULL");
if (!resman_create_resource(name_node->valuestring, type_node->valueint,
args_node ? args_node->valuestring : NULL)) {
ret = false;
break;
}
node = node->next;
}
}
mutex_unlock(&resource_lock);
if (!ret) {
dprintk(0, "%s parse config failed\n%s\n", __func__, buf);
all_resource_uninit();
} else if (resman_configs) {
newconfig = kzalloc(strlen(resman_configs) + strlen(buf) + 1,
GFP_KERNEL);
if (newconfig) {
memcpy(newconfig, resman_configs,
strlen(resman_configs));
memcpy(newconfig + strlen(resman_configs),
buf, strlen(buf));
kfree(resman_configs);
resman_configs = newconfig;
}
} else {
resman_configs = kstrdup(buf, GFP_KERNEL);
}
error:
cjson_delete(root);
kfree(configs);
return ret;
}
#else
/**
* resman_parser_config() - parse resource config
*
* @buf: config string
*
* Parse config
* The config is a string that contains various of resource config split by ';'
* Every resource config contains resource name and key-value pairs split by ','
*
* config_string
* resource_config[];
* {
* resource_name,
* type: type_value,
* [key0: int_value0, key1:int_value1, ...]
* }
* eg: "resource0,type:1;resource1,type:1,key0:1,key1:2;"
*
* Return: true for success or false
*/
static bool resman_parser_config(const char *buf)
{
bool ret = true;
char *config, *cur;
char *name, *type_str, *arg;
__u32 type;
char *configs = kstrdup(buf, GFP_KERNEL);
char *newconfig = NULL;
if (!configs | !buf)
return false;
cur = configs;
if (configs[strlen(configs) - 1] == '\n')
configs[strlen(configs) - 1] = 0;
mutex_lock(&resource_lock);
while ((config = strsep(&cur, ";"))) {
name = strsep(&config, ",");
if (!name) {
ret = false;
break;
}
if (!strlen(name))
break;
type_str = strsep(&config, ",");
if (!type_str) {
ret = false;
break;
}
;
if (!resman_parser_kv(type_str, "type", &type)) {
ret = false;
break;
}
arg = config;
if (!resman_create_resource(name, type, arg)) {
ret = false;
break;
}
}
mutex_unlock(&resource_lock);
if (!ret) {
dprintk(0, "%s parse config failed\n%s\n", __func__, buf);
all_resource_uninit();
} else if (resman_configs) {
newconfig = kzalloc(strlen(resman_configs) + strlen(buf) + 1,
GFP_KERNEL);
if (newconfig) {
memcpy(newconfig, resman_configs,
strlen(resman_configs));
memcpy(newconfig + strlen(resman_configs),
buf, strlen(buf));
kfree(resman_configs);
resman_configs = newconfig;
}
} else {
resman_configs = kstrdup(buf, GFP_KERNEL);
}
kfree(configs);
return ret;
}
#endif
static bool ext_resource_init(void)
{
bool ret = false;
struct file *extfile = NULL;
struct kstat stat;
char *extfilename = EXTCONFIGNAME;
char *extconfig = NULL;
mm_segment_t old_fs;
loff_t pos = 0;
unsigned int flen = 0, readlen = 0;
int error;
old_fs = get_fs();
extfile = filp_open(extfilename, O_RDONLY, 0);
if (IS_ERR_OR_NULL(extfile)) {
dprintk(2, "There(%s) is no ext config or read failed\n", extfilename);
extfilename = EXTCONFIGNAMELIX;
extfile = filp_open(extfilename, O_RDONLY, 0);
if (IS_ERR_OR_NULL(extfile)) {
dprintk(2, "There(%s) is no ext config or read failed\n", extfilename);
return false;
}
}
set_fs(KERNEL_DS);
error = vfs_stat(extfilename, &stat);
if (error) {
dprintk(0, "vfs_stat fail %s\n", extfilename);
ret = false;
goto error;
}
if (flen > EXT_MAX_SIZE)
dprintk(0, "extconfig file is too large%lld\n", stat.size);
else
dprintk(3, "%s file size %lld\n", __func__, stat.size);
flen = (stat.size > EXT_MAX_SIZE) ? EXT_MAX_SIZE : stat.size;
extconfig = kzalloc(flen + 8, GFP_KERNEL);
if (extconfig) {
readlen = vfs_read(extfile, extconfig,
flen, &pos);
if (readlen < flen)
dprintk(0, "size %d read %d\n", flen, readlen);
#ifdef RESMAM_ENABLE_JSON
ret = resman_config_from_json(extconfig);
#else
resman_parser_config(extconfig);
#endif
kfree(extconfig);
}
error:
filp_close(extfile, NULL);
set_fs(old_fs);
return ret;
}
static void all_resource_init(void)
{
#ifdef RESMAM_ENABLE_JSON
/*
* default resman_config json
* {
* "resman_config": [
* {
* "name": "vfm_default",
* "type": 1,
* "args": "avail:1"
* },
* {
* "name": "amvideo",
* "type": 1,
* "args": "avail:1"
* },
* {
* "name": "videopip",
* "type": 1,
* "args": "avail:1"
* },
* {
* "name": "tvp",
* "type": 3
* },
* {
* "name": "tsparser",
* "type": 1,
* "args": "avail:1"
* },
* {
* "name": "codec_mm",
* "type": 4,
* "args": "total:0"
* },
* {
* "name": "adc_pll",
* "type": 1,
* "args": "avail:1"
* },
* {
* "name": "decoder",
* "type": 1,
* "args": "avail:9"
* }
* ]
* }
*/
char *default_configs = NULL;
int len = 0, i = 0;
char c[8][100] = {
"{\"resman_config\":[{\"name\":\"vfm_default\",\"type\":1,\"args\":\"avail:1\"},",
"{\"name\": \"amvideo\",\"type\": 1,\"args\": \"avail:1\"},",
"{\"name\": \"videopip\",\"type\": 1,\"args\": \"avail:1\"},",
"{\"name\": \"tvp\",\"type\": 3},",
"{\"name\": \"tsparser\",\"type\": 1,\"args\": \"avail:1\"},",
"{\"name\": \"codec_mm\",\"type\": 4,\"args\": \"total:0\"},",
"{\"name\": \"adc_pll\",\"type\": 1,\"args\": \"avail:1\"},",
"{\"name\": \"decoder\",\"type\": 1,\"args\": \"avail:9\"}]}"};
len = strlen(c[0]) + strlen(c[1]) + strlen(c[2]) + strlen(c[3]) +
strlen(c[4]) + strlen(c[5]) + strlen(c[6]) + strlen(c[7]);
default_configs = kzalloc(len + 1, GFP_KERNEL);
len = 0;
if (default_configs) {
for (i = 0; i < 8; i++) {
memcpy(default_configs + len, c[i], strlen(c[i]));
len += strlen(c[i]);
}
}
#else
char *default_configs =
"vfm_default,type:1,avail:1;"
"amvideo,type:1,avail:1;"
"videopip,type:1,avail:1;"
"tvp,type:3;"
"tsparser,type:1,avail:1;"
"codec_mm,type:4,total:0;"
"adc_pll,type:1,avail:1;"
"decoder,type:1,avail:9;"
"dmx,type:1,avail:4;"
"di,type:1,avail:4;"
"hwc,type:1,avail:9;";
#endif
INIT_LIST_HEAD(&sessions_head);
INIT_LIST_HEAD(&resources_head);
#ifdef RESMAM_ENABLE_JSON
resman_config_from_json(default_configs);
kfree(default_configs);
#else
resman_parser_config(default_configs);
#endif
}
static long resman_ioctl_acquire(struct resman_session *sess,
unsigned long para)
{
long r = 0;
struct resman_resource *resource;
struct resman_para __user *argp = (void __user *)para;
struct resman_para resman;
int selec_res;
bool preempt = false;
__u32 timeout = 0;
char *arg = NULL;
if (copy_from_user((void *)&resman, argp, sizeof(resman))) {
/*Compat with old userspace lib*/
selec_res = (int)para;
} else {
selec_res = resman.k;
preempt = !!resman.v.acquire.preempt;
timeout = resman.v.acquire.timeout;
arg = resman.v.acquire.arg;
}
resource = resman_find_resource_by_id(selec_res);
if (!resource)
return -EINVAL;
mutex_lock(&sess->lock);
if (resman_find_node_by_resource_id(sess, selec_res)) {
r = 0;
goto beach;
}
if (!resman_acquire_resource(sess, resource, preempt, timeout, arg))
r = -EBUSY;
beach:
mutex_unlock(&sess->lock);
return r;
}
static int resman_estimate_tvp_available(int size)
{
int free_size = codec_mm_get_free_size();
int tvp_fhd, tvp_uhd;
int mode = 0;
free_size += codec_mm_get_tvp_free_size();
if (size <= 0) {
codec_mm_get_default_tvp_size(&tvp_fhd, &tvp_uhd);
if (free_size >= tvp_uhd)
mode = 2;
else if (free_size > tvp_fhd)
mode = 1;
} else {
if (free_size >= size)
mode = 2;
}
return mode;
}
static int resman_codec_mm_available(int issecure)
{
int avail = 0;
dprintk(2, "issecure %d\n", issecure);
if (issecure == QUERY_NO_SECURE_BUFFER)
avail = codec_mm_get_free_size() >> 20;
if (issecure == QUERY_SECURE_BUFFER) {
avail = (codec_mm_get_free_size() +
codec_mm_get_tvp_free_size()) >> 20;
}
dprintk(2, "avail %d\n", avail);
return avail;
}
static long resman_ioctl_query(struct resman_session *sess, unsigned long para)
{
long r = 0;
int selec_res = 0;
struct resman_para __user *argp = (void __user *)para;
char usage[32];
struct resman_para resman;
struct resman_resource *resource;
memset(usage, 0, sizeof(usage));
if (copy_from_user((void *)&resman, argp, sizeof(resman))) {
return -EINVAL;
}
selec_res = resman.k;
resource = resman_find_resource_by_id(selec_res);
if (resource) {
strncpy(resman.v.query.name,
resource->name, sizeof(resman.v.query.name));
resman.v.query.type = resource->type;
switch (resource->type) {
case RESMAN_TYPE_COUNTER:
resman.v.query.value = resource->value;
resman.v.query.avail =
resource->d.counter.avail;
break;
case RESMAN_TYPE_CODEC_MM:
resman.v.query.avail =
resman_codec_mm_available(resman.v.query.value);
resman.v.query.value = codec_mm_get_total_size() >> 20;
break;
case RESMAN_TYPE_CAPACITY_SIZE:
resman.v.query.value = resource->value;
resman.v.query.avail =
(resource->d.capacity.total > resource->value) ?
(resource->d.capacity.total - resource->value) : 0;
break;
case RESMAN_TYPE_TVP:
resman.v.query.avail =
resman_estimate_tvp_available(resman.v.query.value);
break;
default:
break;
}
if (copy_to_user((void *)argp,
&resman, sizeof(resman)))
r = -EFAULT;
} else {
r = -EINVAL;
}
return r;
}
static long resman_ioctl_release(struct resman_session *sess,
unsigned long para)
{
long r = 0;
int selec_res = (int)para;
struct resman_resource *resource;
struct resman_node *node;
dprintk(2, "%d appname:%s, type=%d\n",
sess->id,
sess->app_name,
sess->app_type);
resource = resman_find_resource_by_id(selec_res);
if (!resource)
return -EINVAL;
mutex_lock(&sess->lock);
node = resman_find_node_by_resource_id(sess, selec_res);
if (node)
resman_release_resource(node);
mutex_unlock(&sess->lock);
return r;
}
static long resman_ioctl_setappinfo(struct resman_session *sess,
unsigned long para)
{
long r = 0;
struct app_info __user *argp = (void __user *)para;
struct app_info appinfo;
if (copy_from_user((void *)&appinfo, argp, sizeof(struct app_info)) ||
!sess) {
r = -EINVAL;
} else {
strncpy(sess->app_name, appinfo.app_name,
sizeof(sess->app_name));
sess->app_name[sizeof(sess->app_name) - 1] = '\0';
sess->app_type = appinfo.app_type;
dprintk(1, "%d appname:%s, type=%d\n",
sess->id,
sess->app_name,
sess->app_type);
}
return r;
}
static long resman_ioctl_checksupportres(struct resman_session *sess,
unsigned long para)
{
long r = 0;
struct resman_para __user *argp = (void __user *)para;
struct resman_para resman;
memset(&resman, 0, sizeof(resman));
if (copy_from_user((void *)&resman, argp, sizeof(resman))) {
r = -EINVAL;
} else {
if (resman.k > sizeof(resman.v.support.name) - 1)
return -EINVAL;
if (resman_find_resource_by_name(resman.v.support.name))
return 0;
r = -EINVAL;
}
return r;
}
static long resman_ioctl_release_all(struct resman_session *sess,
unsigned long para)
{
long r = 0;
struct resman_node *node;
struct list_head *pos, *tmp;
mutex_lock(&sess->lock);
list_for_each_safe(pos, tmp, &sess->resources) {
node = list_entry(pos, struct resman_node, rlist);
resman_release_resource(node);
}
mutex_unlock(&sess->lock);
return r;
}
#define APPEND_ATTR_BUF(format, args...) { \
size += snprintf(buf + size, PAGE_SIZE - size, format, args); \
}
static ssize_t usage_show(struct class *class,
struct class_attribute *attr, char *buf)
{
struct list_head *pos1, *tmp1;
struct list_head *pos2, *tmp2;
struct resman_resource *resource;
struct resman_session *sess;
ssize_t size = 0;
int i, j;
mutex_lock(&resource_lock);
j = 0;
list_for_each_safe(pos1, tmp1, &resources_head) {
resource = list_entry(pos1, struct resman_resource, list);
for (i = 0; i < j; i++)
APPEND_ATTR_BUF("%c ", '|')
APPEND_ATTR_BUF("%s %d\n", resource->name, resource->value)
j++;
}
for (i = 0; i < j; i++)
APPEND_ATTR_BUF("%c ", '|')
APPEND_ATTR_BUF("%-32s", "name")
APPEND_ATTR_BUF("%-10s", "status")
APPEND_ATTR_BUF("%-16s\n", "proc/PID/TID")
mutex_lock(&sessions_lock);
list_for_each_safe(pos2, tmp2, &sessions_head) {
sess = list_entry(pos2, struct resman_session, list);
list_for_each_safe(pos1, tmp1, &resources_head) {
resource = list_entry(pos1,
struct resman_resource, list);
if (resman_find_node_by_resource_id(sess, resource->id))
APPEND_ATTR_BUF("%-2c", 'x')
else
APPEND_ATTR_BUF("%-2c", ' ')
}
APPEND_ATTR_BUF("%-32s", sess->app_name)
switch (sess->status) {
case RESMAN_STATUS_INITED:
APPEND_ATTR_BUF("%-10s", "inited")
break;
case RESMAN_STATUS_ACQUIRED:
APPEND_ATTR_BUF("%-10s", "acquired")
break;
case RESMAN_STATUS_PREEMPTED:
APPEND_ATTR_BUF("%-10s", "preempted")
break;
default:
APPEND_ATTR_BUF("%-10s", "unknown")
break;
}
APPEND_ATTR_BUF("%s/%d/%d\n",
sess->comm,
sess->tgid,
sess->pid)
}
mutex_unlock(&sessions_lock);
mutex_unlock(&resource_lock);
APPEND_ATTR_BUF("%s", "\n")
return size;
}
static ssize_t config_show(struct class *class,
struct class_attribute *attr,
char *buf)
{
ssize_t size = 0;
if (resman_configs)
APPEND_ATTR_BUF("%s\n", resman_configs)
return size;
}
#undef APPEND_ATTR_BUF
static ssize_t config_store(struct class *class,
struct class_attribute *attr,
const char *buf, size_t size)
{
all_resource_uninit();
#ifdef RESMAM_ENABLE_JSON
resman_config_from_json((char *)buf);
#else
resman_parser_config(buf);
#endif
return size;
}
static ssize_t extconfig_show(struct class *class,
struct class_attribute *attr,
char *buf)
{
ssize_t size = 0;
if (resman_configs)
dprintk(0, "%s\n", resman_configs);
return size;
}
static ssize_t extconfig_store(struct class *class,
struct class_attribute *attr,
const char *buf, size_t size)
{
int r;
int val;
if (extloaded && resman_debug != 4) {
dprintk(0, "extconfig has already loaded\n");
return size;
}
r = kstrtoint(buf, 10, &val);
if (r < 0) {
dprintk(0, "extcofig format has error\n");
return -EINVAL;
}
if (val == 1 && ext_resource_init())
extloaded = true;
return size;
}
static ssize_t ver_show(struct class *class,
struct class_attribute *attr,
char *buf)
{
sprintf(buf, "%d\n", RESMAN_VERSION);
return RESMAN_VERSION;
}
/* ------------------------------------------------------------------
* File operations for the device
* ------------------------------------------------------------------
*/
int resman_open(struct inode *inode, struct file *filp)
{
struct resman_session *sess;
sess = resman_open_session();
if (!sess)
return -ENOMEM;
filp->private_data = sess;
dprintk(2, "%d open\n",
sess->id);
return 0;
}
ssize_t resman_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
ssize_t ret = -EFAULT;
struct resman_session *sess;
struct resman_event *event;
sess = filp->private_data;
if (len < sizeof(__u32))
return -EINVAL;
mutex_lock(&sess->lock);
event = list_first_entry_or_null(&sess->events,
struct resman_event, list);
if (event) {
if (!copy_to_user(buf, &event->type, sizeof(event->type))) {
ret = sizeof(event->type);
list_del(&event->list);
kfree(event);
}
}
mutex_unlock(&sess->lock);
return ret;
}
unsigned int resman_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct resman_session *sess;
sess = filp->private_data;
poll_wait(filp, &sess->wq_event, wait);
if (!list_empty(&sess->events))
mask |= POLL_IN | POLLRDNORM;
return mask;
}
long resman_ioctl(struct file *filp, unsigned int cmd, unsigned long para)
{
long retval = 0;
struct resman_session *sess;
if (_IOC_TYPE(cmd) != RESMAN_IOC_MAGIC) {
dprintk(0, "[%s] error cmd!\n", __func__);
return -EINVAL;
}
sess = filp->private_data;
if (!sess)
return -EINVAL;
dprintk(3, "%d ioctl cmd:%x\n", sess->id, cmd);
switch (cmd) {
case RESMAN_IOC_ACQUIRE_RES:
retval = resman_ioctl_acquire(sess, para);
break;
case RESMAN_IOC_QUERY_RES:
retval = resman_ioctl_query(sess, para);
break;
case RESMAN_IOC_RELEASE_RES:
retval = resman_ioctl_release(sess, para);
break;
case RESMAN_IOC_SETAPPINFO:
retval = resman_ioctl_setappinfo(sess, para);
break;
case RESMAN_IOC_SUPPORT_RES:
retval = resman_ioctl_checksupportres(sess, para);
break;
case RESMAN_IOC_RELEASE_ALL:
retval = resman_ioctl_release_all(sess, para);
break;
default:
retval = -EINVAL;
break;
}
return retval;
}
#ifdef CONFIG_COMPAT
static long resman_compat_ioctl(struct file *file, unsigned int cmd, ulong arg)
{
long ret = 0;
ret = resman_ioctl(file, cmd, (ulong)compat_ptr(arg));
return ret;
}
#endif
int resman_close(struct inode *inode, struct file *filp)
{
struct resman_session *sess;
sess = filp->private_data;
if (sess) {
dprintk(2, "%d close, proc:%s/%d/%d\n",
sess->id,
sess->comm,
sess->tgid,
sess->pid);
resman_close_session(sess);
}
filp->private_data = NULL;
return 0;
}
const struct file_operations resman_fops = {
.owner = THIS_MODULE,
.open = resman_open,
.read = resman_read,
.poll = resman_poll,
.release = resman_close,
.unlocked_ioctl = resman_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = resman_compat_ioctl,
#endif
};
/* -----------------------------------------------------------------
* Initialization and module stuff
* -----------------------------------------------------------------
*/
static struct class_attribute resman_class_attrs[] = {
__ATTR_RO(usage),
__ATTR_RW(config),
__ATTR_RO(ver),
__ATTR_RW(extconfig),
__ATTR_NULL
};
static void remove_resmansub_attrs(struct class *class)
{
int i = 0;
for (i = 0; resman_class_attrs[i].attr.name; i++)
class_remove_file(class, &resman_class_attrs[i]);
}
static void create_resmansub_attrs(struct class *class)
{
int i = 0;
for (i = 0; resman_class_attrs[i].attr.name; i++) {
if (class_create_file(class, &resman_class_attrs[i]) < 0)
break;
}
}
int __init resman_init(void)
{
int result;
struct device *resman_dev;
result = alloc_chrdev_region(&resman_devno, 0, 1, DEVICE_NAME);
if (result < 0) {
dprintk(0, "failed to allocate amresource_mgr dev region\n");
result = -ENODEV;
return result;
}
resman_class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);
if (IS_ERR(resman_class)) {
result = PTR_ERR(resman_class);
goto fail1;
}
create_resmansub_attrs(resman_class);
resman_cdev = kmalloc(sizeof(*resman_cdev), GFP_KERNEL);
if (!resman_cdev) {
result = -ENOMEM;
goto fail2;
}
cdev_init(resman_cdev, &resman_fops);
resman_cdev->owner = THIS_MODULE;
result = cdev_add(resman_cdev, resman_devno, 1);
if (result) {
dprintk(0, "failed to add cdev\n");
goto fail3;
}
resman_dev = device_create(resman_class,
NULL, MKDEV(MAJOR(resman_devno), 0),
NULL, DEVICE_NAME);
if (IS_ERR(resman_dev)) {
pr_err("Can't create %s device\n", DEVICE_NAME);
result = PTR_ERR(resman_dev);
goto fail4;
}
all_resource_init();
dprintk(1, "%s init success\n", DEVICE_NAME);
return 0;
fail4:
cdev_del(resman_cdev);
fail3:
kfree(resman_cdev);
fail2:
remove_resmansub_attrs(resman_class);
class_destroy(resman_class);
fail1:
unregister_chrdev_region(resman_devno, 1);
return result;
}
void __exit resman_exit(void)
{
all_resource_uninit();
kfree(resman_configs);
cdev_del(resman_cdev);
kfree(resman_cdev);
device_destroy(resman_class, MKDEV(MAJOR(resman_devno), 0));
remove_resmansub_attrs(resman_class);
class_destroy(resman_class);
unregister_chrdev_region(resman_devno, 1);
dprintk(1, "uninstall sourmanage module\n");
}
MODULE_LICENSE("GPL");