You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kernel_samsung_sm7125/drivers/soc/qcom/hgsl/hgsl.c

1510 lines
35 KiB

/*
* Copyright (c) 2019-2020, 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 <asm/ioctl.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/dma-buf.h>
#include <linux/interrupt.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/regmap.h>
#include <linux/uaccess.h>
#include <uapi/linux/hgsl.h>
#include "hgsl_tcsr.h"
#define HGSL_DEVICE_NAME "hgsl"
#define HGSL_DEV_NUM 1
/* Support upto 3 GVMs: 3 DBQs(Low/Medium/High priority) per GVM */
#define MAX_DB_QUEUE 9
#define IORESOURCE_HWINF "hgsl_reg_hwinf"
#define IORESOURCE_GMUCX "hgsl_reg_gmucx"
/* Set-up profiling packets as needed by scope */
#define CMDBATCH_PROFILING 0x00000010
/* Ping the user of HFI when this command is done */
#define CMDBATCH_NOTIFY 0x00000020
#define CMDBATCH_EOF 0x00000100
/* timeout of waiting for free space of doorbell queue. */
#define HGSL_IDLE_TIMEOUT msecs_to_jiffies(1000)
#define GLB_DB_SRC_ISSUEIB_IRQ_ID_0 TCSR_SRC_IRQ_ID_0
#define GLB_DB_SRC_ISSUEIB_IRQ_ID_1 TCSR_SRC_IRQ_ID_1
#define GLB_DB_SRC_ISSUEIB_IRQ_ID_2 TCSR_SRC_IRQ_ID_2
#define GLB_DB_DEST_TS_RETIRE_IRQ_ID TCSR_DEST_IRQ_ID_0
#define GLB_DB_DEST_TS_RETIRE_IRQ_MASK TCSR_DEST_IRQ_MASK_0
#define HGSL_TCSR_NUM 2
struct hw_version {
unsigned int version;
unsigned int release;
};
struct reg {
unsigned long paddr;
unsigned long size;
void __iomem *vaddr;
};
struct db_buffer {
int32_t dwords;
void *vaddr;
};
#define QHDR_STATUS_INACTIVE 0x00
#define QHDR_STATUS_ACTIVE 0x01
/* Updated by HGSL */
#define DBQ_WRITE_INDEX_IN_DWORD 0
/* Updated by GMU */
#define DBQ_READ_INDEX_IN_DWORD 1
#define HGSL_DBQ_HFI_Q_INDEX_BASE_OFFSET_IN_DWORD (1536 >> 2)
#define HGSL_DBQ_CONTEXT_INFO_BASE_OFFSET_IN_DWORD (2048 >> 2)
#define HGSL_CONTEXT_NUM 128
/* Meta info for context */
enum HGSL_DBQ_METADATA_INFO {
HGSL_DBQ_METADATA_CTXT_ID_IN_DWORD = 0x0,
HGSL_DBQ_METADATA_TIMESTAMP_IN_DWORD = 0x1,
HGSL_DBQ_METADATA_CTXT_DESTROY_IN_DWORD = 0x2,
HGSL_DBQ_METADATA_TOTAL_ENTITY_NUM,
/* The context metadata size is 2K in DBQ,
* so we can have maximum 4 dwords for each context
*/
HGSL_DBQ_METADATA_TOTAL_ENTITY_MAX = 0x4
};
/* DBQ structure
* | IBs storage | reserved | w.idx/r.idx | ctxt.info|
* ---------------------------------------------------------------------------
* | [0] | [1K] | [1.5K] | [2K] |
* ---------------------------------------------------------------------------
*/
static inline void dbq_set_qindex(uint32_t *va_base,
uint32_t offset,
uint32_t value)
{
uint32_t *dest;
dest = va_base + HGSL_DBQ_HFI_Q_INDEX_BASE_OFFSET_IN_DWORD + offset;
*dest = value;
}
static inline uint32_t dbq_get_qindex(uint32_t *va_base,
uint32_t offset)
{
uint32_t *dest;
uint32_t val;
dest = va_base + HGSL_DBQ_HFI_Q_INDEX_BASE_OFFSET_IN_DWORD + offset;
val = *dest;
/* ensure read is done before return */
rmb();
return val;
}
static inline void dbq_update_ctxt_info(uint32_t *va_base,
uint32_t ctxt_id,
uint32_t offset,
uint32_t value)
{
uint32_t *dest = (uint32_t *)(va_base +
HGSL_DBQ_CONTEXT_INFO_BASE_OFFSET_IN_DWORD +
(HGSL_DBQ_METADATA_TOTAL_ENTITY_NUM * ctxt_id) +
offset);
if (WARN_ON(ctxt_id >= HGSL_CONTEXT_NUM))
return;
*dest = value;
}
#define RGS_HFI_DEFAULT_TIMEOUT_MS 5000
#define HFI_MSG_TYPE_CMD 0
#define HFI_MSG_TYPE_RET 1
/* HFI command define. */
#define HTOF_MSG_ISSUE_CMD 130
#define MSG_ISSUE_INF_SZ() (sizeof(struct hgsl_db_cmds) >> 2)
#define MSG_ISSUE_IBS_SZ(numIB) \
((numIB) * (sizeof(struct hgsl_fw_ib_desc) >> 2))
#define MSG_SEQ_NO_MASK 0xFFF00000
#define MSG_SEQ_NO_SHIFT 20
#define MSG_SEQ_NO_GET(x) (((x) & MSG_SEQ_NO_MASK) >> MSG_SEQ_NO_SHIFT)
#define MSG_TYPE_MASK 0x000F0000
#define MSG_TYPE_SHIFT 16
#define MSG_TYPE_GET(x) (((x) & MSG_TYPE_MASK) >> MSG_TYPE_SHIFT)
#define MSG_SZ_MASK 0x0000FF00
#define MSG_SZ_SHIFT 8
#define MSG_SZ_GET(x) (((x) & MSG_SZ_MASK) >> MSG_SZ_SHIFT)
#define MSG_ID_MASK 0x000000FF
#define MSG_ID_GET(x) ((x) & MSG_ID_MASK)
#define MAKE_HFI_MSG_HEADER(msgID, msgType, msgSize, msgSeqnum) \
((msgID) | ((msgSize) << MSG_SZ_SHIFT) | \
((msgType) << MSG_TYPE_SHIFT) | \
((msgSeqnum) << MSG_SEQ_NO_SHIFT))
#define HFI_ISSUE_IB_HEADER(numIB, sz, msgSeqnum) \
MAKE_HFI_MSG_HEADER( \
HTOF_MSG_ISSUE_CMD, \
HFI_MSG_TYPE_CMD, \
sz,\
msgSeqnum)
/*
* GMU HFI memory allocation options:
* RGS_GMU_HFI_BUFFER_DTCM: Allocated from GMU CM3 DTCM.
* RGS_GMU_HFI_BUFFER_NON_CACHEMEM: POR mode. Allocated from non cached memory.
*/
enum db_buffer_mode_t {
RGS_GMU_HFI_BUFFER_DTCM = 0,
RGS_GMU_HFI_BUFFER_NON_CACHEMEM = 1,
RGS_GMU_HFI_BUFFER_DEFAULT = 1
};
struct db_msg_request {
int msg_has_response;
int msg_has_ret_packet;
int ignore_ret_packet;
void *ptr_data;
unsigned int msg_dwords;
} __packed;
struct db_msg_response {
void *ptr_data;
unsigned int size_dword;
} __packed;
/*
* IB start address
* IB size
*/
struct hgsl_fw_ib_desc {
uint64_t addr;
uint32_t sz;
} __packed;
/*
* Context ID
* cmd_flags
* Per-context user space gsl timestamp. It has to be
* greater than last retired timestamp.
* Number of IB descriptors
* An array of IB descriptors
*/
struct hgsl_db_cmds {
uint32_t header;
uint32_t ctx_id;
uint32_t cmd_flags;
uint32_t timestamp;
uint64_t user_profile_gpuaddr;
uint32_t num_ibs;
uint32_t ib_desc_gmuaddr;
struct hgsl_fw_ib_desc ib_descs[];
} __packed;
struct hgsl_db_msg_ret {
uint32_t header;
uint32_t ack;
uint32_t err;
} __packed;
struct db_msg_id {
uint32_t seq_no;
uint32_t msg_id;
} __packed;
struct db_wait_retpacket {
size_t event_signal;
int in_use;
struct db_msg_id db_msg_id;
struct db_msg_response response;
} __packed;
struct db_ignore_retpacket {
int in_use;
struct db_msg_id db_msg_id;
} __packed;
struct doorbell_queue {
struct dma_buf *dma;
void *vbase;
struct db_buffer data;
uint32_t state;
struct mutex lock;
};
struct hgsl_context {
uint32_t context_id;
struct dma_buf *shadow_dma;
void *shadow_vbase;
uint32_t shadow_sop_off;
uint32_t shadow_eop_off;
wait_queue_head_t wait_q;
pid_t pid;
bool dbq_assigned;
bool in_destroy;
struct kref kref;
};
struct hgsl_active_wait {
struct list_head head;
struct hgsl_context *ctxt;
unsigned int timestamp;
};
struct qcom_hgsl {
struct device *dev;
struct mutex lock;
/* character device info */
struct cdev cdev;
dev_t device_no;
struct class *driver_class;
struct device *class_dev;
/* registers mapping */
struct reg reg_ver;
struct reg reg_dbidx;
atomic_t seq_num;
struct doorbell_queue dbq[MAX_DB_QUEUE];
/* global doorbell tcsr */
struct hgsl_tcsr *tcsr[HGSL_TCSR_NUM][HGSL_TCSR_ROLE_MAX];
int tcsr_idx;
struct hgsl_context **contexts;
rwlock_t ctxt_lock;
struct list_head active_wait_list;
spinlock_t active_wait_lock;
struct workqueue_struct *wq;
struct work_struct ts_retire_work;
struct hw_version *ver;
};
struct hgsl_priv {
struct qcom_hgsl *dev;
uint32_t dbq_idx;
pid_t pid;
};
static int hgsl_reg_map(struct platform_device *pdev,
char *res_name, struct reg *reg);
static void hgsl_reg_read(struct reg *reg, unsigned int off,
unsigned int *value)
{
if (reg == NULL)
return;
if (WARN(off > reg->size,
"Invalid reg read:0x%x, reg size:0x%x\n",
off, reg->size))
return;
*value = __raw_readl(reg->vaddr + off);
/* ensure this read finishes before the next one.*/
rmb();
}
static void hgsl_reg_write(struct reg *reg, unsigned int off,
unsigned int value)
{
if (reg == NULL)
return;
if (WARN(off > reg->size,
"Invalid reg write:0x%x, reg size:0x%x\n",
off, reg->size))
return;
/*
* ensure previous writes post before this one,
* i.e. act like normal writel()
*/
wmb();
__raw_writel(value, (reg->vaddr + off));
}
static inline bool is_global_db(int tcsr_idx)
{
return (tcsr_idx >= 0);
}
static void gmu_ring_local_db(struct qcom_hgsl *hgsl, unsigned int value)
{
hgsl_reg_write(&hgsl->reg_dbidx, 0, value);
}
static void tcsr_ring_global_db(struct qcom_hgsl *hgsl, uint32_t tcsr_idx,
uint32_t dbq_idx)
{
hgsl_tcsr_irq_trigger(hgsl->tcsr[tcsr_idx][HGSL_TCSR_ROLE_SENDER],
GLB_DB_SRC_ISSUEIB_IRQ_ID_0 + dbq_idx);
}
static bool hgsl_ctx_dbq_ready(struct hgsl_priv *priv)
{
struct qcom_hgsl *hgsl;
struct doorbell_queue *dbq;
hgsl = priv->dev;
dbq = &hgsl->dbq[priv->dbq_idx];
if ((dbq->state & DB_STATE_Q_MASK) == DB_STATE_Q_INIT_DONE)
return true;
return false;
}
static uint32_t db_queue_freedwords(struct doorbell_queue *dbq)
{
uint32_t queue_size;
uint32_t queue_used;
uint32_t wptr;
uint32_t rptr;
if (dbq == NULL)
return 0;
wptr = dbq_get_qindex(dbq->vbase, DBQ_WRITE_INDEX_IN_DWORD);
rptr = dbq_get_qindex(dbq->vbase, DBQ_READ_INDEX_IN_DWORD);
queue_size = dbq->data.dwords;
queue_used = (wptr + queue_size - rptr) % queue_size;
return (queue_size - queue_used - 1);
}
static int db_queue_wait_freewords(struct doorbell_queue *dbq,
uint32_t size)
{
unsigned long t = jiffies + HGSL_IDLE_TIMEOUT;
if (size == 0)
return 0;
if (dbq == NULL)
return -EINVAL;
do {
if (db_queue_freedwords(dbq) >= size)
return 0;
} while (time_before(jiffies, t));
return -ETIMEDOUT;
}
static int db_send_msg(struct hgsl_priv *priv,
struct db_msg_id *db_msg_id,
struct db_msg_request *msg_req,
struct db_msg_response *msg_resp)
{
uint32_t msg_size_align;
int ret;
uint8_t *src, *dst;
uint32_t move_dwords, resid_move_dwords;
uint32_t queue_size_dword;
struct qcom_hgsl *hgsl;
struct doorbell_queue *dbq;
uint32_t wptr;
hgsl = priv->dev;
dbq = &hgsl->dbq[priv->dbq_idx];
mutex_lock(&hgsl->lock);
queue_size_dword = dbq->data.dwords;
msg_size_align = ALIGN(msg_req->msg_dwords, 4);
ret = db_queue_wait_freewords(dbq, msg_size_align);
if (ret < 0) {
dev_err(hgsl->dev,
"Timed out waiting for queue to free up\n");
goto quit;
}
wptr = dbq_get_qindex(dbq->vbase, DBQ_WRITE_INDEX_IN_DWORD);
move_dwords = msg_req->msg_dwords;
if ((msg_req->msg_dwords + wptr) >= queue_size_dword) {
move_dwords = queue_size_dword - wptr;
resid_move_dwords = msg_req->msg_dwords - move_dwords;
dst = (uint8_t *)dbq->data.vaddr;
src = msg_req->ptr_data + (move_dwords << 2);
memcpy(dst, src, (resid_move_dwords << 2));
}
dst = dbq->data.vaddr + (wptr << 2);
src = msg_req->ptr_data;
memcpy(dst, src, (move_dwords << 2));
wptr = (wptr + msg_size_align) % queue_size_dword;
dbq_set_qindex((uint32_t *)dbq->vbase,
DBQ_WRITE_INDEX_IN_DWORD,
wptr);
dbq_update_ctxt_info((uint32_t *)dbq->vbase,
((struct hgsl_db_cmds *)src)->ctx_id,
HGSL_DBQ_METADATA_TIMESTAMP_IN_DWORD,
((struct hgsl_db_cmds *)src)->timestamp);
/* confirm write to memory done before ring door bell. */
wmb();
if (is_global_db(hgsl->tcsr_idx))
/* trigger TCSR interrupt for global doorbell */
tcsr_ring_global_db(hgsl, hgsl->tcsr_idx, priv->dbq_idx);
else
/* trigger GMU interrupt */
gmu_ring_local_db(hgsl, priv->dbq_idx);
quit:
mutex_unlock(&hgsl->lock);
return ret;
}
static int hgsl_db_issue_cmd(struct hgsl_priv *priv,
uint32_t ctx_id, uint32_t num_ibs,
uint32_t gmu_cmd_flags,
uint32_t timestamp,
struct hgsl_fw_ib_desc ib_descs[])
{
int ret;
uint32_t msg_dwords;
uint32_t msg_buf_sz;
struct hgsl_db_cmds *cmds;
struct db_msg_request req;
struct db_msg_response resp;
struct db_msg_id db_msg_id;
struct doorbell_queue *dbq;
struct qcom_hgsl *hgsl = priv->dev;
uint32_t seq_num;
db_msg_id.msg_id = HTOF_MSG_ISSUE_CMD;
seq_num = atomic_inc_return(&hgsl->seq_num);
db_msg_id.seq_no = seq_num;
dbq = &hgsl->dbq[priv->dbq_idx];
msg_dwords = MSG_ISSUE_INF_SZ() + MSG_ISSUE_IBS_SZ(num_ibs);
msg_buf_sz = ALIGN(msg_dwords, 4) << 2;
if (msg_buf_sz > dbq->data.dwords) {
dev_err(hgsl->dev, "number of IBs exceed\n");
return -EINVAL;
}
cmds = kmalloc(msg_buf_sz, GFP_KERNEL);
if (cmds == NULL)
return -ENOMEM;
cmds->header = HFI_ISSUE_IB_HEADER(num_ibs,
msg_dwords,
db_msg_id.seq_no);
cmds->ctx_id = ctx_id;
cmds->num_ibs = num_ibs;
cmds->cmd_flags = gmu_cmd_flags;
cmds->timestamp = timestamp;
memcpy(cmds->ib_descs, ib_descs, sizeof(ib_descs[0]) * num_ibs);
req.msg_has_response = 0;
req.msg_has_ret_packet = 0;
req.ignore_ret_packet = 1;
req.msg_dwords = msg_dwords;
req.ptr_data = cmds;
ret = db_send_msg(priv, &db_msg_id, &req, &resp);
kfree(cmds);
return ret;
}
#define USRPTR(a) u64_to_user_ptr((uint64_t)(a))
static int hgsl_cmdstream_db_issueib(struct file *filep,
unsigned long arg)
{
struct hgsl_priv *priv = filep->private_data;
struct qcom_hgsl *hgsl = priv->dev;
struct hgsl_ibdesc *ibs;
struct hgsl_mem_object *bos;
struct hgsl_fw_ib_desc *fw_ib_list = NULL;
struct hgsl_fhi_issud_cmds submit_info;
struct hgsl_fhi_issud_cmds *submit;
uint64_t submit_size;
uint32_t gmu_flags = CMDBATCH_NOTIFY;
int idx;
int ret = 0;
copy_from_user(&submit_info, USRPTR(arg), sizeof(submit_info));
if (!hgsl_ctx_dbq_ready(priv)) {
dev_err(hgsl->dev, "Doorbell invalid\n");
return -EINVAL;
}
submit_size = sizeof(*submit) +
submit_info.num_ibs * sizeof(ibs[0]) +
submit_info.num_bos * sizeof(bos[0]);
if (submit_size == 0 || submit_size > U32_MAX)
return -EINVAL;
submit = kmalloc(submit_size, GFP_KERNEL);
if (submit == NULL)
return -ENOMEM;
submit->context_id = submit_info.context_id;
submit->timestamp = submit_info.timestamp;
submit->flags = submit_info.flags;
submit->num_ibs = submit_info.num_ibs;
submit->num_bos = submit_info.num_bos;
bos = (void *)(submit) + sizeof(*submit);
ibs = (void *)&(bos[submit->num_bos]);
if (submit->num_ibs) {
ret = copy_from_user(ibs, USRPTR(submit_info.ibs),
(sizeof(ibs[0]) * submit->num_ibs));
if (ret) {
ret = -EFAULT;
goto exit;
}
fw_ib_list = kmalloc((sizeof(*fw_ib_list) * submit->num_ibs),
GFP_KERNEL);
if (fw_ib_list == NULL) {
ret = -ENOMEM;
goto exit;
}
}
if (submit->num_bos) {
ret = copy_from_user(bos, USRPTR(submit_info.bos),
(sizeof(bos[0]) * submit->num_bos));
if (ret) {
ret = -EFAULT;
goto exit;
}
}
for (idx = 0; idx < submit->num_ibs; ++idx) {
fw_ib_list[idx].addr = ibs[idx].gpuaddr;
fw_ib_list[idx].sz = ibs[idx].sizedwords << 2;
}
if (submit->num_ibs)
ret = hgsl_db_issue_cmd(priv, submit->context_id,
submit->num_ibs,
gmu_flags,
submit->timestamp,
fw_ib_list);
exit:
if (ret) {
dev_err(hgsl->dev,
"issueib with ts (%d) from ctxt (%d) failed\n",
submit->timestamp, submit->context_id);
}
kfree(submit);
kfree(fw_ib_list);
return ret;
}
static int hgsl_dbq_get_state(struct file *filep,
unsigned long arg)
{
struct hgsl_priv *priv = filep->private_data;
struct qcom_hgsl *hgsl = priv->dev;
struct doorbell_queue *dbq = &hgsl->dbq[priv->dbq_idx];
uint32_t state;
state = dbq->state & DB_STATE_Q_MASK;
if (copy_to_user(USRPTR(arg), &state, sizeof(state)))
return -EFAULT;
return 0;
}
static void hgsl_reset_dbq(struct doorbell_queue *dbq)
{
if (dbq->dma) {
dma_buf_end_cpu_access(dbq->dma,
DMA_BIDIRECTIONAL);
if (dbq->vbase) {
dma_buf_vunmap(dbq->dma, dbq->vbase);
dbq->vbase = NULL;
}
dma_buf_put(dbq->dma);
dbq->dma = NULL;
}
dbq->state = DB_STATE_Q_UNINIT;
}
static int hgsl_dbq_assign(struct file *filep, unsigned long arg)
{
struct hgsl_priv *priv = filep->private_data;
struct qcom_hgsl *hgsl = priv->dev;
struct doorbell_queue *dbq;
unsigned int dbq_idx;
if (copy_from_user(&dbq_idx, USRPTR(arg), sizeof(dbq_idx)))
return -EFAULT;
if (dbq_idx >= MAX_DB_QUEUE)
return -EINVAL;
priv->dbq_idx = dbq_idx;
dbq = &hgsl->dbq[priv->dbq_idx];
return dbq->state;
}
static inline bool _timestamp_retired(struct hgsl_context *ctxt,
unsigned int timestamp)
{
unsigned int ts = *(unsigned int *)(ctxt->shadow_vbase +
ctxt->shadow_eop_off);
/* ensure read is done before comparison */
rmb();
return (ts >= timestamp);
}
static irqreturn_t hgsl_tcsr_isr(struct device *dev, uint32_t status)
{
struct platform_device *pdev = to_platform_device(dev);
struct qcom_hgsl *hgsl = platform_get_drvdata(pdev);
if ((status & GLB_DB_DEST_TS_RETIRE_IRQ_MASK) == 0)
return IRQ_NONE;
queue_work(hgsl->wq, &hgsl->ts_retire_work);
return IRQ_HANDLED;
}
static void ts_retire_worker(struct work_struct *work)
{
struct qcom_hgsl *hgsl =
container_of(work, struct qcom_hgsl, ts_retire_work);
struct hgsl_active_wait *wait, *w;
spin_lock(&hgsl->active_wait_lock);
list_for_each_entry_safe(wait, w, &hgsl->active_wait_list, head) {
if (_timestamp_retired(wait->ctxt, wait->timestamp))
wake_up_all(&wait->ctxt->wait_q);
}
spin_unlock(&hgsl->active_wait_lock);
}
static int hgsl_init_global_db(struct qcom_hgsl *hgsl,
enum hgsl_tcsr_role role, int idx)
{
struct device *dev = hgsl->dev;
struct device_node *np = dev->of_node;
bool is_sender = (role == HGSL_TCSR_ROLE_SENDER);
const char *node_name = is_sender ? "qcom,glb-db-senders" :
"qcom,glb-db-receivers";
struct device_node *tcsr_np;
struct platform_device *tcsr_pdev;
struct hgsl_tcsr *tcsr;
int ret;
if (hgsl->tcsr[idx][role] != NULL)
return 0;
tcsr_np = of_parse_phandle(np, node_name, idx);
if (IS_ERR_OR_NULL(tcsr_np)) {
dev_err(dev, "failed to find %s node\n", node_name);
ret = -ENODEV;
goto fail;
}
tcsr_pdev = of_find_device_by_node(tcsr_np);
if (IS_ERR_OR_NULL(tcsr_pdev)) {
dev_err(dev,
"failed to find %s tcsr dev from node\n",
is_sender ? "sender" : "receiver");
ret = -ENODEV;
goto fail;
}
tcsr = hgsl_tcsr_request(tcsr_pdev, role, dev,
is_sender ? NULL : hgsl_tcsr_isr);
if (IS_ERR_OR_NULL(tcsr)) {
dev_err(dev,
"failed to request %s tcsr, ret %lx\n",
is_sender ? "sender" : "receiver", PTR_ERR(tcsr));
ret = tcsr ? PTR_ERR(tcsr) : -ENODEV;
goto fail;
}
ret = hgsl_tcsr_enable(tcsr);
if (ret) {
dev_err(dev,
"failed to enable %s tcsr, ret %d\n",
is_sender ? "sender" : "receiver", ret);
goto free_tcsr;
}
if (!is_sender) {
hgsl->contexts = kzalloc(sizeof(struct hgsl_context *) *
HGSL_CONTEXT_NUM, GFP_KERNEL);
if (!hgsl->contexts) {
ret = -ENOMEM;
goto disable_tcsr;
}
hgsl->wq = create_workqueue("hgsl-wq");
if (IS_ERR_OR_NULL(hgsl->wq)) {
dev_err(dev, "failed to create workqueue\n");
ret = PTR_ERR(hgsl->wq);
goto free_contexts;
}
INIT_WORK(&hgsl->ts_retire_work, ts_retire_worker);
INIT_LIST_HEAD(&hgsl->active_wait_list);
spin_lock_init(&hgsl->active_wait_lock);
rwlock_init(&hgsl->ctxt_lock);
hgsl_tcsr_irq_enable(tcsr, GLB_DB_DEST_TS_RETIRE_IRQ_MASK,
true);
}
hgsl->tcsr[idx][role] = tcsr;
return 0;
free_contexts:
kfree(hgsl->contexts);
hgsl->contexts = NULL;
disable_tcsr:
hgsl_tcsr_disable(tcsr);
free_tcsr:
hgsl_tcsr_free(tcsr);
fail:
return ret;
}
static int hgsl_init_local_db(struct qcom_hgsl *hgsl)
{
struct platform_device *pdev = to_platform_device(hgsl->dev);
if (hgsl->reg_dbidx.vaddr != NULL)
return 0;
else
return hgsl_reg_map(pdev, IORESOURCE_GMUCX, &hgsl->reg_dbidx);
}
static int hgsl_init_db_signal(struct qcom_hgsl *hgsl, int tcsr_idx)
{
int ret;
if (is_global_db(tcsr_idx)) {
ret = hgsl_init_global_db(hgsl, HGSL_TCSR_ROLE_SENDER,
tcsr_idx);
ret |= hgsl_init_global_db(hgsl, HGSL_TCSR_ROLE_RECEIVER,
tcsr_idx);
} else {
ret = hgsl_init_local_db(hgsl);
}
return ret;
}
static int hgsl_dbq_init(struct file *filep, unsigned long arg)
{
struct hgsl_priv *priv = filep->private_data;
struct qcom_hgsl *hgsl = priv->dev;
struct dma_buf *dmabuf;
struct doorbell_queue *dbq;
struct hgsl_db_queue_inf param;
int tcsr_idx;
int ret;
copy_from_user(&param, USRPTR(arg), sizeof(param));
if (param.fd < 0) {
dev_err(hgsl->dev, "Invalid dbq fd\n");
return -EINVAL;
}
if (param.head_off_dwords > INT_MAX ||
param.queue_off_dwords > INT_MAX) {
dev_err(hgsl->dev, "Invalid dbq offset\n");
return -EINVAL;
}
if ((param.db_signal <= DB_SIGNAL_INVALID) ||
(param.db_signal > DB_SIGNAL_MAX)) {
dev_err(hgsl->dev, "Invalid db signal %d\n", param.db_signal);
return -EINVAL;
}
dbq = &hgsl->dbq[priv->dbq_idx];
mutex_lock(&dbq->lock);
if (dbq->state == DB_STATE_Q_INIT_DONE) {
mutex_unlock(&dbq->lock);
return 0;
}
dbq->state = DB_STATE_Q_FAULT;
dmabuf = dma_buf_get(param.fd);
if (IS_ERR_OR_NULL(dmabuf)) {
dev_err(hgsl->dev, "Import DBQ buffer fail\n");
ret = -EFAULT;
goto err;
}
dbq->dma = dmabuf;
dma_buf_begin_cpu_access(dbq->dma, DMA_BIDIRECTIONAL);
dbq->vbase = dma_buf_vmap(dbq->dma);
if (dbq->vbase == NULL) {
dma_buf_put(dmabuf);
ret = -EFAULT;
goto err;
}
WARN_ON(param.head_dwords < 2);
dbq_set_qindex((uint32_t *)dbq->vbase,
DBQ_WRITE_INDEX_IN_DWORD, 0);
dbq_set_qindex((uint32_t *)dbq->vbase,
DBQ_READ_INDEX_IN_DWORD, 0);
dbq->data.vaddr = dbq->vbase + (param.queue_off_dwords << 2);
dbq->data.dwords = param.queue_dwords;
tcsr_idx = (param.db_signal != DB_SIGNAL_LOCAL) ?
param.db_signal - DB_SIGNAL_GLOBAL_0 : -1;
ret = hgsl_init_db_signal(hgsl, tcsr_idx);
if (ret != 0) {
dev_err(hgsl->dev, "failed to init dbq signal %d, idx %d\n",
param.db_signal, priv->dbq_idx);
goto err;
}
hgsl->tcsr_idx = tcsr_idx;
dbq->state = DB_STATE_Q_INIT_DONE;
mutex_unlock(&dbq->lock);
return 0;
err:
hgsl_reset_dbq(dbq);
mutex_unlock(&dbq->lock);
return ret;
}
static inline void _destroy_context(struct kref *kref)
{
struct hgsl_context *ctxt =
container_of(kref, struct hgsl_context, kref);
dma_buf_vunmap(ctxt->shadow_dma, ctxt->shadow_vbase);
dma_buf_end_cpu_access(ctxt->shadow_dma, DMA_FROM_DEVICE);
dma_buf_put(ctxt->shadow_dma);
kfree(ctxt);
}
static int hgsl_context_create(struct file *filep, unsigned long arg)
{
struct hgsl_priv *priv = filep->private_data;
struct qcom_hgsl *hgsl = priv->dev;
struct hgsl_ctxt_create_info param;
struct dma_buf *dmabuf;
void *vbase;
struct hgsl_context *ctxt;
int ret;
if (!is_global_db(hgsl->tcsr_idx)) {
dev_err(hgsl->dev, "Global doorbell not supported for this process\n");
return -ENODEV;
}
copy_from_user(&param, USRPTR(arg), sizeof(param));
if (param.shadow_fd < 0) {
dev_err(hgsl->dev, "Invalid shadow fd %d\n",
param.shadow_fd);
return -EBADF;
}
if (param.context_id >= HGSL_CONTEXT_NUM) {
dev_err(hgsl->dev, "Invalid context id %d\n",
param.context_id);
return -EINVAL;
}
dmabuf = dma_buf_get(param.shadow_fd);
if (IS_ERR_OR_NULL(dmabuf)) {
dev_err(hgsl->dev, "Import shadow buffer fail\n");
return -EFAULT;
}
dma_buf_begin_cpu_access(dmabuf, DMA_FROM_DEVICE);
vbase = dma_buf_vmap(dmabuf);
if (vbase == NULL) {
ret = -EFAULT;
goto err_dma_put;
}
ctxt = kzalloc(sizeof(ctxt), GFP_KERNEL);
if (ctxt == NULL) {
ret = -ENOMEM;
goto err_dma_unmap;
}
ctxt->context_id = param.context_id;
ctxt->shadow_dma = dmabuf;
ctxt->shadow_vbase = vbase;
ctxt->shadow_sop_off = param.shadow_sop_offset;
ctxt->shadow_eop_off = param.shadow_eop_offset;
init_waitqueue_head(&ctxt->wait_q);
write_lock(&hgsl->ctxt_lock);
if (hgsl->contexts[param.context_id] != NULL) {
dev_err(hgsl->dev,
"context id %d already created\n",
param.context_id);
ret = -EBUSY;
goto err_unlock;
}
hgsl->contexts[param.context_id] = ctxt;
priv->pid = task_pid_nr(current);
hgsl->contexts[param.context_id]->pid = priv->pid;
hgsl->contexts[param.context_id]->dbq_assigned = true;
kref_init(&ctxt->kref);
write_unlock(&hgsl->ctxt_lock);
return 0;
err_unlock:
write_unlock(&hgsl->ctxt_lock);
kfree(ctxt);
err_dma_unmap:
dma_buf_vunmap(dmabuf, vbase);
err_dma_put:
dma_buf_end_cpu_access(dmabuf, DMA_BIDIRECTIONAL);
dma_buf_put(dmabuf);
return ret;
}
static int hgsl_context_destroy(struct file *filep, unsigned long arg,
bool force_cleanup)
{
struct hgsl_priv *priv = filep->private_data;
struct qcom_hgsl *hgsl = priv->dev;
uint32_t context_id;
struct hgsl_context *ctxt;
if (!is_global_db(hgsl->tcsr_idx)) {
dev_err(hgsl->dev, "Global doorbell not supported for this process\n");
return -ENODEV;
}
if (force_cleanup == false)
copy_from_user(&context_id, USRPTR(arg),
sizeof(context_id));
else
context_id = *(uint32_t *)arg;
if (context_id >= HGSL_CONTEXT_NUM) {
dev_err(hgsl->dev, "Invalid context id %d\n", context_id);
return -EINVAL;
}
write_lock(&hgsl->ctxt_lock);
if (hgsl->contexts[context_id] == NULL) {
write_unlock(&hgsl->ctxt_lock);
dev_err(hgsl->dev,
"context id %d is not created\n",
context_id);
return -EINVAL;
}
ctxt = hgsl->contexts[context_id];
hgsl->contexts[context_id] = NULL;
/* unblock all waiting threads on this context */
ctxt->in_destroy = true;
wake_up_all(&ctxt->wait_q);
ctxt->dbq_assigned = false;
write_unlock(&hgsl->ctxt_lock);
kref_put(&ctxt->kref, _destroy_context);
return 0;
}
static int hgsl_wait_timestamp(struct file *filep, unsigned long arg)
{
struct hgsl_priv *priv = filep->private_data;
struct qcom_hgsl *hgsl = priv->dev;
struct hgsl_wait_ts_info param;
struct hgsl_active_wait *wait;
struct hgsl_context *ctxt;
unsigned int timestamp;
int ret;
if (!is_global_db(hgsl->tcsr_idx)) {
dev_err(hgsl->dev, "Global doorbell not supported for this process\n");
return -ENODEV;
}
copy_from_user(&param, USRPTR(arg), sizeof(param));
if (param.context_id >= HGSL_CONTEXT_NUM) {
dev_err(hgsl->dev, "Invalid context id %d\n",
param.context_id);
return -EINVAL;
}
timestamp = param.timestamp;
read_lock(&hgsl->ctxt_lock);
ctxt = hgsl->contexts[param.context_id];
if (ctxt == NULL) {
read_unlock(&hgsl->ctxt_lock);
dev_err(hgsl->dev,
"context id %d is not created\n",
param.context_id);
return -EINVAL;
}
read_unlock(&hgsl->ctxt_lock);
if (_timestamp_retired(ctxt, timestamp))
return 0;
kref_get(&ctxt->kref);
wait = kzalloc(sizeof(wait), GFP_KERNEL);
if (!wait)
return -ENOMEM;
wait->ctxt = ctxt;
wait->timestamp = timestamp;
spin_lock(&hgsl->active_wait_lock);
list_add_tail(&wait->head, &hgsl->active_wait_list);
spin_unlock(&hgsl->active_wait_lock);
ret = wait_event_interruptible_timeout(ctxt->wait_q,
_timestamp_retired(ctxt, timestamp) ||
ctxt->in_destroy,
msecs_to_jiffies(param.timeout));
if (ret == 0)
ret = -ETIMEDOUT;
else if (ret == -ERESTARTSYS)
/* Let user handle this */
ret = -EINTR;
else
ret = 0;
spin_lock(&hgsl->active_wait_lock);
list_del(&wait->head);
spin_unlock(&hgsl->active_wait_lock);
kfree(wait);
kref_put(&ctxt->kref, _destroy_context);
return ret;
}
static int hgsl_dbq_release(struct file *filep, unsigned long arg,
bool force_cleanup)
{
struct hgsl_priv *priv = filep->private_data;
struct qcom_hgsl *hgsl = priv->dev;
struct doorbell_queue *dbq;
struct hgsl_dbq_release_info rel_info;
if (force_cleanup == false)
copy_from_user(&rel_info, USRPTR(arg),
sizeof(rel_info));
else
rel_info = *(struct hgsl_dbq_release_info *)arg;
dbq = &hgsl->dbq[priv->dbq_idx];
dbq_update_ctxt_info(dbq->vbase,
rel_info.ctxt_id,
HGSL_DBQ_METADATA_CTXT_ID_IN_DWORD,
rel_info.ctxt_id);
dbq_update_ctxt_info(dbq->vbase,
rel_info.ctxt_id,
HGSL_DBQ_METADATA_CTXT_DESTROY_IN_DWORD,
1);
rel_info.ref_count = (dbq->state == DB_STATE_Q_INIT_DONE) ? 1 : 0;
if (force_cleanup == false)
copy_to_user(USRPTR(arg), &rel_info, sizeof(rel_info));
return priv->dbq_idx;
}
static int hgsl_open(struct inode *inodep, struct file *filep)
{
struct hgsl_priv *priv = kzalloc(sizeof(*priv), GFP_KERNEL);
struct qcom_hgsl *hgsl = container_of(inodep->i_cdev,
struct qcom_hgsl, cdev);
if (!priv)
return -ENOMEM;
priv->dev = hgsl;
filep->private_data = priv;
return 0;
}
static int hgsl_release(struct inode *inodep, struct file *filep)
{
struct hgsl_priv *priv = filep->private_data;
struct qcom_hgsl *hgsl = priv->dev;
struct hgsl_dbq_release_info rel_info;
int i;
for (i = 0; i < HGSL_CONTEXT_NUM; i++) {
if ((hgsl->contexts != NULL) &&
(hgsl->contexts[i] != NULL) &&
(priv->pid == hgsl->contexts[i]->pid)) {
rel_info.ctxt_id = hgsl->contexts[i]->context_id;
if (hgsl->contexts[i]->dbq_assigned == true)
hgsl_dbq_release(filep,
(unsigned long)&rel_info,
true);
hgsl_context_destroy(filep,
(unsigned long)&rel_info.ctxt_id,
true);
}
}
kfree(priv);
return 0;
}
static ssize_t hgsl_read(struct file *filep, char __user *buf, size_t count,
loff_t *pos)
{
struct hgsl_priv *priv = filep->private_data;
struct qcom_hgsl *hgsl = priv->dev;
uint32_t version = 0;
uint32_t release = 0;
char buff[100];
hgsl_reg_read(&hgsl->reg_ver, 0, &version);
hgsl_reg_read(&hgsl->reg_ver, 4, &release);
snprintf(buff, 100, "gpu HW Version:%x HW Release:%x\n",
version, release);
return simple_read_from_buffer(buf, count, pos,
buff, strlen(buff) + 1);
}
static long hgsl_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
int ret;
switch (cmd) {
case HGSL_IOCTL_ISSUE_CMDS:
ret = hgsl_cmdstream_db_issueib(filep, arg);
break;
case HGSL_IOCTL_DBQ_GETSTATE:
ret = hgsl_dbq_get_state(filep, arg);
break;
case HGSL_IOCTL_DBQ_ASSIGN:
ret = hgsl_dbq_assign(filep, arg);
break;
case HGSL_IOCTL_DBQ_INIT:
ret = hgsl_dbq_init(filep, arg);
break;
case HGSL_IOCTL_DBQ_RELEASE:
ret = hgsl_dbq_release(filep, arg, false);
break;
case HGSL_IOCTL_CTXT_CREATE:
ret = hgsl_context_create(filep, arg);
break;
case HGSL_IOCTL_CTXT_DESTROY:
ret = hgsl_context_destroy(filep, arg, false);
break;
case HGSL_IOCTL_WAIT_TIMESTAMP:
ret = hgsl_wait_timestamp(filep, arg);
break;
default:
ret = -ENOIOCTLCMD;
}
return ret;
}
static long hgsl_compat_ioctl(struct file *filep, unsigned int cmd,
unsigned long arg)
{
return hgsl_ioctl(filep, cmd, arg);
}
static const struct file_operations hgsl_fops = {
.owner = THIS_MODULE,
.open = hgsl_open,
.release = hgsl_release,
.read = hgsl_read,
.unlocked_ioctl = hgsl_ioctl,
.compat_ioctl = hgsl_compat_ioctl
};
static int qcom_hgsl_register(struct platform_device *pdev,
struct qcom_hgsl *hgsl_dev)
{
int ret;
ret = alloc_chrdev_region(&hgsl_dev->device_no, 0,
HGSL_DEV_NUM,
HGSL_DEVICE_NAME);
if (ret < 0) {
dev_err(&pdev->dev, "alloc_chrdev_region failed %d\n", ret);
return ret;
}
hgsl_dev->driver_class = class_create(THIS_MODULE, HGSL_DEVICE_NAME);
if (IS_ERR(hgsl_dev->driver_class)) {
ret = -ENOMEM;
dev_err(&pdev->dev, "class_create failed %d\n", ret);
goto exit_unreg_chrdev_region;
}
hgsl_dev->class_dev = device_create(hgsl_dev->driver_class,
NULL,
hgsl_dev->device_no,
hgsl_dev, HGSL_DEVICE_NAME);
if (IS_ERR(hgsl_dev->class_dev)) {
dev_err(&pdev->dev, "class_device_create failed %d\n", ret);
ret = -ENOMEM;
goto exit_destroy_class;
}
cdev_init(&hgsl_dev->cdev, &hgsl_fops);
hgsl_dev->cdev.owner = THIS_MODULE;
ret = cdev_add(&hgsl_dev->cdev,
MKDEV(MAJOR(hgsl_dev->device_no), 0),
1);
if (ret < 0) {
dev_err(&pdev->dev, "cdev_add failed %d\n", ret);
goto exit_destroy_device;
}
return 0;
exit_destroy_device:
device_destroy(hgsl_dev->driver_class, hgsl_dev->device_no);
exit_destroy_class:
class_destroy(hgsl_dev->driver_class);
exit_unreg_chrdev_region:
unregister_chrdev_region(hgsl_dev->device_no, 1);
return ret;
}
static void qcom_hgsl_deregister(struct platform_device *pdev)
{
struct qcom_hgsl *hgsl_dev = platform_get_drvdata(pdev);
cdev_del(&hgsl_dev->cdev);
device_destroy(hgsl_dev->driver_class, hgsl_dev->device_no);
class_destroy(hgsl_dev->driver_class);
unregister_chrdev_region(hgsl_dev->device_no, HGSL_DEV_NUM);
}
static int hgsl_reg_map(struct platform_device *pdev,
char *res_name, struct reg *reg)
{
struct resource *res;
int ret = 0;
if ((pdev == NULL) || (res_name == NULL) || (reg == NULL)) {
ret = -EINVAL;
goto exit;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
res_name);
if (res == NULL) {
dev_err(&pdev->dev, "get resource :%s failed\n",
res_name);
ret = -EINVAL;
goto exit;
}
if (res->start == 0 || resource_size(res) == 0) {
dev_err(&pdev->dev, "Register region %s is invalid\n",
res_name);
ret = -EINVAL;
goto exit;
}
reg->paddr = res->start;
reg->size = resource_size(res);
if (devm_request_mem_region(&pdev->dev,
reg->paddr, reg->size,
res_name) == NULL) {
dev_err(&pdev->dev, "request_mem_region for %s failed\n",
res_name);
ret = -ENODEV;
goto exit;
}
reg->vaddr = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (reg->vaddr == NULL) {
dev_err(&pdev->dev, "Unable to remap %s registers\n",
res_name);
ret = -ENODEV;
goto exit;
}
exit:
return ret;
}
static int qcom_hgsl_probe(struct platform_device *pdev)
{
struct qcom_hgsl *hgsl_dev;
int ret;
int i;
hgsl_dev = devm_kzalloc(&pdev->dev, sizeof(*hgsl_dev), GFP_KERNEL);
if (!hgsl_dev)
return -ENOMEM;
hgsl_dev->dev = &pdev->dev;
mutex_init(&hgsl_dev->lock);
ret = qcom_hgsl_register(pdev, hgsl_dev);
if (ret < 0) {
dev_err(&pdev->dev, "qcom_hgsl_register failed, ret %d\n",
ret);
return ret;
}
ret = hgsl_reg_map(pdev, IORESOURCE_HWINF, &hgsl_dev->reg_ver);
if (ret < 0) {
dev_err(&pdev->dev, "Unable to map resource:%s\n",
IORESOURCE_HWINF);
goto exit_dereg;
}
for (i = 0; i < MAX_DB_QUEUE; i++) {
mutex_init(&hgsl_dev->dbq[i].lock);
hgsl_dev->dbq[i].state = DB_STATE_Q_UNINIT;
}
atomic_set(&hgsl_dev->seq_num, 0);
platform_set_drvdata(pdev, hgsl_dev);
return 0;
exit_dereg:
qcom_hgsl_deregister(pdev);
return ret;
}
static int qcom_hgsl_remove(struct platform_device *pdev)
{
struct qcom_hgsl *hgsl = platform_get_drvdata(pdev);
struct hgsl_tcsr *tcsr_sender, *tcsr_receiver;
int i;
for (i = 0; i < HGSL_TCSR_NUM; i++) {
tcsr_sender = hgsl->tcsr[i][HGSL_TCSR_ROLE_SENDER];
tcsr_receiver = hgsl->tcsr[i][HGSL_TCSR_ROLE_RECEIVER];
if (tcsr_sender) {
hgsl_tcsr_disable(tcsr_sender);
hgsl_tcsr_free(tcsr_sender);
}
if (tcsr_receiver) {
hgsl_tcsr_disable(tcsr_receiver);
hgsl_tcsr_free(tcsr_receiver);
flush_workqueue(hgsl->wq);
destroy_workqueue(hgsl->wq);
kfree(hgsl->contexts);
hgsl->contexts = NULL;
}
}
memset(hgsl->tcsr, 0, sizeof(hgsl->tcsr));
for (i = 0; i < MAX_DB_QUEUE; i++)
if (hgsl->dbq[i].state == DB_STATE_Q_INIT_DONE)
hgsl_reset_dbq(&hgsl->dbq[i]);
qcom_hgsl_deregister(pdev);
return 0;
}
static const struct of_device_id qcom_hgsl_of_match[] = {
{ .compatible = "qcom,hgsl" },
{}
};
MODULE_DEVICE_TABLE(of, qcom_hgsl_of_match);
static struct platform_driver qcom_hgsl_driver = {
.probe = qcom_hgsl_probe,
.remove = qcom_hgsl_remove,
.driver = {
.name = "qcom-hgsl",
.of_match_table = qcom_hgsl_of_match,
},
};
module_platform_driver(qcom_hgsl_driver);
MODULE_DESCRIPTION("QTI Hypervisor Graphics system driver");
MODULE_LICENSE("GPL v2");