Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pispbe/rpi 6.6.y/split jobs handling v2 #6324

Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 100 additions & 95 deletions drivers/media/platform/raspberrypi/pisp_be/pisp_be.c
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ struct pispbe_hw_enables {

/* Records a job configuration and memory addresses. */
struct pispbe_job_descriptor {
struct list_head queue;
struct pispbe_buffer *buffers[PISPBE_NUM_NODES];
struct pispbe_node_group *node_group;
dma_addr_t hw_dma_addrs[N_HW_ADDRESSES];
struct pisp_be_tiles_config *config;
struct pispbe_hw_enables hw_enables;
Expand All @@ -235,8 +238,10 @@ struct pispbe_dev {
struct clk *clk;
struct pispbe_node_group node_group[PISPBE_NUM_NODE_GROUPS];
struct pispbe_job queued_job, running_job;
spinlock_t hw_lock; /* protects "hw_busy" flag and streaming_map */
/* protects "hw_busy" flag, streaming_map and job_queue*/
spinlock_t hw_lock;
bool hw_busy; /* non-zero if a job is queued or is being started */
struct list_head job_queue;
int irq;
u32 hw_version;
u8 done, started;
Expand Down Expand Up @@ -388,10 +393,7 @@ static void pispbe_xlate_addrs(struct pispbe_job_descriptor *job,
ret = pispbe_get_planes_addr(addrs, buf[MAIN_INPUT_NODE],
&node_group->node[MAIN_INPUT_NODE]);
if (ret <= 0) {
/*
* This shouldn't happen; pispbe_schedule_internal should insist
* on an input.
*/
/* Shouldn't happen, we have validated an input is available. */
dev_warn(node_group->pispbe->dev, "ISP-BE missing input\n");
hw_en->bayer_enables = 0;
hw_en->rgb_enables = 0;
Expand Down Expand Up @@ -463,42 +465,51 @@ static void pispbe_xlate_addrs(struct pispbe_job_descriptor *job,
* For Output0, Output1, Tdn and Stitch, a buffer only needs to be
* available if the blocks are enabled in the config.
*
* Needs to be called with hw_lock held.
* If all the buffers required to form a job are available, append the
* job descriptor to the job queue to be later queued to the HW.
*
* Returns 0 if a job has been successfully prepared, < 0 otherwise.
*/
static int pispbe_prepare_job(struct pispbe_node_group *node_group,
struct pispbe_job_descriptor *job)
static int pispbe_prepare_job(struct pispbe_dev *pispbe,
struct pispbe_node_group *node_group)
{
struct pispbe_buffer *buf[PISPBE_NUM_NODES] = {};
struct pispbe_dev *pispbe = node_group->pispbe;
struct pispbe_job_descriptor *job;
unsigned int streaming_map;
unsigned int config_index;
struct pispbe_node *node;
unsigned long flags;

lockdep_assert_held(&pispbe->hw_lock);
scoped_guard(spinlock_irqsave, &pispbe->hw_lock) {
static const u32 mask = BIT(CONFIG_NODE) | BIT(MAIN_INPUT_NODE);

memset(job, 0, sizeof(struct pispbe_job_descriptor));
if ((node_group->streaming_map & mask) != mask)
return -ENODEV;

if (((BIT(CONFIG_NODE) | BIT(MAIN_INPUT_NODE)) &
node_group->streaming_map) !=
(BIT(CONFIG_NODE) | BIT(MAIN_INPUT_NODE)))
return -ENODEV;
/*
* Take a copy of streaming_map: nodes activated after this
* point are ignored when preparing this job.
*/
streaming_map = node_group->streaming_map;
}

job = kzalloc(sizeof(*job), GFP_KERNEL);
if (!job)
return -ENOMEM;

node = &node_group->node[CONFIG_NODE];
spin_lock_irqsave(&node->ready_lock, flags);
buf[CONFIG_NODE] = list_first_entry_or_null(&node->ready_queue,
struct pispbe_buffer,
ready_list);
if (buf[CONFIG_NODE]) {

scoped_guard(spinlock_irqsave, &node->ready_lock) {
buf[CONFIG_NODE] = list_first_entry_or_null(&node->ready_queue,
struct pispbe_buffer,
ready_list);
if (!buf[CONFIG_NODE]) {
kfree(job);
return -ENODEV;
}

list_del(&buf[CONFIG_NODE]->ready_list);
pispbe->queued_job.buf[CONFIG_NODE] = buf[CONFIG_NODE];
job->buffers[CONFIG_NODE] = buf[CONFIG_NODE];
}
spin_unlock_irqrestore(&node->ready_lock, flags);

/* Exit early if no config buffer has been queued. */
if (!buf[CONFIG_NODE])
return -ENODEV;

config_index = buf[CONFIG_NODE]->vb.vb2_buf.index;
job->config = &node_group->config[config_index];
Expand All @@ -519,7 +530,7 @@ static int pispbe_prepare_job(struct pispbe_node_group *node_group,
continue;

buf[i] = NULL;
if (!(node_group->streaming_map & BIT(i)))
if (!(streaming_map & BIT(i)))
continue;

if ((!(rgb_en & PISP_BE_RGB_ENABLE_OUTPUT0) &&
Expand All @@ -546,25 +557,29 @@ static int pispbe_prepare_job(struct pispbe_node_group *node_group,
node = &node_group->node[i];

/* Pull a buffer from each V4L2 queue to form the queued job */
spin_lock_irqsave(&node->ready_lock, flags);
spin_lock(&node->ready_lock);
buf[i] = list_first_entry_or_null(&node->ready_queue,
struct pispbe_buffer,
ready_list);
if (buf[i]) {
list_del(&buf[i]->ready_list);
pispbe->queued_job.buf[i] = buf[i];
job->buffers[i] = buf[i];
}
spin_unlock_irqrestore(&node->ready_lock, flags);
spin_unlock(&node->ready_lock);

if (!buf[i] && !ignore_buffers)
goto err_return_buffers;
}

pispbe->queued_job.node_group = node_group;
job->node_group = node_group;

/* Convert buffers to DMA addresses for the hardware */
pispbe_xlate_addrs(job, buf, node_group);

spin_lock(&pispbe->hw_lock);
list_add_tail(&job->queue, &pispbe->job_queue);
spin_unlock(&pispbe->hw_lock);

return 0;

err_return_buffers:
Expand All @@ -575,82 +590,51 @@ static int pispbe_prepare_job(struct pispbe_node_group *node_group,
continue;

/* Return the buffer to the ready_list queue */
spin_lock_irqsave(&n->ready_lock, flags);
spin_lock(&n->ready_lock);
list_add(&buf[i]->ready_list, &n->ready_queue);
spin_unlock_irqrestore(&n->ready_lock, flags);
spin_unlock(&n->ready_lock);
}

memset(&pispbe->queued_job, 0, sizeof(pispbe->queued_job));
kfree(job);

return -ENODEV;
}

static void pispbe_schedule(struct pispbe_dev *pispbe,
struct pispbe_node_group *node_group,
bool clear_hw_busy)
{
struct pispbe_job_descriptor job;
unsigned long flags;

spin_lock_irqsave(&pispbe->hw_lock, flags);
struct pispbe_job_descriptor *job;

if (clear_hw_busy)
pispbe->hw_busy = false;
scoped_guard(spinlock_irqsave, &pispbe->hw_lock) {
if (clear_hw_busy)
pispbe->hw_busy = false;

if (pispbe->hw_busy)
goto unlock_and_return;
if (pispbe->hw_busy)
return;

for (unsigned int i = 0; i < PISPBE_NUM_NODE_GROUPS; i++) {
int ret;
job = list_first_entry_or_null(&pispbe->job_queue,
struct pispbe_job_descriptor,
queue);
if (!job)
return;

/* Schedule jobs only for a specific group. */
if (node_group && &pispbe->node_group[i] != node_group)
continue;
list_del(&job->queue);

/*
* Prepare a job for this group, if the group is not ready
* continue and try with the next one.
*/
ret = pispbe_prepare_job(&pispbe->node_group[i], &job);
if (ret)
continue;
for (unsigned int i = 0; i < PISPBE_NUM_NODES; i++)
pispbe->queued_job.buf[i] = job->buffers[i];
pispbe->queued_job.node_group = job->node_group;

/*
* We can kick the job off without the hw_lock, as this can
* never run again until hw_busy is cleared, which will happen
* only when the following job has been queued and an interrupt
* is rised.
*/
pispbe->hw_busy = true;
spin_unlock_irqrestore(&pispbe->hw_lock, flags);

if (job.config->num_tiles <= 0 ||
job.config->num_tiles > PISP_BACK_END_NUM_TILES ||
!((job.hw_enables.bayer_enables |
job.hw_enables.rgb_enables) &
PISP_BE_BAYER_ENABLE_INPUT)) {
/*
* Bad job. We can't let it proceed as it could lock up
* the hardware, or worse!
*
* For now, just force num_tiles to 0, which causes the
* H/W to do something bizarre but survivable. It
* increments (started,done) counters by more than 1,
* but we seem to survive...
*/
dev_dbg(pispbe->dev, "Bad job: invalid number of tiles: %u\n",
job.config->num_tiles);
job.config->num_tiles = 0;
}

pispbe_queue_job(pispbe, &job);

return;
}

unlock_and_return:
/* No job has been queued, just release the lock and return. */
spin_unlock_irqrestore(&pispbe->hw_lock, flags);
/*
* We can kick the job off without the hw_lock, as this can
* never run again until hw_busy is cleared, which will happen
* only when the following job has been queued and an interrupt
* is rised.
*/
pispbe_queue_job(pispbe, job);
kfree(job);
}

static void pispbe_isr_jobdone(struct pispbe_dev *pispbe,
Expand Down Expand Up @@ -721,7 +705,7 @@ static irqreturn_t pispbe_isr(int irq, void *dev)
}

/* check if there's more to do before going to sleep */
pispbe_schedule(pispbe, NULL, can_queue_another);
pispbe_schedule(pispbe, can_queue_another);

return IRQ_HANDLED;
}
Expand All @@ -741,6 +725,13 @@ static int pisp_be_validate_config(struct pispbe_node_group *node_group,
return -EIO;
}

if (config->num_tiles == 0 ||
config->num_tiles > PISP_BACK_END_NUM_TILES) {
dev_dbg(dev, "%s: Invalid number of tiles: %d\n", __func__,
config->num_tiles);
return -EIO;
}

/* Ensure output config strides and buffer sizes match the V4L2 formats. */
fmt = &node_group->node[TDN_OUTPUT_NODE].format;
if (bayer_enables & PISP_BE_BAYER_ENABLE_TDN_OUTPUT) {
Expand Down Expand Up @@ -907,7 +898,8 @@ static void pispbe_node_buffer_queue(struct vb2_buffer *buf)
* Every time we add a buffer, check if there's now some work for the hw
* to do, but only for this client.
*/
pispbe_schedule(node_group->pispbe, node_group, false);
if (!pispbe_prepare_job(pispbe, node_group))
pispbe_schedule(pispbe, false);
}

static int pispbe_node_start_streaming(struct vb2_queue *q, unsigned int count)
Expand All @@ -934,7 +926,8 @@ static int pispbe_node_start_streaming(struct vb2_queue *q, unsigned int count)
node->node_group->streaming_map);

/* Maybe we're ready to run. */
pispbe_schedule(node_group->pispbe, node_group, false);
if (!pispbe_prepare_job(pispbe, node_group))
pispbe_schedule(pispbe, false);

return 0;

Expand Down Expand Up @@ -973,8 +966,8 @@ static void pispbe_node_stop_streaming(struct vb2_queue *q)

spin_lock_irqsave(&node->ready_lock, flags1);
buf = list_first_entry_or_null(&node->ready_queue,
struct pispbe_buffer,
ready_list);
struct pispbe_buffer,
ready_list);
if (buf) {
list_del(&buf->ready_list);
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
Expand All @@ -987,6 +980,16 @@ static void pispbe_node_stop_streaming(struct vb2_queue *q)

spin_lock_irqsave(&pispbe->hw_lock, flags);
node_group->streaming_map &= ~BIT(node->id);

/* Release all jobs once all nodes have stopped streaming. */
if (node_group->streaming_map == 0) {
struct pispbe_job_descriptor *job, *temp;

list_for_each_entry_safe(job, temp, &pispbe->job_queue, queue) {
list_del(&job->queue);
kfree(job);
}
}
spin_unlock_irqrestore(&pispbe->hw_lock, flags);

pm_runtime_mark_last_busy(pispbe->dev);
Expand Down Expand Up @@ -1746,6 +1749,8 @@ static int pispbe_probe(struct platform_device *pdev)
if (!pispbe)
return -ENOMEM;

INIT_LIST_HEAD(&pispbe->job_queue);

dev_set_drvdata(&pdev->dev, pispbe);
pispbe->dev = &pdev->dev;
platform_set_drvdata(pdev, pispbe);
Expand Down Expand Up @@ -1783,7 +1788,7 @@ static int pispbe_probe(struct platform_device *pdev)
pm_runtime_use_autosuspend(pispbe->dev);
pm_runtime_enable(pispbe->dev);

ret = pispbe_runtime_resume(pispbe->dev);
ret = pm_runtime_resume_and_get(pispbe->dev);
if (ret)
goto pm_runtime_disable_err;

Expand Down