diff options
author | Rob Clark <rob@ti.com> | 2012-05-16 10:46:09 +0800 |
---|---|---|
committer | Andy Green <andy.green@linaro.org> | 2012-06-20 12:09:40 +0800 |
commit | 111739aaac03ae33c1dbf91291a42ac500b8a950 (patch) | |
tree | 6c22e875a79993ef38de9376166f2ec5c6799d97 | |
parent | 2f148701718d018a0925d40eadd736dc44937e13 (diff) |
WIP: add DCE plugin v2
v2 add support to properly abort a txn
-rw-r--r-- | drivers/staging/Kconfig | 2 | ||||
-rw-r--r-- | drivers/staging/Makefile | 1 | ||||
-rw-r--r-- | drivers/staging/omapdce/Kconfig | 10 | ||||
-rw-r--r-- | drivers/staging/omapdce/Makefile | 9 | ||||
-rw-r--r-- | drivers/staging/omapdce/dce.c | 1101 | ||||
-rw-r--r-- | drivers/staging/omapdce/dce_rpc.h | 140 | ||||
-rw-r--r-- | drivers/staging/omapdce/omap_dce.h | 110 |
7 files changed, 1373 insertions, 0 deletions
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index ed94734d90e..7a873c08eef 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -134,4 +134,6 @@ source "drivers/staging/ozwpan/Kconfig" source "drivers/staging/thermal_framework/Kconfig" +source "drivers/staging/omapdce/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index 542876b26cd..a78452d620c 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -58,3 +58,4 @@ obj-$(CONFIG_PHONE) += telephony/ obj-$(CONFIG_RAMSTER) += ramster/ obj-$(CONFIG_USB_WPAN_HCD) += ozwpan/ obj-$(CONFIG_OMAP_THERMAL) += thermal_framework/ +obj-$(CONFIG_DRM_OMAP_DCE) += omapdce/ diff --git a/drivers/staging/omapdce/Kconfig b/drivers/staging/omapdce/Kconfig new file mode 100644 index 00000000000..6dd3c58fe62 --- /dev/null +++ b/drivers/staging/omapdce/Kconfig @@ -0,0 +1,10 @@ +config DRM_OMAP_DCE + tristate "OMAP DRM Distributed Codec Engine" + depends on DRM_OMAP + select OMAP_RPMSG + select OMAP_REMOTEPROC + default n + help + Plugin for OMAP DRM driver to support hw accellerated video + decoders/encoders + diff --git a/drivers/staging/omapdce/Makefile b/drivers/staging/omapdce/Makefile new file mode 100644 index 00000000000..87603714727 --- /dev/null +++ b/drivers/staging/omapdce/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the DCE plugin for OMAP DRM driver. This driver provides +# support for hw accellerated video decode/encode. +# + +ccflags-y := -Iinclude/drm -Werror +omapdce-y := dce.o + +obj-$(CONFIG_DRM_OMAP_DCE) += omapdce.o diff --git a/drivers/staging/omapdce/dce.c b/drivers/staging/omapdce/dce.c new file mode 100644 index 00000000000..734ab48b46b --- /dev/null +++ b/drivers/staging/omapdce/dce.c @@ -0,0 +1,1101 @@ +/* + * drivers/staging/omapdce/dce.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/rpmsg.h> + +#include "../omapdrm/omap_drm.h" +#include "../omapdrm/omap_drv.h" +#include "omap_dce.h" + +#include "dce_rpc.h" + +/* TODO: split util stuff into other file.. maybe split out some of the + * _process() munging stuff.. + */ + +#define DBG(fmt,...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) +#define VERB(fmt,...) if (0) DRM_DEBUG(fmt, ##__VA_ARGS__) /* verbose debug */ + +/* Removeme.. */ +static long mark(long *last) +{ + struct timeval t; + do_gettimeofday(&t); + if (last) { + return t.tv_usec - *last; + } + return t.tv_usec; +} + +#define MAX_ENGINES 32 +#define MAX_CODECS 32 +#define MAX_LOCKED_BUFFERS 32 /* actually, 16 should be enough if reorder queue is disabled */ +#define MAX_BUFFER_OBJECTS ((2 * MAX_LOCKED_BUFFERS) + 4) +#define MAX_TRANSACTIONS MAX_CODECS + +struct dce_buffer { + int32_t id; /* zero for unused */ + struct drm_gem_object *y, *uv; +}; + +struct dce_engine { + uint32_t engine; +}; + +struct dce_codec { + enum omap_dce_codec codec_id; + uint32_t codec; + struct dce_buffer locked_buffers[MAX_LOCKED_BUFFERS]; +}; + +struct dce_file_priv { + struct drm_device *dev; + struct drm_file *file; + + /* NOTE: engine/codec end up being pointers (or similar) on + * coprocessor side.. so these are not exposed directly to + * userspace. Instead userspace sees per-file unique handles + * which index into the table of engines/codecs. + */ + struct dce_engine engines[MAX_ENGINES]; + + struct dce_codec codecs[MAX_CODECS]; + + /* the token returned to userspace is an index into this table + * of msg request-ids. This avoids any chance that userspace might + * try to guess another processes txn req_id and try to intercept + * the other processes reply.. + */ + uint16_t req_ids[MAX_CODECS]; + atomic_t next_token; +}; + +/* per-transaction data.. indexed by req_id%MAX_REQUESTS + */ + +struct omap_dce_txn { + struct dce_file_priv *priv; /* file currently using thix txn slot */ + struct dce_rpc_hdr *rsp; + int len; + int bo_count; + struct drm_gem_object *objs[MAX_BUFFER_OBJECTS]; +}; +static struct omap_dce_txn txns[MAX_TRANSACTIONS]; + +/* note: eventually we could perhaps have per drm_device private data + * and pull all this into a struct.. but in rpmsg_cb we don't have a + * way to get at that, so for now all global.. + */ +struct rpmsg_channel *rpdev; +static int dce_mapper_id = -1; +static atomic_t next_req_id = ATOMIC_INIT(0); +static DECLARE_WAIT_QUEUE_HEAD(wq); +static DEFINE_MUTEX(lock); // TODO probably more locking needed.. + +/* + * Utils: + */ + +#define hdr(r) ((void *)(r)) + +/* initialize header and assign request id: */ +#define MKHDR(x) (struct dce_rpc_hdr){ .msg_id = DCE_RPC_##x, .req_id = atomic_inc_return(&next_req_id) } + +/* GEM buffer -> paddr, plus add the buffer to the txn bookkeeping of + * associated buffers that eventually need to be cleaned up when the + * transaction completes + */ +static struct drm_gem_object * get_paddr(struct dce_file_priv *priv, + struct dce_rpc_hdr *req, uint32_t *paddrp, int bo) +{ + struct omap_dce_txn *txn = &txns[req->req_id % ARRAY_SIZE(txns)]; + struct drm_gem_object *obj; + dma_addr_t paddr; + int ret; +long t; + + if (txn->bo_count >= ARRAY_SIZE(txn->objs)) { + DBG("too many buffers!"); + return ERR_PTR(-ENOMEM); + } + + obj = drm_gem_object_lookup(priv->dev, priv->file, bo); + if (!obj) { + DBG("bad handle: %d", bo); + return ERR_PTR(-ENOENT); + } + +t = mark(NULL); + ret = omap_gem_get_paddr(obj, &paddr, true); +DBG("get_paddr in %ld us", mark(&t)); + if (ret) { + DBG("cannot map: %d", ret); + return ERR_PTR(ret); + } + + /* the coproc can only see 32bit addresses.. this might need + * to be revisited in the future with some conversion between + * device address and host address. But currently they are + * the same. + */ + *paddrp = (uint32_t)paddr; + + txn->objs[txn->bo_count++] = obj; + + DBG("obj=%p", obj); + + return obj; +} + +static int rpsend(struct dce_file_priv *priv, uint32_t *token, + struct dce_rpc_hdr *req, int len) +{ + struct omap_dce_txn *txn = + &txns[req->req_id % ARRAY_SIZE(txns)]; + + WARN_ON(txn->priv); + + /* assign token: */ + if (token) { + *token = atomic_inc_return(&priv->next_token) + 1; + priv->req_ids[(*token-1) % ARRAY_SIZE(priv->req_ids)] = req->req_id; + + txn->priv = priv; + + // XXX wait for paddrs to become valid! + + } else { + /* message with no response: */ + req->req_id = 0xffff; /* just for debug */ + WARN_ON(txn->bo_count > 0); /* this is not valid */ + + memset(txn, 0, sizeof(*txn)); + } + + return rpmsg_send(rpdev, req, len); +} + +static int rpwait(struct dce_file_priv *priv, uint32_t token, + struct dce_rpc_hdr **rsp, int len) +{ + uint16_t req_id = priv->req_ids[(token-1) % ARRAY_SIZE(priv->req_ids)]; + struct omap_dce_txn *txn = &txns[req_id % ARRAY_SIZE(txns)]; + int ret; + + if (txn->priv != priv) { + dev_err(priv->dev->dev, "not my txn\n"); + return -EINVAL; + } + + ret = wait_event_interruptible(wq, (txn->rsp)); + if (ret) { + DBG("ret=%d", ret); + return ret; + } + + if (txn->len < len) { + dev_err(priv->dev->dev, "rsp too short: %d < %d\n", txn->len, len); + ret = -EINVAL; + goto fail; + } + + *rsp = txn->rsp; + +fail: + /* clear out state: */ + memset(txn, 0, sizeof(*txn)); + + return ret; +} + +static void txn_cleanup(struct omap_dce_txn *txn) +{ + int i; + + mutex_lock(&lock); + + kfree(txn->rsp); + txn->rsp = NULL; + + /* unpin/unref buffers associated with this transaction */ + for (i = 0; i < txn->bo_count; i++) { + struct drm_gem_object *obj = txn->objs[i]; + DBG("obj=%p", obj); + omap_gem_put_paddr(obj); + drm_gem_object_unreference_unlocked(obj); + } + txn->bo_count = 0; + + mutex_unlock(&lock); +} + +static void rpcomplete(struct dce_rpc_hdr *rsp, int len) +{ + struct omap_dce_txn *txn = &txns[rsp->req_id % ARRAY_SIZE(txns)]; + + if (!txn->priv) { + /* we must of cleaned up already (killed process) */ + printk(KERN_ERR "dce: unexpected response.. killed process?\n"); + kfree(rsp); + return; + } + + txn_cleanup(txn); + + txn->len = len; + txn->rsp = rsp; + + wake_up_all(&wq); +} + +static int rpabort(struct dce_rpc_hdr *req, int ret) +{ + struct omap_dce_txn *txn = + &txns[req->req_id % ARRAY_SIZE(txns)]; + + DBG("txn failed: msg_id=%u, req_id=%u, ret=%d", + (uint32_t)req->msg_id, (uint32_t)req->req_id, ret); + + txn_cleanup(txn); + + /* clear out state: */ + memset(txn, 0, sizeof(*txn)); + + return ret; +} + +/* helpers for tracking engine instances and mapping engine handle to engine + * instance: + */ + +static uint32_t engine_register(struct dce_file_priv *priv, uint32_t engine) +{ + int i; + for (i = 0; i < ARRAY_SIZE(priv->engines); i++) { + if (!priv->engines[i].engine) { + priv->engines[i].engine = engine; + return i+1; + } + } + dev_err(priv->dev->dev, "too many engines\n"); + return 0; +} + +static void engine_unregister(struct dce_file_priv *priv, uint32_t eng_handle) +{ + priv->engines[eng_handle-1].engine = 0; +} + +static bool engine_valid(struct dce_file_priv *priv, uint32_t eng_handle) +{ + return (eng_handle > 0) && + (eng_handle <= ARRAY_SIZE(priv->engines)) && + (priv->engines[eng_handle-1].engine); +} + +static int engine_get(struct dce_file_priv *priv, uint32_t eng_handle, + uint32_t *engine) +{ + if (!engine_valid(priv, eng_handle)) + return -EINVAL; + *engine = priv->engines[eng_handle-1].engine; + return 0; +} + +/* helpers for tracking codec instances and mapping codec handle to codec + * instance: + */ + +static void codec_unlockbuf(struct dce_file_priv *priv, + uint32_t codec_handle, int32_t id); + +static uint32_t codec_register(struct dce_file_priv *priv, uint32_t codec, + enum omap_dce_codec codec_id) +{ + int i; + for (i = 0; i < ARRAY_SIZE(priv->codecs); i++) { + if (!priv->codecs[i].codec) { + priv->codecs[i].codec_id = codec_id; + priv->codecs[i].codec = codec; + return i+1; + } + } + dev_err(priv->dev->dev, "too many codecs\n"); + return 0; +} + +static void codec_unregister(struct dce_file_priv *priv, + uint32_t codec_handle) +{ + codec_unlockbuf(priv, codec_handle, 0); + priv->codecs[codec_handle-1].codec = 0; + priv->codecs[codec_handle-1].codec_id = 0; +} + +static bool codec_valid(struct dce_file_priv *priv, uint32_t codec_handle) +{ + return (codec_handle > 0) && + (codec_handle <= ARRAY_SIZE(priv->codecs)) && + (priv->codecs[codec_handle-1].codec); +} + +static int codec_get(struct dce_file_priv *priv, uint32_t codec_handle, + uint32_t *codec, uint32_t *codec_id) +{ + if (!codec_valid(priv, codec_handle)) + return -EINVAL; + *codec = priv->codecs[codec_handle-1].codec; + *codec_id = priv->codecs[codec_handle-1].codec_id; + return 0; +} + +static int codec_lockbuf(struct dce_file_priv *priv, + uint32_t codec_handle, int32_t id, + struct drm_gem_object *y, struct drm_gem_object *uv) +{ + struct dce_codec *codec = &priv->codecs[codec_handle-1]; + int i; + + for (i = 0; i < ARRAY_SIZE(codec->locked_buffers); i++) { + struct dce_buffer *buf = &codec->locked_buffers[i]; + if (buf->id == 0) { + dma_addr_t paddr; + + DBG("lock[%d]: y=%p, uv=%p", id, y, uv); + + /* for now, until the codecs support relocated buffers, keep + * an extra ref and paddr to keep it pinned + */ + drm_gem_object_reference(y); + omap_gem_get_paddr(y, &paddr, true); + + if (uv) { + drm_gem_object_reference(uv); + omap_gem_get_paddr(uv, &paddr, true); + } + + buf->id = id; + buf->y = y; + buf->uv = uv; + + return 0; + } + } + dev_err(priv->dev->dev, "too many locked buffers!\n"); + return -ENOMEM; +} + +static void codec_unlockbuf(struct dce_file_priv *priv, + uint32_t codec_handle, int32_t id) +{ + struct dce_codec *codec = &priv->codecs[codec_handle-1]; + int i; + + for (i = 0; i < ARRAY_SIZE(codec->locked_buffers); i++) { + struct dce_buffer *buf = &codec->locked_buffers[i]; + /* if id==0, unlock all buffers.. */ + if (((id == 0) && (buf->id != 0)) || + ((id != 0) && (buf->id == id))) { + struct drm_gem_object *y, *uv; + + y = buf->y; + uv = buf->uv; + + DBG("unlock[%d]: y=%p, uv=%p", buf->id, y, uv); + + /* release extra ref */ + omap_gem_put_paddr(y); + drm_gem_object_unreference_unlocked(y); + + if (uv) { + omap_gem_put_paddr(uv); + drm_gem_object_unreference_unlocked(uv); + } + + buf->id = 0; + buf->y = NULL; + buf->uv = NULL; + + /* if id==0, unlock all buffers.. */ + if (id != 0) + return; + } + } +} + + +/* + * Ioctl Handlers: + */ + +static int engine_close(struct dce_file_priv *priv, uint32_t engine); +static int codec_delete(struct dce_file_priv *priv, uint32_t codec, + enum omap_dce_codec codec_id); + +static int ioctl_engine_open(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct dce_file_priv *priv = omap_drm_file_priv(file, dce_mapper_id); + struct drm_omap_dce_engine_open *arg = data; + struct dce_rpc_engine_open_rsp *rsp; + int ret; + + /* if we are not re-starting a syscall, send req */ + if (!arg->token) { + struct dce_rpc_engine_open_req req = { + .hdr = MKHDR(ENGINE_OPEN), + }; + strncpy(req.name, arg->name, sizeof(req.name)); + ret = rpsend(priv, &arg->token, hdr(&req), sizeof(req)); + if (ret) + return ret; + } + + /* then wait for reply, which is interruptible */ + ret = rpwait(priv, arg->token, hdr(&rsp), sizeof(*rsp)); + if (ret) + return ret; + + arg->eng_handle = engine_register(priv, rsp->engine); + arg->error_code = rsp->error_code; + + if (!engine_valid(priv, arg->eng_handle)) { + engine_close(priv, rsp->engine); + ret = -ENOMEM; + } + + kfree(rsp); + + return ret; +} + +static int engine_close(struct dce_file_priv *priv, uint32_t engine) +{ + struct dce_rpc_engine_close_req req = { + .hdr = MKHDR(ENGINE_CLOSE), + .engine = engine, + }; + return rpsend(priv, NULL, hdr(&req), sizeof(req)); +} + +static int ioctl_engine_close(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct dce_file_priv *priv = omap_drm_file_priv(file, dce_mapper_id); + struct drm_omap_dce_engine_close *arg = data; + uint32_t engine; + int ret; + + ret = engine_get(priv, arg->eng_handle, &engine); + if (ret) + return ret; + + engine_unregister(priv, arg->eng_handle); + + return engine_close(priv, engine); +} + +static int ioctl_codec_create(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct dce_file_priv *priv = omap_drm_file_priv(file, dce_mapper_id); + struct drm_omap_dce_codec_create *arg = data; + struct dce_rpc_codec_create_rsp *rsp; + int ret; + + /* if we are not re-starting a syscall, send req */ + if (!arg->token) { + struct dce_rpc_codec_create_req req = { + .hdr = MKHDR(CODEC_CREATE), + .codec_id = arg->codec_id, + }; + + strncpy(req.name, arg->name, sizeof(req.name)); + + ret = engine_get(priv, arg->eng_handle, &req.engine); + if (ret) + return ret; + + ret = PTR_RET(get_paddr(priv, hdr(&req), &req.sparams, arg->sparams_bo)); + if (ret) + goto rpsend_out; + + ret = rpsend(priv, &arg->token, hdr(&req), sizeof(req)); +rpsend_out: + if (ret) + return rpabort(hdr(&req), ret); + } + + /* then wait for reply, which is interruptible */ + ret = rpwait(priv, arg->token, hdr(&rsp), sizeof(*rsp)); + if (ret) + return ret; + + arg->codec_handle = codec_register(priv, rsp->codec, arg->codec_id); + + if (!codec_valid(priv, arg->codec_handle)) { + codec_delete(priv, rsp->codec, arg->codec_id); + ret = -ENOMEM; + } + + kfree(rsp); + + return ret; +} + +static int ioctl_codec_control(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct dce_file_priv *priv = omap_drm_file_priv(file, dce_mapper_id); + struct drm_omap_dce_codec_control *arg = data; + struct dce_rpc_codec_control_rsp *rsp; + int ret; + + /* if we are not re-starting a syscall, send req */ + if (!arg->token) { + struct dce_rpc_codec_control_req req = { + .hdr = MKHDR(CODEC_CONTROL), + .cmd_id = arg->cmd_id, + }; + + ret = codec_get(priv, arg->codec_handle, &req.codec, &req.codec_id); + if (ret) + return ret; + + ret = PTR_RET(get_paddr(priv, hdr(&req), &req.dparams, arg->dparams_bo)); + if (ret) + goto rpsend_out; + + ret = PTR_RET(get_paddr(priv, hdr(&req), &req.status, arg->status_bo)); + if (ret) + goto rpsend_out; + + ret = rpsend(priv, &arg->token, hdr(&req), sizeof(req)); +rpsend_out: + if (ret) + return rpabort(hdr(&req), ret); + } + + /* then wait for reply, which is interruptible */ + ret = rpwait(priv, arg->token, hdr(&rsp), sizeof(*rsp)); + if (ret) + return ret; + + arg->result = rsp->result; + + kfree(rsp); + + return 0; +} + +struct viddec3_in_args { + int32_t size; /* struct size */ + int32_t num_bytes; + int32_t input_id; +}; +struct videnc2_in_args { + int32_t size; + int32_t input_id; + int32_t control; +}; + +union xdm2_buf_size { + struct { + int32_t width; + int32_t height; + } tiled; + int32_t bytes; +}; +struct xdm2_single_buf_desc { + uint32_t buf; + int16_t mem_type; /* XXX should be XDM_MEMTYPE_BO */ + int16_t usage_mode; + union xdm2_buf_size buf_size; + int32_t accessMask; +}; +struct xdm2_buf_desc { + int32_t num_bufs; + struct xdm2_single_buf_desc descs[16]; +}; + +struct video2_buf_desc { + int32_t num_planes; + int32_t num_meta_planes; + int32_t data_layout; + struct xdm2_single_buf_desc plane_desc[3]; + struct xdm2_single_buf_desc metadata_plane_desc[3]; + /* rest of the struct isn't interesting to kernel.. if you are + * curious look at IVIDEO2_BufDesc in ivideo.h in codec-engine + */ + uint32_t data[30]; +}; +#define XDM_MEMTYPE_RAW 0 +#define XDM_MEMTYPE_TILED8 1 +#define XDM_MEMTYPE_TILED16 2 +#define XDM_MEMTYPE_TILED32 3 +#define XDM_MEMTYPE_TILEDPAGE 4 + +/* copy_from_user helper that also checks to avoid overrunning + * the 'to' buffer and advances dst ptr + */ +static inline int cfu(void **top, uint64_t from, int n, void *end) +{ + void *to = *top; + int ret; + if ((to + n) >= end) { + DBG("dst buffer overflow!"); + return -EFAULT; + } + ret = copy_from_user(to, (char __user *)(uintptr_t)from, n); + *top = to + n; + return ret; +} + +static inline struct drm_gem_object * handle_single_buf_desc( + struct dce_file_priv *priv, struct dce_rpc_hdr *req, + struct xdm2_single_buf_desc *desc) +{ + struct drm_gem_object *obj; + uint32_t flags; + + /* maybe support remapping user ptrs later on.. */ + if (desc->mem_type != XDM_MEMTYPE_BO) + return ERR_PTR(-EINVAL); + + obj = get_paddr(priv, req, &desc->buf, desc->buf); + if (IS_ERR(obj)) + return obj; + + flags = omap_gem_flags(obj); + switch(flags & OMAP_BO_TILED) { + case OMAP_BO_TILED_8: + desc->mem_type = XDM_MEMTYPE_TILED8; + break; + case OMAP_BO_TILED_16: + desc->mem_type = XDM_MEMTYPE_TILED16; + break; + case OMAP_BO_TILED_32: + desc->mem_type = XDM_MEMTYPE_TILED32; + break; + default: + // XXX this is where it gets a bit messy.. some codecs + // might want to see XDM_MEMTYPE_RAW for bitstream buffers + desc->mem_type = XDM_MEMTYPE_TILEDPAGE; + break; + } + + if (flags & OMAP_BO_TILED) { + uint16_t w, h; + omap_gem_tiled_size(obj, &w, &h); + desc->buf_size.tiled.width = w; + desc->buf_size.tiled.height = h; + } else { + desc->buf_size.bytes = obj->size; + } + + // XXX not sure if the codecs care about usage_mode.. but we + // know if the buffer is cached or not so we could set DATASYNC + // bit if needed.. + + return obj; +} + +static inline int handle_buf_desc(struct dce_file_priv *priv, + void **ptr, void *end, struct dce_rpc_hdr *req, uint64_t usr, + struct drm_gem_object **o1, struct drm_gem_object **o2, uint8_t *len) +{ + struct xdm2_buf_desc *bufs = *ptr; + int i, ret; + + /* read num_bufs field: */ + ret = cfu(ptr, usr, 4, end); + if (ret) + return ret; + + /* read rest of structure: */ + ret = cfu(ptr, usr+4, bufs->num_bufs * sizeof(bufs->descs[0]), end); + if (ret) + return ret; + + *len = (4 + bufs->num_bufs * sizeof(bufs->descs[0])) / 4; + + /* handle buffer mapping.. */ + for (i = 0; i < bufs->num_bufs; i++) { + struct drm_gem_object *obj = + handle_single_buf_desc(priv, req, &bufs->descs[i]); + if (IS_ERR(obj)) { + return PTR_ERR(obj); + } + if (i == 0) + *o1 = obj; + if (o2 && (i == 1)) + *o2 = obj; + + } + + return 0; +} + +/* + * VIDDEC3_process VIDENC2_process + * VIDDEC3_InArgs *inArgs VIDENC2_InArgs *inArgs + * XDM2_BufDesc *outBufs XDM2_BufDesc *outBufs + * XDM2_BufDesc *inBufs VIDEO2_BufDesc *inBufs + */ + +static inline int handle_videnc2(struct dce_file_priv *priv, + void **ptr, void *end, int32_t *input_id, + struct dce_rpc_codec_process_req *req, + struct drm_omap_dce_codec_process *arg) +{ + WARN_ON(1); // XXX not implemented + // codec_lockbuf(priv, arg->codec_handle, in_args->input_id, y, uv); + return -EFAULT; +} + +static inline int handle_viddec3(struct dce_file_priv *priv, + void **ptr, void *end, int32_t *input_id, + struct dce_rpc_codec_process_req *req, + struct drm_omap_dce_codec_process *arg) +{ + struct drm_gem_object *in, *y = NULL, *uv = NULL; + struct viddec3_in_args *in_args = *ptr; + int ret; + + /* handle in_args: */ + ret = cfu(ptr, arg->in_args, sizeof(*in_args), end); + if (ret) + return ret; + + if (in_args->size > sizeof(*in_args)) { + int sz = in_args->size - sizeof(*in_args); + /* this param can be variable length */ + ret = cfu(ptr, arg->in_args + sizeof(*in_args), sz, end); + if (ret) + return ret; + /* in case the extra part size is not multiple of 4 */ + *ptr += round_up(sz, 4) - sz; + } + + req->in_args_len = round_up(in_args->size, 4) / 4; + + /* handle out_bufs: */ + ret = handle_buf_desc(priv, ptr, end, hdr(req), arg->out_bufs, + &y, &uv, &req->out_bufs_len); + if (ret) + return ret; + + /* handle in_bufs: */ + ret = handle_buf_desc(priv, ptr, end, hdr(req), arg->in_bufs, + &in, NULL, &req->in_bufs_len); + if (ret) + return ret; + + *input_id = in_args->input_id; + codec_lockbuf(priv, arg->codec_handle, in_args->input_id, y, uv); + + return 0; +} + +#define RPMSG_BUF_SIZE (512) // ugg, would be nice not to hard-code.. + +static int ioctl_codec_process(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct dce_file_priv *priv = omap_drm_file_priv(file, dce_mapper_id); + struct drm_omap_dce_codec_process *arg = data; + struct dce_rpc_codec_process_rsp *rsp; + int ret, i; + + /* if we are not re-starting a syscall, send req */ + if (!arg->token) { + /* worst-case size allocation.. */ + struct dce_rpc_codec_process_req *req = kzalloc(RPMSG_BUF_SIZE, GFP_KERNEL); + void *ptr = &req->data[0]; + void *end = ((void *)req) + RPMSG_BUF_SIZE; + int32_t input_id = 0; + + req->hdr = MKHDR(CODEC_PROCESS); + + ret = codec_get(priv, arg->codec_handle, &req->codec, &req->codec_id); + if (ret) + goto rpsend_out; + + ret = PTR_RET(get_paddr(priv, hdr(&req), &req->out_args, arg->out_args_bo)); + if (ret) + return rpabort(hdr(req), ret); + + /* the remainder of the req varies depending on codec family */ + switch (req->codec_id) { + case OMAP_DCE_VIDENC2: + ret = handle_videnc2(priv, &ptr, end, &input_id, req, arg); + break; + case OMAP_DCE_VIDDEC3: + ret = handle_viddec3(priv, &ptr, end, &input_id, req, arg); + break; + default: + ret = -EINVAL; + break; + } + + if (ret) + goto rpsend_out; + + ret = rpsend(priv, &arg->token, hdr(req), ptr - (void *)req); +rpsend_out: + kfree(req); + if (ret) { + /* if input buffer is already locked, unlock it now so we + * don't have a leak: + */ + if (input_id) + codec_unlockbuf(priv, arg->codec_handle, input_id); + return rpabort(hdr(req), ret); + } + } + + /* then wait for reply, which is interruptible */ + ret = rpwait(priv, arg->token, hdr(&rsp), sizeof(*rsp)); + if (ret) + return ret; + + for (i = 0; i < rsp->count; i++) { + codec_unlockbuf(priv, arg->codec_handle, rsp->freebuf_ids[i]); + } + + arg->result = rsp->result; + + kfree(rsp); + + return 0; +} + +static int codec_delete(struct dce_file_priv *priv, uint32_t codec, + enum omap_dce_codec codec_id) +{ + struct dce_rpc_codec_delete_req req = { + .hdr = MKHDR(CODEC_DELETE), + .codec_id = codec_id, + .codec = codec, + }; + return rpsend(priv, NULL, hdr(&req), sizeof(req)); +} + +static int ioctl_codec_delete(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct dce_file_priv *priv = omap_drm_file_priv(file, dce_mapper_id); + struct drm_omap_dce_codec_delete *arg = data; + uint32_t codec, codec_id; + int ret; + + ret = codec_get(priv, arg->codec_handle, &codec, &codec_id); + if (ret) + return ret; + + codec_unregister(priv, arg->codec_handle); + + return codec_delete(priv, codec, codec_id); +} + +/* NOTE: these are not public because the actual ioctl NR is dynamic.. + * use drmCommandXYZ(fd, dce_base + idx, ..) + */ +#define DRM_IOCTL_OMAP_DCE_ENGINE_OPEN DRM_IOWR(DRM_OMAP_DCE_ENGINE_OPEN, struct drm_omap_dce_engine_open) +#define DRM_IOCTL_OMAP_DCE_ENGINE_CLOSE DRM_IOW (DRM_OMAP_DCE_ENGINE_CLOSE, struct drm_omap_dce_engine_close) +#define DRM_IOCTL_OMAP_DCE_CODEC_CREATE DRM_IOWR(DRM_OMAP_DCE_CODEC_CREATE, struct drm_omap_dce_codec_create) +#define DRM_IOCTL_OMAP_DCE_CODEC_CONTROL DRM_IOWR(DRM_OMAP_DCE_CODEC_CONTROL, struct drm_omap_dce_codec_control) +#define DRM_IOCTL_OMAP_DCE_CODEC_PROCESS DRM_IOWR(DRM_OMAP_DCE_CODEC_PROCESS, struct drm_omap_dce_codec_process) +#define DRM_IOCTL_OMAP_DCE_CODEC_DELETE DRM_IOW (DRM_OMAP_DCE_CODEC_DELETE, struct drm_omap_dce_codec_delete) + +static struct drm_ioctl_desc dce_ioctls[] = { + DRM_IOCTL_DEF_DRV(OMAP_DCE_ENGINE_OPEN, ioctl_engine_open, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_DCE_ENGINE_CLOSE, ioctl_engine_close, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_DCE_CODEC_CREATE, ioctl_codec_create, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_DCE_CODEC_CONTROL, ioctl_codec_control, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_DCE_CODEC_PROCESS, ioctl_codec_process, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_DCE_CODEC_DELETE, ioctl_codec_delete, DRM_UNLOCKED|DRM_AUTH), +}; + +/* + * Plugin API: + */ + +static int dce_load(struct drm_device *dev, unsigned long flags) +{ + dce_mapper_id = omap_drm_register_mapper(); + return 0; +} + +static int dce_unload(struct drm_device *dev) +{ + omap_drm_unregister_mapper(dce_mapper_id); + dce_mapper_id = -1; + // XXX should block until pending txns are done.. + return 0; +} + +static int dce_open(struct drm_device *dev, struct drm_file *file) +{ + struct dce_file_priv *priv = kzalloc(sizeof(*priv), GFP_KERNEL); + priv->dev = dev; + priv->file = file; + omap_drm_file_set_priv(file, dce_mapper_id, priv); + return 0; +} + +static int dce_release(struct drm_device *dev, struct drm_file *file) +{ + struct dce_file_priv *priv = omap_drm_file_priv(file, dce_mapper_id); + int i; + + // XXX not sure if this is legit.. maybe we end up with this scenario + // when drm device file is opened prior to dce module being loaded?? + WARN_ON(!priv); + if (!priv) + return 0; + + /* cleanup any remaining codecs and engines on behalf of the process, + * in case the process crashed or didn't clean up properly for itself: + */ + + for (i = 0; i < ARRAY_SIZE(priv->codecs); i++) { + uint32_t codec = priv->codecs[i].codec; + if (codec) { + enum omap_dce_codec codec_id = priv->codecs[i].codec_id; + codec_unregister(priv, i+1); + codec_delete(priv, codec, codec_id); + } + } + + for (i = 0; i < ARRAY_SIZE(priv->engines); i++) { + uint32_t engine = priv->engines[i].engine; + if (engine) { + engine_unregister(priv, i+1); + engine_close(priv, engine); + } + } + + for (i = 0; i < ARRAY_SIZE(txns); i++) { + if (txns[i].priv == priv) { + txn_cleanup(&txns[i]); + memset(&txns[i], 0, sizeof(txns[i])); + } + } + + kfree(priv); + + return 0; +} + +static struct omap_drm_plugin plugin = { + .name = "dce", + + .load = dce_load, + .unload = dce_unload, + .open = dce_open, + .release = dce_release, + + .ioctls = dce_ioctls, + .num_ioctls = ARRAY_SIZE(dce_ioctls), + .ioctl_base = 0, /* initialized when plugin is registered */ +}; + +/* + * RPMSG API: + */ + +static int rpmsg_probe(struct rpmsg_channel *_rpdev) +{ + struct dce_rpc_connect_req req = { + .hdr = MKHDR(CONNECT), + .chipset_id = GET_OMAP_TYPE, + .debug = drm_debug ? 1 : 3, + }; + int ret; + + DBG(""); + rpdev = _rpdev; + + /* send connect msg: */ + ret = rpsend(NULL, NULL, hdr(&req), sizeof(req)); + if (ret) { + DBG("rpsend failed: %d", ret); + return ret; + } + + return omap_drm_register_plugin(&plugin); +} + +static void __devexit rpmsg_remove(struct rpmsg_channel *_rpdev) +{ + DBG(""); + omap_drm_unregister_plugin(&plugin); + rpdev = NULL; +} + +static void rpmsg_cb(struct rpmsg_channel *rpdev, void *data, + int len, void *priv, u32 src) +{ + void *data2; + DBG("len=%d, src=%d", len, src); + /* note: we have to copy the data, because the ptr is no more valid + * once this fxn returns, and it could take a while for the requesting + * thread to pick up the data.. maybe there is a more clever way to + * handle this.. + */ + data2 = kzalloc(len, GFP_KERNEL); + memcpy(data2, data, len); + rpcomplete(data2, len); +} + +static struct rpmsg_device_id rpmsg_id_table[] = { + { .name = "rpmsg-dce" }, + { }, +}; + +static struct rpmsg_driver rpmsg_driver = { + .drv.name = KBUILD_MODNAME, + .drv.owner = THIS_MODULE, + .id_table = rpmsg_id_table, + .probe = rpmsg_probe, + .callback = rpmsg_cb, + .remove = __devexit_p(rpmsg_remove), +}; + +static int __init omap_dce_init(void) +{ + DBG(""); + return register_rpmsg_driver(&rpmsg_driver); +} + +static void __exit omap_dce_fini(void) +{ + DBG(""); + unregister_rpmsg_driver(&rpmsg_driver); +} + +module_init(omap_dce_init); +module_exit(omap_dce_fini); + +MODULE_AUTHOR("Rob Clark <rob.clark@linaro.org>"); +MODULE_DESCRIPTION("OMAP DRM Video Decode/Encode"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/omapdce/dce_rpc.h b/drivers/staging/omapdce/dce_rpc.h new file mode 100644 index 00000000000..7521329d1c1 --- /dev/null +++ b/drivers/staging/omapdce/dce_rpc.h @@ -0,0 +1,140 @@ +/* + * drivers/staging/omapdce/dce_rpc.h + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __DCE_RPC_H__ +#define __DCE_RPC_H__ + +/* RPC layer types.. these define the payload of messages between firmware + * and linux side. This should be kept in sync between firmware build and + * driver. + * + * TODO: xxx_control(XDM_GETVERSION) is a bit awkward to deal with, because + * this seems to be the one special case where status->data is used.. + * possibly we should define a special ioctl and msg to handle this case. + */ + +/* Message-Ids: + */ +#define DCE_RPC_CONNECT 0x00 +/* leave room to add some control plane msgs later */ +#define DCE_RPC_ENGINE_OPEN 0x10 +#define DCE_RPC_ENGINE_CLOSE 0x11 +#define DCE_RPC_CODEC_CREATE 0x12 +#define DCE_RPC_CODEC_CONTROL 0x13 +#define DCE_RPC_CODEC_PROCESS 0x14 +#define DCE_RPC_CODEC_DELETE 0x15 + +struct dce_rpc_hdr { + /* A Message-Id as defined above: + */ + uint16_t msg_id; + + /* request-id is assigned on host side, and echoed back from + * coprocessor to allow the host side to match up responses + * to requests: + */ + uint16_t req_id; +} __packed; + +struct dce_rpc_connect_req { + struct dce_rpc_hdr hdr; + uint32_t chipset_id; + uint32_t debug; +} __packed; + +struct dce_rpc_engine_open_req { + struct dce_rpc_hdr hdr; + char name[32]; +} __packed; + +struct dce_rpc_engine_open_rsp { + struct dce_rpc_hdr hdr; + int32_t error_code; + uint32_t engine; +} __packed; + +struct dce_rpc_engine_close_req { + struct dce_rpc_hdr hdr; + uint32_t engine; +} __packed; + +struct dce_rpc_codec_create_req { + struct dce_rpc_hdr hdr; + uint32_t codec_id; + uint32_t engine; + char name[32]; + uint32_t sparams; +} __packed; + +struct dce_rpc_codec_create_rsp { + struct dce_rpc_hdr hdr; + uint32_t codec; +} __packed; + +struct dce_rpc_codec_control_req { + struct dce_rpc_hdr hdr; + uint32_t codec_id; + uint32_t codec; + uint32_t cmd_id; + uint32_t dparams; + uint32_t status; +} __packed; + +struct dce_rpc_codec_control_rsp { + struct dce_rpc_hdr hdr; + int32_t result; +} __packed; + +/* NOTE: CODEC_PROCESS does somewhat more than the other ioctls, in that it + * handles buffer mapping/unmapping. So the inBufs/outBufs are copied inline + * (with translated addresses in the copy sent inline with codec_process_req). + * Since we need the inputID from inArgs, and it is a small struct, it is also + * copied inline. + * + * Therefore, the variable length data[] section has the format: + * uint8_t inargs[in_args_length * 4]; + * uint8_t outbufs[in_bufs_length * 4]; + * uint8_t inbufs[in_bufs_length * 4]; + */ +struct dce_rpc_codec_process_req { + struct dce_rpc_hdr hdr; + uint32_t codec_id; + uint32_t codec; + uint8_t pad; + uint8_t in_args_len; /* length/4 */ + uint8_t out_bufs_len; /* length/4 */ + uint8_t in_bufs_len; /* length/4 */ + uint32_t out_args; + uint8_t data[]; +} __packed; + +struct dce_rpc_codec_process_rsp { + struct dce_rpc_hdr hdr; + int32_t result; + uint8_t count; + int32_t freebuf_ids[]; +} __packed; + +struct dce_rpc_codec_delete_req { + struct dce_rpc_hdr hdr; + uint32_t codec_id; + uint32_t codec; +} __packed; + +#endif /* __DCE_RPC_H__ */ diff --git a/drivers/staging/omapdce/omap_dce.h b/drivers/staging/omapdce/omap_dce.h new file mode 100644 index 00000000000..317ff9d28e7 --- /dev/null +++ b/drivers/staging/omapdce/omap_dce.h @@ -0,0 +1,110 @@ +/* + * include/drm/omap_drm.h + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __OMAP_DCE_H__ +#define __OMAP_DCE_H__ + +/* Please note that modifications to all structs defined here are subject to + * backwards-compatibility constraints. + * + * Cacheability and control structs: + * ------------ --- ------- -------- + * All the ioctl params w/ names ending in _bo are GEM buffer object handles + * for control structures that are shared with the coprocessor. You probably + * want to create them as uncached or writecombine. + * + * NOTE: The DCE ioctl calls are synchronous, and the coprocessor will not + * access the control structures passed as parameters other than during + * the duration of the ioctl call that they are passed to. So if we avoid + * adding asynchronous ioctl calls (or if we do, if userspace brackets CPU + * access to the control structures with OMAP_GEM_CPU_PREP/OMAP_GEM_CPU_FINI), + * we could handle the necessary clean/invalidate ops in the ioctl handlers. + * But for now be safe and use OMAP_BO_WC. + * + * About resuming interrupted ioctl calls: + * ----- -------- ----------- ----- ------ + * In the ioctl command structs which return values, there is a 'token' + * field. Userspace should initially set this value to zero. If the + * syscall is interrupted, the driver will set a (file-private) token + * value, and userspace should loop and re-start the ioctl returning the + * token value that the driver set. This allows the driver to realize + * that it is a restarted syscall, and that it should simply wait for a + * response from coprocessor rather than starting a new request. + */ + +enum omap_dce_codec { + OMAP_DCE_VIDENC2 = 1, + OMAP_DCE_VIDDEC3 = 2, +}; + +struct drm_omap_dce_engine_open { + char name[32]; /* engine name (in) */ + int32_t error_code; /* error code (out) */ + uint32_t eng_handle; /* engine handle (out) */ + uint32_t token; +}; + +struct drm_omap_dce_engine_close { + uint32_t eng_handle; /* engine handle (in) */ + uint32_t __pad; +}; + +struct drm_omap_dce_codec_create { + uint32_t codec_id; /* enum omap_dce_codec (in) */ + uint32_t eng_handle; /* engine handle (in) */ + char name[32]; /* codec name (in) */ + uint32_t sparams_bo; /* static params (in) */ + uint32_t codec_handle; /* codec handle, zero if failed (out) */ + uint32_t token; +}; + +struct drm_omap_dce_codec_control { + uint32_t codec_handle; /* codec handle (in) */ + uint32_t cmd_id; /* control cmd id (in) */ + uint32_t dparams_bo; /* dynamic params (in) */ + uint32_t status_bo; /* status (in) */ + int32_t result; /* return value (out) */ + uint32_t token; +}; + +struct drm_omap_dce_codec_process { + uint32_t codec_handle; /* codec handle (in) */ + uint32_t out_args_bo; /* output args (in) */ + uint64_t in_args; /* input args ptr (in) */ + uint64_t out_bufs; /* output buffer-descriptor ptr (in) */ + uint64_t in_bufs; /* input buffer-descriptor ptr (in) */ + int32_t result; /* return value (out) */ + uint32_t token; +}; + +struct drm_omap_dce_codec_delete { + uint32_t codec_handle; /* codec handle (in) */ + uint32_t __pad; +}; + +#define DRM_OMAP_DCE_ENGINE_OPEN 0x00 +#define DRM_OMAP_DCE_ENGINE_CLOSE 0x01 +#define DRM_OMAP_DCE_CODEC_CREATE 0x02 +#define DRM_OMAP_DCE_CODEC_CONTROL 0x03 +#define DRM_OMAP_DCE_CODEC_PROCESS 0x04 +#define DRM_OMAP_DCE_CODEC_DELETE 0x05 + +#define XDM_MEMTYPE_BO 10 + +#endif /* __OMAP_DCE_H__ */ |