|
@@ -26,6 +26,294 @@ MODULE_PARM_DESC(debug, "netif level (0=none,...,16=all), hw debug_mask (0x8XXXX
|
|
|
MODULE_PARM_DESC(debug, "netif level (0=none,...,16=all)");
|
|
|
#endif /* !CONFIG_DYNAMIC_DEBUG */
|
|
|
|
|
|
+static struct workqueue_struct *ice_wq;
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_search_res - Search the tracker for a block of resources
|
|
|
+ * @res: pointer to the resource
|
|
|
+ * @needed: size of the block needed
|
|
|
+ * @id: identifier to track owner
|
|
|
+ * Returns the base item index of the block, or -ENOMEM for error
|
|
|
+ */
|
|
|
+static int ice_search_res(struct ice_res_tracker *res, u16 needed, u16 id)
|
|
|
+{
|
|
|
+ int start = res->search_hint;
|
|
|
+ int end = start;
|
|
|
+
|
|
|
+ id |= ICE_RES_VALID_BIT;
|
|
|
+
|
|
|
+ do {
|
|
|
+ /* skip already allocated entries */
|
|
|
+ if (res->list[end++] & ICE_RES_VALID_BIT) {
|
|
|
+ start = end;
|
|
|
+ if ((start + needed) > res->num_entries)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (end == (start + needed)) {
|
|
|
+ int i = start;
|
|
|
+
|
|
|
+ /* there was enough, so assign it to the requestor */
|
|
|
+ while (i != end)
|
|
|
+ res->list[i++] = id;
|
|
|
+
|
|
|
+ if (end == res->num_entries)
|
|
|
+ end = 0;
|
|
|
+
|
|
|
+ res->search_hint = end;
|
|
|
+ return start;
|
|
|
+ }
|
|
|
+ } while (1);
|
|
|
+
|
|
|
+ return -ENOMEM;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_get_res - get a block of resources
|
|
|
+ * @pf: board private structure
|
|
|
+ * @res: pointer to the resource
|
|
|
+ * @needed: size of the block needed
|
|
|
+ * @id: identifier to track owner
|
|
|
+ *
|
|
|
+ * Returns the base item index of the block, or -ENOMEM for error
|
|
|
+ * The search_hint trick and lack of advanced fit-finding only works
|
|
|
+ * because we're highly likely to have all the same sized requests.
|
|
|
+ * Linear search time and any fragmentation should be minimal.
|
|
|
+ */
|
|
|
+static int
|
|
|
+ice_get_res(struct ice_pf *pf, struct ice_res_tracker *res, u16 needed, u16 id)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (!res || !pf)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (!needed || needed > res->num_entries || id >= ICE_RES_VALID_BIT) {
|
|
|
+ dev_err(&pf->pdev->dev,
|
|
|
+ "param err: needed=%d, num_entries = %d id=0x%04x\n",
|
|
|
+ needed, res->num_entries, id);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* search based on search_hint */
|
|
|
+ ret = ice_search_res(res, needed, id);
|
|
|
+
|
|
|
+ if (ret < 0) {
|
|
|
+ /* previous search failed. Reset search hint and try again */
|
|
|
+ res->search_hint = 0;
|
|
|
+ ret = ice_search_res(res, needed, id);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_free_res - free a block of resources
|
|
|
+ * @res: pointer to the resource
|
|
|
+ * @index: starting index previously returned by ice_get_res
|
|
|
+ * @id: identifier to track owner
|
|
|
+ * Returns number of resources freed
|
|
|
+ */
|
|
|
+static int ice_free_res(struct ice_res_tracker *res, u16 index, u16 id)
|
|
|
+{
|
|
|
+ int count = 0;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (!res || index >= res->num_entries)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ id |= ICE_RES_VALID_BIT;
|
|
|
+ for (i = index; i < res->num_entries && res->list[i] == id; i++) {
|
|
|
+ res->list[i] = 0;
|
|
|
+ count++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return count;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * __ice_clean_ctrlq - helper function to clean controlq rings
|
|
|
+ * @pf: ptr to struct ice_pf
|
|
|
+ * @q_type: specific Control queue type
|
|
|
+ */
|
|
|
+static int __ice_clean_ctrlq(struct ice_pf *pf, enum ice_ctl_q q_type)
|
|
|
+{
|
|
|
+ struct ice_rq_event_info event;
|
|
|
+ struct ice_hw *hw = &pf->hw;
|
|
|
+ struct ice_ctl_q_info *cq;
|
|
|
+ u16 pending, i = 0;
|
|
|
+ const char *qtype;
|
|
|
+ u32 oldval, val;
|
|
|
+
|
|
|
+ switch (q_type) {
|
|
|
+ case ICE_CTL_Q_ADMIN:
|
|
|
+ cq = &hw->adminq;
|
|
|
+ qtype = "Admin";
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ dev_warn(&pf->pdev->dev, "Unknown control queue type 0x%x\n",
|
|
|
+ q_type);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* check for error indications - PF_xx_AxQLEN register layout for
|
|
|
+ * FW/MBX/SB are identical so just use defines for PF_FW_AxQLEN.
|
|
|
+ */
|
|
|
+ val = rd32(hw, cq->rq.len);
|
|
|
+ if (val & (PF_FW_ARQLEN_ARQVFE_M | PF_FW_ARQLEN_ARQOVFL_M |
|
|
|
+ PF_FW_ARQLEN_ARQCRIT_M)) {
|
|
|
+ oldval = val;
|
|
|
+ if (val & PF_FW_ARQLEN_ARQVFE_M)
|
|
|
+ dev_dbg(&pf->pdev->dev,
|
|
|
+ "%s Receive Queue VF Error detected\n", qtype);
|
|
|
+ if (val & PF_FW_ARQLEN_ARQOVFL_M) {
|
|
|
+ dev_dbg(&pf->pdev->dev,
|
|
|
+ "%s Receive Queue Overflow Error detected\n",
|
|
|
+ qtype);
|
|
|
+ }
|
|
|
+ if (val & PF_FW_ARQLEN_ARQCRIT_M)
|
|
|
+ dev_dbg(&pf->pdev->dev,
|
|
|
+ "%s Receive Queue Critical Error detected\n",
|
|
|
+ qtype);
|
|
|
+ val &= ~(PF_FW_ARQLEN_ARQVFE_M | PF_FW_ARQLEN_ARQOVFL_M |
|
|
|
+ PF_FW_ARQLEN_ARQCRIT_M);
|
|
|
+ if (oldval != val)
|
|
|
+ wr32(hw, cq->rq.len, val);
|
|
|
+ }
|
|
|
+
|
|
|
+ val = rd32(hw, cq->sq.len);
|
|
|
+ if (val & (PF_FW_ATQLEN_ATQVFE_M | PF_FW_ATQLEN_ATQOVFL_M |
|
|
|
+ PF_FW_ATQLEN_ATQCRIT_M)) {
|
|
|
+ oldval = val;
|
|
|
+ if (val & PF_FW_ATQLEN_ATQVFE_M)
|
|
|
+ dev_dbg(&pf->pdev->dev,
|
|
|
+ "%s Send Queue VF Error detected\n", qtype);
|
|
|
+ if (val & PF_FW_ATQLEN_ATQOVFL_M) {
|
|
|
+ dev_dbg(&pf->pdev->dev,
|
|
|
+ "%s Send Queue Overflow Error detected\n",
|
|
|
+ qtype);
|
|
|
+ }
|
|
|
+ if (val & PF_FW_ATQLEN_ATQCRIT_M)
|
|
|
+ dev_dbg(&pf->pdev->dev,
|
|
|
+ "%s Send Queue Critical Error detected\n",
|
|
|
+ qtype);
|
|
|
+ val &= ~(PF_FW_ATQLEN_ATQVFE_M | PF_FW_ATQLEN_ATQOVFL_M |
|
|
|
+ PF_FW_ATQLEN_ATQCRIT_M);
|
|
|
+ if (oldval != val)
|
|
|
+ wr32(hw, cq->sq.len, val);
|
|
|
+ }
|
|
|
+
|
|
|
+ event.buf_len = cq->rq_buf_size;
|
|
|
+ event.msg_buf = devm_kzalloc(&pf->pdev->dev, event.buf_len,
|
|
|
+ GFP_KERNEL);
|
|
|
+ if (!event.msg_buf)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ do {
|
|
|
+ enum ice_status ret;
|
|
|
+
|
|
|
+ ret = ice_clean_rq_elem(hw, cq, &event, &pending);
|
|
|
+ if (ret == ICE_ERR_AQ_NO_WORK)
|
|
|
+ break;
|
|
|
+ if (ret) {
|
|
|
+ dev_err(&pf->pdev->dev,
|
|
|
+ "%s Receive Queue event error %d\n", qtype,
|
|
|
+ ret);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } while (pending && (i++ < ICE_DFLT_IRQ_WORK));
|
|
|
+
|
|
|
+ devm_kfree(&pf->pdev->dev, event.msg_buf);
|
|
|
+
|
|
|
+ return pending && (i == ICE_DFLT_IRQ_WORK);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_clean_adminq_subtask - clean the AdminQ rings
|
|
|
+ * @pf: board private structure
|
|
|
+ */
|
|
|
+static void ice_clean_adminq_subtask(struct ice_pf *pf)
|
|
|
+{
|
|
|
+ struct ice_hw *hw = &pf->hw;
|
|
|
+ u32 val;
|
|
|
+
|
|
|
+ if (!test_bit(__ICE_ADMINQ_EVENT_PENDING, pf->state))
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (__ice_clean_ctrlq(pf, ICE_CTL_Q_ADMIN))
|
|
|
+ return;
|
|
|
+
|
|
|
+ clear_bit(__ICE_ADMINQ_EVENT_PENDING, pf->state);
|
|
|
+
|
|
|
+ /* re-enable Admin queue interrupt causes */
|
|
|
+ val = rd32(hw, PFINT_FW_CTL);
|
|
|
+ wr32(hw, PFINT_FW_CTL, (val | PFINT_FW_CTL_CAUSE_ENA_M));
|
|
|
+
|
|
|
+ ice_flush(hw);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_service_task_schedule - schedule the service task to wake up
|
|
|
+ * @pf: board private structure
|
|
|
+ *
|
|
|
+ * If not already scheduled, this puts the task into the work queue.
|
|
|
+ */
|
|
|
+static void ice_service_task_schedule(struct ice_pf *pf)
|
|
|
+{
|
|
|
+ if (!test_bit(__ICE_DOWN, pf->state) &&
|
|
|
+ !test_and_set_bit(__ICE_SERVICE_SCHED, pf->state))
|
|
|
+ queue_work(ice_wq, &pf->serv_task);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_service_task_complete - finish up the service task
|
|
|
+ * @pf: board private structure
|
|
|
+ */
|
|
|
+static void ice_service_task_complete(struct ice_pf *pf)
|
|
|
+{
|
|
|
+ WARN_ON(!test_bit(__ICE_SERVICE_SCHED, pf->state));
|
|
|
+
|
|
|
+ /* force memory (pf->state) to sync before next service task */
|
|
|
+ smp_mb__before_atomic();
|
|
|
+ clear_bit(__ICE_SERVICE_SCHED, pf->state);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_service_timer - timer callback to schedule service task
|
|
|
+ * @t: pointer to timer_list
|
|
|
+ */
|
|
|
+static void ice_service_timer(struct timer_list *t)
|
|
|
+{
|
|
|
+ struct ice_pf *pf = from_timer(pf, t, serv_tmr);
|
|
|
+
|
|
|
+ mod_timer(&pf->serv_tmr, round_jiffies(pf->serv_tmr_period + jiffies));
|
|
|
+ ice_service_task_schedule(pf);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_service_task - manage and run subtasks
|
|
|
+ * @work: pointer to work_struct contained by the PF struct
|
|
|
+ */
|
|
|
+static void ice_service_task(struct work_struct *work)
|
|
|
+{
|
|
|
+ struct ice_pf *pf = container_of(work, struct ice_pf, serv_task);
|
|
|
+ unsigned long start_time = jiffies;
|
|
|
+
|
|
|
+ /* subtasks */
|
|
|
+ ice_clean_adminq_subtask(pf);
|
|
|
+
|
|
|
+ /* Clear __ICE_SERVICE_SCHED flag to allow scheduling next event */
|
|
|
+ ice_service_task_complete(pf);
|
|
|
+
|
|
|
+ /* If the tasks have taken longer than one service timer period
|
|
|
+ * or there is more work to be done, reset the service timer to
|
|
|
+ * schedule the service task now.
|
|
|
+ */
|
|
|
+ if (time_after(jiffies, (start_time + pf->serv_tmr_period)) ||
|
|
|
+ test_bit(__ICE_ADMINQ_EVENT_PENDING, pf->state))
|
|
|
+ mod_timer(&pf->serv_tmr, jiffies);
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* ice_set_ctrlq_len - helper function to set controlq length
|
|
|
* @hw: pointer to the hw instance
|
|
@@ -38,6 +326,361 @@ static void ice_set_ctrlq_len(struct ice_hw *hw)
|
|
|
hw->adminq.sq_buf_size = ICE_AQ_MAX_BUF_LEN;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * ice_ena_misc_vector - enable the non-queue interrupts
|
|
|
+ * @pf: board private structure
|
|
|
+ */
|
|
|
+static void ice_ena_misc_vector(struct ice_pf *pf)
|
|
|
+{
|
|
|
+ struct ice_hw *hw = &pf->hw;
|
|
|
+ u32 val;
|
|
|
+
|
|
|
+ /* clear things first */
|
|
|
+ wr32(hw, PFINT_OICR_ENA, 0); /* disable all */
|
|
|
+ rd32(hw, PFINT_OICR); /* read to clear */
|
|
|
+
|
|
|
+ val = (PFINT_OICR_HLP_RDY_M |
|
|
|
+ PFINT_OICR_CPM_RDY_M |
|
|
|
+ PFINT_OICR_ECC_ERR_M |
|
|
|
+ PFINT_OICR_MAL_DETECT_M |
|
|
|
+ PFINT_OICR_GRST_M |
|
|
|
+ PFINT_OICR_PCI_EXCEPTION_M |
|
|
|
+ PFINT_OICR_GPIO_M |
|
|
|
+ PFINT_OICR_STORM_DETECT_M |
|
|
|
+ PFINT_OICR_HMC_ERR_M);
|
|
|
+
|
|
|
+ wr32(hw, PFINT_OICR_ENA, val);
|
|
|
+
|
|
|
+ /* SW_ITR_IDX = 0, but don't change INTENA */
|
|
|
+ wr32(hw, GLINT_DYN_CTL(pf->oicr_idx),
|
|
|
+ GLINT_DYN_CTL_SW_ITR_INDX_M | GLINT_DYN_CTL_INTENA_MSK_M);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_misc_intr - misc interrupt handler
|
|
|
+ * @irq: interrupt number
|
|
|
+ * @data: pointer to a q_vector
|
|
|
+ */
|
|
|
+static irqreturn_t ice_misc_intr(int __always_unused irq, void *data)
|
|
|
+{
|
|
|
+ struct ice_pf *pf = (struct ice_pf *)data;
|
|
|
+ struct ice_hw *hw = &pf->hw;
|
|
|
+ irqreturn_t ret = IRQ_NONE;
|
|
|
+ u32 oicr, ena_mask;
|
|
|
+
|
|
|
+ set_bit(__ICE_ADMINQ_EVENT_PENDING, pf->state);
|
|
|
+
|
|
|
+ oicr = rd32(hw, PFINT_OICR);
|
|
|
+ ena_mask = rd32(hw, PFINT_OICR_ENA);
|
|
|
+
|
|
|
+ if (!(oicr & PFINT_OICR_INTEVENT_M))
|
|
|
+ goto ena_intr;
|
|
|
+
|
|
|
+ if (oicr & PFINT_OICR_HMC_ERR_M) {
|
|
|
+ ena_mask &= ~PFINT_OICR_HMC_ERR_M;
|
|
|
+ dev_dbg(&pf->pdev->dev,
|
|
|
+ "HMC Error interrupt - info 0x%x, data 0x%x\n",
|
|
|
+ rd32(hw, PFHMC_ERRORINFO),
|
|
|
+ rd32(hw, PFHMC_ERRORDATA));
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Report and mask off any remaining unexpected interrupts */
|
|
|
+ oicr &= ena_mask;
|
|
|
+ if (oicr) {
|
|
|
+ dev_dbg(&pf->pdev->dev, "unhandled interrupt oicr=0x%08x\n",
|
|
|
+ oicr);
|
|
|
+ /* If a critical error is pending there is no choice but to
|
|
|
+ * reset the device.
|
|
|
+ */
|
|
|
+ if (oicr & (PFINT_OICR_PE_CRITERR_M |
|
|
|
+ PFINT_OICR_PCI_EXCEPTION_M |
|
|
|
+ PFINT_OICR_ECC_ERR_M))
|
|
|
+ set_bit(__ICE_PFR_REQ, pf->state);
|
|
|
+
|
|
|
+ ena_mask &= ~oicr;
|
|
|
+ }
|
|
|
+ ret = IRQ_HANDLED;
|
|
|
+
|
|
|
+ena_intr:
|
|
|
+ /* re-enable interrupt causes that are not handled during this pass */
|
|
|
+ wr32(hw, PFINT_OICR_ENA, ena_mask);
|
|
|
+ if (!test_bit(__ICE_DOWN, pf->state)) {
|
|
|
+ ice_service_task_schedule(pf);
|
|
|
+ ice_irq_dynamic_ena(hw);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_free_irq_msix_misc - Unroll misc vector setup
|
|
|
+ * @pf: board private structure
|
|
|
+ */
|
|
|
+static void ice_free_irq_msix_misc(struct ice_pf *pf)
|
|
|
+{
|
|
|
+ /* disable OICR interrupt */
|
|
|
+ wr32(&pf->hw, PFINT_OICR_ENA, 0);
|
|
|
+ ice_flush(&pf->hw);
|
|
|
+
|
|
|
+ if (test_bit(ICE_FLAG_MSIX_ENA, pf->flags) && pf->msix_entries) {
|
|
|
+ synchronize_irq(pf->msix_entries[pf->oicr_idx].vector);
|
|
|
+ devm_free_irq(&pf->pdev->dev,
|
|
|
+ pf->msix_entries[pf->oicr_idx].vector, pf);
|
|
|
+ }
|
|
|
+
|
|
|
+ ice_free_res(pf->irq_tracker, pf->oicr_idx, ICE_RES_MISC_VEC_ID);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_req_irq_msix_misc - Setup the misc vector to handle non queue events
|
|
|
+ * @pf: board private structure
|
|
|
+ *
|
|
|
+ * This sets up the handler for MSIX 0, which is used to manage the
|
|
|
+ * non-queue interrupts, e.g. AdminQ and errors. This is not used
|
|
|
+ * when in MSI or Legacy interrupt mode.
|
|
|
+ */
|
|
|
+static int ice_req_irq_msix_misc(struct ice_pf *pf)
|
|
|
+{
|
|
|
+ struct ice_hw *hw = &pf->hw;
|
|
|
+ int oicr_idx, err = 0;
|
|
|
+ u8 itr_gran;
|
|
|
+ u32 val;
|
|
|
+
|
|
|
+ if (!pf->int_name[0])
|
|
|
+ snprintf(pf->int_name, sizeof(pf->int_name) - 1, "%s-%s:misc",
|
|
|
+ dev_driver_string(&pf->pdev->dev),
|
|
|
+ dev_name(&pf->pdev->dev));
|
|
|
+
|
|
|
+ /* reserve one vector in irq_tracker for misc interrupts */
|
|
|
+ oicr_idx = ice_get_res(pf, pf->irq_tracker, 1, ICE_RES_MISC_VEC_ID);
|
|
|
+ if (oicr_idx < 0)
|
|
|
+ return oicr_idx;
|
|
|
+
|
|
|
+ pf->oicr_idx = oicr_idx;
|
|
|
+
|
|
|
+ err = devm_request_irq(&pf->pdev->dev,
|
|
|
+ pf->msix_entries[pf->oicr_idx].vector,
|
|
|
+ ice_misc_intr, 0, pf->int_name, pf);
|
|
|
+ if (err) {
|
|
|
+ dev_err(&pf->pdev->dev,
|
|
|
+ "devm_request_irq for %s failed: %d\n",
|
|
|
+ pf->int_name, err);
|
|
|
+ ice_free_res(pf->irq_tracker, 1, ICE_RES_MISC_VEC_ID);
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+
|
|
|
+ ice_ena_misc_vector(pf);
|
|
|
+
|
|
|
+ val = (pf->oicr_idx & PFINT_OICR_CTL_MSIX_INDX_M) |
|
|
|
+ (ICE_RX_ITR & PFINT_OICR_CTL_ITR_INDX_M) |
|
|
|
+ PFINT_OICR_CTL_CAUSE_ENA_M;
|
|
|
+ wr32(hw, PFINT_OICR_CTL, val);
|
|
|
+
|
|
|
+ /* This enables Admin queue Interrupt causes */
|
|
|
+ val = (pf->oicr_idx & PFINT_FW_CTL_MSIX_INDX_M) |
|
|
|
+ (ICE_RX_ITR & PFINT_FW_CTL_ITR_INDX_M) |
|
|
|
+ PFINT_FW_CTL_CAUSE_ENA_M;
|
|
|
+ wr32(hw, PFINT_FW_CTL, val);
|
|
|
+
|
|
|
+ itr_gran = hw->itr_gran_200;
|
|
|
+
|
|
|
+ wr32(hw, GLINT_ITR(ICE_RX_ITR, pf->oicr_idx),
|
|
|
+ ITR_TO_REG(ICE_ITR_8K, itr_gran));
|
|
|
+
|
|
|
+ ice_flush(hw);
|
|
|
+ ice_irq_dynamic_ena(hw);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_determine_q_usage - Calculate queue distribution
|
|
|
+ * @pf: board private structure
|
|
|
+ *
|
|
|
+ * Return -ENOMEM if we don't get enough queues for all ports
|
|
|
+ */
|
|
|
+static void ice_determine_q_usage(struct ice_pf *pf)
|
|
|
+{
|
|
|
+ u16 q_left_tx, q_left_rx;
|
|
|
+
|
|
|
+ q_left_tx = pf->hw.func_caps.common_cap.num_txq;
|
|
|
+ q_left_rx = pf->hw.func_caps.common_cap.num_rxq;
|
|
|
+
|
|
|
+ /* initial support for only 1 tx and 1 rx queue */
|
|
|
+ pf->num_lan_tx = 1;
|
|
|
+ pf->num_lan_rx = 1;
|
|
|
+
|
|
|
+ pf->q_left_tx = q_left_tx - pf->num_lan_tx;
|
|
|
+ pf->q_left_rx = q_left_rx - pf->num_lan_rx;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_deinit_pf - Unrolls initialziations done by ice_init_pf
|
|
|
+ * @pf: board private structure to initialize
|
|
|
+ */
|
|
|
+static void ice_deinit_pf(struct ice_pf *pf)
|
|
|
+{
|
|
|
+ if (pf->serv_tmr.function)
|
|
|
+ del_timer_sync(&pf->serv_tmr);
|
|
|
+ if (pf->serv_task.func)
|
|
|
+ cancel_work_sync(&pf->serv_task);
|
|
|
+ mutex_destroy(&pf->sw_mutex);
|
|
|
+ mutex_destroy(&pf->avail_q_mutex);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_init_pf - Initialize general software structures (struct ice_pf)
|
|
|
+ * @pf: board private structure to initialize
|
|
|
+ */
|
|
|
+static void ice_init_pf(struct ice_pf *pf)
|
|
|
+{
|
|
|
+ bitmap_zero(pf->flags, ICE_PF_FLAGS_NBITS);
|
|
|
+ set_bit(ICE_FLAG_MSIX_ENA, pf->flags);
|
|
|
+
|
|
|
+ mutex_init(&pf->sw_mutex);
|
|
|
+ mutex_init(&pf->avail_q_mutex);
|
|
|
+
|
|
|
+ /* Clear avail_[t|r]x_qs bitmaps (set all to avail) */
|
|
|
+ mutex_lock(&pf->avail_q_mutex);
|
|
|
+ bitmap_zero(pf->avail_txqs, ICE_MAX_TXQS);
|
|
|
+ bitmap_zero(pf->avail_rxqs, ICE_MAX_RXQS);
|
|
|
+ mutex_unlock(&pf->avail_q_mutex);
|
|
|
+
|
|
|
+ /* setup service timer and periodic service task */
|
|
|
+ timer_setup(&pf->serv_tmr, ice_service_timer, 0);
|
|
|
+ pf->serv_tmr_period = HZ;
|
|
|
+ INIT_WORK(&pf->serv_task, ice_service_task);
|
|
|
+ clear_bit(__ICE_SERVICE_SCHED, pf->state);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_ena_msix_range - Request a range of MSIX vectors from the OS
|
|
|
+ * @pf: board private structure
|
|
|
+ *
|
|
|
+ * compute the number of MSIX vectors required (v_budget) and request from
|
|
|
+ * the OS. Return the number of vectors reserved or negative on failure
|
|
|
+ */
|
|
|
+static int ice_ena_msix_range(struct ice_pf *pf)
|
|
|
+{
|
|
|
+ int v_left, v_actual, v_budget = 0;
|
|
|
+ int needed, err, i;
|
|
|
+
|
|
|
+ v_left = pf->hw.func_caps.common_cap.num_msix_vectors;
|
|
|
+
|
|
|
+ /* reserve one vector for miscellaneous handler */
|
|
|
+ needed = 1;
|
|
|
+ v_budget += needed;
|
|
|
+ v_left -= needed;
|
|
|
+
|
|
|
+ /* reserve vectors for LAN traffic */
|
|
|
+ pf->num_lan_msix = min_t(int, num_online_cpus(), v_left);
|
|
|
+ v_budget += pf->num_lan_msix;
|
|
|
+
|
|
|
+ pf->msix_entries = devm_kcalloc(&pf->pdev->dev, v_budget,
|
|
|
+ sizeof(struct msix_entry), GFP_KERNEL);
|
|
|
+
|
|
|
+ if (!pf->msix_entries) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto exit_err;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < v_budget; i++)
|
|
|
+ pf->msix_entries[i].entry = i;
|
|
|
+
|
|
|
+ /* actually reserve the vectors */
|
|
|
+ v_actual = pci_enable_msix_range(pf->pdev, pf->msix_entries,
|
|
|
+ ICE_MIN_MSIX, v_budget);
|
|
|
+
|
|
|
+ if (v_actual < 0) {
|
|
|
+ dev_err(&pf->pdev->dev, "unable to reserve MSI-X vectors\n");
|
|
|
+ err = v_actual;
|
|
|
+ goto msix_err;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (v_actual < v_budget) {
|
|
|
+ dev_warn(&pf->pdev->dev,
|
|
|
+ "not enough vectors. requested = %d, obtained = %d\n",
|
|
|
+ v_budget, v_actual);
|
|
|
+ if (v_actual >= (pf->num_lan_msix + 1)) {
|
|
|
+ pf->num_avail_msix = v_actual - (pf->num_lan_msix + 1);
|
|
|
+ } else if (v_actual >= 2) {
|
|
|
+ pf->num_lan_msix = 1;
|
|
|
+ pf->num_avail_msix = v_actual - 2;
|
|
|
+ } else {
|
|
|
+ pci_disable_msix(pf->pdev);
|
|
|
+ err = -ERANGE;
|
|
|
+ goto msix_err;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return v_actual;
|
|
|
+
|
|
|
+msix_err:
|
|
|
+ devm_kfree(&pf->pdev->dev, pf->msix_entries);
|
|
|
+ goto exit_err;
|
|
|
+
|
|
|
+exit_err:
|
|
|
+ pf->num_lan_msix = 0;
|
|
|
+ clear_bit(ICE_FLAG_MSIX_ENA, pf->flags);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_dis_msix - Disable MSI-X interrupt setup in OS
|
|
|
+ * @pf: board private structure
|
|
|
+ */
|
|
|
+static void ice_dis_msix(struct ice_pf *pf)
|
|
|
+{
|
|
|
+ pci_disable_msix(pf->pdev);
|
|
|
+ devm_kfree(&pf->pdev->dev, pf->msix_entries);
|
|
|
+ pf->msix_entries = NULL;
|
|
|
+ clear_bit(ICE_FLAG_MSIX_ENA, pf->flags);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_init_interrupt_scheme - Determine proper interrupt scheme
|
|
|
+ * @pf: board private structure to initialize
|
|
|
+ */
|
|
|
+static int ice_init_interrupt_scheme(struct ice_pf *pf)
|
|
|
+{
|
|
|
+ int vectors = 0;
|
|
|
+ ssize_t size;
|
|
|
+
|
|
|
+ if (test_bit(ICE_FLAG_MSIX_ENA, pf->flags))
|
|
|
+ vectors = ice_ena_msix_range(pf);
|
|
|
+ else
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ if (vectors < 0)
|
|
|
+ return vectors;
|
|
|
+
|
|
|
+ /* set up vector assignment tracking */
|
|
|
+ size = sizeof(struct ice_res_tracker) + (sizeof(u16) * vectors);
|
|
|
+
|
|
|
+ pf->irq_tracker = devm_kzalloc(&pf->pdev->dev, size, GFP_KERNEL);
|
|
|
+ if (!pf->irq_tracker) {
|
|
|
+ ice_dis_msix(pf);
|
|
|
+ return -ENOMEM;
|
|
|
+ }
|
|
|
+
|
|
|
+ pf->irq_tracker->num_entries = vectors;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ice_clear_interrupt_scheme - Undo things done by ice_init_interrupt_scheme
|
|
|
+ * @pf: board private structure
|
|
|
+ */
|
|
|
+static void ice_clear_interrupt_scheme(struct ice_pf *pf)
|
|
|
+{
|
|
|
+ if (test_bit(ICE_FLAG_MSIX_ENA, pf->flags))
|
|
|
+ ice_dis_msix(pf);
|
|
|
+
|
|
|
+ devm_kfree(&pf->pdev->dev, pf->irq_tracker);
|
|
|
+ pf->irq_tracker = NULL;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* ice_probe - Device initialization routine
|
|
|
* @pdev: PCI device information struct
|
|
@@ -113,8 +756,70 @@ static int ice_probe(struct pci_dev *pdev,
|
|
|
hw->fw_maj_ver, hw->fw_min_ver, hw->fw_build,
|
|
|
hw->api_maj_ver, hw->api_min_ver);
|
|
|
|
|
|
+ ice_init_pf(pf);
|
|
|
+
|
|
|
+ ice_determine_q_usage(pf);
|
|
|
+
|
|
|
+ pf->num_alloc_vsi = min_t(u16, ICE_MAX_VSI_ALLOC,
|
|
|
+ hw->func_caps.guaranteed_num_vsi);
|
|
|
+ if (!pf->num_alloc_vsi) {
|
|
|
+ err = -EIO;
|
|
|
+ goto err_init_pf_unroll;
|
|
|
+ }
|
|
|
+
|
|
|
+ pf->vsi = devm_kcalloc(&pdev->dev, pf->num_alloc_vsi,
|
|
|
+ sizeof(struct ice_vsi *), GFP_KERNEL);
|
|
|
+ if (!pf->vsi) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto err_init_pf_unroll;
|
|
|
+ }
|
|
|
+
|
|
|
+ err = ice_init_interrupt_scheme(pf);
|
|
|
+ if (err) {
|
|
|
+ dev_err(&pdev->dev,
|
|
|
+ "ice_init_interrupt_scheme failed: %d\n", err);
|
|
|
+ err = -EIO;
|
|
|
+ goto err_init_interrupt_unroll;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* In case of MSIX we are going to setup the misc vector right here
|
|
|
+ * to handle admin queue events etc. In case of legacy and MSI
|
|
|
+ * the misc functionality and queue processing is combined in
|
|
|
+ * the same vector and that gets setup at open.
|
|
|
+ */
|
|
|
+ if (test_bit(ICE_FLAG_MSIX_ENA, pf->flags)) {
|
|
|
+ err = ice_req_irq_msix_misc(pf);
|
|
|
+ if (err) {
|
|
|
+ dev_err(&pdev->dev,
|
|
|
+ "setup of misc vector failed: %d\n", err);
|
|
|
+ goto err_init_interrupt_unroll;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* create switch struct for the switch element created by FW on boot */
|
|
|
+ pf->first_sw = devm_kzalloc(&pdev->dev, sizeof(struct ice_sw),
|
|
|
+ GFP_KERNEL);
|
|
|
+ if (!pf->first_sw) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto err_msix_misc_unroll;
|
|
|
+ }
|
|
|
+
|
|
|
+ pf->first_sw->bridge_mode = BRIDGE_MODE_VEB;
|
|
|
+ pf->first_sw->pf = pf;
|
|
|
+
|
|
|
+ /* record the sw_id available for later use */
|
|
|
+ pf->first_sw->sw_id = hw->port_info->sw_id;
|
|
|
+
|
|
|
return 0;
|
|
|
|
|
|
+err_msix_misc_unroll:
|
|
|
+ ice_free_irq_msix_misc(pf);
|
|
|
+err_init_interrupt_unroll:
|
|
|
+ ice_clear_interrupt_scheme(pf);
|
|
|
+ devm_kfree(&pdev->dev, pf->vsi);
|
|
|
+err_init_pf_unroll:
|
|
|
+ ice_deinit_pf(pf);
|
|
|
+ ice_deinit_hw(hw);
|
|
|
err_exit_unroll:
|
|
|
pci_disable_pcie_error_reporting(pdev);
|
|
|
return err;
|
|
@@ -133,6 +838,9 @@ static void ice_remove(struct pci_dev *pdev)
|
|
|
|
|
|
set_bit(__ICE_DOWN, pf->state);
|
|
|
|
|
|
+ ice_free_irq_msix_misc(pf);
|
|
|
+ ice_clear_interrupt_scheme(pf);
|
|
|
+ ice_deinit_pf(pf);
|
|
|
ice_deinit_hw(&pf->hw);
|
|
|
pci_disable_pcie_error_reporting(pdev);
|
|
|
}
|
|
@@ -176,9 +884,17 @@ static int __init ice_module_init(void)
|
|
|
pr_info("%s - version %s\n", ice_driver_string, ice_drv_ver);
|
|
|
pr_info("%s\n", ice_copyright);
|
|
|
|
|
|
+ ice_wq = alloc_ordered_workqueue("%s", WQ_MEM_RECLAIM, KBUILD_MODNAME);
|
|
|
+ if (!ice_wq) {
|
|
|
+ pr_err("Failed to create workqueue\n");
|
|
|
+ return -ENOMEM;
|
|
|
+ }
|
|
|
+
|
|
|
status = pci_register_driver(&ice_driver);
|
|
|
- if (status)
|
|
|
+ if (status) {
|
|
|
pr_err("failed to register pci driver, err %d\n", status);
|
|
|
+ destroy_workqueue(ice_wq);
|
|
|
+ }
|
|
|
|
|
|
return status;
|
|
|
}
|
|
@@ -193,6 +909,7 @@ module_init(ice_module_init);
|
|
|
static void __exit ice_module_exit(void)
|
|
|
{
|
|
|
pci_unregister_driver(&ice_driver);
|
|
|
+ destroy_workqueue(ice_wq);
|
|
|
pr_info("module unloaded\n");
|
|
|
}
|
|
|
module_exit(ice_module_exit);
|