Add ioctl commands to create DLB queues and query their depth, and the corresponding scheduling domain reset code to drain the queues when they are no longer in use.
When a CPU enqueues a queue entry (QE) to DLB, the QE entry is sent to a DLB queue. These queues hold queue entries (QEs) that have not yet been scheduled to a destination port. The queue's depth is the number of QEs residing in a queue. Each queue supports multiple priority levels, and while a directed queue has a 1:1 mapping with a directed port, load-balanced queues can be configured with a set of load-balanced ports that software desires the queue's QEs to be scheduled to. For ease of review, this commit is limited to higher-level code including the ioctl commands, request verification, and debug log messages. All register access/configuration code will be included in a subsequent commit. Signed-off-by: Gage Eads <gage.e...@intel.com> Signed-off-by: Mike Ximing Chen <mike.ximing.c...@intel.com> Reviewed-by: Magnus Karlsson <magnus.karls...@intel.com> Reviewed-by: Dan Williams <dan.j.willi...@intel.com> --- drivers/misc/dlb/dlb_hw_types.h | 27 ++ drivers/misc/dlb/dlb_ioctl.c | 63 ++++ drivers/misc/dlb/dlb_main.c | 2 + drivers/misc/dlb/dlb_main.h | 17 + drivers/misc/dlb/dlb_pf_ops.c | 36 ++ drivers/misc/dlb/dlb_resource.c | 589 ++++++++++++++++++++++++++++++++ drivers/misc/dlb/dlb_resource.h | 20 ++ include/uapi/linux/dlb.h | 151 ++++++++ 8 files changed, 905 insertions(+) diff --git a/drivers/misc/dlb/dlb_hw_types.h b/drivers/misc/dlb/dlb_hw_types.h index c486ea344292..d382c414e2b0 100644 --- a/drivers/misc/dlb/dlb_hw_types.h +++ b/drivers/misc/dlb/dlb_hw_types.h @@ -150,6 +150,33 @@ struct dlb_sn_group { u32 id; }; +static inline bool dlb_sn_group_full(struct dlb_sn_group *group) +{ + const u32 mask[] = { + 0x0000ffff, /* 64 SNs per queue */ + 0x000000ff, /* 128 SNs per queue */ + 0x0000000f, /* 256 SNs per queue */ + 0x00000003, /* 512 SNs per queue */ + 0x00000001}; /* 1024 SNs per queue */ + + return group->slot_use_bitmap == mask[group->mode]; +} + +static inline int dlb_sn_group_alloc_slot(struct dlb_sn_group *group) +{ + const u32 bound[] = {16, 8, 4, 2, 1}; + u32 i; + + for (i = 0; i < bound[group->mode]; i++) { + if (!(group->slot_use_bitmap & BIT(i))) { + group->slot_use_bitmap |= BIT(i); + return i; + } + } + + return -1; +} + static inline void dlb_sn_group_free_slot(struct dlb_sn_group *group, int slot) { diff --git a/drivers/misc/dlb/dlb_ioctl.c b/drivers/misc/dlb/dlb_ioctl.c index 75892966f061..0fc20b32f0cf 100644 --- a/drivers/misc/dlb/dlb_ioctl.c +++ b/drivers/misc/dlb/dlb_ioctl.c @@ -8,6 +8,69 @@ #include "dlb_main.h" +/* + * The DLB domain ioctl callback template minimizes replication of boilerplate + * code to copy arguments, acquire and release the resource lock, and execute + * the command. The arguments and response structure name should have the + * format dlb_<lower_name>_args. + */ +#define DLB_DOMAIN_IOCTL_CALLBACK_TEMPLATE(lower_name) \ +static int dlb_domain_ioctl_##lower_name(struct dlb *dlb, \ + struct dlb_domain *domain, \ + unsigned long user_arg) \ +{ \ + struct dlb_##lower_name##_args __user *uarg; \ + struct dlb_cmd_response response = {0}; \ + struct dlb_##lower_name##_args arg; \ + int ret; \ + \ + uarg = (void __user *)user_arg; \ + if (copy_from_user(&arg, uarg, sizeof(arg))) \ + return -EFAULT; \ + \ + mutex_lock(&dlb->resource_mutex); \ + \ + ret = dlb->ops->lower_name(&dlb->hw, \ + domain->id, \ + &arg, \ + &response); \ + \ + mutex_unlock(&dlb->resource_mutex); \ + \ + BUILD_BUG_ON(offsetof(typeof(arg), response) != 0); \ + \ + if (copy_to_user((void __user *)&uarg->response, \ + &response, \ + sizeof(response))) \ + return -EFAULT; \ + \ + return ret; \ +} + +DLB_DOMAIN_IOCTL_CALLBACK_TEMPLATE(create_ldb_queue) +DLB_DOMAIN_IOCTL_CALLBACK_TEMPLATE(create_dir_queue) +DLB_DOMAIN_IOCTL_CALLBACK_TEMPLATE(get_ldb_queue_depth) +DLB_DOMAIN_IOCTL_CALLBACK_TEMPLATE(get_dir_queue_depth) + +long dlb_domain_ioctl(struct file *f, unsigned int cmd, unsigned long arg) +{ + struct dlb_domain *dom = f->private_data; + struct dlb *dlb = dom->dlb; + + switch (cmd) { + case DLB_IOC_CREATE_LDB_QUEUE: + return dlb_domain_ioctl_create_ldb_queue(dlb, dom, arg); + case DLB_IOC_CREATE_DIR_QUEUE: + return dlb_domain_ioctl_create_dir_queue(dlb, dom, arg); + case DLB_IOC_GET_LDB_QUEUE_DEPTH: + return dlb_domain_ioctl_get_ldb_queue_depth(dlb, dom, arg); + case DLB_IOC_GET_DIR_QUEUE_DEPTH: + return dlb_domain_ioctl_get_dir_queue_depth(dlb, dom, arg); + default: + return -ENOTTY; + } +} + /* [7:0]: device revision, [15:8]: device version */ #define DLB_SET_DEVICE_VERSION(ver, rev) (((ver) << 8) | (rev)) diff --git a/drivers/misc/dlb/dlb_main.c b/drivers/misc/dlb/dlb_main.c index 5484d9aee02c..64915824ca03 100644 --- a/drivers/misc/dlb/dlb_main.c +++ b/drivers/misc/dlb/dlb_main.c @@ -185,6 +185,8 @@ static int dlb_domain_close(struct inode *i, struct file *f) const struct file_operations dlb_domain_fops = { .owner = THIS_MODULE, .release = dlb_domain_close, + .unlocked_ioctl = dlb_domain_ioctl, + .compat_ioctl = compat_ptr_ioctl, }; /**********************************/ diff --git a/drivers/misc/dlb/dlb_main.h b/drivers/misc/dlb/dlb_main.h index ecfda11b297b..227630adf8ac 100644 --- a/drivers/misc/dlb/dlb_main.h +++ b/drivers/misc/dlb/dlb_main.h @@ -42,9 +42,25 @@ struct dlb_device_ops { int (*create_sched_domain)(struct dlb_hw *hw, struct dlb_create_sched_domain_args *args, struct dlb_cmd_response *resp); + int (*create_ldb_queue)(struct dlb_hw *hw, + u32 domain_id, + struct dlb_create_ldb_queue_args *args, + struct dlb_cmd_response *resp); + int (*create_dir_queue)(struct dlb_hw *hw, + u32 domain_id, + struct dlb_create_dir_queue_args *args, + struct dlb_cmd_response *resp); int (*get_num_resources)(struct dlb_hw *hw, struct dlb_get_num_resources_args *args); int (*reset_domain)(struct dlb_hw *hw, u32 domain_id); + int (*get_ldb_queue_depth)(struct dlb_hw *hw, + u32 domain_id, + struct dlb_get_ldb_queue_depth_args *args, + struct dlb_cmd_response *resp); + int (*get_dir_queue_depth)(struct dlb_hw *hw, + u32 domain_id, + struct dlb_get_dir_queue_depth_args *args, + struct dlb_cmd_response *resp); }; extern struct dlb_device_ops dlb_pf_ops; @@ -76,6 +92,7 @@ struct dlb { /* Prototypes for dlb_ioctl.c */ long dlb_ioctl(struct file *f, unsigned int cmd, unsigned long arg); +long dlb_domain_ioctl(struct file *f, unsigned int cmd, unsigned long arg); int dlb_init_domain(struct dlb *dlb, u32 domain_id); void dlb_free_domain(struct kref *kref); diff --git a/drivers/misc/dlb/dlb_pf_ops.c b/drivers/misc/dlb/dlb_pf_ops.c index 494a482368f6..32991c5f3366 100644 --- a/drivers/misc/dlb/dlb_pf_ops.c +++ b/drivers/misc/dlb/dlb_pf_ops.c @@ -114,6 +114,22 @@ static int dlb_pf_create_sched_domain(struct dlb_hw *hw, return dlb_hw_create_sched_domain(hw, args, resp, false, 0); } +static int +dlb_pf_create_ldb_queue(struct dlb_hw *hw, u32 id, + struct dlb_create_ldb_queue_args *args, + struct dlb_cmd_response *resp) +{ + return dlb_hw_create_ldb_queue(hw, id, args, resp, false, 0); +} + +static int +dlb_pf_create_dir_queue(struct dlb_hw *hw, u32 id, + struct dlb_create_dir_queue_args *args, + struct dlb_cmd_response *resp) +{ + return dlb_hw_create_dir_queue(hw, id, args, resp, false, 0); +} + static int dlb_pf_get_num_resources(struct dlb_hw *hw, struct dlb_get_num_resources_args *args) { @@ -126,6 +142,22 @@ dlb_pf_reset_domain(struct dlb_hw *hw, u32 id) return dlb_reset_domain(hw, id, false, 0); } +static int +dlb_pf_get_ldb_queue_depth(struct dlb_hw *hw, u32 id, + struct dlb_get_ldb_queue_depth_args *args, + struct dlb_cmd_response *resp) +{ + return dlb_hw_get_ldb_queue_depth(hw, id, args, resp, false, 0); +} + +static int +dlb_pf_get_dir_queue_depth(struct dlb_hw *hw, u32 id, + struct dlb_get_dir_queue_depth_args *args, + struct dlb_cmd_response *resp) +{ + return dlb_hw_get_dir_queue_depth(hw, id, args, resp, false, 0); +} + /********************************/ /****** DLB PF Device Ops ******/ /********************************/ @@ -137,6 +169,10 @@ struct dlb_device_ops dlb_pf_ops = { .enable_pm = dlb_pf_enable_pm, .wait_for_device_ready = dlb_pf_wait_for_device_ready, .create_sched_domain = dlb_pf_create_sched_domain, + .create_ldb_queue = dlb_pf_create_ldb_queue, + .create_dir_queue = dlb_pf_create_dir_queue, .get_num_resources = dlb_pf_get_num_resources, .reset_domain = dlb_pf_reset_domain, + .get_ldb_queue_depth = dlb_pf_get_ldb_queue_depth, + .get_dir_queue_depth = dlb_pf_get_dir_queue_depth, }; diff --git a/drivers/misc/dlb/dlb_resource.c b/drivers/misc/dlb/dlb_resource.c index 26b7d9ea94e3..b36f14a661fa 100644 --- a/drivers/misc/dlb/dlb_resource.c +++ b/drivers/misc/dlb/dlb_resource.c @@ -224,6 +224,40 @@ static struct dlb_hw_domain *dlb_get_domain_from_id(struct dlb_hw *hw, u32 id, return NULL; } +static struct dlb_dir_pq_pair * +dlb_get_domain_used_dir_pq(u32 id, bool vdev_req, struct dlb_hw_domain *domain) +{ + struct dlb_dir_pq_pair *port; + + if (id >= DLB_MAX_NUM_DIR_PORTS) + return NULL; + + list_for_each_entry(port, &domain->used_dir_pq_pairs, domain_list) { + if ((!vdev_req && port->id.phys_id == id) || + (vdev_req && port->id.virt_id == id)) + return port; + } + + return NULL; +} + +static struct dlb_ldb_queue * +dlb_get_domain_ldb_queue(u32 id, bool vdev_req, struct dlb_hw_domain *domain) +{ + struct dlb_ldb_queue *queue; + + if (id >= DLB_MAX_NUM_LDB_QUEUES) + return NULL; + + list_for_each_entry(queue, &domain->used_ldb_queues, domain_list) { + if ((!vdev_req && queue->id.phys_id == id) || + (vdev_req && queue->id.virt_id == id)) + return queue; + } + + return NULL; +} + static int dlb_attach_ldb_queues(struct dlb_hw *hw, struct dlb_function_resources *rsrcs, struct dlb_hw_domain *domain, u32 num_queues, @@ -660,6 +694,156 @@ dlb_verify_create_sched_dom_args(struct dlb_function_resources *rsrcs, return 0; } +static int +dlb_verify_create_ldb_queue_args(struct dlb_hw *hw, u32 domain_id, + struct dlb_create_ldb_queue_args *args, + struct dlb_cmd_response *resp, + bool vdev_req, unsigned int vdev_id, + struct dlb_hw_domain **out_domain, + struct dlb_ldb_queue **out_queue) +{ + struct dlb_hw_domain *domain; + struct dlb_ldb_queue *queue; + int i; + + domain = dlb_get_domain_from_id(hw, domain_id, vdev_req, vdev_id); + + if (!domain) { + resp->status = DLB_ST_INVALID_DOMAIN_ID; + return -EINVAL; + } + + if (!domain->configured) { + resp->status = DLB_ST_DOMAIN_NOT_CONFIGURED; + return -EINVAL; + } + + if (domain->started) { + resp->status = DLB_ST_DOMAIN_STARTED; + return -EINVAL; + } + + queue = list_first_entry_or_null(&domain->avail_ldb_queues, + typeof(*queue), domain_list); + if (!queue) { + resp->status = DLB_ST_LDB_QUEUES_UNAVAILABLE; + return -EINVAL; + } + + if (args->num_sequence_numbers) { + for (i = 0; i < DLB_MAX_NUM_SEQUENCE_NUMBER_GROUPS; i++) { + struct dlb_sn_group *group = &hw->rsrcs.sn_groups[i]; + + if (group->sequence_numbers_per_queue == + args->num_sequence_numbers && + !dlb_sn_group_full(group)) + break; + } + + if (i == DLB_MAX_NUM_SEQUENCE_NUMBER_GROUPS) { + resp->status = DLB_ST_SEQUENCE_NUMBERS_UNAVAILABLE; + return -EINVAL; + } + } + + if (args->num_qid_inflights > 4096) { + resp->status = DLB_ST_INVALID_QID_INFLIGHT_ALLOCATION; + return -EINVAL; + } + + /* Inflights must be <= number of sequence numbers if ordered */ + if (args->num_sequence_numbers != 0 && + args->num_qid_inflights > args->num_sequence_numbers) { + resp->status = DLB_ST_INVALID_QID_INFLIGHT_ALLOCATION; + return -EINVAL; + } + + if (domain->num_avail_aqed_entries < args->num_atomic_inflights) { + resp->status = DLB_ST_ATOMIC_INFLIGHTS_UNAVAILABLE; + return -EINVAL; + } + + if (args->num_atomic_inflights && + args->lock_id_comp_level != 0 && + args->lock_id_comp_level != 64 && + args->lock_id_comp_level != 128 && + args->lock_id_comp_level != 256 && + args->lock_id_comp_level != 512 && + args->lock_id_comp_level != 1024 && + args->lock_id_comp_level != 2048 && + args->lock_id_comp_level != 4096 && + args->lock_id_comp_level != 65536) { + resp->status = DLB_ST_INVALID_LOCK_ID_COMP_LEVEL; + return -EINVAL; + } + + *out_domain = domain; + *out_queue = queue; + + return 0; +} + +static int +dlb_verify_create_dir_queue_args(struct dlb_hw *hw, u32 domain_id, + struct dlb_create_dir_queue_args *args, + struct dlb_cmd_response *resp, + bool vdev_req, unsigned int vdev_id, + struct dlb_hw_domain **out_domain, + struct dlb_dir_pq_pair **out_queue) +{ + struct dlb_hw_domain *domain; + struct dlb_dir_pq_pair *pq; + + domain = dlb_get_domain_from_id(hw, domain_id, vdev_req, vdev_id); + + if (!domain) { + resp->status = DLB_ST_INVALID_DOMAIN_ID; + return -EINVAL; + } + + if (!domain->configured) { + resp->status = DLB_ST_DOMAIN_NOT_CONFIGURED; + return -EINVAL; + } + + if (domain->started) { + resp->status = DLB_ST_DOMAIN_STARTED; + return -EINVAL; + } + + /* + * If the user claims the port is already configured, validate the port + * ID, its domain, and whether the port is configured. + */ + if (args->port_id != -1) { + pq = dlb_get_domain_used_dir_pq(args->port_id, + vdev_req, + domain); + + if (!pq || pq->domain_id.phys_id != domain->id.phys_id || + !pq->port_configured) { + resp->status = DLB_ST_INVALID_PORT_ID; + return -EINVAL; + } + } else { + /* + * If the queue's port is not configured, validate that a free + * port-queue pair is available. + */ + pq = list_first_entry_or_null(&domain->avail_dir_pq_pairs, + typeof(*pq), domain_list); + if (!pq) { + resp->status = DLB_ST_DIR_QUEUES_UNAVAILABLE; + return -EINVAL; + } + } + + *out_domain = domain; + *out_queue = pq; + + return 0; +} + static void dlb_configure_domain_credits(struct dlb_hw *hw, struct dlb_hw_domain *domain) { @@ -725,6 +909,68 @@ dlb_domain_attach_resources(struct dlb_hw *hw, return 0; } +static int +dlb_ldb_queue_attach_to_sn_group(struct dlb_hw *hw, + struct dlb_ldb_queue *queue, + struct dlb_create_ldb_queue_args *args) +{ + int slot = -1; + int i; + + queue->sn_cfg_valid = false; + + if (args->num_sequence_numbers == 0) + return 0; + + for (i = 0; i < DLB_MAX_NUM_SEQUENCE_NUMBER_GROUPS; i++) { + struct dlb_sn_group *group = &hw->rsrcs.sn_groups[i]; + + if (group->sequence_numbers_per_queue == + args->num_sequence_numbers && + !dlb_sn_group_full(group)) { + slot = dlb_sn_group_alloc_slot(group); + if (slot >= 0) + break; + } + } + + if (slot == -1) { + DLB_HW_ERR(hw, + "[%s():%d] Internal error: no sequence number slots available\n", + __func__, __LINE__); + return -EFAULT; + } + + queue->sn_cfg_valid = true; + queue->sn_group = i; + queue->sn_slot = slot; + return 0; +} + +static int +dlb_ldb_queue_attach_resources(struct dlb_hw *hw, + struct dlb_hw_domain *domain, + struct dlb_ldb_queue *queue, + struct dlb_create_ldb_queue_args *args) +{ + int ret; + + ret = dlb_ldb_queue_attach_to_sn_group(hw, queue, args); + if (ret) + return ret; + + /* Attach QID inflights */ + queue->num_qid_inflights = args->num_qid_inflights; + + /* Attach atomic inflights */ + queue->aqed_limit = args->num_atomic_inflights; + + domain->num_avail_aqed_entries -= args->num_atomic_inflights; + domain->num_used_aqed_entries += args->num_atomic_inflights; + + return 0; +} + static void dlb_log_create_sched_domain_args(struct dlb_hw *hw, struct dlb_create_sched_domain_args *args, @@ -828,6 +1074,172 @@ int dlb_hw_create_sched_domain(struct dlb_hw *hw, return 0; } +static void +dlb_log_create_ldb_queue_args(struct dlb_hw *hw, u32 domain_id, + struct dlb_create_ldb_queue_args *args, + bool vdev_req, unsigned int vdev_id) +{ + DLB_HW_DBG(hw, "DLB create load-balanced queue arguments:\n"); + if (vdev_req) + DLB_HW_DBG(hw, "(Request from vdev %d)\n", vdev_id); + DLB_HW_DBG(hw, "\tDomain ID: %d\n", + domain_id); + DLB_HW_DBG(hw, "\tNumber of sequence numbers: %d\n", + args->num_sequence_numbers); + DLB_HW_DBG(hw, "\tNumber of QID inflights: %d\n", + args->num_qid_inflights); + DLB_HW_DBG(hw, "\tNumber of ATM inflights: %d\n", + args->num_atomic_inflights); +} + +/** + * dlb_hw_create_ldb_queue() - create a load-balanced queue + * @hw: dlb_hw handle for a particular device. + * @domain_id: domain ID. + * @args: queue creation arguments. + * @resp: response structure. + * @vdev_req: indicates whether this request came from a vdev. + * @vdev_id: If vdev_req is true, this contains the vdev's ID. + * + * This function creates a load-balanced queue. + * + * A vdev can be either an SR-IOV virtual function or a Scalable IOV virtual + * device. + * + * Return: + * Returns 0 upon success, < 0 otherwise. If an error occurs, resp->status is + * assigned a detailed error code from enum dlb_error. If successful, resp->id + * contains the queue ID. + * + * resp->id contains a virtual ID if vdev_req is true. + * + * Errors: + * EINVAL - A requested resource is unavailable, the domain is not configured, + * the domain has already been started, or the requested queue name is + * already in use. + * EFAULT - Internal error (resp->status not set). + */ +int dlb_hw_create_ldb_queue(struct dlb_hw *hw, u32 domain_id, + struct dlb_create_ldb_queue_args *args, + struct dlb_cmd_response *resp, + bool vdev_req, unsigned int vdev_id) +{ + struct dlb_hw_domain *domain; + struct dlb_ldb_queue *queue; + int ret; + + dlb_log_create_ldb_queue_args(hw, domain_id, args, vdev_req, vdev_id); + + /* + * Verify that hardware resources are available before attempting to + * satisfy the request. This simplifies the error unwinding code. + */ + ret = dlb_verify_create_ldb_queue_args(hw, domain_id, args, resp, + vdev_req, vdev_id, &domain, &queue); + if (ret) + return ret; + + ret = dlb_ldb_queue_attach_resources(hw, domain, queue, args); + if (ret) { + DLB_HW_ERR(hw, + "[%s():%d] Internal error: failed to attach the ldb queue resources\n", + __func__, __LINE__); + return ret; + } + + queue->num_mappings = 0; + + queue->configured = true; + + /* + * Configuration succeeded, so move the resource from the 'avail' to + * the 'used' list. + */ + list_del(&queue->domain_list); + + list_add(&queue->domain_list, &domain->used_ldb_queues); + + resp->status = 0; + resp->id = (vdev_req) ? queue->id.virt_id : queue->id.phys_id; + + return 0; +} + +static void +dlb_log_create_dir_queue_args(struct dlb_hw *hw, u32 domain_id, + struct dlb_create_dir_queue_args *args, + bool vdev_req, unsigned int vdev_id) +{ + DLB_HW_DBG(hw, "DLB create directed queue arguments:\n"); + if (vdev_req) + DLB_HW_DBG(hw, "(Request from vdev %d)\n", vdev_id); + DLB_HW_DBG(hw, "\tDomain ID: %d\n", domain_id); + DLB_HW_DBG(hw, "\tPort ID: %d\n", args->port_id); +} + +/** + * dlb_hw_create_dir_queue() - create a directed queue + * @hw: dlb_hw handle for a particular device. + * @domain_id: domain ID. + * @args: queue creation arguments. + * @resp: response structure. + * @vdev_req: indicates whether this request came from a vdev. + * @vdev_id: If vdev_req is true, this contains the vdev's ID. + * + * This function creates a directed queue. + * + * A vdev can be either an SR-IOV virtual function or a Scalable IOV virtual + * device. + * + * Return: + * Returns 0 upon success, < 0 otherwise. If an error occurs, resp->status is + * assigned a detailed error code from enum dlb_error. If successful, resp->id + * contains the queue ID. + * + * resp->id contains a virtual ID if vdev_req is true. + * + * Errors: + * EINVAL - A requested resource is unavailable, the domain is not configured, + * or the domain has already been started. + * EFAULT - Internal error (resp->status not set). + */ +int dlb_hw_create_dir_queue(struct dlb_hw *hw, u32 domain_id, + struct dlb_create_dir_queue_args *args, + struct dlb_cmd_response *resp, + bool vdev_req, unsigned int vdev_id) +{ + struct dlb_dir_pq_pair *queue; + struct dlb_hw_domain *domain; + int ret; + + dlb_log_create_dir_queue_args(hw, domain_id, args, vdev_req, vdev_id); + + /* + * Verify that hardware resources are available before attempting to + * satisfy the request. This simplifies the error unwinding code. + */ + ret = dlb_verify_create_dir_queue_args(hw, domain_id, args, resp, + vdev_req, vdev_id, &domain, &queue); + if (ret) + return ret; + + /* + * Configuration succeeded, so move the resource from the 'avail' to + * the 'used' list (if it's not already there). + */ + if (args->port_id == -1) { + list_del(&queue->domain_list); + + list_add(&queue->domain_list, &domain->used_dir_pq_pairs); + } + + resp->status = 0; + + resp->id = (vdev_req) ? queue->id.virt_id : queue->id.phys_id; + + return 0; +} + static int dlb_domain_reset_software_state(struct dlb_hw *hw, struct dlb_hw_domain *domain) { @@ -973,6 +1385,151 @@ static int dlb_domain_reset_software_state(struct dlb_hw *hw, return 0; } +static void dlb_log_get_dir_queue_depth(struct dlb_hw *hw, u32 domain_id, + u32 queue_id, bool vdev_req, + unsigned int vf_id) +{ + DLB_HW_DBG(hw, "DLB get directed queue depth:\n"); + if (vdev_req) + DLB_HW_DBG(hw, "(Request from VF %d)\n", vf_id); + DLB_HW_DBG(hw, "\tDomain ID: %d\n", domain_id); + DLB_HW_DBG(hw, "\tQueue ID: %d\n", queue_id); +} + +/** + * dlb_hw_get_dir_queue_depth() - returns the depth of a directed queue + * @hw: dlb_hw handle for a particular device. + * @domain_id: domain ID. + * @args: queue depth args + * @resp: response structure. + * @vdev_req: indicates whether this request came from a vdev. + * @vdev_id: If vdev_req is true, this contains the vdev's ID. + * + * This function returns the depth of a directed queue. + * + * A vdev can be either an SR-IOV virtual function or a Scalable IOV virtual + * device. + * + * Return: + * Returns 0 upon success, < 0 otherwise. If an error occurs, resp->status is + * assigned a detailed error code from enum dlb_error. If successful, resp->id + * contains the depth. + * + * Errors: + * EINVAL - Invalid domain ID or queue ID. + */ +int dlb_hw_get_dir_queue_depth(struct dlb_hw *hw, u32 domain_id, + struct dlb_get_dir_queue_depth_args *args, + struct dlb_cmd_response *resp, + bool vdev_req, unsigned int vdev_id) +{ + struct dlb_dir_pq_pair *queue; + struct dlb_hw_domain *domain; + int id; + + id = domain_id; + + dlb_log_get_dir_queue_depth(hw, domain_id, args->queue_id, + vdev_req, vdev_id); + + domain = dlb_get_domain_from_id(hw, id, vdev_req, vdev_id); + if (!domain) { + resp->status = DLB_ST_INVALID_DOMAIN_ID; + return -EINVAL; + } + + id = args->queue_id; + + queue = dlb_get_domain_used_dir_pq(id, vdev_req, domain); + if (!queue) { + resp->status = DLB_ST_INVALID_QID; + return -EINVAL; + } + + resp->id = 0; + + return 0; +} + +static u32 dlb_ldb_queue_depth(struct dlb_hw *hw, struct dlb_ldb_queue *queue) +{ + u32 aqed, ldb, atm; + + aqed = DLB_CSR_RD(hw, LSP_QID_AQED_ACTIVE_CNT(queue->id.phys_id)); + ldb = DLB_CSR_RD(hw, LSP_QID_LDB_ENQUEUE_CNT(queue->id.phys_id)); + atm = DLB_CSR_RD(hw, LSP_QID_ATM_ACTIVE(queue->id.phys_id)); + + return BITS_GET(aqed, LSP_QID_AQED_ACTIVE_CNT_COUNT) + + BITS_GET(ldb, LSP_QID_LDB_ENQUEUE_CNT_COUNT) + + BITS_GET(atm, LSP_QID_ATM_ACTIVE_COUNT); +} + +static bool dlb_ldb_queue_is_empty(struct dlb_hw *hw, struct dlb_ldb_queue *queue) +{ + return dlb_ldb_queue_depth(hw, queue) == 0; +} + +static void dlb_log_get_ldb_queue_depth(struct dlb_hw *hw, u32 domain_id, + u32 queue_id, bool vdev_req, + unsigned int vf_id) +{ + DLB_HW_DBG(hw, "DLB get load-balanced queue depth:\n"); + if (vdev_req) + DLB_HW_DBG(hw, "(Request from VF %d)\n", vf_id); + DLB_HW_DBG(hw, "\tDomain ID: %d\n", domain_id); + DLB_HW_DBG(hw, "\tQueue ID: %d\n", queue_id); +} + +/** + * dlb_hw_get_ldb_queue_depth() - returns the depth of a load-balanced queue + * @hw: dlb_hw handle for a particular device. + * @domain_id: domain ID. + * @args: queue depth args + * @resp: response structure. + * @vdev_req: indicates whether this request came from a vdev. + * @vdev_id: If vdev_req is true, this contains the vdev's ID. + * + * This function returns the depth of a load-balanced queue. + * + * A vdev can be either an SR-IOV virtual function or a Scalable IOV virtual + * device. + * + * Return: + * Returns 0 upon success, < 0 otherwise. If an error occurs, resp->status is + * assigned a detailed error code from enum dlb_error. If successful, resp->id + * contains the depth. + * + * Errors: + * EINVAL - Invalid domain ID or queue ID. + */ +int dlb_hw_get_ldb_queue_depth(struct dlb_hw *hw, u32 domain_id, + struct dlb_get_ldb_queue_depth_args *args, + struct dlb_cmd_response *resp, + bool vdev_req, unsigned int vdev_id) +{ + struct dlb_hw_domain *domain; + struct dlb_ldb_queue *queue; + + dlb_log_get_ldb_queue_depth(hw, domain_id, args->queue_id, + vdev_req, vdev_id); + + domain = dlb_get_domain_from_id(hw, domain_id, vdev_req, vdev_id); + if (!domain) { + resp->status = DLB_ST_INVALID_DOMAIN_ID; + return -EINVAL; + } + + queue = dlb_get_domain_ldb_queue(args->queue_id, vdev_req, domain); + if (!queue) { + resp->status = DLB_ST_INVALID_QID; + return -EINVAL; + } + + resp->id = 0; + + return 0; +} + static void __dlb_domain_reset_ldb_port_registers(struct dlb_hw *hw, struct dlb_ldb_port *port) { @@ -1337,6 +1894,27 @@ static void dlb_domain_reset_dir_queue_registers(struct dlb_hw *hw, } } +static int dlb_domain_verify_reset_success(struct dlb_hw *hw, + struct dlb_hw_domain *domain) +{ + struct dlb_ldb_queue *queue; + + /* + * Confirm that all the domain's queue's inflight counts and AQED + * active counts are 0. + */ + list_for_each_entry(queue, &domain->used_ldb_queues, domain_list) { + if (!dlb_ldb_queue_is_empty(hw, queue)) { + DLB_HW_ERR(hw, + "[%s()] Internal error: failed to empty ldb queue %d\n", + __func__, queue->id.phys_id); + return -EFAULT; + } + } + + return 0; +} + static void dlb_domain_reset_registers(struct dlb_hw *hw, struct dlb_hw_domain *domain) { @@ -1395,6 +1973,7 @@ int dlb_reset_domain(struct dlb_hw *hw, u32 domain_id, bool vdev_req, unsigned int vdev_id) { struct dlb_hw_domain *domain; + int ret; dlb_log_reset_domain(hw, domain_id, vdev_req, vdev_id); @@ -1403,6 +1982,16 @@ int dlb_reset_domain(struct dlb_hw *hw, u32 domain_id, bool vdev_req, if (!domain || !domain->configured) return -EINVAL; + /* + * For each queue owned by this domain, disable its write permissions to + * cause any traffic sent to it to be dropped. Well-behaved software + * should not be sending QEs at this point. + */ + + ret = dlb_domain_verify_reset_success(hw, domain); + if (ret) + return ret; + /* Reset the QID and port state. */ dlb_domain_reset_registers(hw, domain); diff --git a/drivers/misc/dlb/dlb_resource.h b/drivers/misc/dlb/dlb_resource.h index 8c50f449cb9b..50e674e46dbb 100644 --- a/drivers/misc/dlb/dlb_resource.h +++ b/drivers/misc/dlb/dlb_resource.h @@ -19,6 +19,16 @@ int dlb_hw_create_sched_domain(struct dlb_hw *hw, struct dlb_cmd_response *resp, bool vdev_req, unsigned int vdev_id); +int dlb_hw_create_ldb_queue(struct dlb_hw *hw, u32 domain_id, + struct dlb_create_ldb_queue_args *args, + struct dlb_cmd_response *resp, + bool vdev_req, unsigned int vdev_id); + +int dlb_hw_create_dir_queue(struct dlb_hw *hw, u32 domain_id, + struct dlb_create_dir_queue_args *args, + struct dlb_cmd_response *resp, + bool vdev_req, unsigned int vdev_id); + int dlb_reset_domain(struct dlb_hw *hw, u32 domain_id, bool vdev_req, unsigned int vdev_id); @@ -28,4 +38,14 @@ int dlb_hw_get_num_resources(struct dlb_hw *hw, void dlb_clr_pmcsr_disable(struct dlb_hw *hw); +int dlb_hw_get_ldb_queue_depth(struct dlb_hw *hw, u32 domain_id, + struct dlb_get_ldb_queue_depth_args *args, + struct dlb_cmd_response *resp, + bool vdev_req, unsigned int vdev_id); + +int dlb_hw_get_dir_queue_depth(struct dlb_hw *hw, u32 domain_id, + struct dlb_get_dir_queue_depth_args *args, + struct dlb_cmd_response *resp, + bool vdev_req, unsigned int vdev_id); + #endif /* __DLB_RESOURCE_H */ diff --git a/include/uapi/linux/dlb.h b/include/uapi/linux/dlb.h index 0513116072a7..0c09a9bbc9d3 100644 --- a/include/uapi/linux/dlb.h +++ b/include/uapi/linux/dlb.h @@ -19,6 +19,15 @@ enum dlb_error { DLB_ST_HIST_LIST_ENTRIES_UNAVAILABLE, DLB_ST_LDB_PORT_REQUIRED_FOR_LDB_QUEUES, DLB_ST_DOMAIN_RESET_FAILED, + DLB_ST_INVALID_DOMAIN_ID, + DLB_ST_INVALID_QID_INFLIGHT_ALLOCATION, + DLB_ST_INVALID_LDB_QUEUE_ID, + DLB_ST_DOMAIN_NOT_CONFIGURED, + DLB_ST_INVALID_QID, + DLB_ST_DOMAIN_STARTED, + DLB_ST_DIR_QUEUES_UNAVAILABLE, + DLB_ST_INVALID_PORT_ID, + DLB_ST_INVALID_LOCK_ID_COMP_LEVEL, }; struct dlb_cmd_response { @@ -160,6 +169,132 @@ enum dlb_user_interface_commands { NUM_DLB_CMD, }; +/*********************************/ +/* 'domain' device file commands */ +/*********************************/ + +/* + * DLB_DOMAIN_CMD_CREATE_LDB_QUEUE: Configure a load-balanced queue. + * + * Output parameters: + * @response.status: Detailed error code. In certain cases, such as if the + * ioctl request arg is invalid, the driver won't set status. + * @response.id: Queue ID. + * + * Input parameters: + * @num_atomic_inflights: This specifies the amount of temporary atomic QE + * storage for this queue. If zero, the queue will not support atomic + * scheduling. + * @num_sequence_numbers: This specifies the number of sequence numbers used + * by this queue. If zero, the queue will not support ordered scheduling. + * If non-zero, the queue will not support unordered scheduling. + * @num_qid_inflights: The maximum number of QEs that can be inflight + * (scheduled to a CQ but not completed) at any time. If + * num_sequence_numbers is non-zero, num_qid_inflights must be set equal + * to num_sequence_numbers. + * @lock_id_comp_level: Lock ID compression level. Specifies the number of + * unique lock IDs the queue should compress down to. Valid compression + * levels: 0, 64, 128, 256, 512, 1k, 2k, 4k, 64k. If lock_id_comp_level is + * 0, the queue won't compress its lock IDs. + * @depth_threshold: DLB sets two bits in the received QE to indicate the + * depth of the queue relative to the threshold before scheduling the + * QE to a CQ: + * - 2’b11: depth > threshold + * - 2’b10: threshold >= depth > 0.75 * threshold + * - 2’b01: 0.75 * threshold >= depth > 0.5 * threshold + * - 2’b00: depth <= 0.5 * threshold + * @padding0: Reserved for future use. + */ +struct dlb_create_ldb_queue_args { + /* Output parameters */ + struct dlb_cmd_response response; + /* Input parameters */ + __u32 num_sequence_numbers; + __u32 num_qid_inflights; + __u32 num_atomic_inflights; + __u32 lock_id_comp_level; + __u32 depth_threshold; + __u32 padding0; +}; + +/* + * DLB_DOMAIN_CMD_CREATE_DIR_QUEUE: Configure a directed queue. + * + * Output parameters: + * @response.status: Detailed error code. In certain cases, such as if the + * ioctl request arg is invalid, the driver won't set status. + * @response.id: Queue ID. + * + * Input parameters: + * @port_id: Port ID. If the corresponding directed port is already created, + * specify its ID here. Else this argument must be 0xFFFFFFFF to indicate + * that the queue is being created before the port. + * @depth_threshold: DLB sets two bits in the received QE to indicate the + * depth of the queue relative to the threshold before scheduling the + * QE to a CQ: + * - 2’b11: depth > threshold + * - 2’b10: threshold >= depth > 0.75 * threshold + * - 2’b01: 0.75 * threshold >= depth > 0.5 * threshold + * - 2’b00: depth <= 0.5 * threshold + */ +struct dlb_create_dir_queue_args { + /* Output parameters */ + struct dlb_cmd_response response; + /* Input parameters */ + __s32 port_id; + __u32 depth_threshold; +}; + +/* + * DLB_DOMAIN_CMD_GET_LDB_QUEUE_DEPTH: Get a load-balanced queue's depth. + * + * Output parameters: + * @response.status: Detailed error code. In certain cases, such as if the + * ioctl request arg is invalid, the driver won't set status. + * @response.id: queue depth. + * + * Input parameters: + * @queue_id: The load-balanced queue ID. + * @padding0: Reserved for future use. + */ +struct dlb_get_ldb_queue_depth_args { + /* Output parameters */ + struct dlb_cmd_response response; + /* Input parameters */ + __u32 queue_id; + __u32 padding0; +}; + +/* + * DLB_DOMAIN_CMD_DIR_QUEUE_DEPTH: Get a directed queue's depth. + * + * Output parameters: + * @response.status: Detailed error code. In certain cases, such as if the + * ioctl request arg is invalid, the driver won't set status. + * @response.id: queue depth. + * + * Input parameters: + * @queue_id: The directed queue ID. + * @padding0: Reserved for future use. + */ +struct dlb_get_dir_queue_depth_args { + /* Output parameters */ + struct dlb_cmd_response response; + /* Input parameters */ + __u32 queue_id; + __u32 padding0; +}; + +enum dlb_domain_user_interface_commands { + DLB_DOMAIN_CMD_CREATE_LDB_QUEUE, + DLB_DOMAIN_CMD_CREATE_DIR_QUEUE, + DLB_DOMAIN_CMD_GET_LDB_QUEUE_DEPTH, + DLB_DOMAIN_CMD_GET_DIR_QUEUE_DEPTH, + + /* NUM_DLB_DOMAIN_CMD must be last */ + NUM_DLB_DOMAIN_CMD, +}; + /********************/ /* dlb ioctl codes */ /********************/ @@ -178,5 +313,21 @@ enum dlb_user_interface_commands { _IOR(DLB_IOC_MAGIC, \ DLB_CMD_GET_NUM_RESOURCES, \ struct dlb_get_num_resources_args) +#define DLB_IOC_CREATE_LDB_QUEUE \ + _IOWR(DLB_IOC_MAGIC, \ + DLB_DOMAIN_CMD_CREATE_LDB_QUEUE, \ + struct dlb_create_ldb_queue_args) +#define DLB_IOC_CREATE_DIR_QUEUE \ + _IOWR(DLB_IOC_MAGIC, \ + DLB_DOMAIN_CMD_CREATE_DIR_QUEUE, \ + struct dlb_create_dir_queue_args) +#define DLB_IOC_GET_LDB_QUEUE_DEPTH \ + _IOWR(DLB_IOC_MAGIC, \ + DLB_DOMAIN_CMD_GET_LDB_QUEUE_DEPTH, \ + struct dlb_get_ldb_queue_depth_args) +#define DLB_IOC_GET_DIR_QUEUE_DEPTH \ + _IOWR(DLB_IOC_MAGIC, \ + DLB_DOMAIN_CMD_GET_DIR_QUEUE_DEPTH, \ + struct dlb_get_dir_queue_depth_args) #endif /* __DLB_H */ -- 2.17.1