blob: a34703ca159fca825ad5e8627a9a57772c13403f [file] [log] [blame]
/* Copyright (c) 2016-2017, 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/module.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include "cam_req_mgr_interface.h"
#include "cam_req_mgr_util.h"
#include "cam_req_mgr_core.h"
#include "cam_req_mgr_workq.h"
/* Forward declarations */
static int cam_req_mgr_cb_notify_sof(
struct cam_req_mgr_sof_notify *sof_data);
static struct cam_req_mgr_core_device *g_crm_core_dev;
static struct cam_req_mgr_crm_cb cam_req_mgr_ops = {
.notify_sof = cam_req_mgr_cb_notify_sof,
.notify_err = NULL,
.add_req = NULL,
};
/**
* cam_req_mgr_pvt_find_link()
*
* @brief: Finds link matching with handle within session
* @session: session indetifier
* @link_hdl: link handle
*
* Returns pointer to link matching handle
*/
static struct cam_req_mgr_core_link *cam_req_mgr_pvt_find_link(
struct cam_req_mgr_core_session *session, int32_t link_hdl)
{
int32_t i;
struct cam_req_mgr_core_link *link = NULL;
if (!session) {
CRM_ERR("NULL session ptr");
return NULL;
}
spin_lock(&session->lock);
for (i = 0; i < MAX_LINKS_PER_SESSION; i++) {
link = &session->links[i];
spin_lock(&link->lock);
if (link->link_hdl == link_hdl) {
CRM_DBG("Link found p_delay %d",
link->max_pipeline_delay);
spin_unlock(&link->lock);
break;
}
spin_unlock(&link->lock);
}
if (i >= MAX_LINKS_PER_SESSION)
link = NULL;
spin_unlock(&session->lock);
return link;
}
/**
* cam_req_mgr_process_sof()
*
* @brief: This runs in workque thread context. Call core funcs to check
* which peding requests can be processed.
* @data:contains information about frame_id, link etc.
*
* Returns 0 on success.
*/
static int cam_req_mgr_process_sof(void *priv, void *data)
{
int ret = 0, i = 0;
struct cam_req_mgr_sof_notify *sof_data = NULL;
struct cam_req_mgr_core_link *link = NULL;
struct cam_req_mgr_connected_device *device = NULL;
struct cam_req_mgr_apply_request apply_req;
if (!data || !priv) {
CRM_ERR("input args NULL %pK %pK", data, priv);
ret = -EINVAL;
goto end;
}
link = (struct cam_req_mgr_core_link *)priv;
sof_data = (struct cam_req_mgr_sof_notify *)data;
CRM_DBG("link_hdl %x frame_id %lld",
sof_data->link_hdl,
sof_data->frame_id);
apply_req.link_hdl = sof_data->link_hdl;
/* @TODO: go through request table and issue
* request id based on dev status
*/
apply_req.request_id = sof_data->frame_id;
apply_req.report_if_bubble = 0;
CRM_DBG("link %pK l_dev %pK num_dev %d",
link, link->l_devices, link->num_connections);
for (i = 0; i < link->num_connections; i++) {
device = &link->l_devices[i];
if (device != NULL) {
CRM_DBG("dev_id %d dev_hdl %x ops %pK p_delay %d",
device->dev_info.dev_id, device->dev_hdl,
device->ops, device->dev_info.p_delay);
apply_req.dev_hdl = device->dev_hdl;
if (device->ops && device->ops->apply_req) {
ret = device->ops->apply_req(&apply_req);
/* Error handling for this failure is pending */
if (ret < 0)
CRM_ERR("Failure:%d dev=%d", ret,
device->dev_info.dev_id);
}
}
}
end:
return ret;
}
/**
* cam_req_mgr_notify_sof()
*
* @brief: SOF received from device, sends trigger through workqueue
* @sof_data: contains information about frame_id, link etc.
*
* Returns 0 on success
*/
static int cam_req_mgr_cb_notify_sof(struct cam_req_mgr_sof_notify *sof_data)
{
int ret = 0;
struct crm_workq_task *task = NULL;
struct cam_req_mgr_core_link *link = NULL;
if (!sof_data) {
CRM_ERR("sof_data is NULL");
ret = -EINVAL;
goto end;
}
CRM_DBG("link_hdl %x frame_id %lld",
sof_data->link_hdl,
sof_data->frame_id);
link = (struct cam_req_mgr_core_link *)
cam_get_device_priv(sof_data->link_hdl);
if (!link) {
CRM_DBG("link ptr NULL %x", sof_data->link_hdl);
ret = -EINVAL;
goto end;
}
task = cam_req_mgr_workq_get_task(link->workq);
if (!task) {
CRM_ERR("no empty task frame %lld", sof_data->frame_id);
ret = -EBUSY;
goto end;
}
task->type = CRM_WORKQ_TASK_NOTIFY_SOF;
task->u.notify_sof.frame_id = sof_data->frame_id;
task->u.notify_sof.link_hdl = sof_data->link_hdl;
task->u.notify_sof.dev_hdl = sof_data->dev_hdl;
task->process_cb = &cam_req_mgr_process_sof;
task->priv = link;
cam_req_mgr_workq_enqueue_task(task);
end:
return ret;
}
/**
* cam_req_mgr_pvt_reserve_link()
*
* @brief: Reserves one link data struct within session
* @session: session identifier
*
* Returns pointer to link reserved
*/
static struct cam_req_mgr_core_link *cam_req_mgr_pvt_reserve_link(
struct cam_req_mgr_core_session *session)
{
int32_t i;
struct cam_req_mgr_core_link *link;
if (!session) {
CRM_ERR("NULL session ptr");
return NULL;
}
spin_lock(&session->lock);
for (i = 0; i < MAX_LINKS_PER_SESSION; i++) {
link = &session->links[i];
spin_lock(&link->lock);
if (link->link_state == CAM_CRM_LINK_STATE_AVAILABLE) {
link->num_connections = 0;
link->max_pipeline_delay = 0;
memset(link->req_table, 0,
sizeof(struct cam_req_mgr_request_table));
link->link_state = CAM_CRM_LINK_STATE_IDLE;
spin_unlock(&link->lock);
break;
}
spin_unlock(&link->lock);
}
CRM_DBG("Link available (total %d)", session->num_active_links);
spin_unlock(&session->lock);
if (i >= MAX_LINKS_PER_SESSION)
link = NULL;
return link;
}
/**
* cam_req_mgr_pvt_create_subdevs()
*
* @brief: Create new crm subdev to link with realtime devices
* @l_devices: list of subdevs internal to crm
* @num_dev: num of subdevs to be created for link
*
* Returns pointer to allocated list of devices
*/
static struct cam_req_mgr_connected_device *
cam_req_mgr_pvt_create_subdevs(int32_t num_dev)
{
struct cam_req_mgr_connected_device *l_devices;
l_devices = (struct cam_req_mgr_connected_device *)
kzalloc(sizeof(struct cam_req_mgr_connected_device) * num_dev,
GFP_KERNEL);
if (!l_devices)
CRM_DBG("Insufficient memory %lu",
sizeof(struct cam_req_mgr_connected_device) * num_dev);
return l_devices;
}
/**
* cam_req_mgr_pvt_destroy_subdev()
*
* @brief: Cleans up the subdevs allocated by crm for link
* @l_device: pointer to list of subdevs crm created
*
* Returns 0 for success
*/
static int cam_req_mgr_pvt_destroy_subdev(
struct cam_req_mgr_connected_device **l_device)
{
int ret = 0;
if (!(*l_device))
ret = -EINVAL;
else {
kfree(*l_device);
*l_device = NULL;
}
return ret;
}
int cam_req_mgr_create_session(
struct cam_req_mgr_session_info *ses_info)
{
int ret = 0;
int32_t i;
int32_t session_hdl;
struct cam_req_mgr_core_session *cam_session;
if (!ses_info) {
CRM_ERR("NULL session info pointer");
return -EINVAL;
}
mutex_lock(&g_crm_core_dev->crm_lock);
cam_session = (struct cam_req_mgr_core_session *)
kzalloc(sizeof(*cam_session), GFP_KERNEL);
if (!cam_session) {
ret = -ENOMEM;
goto end;
}
session_hdl = cam_create_session_hdl((void *)cam_session);
if (session_hdl < 0) {
CRM_ERR("unable to create session_hdl = %x", session_hdl);
ret = session_hdl;
goto session_hdl_failed;
}
ses_info->session_hdl = session_hdl;
cam_session->session_hdl = session_hdl;
spin_lock_init(&cam_session->lock);
cam_session->num_active_links = 0;
for (i = 0; i < MAX_LINKS_PER_SESSION; i++) {
spin_lock_init(&cam_session->links[i].lock);
cam_session->links[i].link_state = CAM_CRM_LINK_STATE_AVAILABLE;
INIT_LIST_HEAD(&cam_session->links[i].link_head);
cam_session->links[i].workq = NULL;
}
list_add(&cam_session->entry, &g_crm_core_dev->session_head);
mutex_unlock(&g_crm_core_dev->crm_lock);
return ret;
session_hdl_failed:
kfree(cam_session);
end:
mutex_unlock(&g_crm_core_dev->crm_lock);
return ret;
}
int cam_req_mgr_destroy_session(
struct cam_req_mgr_session_info *ses_info)
{
int ret;
int32_t i;
struct cam_req_mgr_core_session *cam_session;
struct cam_req_mgr_core_link *link = NULL;
if (!ses_info) {
CRM_ERR("NULL session info pointer");
return -EINVAL;
}
mutex_lock(&g_crm_core_dev->crm_lock);
cam_session = (struct cam_req_mgr_core_session *)
cam_get_device_priv(ses_info->session_hdl);
if (cam_session == NULL) {
CRM_ERR("failed to get session priv");
ret = -ENOENT;
goto end;
}
spin_lock(&cam_session->lock);
for (i = 0; i < cam_session->num_active_links; i++) {
link = &cam_session->links[i];
CRM_ERR("session %x active_links %d hdl %x connections %d",
ses_info->session_hdl,
cam_session->num_active_links,
link->link_hdl, link->num_connections);
}
list_del(&cam_session->entry);
spin_unlock(&cam_session->lock);
kfree(cam_session);
ret = cam_destroy_session_hdl(ses_info->session_hdl);
if (ret)
CRM_ERR("unable to destroy session_hdl = %x ret %d",
ses_info->session_hdl, ret);
end:
mutex_unlock(&g_crm_core_dev->crm_lock);
return ret;
}
int cam_req_mgr_link(struct cam_req_mgr_link_info *link_info)
{
int ret = 0;
int32_t i, link_hdl;
char buf[128];
struct cam_create_dev_hdl root_dev;
struct cam_req_mgr_core_session *cam_session;
struct cam_req_mgr_core_link *link;
struct cam_req_mgr_core_dev_link_setup link_data;
struct cam_req_mgr_connected_device *l_devices;
enum cam_pipeline_delay max_delay = CAM_PIPELINE_DELAY_0;
if (!link_info) {
CRM_ERR("NULL pointer");
return -EINVAL;
}
if (link_info->num_devices > CAM_REQ_MGR_MAX_HANDLES) {
CRM_ERR("Invalid num devices %d", link_info->num_devices);
return -EINVAL;
}
cam_session = (struct cam_req_mgr_core_session *)
cam_get_device_priv(link_info->session_hdl);
if (!cam_session) {
CRM_ERR("NULL session pointer");
return -EINVAL;
}
link = cam_req_mgr_pvt_reserve_link(cam_session);
if (!link) {
CRM_ERR("NULL link pointer");
return -EINVAL;
}
memset(&root_dev, 0, sizeof(struct cam_create_dev_hdl));
root_dev.session_hdl = link_info->session_hdl;
root_dev.priv = (void *)link;
link_hdl = cam_create_device_hdl(&root_dev);
if (link_hdl < 0) {
CRM_ERR("Insufficient memory to create new device handle");
ret = link_hdl;
goto link_hdl_fail;
}
l_devices = cam_req_mgr_pvt_create_subdevs(link_info->num_devices);
if (!l_devices) {
ret = -ENOMEM;
goto create_subdev_failed;
}
for (i = 0; i < link_info->num_devices; i++) {
l_devices[i].dev_hdl = link_info->dev_hdls[i];
l_devices[i].parent = (void *)link;
l_devices[i].ops = (struct cam_req_mgr_kmd_ops *)
cam_get_device_ops(link_info->dev_hdls[i]);
link_data.dev_hdl = l_devices[i].dev_hdl;
l_devices[i].dev_info.dev_hdl = l_devices[i].dev_hdl;
if (l_devices[i].ops) {
if (l_devices[i].ops->get_dev_info) {
ret = l_devices[i].ops->get_dev_info(
&l_devices[i].dev_info);
if (ret < 0 ||
l_devices[i].dev_info.p_delay >=
CAM_PIPELINE_DELAY_MAX ||
l_devices[i].dev_info.p_delay <
CAM_PIPELINE_DELAY_0) {
CRM_ERR("get device info failed");
goto error;
} else {
CRM_DBG("%x: connected: %s, delay %d",
link_info->session_hdl,
l_devices[i].dev_info.name,
l_devices[i].dev_info.p_delay);
if (l_devices[i].dev_info.p_delay >
max_delay)
max_delay =
l_devices[i].dev_info.p_delay;
}
}
} else {
CRM_ERR("FATAL: device ops NULL");
ret = -ENXIO;
goto error;
}
}
link_data.link_enable = true;
link_data.link_hdl = link_hdl;
link_data.crm_cb = &cam_req_mgr_ops;
link_data.max_delay = max_delay;
/* After getting info about all devices, establish link */
for (i = 0; i < link_info->num_devices; i++) {
l_devices[i].dev_hdl = link_info->dev_hdls[i];
l_devices[i].parent = (void *)link;
l_devices[i].ops = (struct cam_req_mgr_kmd_ops *)
cam_get_device_ops(link_info->dev_hdls[i]);
link_data.dev_hdl = l_devices[i].dev_hdl;
l_devices[i].dev_info.dev_hdl = l_devices[i].dev_hdl;
if (l_devices[i].ops) {
if (l_devices[i].ops->link_setup) {
ret = l_devices[i].ops->link_setup(&link_data);
if (ret < 0) {
/* TODO check handlng of this failure */
CRM_ERR("link setup failed");
goto error;
}
}
}
list_add_tail(&l_devices[i].entry, &link->link_head);
}
/* Create worker for current link */
snprintf(buf, sizeof(buf), "%x-%x", link_info->session_hdl, link_hdl);
ret = cam_req_mgr_workq_create(buf, &link->workq);
if (ret < 0) {
CRM_ERR("FATAL: unable to create worker");
goto error;
}
link_info->link_hdl = link_hdl;
spin_lock(&link->lock);
link->l_devices = l_devices;
link->link_hdl = link_hdl;
link->parent = (void *)cam_session;
link->num_connections = link_info->num_devices;
link->link_state = CAM_CRM_LINK_STATE_READY;
spin_unlock(&link->lock);
spin_lock(&cam_session->lock);
cam_session->num_active_links++;
spin_unlock(&cam_session->lock);
return ret;
error:
cam_req_mgr_pvt_destroy_subdev(&l_devices);
create_subdev_failed:
cam_destroy_device_hdl(link_hdl);
link_hdl_fail:
spin_lock(&link->lock);
link->link_state = CAM_CRM_LINK_STATE_AVAILABLE;
spin_unlock(&link->lock);
return ret;
}
int cam_req_mgr_unlink(struct cam_req_mgr_unlink_info *unlink_info)
{
int ret = 0;
int32_t i = 0;
struct cam_req_mgr_core_session *cam_session;
struct cam_req_mgr_core_link *link;
struct cam_req_mgr_connected_device *device;
struct cam_req_mgr_core_dev_link_setup link_data;
if (!unlink_info) {
CRM_ERR("NULL pointer");
return -EINVAL;
}
cam_session = (struct cam_req_mgr_core_session *)
cam_get_device_priv(unlink_info->session_hdl);
if (!cam_session) {
CRM_ERR("NULL pointer");
return -EINVAL;
}
link = cam_req_mgr_pvt_find_link(cam_session,
unlink_info->link_hdl);
if (!link) {
CRM_ERR("NULL pointer");
return -EINVAL;
}
ret = cam_destroy_device_hdl(link->link_hdl);
if (ret < 0) {
CRM_ERR("error in destroying dev handle %d %x",
ret, link->link_hdl);
ret = -EINVAL;
}
link_data.link_enable = false;
link_data.link_hdl = link->link_hdl;
link_data.crm_cb = NULL;
for (i = 0; i < link->num_connections; i++) {
device = &link->l_devices[i];
link_data.dev_hdl = device->dev_hdl;
if (device->ops && device->ops->link_setup)
device->ops->link_setup(&link_data);
device->dev_hdl = 0;
device->parent = NULL;
device->ops = NULL;
list_del(&device->entry);
}
/* Destroy worker of link */
cam_req_mgr_workq_destroy(link->workq);
spin_lock(&link->lock);
link->link_state = CAM_CRM_LINK_STATE_AVAILABLE;
link->parent = NULL;
link->num_connections = 0;
link->link_hdl = 0;
link->workq = NULL;
spin_unlock(&link->lock);
spin_lock(&cam_session->lock);
cam_session->num_active_links--;
spin_unlock(&cam_session->lock);
ret = cam_req_mgr_pvt_destroy_subdev(&link->l_devices);
if (ret < 0) {
CRM_ERR("error while destroying subdev link %x",
link_data.link_hdl);
ret = -EINVAL;
}
return ret;
}
int cam_req_mgr_schedule_request(
struct cam_req_mgr_sched_request *sched_req)
{
if (!sched_req) {
CRM_ERR("NULL pointer");
return -EINVAL;
}
/* This function handles ioctl, implementation pending */
return 0;
}
int cam_req_mgr_sync_mode(
struct cam_req_mgr_sync_mode *sync_links)
{
if (!sync_links) {
CRM_ERR("NULL pointer");
return -EINVAL;
}
/* This function handles ioctl, implementation pending */
return 0;
}
int cam_req_mgr_flush_requests(
struct cam_req_mgr_flush_info *flush_info)
{
if (!flush_info) {
CRM_ERR("NULL pointer");
return -EINVAL;
}
/* This function handles ioctl, implementation pending */
return 0;
}
int cam_req_mgr_core_device_init(void)
{
CRM_DBG("Enter g_crm_core_dev %pK", g_crm_core_dev);
if (g_crm_core_dev) {
CRM_WARN("core device is already initialized");
return 0;
}
g_crm_core_dev = (struct cam_req_mgr_core_device *)
kzalloc(sizeof(*g_crm_core_dev), GFP_KERNEL);
if (!g_crm_core_dev)
return -ENOMEM;
CRM_DBG("g_crm_core_dev %pK", g_crm_core_dev);
INIT_LIST_HEAD(&g_crm_core_dev->session_head);
mutex_init(&g_crm_core_dev->crm_lock);
return 0;
}
int cam_req_mgr_core_device_deinit(void)
{
if (!g_crm_core_dev) {
CRM_ERR("NULL pointer");
return -EINVAL;
}
CRM_DBG("g_crm_core_dev %pK", g_crm_core_dev);
mutex_destroy(&g_crm_core_dev->crm_lock);
kfree(g_crm_core_dev);
g_crm_core_dev = NULL;
return 0;
}