Commit 08c4d550 authored by Sreekanth Reddy's avatar Sreekanth Reddy Committed by Martin K. Petersen

mpt3sas: setpci reset kernel oops fix

setpci reset on nytro warpdrive card along with sysfs access and cli
ioctl access resulted in kernel oops

1. pci_access_mutex lock added to provide synchronization between IOCTL,
   sysfs, PCI resource handling path

2. gioc_lock spinlock to protect list operations over multiple
   controllers

This patch is ported from commit 6229b414 ("mpt2sas: setpci reset
kernel oops fix").
Signed-off-by: default avatarSreekanth Reddy <Sreekanth.Reddy@avagotech.com>
Acked-by: default avatarChristoph Hellwig <hch@lst.de>
Reviewed-by: default avatarHannes Reinecke <hare@suse.de>
Signed-off-by: default avatarMartin K. Petersen <martin.petersen@oracle.com>
parent 989e43c7
...@@ -108,9 +108,12 @@ _scsih_set_fwfault_debug(const char *val, struct kernel_param *kp) ...@@ -108,9 +108,12 @@ _scsih_set_fwfault_debug(const char *val, struct kernel_param *kp)
if (ret) if (ret)
return ret; return ret;
/* global ioc spinlock to protect controller list on list operations */
pr_info("setting fwfault_debug(%d)\n", mpt3sas_fwfault_debug); pr_info("setting fwfault_debug(%d)\n", mpt3sas_fwfault_debug);
spin_lock(&gioc_lock);
list_for_each_entry(ioc, &mpt3sas_ioc_list, list) list_for_each_entry(ioc, &mpt3sas_ioc_list, list)
ioc->fwfault_debug = mpt3sas_fwfault_debug; ioc->fwfault_debug = mpt3sas_fwfault_debug;
spin_unlock(&gioc_lock);
return 0; return 0;
} }
module_param_call(mpt3sas_fwfault_debug, _scsih_set_fwfault_debug, module_param_call(mpt3sas_fwfault_debug, _scsih_set_fwfault_debug,
...@@ -5136,6 +5139,8 @@ mpt3sas_base_free_resources(struct MPT3SAS_ADAPTER *ioc) ...@@ -5136,6 +5139,8 @@ mpt3sas_base_free_resources(struct MPT3SAS_ADAPTER *ioc)
dexitprintk(ioc, pr_info(MPT3SAS_FMT "%s\n", ioc->name, dexitprintk(ioc, pr_info(MPT3SAS_FMT "%s\n", ioc->name,
__func__)); __func__));
/* synchronizing freeing resource with pci_access_mutex lock */
mutex_lock(&ioc->pci_access_mutex);
if (ioc->chip_phys && ioc->chip) { if (ioc->chip_phys && ioc->chip) {
_base_mask_interrupts(ioc); _base_mask_interrupts(ioc);
ioc->shost_recovery = 1; ioc->shost_recovery = 1;
...@@ -5144,6 +5149,7 @@ mpt3sas_base_free_resources(struct MPT3SAS_ADAPTER *ioc) ...@@ -5144,6 +5149,7 @@ mpt3sas_base_free_resources(struct MPT3SAS_ADAPTER *ioc)
} }
mpt3sas_base_unmap_resources(ioc); mpt3sas_base_unmap_resources(ioc);
mutex_unlock(&ioc->pci_access_mutex);
return; return;
} }
......
...@@ -916,7 +916,13 @@ typedef void (*MPT3SAS_FLUSH_RUNNING_CMDS)(struct MPT3SAS_ADAPTER *ioc); ...@@ -916,7 +916,13 @@ typedef void (*MPT3SAS_FLUSH_RUNNING_CMDS)(struct MPT3SAS_ADAPTER *ioc);
* @replyPostRegisterIndex: index of next position in Reply Desc Post Queue * @replyPostRegisterIndex: index of next position in Reply Desc Post Queue
* @delayed_tr_list: target reset link list * @delayed_tr_list: target reset link list
* @delayed_tr_volume_list: volume target reset link list * @delayed_tr_volume_list: volume target reset link list
* @@temp_sensors_count: flag to carry the number of temperature sensors * @temp_sensors_count: flag to carry the number of temperature sensors
* @pci_access_mutex: Mutex to synchronize ioctl,sysfs show path and
* pci resource handling. PCI resource freeing will lead to free
* vital hardware/memory resource, which might be in use by cli/sysfs
* path functions resulting in Null pointer reference followed by kernel
* crash. To avoid the above race condition we use mutex syncrhonization
* which ensures the syncrhonization between cli/sysfs_show path.
*/ */
struct MPT3SAS_ADAPTER { struct MPT3SAS_ADAPTER {
struct list_head list; struct list_head list;
...@@ -1131,6 +1137,7 @@ struct MPT3SAS_ADAPTER { ...@@ -1131,6 +1137,7 @@ struct MPT3SAS_ADAPTER {
struct list_head delayed_tr_list; struct list_head delayed_tr_list;
struct list_head delayed_tr_volume_list; struct list_head delayed_tr_volume_list;
u8 temp_sensors_count; u8 temp_sensors_count;
struct mutex pci_access_mutex;
/* diag buffer support */ /* diag buffer support */
u8 *diag_buffer[MPI2_DIAG_BUF_TYPE_COUNT]; u8 *diag_buffer[MPI2_DIAG_BUF_TYPE_COUNT];
...@@ -1161,6 +1168,17 @@ typedef u8 (*MPT_CALLBACK)(struct MPT3SAS_ADAPTER *ioc, u16 smid, u8 msix_index, ...@@ -1161,6 +1168,17 @@ typedef u8 (*MPT_CALLBACK)(struct MPT3SAS_ADAPTER *ioc, u16 smid, u8 msix_index,
/* base shared API */ /* base shared API */
extern struct list_head mpt3sas_ioc_list; extern struct list_head mpt3sas_ioc_list;
extern char driver_name[MPT_NAME_LENGTH]; extern char driver_name[MPT_NAME_LENGTH];
/* spinlock on list operations over IOCs
* Case: when multiple warpdrive cards(IOCs) are in use
* Each IOC will added to the ioc list structure on initialization.
* Watchdog threads run at regular intervals to check IOC for any
* fault conditions which will trigger the dead_ioc thread to
* deallocate pci resource, resulting deleting the IOC netry from list,
* this deletion need to protected by spinlock to enusre that
* ioc removal is syncrhonized, if not synchronized it might lead to
* list_del corruption as the ioc list is traversed in cli path.
*/
extern spinlock_t gioc_lock;
void mpt3sas_base_start_watchdog(struct MPT3SAS_ADAPTER *ioc); void mpt3sas_base_start_watchdog(struct MPT3SAS_ADAPTER *ioc);
void mpt3sas_base_stop_watchdog(struct MPT3SAS_ADAPTER *ioc); void mpt3sas_base_stop_watchdog(struct MPT3SAS_ADAPTER *ioc);
......
...@@ -416,13 +416,16 @@ static int ...@@ -416,13 +416,16 @@ static int
_ctl_verify_adapter(int ioc_number, struct MPT3SAS_ADAPTER **iocpp) _ctl_verify_adapter(int ioc_number, struct MPT3SAS_ADAPTER **iocpp)
{ {
struct MPT3SAS_ADAPTER *ioc; struct MPT3SAS_ADAPTER *ioc;
/* global ioc lock to protect controller on list operations */
spin_lock(&gioc_lock);
list_for_each_entry(ioc, &mpt3sas_ioc_list, list) { list_for_each_entry(ioc, &mpt3sas_ioc_list, list) {
if (ioc->id != ioc_number) if (ioc->id != ioc_number)
continue; continue;
spin_unlock(&gioc_lock);
*iocpp = ioc; *iocpp = ioc;
return ioc_number; return ioc_number;
} }
spin_unlock(&gioc_lock);
*iocpp = NULL; *iocpp = NULL;
return -1; return -1;
} }
...@@ -511,10 +514,15 @@ ctl_poll(struct file *filep, poll_table *wait) ...@@ -511,10 +514,15 @@ ctl_poll(struct file *filep, poll_table *wait)
poll_wait(filep, &ctl_poll_wait, wait); poll_wait(filep, &ctl_poll_wait, wait);
/* global ioc lock to protect controller on list operations */
spin_lock(&gioc_lock);
list_for_each_entry(ioc, &mpt3sas_ioc_list, list) { list_for_each_entry(ioc, &mpt3sas_ioc_list, list) {
if (ioc->aen_event_read_flag) if (ioc->aen_event_read_flag) {
spin_unlock(&gioc_lock);
return POLLIN | POLLRDNORM; return POLLIN | POLLRDNORM;
}
} }
spin_unlock(&gioc_lock);
return 0; return 0;
} }
...@@ -2211,16 +2219,25 @@ _ctl_ioctl_main(struct file *file, unsigned int cmd, void __user *arg, ...@@ -2211,16 +2219,25 @@ _ctl_ioctl_main(struct file *file, unsigned int cmd, void __user *arg,
if (_ctl_verify_adapter(ioctl_header.ioc_number, &ioc) == -1 || !ioc) if (_ctl_verify_adapter(ioctl_header.ioc_number, &ioc) == -1 || !ioc)
return -ENODEV; return -ENODEV;
/* pci_access_mutex lock acquired by ioctl path */
mutex_lock(&ioc->pci_access_mutex);
if (ioc->shost_recovery || ioc->pci_error_recovery || if (ioc->shost_recovery || ioc->pci_error_recovery ||
ioc->is_driver_loading) ioc->is_driver_loading || ioc->remove_host) {
return -EAGAIN; ret = -EAGAIN;
goto out_unlock_pciaccess;
}
state = (file->f_flags & O_NONBLOCK) ? NON_BLOCKING : BLOCKING; state = (file->f_flags & O_NONBLOCK) ? NON_BLOCKING : BLOCKING;
if (state == NON_BLOCKING) { if (state == NON_BLOCKING) {
if (!mutex_trylock(&ioc->ctl_cmds.mutex)) if (!mutex_trylock(&ioc->ctl_cmds.mutex)) {
return -EAGAIN; ret = -EAGAIN;
} else if (mutex_lock_interruptible(&ioc->ctl_cmds.mutex)) goto out_unlock_pciaccess;
return -ERESTARTSYS; }
} else if (mutex_lock_interruptible(&ioc->ctl_cmds.mutex)) {
ret = -ERESTARTSYS;
goto out_unlock_pciaccess;
}
switch (cmd) { switch (cmd) {
...@@ -2301,6 +2318,8 @@ _ctl_ioctl_main(struct file *file, unsigned int cmd, void __user *arg, ...@@ -2301,6 +2318,8 @@ _ctl_ioctl_main(struct file *file, unsigned int cmd, void __user *arg,
} }
mutex_unlock(&ioc->ctl_cmds.mutex); mutex_unlock(&ioc->ctl_cmds.mutex);
out_unlock_pciaccess:
mutex_unlock(&ioc->pci_access_mutex);
return ret; return ret;
} }
...@@ -2748,6 +2767,12 @@ _ctl_BRM_status_show(struct device *cdev, struct device_attribute *attr, ...@@ -2748,6 +2767,12 @@ _ctl_BRM_status_show(struct device *cdev, struct device_attribute *attr,
" warpdrive\n", ioc->name, __func__); " warpdrive\n", ioc->name, __func__);
goto out; goto out;
} }
/* pci_access_mutex lock acquired by sysfs show path */
mutex_lock(&ioc->pci_access_mutex);
if (ioc->pci_error_recovery || ioc->remove_host) {
mutex_unlock(&ioc->pci_access_mutex);
return 0;
}
/* allocate upto GPIOVal 36 entries */ /* allocate upto GPIOVal 36 entries */
sz = offsetof(Mpi2IOUnitPage3_t, GPIOVal) + (sizeof(u16) * 36); sz = offsetof(Mpi2IOUnitPage3_t, GPIOVal) + (sizeof(u16) * 36);
...@@ -2786,6 +2811,7 @@ _ctl_BRM_status_show(struct device *cdev, struct device_attribute *attr, ...@@ -2786,6 +2811,7 @@ _ctl_BRM_status_show(struct device *cdev, struct device_attribute *attr,
out: out:
kfree(io_unit_pg3); kfree(io_unit_pg3);
mutex_unlock(&ioc->pci_access_mutex);
return rc; return rc;
} }
static DEVICE_ATTR(BRM_status, S_IRUGO, _ctl_BRM_status_show, NULL); static DEVICE_ATTR(BRM_status, S_IRUGO, _ctl_BRM_status_show, NULL);
......
...@@ -90,6 +90,8 @@ _scsih_setup_direct_io(struct MPT3SAS_ADAPTER *ioc, struct scsi_cmnd *scmd, ...@@ -90,6 +90,8 @@ _scsih_setup_direct_io(struct MPT3SAS_ADAPTER *ioc, struct scsi_cmnd *scmd,
/* global parameters */ /* global parameters */
LIST_HEAD(mpt3sas_ioc_list); LIST_HEAD(mpt3sas_ioc_list);
char driver_name[MPT_NAME_LENGTH]; char driver_name[MPT_NAME_LENGTH];
/* global ioc lock for list operations */
DEFINE_SPINLOCK(gioc_lock);
/* local parameters */ /* local parameters */
static u8 scsi_io_cb_idx = -1; static u8 scsi_io_cb_idx = -1;
...@@ -294,8 +296,10 @@ _scsih_set_debug_level(const char *val, struct kernel_param *kp) ...@@ -294,8 +296,10 @@ _scsih_set_debug_level(const char *val, struct kernel_param *kp)
return ret; return ret;
pr_info("setting logging_level(0x%08x)\n", logging_level); pr_info("setting logging_level(0x%08x)\n", logging_level);
spin_lock(&gioc_lock);
list_for_each_entry(ioc, &mpt3sas_ioc_list, list) list_for_each_entry(ioc, &mpt3sas_ioc_list, list)
ioc->logging_level = logging_level; ioc->logging_level = logging_level;
spin_unlock(&gioc_lock);
return 0; return 0;
} }
module_param_call(logging_level, _scsih_set_debug_level, param_get_int, module_param_call(logging_level, _scsih_set_debug_level, param_get_int,
...@@ -7997,7 +8001,9 @@ void scsih_remove(struct pci_dev *pdev) ...@@ -7997,7 +8001,9 @@ void scsih_remove(struct pci_dev *pdev)
sas_remove_host(shost); sas_remove_host(shost);
scsi_remove_host(shost); scsi_remove_host(shost);
mpt3sas_base_detach(ioc); mpt3sas_base_detach(ioc);
spin_lock(&gioc_lock);
list_del(&ioc->list); list_del(&ioc->list);
spin_unlock(&gioc_lock);
scsi_host_put(shost); scsi_host_put(shost);
} }
...@@ -8384,7 +8390,9 @@ scsih_probe(struct pci_dev *pdev, struct Scsi_Host *shost) ...@@ -8384,7 +8390,9 @@ scsih_probe(struct pci_dev *pdev, struct Scsi_Host *shost)
ioc = shost_priv(shost); ioc = shost_priv(shost);
memset(ioc, 0, sizeof(struct MPT3SAS_ADAPTER)); memset(ioc, 0, sizeof(struct MPT3SAS_ADAPTER));
INIT_LIST_HEAD(&ioc->list); INIT_LIST_HEAD(&ioc->list);
spin_lock(&gioc_lock);
list_add_tail(&ioc->list, &mpt3sas_ioc_list); list_add_tail(&ioc->list, &mpt3sas_ioc_list);
spin_unlock(&gioc_lock);
ioc->shost = shost; ioc->shost = shost;
ioc->id = mpt_ids++; ioc->id = mpt_ids++;
ioc->pdev = pdev; ioc->pdev = pdev;
...@@ -8403,6 +8411,8 @@ scsih_probe(struct pci_dev *pdev, struct Scsi_Host *shost) ...@@ -8403,6 +8411,8 @@ scsih_probe(struct pci_dev *pdev, struct Scsi_Host *shost)
ioc->schedule_dead_ioc_flush_running_cmds = &_scsih_flush_running_cmds; ioc->schedule_dead_ioc_flush_running_cmds = &_scsih_flush_running_cmds;
/* misc semaphores and spin locks */ /* misc semaphores and spin locks */
mutex_init(&ioc->reset_in_progress_mutex); mutex_init(&ioc->reset_in_progress_mutex);
/* initializing pci_access_mutex lock */
mutex_init(&ioc->pci_access_mutex);
spin_lock_init(&ioc->ioc_reset_in_progress_lock); spin_lock_init(&ioc->ioc_reset_in_progress_lock);
spin_lock_init(&ioc->scsi_lookup_lock); spin_lock_init(&ioc->scsi_lookup_lock);
spin_lock_init(&ioc->sas_device_lock); spin_lock_init(&ioc->sas_device_lock);
...@@ -8510,7 +8520,9 @@ scsih_probe(struct pci_dev *pdev, struct Scsi_Host *shost) ...@@ -8510,7 +8520,9 @@ scsih_probe(struct pci_dev *pdev, struct Scsi_Host *shost)
out_attach_fail: out_attach_fail:
destroy_workqueue(ioc->firmware_event_thread); destroy_workqueue(ioc->firmware_event_thread);
out_thread_fail: out_thread_fail:
spin_lock(&gioc_lock);
list_del(&ioc->list); list_del(&ioc->list);
spin_unlock(&gioc_lock);
scsi_host_put(shost); scsi_host_put(shost);
return rv; return rv;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment