162306a36Sopenharmony_ci/******************************************************************* 262306a36Sopenharmony_ci * This file is part of the Emulex Linux Device Driver for * 362306a36Sopenharmony_ci * Fibre Channel Host Bus Adapters. * 462306a36Sopenharmony_ci * Copyright (C) 2017-2023 Broadcom. All Rights Reserved. The term * 562306a36Sopenharmony_ci * “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. * 662306a36Sopenharmony_ci * Copyright (C) 2004-2016 Emulex. All rights reserved. * 762306a36Sopenharmony_ci * EMULEX and SLI are trademarks of Emulex. * 862306a36Sopenharmony_ci * www.broadcom.com * 962306a36Sopenharmony_ci * Portions Copyright (C) 2004-2005 Christoph Hellwig * 1062306a36Sopenharmony_ci * * 1162306a36Sopenharmony_ci * This program is free software; you can redistribute it and/or * 1262306a36Sopenharmony_ci * modify it under the terms of version 2 of the GNU General * 1362306a36Sopenharmony_ci * Public License as published by the Free Software Foundation. * 1462306a36Sopenharmony_ci * This program is distributed in the hope that it will be useful. * 1562306a36Sopenharmony_ci * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND * 1662306a36Sopenharmony_ci * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, * 1762306a36Sopenharmony_ci * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE * 1862306a36Sopenharmony_ci * DISCLAIMED, EXCEPT TO THE EXTENT THAT SUCH DISCLAIMERS ARE HELD * 1962306a36Sopenharmony_ci * TO BE LEGALLY INVALID. See the GNU General Public License for * 2062306a36Sopenharmony_ci * more details, a copy of which can be found in the file COPYING * 2162306a36Sopenharmony_ci * included with this package. * 2262306a36Sopenharmony_ci *******************************************************************/ 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#include <linux/interrupt.h> 2562306a36Sopenharmony_ci#include <linux/dma-direction.h> 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#include <scsi/scsi_transport_fc.h> 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#include "lpfc_hw4.h" 3062306a36Sopenharmony_ci#include "lpfc_hw.h" 3162306a36Sopenharmony_ci#include "lpfc_sli.h" 3262306a36Sopenharmony_ci#include "lpfc_sli4.h" 3362306a36Sopenharmony_ci#include "lpfc_nl.h" 3462306a36Sopenharmony_ci#include "lpfc_disc.h" 3562306a36Sopenharmony_ci#include "lpfc.h" 3662306a36Sopenharmony_ci#include "lpfc_crtn.h" 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci/* 4062306a36Sopenharmony_ci * lpfc_get_vmid_from_hashtable - search the UUID in the hash table 4162306a36Sopenharmony_ci * @vport: The virtual port for which this call is being executed. 4262306a36Sopenharmony_ci * @hash: calculated hash value 4362306a36Sopenharmony_ci * @buf: uuid associated with the VE 4462306a36Sopenharmony_ci * Return the VMID entry associated with the UUID 4562306a36Sopenharmony_ci * Make sure to acquire the appropriate lock before invoking this routine. 4662306a36Sopenharmony_ci */ 4762306a36Sopenharmony_cistruct lpfc_vmid *lpfc_get_vmid_from_hashtable(struct lpfc_vport *vport, 4862306a36Sopenharmony_ci u32 hash, u8 *buf) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci struct lpfc_vmid *vmp; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci hash_for_each_possible(vport->hash_table, vmp, hnode, hash) { 5362306a36Sopenharmony_ci if (memcmp(&vmp->host_vmid[0], buf, 16) == 0) 5462306a36Sopenharmony_ci return vmp; 5562306a36Sopenharmony_ci } 5662306a36Sopenharmony_ci return NULL; 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci/* 6062306a36Sopenharmony_ci * lpfc_put_vmid_in_hashtable - put the VMID in the hash table 6162306a36Sopenharmony_ci * @vport: The virtual port for which this call is being executed. 6262306a36Sopenharmony_ci * @hash - calculated hash value 6362306a36Sopenharmony_ci * @vmp: Pointer to a VMID entry representing a VM sending I/O 6462306a36Sopenharmony_ci * 6562306a36Sopenharmony_ci * This routine will insert the newly acquired VMID entity in the hash table. 6662306a36Sopenharmony_ci * Make sure to acquire the appropriate lock before invoking this routine. 6762306a36Sopenharmony_ci */ 6862306a36Sopenharmony_cistatic void 6962306a36Sopenharmony_cilpfc_put_vmid_in_hashtable(struct lpfc_vport *vport, u32 hash, 7062306a36Sopenharmony_ci struct lpfc_vmid *vmp) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci hash_add(vport->hash_table, &vmp->hnode, hash); 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci/* 7662306a36Sopenharmony_ci * lpfc_vmid_hash_fn - create a hash value of the UUID 7762306a36Sopenharmony_ci * @vmid: uuid associated with the VE 7862306a36Sopenharmony_ci * @len: length of the VMID string 7962306a36Sopenharmony_ci * Returns the calculated hash value 8062306a36Sopenharmony_ci */ 8162306a36Sopenharmony_ciint lpfc_vmid_hash_fn(const char *vmid, int len) 8262306a36Sopenharmony_ci{ 8362306a36Sopenharmony_ci int c; 8462306a36Sopenharmony_ci int hash = 0; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci if (len == 0) 8762306a36Sopenharmony_ci return 0; 8862306a36Sopenharmony_ci while (len--) { 8962306a36Sopenharmony_ci c = *vmid++; 9062306a36Sopenharmony_ci if (c >= 'A' && c <= 'Z') 9162306a36Sopenharmony_ci c += 'a' - 'A'; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci hash = (hash + (c << LPFC_VMID_HASH_SHIFT) + 9462306a36Sopenharmony_ci (c >> LPFC_VMID_HASH_SHIFT)) * 19; 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci return hash & LPFC_VMID_HASH_MASK; 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci/* 10162306a36Sopenharmony_ci * lpfc_vmid_update_entry - update the vmid entry in the hash table 10262306a36Sopenharmony_ci * @vport: The virtual port for which this call is being executed. 10362306a36Sopenharmony_ci * @iodir: io direction 10462306a36Sopenharmony_ci * @vmp: Pointer to a VMID entry representing a VM sending I/O 10562306a36Sopenharmony_ci * @tag: VMID tag 10662306a36Sopenharmony_ci */ 10762306a36Sopenharmony_cistatic void lpfc_vmid_update_entry(struct lpfc_vport *vport, 10862306a36Sopenharmony_ci enum dma_data_direction iodir, 10962306a36Sopenharmony_ci struct lpfc_vmid *vmp, 11062306a36Sopenharmony_ci union lpfc_vmid_io_tag *tag) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci u64 *lta; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci if (vport->phba->pport->vmid_flag & LPFC_VMID_TYPE_PRIO) 11562306a36Sopenharmony_ci tag->cs_ctl_vmid = vmp->un.cs_ctl_vmid; 11662306a36Sopenharmony_ci else if (vport->phba->cfg_vmid_app_header) 11762306a36Sopenharmony_ci tag->app_id = vmp->un.app_id; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci if (iodir == DMA_TO_DEVICE) 12062306a36Sopenharmony_ci vmp->io_wr_cnt++; 12162306a36Sopenharmony_ci else if (iodir == DMA_FROM_DEVICE) 12262306a36Sopenharmony_ci vmp->io_rd_cnt++; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci /* update the last access timestamp in the table */ 12562306a36Sopenharmony_ci lta = per_cpu_ptr(vmp->last_io_time, raw_smp_processor_id()); 12662306a36Sopenharmony_ci *lta = jiffies; 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic void lpfc_vmid_assign_cs_ctl(struct lpfc_vport *vport, 13062306a36Sopenharmony_ci struct lpfc_vmid *vmid) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci u32 hash; 13362306a36Sopenharmony_ci struct lpfc_vmid *pvmid; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci if (vport->port_type == LPFC_PHYSICAL_PORT) { 13662306a36Sopenharmony_ci vmid->un.cs_ctl_vmid = lpfc_vmid_get_cs_ctl(vport); 13762306a36Sopenharmony_ci } else { 13862306a36Sopenharmony_ci hash = lpfc_vmid_hash_fn(vmid->host_vmid, vmid->vmid_len); 13962306a36Sopenharmony_ci pvmid = 14062306a36Sopenharmony_ci lpfc_get_vmid_from_hashtable(vport->phba->pport, hash, 14162306a36Sopenharmony_ci vmid->host_vmid); 14262306a36Sopenharmony_ci if (pvmid) 14362306a36Sopenharmony_ci vmid->un.cs_ctl_vmid = pvmid->un.cs_ctl_vmid; 14462306a36Sopenharmony_ci else 14562306a36Sopenharmony_ci vmid->un.cs_ctl_vmid = lpfc_vmid_get_cs_ctl(vport); 14662306a36Sopenharmony_ci } 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci/* 15062306a36Sopenharmony_ci * lpfc_vmid_get_appid - get the VMID associated with the UUID 15162306a36Sopenharmony_ci * @vport: The virtual port for which this call is being executed. 15262306a36Sopenharmony_ci * @uuid: UUID associated with the VE 15362306a36Sopenharmony_ci * @cmd: address of scsi_cmd descriptor 15462306a36Sopenharmony_ci * @iodir: io direction 15562306a36Sopenharmony_ci * @tag: VMID tag 15662306a36Sopenharmony_ci * Returns status of the function 15762306a36Sopenharmony_ci */ 15862306a36Sopenharmony_ciint lpfc_vmid_get_appid(struct lpfc_vport *vport, char *uuid, 15962306a36Sopenharmony_ci enum dma_data_direction iodir, 16062306a36Sopenharmony_ci union lpfc_vmid_io_tag *tag) 16162306a36Sopenharmony_ci{ 16262306a36Sopenharmony_ci struct lpfc_vmid *vmp = NULL; 16362306a36Sopenharmony_ci int hash, len, rc = -EPERM, i; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci /* check if QFPA is complete */ 16662306a36Sopenharmony_ci if (lpfc_vmid_is_type_priority_tag(vport) && 16762306a36Sopenharmony_ci !(vport->vmid_flag & LPFC_VMID_QFPA_CMPL) && 16862306a36Sopenharmony_ci (vport->vmid_flag & LPFC_VMID_ISSUE_QFPA)) { 16962306a36Sopenharmony_ci vport->work_port_events |= WORKER_CHECK_VMID_ISSUE_QFPA; 17062306a36Sopenharmony_ci return -EAGAIN; 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci /* search if the UUID has already been mapped to the VMID */ 17462306a36Sopenharmony_ci len = strlen(uuid); 17562306a36Sopenharmony_ci hash = lpfc_vmid_hash_fn(uuid, len); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci /* search for the VMID in the table */ 17862306a36Sopenharmony_ci read_lock(&vport->vmid_lock); 17962306a36Sopenharmony_ci vmp = lpfc_get_vmid_from_hashtable(vport, hash, uuid); 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci /* if found, check if its already registered */ 18262306a36Sopenharmony_ci if (vmp && vmp->flag & LPFC_VMID_REGISTERED) { 18362306a36Sopenharmony_ci read_unlock(&vport->vmid_lock); 18462306a36Sopenharmony_ci lpfc_vmid_update_entry(vport, iodir, vmp, tag); 18562306a36Sopenharmony_ci rc = 0; 18662306a36Sopenharmony_ci } else if (vmp && (vmp->flag & LPFC_VMID_REQ_REGISTER || 18762306a36Sopenharmony_ci vmp->flag & LPFC_VMID_DE_REGISTER)) { 18862306a36Sopenharmony_ci /* else if register or dereg request has already been sent */ 18962306a36Sopenharmony_ci /* Hence VMID tag will not be added for this I/O */ 19062306a36Sopenharmony_ci read_unlock(&vport->vmid_lock); 19162306a36Sopenharmony_ci rc = -EBUSY; 19262306a36Sopenharmony_ci } else { 19362306a36Sopenharmony_ci /* The VMID was not found in the hashtable. At this point, */ 19462306a36Sopenharmony_ci /* drop the read lock first before proceeding further */ 19562306a36Sopenharmony_ci read_unlock(&vport->vmid_lock); 19662306a36Sopenharmony_ci /* start the process to obtain one as per the */ 19762306a36Sopenharmony_ci /* type of the VMID indicated */ 19862306a36Sopenharmony_ci write_lock(&vport->vmid_lock); 19962306a36Sopenharmony_ci vmp = lpfc_get_vmid_from_hashtable(vport, hash, uuid); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci /* while the read lock was released, in case the entry was */ 20262306a36Sopenharmony_ci /* added by other context or is in process of being added */ 20362306a36Sopenharmony_ci if (vmp && vmp->flag & LPFC_VMID_REGISTERED) { 20462306a36Sopenharmony_ci lpfc_vmid_update_entry(vport, iodir, vmp, tag); 20562306a36Sopenharmony_ci write_unlock(&vport->vmid_lock); 20662306a36Sopenharmony_ci return 0; 20762306a36Sopenharmony_ci } else if (vmp && vmp->flag & LPFC_VMID_REQ_REGISTER) { 20862306a36Sopenharmony_ci write_unlock(&vport->vmid_lock); 20962306a36Sopenharmony_ci return -EBUSY; 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci /* else search and allocate a free slot in the hash table */ 21362306a36Sopenharmony_ci if (vport->cur_vmid_cnt < vport->max_vmid) { 21462306a36Sopenharmony_ci for (i = 0; i < vport->max_vmid; i++) { 21562306a36Sopenharmony_ci vmp = vport->vmid + i; 21662306a36Sopenharmony_ci if (vmp->flag == LPFC_VMID_SLOT_FREE) 21762306a36Sopenharmony_ci break; 21862306a36Sopenharmony_ci } 21962306a36Sopenharmony_ci if (i == vport->max_vmid) 22062306a36Sopenharmony_ci vmp = NULL; 22162306a36Sopenharmony_ci } else { 22262306a36Sopenharmony_ci vmp = NULL; 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci if (!vmp) { 22662306a36Sopenharmony_ci write_unlock(&vport->vmid_lock); 22762306a36Sopenharmony_ci return -ENOMEM; 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci /* Add the vmid and register */ 23162306a36Sopenharmony_ci lpfc_put_vmid_in_hashtable(vport, hash, vmp); 23262306a36Sopenharmony_ci vmp->vmid_len = len; 23362306a36Sopenharmony_ci memcpy(vmp->host_vmid, uuid, vmp->vmid_len); 23462306a36Sopenharmony_ci vmp->io_rd_cnt = 0; 23562306a36Sopenharmony_ci vmp->io_wr_cnt = 0; 23662306a36Sopenharmony_ci vmp->flag = LPFC_VMID_SLOT_USED; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci vmp->delete_inactive = 23962306a36Sopenharmony_ci vport->vmid_inactivity_timeout ? 1 : 0; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci /* if type priority tag, get next available VMID */ 24262306a36Sopenharmony_ci if (vport->phba->pport->vmid_flag & LPFC_VMID_TYPE_PRIO) 24362306a36Sopenharmony_ci lpfc_vmid_assign_cs_ctl(vport, vmp); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci /* allocate the per cpu variable for holding */ 24662306a36Sopenharmony_ci /* the last access time stamp only if VMID is enabled */ 24762306a36Sopenharmony_ci if (!vmp->last_io_time) 24862306a36Sopenharmony_ci vmp->last_io_time = alloc_percpu_gfp(u64, GFP_ATOMIC); 24962306a36Sopenharmony_ci if (!vmp->last_io_time) { 25062306a36Sopenharmony_ci hash_del(&vmp->hnode); 25162306a36Sopenharmony_ci vmp->flag = LPFC_VMID_SLOT_FREE; 25262306a36Sopenharmony_ci write_unlock(&vport->vmid_lock); 25362306a36Sopenharmony_ci return -EIO; 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci write_unlock(&vport->vmid_lock); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci /* complete transaction with switch */ 25962306a36Sopenharmony_ci if (vport->phba->pport->vmid_flag & LPFC_VMID_TYPE_PRIO) 26062306a36Sopenharmony_ci rc = lpfc_vmid_uvem(vport, vmp, true); 26162306a36Sopenharmony_ci else if (vport->phba->cfg_vmid_app_header) 26262306a36Sopenharmony_ci rc = lpfc_vmid_cmd(vport, SLI_CTAS_RAPP_IDENT, vmp); 26362306a36Sopenharmony_ci if (!rc) { 26462306a36Sopenharmony_ci write_lock(&vport->vmid_lock); 26562306a36Sopenharmony_ci vport->cur_vmid_cnt++; 26662306a36Sopenharmony_ci vmp->flag |= LPFC_VMID_REQ_REGISTER; 26762306a36Sopenharmony_ci write_unlock(&vport->vmid_lock); 26862306a36Sopenharmony_ci } else { 26962306a36Sopenharmony_ci write_lock(&vport->vmid_lock); 27062306a36Sopenharmony_ci hash_del(&vmp->hnode); 27162306a36Sopenharmony_ci vmp->flag = LPFC_VMID_SLOT_FREE; 27262306a36Sopenharmony_ci free_percpu(vmp->last_io_time); 27362306a36Sopenharmony_ci write_unlock(&vport->vmid_lock); 27462306a36Sopenharmony_ci return -EIO; 27562306a36Sopenharmony_ci } 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci /* finally, enable the idle timer once */ 27862306a36Sopenharmony_ci if (!(vport->phba->pport->vmid_flag & LPFC_VMID_TIMER_ENBLD)) { 27962306a36Sopenharmony_ci mod_timer(&vport->phba->inactive_vmid_poll, 28062306a36Sopenharmony_ci jiffies + 28162306a36Sopenharmony_ci msecs_to_jiffies(1000 * LPFC_VMID_TIMER)); 28262306a36Sopenharmony_ci vport->phba->pport->vmid_flag |= LPFC_VMID_TIMER_ENBLD; 28362306a36Sopenharmony_ci } 28462306a36Sopenharmony_ci } 28562306a36Sopenharmony_ci return rc; 28662306a36Sopenharmony_ci} 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci/* 28962306a36Sopenharmony_ci * lpfc_reinit_vmid - reinitializes the vmid data structure 29062306a36Sopenharmony_ci * @vport: pointer to vport data structure 29162306a36Sopenharmony_ci * 29262306a36Sopenharmony_ci * This routine reinitializes the vmid post flogi completion 29362306a36Sopenharmony_ci * 29462306a36Sopenharmony_ci * Return codes 29562306a36Sopenharmony_ci * None 29662306a36Sopenharmony_ci */ 29762306a36Sopenharmony_civoid 29862306a36Sopenharmony_cilpfc_reinit_vmid(struct lpfc_vport *vport) 29962306a36Sopenharmony_ci{ 30062306a36Sopenharmony_ci u32 bucket, i, cpu; 30162306a36Sopenharmony_ci struct lpfc_vmid *cur; 30262306a36Sopenharmony_ci struct lpfc_vmid *vmp = NULL; 30362306a36Sopenharmony_ci struct hlist_node *tmp; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci write_lock(&vport->vmid_lock); 30662306a36Sopenharmony_ci vport->cur_vmid_cnt = 0; 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci for (i = 0; i < vport->max_vmid; i++) { 30962306a36Sopenharmony_ci vmp = &vport->vmid[i]; 31062306a36Sopenharmony_ci vmp->flag = LPFC_VMID_SLOT_FREE; 31162306a36Sopenharmony_ci memset(vmp->host_vmid, 0, sizeof(vmp->host_vmid)); 31262306a36Sopenharmony_ci vmp->io_rd_cnt = 0; 31362306a36Sopenharmony_ci vmp->io_wr_cnt = 0; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci if (vmp->last_io_time) 31662306a36Sopenharmony_ci for_each_possible_cpu(cpu) 31762306a36Sopenharmony_ci *per_cpu_ptr(vmp->last_io_time, cpu) = 0; 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci /* for all elements in the hash table */ 32162306a36Sopenharmony_ci if (!hash_empty(vport->hash_table)) 32262306a36Sopenharmony_ci hash_for_each_safe(vport->hash_table, bucket, tmp, cur, hnode) 32362306a36Sopenharmony_ci hash_del(&cur->hnode); 32462306a36Sopenharmony_ci vport->vmid_flag = 0; 32562306a36Sopenharmony_ci write_unlock(&vport->vmid_lock); 32662306a36Sopenharmony_ci} 327