| /* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include "cam_req_mgr_workq.h" |
| |
| |
| struct crm_workq_task *cam_req_mgr_workq_get_task( |
| struct cam_req_mgr_core_workq *workq) |
| { |
| struct crm_workq_task *task = NULL; |
| |
| if (!workq) |
| return NULL; |
| |
| spin_lock_bh(&workq->lock_bh); |
| if (list_empty(&workq->task.empty_head)) |
| goto end; |
| |
| task = list_first_entry(&workq->task.empty_head, |
| struct crm_workq_task, entry); |
| if (task) { |
| atomic_sub(1, &workq->task.free_cnt); |
| list_del_init(&task->entry); |
| } |
| |
| end: |
| spin_unlock_bh(&workq->lock_bh); |
| return task; |
| } |
| |
| static void cam_req_mgr_workq_put_task(struct crm_workq_task *task) |
| { |
| struct cam_req_mgr_core_workq *workq = |
| (struct cam_req_mgr_core_workq *)task->parent; |
| |
| spin_lock_bh(&workq->lock_bh); |
| list_del_init(&task->entry); |
| task->cancel = 0; |
| task->process_cb = NULL; |
| task->priv = NULL; |
| list_add_tail(&task->entry, |
| &workq->task.empty_head); |
| atomic_add(1, &workq->task.free_cnt); |
| spin_unlock_bh(&workq->lock_bh); |
| } |
| |
| /** |
| * cam_req_mgr_process_task() - Process the enqueued task |
| * @task: pointer to task workq thread shall process |
| */ |
| static int cam_req_mgr_process_task(struct crm_workq_task *task) |
| { |
| struct cam_req_mgr_core_workq *workq = NULL; |
| |
| if (!task) |
| return -EINVAL; |
| |
| workq = (struct cam_req_mgr_core_workq *)task->parent; |
| if (task->process_cb) |
| task->process_cb(task->priv, task->payload); |
| else |
| CRM_WARN("FATAL:no task handler registered for workq"); |
| cam_req_mgr_workq_put_task(task); |
| |
| return 0; |
| } |
| |
| /** |
| * cam_req_mgr_process_workq() - main loop handling |
| * @w: workqueue task pointer |
| */ |
| static void cam_req_mgr_process_workq(struct work_struct *w) |
| { |
| struct cam_req_mgr_core_workq *workq = NULL; |
| struct crm_workq_task *task, *task_save; |
| int32_t i = CRM_TASK_PRIORITY_0; |
| if (!w) { |
| CRM_ERR("NULL task pointer can not schedule"); |
| return; |
| } |
| workq = (struct cam_req_mgr_core_workq *) |
| container_of(w, struct cam_req_mgr_core_workq, work); |
| |
| while (i < CRM_TASK_PRIORITY_MAX) { |
| if (!list_empty(&workq->task.process_head[i])) { |
| list_for_each_entry_safe(task, task_save, |
| &workq->task.process_head[i], entry) { |
| atomic_sub(1, &workq->task.pending_cnt); |
| cam_req_mgr_process_task(task); |
| } |
| CRM_DBG("processed task %pK free_cnt %d", |
| task, atomic_read(&workq->task.free_cnt)); |
| } |
| i++; |
| } |
| } |
| |
| void crm_workq_clear_q(struct cam_req_mgr_core_workq *workq) |
| { |
| int32_t i = CRM_TASK_PRIORITY_0; |
| struct crm_workq_task *task, *task_save; |
| |
| CRM_DBG("pending_cnt %d", |
| atomic_read(&workq->task.pending_cnt)); |
| |
| while (i < CRM_TASK_PRIORITY_MAX) { |
| if (!list_empty(&workq->task.process_head[i])) { |
| list_for_each_entry_safe(task, task_save, |
| &workq->task.process_head[i], entry) { |
| cam_req_mgr_workq_put_task(task); |
| CRM_WARN("flush task %pK, %d, cnt %d", |
| task, i, atomic_read( |
| &workq->task.free_cnt)); |
| } |
| } |
| i++; |
| } |
| } |
| |
| int cam_req_mgr_workq_enqueue_task(struct crm_workq_task *task, |
| void *priv, int32_t prio) |
| { |
| int rc = 0; |
| struct cam_req_mgr_core_workq *workq = NULL; |
| |
| if (!task) { |
| CRM_WARN("NULL task pointer can not schedule"); |
| rc = -EINVAL; |
| goto end; |
| } |
| workq = (struct cam_req_mgr_core_workq *)task->parent; |
| if (!workq) { |
| CRM_DBG("NULL workq pointer suspect mem corruption"); |
| rc = -EINVAL; |
| goto end; |
| } |
| if (!workq->job) { |
| rc = -EINVAL; |
| goto end; |
| } |
| |
| spin_lock_bh(&workq->lock_bh); |
| if (task->cancel == 1) { |
| cam_req_mgr_workq_put_task(task); |
| CRM_WARN("task aborted and queued back to pool"); |
| rc = 0; |
| spin_unlock_bh(&workq->lock_bh); |
| goto end; |
| } |
| task->priv = priv; |
| task->priority = |
| (prio < CRM_TASK_PRIORITY_MAX && prio >= CRM_TASK_PRIORITY_0) |
| ? prio : CRM_TASK_PRIORITY_0; |
| list_add_tail(&task->entry, |
| &workq->task.process_head[task->priority]); |
| atomic_add(1, &workq->task.pending_cnt); |
| CRM_DBG("enq task %pK pending_cnt %d", |
| task, atomic_read(&workq->task.pending_cnt)); |
| spin_unlock_bh(&workq->lock_bh); |
| |
| queue_work(workq->job, &workq->work); |
| |
| end: |
| return rc; |
| } |
| |
| int cam_req_mgr_workq_create(char *name, int32_t num_tasks, |
| struct cam_req_mgr_core_workq **workq) |
| { |
| int32_t i; |
| struct crm_workq_task *task; |
| struct cam_req_mgr_core_workq *crm_workq = NULL; |
| char buf[128] = "crm_workq-"; |
| |
| if (!*workq) { |
| crm_workq = (struct cam_req_mgr_core_workq *) |
| kzalloc(sizeof(struct cam_req_mgr_core_workq), |
| GFP_KERNEL); |
| if (crm_workq == NULL) |
| return -ENOMEM; |
| |
| strlcat(buf, name, sizeof(buf)); |
| CRM_DBG("create workque crm_workq-%s", name); |
| crm_workq->job = alloc_workqueue(buf, |
| WQ_HIGHPRI | WQ_UNBOUND, 0, NULL); |
| if (!crm_workq->job) { |
| kfree(crm_workq); |
| return -ENOMEM; |
| } |
| |
| /* Workq attributes initialization */ |
| INIT_WORK(&crm_workq->work, cam_req_mgr_process_workq); |
| spin_lock_init(&crm_workq->lock_bh); |
| CRM_DBG("LOCK_DBG workq %s lock %pK", |
| name, &crm_workq->lock_bh); |
| |
| /* Task attributes initialization */ |
| atomic_set(&crm_workq->task.pending_cnt, 0); |
| atomic_set(&crm_workq->task.free_cnt, 0); |
| for (i = CRM_TASK_PRIORITY_0; i < CRM_TASK_PRIORITY_MAX; i++) |
| INIT_LIST_HEAD(&crm_workq->task.process_head[i]); |
| INIT_LIST_HEAD(&crm_workq->task.empty_head); |
| crm_workq->task.num_task = num_tasks; |
| crm_workq->task.pool = (struct crm_workq_task *) |
| kzalloc(sizeof(struct crm_workq_task) * |
| crm_workq->task.num_task, |
| GFP_KERNEL); |
| if (!crm_workq->task.pool) { |
| CRM_WARN("Insufficient memory %lu", |
| sizeof(struct crm_workq_task) * |
| crm_workq->task.num_task); |
| kfree(crm_workq); |
| return -ENOMEM; |
| } |
| |
| for (i = 0; i < crm_workq->task.num_task; i++) { |
| task = &crm_workq->task.pool[i]; |
| task->parent = (void *)crm_workq; |
| /* Put all tasks in free pool */ |
| list_add_tail(&task->entry, |
| &crm_workq->task.process_head[CRM_TASK_PRIORITY_0]); |
| cam_req_mgr_workq_put_task(task); |
| } |
| *workq = crm_workq; |
| CRM_DBG("free tasks %d", |
| atomic_read(&crm_workq->task.free_cnt)); |
| } |
| |
| return 0; |
| } |
| |
| void cam_req_mgr_workq_destroy(struct cam_req_mgr_core_workq **crm_workq) |
| { |
| CRM_DBG("destroy workque %pK", crm_workq); |
| if (*crm_workq) { |
| crm_workq_clear_q(*crm_workq); |
| if ((*crm_workq)->job) { |
| destroy_workqueue((*crm_workq)->job); |
| (*crm_workq)->job = NULL; |
| } |
| kfree(*crm_workq); |
| *crm_workq = NULL; |
| } |
| } |