enum g2d_flag_bits { /* * If set, suspends the runqueue worker after the currently * processed node is finished.
*/
G2D_BIT_SUSPEND_RUNQUEUE, /* * If set, indicates that the engine is currently busy.
*/
G2D_BIT_ENGINE_BUSY,
};
/* cmdlist data structure */ struct g2d_cmdlist {
u32 head; unsignedlong data[G2D_CMDLIST_DATA_NUM];
u32 last; /* last data offset */
};
/* * A structure of buffer description * * @format: color format * @stride: buffer stride/pitch in bytes * @left_x: the x coordinates of left top corner * @top_y: the y coordinates of left top corner * @right_x: the x coordinates of right bottom corner * @bottom_y: the y coordinates of right bottom corner *
*/ struct g2d_buf_desc { unsignedint format; unsignedint stride; unsignedint left_x; unsignedint top_y; unsignedint right_x; unsignedint bottom_y;
};
/* * A structure of buffer information * * @map_nr: manages the number of mapped buffers * @reg_types: stores regitster type in the order of requested command * @handles: stores buffer handle in its reg_type position * @types: stores buffer type in its reg_type position * @descs: stores buffer description in its reg_type position *
*/ struct g2d_buf_info { unsignedint map_nr; enum g2d_reg_type reg_types[MAX_REG_TYPE_NR]; void *obj[MAX_REG_TYPE_NR]; unsignedint types[MAX_REG_TYPE_NR]; struct g2d_buf_desc descs[MAX_REG_TYPE_NR];
};
mutex_lock(&g2d->cmdlist_mutex); if (list_empty(&g2d->free_cmdlist)) {
dev_err(dev, "there is no free cmdlist\n");
mutex_unlock(&g2d->cmdlist_mutex); return NULL;
}
if (list_empty(&file_priv->inuse_cmdlist)) goto add_to_list;
/* this links to base address of new cmdlist */
lnode = list_entry(file_priv->inuse_cmdlist.prev, struct g2d_cmdlist_node, list);
lnode->cmdlist->data[lnode->cmdlist->last] = node->dma_addr;
if (!size) {
DRM_DEV_ERROR(g2d->dev, "invalid userptr size.\n"); return ERR_PTR(-EINVAL);
}
/* check if userptr already exists in userptr_list. */
list_for_each_entry(g2d_userptr, &file_priv->userptr_list, list) { if (g2d_userptr->userptr == userptr) { /* * also check size because there could be same address * and different size.
*/ if (g2d_userptr->size == size) {
refcount_inc(&g2d_userptr->refcount);
*obj = g2d_userptr;
return &g2d_userptr->dma_addr;
}
/* * at this moment, maybe g2d dma is accessing this * g2d_userptr memory region so just remove this * g2d_userptr object from userptr_list not to be * referred again and also except it the userptr * pool to be released after the dma access completion.
*/
g2d_userptr->out_of_list = true;
g2d_userptr->in_pool = false;
list_del_init(&g2d_userptr->list);
break;
}
}
g2d_userptr = kzalloc(sizeof(*g2d_userptr), GFP_KERNEL); if (!g2d_userptr) return ERR_PTR(-ENOMEM);
ret = pin_user_pages_fast(start, npages,
FOLL_WRITE | FOLL_LONGTERM,
g2d_userptr->pages); if (ret != npages) {
DRM_DEV_ERROR(g2d->dev, "failed to get user pages from userptr.\n"); if (ret < 0) goto err_destroy_pages;
npages = ret;
ret = -EFAULT; goto err_unpin_pages;
}
g2d_userptr->npages = npages;
sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); if (!sgt) {
ret = -ENOMEM; goto err_unpin_pages;
}
ret = sg_alloc_table_from_pages(sgt,
g2d_userptr->pages,
npages, offset, size, GFP_KERNEL); if (ret < 0) {
DRM_DEV_ERROR(g2d->dev, "failed to get sgt from pages.\n"); goto err_free_sgt;
}
g2d_userptr->sgt = sgt;
ret = dma_map_sgtable(to_dma_dev(g2d->drm_dev), sgt,
DMA_BIDIRECTIONAL, 0); if (ret) {
DRM_DEV_ERROR(g2d->dev, "failed to map sgt with dma region.\n"); goto err_sg_free_table;
}
switch (reg_offset) { case G2D_SRC_BASE_ADDR: case G2D_SRC_STRIDE: case G2D_SRC_COLOR_MODE: case G2D_SRC_LEFT_TOP: case G2D_SRC_RIGHT_BOTTOM:
reg_type = REG_TYPE_SRC; break; case G2D_SRC_PLANE2_BASE_ADDR:
reg_type = REG_TYPE_SRC_PLANE2; break; case G2D_DST_BASE_ADDR: case G2D_DST_STRIDE: case G2D_DST_COLOR_MODE: case G2D_DST_LEFT_TOP: case G2D_DST_RIGHT_BOTTOM:
reg_type = REG_TYPE_DST; break; case G2D_DST_PLANE2_BASE_ADDR:
reg_type = REG_TYPE_DST_PLANE2; break; case G2D_PAT_BASE_ADDR:
reg_type = REG_TYPE_PAT; break; case G2D_MSK_BASE_ADDR:
reg_type = REG_TYPE_MSK; break; default:
reg_type = REG_TYPE_NONE;
DRM_DEV_ERROR(g2d->dev, "Unknown register offset![%d]\n",
reg_offset); break;
}
switch (format) { case G2D_FMT_XRGB8888: case G2D_FMT_ARGB8888:
bpp = 4; break; case G2D_FMT_RGB565: case G2D_FMT_XRGB1555: case G2D_FMT_ARGB1555: case G2D_FMT_XRGB4444: case G2D_FMT_ARGB4444:
bpp = 2; break; case G2D_FMT_PACKED_RGB888:
bpp = 3; break; default:
bpp = 1; break;
}
/* * check source and destination buffers only. * so the others are always valid.
*/ if (reg_type != REG_TYPE_SRC && reg_type != REG_TYPE_DST) returntrue;
/* This check also makes sure that right_x > left_x. */
width = (int)buf_desc->right_x - (int)buf_desc->left_x; if (width < G2D_LEN_MIN || width > G2D_LEN_MAX) {
DRM_DEV_ERROR(g2d->dev, "width[%d] is out of range!\n", width); returnfalse;
}
/* This check also makes sure that bottom_y > top_y. */
height = (int)buf_desc->bottom_y - (int)buf_desc->top_y; if (height < G2D_LEN_MIN || height > G2D_LEN_MAX) {
DRM_DEV_ERROR(g2d->dev, "height[%d] is out of range!\n", height); returnfalse;
}
bpp = g2d_get_buf_bpp(buf_desc->format);
/* Compute the position of the last byte that the engine accesses. */
last_pos = ((unsignedlong)buf_desc->bottom_y - 1) *
(unsignedlong)buf_desc->stride +
(unsignedlong)buf_desc->right_x * bpp - 1;
/* * Since right_x > left_x and bottom_y > top_y we already know * that the first_pos < last_pos (first_pos being the position * of the first byte the engine accesses), it just remains to * check if last_pos is smaller then the buffer size.
*/
if (last_pos >= size) {
DRM_DEV_ERROR(g2d->dev, "last engine access position [%lu] " "is out of range [%lu]!\n", last_pos, size); returnfalse;
}
returntrue;
}
staticint g2d_map_cmdlist_gem(struct g2d_data *g2d, struct g2d_cmdlist_node *node, struct drm_device *drm_dev, struct drm_file *file)
{ struct g2d_cmdlist *cmdlist = node->cmdlist; struct g2d_buf_info *buf_info = &node->buf_info; int offset; int ret; int i;
for (i = 0; i < buf_info->map_nr; i++) { struct g2d_buf_desc *buf_desc; enum g2d_reg_type reg_type; int reg_pos; unsignedlong handle;
dma_addr_t *addr;
mutex_lock(&g2d->cmdlist_mutex); /* * commands in run_cmdlist have been completed so unmap all gem * objects in each command node so that they are unreferenced.
*/
list_for_each_entry(node, &runqueue_node->run_cmdlist, list)
g2d_unmap_cmdlist_gem(g2d, node, runqueue_node->filp);
list_splice_tail_init(&runqueue_node->run_cmdlist, &g2d->free_cmdlist);
mutex_unlock(&g2d->cmdlist_mutex);
/** * g2d_remove_runqueue_nodes - remove items from the list of runqueue nodes * @g2d: G2D state object * @file: if not zero, only remove items with this DRM file * * Has to be called under runqueue lock.
*/ staticvoid g2d_remove_runqueue_nodes(struct g2d_data *g2d, struct drm_file *file)
{ struct g2d_runqueue_node *node, *n;
if (list_empty(&g2d->runqueue)) return;
list_for_each_entry_safe(node, n, &g2d->runqueue, list) { if (file && node->filp != file) continue;
/* * The engine is busy and the completion of the current node is going * to poke the runqueue worker, so nothing to do here.
*/ if (test_bit(G2D_BIT_ENGINE_BUSY, &g2d->flags)) return;
if (pending & G2D_INTP_ACMD_FIN) {
clear_bit(G2D_BIT_ENGINE_BUSY, &g2d->flags);
queue_work(g2d->g2d_workq, &g2d->runqueue_work);
}
return IRQ_HANDLED;
}
/** * g2d_wait_finish - wait for the G2D engine to finish the current runqueue node * @g2d: G2D state object * @file: if not zero, only wait if the current runqueue node belongs * to the DRM file * * Should the engine not become idle after a 100ms timeout, a hardware * reset is issued.
*/ staticvoid g2d_wait_finish(struct g2d_data *g2d, struct drm_file *file)
{ struct device *dev = g2d->dev;
/* * After the hardware reset of the engine we are going to loose * the IRQ which triggers the PM runtime put(). * So do this manually here.
*/
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
complete(&runqueue_node->complete); if (runqueue_node->async)
g2d_free_runqueue_node(g2d, runqueue_node);
out:
mutex_unlock(&g2d->runqueue_mutex);
}
staticint g2d_check_reg_offset(struct g2d_data *g2d, struct g2d_cmdlist_node *node, int nr, bool for_addr)
{ struct g2d_cmdlist *cmdlist = node->cmdlist; int reg_offset; int index; int i;
for (i = 0; i < nr; i++) { struct g2d_buf_info *buf_info = &node->buf_info; struct g2d_buf_desc *buf_desc; enum g2d_reg_type reg_type; unsignedlong value;
switch (reg_offset) { case G2D_SRC_BASE_ADDR: case G2D_SRC_PLANE2_BASE_ADDR: case G2D_DST_BASE_ADDR: case G2D_DST_PLANE2_BASE_ADDR: case G2D_PAT_BASE_ADDR: case G2D_MSK_BASE_ADDR: if (!for_addr) goto err;
reg_type = g2d_get_reg_type(g2d, reg_offset);
/* check userptr buffer type. */ if ((cmdlist->data[index] & ~0x7fffffff) >> 31) {
buf_info->types[reg_type] = BUF_TYPE_USERPTR;
cmdlist->data[index] &= ~G2D_BUF_USERPTR;
} else
buf_info->types[reg_type] = BUF_TYPE_GEM; break; case G2D_SRC_STRIDE: case G2D_DST_STRIDE: if (for_addr) goto err;
reg_type = g2d_get_reg_type(g2d, reg_offset);
buf_desc = &buf_info->descs[reg_type];
buf_desc->stride = cmdlist->data[index + 1]; break; case G2D_SRC_COLOR_MODE: case G2D_DST_COLOR_MODE: if (for_addr) goto err;
reg_type = g2d_get_reg_type(g2d, reg_offset);
buf_desc = &buf_info->descs[reg_type];
value = cmdlist->data[index + 1];
buf_desc->format = value & 0xf; break; case G2D_SRC_LEFT_TOP: case G2D_DST_LEFT_TOP: if (for_addr) goto err;
reg_type = g2d_get_reg_type(g2d, reg_offset);
buf_desc = &buf_info->descs[reg_type];
value = cmdlist->data[index + 1];
buf_desc->left_x = value & 0x1fff;
buf_desc->top_y = (value & 0x1fff0000) >> 16; break; case G2D_SRC_RIGHT_BOTTOM: case G2D_DST_RIGHT_BOTTOM: if (for_addr) goto err;
reg_type = g2d_get_reg_type(g2d, reg_offset);
buf_desc = &buf_info->descs[reg_type];
value = cmdlist->data[index + 1];
node = g2d_get_cmdlist(g2d); if (!node) return -ENOMEM;
/* * To avoid an integer overflow for the later size computations, we * enforce a maximum number of submitted commands here. This limit is * sufficient for all conceivable usage cases of the G2D.
*/ if (req->cmd_nr > G2D_CMDLIST_DATA_NUM ||
req->cmd_buf_nr > G2D_CMDLIST_DATA_NUM) {
dev_err(g2d->dev, "number of submitted G2D commands exceeds limit\n"); return -EINVAL;
}
node->event = NULL;
if (req->event_type != G2D_EVENT_NOT) {
e = kzalloc(sizeof(*node->event), GFP_KERNEL); if (!e) {
ret = -ENOMEM; goto err;
}
ret = drm_event_reserve_init(drm_dev, file, &e->base, &e->event.base); if (ret) {
kfree(e); goto err;
}
node->event = e;
}
cmdlist = node->cmdlist;
cmdlist->last = 0;
/* * If don't clear SFR registers, the cmdlist is affected by register * values of previous cmdlist. G2D hw executes SFR clear command and * a next command at the same time then the next command is ignored and * is executed rightly from next next command, so needs a dummy command * to next command of SFR clear command.
*/
cmdlist->data[cmdlist->last++] = G2D_SOFT_RESET;
cmdlist->data[cmdlist->last++] = G2D_SFRCLEAR;
cmdlist->data[cmdlist->last++] = G2D_SRC_BASE_ADDR;
cmdlist->data[cmdlist->last++] = 0;
/* * 'LIST_HOLD' command should be set to the DMA_HOLD_CMD_REG * and GCF bit should be set to INTEN register if user wants * G2D interrupt event once current command list execution is * finished. * Otherwise only ACF bit should be set to INTEN register so * that one interrupt is occurred after all command lists * have been completed.
*/ if (node->event) {
cmdlist->data[cmdlist->last++] = G2D_INTEN;
cmdlist->data[cmdlist->last++] = G2D_INTEN_ACF | G2D_INTEN_GCF;
cmdlist->data[cmdlist->last++] = G2D_DMA_HOLD_CMD;
cmdlist->data[cmdlist->last++] = G2D_LIST_HOLD;
} else {
cmdlist->data[cmdlist->last++] = G2D_INTEN;
cmdlist->data[cmdlist->last++] = G2D_INTEN_ACF;
}
/* * Check the size of cmdlist. The 2 that is added last comes from * the implicit G2D_BITBLT_START that is appended once we have * checked all the submitted commands.
*/
size = cmdlist->last + req->cmd_nr * 2 + req->cmd_buf_nr * 2 + 2; if (size > G2D_CMDLIST_DATA_NUM) {
dev_err(g2d->dev, "cmdlist size is too big\n");
ret = -EINVAL; goto err_free_event;
}
/* Remove the runqueue nodes that belong to us. */
mutex_lock(&g2d->runqueue_mutex);
g2d_remove_runqueue_nodes(g2d, file);
mutex_unlock(&g2d->runqueue_mutex);
/* * Wait for the runqueue worker to finish its current node. * After this the engine should no longer be accessing any * memory belonging to us.
*/
g2d_wait_finish(g2d, file);
/* * Even after the engine is idle, there might still be stale cmdlists * (i.e. cmdlisst which we submitted but never executed) around, with * their corresponding GEM/userptr buffers. * Properly unmap these buffers here.
*/
mutex_lock(&g2d->cmdlist_mutex);
list_for_each_entry_safe(node, n, &file_priv->inuse_cmdlist, list) {
g2d_unmap_cmdlist_gem(g2d, node, file);
list_move_tail(&node->list, &g2d->free_cmdlist);
}
mutex_unlock(&g2d->cmdlist_mutex);
/* release all g2d_userptr in pool. */
g2d_userptr_free_all(g2d, file);
}
g2d->gate_clk = devm_clk_get(dev, "fimg2d"); if (IS_ERR(g2d->gate_clk)) {
dev_err(dev, "failed to get gate clock\n");
ret = PTR_ERR(g2d->gate_clk); goto err_destroy_workqueue;
}
/* * Suspend the runqueue worker operation and wait until the G2D * engine is idle.
*/
set_bit(G2D_BIT_SUSPEND_RUNQUEUE, &g2d->flags);
g2d_wait_finish(g2d, NULL);
flush_work(&g2d->runqueue_work);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.