/******************** Doorbell Recovery *******************/ /* The doorbell recovery mechanism consists of a list of entries which represent * doorbelling entities (l2 queues, roce sq/rq/cqs, the slowpath spq, etc). Each * entity needs to register with the mechanism and provide the parameters * describing it's doorbell, including a location where last used doorbell data * can be found. The doorbell execute function will traverse the list and * doorbell all of the registered entries.
*/ struct qed_db_recovery_entry { struct list_head list_entry; void __iomem *db_addr; void *db_data; enum qed_db_rec_width db_width; enum qed_db_rec_space db_space;
u8 hwfn_idx;
};
/* Make sure doorbell address is within the doorbell bar */ if (db_addr < cdev->doorbells ||
(u8 __iomem *)db_addr + width >
(u8 __iomem *)cdev->doorbells + cdev->db_size) {
WARN(true, "Illegal doorbell address: %p. Legal range for doorbell addresses is [%p..%p]\n",
db_addr,
cdev->doorbells,
(u8 __iomem *)cdev->doorbells + cdev->db_size); returnfalse;
}
/* ake sure doorbell data pointer is not null */ if (!db_data) {
WARN(true, "Illegal doorbell data pointer: %p", db_data); returnfalse;
}
returntrue;
}
/* Find hwfn according to the doorbell address */ staticstruct qed_hwfn *qed_db_rec_find_hwfn(struct qed_dev *cdev, void __iomem *db_addr)
{ struct qed_hwfn *p_hwfn;
/* In CMT doorbell bar is split down the middle between engine 0 and enigne 1 */ if (cdev->num_hwfns > 1)
p_hwfn = db_addr < cdev->hwfns[1].doorbells ?
&cdev->hwfns[0] : &cdev->hwfns[1]; else
p_hwfn = QED_LEADING_HWFN(cdev);
return p_hwfn;
}
/* Add a new entry to the doorbell recovery mechanism */ int qed_db_recovery_add(struct qed_dev *cdev, void __iomem *db_addr, void *db_data, enum qed_db_rec_width db_width, enum qed_db_rec_space db_space)
{ struct qed_db_recovery_entry *db_entry; struct qed_hwfn *p_hwfn;
/* Shortcircuit VFs, for now */ if (IS_VF(cdev)) {
DP_VERBOSE(cdev,
QED_MSG_IOV, "db recovery - skipping VF doorbell\n"); return 0;
}
/* Protect the list */
spin_lock_bh(&p_hwfn->db_recovery_info.lock);
list_add_tail(&db_entry->list_entry, &p_hwfn->db_recovery_info.list);
spin_unlock_bh(&p_hwfn->db_recovery_info.lock);
return 0;
}
/* Remove an entry from the doorbell recovery mechanism */ int qed_db_recovery_del(struct qed_dev *cdev, void __iomem *db_addr, void *db_data)
{ struct qed_db_recovery_entry *db_entry = NULL; struct qed_hwfn *p_hwfn; int rc = -EINVAL;
/* Shortcircuit VFs, for now */ if (IS_VF(cdev)) {
DP_VERBOSE(cdev,
QED_MSG_IOV, "db recovery - skipping VF doorbell\n"); return 0;
}
/* Protect the list */
spin_lock_bh(&p_hwfn->db_recovery_info.lock);
list_for_each_entry(db_entry,
&p_hwfn->db_recovery_info.list, list_entry) { /* search according to db_data addr since db_addr is not unique (roce) */ if (db_entry->db_data == db_data) {
qed_db_recovery_dp_entry(p_hwfn, db_entry, "Deleting");
list_del(&db_entry->list_entry);
rc = 0; break;
}
}
spin_unlock_bh(&p_hwfn->db_recovery_info.lock);
if (rc == -EINVAL)
DP_NOTICE(p_hwfn, "Failed to find element in list. Key (db_data addr) was %p. db_addr was %p\n",
db_data, db_addr); else
kfree(db_entry);
return rc;
}
/* Initialize the doorbell recovery mechanism */ staticint qed_db_recovery_setup(struct qed_hwfn *p_hwfn)
{
DP_VERBOSE(p_hwfn, QED_MSG_SPQ, "Setting up db recovery\n");
/* Make sure db_size was set in cdev */ if (!p_hwfn->cdev->db_size) {
DP_ERR(p_hwfn->cdev, "db_size not set\n"); return -EINVAL;
}
DP_VERBOSE(p_hwfn, QED_MSG_SPQ, "Tearing down db recovery\n"); if (!list_empty(&p_hwfn->db_recovery_info.list)) {
DP_VERBOSE(p_hwfn,
QED_MSG_SPQ, "Doorbell Recovery teardown found the doorbell recovery list was not empty (Expected in disorderly driver unload (e.g. recovery) otherwise this probably means some flow forgot to db_recovery_del). Prepare to purge doorbell recovery list...\n"); while (!list_empty(&p_hwfn->db_recovery_info.list)) {
db_entry =
list_first_entry(&p_hwfn->db_recovery_info.list, struct qed_db_recovery_entry,
list_entry);
qed_db_recovery_dp_entry(p_hwfn, db_entry, "Purging");
list_del(&db_entry->list_entry);
kfree(db_entry);
}
}
p_hwfn->db_recovery_info.db_recovery_counter = 0;
}
/* Ring the doorbell of a single doorbell recovery entry */ staticvoid qed_db_recovery_ring(struct qed_hwfn *p_hwfn, struct qed_db_recovery_entry *db_entry)
{ /* Print according to width */ if (db_entry->db_width == DB_REC_WIDTH_32B) {
DP_VERBOSE(p_hwfn, QED_MSG_SPQ, "ringing doorbell address %p data %x\n",
db_entry->db_addr,
*(u32 *)db_entry->db_data);
} else {
DP_VERBOSE(p_hwfn, QED_MSG_SPQ, "ringing doorbell address %p data %llx\n",
db_entry->db_addr,
*(u64 *)(db_entry->db_data));
}
/* Sanity */ if (!qed_db_rec_sanity(p_hwfn->cdev, db_entry->db_addr,
db_entry->db_width, db_entry->db_data)) return;
/* Flush the write combined buffer. Since there are multiple doorbelling * entities using the same address, if we don't flush, a transaction * could be lost.
*/
wmb();
/* Ring the doorbell */ if (db_entry->db_width == DB_REC_WIDTH_32B)
DIRECT_REG_WR(db_entry->db_addr,
*(u32 *)(db_entry->db_data)); else
DIRECT_REG_WR64(db_entry->db_addr,
*(u64 *)(db_entry->db_data));
/* Flush the write combined buffer. Next doorbell may come from a * different entity to the same address...
*/
wmb();
}
/* Traverse the doorbell recovery entry list and ring all the doorbells */ void qed_db_recovery_execute(struct qed_hwfn *p_hwfn)
{ struct qed_db_recovery_entry *db_entry = NULL;
DP_NOTICE(p_hwfn, "Executing doorbell recovery. Counter was %d\n",
p_hwfn->db_recovery_info.db_recovery_counter);
/* Track amount of times recovery was executed */
p_hwfn->db_recovery_info.db_recovery_counter++;
/* Protect the list */
spin_lock_bh(&p_hwfn->db_recovery_info.lock);
list_for_each_entry(db_entry,
&p_hwfn->db_recovery_info.list, list_entry)
qed_db_recovery_ring(p_hwfn, db_entry);
spin_unlock_bh(&p_hwfn->db_recovery_info.lock);
}
/******************** Doorbell Recovery end ****************/
/* Array of filters arrays: * "num_ppfid" elements of filters banks, where each is an array of * "NIG_REG_LLH_FUNC_FILTER_EN_SIZE" filters.
*/ struct qed_llh_filter_info **pp_filters;
};
if (ppfid >= p_llh_info->num_ppfid) {
DP_NOTICE(cdev, "LLH shadow [%s]: using ppfid %d while only %d ppfids are available\n",
action, ppfid, p_llh_info->num_ppfid); return -EINVAL;
}
if (filter_idx >= NIG_REG_LLH_FUNC_FILTER_EN_SIZE) {
DP_NOTICE(cdev, "LLH shadow [%s]: using filter_idx %d while only %d filters are available\n",
action, filter_idx, NIG_REG_LLH_FUNC_FILTER_EN_SIZE); return -EINVAL;
}
staticint
qed_llh_shadow_add_filter(struct qed_dev *cdev,
u8 ppfid, enum qed_llh_filter_type type, union qed_llh_filter *p_filter,
u8 *p_filter_idx, u32 *p_ref_cnt)
{ int rc;
/* Check if the same filter already exist */
rc = qed_llh_shadow_search_filter(cdev, ppfid, p_filter, p_filter_idx); if (rc) return rc;
/* Find a new entry in case of a new filter */ if (*p_filter_idx == QED_LLH_INVALID_FILTER_IDX) {
rc = qed_llh_shadow_get_free_idx(cdev, ppfid, p_filter_idx); if (rc) return rc;
}
/* No free entry was found */ if (*p_filter_idx == QED_LLH_INVALID_FILTER_IDX) {
DP_NOTICE(cdev, "Failed to find an empty LLH filter to utilize [ppfid %d]\n",
ppfid); return -EINVAL;
}
rc = qed_llh_shadow_sanity(cdev, ppfid, filter_idx, "remove"); if (rc) return rc;
p_filters = p_llh_info->pp_filters[ppfid]; if (!p_filters[filter_idx].ref_cnt) {
DP_NOTICE(cdev, "LLH shadow: trying to remove a filter with ref_cnt=0\n"); return -EINVAL;
}
*p_ref_cnt = --p_filters[filter_idx].ref_cnt; if (!p_filters[filter_idx].ref_cnt)
memset(&p_filters[filter_idx],
0, sizeof(p_filters[filter_idx]));
return 0;
}
staticint
qed_llh_shadow_remove_filter(struct qed_dev *cdev,
u8 ppfid, union qed_llh_filter *p_filter,
u8 *p_filter_idx, u32 *p_ref_cnt)
{ int rc;
rc = qed_llh_shadow_search_filter(cdev, ppfid, p_filter, p_filter_idx); if (rc) return rc;
/* No matching filter was found */ if (*p_filter_idx == QED_LLH_INVALID_FILTER_IDX) {
DP_NOTICE(cdev, "Failed to find a filter in the LLH shadow\n"); return -EINVAL;
}
if (ppfid >= p_llh_info->num_ppfid) {
DP_NOTICE(cdev, "ppfid %d is not valid, available indices are 0..%d\n",
ppfid, p_llh_info->num_ppfid - 1);
*p_abs_ppfid = 0; return -EINVAL;
}
rc = qed_mcp_get_engine_config(p_hwfn, p_ptt); if (rc != 0 && rc != -EOPNOTSUPP) {
DP_NOTICE(p_hwfn, "Failed to get the engine affinity configuration\n"); return rc;
}
/* RoCE PF is bound to a single engine */ if (QED_IS_ROCE_PERSONALITY(p_hwfn)) {
eng = cdev->fir_affin ? QED_ENG1 : QED_ENG0;
rc = qed_llh_set_roce_affinity(cdev, eng); if (rc) {
DP_NOTICE(cdev, "Failed to set the RoCE engine affinity\n"); return rc;
}
DP_VERBOSE(cdev,
QED_MSG_SP, "LLH: Set the engine affinity of RoCE packets as %d\n",
eng);
}
/* Storage PF is bound to a single engine while L2 PF uses both */ if (QED_IS_FCOE_PERSONALITY(p_hwfn) || QED_IS_ISCSI_PERSONALITY(p_hwfn) ||
QED_IS_NVMETCP_PERSONALITY(p_hwfn))
eng = cdev->fir_affin ? QED_ENG1 : QED_ENG0; else/* L2_PERSONALITY */
eng = QED_BOTH_ENG;
for (ppfid = 0; ppfid < cdev->p_llh_info->num_ppfid; ppfid++) {
rc = qed_llh_set_ppfid_affinity(cdev, ppfid, eng); if (rc) {
DP_NOTICE(cdev, "Failed to set the engine affinity of ppfid %d\n",
ppfid); return rc;
}
}
DP_VERBOSE(cdev, QED_MSG_SP, "LLH: Set the engine affinity of non-RoCE packets as %d\n",
eng);
if (test_bit(QED_MF_LLH_MAC_CLSS, &cdev->mf_bits) &&
!QED_IS_FCOE_PERSONALITY(p_hwfn)) {
rc = qed_llh_add_mac_filter(cdev, 0,
p_hwfn->hw_info.hw_mac_addr); if (rc)
DP_NOTICE(cdev, "Failed to add an LLH filter with the primary MAC\n");
}
if (QED_IS_CMT(cdev)) {
rc = qed_llh_set_engine_affin(p_hwfn, p_ptt); if (rc) return rc;
}
/* The iWARP affinity is set as the affinity of ppfid 0 */ if (!ppfid && QED_IS_IWARP_PERSONALITY(p_hwfn))
cdev->iwarp_affin = (eng == QED_ENG1) ? 1 : 0;
out:
qed_ptt_release(p_hwfn, p_ptt);
/* The NIG/LLH registers that are accessed in this function have only 16 * rows which are exposed to a PF. I.e. only the 16 filters of its * default ppfid. Accessing filters of other ppfids requires pretending * to another PFs. * The calculation of PPFID->PFID in AH is based on the relative index * of a PF on its port. * For BB the pfid is actually the abs_ppfid.
*/ if (QED_IS_BB(p_hwfn->cdev))
pfid = abs_ppfid; else
pfid = abs_ppfid * p_hwfn->cdev->num_ports_in_engine +
MFW_PORT(p_hwfn);
/* Filter enable - should be done first when removing a filter */ if (!p_details->enable) {
qed_fid_pretend(p_hwfn, p_ptt,
pfid << PXP_PRETEND_CONCRETE_FID_PFID_SHIFT);
/* Filter enable - should be done last when adding a filter */ if (p_details->enable) {
addr = NIG_REG_LLH_FUNC_FILTER_EN + filter_idx * 0x4;
qed_wr(p_hwfn, p_ptt, addr, p_details->enable);
}
rc = qed_llh_abs_ppfid(cdev, ppfid, &abs_ppfid); if (rc) goto err;
/* Configure the LLH only in case of a new the filter */ if (ref_cnt == 1) {
rc = qed_llh_protocol_filter_to_hilo(cdev, type,
source_port_or_eth_type,
dest_port, &high, &low); if (rc) goto err;
rc = qed_llh_abs_ppfid(cdev, ppfid, &abs_ppfid); if (rc) goto err;
/* Remove from the LLH in case the filter is not in use */ if (!ref_cnt) {
rc = qed_llh_remove_filter(p_hwfn, p_ptt, abs_ppfid,
filter_idx); if (rc) goto err;
}
DP_VERBOSE(cdev,
QED_MSG_SP, "LLH: Removed MAC filter [%pM] from ppfid %hhd [abs %hhd] at idx %hhd [ref_cnt %d]\n",
mac_addr, ppfid, abs_ppfid, filter_idx, ref_cnt);
goto out;
err: DP_NOTICE(cdev, "LLH: Failed to remove MAC filter [%pM] from ppfid %hhd\n",
mac_addr, ppfid);
out:
qed_ptt_release(p_hwfn, p_ptt);
}
rc = qed_llh_abs_ppfid(cdev, ppfid, &abs_ppfid); if (rc) goto err;
/* Remove from the LLH in case the filter is not in use */ if (!ref_cnt) {
rc = qed_llh_remove_filter(p_hwfn, p_ptt, abs_ppfid,
filter_idx); if (rc) goto err;
}
if (IS_VF(p_hwfn->cdev)) return qed_vf_hw_bar_size(p_hwfn, bar_id);
val = qed_rd(p_hwfn, p_ptt, bar_reg); if (val) return 1 << (val + 15);
/* Old MFW initialized above registered only conditionally */ if (p_hwfn->cdev->num_hwfns > 1) {
DP_INFO(p_hwfn, "BAR size not configured. Assuming BAR size of 256kB for GRC and 512kB for DB\n"); return BAR_ID_0 ? 256 * 1024 : 512 * 1024;
} else {
DP_INFO(p_hwfn, "BAR size not configured. Assuming BAR size of 512kB for GRC and 512kB for DB\n"); return 512 * 1024;
}
}
/* all vports participate in weighted fair queueing */ for (i = 0; i < qed_init_qm_get_num_vports(p_hwfn); i++)
qm_info->qm_vport_params[i].wfq = 1;
}
/* initialize qm port params */ staticvoid qed_init_qm_port_params(struct qed_hwfn *p_hwfn)
{ /* Initialize qm port parameters */
u8 i, active_phys_tcs, num_ports = p_hwfn->cdev->num_ports_in_engine; struct qed_dev *cdev = p_hwfn->cdev;
/* indicate how ooo and high pri traffic is dealt with */
active_phys_tcs = num_ports == MAX_NUM_PORTS_K2 ?
ACTIVE_TCS_BMAP_4PORT_K2 :
ACTIVE_TCS_BMAP;
for (i = 0; i < num_ports; i++) { struct init_qm_port_params *p_qm_port =
&p_hwfn->qm_info.qm_port_params[i];
u16 pbf_max_cmd_lines;
/* Reset the params which must be reset for qm init. QM init may be called as * a result of flows other than driver load (e.g. dcbx renegotiation). Other * params may be affected by the init but would simply recalculate to the same * values. The allocations made for QM init, ports, vports, pqs and vfqs are not * affected as these amounts stay the same.
*/ staticvoid qed_init_qm_reset_params(struct qed_hwfn *p_hwfn)
{ struct qed_qm_info *qm_info = &p_hwfn->qm_info;
/* initialize a single pq and manage qm_info resources accounting. * The pq_init_flags param determines whether the PQ is rate limited * (for VF or PF) and whether a new vport is allocated to the pq or not * (i.e. vport will be shared).
*/
/* get pq index according to PQ_FLAGS */ static u16 *qed_init_qm_get_idx_from_flags(struct qed_hwfn *p_hwfn, unsignedlong pq_flags)
{ struct qed_qm_info *qm_info = &p_hwfn->qm_info;
/* Can't have multiple flags set here */ if (bitmap_weight(&pq_flags, sizeof(pq_flags) * BITS_PER_BYTE) > 1) {
DP_ERR(p_hwfn, "requested multiple pq flags 0x%lx\n", pq_flags); goto err;
}
if (!(qed_get_pq_flags(p_hwfn) & pq_flags)) {
DP_ERR(p_hwfn, "pq flag 0x%lx is not set\n", pq_flags); goto err;
}
switch (pq_flags) { case PQ_FLAGS_RLS: return &qm_info->first_rl_pq; case PQ_FLAGS_MCOS: return &qm_info->first_mcos_pq; case PQ_FLAGS_LB: return &qm_info->pure_lb_pq; case PQ_FLAGS_OOO: return &qm_info->ooo_pq; case PQ_FLAGS_ACK: return &qm_info->pure_ack_pq; case PQ_FLAGS_OFLD: return &qm_info->first_ofld_pq; case PQ_FLAGS_LLT: return &qm_info->first_llt_pq; case PQ_FLAGS_VFS: return &qm_info->first_vf_pq; default: goto err;
}
err: return &qm_info->start_pq;
}
/* save pq index in qm info */ staticvoid qed_init_qm_set_idx(struct qed_hwfn *p_hwfn,
u32 pq_flags, u16 pq_val)
{
u16 *base_pq_idx = qed_init_qm_get_idx_from_flags(p_hwfn, pq_flags);
/* get tx pq index, with the PQ TX base already set (ready for context init) */
u16 qed_get_cm_pq_idx(struct qed_hwfn *p_hwfn, u32 pq_flags)
{
u16 *base_pq_idx = qed_init_qm_get_idx_from_flags(p_hwfn, pq_flags);
/* compare values of getters against resources amounts */ staticint qed_init_qm_sanity(struct qed_hwfn *p_hwfn)
{ if (qed_init_qm_get_num_vports(p_hwfn) > RESC_NUM(p_hwfn, QED_VPORT)) {
DP_ERR(p_hwfn, "requested amount of vports exceeds resource\n"); return -EINVAL;
}
if (qed_init_qm_get_num_pqs(p_hwfn) <= RESC_NUM(p_hwfn, QED_PQ)) return 0;
if (QED_IS_ROCE_PERSONALITY(p_hwfn)) {
p_hwfn->hw_info.multi_tc_roce_en = false;
DP_NOTICE(p_hwfn, "multi-tc roce was disabled to reduce requested amount of pqs\n"); if (qed_init_qm_get_num_pqs(p_hwfn) <= RESC_NUM(p_hwfn, QED_PQ)) return 0;
}
DP_ERR(p_hwfn, "requested amount of pqs exceeds resource\n"); return -EINVAL;
}
/* display all that init */
qed_dp_init_qm_params(p_hwfn);
}
/* This function reconfigures the QM pf on the fly. * For this purpose we: * 1. reconfigure the QM database * 2. set new values to runtime array * 3. send an sdm_qm_cmd through the rbc interface to stop the QM * 4. activate init tool in QM_PF stage * 5. send an sdm_qm_cmd through rbc interface to release the QM
*/ int qed_qm_reconf(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{ struct qed_qm_info *qm_info = &p_hwfn->qm_info; bool b_rc; int rc;
/* initialize qed's qm data structure */
qed_init_qm_info(p_hwfn);
/* Initialize the doorbell recovery mechanism */
rc = qed_db_recovery_setup(p_hwfn); if (rc) goto alloc_err;
/* First allocate the context manager structure */
rc = qed_cxt_mngr_alloc(p_hwfn); if (rc) goto alloc_err;
/* Set the HW cid/tid numbers (in the contest manager) * Must be done prior to any further computations.
*/
rc = qed_cxt_set_pf_params(p_hwfn, RDMA_MAX_TIDS); if (rc) goto alloc_err;
rc = qed_alloc_qm_data(p_hwfn); if (rc) goto alloc_err;
/* init qm info */
qed_init_qm_info(p_hwfn);
/* Compute the ILT client partition */
rc = qed_cxt_cfg_ilt_compute(p_hwfn, &line_count); if (rc) {
DP_NOTICE(p_hwfn, "too many ILT lines; re-computing with less lines\n"); /* In case there are not enough ILT lines we reduce the * number of RDMA tasks and re-compute.
*/
excess_tasks =
qed_cxt_cfg_ilt_compute_excess(p_hwfn, line_count); if (!excess_tasks) goto alloc_err;
if (QED_IS_ROCE_PERSONALITY(p_hwfn))
rdma_proto = PROTOCOLID_ROCE; else
rdma_proto = PROTOCOLID_IWARP;
num_cons = qed_cxt_get_proto_cid_count(p_hwfn,
rdma_proto,
NULL) * 2; /* EQ should be able to get events from all SRQ's * at the same time
*/
n_eqes += num_cons + 2 * MAX_NUM_VFS_BB + n_srq;
} elseif (p_hwfn->hw_info.personality == QED_PCI_ISCSI ||
p_hwfn->hw_info.personality == QED_PCI_NVMETCP) {
num_cons =
qed_cxt_get_proto_cid_count(p_hwfn,
PROTOCOLID_TCP_ULP,
NULL);
n_eqes += 2 * num_cons;
}
if (n_eqes > 0xFFFF) {
DP_ERR(p_hwfn, "Cannot allocate 0x%x EQ elements. The maximum of a u16 chain is 0x%x\n",
n_eqes, 0xFFFF); goto alloc_no_mem;
}
rc = qed_eq_alloc(p_hwfn, (u16)n_eqes); if (rc) goto alloc_err;
rc = qed_consq_alloc(p_hwfn); if (rc) goto alloc_err;
rc = qed_l2_alloc(p_hwfn); if (rc) goto alloc_err;
#ifdef CONFIG_QED_LL2 if (p_hwfn->using_ll2) {
rc = qed_ll2_alloc(p_hwfn); if (rc) goto alloc_err;
} #endif
if (p_hwfn->hw_info.personality == QED_PCI_FCOE) {
rc = qed_fcoe_alloc(p_hwfn); if (rc) goto alloc_err;
}
if (p_hwfn->hw_info.personality == QED_PCI_ISCSI) {
rc = qed_iscsi_alloc(p_hwfn); if (rc) goto alloc_err;
rc = qed_ooo_alloc(p_hwfn); if (rc) goto alloc_err;
}
if (p_hwfn->hw_info.personality == QED_PCI_NVMETCP) {
rc = qed_nvmetcp_alloc(p_hwfn); if (rc) goto alloc_err;
rc = qed_ooo_alloc(p_hwfn); if (rc) goto alloc_err;
}
if (QED_IS_RDMA_PERSONALITY(p_hwfn)) {
rc = qed_rdma_info_alloc(p_hwfn); if (rc) goto alloc_err;
}
/* DMA info initialization */
rc = qed_dmae_info_alloc(p_hwfn); if (rc) goto alloc_err;
/* Make sure notification is not set before initiating final cleanup */ if (REG_RD(p_hwfn, addr)) {
DP_NOTICE(p_hwfn, "Unexpected; Found final cleanup notification before initiating final cleanup\n");
REG_WR(p_hwfn, addr, 0);
}
DP_VERBOSE(p_hwfn, QED_MSG_IOV, "Sending final cleanup for PFVF[%d] [Command %08x]\n",
id, command);
if (p_hwfn->cdev->num_hwfns > 1)
hw_mode |= 1 << MODE_100G;
p_hwfn->hw_info.hw_mode = hw_mode;
DP_VERBOSE(p_hwfn, (NETIF_MSG_PROBE | NETIF_MSG_IFUP), "Configuring function for hw_mode: 0x%08x\n",
p_hwfn->hw_info.hw_mode);
return 0;
}
/* Init run time data for all PFs on an engine. */ staticvoid qed_init_cau_rt_data(struct qed_dev *cdev)
{
u32 offset = CAU_REG_SB_VAR_MEMORY_RT_OFFSET; int i, igu_sb_id;
val = qed_rd(p_hwfn, p_ptt, PSWRQ2_REG_WR_MBS0); switch (val) { case 0:
wr_mbs = 128; break; case 1:
wr_mbs = 256; break; case 2:
wr_mbs = 512; break; default:
DP_INFO(p_hwfn, "Unexpected value of PSWRQ2_REG_WR_MBS0 [0x%x]. Avoid configuring PGLUE_B_REG_CACHE_LINE_SIZE.\n",
val); return;
}
cache_line_size = min_t(u32, L1_CACHE_BYTES, wr_mbs); switch (cache_line_size) { case 32:
val = 0; break; case 64:
val = 1; break; case 128:
val = 2; break; case 256:
val = 3; break; default:
DP_INFO(p_hwfn, "Unexpected value of cache line size [0x%x]. Avoid configuring PGLUE_B_REG_CACHE_LINE_SIZE.\n",
cache_line_size);
}
if (wr_mbs < L1_CACHE_BYTES)
DP_INFO(p_hwfn, "The cache line size for padding is suboptimal for performance [OS cache line size 0x%x, wr mbs 0x%x]\n",
L1_CACHE_BYTES, wr_mbs);
params = kzalloc(sizeof(*params), GFP_KERNEL); if (!params) {
DP_NOTICE(p_hwfn->cdev, "Failed to allocate common init params\n");
return -ENOMEM;
}
qed_init_cau_rt_data(cdev);
/* Program GTT windows */
qed_gtt_init(p_hwfn);
if (p_hwfn->mcp_info) { if (p_hwfn->mcp_info->func_info.bandwidth_max)
qm_info->pf_rl_en = true; if (p_hwfn->mcp_info->func_info.bandwidth_min)
qm_info->pf_wfq_en = true;
}
/* Check that the normal and PWM sizes are valid */ if (db_bar_size < norm_regsize) {
DP_ERR(p_hwfn->cdev, "Doorbell BAR size 0x%x is too small (normal region is 0x%0x )\n",
db_bar_size, norm_regsize); return -EINVAL;
}
if (pwm_regsize < QED_MIN_PWM_REGION) {
DP_ERR(p_hwfn->cdev, "PWM region size 0x%0x is too small. Should be at least 0x%0x (Doorbell BAR size is 0x%x and normal region size is 0x%0x)\n",
pwm_regsize,
QED_MIN_PWM_REGION, db_bar_size, norm_regsize); return -EINVAL;
}
/* Calculate number of DPIs */
roce_edpm_mode = p_hwfn->pf_params.rdma_pf_params.roce_edpm_mode; if ((roce_edpm_mode == QED_ROCE_EDPM_MODE_ENABLE) ||
((roce_edpm_mode == QED_ROCE_EDPM_MODE_FORCE_ON))) { /* Either EDPM is mandatory, or we are attempting to allocate a * WID per CPU.
*/
n_cpus = num_present_cpus();
rc = qed_hw_init_dpi_size(p_hwfn, p_ptt, pwm_regsize, n_cpus);
}
cond = (rc && (roce_edpm_mode == QED_ROCE_EDPM_MODE_ENABLE)) ||
(roce_edpm_mode == QED_ROCE_EDPM_MODE_DISABLE); if (cond || p_hwfn->dcbx_no_edpm) { /* Either EDPM is disabled from user configuration, or it is * disabled via DCBx, or it is not mandatory and we failed to * allocated a WID per CPU.
*/
n_cpus = 1;
rc = qed_hw_init_dpi_size(p_hwfn, p_ptt, pwm_regsize, n_cpus);
if (rc) {
DP_ERR(p_hwfn, "Failed to allocate enough DPIs. Allocated %d but the current minimum is %d.\n",
p_hwfn->dpi_count,
p_hwfn->pf_params.rdma_pf_params.min_dpis); return -EINVAL;
}
p_hwfn->dpi_start_offset = norm_regsize;
/* DEMS size is configured log2 of DWORDs, hence the division by 4 */
pf_dems_shift = ilog2(QED_PF_DEMS_SIZE / 4);
qed_wr(p_hwfn, p_ptt, DORQ_REG_PF_ICID_BIT_SHIFT_NORM, pf_dems_shift);
qed_wr(p_hwfn, p_ptt, DORQ_REG_PF_MIN_ADDR_REG1, min_addr_reg1);
return 0;
}
staticint qed_hw_init_port(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt, int hw_mode)
{ int rc = 0;
/* In CMT the gate should be cleared by the 2nd hwfn */ if (!QED_IS_CMT(p_hwfn->cdev) || !IS_LEAD_HWFN(p_hwfn))
STORE_RT_REG(p_hwfn, NIG_REG_BRB_GATE_DNTFWD_PORT_RT_OFFSET, 0);
/* Pure runtime initializations - directly to the HW */
qed_int_igu_init_pure_rt(p_hwfn, p_ptt, true, true);
rc = qed_hw_init_pf_doorbell_bar(p_hwfn, p_ptt); if (rc) return rc;
/* Use the leading hwfn since in CMT only NIG #0 is operational */ if (IS_LEAD_HWFN(p_hwfn)) {
rc = qed_llh_hw_init_pf(p_hwfn, p_ptt); if (rc) return rc;
}
if (b_hw_start) { /* enable interrupts */
qed_int_igu_enable(p_hwfn, p_ptt, int_mode);
/* Configure the PF's internal FID_enable for master transactions */
qed_wr(p_hwfn, p_ptt, PGLUE_B_REG_INTERNAL_PFID_ENABLE_MASTER, set_val);
/* Wait until value is set - try for 1 second every 50us */ for (delay_idx = 0; delay_idx < 20000; delay_idx++) {
val = qed_rd(p_hwfn, p_ptt,
PGLUE_B_REG_INTERNAL_PFID_ENABLE_MASTER); if (val == set_val) break;
usleep_range(50, 60);
}
if (val != set_val) {
DP_NOTICE(p_hwfn, "PFID_ENABLE_MASTER wasn't changed after a second\n"); return -EAGAIN;
}
/* Clean up chip from previous driver if such remains exist. * This is not needed when the PF is the first one on the * engine, since afterwards we are going to init the FW.
*/ if (load_code != FW_MSG_CODE_DRV_LOAD_ENGINE) {
rc = qed_final_cleanup(p_hwfn, p_hwfn->p_main_ptt,
p_hwfn->rel_pf_id, false); if (rc) {
qed_hw_err_notify(p_hwfn, p_hwfn->p_main_ptt,
QED_HW_ERR_RAMROD_FAIL, "Final cleanup failed\n"); goto load_err;
}
}
/* Log and clear previous pglue_b errors if such exist */
qed_pglueb_rbc_attn_handler(p_hwfn, p_hwfn->p_main_ptt, true);
/* Enable the PF's internal FID_enable in the PXP */
rc = qed_pglueb_set_pfid_enable(p_hwfn, p_hwfn->p_main_ptt, true); if (rc) goto load_err;
/* Clear the pglue_b was_error indication. * In E4 it must be done after the BME and the internal * FID_enable for the PF are set, since VDMs may cause the * indication to be set again.
*/
qed_pglueb_clear_err(p_hwfn, p_hwfn->p_main_ptt);
for (i = 0; i < QED_HW_STOP_RETRY_LIMIT; i++) { if ((!qed_rd(p_hwfn, p_ptt,
TM_REG_PF_SCAN_ACTIVE_CONN)) &&
(!qed_rd(p_hwfn, p_ptt, TM_REG_PF_SCAN_ACTIVE_TASK))) break;
/* Dependent on number of connection/tasks, possibly * 1ms sleep is required between polls
*/
usleep_range(1000, 2000);
}
if (i < QED_HW_STOP_RETRY_LIMIT) return;
DP_NOTICE(p_hwfn, "Timers linear scans are not over [Connection %02x Tasks %02x]\n",
(u8)qed_rd(p_hwfn, p_ptt, TM_REG_PF_SCAN_ACTIVE_CONN),
(u8)qed_rd(p_hwfn, p_ptt, TM_REG_PF_SCAN_ACTIVE_TASK));
}
void qed_hw_timers_stop_all(struct qed_dev *cdev)
{ int j;
/* mark the hw as uninitialized... */
p_hwfn->hw_init_done = false;
/* Send unload command to MCP */ if (!cdev->recov_in_prog) {
rc = qed_mcp_unload_req(p_hwfn, p_ptt); if (rc) {
DP_NOTICE(p_hwfn, "Failed sending a UNLOAD_REQ command. rc = %d.\n",
rc);
rc2 = -EINVAL;
}
}
qed_slowpath_irq_sync(p_hwfn);
/* After this point no MFW attentions are expected, e.g. prevent * race between pf stop and dcbx pf update.
*/
rc = qed_sp_pf_stop(p_hwfn); if (rc) {
DP_NOTICE(p_hwfn, "Failed to close PF against FW [rc = %d]. Continue to stop HW to prevent illegal host access by the device.\n",
rc);
rc2 = -EINVAL;
}
/* Clear the PF's internal FID_enable in the PXP. * In CMT this should only be done for first hw-function, and * only after all transactions have stopped for all active * hw-functions.
*/
rc = qed_pglueb_set_pfid_enable(p_hwfn, p_ptt, false); if (rc) {
DP_NOTICE(p_hwfn, "qed_pglueb_set_pfid_enable() failed. rc = %d.\n",
rc);
rc2 = -EINVAL;
}
}
return rc2;
}
int qed_hw_stop_fastpath(struct qed_dev *cdev)
{ int j;
if (IS_ENABLED(CONFIG_QED_RDMA) &&
QED_IS_RDMA_PERSONALITY(p_hwfn)) { /* Roce CNQ each requires: 1 status block + 1 CNQ. We divide * the status blocks equally between L2 / RoCE but with * consideration as to how many l2 queues / cnqs we have.
*/
feat_num[QED_RDMA_CNQ] =
min_t(u32, sb_cnt.cnt / 2,
RESC_NUM(p_hwfn, QED_RDMA_CNQ_RAM));
constchar *qed_hw_get_resc_name(enum qed_resources res_id)
{ switch (res_id) { case QED_L2_QUEUE: return"L2_QUEUE"; case QED_VPORT: return"VPORT"; case QED_RSS_ENG: return"RSS_ENG"; case QED_PQ: return"PQ"; case QED_RL: return"RL"; case QED_MAC: return"MAC"; case QED_VLAN: return"VLAN"; case QED_RDMA_CNQ_RAM: return"RDMA_CNQ_RAM"; case QED_ILT: return"ILT"; case QED_LL2_RAM_QUEUE: return"LL2_RAM_QUEUE"; case QED_LL2_CTX_QUEUE: return"LL2_CTX_QUEUE"; case QED_CMDQS_CQS: return"CMDQS_CQS"; case QED_RDMA_STATS_QUEUE: return"RDMA_STATS_QUEUE"; case QED_BDQ: return"BDQ"; case QED_SB: return"SB"; default: return"UNKNOWN_RESOURCE";
}
}
rc = qed_mcp_set_resc_max_val(p_hwfn, p_ptt, res_id,
resc_max_val, p_mcp_resp); if (rc) {
DP_NOTICE(p_hwfn, "MFW response failure for a max value setting of resource %d [%s]\n",
res_id, qed_hw_get_resc_name(res_id)); return rc;
}
if (*p_mcp_resp != FW_MSG_CODE_RESOURCE_ALLOC_OK)
DP_INFO(p_hwfn, "Failed to set the max value of resource %d [%s]. mcp_resp = 0x%08x.\n",
res_id, qed_hw_get_resc_name(res_id), *p_mcp_resp);
for (res_id = 0; res_id < QED_MAX_RESC; res_id++) { switch (res_id) { case QED_LL2_RAM_QUEUE:
resc_max_val = MAX_NUM_LL2_RX_RAM_QUEUES; break; case QED_LL2_CTX_QUEUE:
resc_max_val = MAX_NUM_LL2_RX_CTX_QUEUES; break; case QED_RDMA_CNQ_RAM: /* No need for a case for QED_CMDQS_CQS since * CNQ/CMDQS are the same resource.
*/
resc_max_val = NUM_OF_GLOBAL_QUEUES; break; case QED_RDMA_STATS_QUEUE:
resc_max_val =
NUM_OF_RDMA_STATISTIC_COUNTERS(p_hwfn->cdev); break; case QED_BDQ:
resc_max_val = BDQ_NUM_RESOURCES; break; default: continue;
}
/* There's no point to continue to the next resource if the * command is not supported by the MFW. * We do continue if the command is supported but the resource * is unknown to the MFW. Such a resource will be later * configured with the default allocation values.
*/ if (mcp_resp == FW_MSG_CODE_UNSUPPORTED) return -EINVAL;
}
switch (res_id) { case QED_L2_QUEUE:
*p_resc_num = NUM_OF_L2_QUEUES(cdev) / num_funcs; break; case QED_VPORT:
*p_resc_num = NUM_OF_VPORTS(cdev) / num_funcs; break; case QED_RSS_ENG:
*p_resc_num = NUM_OF_RSS_ENGINES(cdev) / num_funcs; break; case QED_PQ:
*p_resc_num = NUM_OF_QM_TX_QUEUES(cdev) / num_funcs;
*p_resc_num &= ~0x7; /* The granularity of the PQs is 8 */ break; case QED_RL:
*p_resc_num = NUM_OF_QM_GLOBAL_RLS(cdev) / num_funcs; break; case QED_MAC: case QED_VLAN: /* Each VFC resource can accommodate both a MAC and a VLAN */
*p_resc_num = ETH_NUM_MAC_FILTERS / num_funcs; break; case QED_ILT:
*p_resc_num = NUM_OF_PXP_ILT_RECORDS(cdev) / num_funcs; break; case QED_LL2_RAM_QUEUE:
*p_resc_num = MAX_NUM_LL2_RX_RAM_QUEUES / num_funcs; break; case QED_LL2_CTX_QUEUE:
*p_resc_num = MAX_NUM_LL2_RX_CTX_QUEUES / num_funcs; break; case QED_RDMA_CNQ_RAM: case QED_CMDQS_CQS: /* CNQ/CMDQS are the same resource */
*p_resc_num = NUM_OF_GLOBAL_QUEUES / num_funcs; break; case QED_RDMA_STATS_QUEUE:
*p_resc_num = NUM_OF_RDMA_STATISTIC_COUNTERS(cdev) / num_funcs; break; case QED_BDQ: if (p_hwfn->hw_info.personality != QED_PCI_ISCSI &&
p_hwfn->hw_info.personality != QED_PCI_FCOE &&
p_hwfn->hw_info.personality != QED_PCI_NVMETCP)
*p_resc_num = 0; else
*p_resc_num = 1; break; case QED_SB: /* Since we want its value to reflect whether MFW supports * the new scheme, have a default of 0.
*/
*p_resc_num = 0; break; default: return -EINVAL;
}
rc = qed_hw_get_dflt_resc(p_hwfn, res_id, &dflt_resc_num,
&dflt_resc_start); if (rc) {
DP_ERR(p_hwfn, "Failed to get default amount for resource %d [%s]\n",
res_id, qed_hw_get_resc_name(res_id)); return rc;
}
rc = qed_mcp_get_resc_info(p_hwfn, p_hwfn->p_main_ptt, res_id,
&mcp_resp, p_resc_num, p_resc_start); if (rc) {
DP_NOTICE(p_hwfn, "MFW response failure for an allocation request for resource %d [%s]\n",
res_id, qed_hw_get_resc_name(res_id)); return rc;
}
/* Default driver values are applied in the following cases: * - The resource allocation MB command is not supported by the MFW * - There is an internal error in the MFW while processing the request * - The resource ID is unknown to the MFW
*/ if (mcp_resp != FW_MSG_CODE_RESOURCE_ALLOC_OK) {
DP_INFO(p_hwfn, "Failed to receive allocation info for resource %d [%s]. mcp_resp = 0x%x. Applying default values [%d,%d].\n",
res_id,
qed_hw_get_resc_name(res_id),
mcp_resp, dflt_resc_num, dflt_resc_start);
*p_resc_num = dflt_resc_num;
*p_resc_start = dflt_resc_start; goto out;
}
out: /* PQs have to divide by 8 [that's the HW granularity]. * Reduce number so it would fit.
*/ if ((res_id == QED_PQ) && ((*p_resc_num % 8) || (*p_resc_start % 8))) {
DP_INFO(p_hwfn, "PQs need to align by 8; Number %08x --> %08x, Start %08x --> %08x\n",
*p_resc_num,
(*p_resc_num) & ~0x7,
*p_resc_start, (*p_resc_start) & ~0x7);
*p_resc_num &= ~0x7;
*p_resc_start &= ~0x7;
}
return 0;
}
staticint qed_hw_set_resc_info(struct qed_hwfn *p_hwfn)
{ int rc;
u8 res_id;
for (res_id = 0; res_id < QED_MAX_RESC; res_id++) {
rc = __qed_hw_set_resc_info(p_hwfn, res_id); if (rc) return rc;
}
/* Calculation of BB/AH is different for native_ppfid_idx */ if (QED_IS_BB(cdev))
native_ppfid_idx = p_hwfn->rel_pf_id; else
native_ppfid_idx = p_hwfn->rel_pf_id /
cdev->num_ports_in_engine;
/* Setting the max values of the soft resources and the following * resources allocation queries should be atomic. Since several PFs can * run in parallel - a resource lock is needed. * If either the resource lock or resource set value commands are not * supported - skip the max values setting, release the lock if * needed, and proceed to the queries. Other failures, including a * failure to acquire the lock, will cause this function to fail.
*/
qed_mcp_resc_lock_default_init(&resc_lock_params, &resc_unlock_params,
QED_RESC_LOCK_RESC_ALLOC, false);
rc = qed_mcp_resc_lock(p_hwfn, p_ptt, &resc_lock_params); if (rc && rc != -EINVAL) { return rc;
} elseif (rc == -EINVAL) {
DP_INFO(p_hwfn, "Skip the max values setting of the soft resources since the resource lock is not supported by the MFW\n");
} elseif (!resc_lock_params.b_granted) {
DP_NOTICE(p_hwfn, "Failed to acquire the resource lock for the resource allocation commands\n"); return -EBUSY;
} else {
rc = qed_hw_set_soft_resc_size(p_hwfn, p_ptt); if (rc && rc != -EINVAL) {
DP_NOTICE(p_hwfn, "Failed to set the max values of the soft resources\n"); goto unlock_and_exit;
} elseif (rc == -EINVAL) {
DP_INFO(p_hwfn, "Skip the max values setting of the soft resources since it is not supported by the MFW\n");
rc = qed_mcp_resc_unlock(p_hwfn, p_ptt,
&resc_unlock_params); if (rc)
DP_INFO(p_hwfn, "Failed to release the resource lock for the resource allocation commands\n");
}
}
rc = qed_hw_set_resc_info(p_hwfn); if (rc) goto unlock_and_exit;
if (resc_lock_params.b_granted && !resc_unlock_params.b_released) {
rc = qed_mcp_resc_unlock(p_hwfn, p_ptt, &resc_unlock_params); if (rc)
DP_INFO(p_hwfn, "Failed to release the resource lock for the resource allocation commands\n");
}
/* PPFID bitmap */ if (IS_LEAD_HWFN(p_hwfn)) {
rc = qed_hw_get_ppfid_bitmap(p_hwfn, p_ptt); if (rc) return rc;
}
switch ((core_cfg & NVM_CFG1_GLOB_NETWORK_PORT_MODE_MASK) >>
NVM_CFG1_GLOB_NETWORK_PORT_MODE_OFFSET) { case NVM_CFG1_GLOB_NETWORK_PORT_MODE_BB_2X40G: case NVM_CFG1_GLOB_NETWORK_PORT_MODE_2X50G: case NVM_CFG1_GLOB_NETWORK_PORT_MODE_BB_1X100G: case NVM_CFG1_GLOB_NETWORK_PORT_MODE_4X10G_F: case NVM_CFG1_GLOB_NETWORK_PORT_MODE_BB_4X10G_E: case NVM_CFG1_GLOB_NETWORK_PORT_MODE_BB_4X20G: case NVM_CFG1_GLOB_NETWORK_PORT_MODE_1X40G: case NVM_CFG1_GLOB_NETWORK_PORT_MODE_2X25G: case NVM_CFG1_GLOB_NETWORK_PORT_MODE_2X10G: case NVM_CFG1_GLOB_NETWORK_PORT_MODE_1X25G: case NVM_CFG1_GLOB_NETWORK_PORT_MODE_4X25G: case NVM_CFG1_GLOB_NETWORK_PORT_MODE_AHP_2X50G_R1: case NVM_CFG1_GLOB_NETWORK_PORT_MODE_AHP_4X50G_R1: case NVM_CFG1_GLOB_NETWORK_PORT_MODE_AHP_1X100G_R2: case NVM_CFG1_GLOB_NETWORK_PORT_MODE_AHP_2X100G_R2: case NVM_CFG1_GLOB_NETWORK_PORT_MODE_AHP_1X100G_R4: break; default:
DP_NOTICE(p_hwfn, "Unknown port mode in 0x%08x\n", core_cfg); break;
}
DP_INFO(p_hwfn, "Multi function mode is 0x%lx\n",
cdev->mf_bits);
/* In CMT the PF is unknown when the GFS block processes the * packet. Therefore cannot use searcher as it has a per PF * database, and thus ARFS must be disabled. *
*/ if (QED_IS_CMT(cdev))
cdev->mf_bits |= BIT(QED_MF_DISABLE_ARFS);
}
DP_INFO(p_hwfn, "Multi function mode is 0x%lx\n",
p_hwfn->cdev->mf_bits);
/* Bit 0 of MISCS_REG_FUNCTION_HIDE indicates whether the bypass values * in the other bits are selected. * Bits 1-15 are for functions 1-15, respectively, and their value is * '0' only for enabled functions (function 0 always exists and * enabled). * In case of CMT, only the "even" functions are enabled, and thus the * number of functions for both hwfns is learnt from the same bits.
*/
reg_function_hide = qed_rd(p_hwfn, p_ptt, MISCS_REG_FUNCTION_HIDE);
/* Get the number of the enabled functions on the engine */
tmp = (reg_function_hide ^ 0xffffffff) & eng_mask; while (tmp) { if (tmp & 0x1)
num_funcs++;
tmp >>= 0x1;
}
/* Get the PF index within the enabled functions */
low_pfs_mask = (0x1 << p_hwfn->abs_pf_id) - 1;
tmp = reg_function_hide & eng_mask & low_pfs_mask; while (tmp) { if (tmp & 0x1)
enabled_func_idx--;
tmp >>= 0x1;
}
}
DP_VERBOSE(p_hwfn,
NETIF_MSG_PROBE, "PF [rel_id %d, abs_id %d] occupies index %d within the %d enabled functions on the engine\n",
p_hwfn->rel_pf_id,
p_hwfn->abs_pf_id,
p_hwfn->enabled_func_idx, p_hwfn->num_funcs_on_engine);
}
if (IS_VF(p_hwfn->cdev)) return qed_vf_hw_prepare(p_hwfn);
/* Validate that chip access is feasible */ if (REG_RD(p_hwfn, PXP_PF_ME_OPAQUE_ADDR) == 0xffffffff) {
DP_ERR(p_hwfn, "Reading the ME register returns all Fs; Preventing further chip access\n"); return -EINVAL;
}
get_function_id(p_hwfn);
/* Allocate PTT pool */
rc = qed_ptt_pool_alloc(p_hwfn); if (rc) goto err0;
/* Allocate the main PTT */
p_hwfn->p_main_ptt = qed_get_reserved_ptt(p_hwfn, RESERVED_PTT_MAIN);
/* First hwfn learns basic information, e.g., number of hwfns */ if (!p_hwfn->my_id) {
rc = qed_get_dev_info(p_hwfn, p_hwfn->p_main_ptt); if (rc) goto err1;
}
/* Read the device configuration information from the HW and SHMEM */
rc = qed_get_hw_info(p_hwfn, p_hwfn->p_main_ptt, personality); if (rc) {
DP_NOTICE(p_hwfn, "Failed to get HW information\n"); goto err2;
}
/* Sending a mailbox to the MFW should be done after qed_get_hw_info() * is called as it sets the ports number in an engine.
*/ if (IS_LEAD_HWFN(p_hwfn) && !cdev->recov_in_prog) {
rc = qed_mcp_initiate_pf_flr(p_hwfn, p_hwfn->p_main_ptt); if (rc)
DP_NOTICE(p_hwfn, "Failed to initiate PF FLR\n");
}
/* NVRAM info initialization and population */ if (IS_LEAD_HWFN(p_hwfn)) {
rc = qed_mcp_nvm_info_populate(p_hwfn); if (rc) {
DP_NOTICE(p_hwfn, "Failed to populate nvm info shadow\n"); goto err2;
}
}
/* Allocate the init RT array and initialize the init-ops engine */
rc = qed_init_alloc(p_hwfn); if (rc) goto err3;
return rc;
err3: if (IS_LEAD_HWFN(p_hwfn))
qed_mcp_nvm_info_free(p_hwfn);
err2: if (IS_LEAD_HWFN(p_hwfn))
qed_iov_free_hw_info(p_hwfn->cdev);
qed_mcp_free(p_hwfn);
err1:
qed_hw_hwfn_free(p_hwfn);
err0: return rc;
}
int qed_hw_prepare(struct qed_dev *cdev, int personality)
{ struct qed_hwfn *p_hwfn = QED_LEADING_HWFN(cdev); int rc;
/* Store the precompiled init data ptrs */ if (IS_PF(cdev))
qed_init_iro_array(cdev);
/* Initialize the first hwfn - will learn number of hwfns */
rc = qed_hw_prepare_single(p_hwfn,
cdev->regview,
cdev->doorbells,
cdev->db_phys_addr,
personality); if (rc) return rc;
personality = p_hwfn->hw_info.personality;
/* Initialize the rest of the hwfns */ if (cdev->num_hwfns > 1) { void __iomem *p_regview, *p_doorbell;
u64 db_phys_addr;
u32 offset;
/* adjust bar offset for second engine */
offset = qed_hw_bar_size(p_hwfn, p_hwfn->p_main_ptt,
BAR_ID_0) / 2;
p_regview = cdev->regview + offset;
/* prepare second hw function */
rc = qed_hw_prepare_single(&cdev->hwfns[1], p_regview,
p_doorbell, db_phys_addr,
personality);
/* in case of error, need to free the previously * initiliazed hwfn 0.
*/ if (rc) { if (IS_PF(cdev)) {
qed_init_free(p_hwfn);
qed_mcp_nvm_info_free(p_hwfn);
qed_mcp_free(p_hwfn);
qed_hw_hwfn_free(p_hwfn);
}
}
}
int qed_fw_l2_queue(struct qed_hwfn *p_hwfn, u16 src_id, u16 *dst_id)
{ if (src_id >= RESC_NUM(p_hwfn, QED_L2_QUEUE)) {
u16 min, max;
min = (u16)RESC_START(p_hwfn, QED_L2_QUEUE);
max = min + RESC_NUM(p_hwfn, QED_L2_QUEUE);
DP_NOTICE(p_hwfn, "l2_queue id [%d] is not valid, available indices [%d - %d]\n",
src_id, min, max);
int qed_fw_vport(struct qed_hwfn *p_hwfn, u8 src_id, u8 *dst_id)
{ if (src_id >= RESC_NUM(p_hwfn, QED_VPORT)) {
u8 min, max;
min = (u8)RESC_START(p_hwfn, QED_VPORT);
max = min + RESC_NUM(p_hwfn, QED_VPORT);
DP_NOTICE(p_hwfn, "vport id [%d] is not valid, available indices [%d - %d]\n",
src_id, min, max);
return -EINVAL;
}
*dst_id = RESC_START(p_hwfn, QED_VPORT) + src_id;
return 0;
}
int qed_fw_rss_eng(struct qed_hwfn *p_hwfn, u8 src_id, u8 *dst_id)
{ if (src_id >= RESC_NUM(p_hwfn, QED_RSS_ENG)) {
u8 min, max;
min = (u8)RESC_START(p_hwfn, QED_RSS_ENG);
max = min + RESC_NUM(p_hwfn, QED_RSS_ENG);
DP_NOTICE(p_hwfn, "rss_eng id [%d] is not valid, available indices [%d - %d]\n",
src_id, min, max);
/* Calculate final WFQ values for all vports and configure them. * After this configuration each vport will have * approx min rate = min_pf_rate * (vport_wfq / QED_WFQ_UNIT)
*/ staticvoid qed_configure_wfq_for_all_vports(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt,
u32 min_pf_rate)
{ struct init_qm_vport_params *vport_params; int i;
vport_params = p_hwfn->qm_info.qm_vport_params;
for (i = 0; i < p_hwfn->qm_info.num_vports; i++) {
u32 wfq_speed = p_hwfn->qm_info.wfq_data[i].min_speed;
for (i = 0; i < p_hwfn->qm_info.num_vports; i++) {
qed_init_wfq_default_param(p_hwfn, min_pf_rate);
qed_init_vport_wfq(p_hwfn, p_ptt,
vport_params[i].first_tx_pq_id,
vport_params[i].wfq);
}
}
/* This function performs several validations for WFQ * configuration and required min rate for a given vport * 1. req_rate must be greater than one percent of min_pf_rate. * 2. req_rate should not cause other vports [not configured for WFQ explicitly] * rates to get less than one percent of min_pf_rate. * 3. total_req_min_rate [all vports min rate sum] shouldn't exceed min_pf_rate.
*/ staticint qed_init_wfq_param(struct qed_hwfn *p_hwfn,
u16 vport_id, u32 req_rate, u32 min_pf_rate)
{
u32 total_req_min_rate = 0, total_left_rate = 0, left_rate_per_vp = 0; int non_requested_count = 0, req_count = 0, i, num_vports;
/* Include current vport data as well */
req_count++;
total_req_min_rate += req_rate;
non_requested_count = num_vports - req_count;
if (req_rate < min_pf_rate / QED_WFQ_UNIT) {
DP_VERBOSE(p_hwfn, NETIF_MSG_LINK, "Vport [%d] - Requested rate[%d Mbps] is less than one percent of configured PF min rate[%d Mbps]\n",
vport_id, req_rate, min_pf_rate); return -EINVAL;
}
if (num_vports > QED_WFQ_UNIT) {
DP_VERBOSE(p_hwfn, NETIF_MSG_LINK, "Number of vports is greater than %d\n",
QED_WFQ_UNIT); return -EINVAL;
}
if (total_req_min_rate > min_pf_rate) {
DP_VERBOSE(p_hwfn, NETIF_MSG_LINK, "Total requested min rate for all vports[%d Mbps] is greater than configured PF min rate[%d Mbps]\n",
total_req_min_rate, min_pf_rate); return -EINVAL;
}
if (!rc)
qed_configure_wfq_for_all_vports(p_hwfn, p_ptt,
p_link->min_pf_rate); else
DP_NOTICE(p_hwfn, "Validation failed while configuring min rate\n");
/* Main API for qed clients to configure vport min rate. * vp_id - vport id in PF Range[0 - (total_num_vports_per_pf - 1)] * rate - Speed in Mbps needs to be assigned to a given vport.
*/ int qed_configure_vport_wfq(struct qed_dev *cdev, u16 vp_id, u32 rate)
{ int i, rc = -EINVAL;
/* Currently not supported; Might change in future */ if (cdev->num_hwfns > 1) {
DP_NOTICE(cdev, "WFQ configuration is not supported for this device\n"); return rc;
}
if (rc) {
qed_ptt_release(p_hwfn, p_ptt); return rc;
}
qed_ptt_release(p_hwfn, p_ptt);
}
return rc;
}
/* API to configure WFQ from mcp link change */ void qed_configure_vp_wfq_on_link_change(struct qed_dev *cdev, struct qed_ptt *p_ptt, u32 min_pf_rate)
{ int i;
if (cdev->num_hwfns > 1) {
DP_VERBOSE(cdev,
NETIF_MSG_LINK, "WFQ configuration is not supported for this device\n"); return;
}
/* Since the limiter also affects Tx-switched traffic, we don't want it * to limit such traffic in case there's no actual limit. * In that case, set limit to imaginary high boundary.
*/ if (max_bw == 100)
p_hwfn->qm_info.pf_rl = 100000;
DP_VERBOSE(p_hwfn, NETIF_MSG_LINK, "Configured MAX bandwidth to be %08x Mb/sec\n",
p_link->speed);
return rc;
}
/* Main API to configure PF max bandwidth where bw range is [1 - 100] */ int qed_configure_pf_max_bandwidth(struct qed_dev *cdev, u8 max_bw)
{ int i, rc = -EINVAL;
if (max_bw < 1 || max_bw > 100) {
DP_NOTICE(cdev, "PF max bw valid range is [1-100]\n"); return rc;
}
DP_VERBOSE(p_hwfn, NETIF_MSG_LINK, "Configured MIN bandwidth to be %d Mb/sec\n",
p_link->min_pf_rate);
return rc;
}
/* Main API to configure PF min bandwidth where bw range is [1-100] */ int qed_configure_pf_min_bandwidth(struct qed_dev *cdev, u8 min_bw)
{ int i, rc = -EINVAL;
if (min_bw < 1 || min_bw > 100) {
DP_NOTICE(cdev, "PF min bw valid range is [1-100]\n"); return rc;
}
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.