mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-04 01:49:36 +00:00
![James Bottomley](/assets/img/avatar_default.png)
This crash was reported:
[ 366.947370] sd 3:0:1:0: [sdb] Spinning up disk....
[ 368.804046] BUG: unable to handle kernel NULL pointer dereference at (null)
[ 368.804072] IP: [<ffffffff81358457>] __mutex_lock_common.isra.7+0x9c/0x15b
[ 368.804098] PGD 0
[ 368.804114] Oops: 0002 [#1] SMP
[ 368.804143] CPU 1
[ 368.804151] Modules linked in: sg netconsole s3g(PO) uinput joydev hid_multitouch usbhid hid snd_hda_codec_via cpufreq_userspace cpufreq_powersave cpufreq_stats uhci_hcd cpufreq_conservative snd_hda_intel snd_hda_codec snd_hwdep snd_pcm sdhci_pci snd_page_alloc sdhci snd_timer snd psmouse evdev serio_raw pcspkr soundcore xhci_hcd shpchp s3g_drm(O) mvsas mmc_core ahci libahci drm i2c_core acpi_cpufreq mperf video processor button thermal_sys dm_dmirror exfat_fs exfat_core dm_zcache dm_mod padlock_aes aes_generic padlock_sha iscsi_target_mod target_core_mod configfs sswipe libsas libata scsi_transport_sas picdev via_cputemp hwmon_vid fuse parport_pc ppdev lp parport autofs4 ext4 crc16 mbcache jbd2 sd_mod crc_t10dif usb_storage scsi_mod ehci_hcd usbcore usb_common
[ 368.804749]
[ 368.804764] Pid: 392, comm: kworker/u:3 Tainted: P W O 3.4.87-logicube-ng.22 #1 To be filled by O.E.M. To be filled by O.E.M./EPIA-M920
[ 368.804802] RIP: 0010:[<ffffffff81358457>] [<ffffffff81358457>] __mutex_lock_common.isra.7+0x9c/0x15b
[ 368.804827] RSP: 0018:ffff880117001cc0 EFLAGS: 00010246
[ 368.804842] RAX: 0000000000000000 RBX: ffff8801185030d0 RCX: ffff88008edcb420
[ 368.804857] RDX: 0000000000000000 RSI: 0000000000000002 RDI: ffff8801185030d4
[ 368.804873] RBP: ffff8801181531c0 R08: 0000000000000020 R09: 00000000fffffffe
[ 368.804885] R10: 0000000000000000 R11: 0000000000000000 R12: ffff8801185030d4
[ 368.804899] R13: 0000000000000002 R14: ffff880117001fd8 R15: ffff8801185030d8
[ 368.804916] FS: 0000000000000000(0000) GS:ffff88011fc80000(0000) knlGS:0000000000000000
[ 368.804931] CS: 0010 DS: 0000 ES: 0000 CR0: 000000008005003b
[ 368.804946] CR2: 0000000000000000 CR3: 000000000160b000 CR4: 00000000000006e0
[ 368.804962] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[ 368.804978] DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
[ 368.804995] Process kworker/u:3 (pid: 392, threadinfo ffff880117000000, task ffff8801181531c0)
[ 368.805009] Stack:
[ 368.805017] ffff8801185030d8 0000000000000000 ffffffff8161ddf0 ffffffff81056f7c
[ 368.805062] 000000000000b503 ffff8801185030d0 ffff880118503000 0000000000000000
[ 368.805100] ffff8801185030d0 ffff8801188b8000 ffff88008edcb420 ffffffff813583ac
[ 368.805135] Call Trace:
[ 368.805153] [<ffffffff81056f7c>] ? up+0xb/0x33
[ 368.805168] [<ffffffff813583ac>] ? mutex_lock+0x16/0x25
[ 368.805194] [<ffffffffa018c414>] ? smp_execute_task+0x4e/0x222 [libsas]
[ 368.805217] [<ffffffffa018ce1c>] ? sas_find_bcast_dev+0x3c/0x15d [libsas]
[ 368.805240] [<ffffffffa018ce4f>] ? sas_find_bcast_dev+0x6f/0x15d [libsas]
[ 368.805264] [<ffffffffa018e989>] ? sas_ex_revalidate_domain+0x37/0x2ec [libsas]
[ 368.805280] [<ffffffff81355a2a>] ? printk+0x43/0x48
[ 368.805296] [<ffffffff81359a65>] ? _raw_spin_unlock_irqrestore+0xc/0xd
[ 368.805318] [<ffffffffa018b767>] ? sas_revalidate_domain+0x85/0xb6 [libsas]
[ 368.805336] [<ffffffff8104e5d9>] ? process_one_work+0x151/0x27c
[ 368.805351] [<ffffffff8104f6cd>] ? worker_thread+0xbb/0x152
[ 368.805366] [<ffffffff8104f612>] ? manage_workers.isra.29+0x163/0x163
[ 368.805382] [<ffffffff81052c4e>] ? kthread+0x79/0x81
[ 368.805399] [<ffffffff8135fea4>] ? kernel_thread_helper+0x4/0x10
[ 368.805416] [<ffffffff81052bd5>] ? kthread_flush_work_fn+0x9/0x9
[ 368.805431] [<ffffffff8135fea0>] ? gs_change+0x13/0x13
[ 368.805442] Code: 83 7d 30 63 7e 04 f3 90 eb ab 4c 8d 63 04 4c 8d 7b 08 4c 89 e7 e8 fa 15 00 00 48 8b 43 10 4c 89 3c 24 48 89 63 10 48 89 44 24 08 <48> 89 20 83 c8 ff 48 89 6c 24 10 87 03 ff c8 74 35 4d 89 ee 41
[ 368.805851] RIP [<ffffffff81358457>] __mutex_lock_common.isra.7+0x9c/0x15b
[ 368.805877] RSP <ffff880117001cc0>
[ 368.805886] CR2: 0000000000000000
[ 368.805899] ---[ end trace b720682065d8f4cc ]---
It's directly caused by 89d3cf6
[SCSI] libsas: add mutex for SMP task
execution, but shows a deeper cause: expander functions expect to be able to
cast to and treat domain devices as expanders. The correct fix is to only do
expander discover when we know we've got an expander device to avoid wrongly
casting a non-expander device.
Reported-by: Praveen Murali <pmurali@logicube.com>
Tested-by: Praveen Murali <pmurali@logicube.com>
Cc: stable@vger.kernel.org
Signed-off-by: James Bottomley <JBottomley@Parallels.com>
592 lines
16 KiB
C
592 lines
16 KiB
C
/*
|
|
* Serial Attached SCSI (SAS) Discover process
|
|
*
|
|
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
|
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
|
*
|
|
* This file is licensed under GPLv2.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/async.h>
|
|
#include <scsi/scsi_host.h>
|
|
#include <scsi/scsi_eh.h>
|
|
#include "sas_internal.h"
|
|
|
|
#include <scsi/scsi_transport.h>
|
|
#include <scsi/scsi_transport_sas.h>
|
|
#include <scsi/sas_ata.h>
|
|
#include "../scsi_sas_internal.h"
|
|
|
|
/* ---------- Basic task processing for discovery purposes ---------- */
|
|
|
|
void sas_init_dev(struct domain_device *dev)
|
|
{
|
|
switch (dev->dev_type) {
|
|
case SAS_END_DEVICE:
|
|
INIT_LIST_HEAD(&dev->ssp_dev.eh_list_node);
|
|
break;
|
|
case SAS_EDGE_EXPANDER_DEVICE:
|
|
case SAS_FANOUT_EXPANDER_DEVICE:
|
|
INIT_LIST_HEAD(&dev->ex_dev.children);
|
|
mutex_init(&dev->ex_dev.cmd_mutex);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* ---------- Domain device discovery ---------- */
|
|
|
|
/**
|
|
* sas_get_port_device -- Discover devices which caused port creation
|
|
* @port: pointer to struct sas_port of interest
|
|
*
|
|
* Devices directly attached to a HA port, have no parent. This is
|
|
* how we know they are (domain) "root" devices. All other devices
|
|
* do, and should have their "parent" pointer set appropriately as
|
|
* soon as a child device is discovered.
|
|
*/
|
|
static int sas_get_port_device(struct asd_sas_port *port)
|
|
{
|
|
struct asd_sas_phy *phy;
|
|
struct sas_rphy *rphy;
|
|
struct domain_device *dev;
|
|
int rc = -ENODEV;
|
|
|
|
dev = sas_alloc_device();
|
|
if (!dev)
|
|
return -ENOMEM;
|
|
|
|
spin_lock_irq(&port->phy_list_lock);
|
|
if (list_empty(&port->phy_list)) {
|
|
spin_unlock_irq(&port->phy_list_lock);
|
|
sas_put_device(dev);
|
|
return -ENODEV;
|
|
}
|
|
phy = container_of(port->phy_list.next, struct asd_sas_phy, port_phy_el);
|
|
spin_lock(&phy->frame_rcvd_lock);
|
|
memcpy(dev->frame_rcvd, phy->frame_rcvd, min(sizeof(dev->frame_rcvd),
|
|
(size_t)phy->frame_rcvd_size));
|
|
spin_unlock(&phy->frame_rcvd_lock);
|
|
spin_unlock_irq(&port->phy_list_lock);
|
|
|
|
if (dev->frame_rcvd[0] == 0x34 && port->oob_mode == SATA_OOB_MODE) {
|
|
struct dev_to_host_fis *fis =
|
|
(struct dev_to_host_fis *) dev->frame_rcvd;
|
|
if (fis->interrupt_reason == 1 && fis->lbal == 1 &&
|
|
fis->byte_count_low==0x69 && fis->byte_count_high == 0x96
|
|
&& (fis->device & ~0x10) == 0)
|
|
dev->dev_type = SAS_SATA_PM;
|
|
else
|
|
dev->dev_type = SAS_SATA_DEV;
|
|
dev->tproto = SAS_PROTOCOL_SATA;
|
|
} else {
|
|
struct sas_identify_frame *id =
|
|
(struct sas_identify_frame *) dev->frame_rcvd;
|
|
dev->dev_type = id->dev_type;
|
|
dev->iproto = id->initiator_bits;
|
|
dev->tproto = id->target_bits;
|
|
}
|
|
|
|
sas_init_dev(dev);
|
|
|
|
dev->port = port;
|
|
switch (dev->dev_type) {
|
|
case SAS_SATA_DEV:
|
|
rc = sas_ata_init(dev);
|
|
if (rc) {
|
|
rphy = NULL;
|
|
break;
|
|
}
|
|
/* fall through */
|
|
case SAS_END_DEVICE:
|
|
rphy = sas_end_device_alloc(port->port);
|
|
break;
|
|
case SAS_EDGE_EXPANDER_DEVICE:
|
|
rphy = sas_expander_alloc(port->port,
|
|
SAS_EDGE_EXPANDER_DEVICE);
|
|
break;
|
|
case SAS_FANOUT_EXPANDER_DEVICE:
|
|
rphy = sas_expander_alloc(port->port,
|
|
SAS_FANOUT_EXPANDER_DEVICE);
|
|
break;
|
|
default:
|
|
printk("ERROR: Unidentified device type %d\n", dev->dev_type);
|
|
rphy = NULL;
|
|
break;
|
|
}
|
|
|
|
if (!rphy) {
|
|
sas_put_device(dev);
|
|
return rc;
|
|
}
|
|
|
|
rphy->identify.phy_identifier = phy->phy->identify.phy_identifier;
|
|
memcpy(dev->sas_addr, port->attached_sas_addr, SAS_ADDR_SIZE);
|
|
sas_fill_in_rphy(dev, rphy);
|
|
sas_hash_addr(dev->hashed_sas_addr, dev->sas_addr);
|
|
port->port_dev = dev;
|
|
dev->linkrate = port->linkrate;
|
|
dev->min_linkrate = port->linkrate;
|
|
dev->max_linkrate = port->linkrate;
|
|
dev->pathways = port->num_phys;
|
|
memset(port->disc.fanout_sas_addr, 0, SAS_ADDR_SIZE);
|
|
memset(port->disc.eeds_a, 0, SAS_ADDR_SIZE);
|
|
memset(port->disc.eeds_b, 0, SAS_ADDR_SIZE);
|
|
port->disc.max_level = 0;
|
|
sas_device_set_phy(dev, port->port);
|
|
|
|
dev->rphy = rphy;
|
|
get_device(&dev->rphy->dev);
|
|
|
|
if (dev_is_sata(dev) || dev->dev_type == SAS_END_DEVICE)
|
|
list_add_tail(&dev->disco_list_node, &port->disco_list);
|
|
else {
|
|
spin_lock_irq(&port->dev_list_lock);
|
|
list_add_tail(&dev->dev_list_node, &port->dev_list);
|
|
spin_unlock_irq(&port->dev_list_lock);
|
|
}
|
|
|
|
spin_lock_irq(&port->phy_list_lock);
|
|
list_for_each_entry(phy, &port->phy_list, port_phy_el)
|
|
sas_phy_set_target(phy, dev);
|
|
spin_unlock_irq(&port->phy_list_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ---------- Discover and Revalidate ---------- */
|
|
|
|
int sas_notify_lldd_dev_found(struct domain_device *dev)
|
|
{
|
|
int res = 0;
|
|
struct sas_ha_struct *sas_ha = dev->port->ha;
|
|
struct Scsi_Host *shost = sas_ha->core.shost;
|
|
struct sas_internal *i = to_sas_internal(shost->transportt);
|
|
|
|
if (!i->dft->lldd_dev_found)
|
|
return 0;
|
|
|
|
res = i->dft->lldd_dev_found(dev);
|
|
if (res) {
|
|
printk("sas: driver on pcidev %s cannot handle "
|
|
"device %llx, error:%d\n",
|
|
dev_name(sas_ha->dev),
|
|
SAS_ADDR(dev->sas_addr), res);
|
|
}
|
|
set_bit(SAS_DEV_FOUND, &dev->state);
|
|
kref_get(&dev->kref);
|
|
return res;
|
|
}
|
|
|
|
|
|
void sas_notify_lldd_dev_gone(struct domain_device *dev)
|
|
{
|
|
struct sas_ha_struct *sas_ha = dev->port->ha;
|
|
struct Scsi_Host *shost = sas_ha->core.shost;
|
|
struct sas_internal *i = to_sas_internal(shost->transportt);
|
|
|
|
if (!i->dft->lldd_dev_gone)
|
|
return;
|
|
|
|
if (test_and_clear_bit(SAS_DEV_FOUND, &dev->state)) {
|
|
i->dft->lldd_dev_gone(dev);
|
|
sas_put_device(dev);
|
|
}
|
|
}
|
|
|
|
static void sas_probe_devices(struct work_struct *work)
|
|
{
|
|
struct domain_device *dev, *n;
|
|
struct sas_discovery_event *ev = to_sas_discovery_event(work);
|
|
struct asd_sas_port *port = ev->port;
|
|
|
|
clear_bit(DISCE_PROBE, &port->disc.pending);
|
|
|
|
/* devices must be domain members before link recovery and probe */
|
|
list_for_each_entry(dev, &port->disco_list, disco_list_node) {
|
|
spin_lock_irq(&port->dev_list_lock);
|
|
list_add_tail(&dev->dev_list_node, &port->dev_list);
|
|
spin_unlock_irq(&port->dev_list_lock);
|
|
}
|
|
|
|
sas_probe_sata(port);
|
|
|
|
list_for_each_entry_safe(dev, n, &port->disco_list, disco_list_node) {
|
|
int err;
|
|
|
|
err = sas_rphy_add(dev->rphy);
|
|
if (err)
|
|
sas_fail_probe(dev, __func__, err);
|
|
else
|
|
list_del_init(&dev->disco_list_node);
|
|
}
|
|
}
|
|
|
|
static void sas_suspend_devices(struct work_struct *work)
|
|
{
|
|
struct asd_sas_phy *phy;
|
|
struct domain_device *dev;
|
|
struct sas_discovery_event *ev = to_sas_discovery_event(work);
|
|
struct asd_sas_port *port = ev->port;
|
|
struct Scsi_Host *shost = port->ha->core.shost;
|
|
struct sas_internal *si = to_sas_internal(shost->transportt);
|
|
|
|
clear_bit(DISCE_SUSPEND, &port->disc.pending);
|
|
|
|
sas_suspend_sata(port);
|
|
|
|
/* lldd is free to forget the domain_device across the
|
|
* suspension, we force the issue here to keep the reference
|
|
* counts aligned
|
|
*/
|
|
list_for_each_entry(dev, &port->dev_list, dev_list_node)
|
|
sas_notify_lldd_dev_gone(dev);
|
|
|
|
/* we are suspending, so we know events are disabled and
|
|
* phy_list is not being mutated
|
|
*/
|
|
list_for_each_entry(phy, &port->phy_list, port_phy_el) {
|
|
if (si->dft->lldd_port_formed)
|
|
si->dft->lldd_port_deformed(phy);
|
|
phy->suspended = 1;
|
|
port->suspended = 1;
|
|
}
|
|
}
|
|
|
|
static void sas_resume_devices(struct work_struct *work)
|
|
{
|
|
struct sas_discovery_event *ev = to_sas_discovery_event(work);
|
|
struct asd_sas_port *port = ev->port;
|
|
|
|
clear_bit(DISCE_RESUME, &port->disc.pending);
|
|
|
|
sas_resume_sata(port);
|
|
}
|
|
|
|
/**
|
|
* sas_discover_end_dev -- discover an end device (SSP, etc)
|
|
* @end: pointer to domain device of interest
|
|
*
|
|
* See comment in sas_discover_sata().
|
|
*/
|
|
int sas_discover_end_dev(struct domain_device *dev)
|
|
{
|
|
int res;
|
|
|
|
res = sas_notify_lldd_dev_found(dev);
|
|
if (res)
|
|
return res;
|
|
sas_discover_event(dev->port, DISCE_PROBE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ---------- Device registration and unregistration ---------- */
|
|
|
|
void sas_free_device(struct kref *kref)
|
|
{
|
|
struct domain_device *dev = container_of(kref, typeof(*dev), kref);
|
|
|
|
put_device(&dev->rphy->dev);
|
|
dev->rphy = NULL;
|
|
|
|
if (dev->parent)
|
|
sas_put_device(dev->parent);
|
|
|
|
sas_port_put_phy(dev->phy);
|
|
dev->phy = NULL;
|
|
|
|
/* remove the phys and ports, everything else should be gone */
|
|
if (dev->dev_type == SAS_EDGE_EXPANDER_DEVICE || dev->dev_type == SAS_FANOUT_EXPANDER_DEVICE)
|
|
kfree(dev->ex_dev.ex_phy);
|
|
|
|
if (dev_is_sata(dev) && dev->sata_dev.ap) {
|
|
ata_sas_port_destroy(dev->sata_dev.ap);
|
|
dev->sata_dev.ap = NULL;
|
|
}
|
|
|
|
kfree(dev);
|
|
}
|
|
|
|
static void sas_unregister_common_dev(struct asd_sas_port *port, struct domain_device *dev)
|
|
{
|
|
struct sas_ha_struct *ha = port->ha;
|
|
|
|
sas_notify_lldd_dev_gone(dev);
|
|
if (!dev->parent)
|
|
dev->port->port_dev = NULL;
|
|
else
|
|
list_del_init(&dev->siblings);
|
|
|
|
spin_lock_irq(&port->dev_list_lock);
|
|
list_del_init(&dev->dev_list_node);
|
|
if (dev_is_sata(dev))
|
|
sas_ata_end_eh(dev->sata_dev.ap);
|
|
spin_unlock_irq(&port->dev_list_lock);
|
|
|
|
spin_lock_irq(&ha->lock);
|
|
if (dev->dev_type == SAS_END_DEVICE &&
|
|
!list_empty(&dev->ssp_dev.eh_list_node)) {
|
|
list_del_init(&dev->ssp_dev.eh_list_node);
|
|
ha->eh_active--;
|
|
}
|
|
spin_unlock_irq(&ha->lock);
|
|
|
|
sas_put_device(dev);
|
|
}
|
|
|
|
static void sas_destruct_devices(struct work_struct *work)
|
|
{
|
|
struct domain_device *dev, *n;
|
|
struct sas_discovery_event *ev = to_sas_discovery_event(work);
|
|
struct asd_sas_port *port = ev->port;
|
|
|
|
clear_bit(DISCE_DESTRUCT, &port->disc.pending);
|
|
|
|
list_for_each_entry_safe(dev, n, &port->destroy_list, disco_list_node) {
|
|
list_del_init(&dev->disco_list_node);
|
|
|
|
sas_remove_children(&dev->rphy->dev);
|
|
sas_rphy_delete(dev->rphy);
|
|
sas_unregister_common_dev(port, dev);
|
|
}
|
|
}
|
|
|
|
void sas_unregister_dev(struct asd_sas_port *port, struct domain_device *dev)
|
|
{
|
|
if (!test_bit(SAS_DEV_DESTROY, &dev->state) &&
|
|
!list_empty(&dev->disco_list_node)) {
|
|
/* this rphy never saw sas_rphy_add */
|
|
list_del_init(&dev->disco_list_node);
|
|
sas_rphy_free(dev->rphy);
|
|
sas_unregister_common_dev(port, dev);
|
|
return;
|
|
}
|
|
|
|
if (!test_and_set_bit(SAS_DEV_DESTROY, &dev->state)) {
|
|
sas_rphy_unlink(dev->rphy);
|
|
list_move_tail(&dev->disco_list_node, &port->destroy_list);
|
|
sas_discover_event(dev->port, DISCE_DESTRUCT);
|
|
}
|
|
}
|
|
|
|
void sas_unregister_domain_devices(struct asd_sas_port *port, int gone)
|
|
{
|
|
struct domain_device *dev, *n;
|
|
|
|
list_for_each_entry_safe_reverse(dev, n, &port->dev_list, dev_list_node) {
|
|
if (gone)
|
|
set_bit(SAS_DEV_GONE, &dev->state);
|
|
sas_unregister_dev(port, dev);
|
|
}
|
|
|
|
list_for_each_entry_safe(dev, n, &port->disco_list, disco_list_node)
|
|
sas_unregister_dev(port, dev);
|
|
|
|
port->port->rphy = NULL;
|
|
|
|
}
|
|
|
|
void sas_device_set_phy(struct domain_device *dev, struct sas_port *port)
|
|
{
|
|
struct sas_ha_struct *ha;
|
|
struct sas_phy *new_phy;
|
|
|
|
if (!dev)
|
|
return;
|
|
|
|
ha = dev->port->ha;
|
|
new_phy = sas_port_get_phy(port);
|
|
|
|
/* pin and record last seen phy */
|
|
spin_lock_irq(&ha->phy_port_lock);
|
|
if (new_phy) {
|
|
sas_port_put_phy(dev->phy);
|
|
dev->phy = new_phy;
|
|
}
|
|
spin_unlock_irq(&ha->phy_port_lock);
|
|
}
|
|
|
|
/* ---------- Discovery and Revalidation ---------- */
|
|
|
|
/**
|
|
* sas_discover_domain -- discover the domain
|
|
* @port: port to the domain of interest
|
|
*
|
|
* NOTE: this process _must_ quit (return) as soon as any connection
|
|
* errors are encountered. Connection recovery is done elsewhere.
|
|
* Discover process only interrogates devices in order to discover the
|
|
* domain.
|
|
*/
|
|
static void sas_discover_domain(struct work_struct *work)
|
|
{
|
|
struct domain_device *dev;
|
|
int error = 0;
|
|
struct sas_discovery_event *ev = to_sas_discovery_event(work);
|
|
struct asd_sas_port *port = ev->port;
|
|
|
|
clear_bit(DISCE_DISCOVER_DOMAIN, &port->disc.pending);
|
|
|
|
if (port->port_dev)
|
|
return;
|
|
|
|
error = sas_get_port_device(port);
|
|
if (error)
|
|
return;
|
|
dev = port->port_dev;
|
|
|
|
SAS_DPRINTK("DOING DISCOVERY on port %d, pid:%d\n", port->id,
|
|
task_pid_nr(current));
|
|
|
|
switch (dev->dev_type) {
|
|
case SAS_END_DEVICE:
|
|
error = sas_discover_end_dev(dev);
|
|
break;
|
|
case SAS_EDGE_EXPANDER_DEVICE:
|
|
case SAS_FANOUT_EXPANDER_DEVICE:
|
|
error = sas_discover_root_expander(dev);
|
|
break;
|
|
case SAS_SATA_DEV:
|
|
case SAS_SATA_PM:
|
|
#ifdef CONFIG_SCSI_SAS_ATA
|
|
error = sas_discover_sata(dev);
|
|
break;
|
|
#else
|
|
SAS_DPRINTK("ATA device seen but CONFIG_SCSI_SAS_ATA=N so cannot attach\n");
|
|
/* Fall through */
|
|
#endif
|
|
default:
|
|
error = -ENXIO;
|
|
SAS_DPRINTK("unhandled device %d\n", dev->dev_type);
|
|
break;
|
|
}
|
|
|
|
if (error) {
|
|
sas_rphy_free(dev->rphy);
|
|
list_del_init(&dev->disco_list_node);
|
|
spin_lock_irq(&port->dev_list_lock);
|
|
list_del_init(&dev->dev_list_node);
|
|
spin_unlock_irq(&port->dev_list_lock);
|
|
|
|
sas_put_device(dev);
|
|
port->port_dev = NULL;
|
|
}
|
|
|
|
SAS_DPRINTK("DONE DISCOVERY on port %d, pid:%d, result:%d\n", port->id,
|
|
task_pid_nr(current), error);
|
|
}
|
|
|
|
static void sas_revalidate_domain(struct work_struct *work)
|
|
{
|
|
int res = 0;
|
|
struct sas_discovery_event *ev = to_sas_discovery_event(work);
|
|
struct asd_sas_port *port = ev->port;
|
|
struct sas_ha_struct *ha = port->ha;
|
|
struct domain_device *ddev = port->port_dev;
|
|
|
|
/* prevent revalidation from finding sata links in recovery */
|
|
mutex_lock(&ha->disco_mutex);
|
|
if (test_bit(SAS_HA_ATA_EH_ACTIVE, &ha->state)) {
|
|
SAS_DPRINTK("REVALIDATION DEFERRED on port %d, pid:%d\n",
|
|
port->id, task_pid_nr(current));
|
|
goto out;
|
|
}
|
|
|
|
clear_bit(DISCE_REVALIDATE_DOMAIN, &port->disc.pending);
|
|
|
|
SAS_DPRINTK("REVALIDATING DOMAIN on port %d, pid:%d\n", port->id,
|
|
task_pid_nr(current));
|
|
|
|
if (ddev && (ddev->dev_type == SAS_FANOUT_EXPANDER_DEVICE ||
|
|
ddev->dev_type == SAS_EDGE_EXPANDER_DEVICE))
|
|
res = sas_ex_revalidate_domain(ddev);
|
|
|
|
SAS_DPRINTK("done REVALIDATING DOMAIN on port %d, pid:%d, res 0x%x\n",
|
|
port->id, task_pid_nr(current), res);
|
|
out:
|
|
mutex_unlock(&ha->disco_mutex);
|
|
}
|
|
|
|
/* ---------- Events ---------- */
|
|
|
|
static void sas_chain_work(struct sas_ha_struct *ha, struct sas_work *sw)
|
|
{
|
|
/* chained work is not subject to SA_HA_DRAINING or
|
|
* SAS_HA_REGISTERED, because it is either submitted in the
|
|
* workqueue, or known to be submitted from a context that is
|
|
* not racing against draining
|
|
*/
|
|
scsi_queue_work(ha->core.shost, &sw->work);
|
|
}
|
|
|
|
static void sas_chain_event(int event, unsigned long *pending,
|
|
struct sas_work *sw,
|
|
struct sas_ha_struct *ha)
|
|
{
|
|
if (!test_and_set_bit(event, pending)) {
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&ha->lock, flags);
|
|
sas_chain_work(ha, sw);
|
|
spin_unlock_irqrestore(&ha->lock, flags);
|
|
}
|
|
}
|
|
|
|
int sas_discover_event(struct asd_sas_port *port, enum discover_event ev)
|
|
{
|
|
struct sas_discovery *disc;
|
|
|
|
if (!port)
|
|
return 0;
|
|
disc = &port->disc;
|
|
|
|
BUG_ON(ev >= DISC_NUM_EVENTS);
|
|
|
|
sas_chain_event(ev, &disc->pending, &disc->disc_work[ev].work, port->ha);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* sas_init_disc -- initialize the discovery struct in the port
|
|
* @port: pointer to struct port
|
|
*
|
|
* Called when the ports are being initialized.
|
|
*/
|
|
void sas_init_disc(struct sas_discovery *disc, struct asd_sas_port *port)
|
|
{
|
|
int i;
|
|
|
|
static const work_func_t sas_event_fns[DISC_NUM_EVENTS] = {
|
|
[DISCE_DISCOVER_DOMAIN] = sas_discover_domain,
|
|
[DISCE_REVALIDATE_DOMAIN] = sas_revalidate_domain,
|
|
[DISCE_PROBE] = sas_probe_devices,
|
|
[DISCE_SUSPEND] = sas_suspend_devices,
|
|
[DISCE_RESUME] = sas_resume_devices,
|
|
[DISCE_DESTRUCT] = sas_destruct_devices,
|
|
};
|
|
|
|
disc->pending = 0;
|
|
for (i = 0; i < DISC_NUM_EVENTS; i++) {
|
|
INIT_SAS_WORK(&disc->disc_work[i].work, sas_event_fns[i]);
|
|
disc->disc_work[i].port = port;
|
|
}
|
|
}
|