mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 13:41:51 +00:00
3fe97ff3d9
An enclosure with no components can't usefully be operated by the driver (since effectively it has nothing to manage), so report the problem and don't attach. Not attaching also fixes an oops which could occur if the driver tries to manage a zero component enclosure. [mkp: Switched to KERN_WARNING since this scenario is common] Link: https://lore.kernel.org/r/c5deac044ac409e32d9ad9968ce0dcbc996bfc7a.camel@linux.ibm.com Cc: stable@vger.kernel.org Reported-by: Ding Hui <dinghui@sangfor.com.cn> Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com> Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
930 lines
21 KiB
C
930 lines
21 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* SCSI Enclosure Services
|
|
*
|
|
* Copyright (C) 2008 James Bottomley <James.Bottomley@HansenPartnership.com>
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/enclosure.h>
|
|
#include <asm/unaligned.h>
|
|
|
|
#include <scsi/scsi.h>
|
|
#include <scsi/scsi_cmnd.h>
|
|
#include <scsi/scsi_dbg.h>
|
|
#include <scsi/scsi_device.h>
|
|
#include <scsi/scsi_driver.h>
|
|
#include <scsi/scsi_host.h>
|
|
|
|
#include <scsi/scsi_transport_sas.h>
|
|
|
|
struct ses_device {
|
|
unsigned char *page1;
|
|
unsigned char *page1_types;
|
|
unsigned char *page2;
|
|
unsigned char *page10;
|
|
short page1_len;
|
|
short page1_num_types;
|
|
short page2_len;
|
|
short page10_len;
|
|
};
|
|
|
|
struct ses_component {
|
|
u64 addr;
|
|
};
|
|
|
|
static bool ses_page2_supported(struct enclosure_device *edev)
|
|
{
|
|
struct ses_device *ses_dev = edev->scratch;
|
|
|
|
return (ses_dev->page2 != NULL);
|
|
}
|
|
|
|
static int ses_probe(struct device *dev)
|
|
{
|
|
struct scsi_device *sdev = to_scsi_device(dev);
|
|
int err = -ENODEV;
|
|
|
|
if (sdev->type != TYPE_ENCLOSURE)
|
|
goto out;
|
|
|
|
err = 0;
|
|
sdev_printk(KERN_NOTICE, sdev, "Attached Enclosure device\n");
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
#define SES_TIMEOUT (30 * HZ)
|
|
#define SES_RETRIES 3
|
|
|
|
static void init_device_slot_control(unsigned char *dest_desc,
|
|
struct enclosure_component *ecomp,
|
|
unsigned char *status)
|
|
{
|
|
memcpy(dest_desc, status, 4);
|
|
dest_desc[0] = 0;
|
|
/* only clear byte 1 for ENCLOSURE_COMPONENT_DEVICE */
|
|
if (ecomp->type == ENCLOSURE_COMPONENT_DEVICE)
|
|
dest_desc[1] = 0;
|
|
dest_desc[2] &= 0xde;
|
|
dest_desc[3] &= 0x3c;
|
|
}
|
|
|
|
|
|
static int ses_recv_diag(struct scsi_device *sdev, int page_code,
|
|
void *buf, int bufflen)
|
|
{
|
|
int ret;
|
|
unsigned char cmd[] = {
|
|
RECEIVE_DIAGNOSTIC,
|
|
1, /* Set PCV bit */
|
|
page_code,
|
|
bufflen >> 8,
|
|
bufflen & 0xff,
|
|
0
|
|
};
|
|
unsigned char recv_page_code;
|
|
unsigned int retries = SES_RETRIES;
|
|
struct scsi_sense_hdr sshdr;
|
|
const struct scsi_exec_args exec_args = {
|
|
.sshdr = &sshdr,
|
|
};
|
|
|
|
do {
|
|
ret = scsi_execute_cmd(sdev, cmd, REQ_OP_DRV_IN, buf, bufflen,
|
|
SES_TIMEOUT, 1, &exec_args);
|
|
} while (ret > 0 && --retries && scsi_sense_valid(&sshdr) &&
|
|
(sshdr.sense_key == NOT_READY ||
|
|
(sshdr.sense_key == UNIT_ATTENTION && sshdr.asc == 0x29)));
|
|
|
|
if (unlikely(ret))
|
|
return ret;
|
|
|
|
recv_page_code = ((unsigned char *)buf)[0];
|
|
|
|
if (likely(recv_page_code == page_code))
|
|
return ret;
|
|
|
|
/* successful diagnostic but wrong page code. This happens to some
|
|
* USB devices, just print a message and pretend there was an error */
|
|
|
|
sdev_printk(KERN_ERR, sdev,
|
|
"Wrong diagnostic page; asked for %d got %u\n",
|
|
page_code, recv_page_code);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int ses_send_diag(struct scsi_device *sdev, int page_code,
|
|
void *buf, int bufflen)
|
|
{
|
|
int result;
|
|
|
|
unsigned char cmd[] = {
|
|
SEND_DIAGNOSTIC,
|
|
0x10, /* Set PF bit */
|
|
0,
|
|
bufflen >> 8,
|
|
bufflen & 0xff,
|
|
0
|
|
};
|
|
struct scsi_sense_hdr sshdr;
|
|
unsigned int retries = SES_RETRIES;
|
|
const struct scsi_exec_args exec_args = {
|
|
.sshdr = &sshdr,
|
|
};
|
|
|
|
do {
|
|
result = scsi_execute_cmd(sdev, cmd, REQ_OP_DRV_OUT, buf,
|
|
bufflen, SES_TIMEOUT, 1, &exec_args);
|
|
} while (result > 0 && --retries && scsi_sense_valid(&sshdr) &&
|
|
(sshdr.sense_key == NOT_READY ||
|
|
(sshdr.sense_key == UNIT_ATTENTION && sshdr.asc == 0x29)));
|
|
|
|
if (result)
|
|
sdev_printk(KERN_ERR, sdev, "SEND DIAGNOSTIC result: %8x\n",
|
|
result);
|
|
return result;
|
|
}
|
|
|
|
static int ses_set_page2_descriptor(struct enclosure_device *edev,
|
|
struct enclosure_component *ecomp,
|
|
unsigned char *desc)
|
|
{
|
|
int i, j, count = 0, descriptor = ecomp->number;
|
|
struct scsi_device *sdev = to_scsi_device(edev->edev.parent);
|
|
struct ses_device *ses_dev = edev->scratch;
|
|
unsigned char *type_ptr = ses_dev->page1_types;
|
|
unsigned char *desc_ptr = ses_dev->page2 + 8;
|
|
|
|
/* Clear everything */
|
|
memset(desc_ptr, 0, ses_dev->page2_len - 8);
|
|
for (i = 0; i < ses_dev->page1_num_types; i++, type_ptr += 4) {
|
|
for (j = 0; j < type_ptr[1]; j++) {
|
|
desc_ptr += 4;
|
|
if (type_ptr[0] != ENCLOSURE_COMPONENT_DEVICE &&
|
|
type_ptr[0] != ENCLOSURE_COMPONENT_ARRAY_DEVICE)
|
|
continue;
|
|
if (count++ == descriptor) {
|
|
memcpy(desc_ptr, desc, 4);
|
|
/* set select */
|
|
desc_ptr[0] |= 0x80;
|
|
/* clear reserved, just in case */
|
|
desc_ptr[0] &= 0xf0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ses_send_diag(sdev, 2, ses_dev->page2, ses_dev->page2_len);
|
|
}
|
|
|
|
static unsigned char *ses_get_page2_descriptor(struct enclosure_device *edev,
|
|
struct enclosure_component *ecomp)
|
|
{
|
|
int i, j, count = 0, descriptor = ecomp->number;
|
|
struct scsi_device *sdev = to_scsi_device(edev->edev.parent);
|
|
struct ses_device *ses_dev = edev->scratch;
|
|
unsigned char *type_ptr = ses_dev->page1_types;
|
|
unsigned char *desc_ptr = ses_dev->page2 + 8;
|
|
|
|
if (ses_recv_diag(sdev, 2, ses_dev->page2, ses_dev->page2_len) < 0)
|
|
return NULL;
|
|
|
|
for (i = 0; i < ses_dev->page1_num_types; i++, type_ptr += 4) {
|
|
for (j = 0; j < type_ptr[1]; j++) {
|
|
desc_ptr += 4;
|
|
if (type_ptr[0] != ENCLOSURE_COMPONENT_DEVICE &&
|
|
type_ptr[0] != ENCLOSURE_COMPONENT_ARRAY_DEVICE)
|
|
continue;
|
|
if (count++ == descriptor)
|
|
return desc_ptr;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* For device slot and array device slot elements, byte 3 bit 6
|
|
* is "fault sensed" while byte 3 bit 5 is "fault reqstd". As this
|
|
* code stands these bits are shifted 4 positions right so in
|
|
* sysfs they will appear as bits 2 and 1 respectively. Strange. */
|
|
static void ses_get_fault(struct enclosure_device *edev,
|
|
struct enclosure_component *ecomp)
|
|
{
|
|
unsigned char *desc;
|
|
|
|
if (!ses_page2_supported(edev)) {
|
|
ecomp->fault = 0;
|
|
return;
|
|
}
|
|
desc = ses_get_page2_descriptor(edev, ecomp);
|
|
if (desc)
|
|
ecomp->fault = (desc[3] & 0x60) >> 4;
|
|
}
|
|
|
|
static int ses_set_fault(struct enclosure_device *edev,
|
|
struct enclosure_component *ecomp,
|
|
enum enclosure_component_setting val)
|
|
{
|
|
unsigned char desc[4];
|
|
unsigned char *desc_ptr;
|
|
|
|
if (!ses_page2_supported(edev))
|
|
return -EINVAL;
|
|
|
|
desc_ptr = ses_get_page2_descriptor(edev, ecomp);
|
|
|
|
if (!desc_ptr)
|
|
return -EIO;
|
|
|
|
init_device_slot_control(desc, ecomp, desc_ptr);
|
|
|
|
switch (val) {
|
|
case ENCLOSURE_SETTING_DISABLED:
|
|
desc[3] &= 0xdf;
|
|
break;
|
|
case ENCLOSURE_SETTING_ENABLED:
|
|
desc[3] |= 0x20;
|
|
break;
|
|
default:
|
|
/* SES doesn't do the SGPIO blink settings */
|
|
return -EINVAL;
|
|
}
|
|
|
|
return ses_set_page2_descriptor(edev, ecomp, desc);
|
|
}
|
|
|
|
static void ses_get_status(struct enclosure_device *edev,
|
|
struct enclosure_component *ecomp)
|
|
{
|
|
unsigned char *desc;
|
|
|
|
if (!ses_page2_supported(edev)) {
|
|
ecomp->status = 0;
|
|
return;
|
|
}
|
|
desc = ses_get_page2_descriptor(edev, ecomp);
|
|
if (desc)
|
|
ecomp->status = (desc[0] & 0x0f);
|
|
}
|
|
|
|
static void ses_get_locate(struct enclosure_device *edev,
|
|
struct enclosure_component *ecomp)
|
|
{
|
|
unsigned char *desc;
|
|
|
|
if (!ses_page2_supported(edev)) {
|
|
ecomp->locate = 0;
|
|
return;
|
|
}
|
|
desc = ses_get_page2_descriptor(edev, ecomp);
|
|
if (desc)
|
|
ecomp->locate = (desc[2] & 0x02) ? 1 : 0;
|
|
}
|
|
|
|
static int ses_set_locate(struct enclosure_device *edev,
|
|
struct enclosure_component *ecomp,
|
|
enum enclosure_component_setting val)
|
|
{
|
|
unsigned char desc[4];
|
|
unsigned char *desc_ptr;
|
|
|
|
if (!ses_page2_supported(edev))
|
|
return -EINVAL;
|
|
|
|
desc_ptr = ses_get_page2_descriptor(edev, ecomp);
|
|
|
|
if (!desc_ptr)
|
|
return -EIO;
|
|
|
|
init_device_slot_control(desc, ecomp, desc_ptr);
|
|
|
|
switch (val) {
|
|
case ENCLOSURE_SETTING_DISABLED:
|
|
desc[2] &= 0xfd;
|
|
break;
|
|
case ENCLOSURE_SETTING_ENABLED:
|
|
desc[2] |= 0x02;
|
|
break;
|
|
default:
|
|
/* SES doesn't do the SGPIO blink settings */
|
|
return -EINVAL;
|
|
}
|
|
return ses_set_page2_descriptor(edev, ecomp, desc);
|
|
}
|
|
|
|
static int ses_set_active(struct enclosure_device *edev,
|
|
struct enclosure_component *ecomp,
|
|
enum enclosure_component_setting val)
|
|
{
|
|
unsigned char desc[4];
|
|
unsigned char *desc_ptr;
|
|
|
|
if (!ses_page2_supported(edev))
|
|
return -EINVAL;
|
|
|
|
desc_ptr = ses_get_page2_descriptor(edev, ecomp);
|
|
|
|
if (!desc_ptr)
|
|
return -EIO;
|
|
|
|
init_device_slot_control(desc, ecomp, desc_ptr);
|
|
|
|
switch (val) {
|
|
case ENCLOSURE_SETTING_DISABLED:
|
|
desc[2] &= 0x7f;
|
|
ecomp->active = 0;
|
|
break;
|
|
case ENCLOSURE_SETTING_ENABLED:
|
|
desc[2] |= 0x80;
|
|
ecomp->active = 1;
|
|
break;
|
|
default:
|
|
/* SES doesn't do the SGPIO blink settings */
|
|
return -EINVAL;
|
|
}
|
|
return ses_set_page2_descriptor(edev, ecomp, desc);
|
|
}
|
|
|
|
static int ses_show_id(struct enclosure_device *edev, char *buf)
|
|
{
|
|
struct ses_device *ses_dev = edev->scratch;
|
|
unsigned long long id = get_unaligned_be64(ses_dev->page1+8+4);
|
|
|
|
return sprintf(buf, "%#llx\n", id);
|
|
}
|
|
|
|
static void ses_get_power_status(struct enclosure_device *edev,
|
|
struct enclosure_component *ecomp)
|
|
{
|
|
unsigned char *desc;
|
|
|
|
if (!ses_page2_supported(edev)) {
|
|
ecomp->power_status = 0;
|
|
return;
|
|
}
|
|
|
|
desc = ses_get_page2_descriptor(edev, ecomp);
|
|
if (desc)
|
|
ecomp->power_status = (desc[3] & 0x10) ? 0 : 1;
|
|
}
|
|
|
|
static int ses_set_power_status(struct enclosure_device *edev,
|
|
struct enclosure_component *ecomp,
|
|
int val)
|
|
{
|
|
unsigned char desc[4];
|
|
unsigned char *desc_ptr;
|
|
|
|
if (!ses_page2_supported(edev))
|
|
return -EINVAL;
|
|
|
|
desc_ptr = ses_get_page2_descriptor(edev, ecomp);
|
|
|
|
if (!desc_ptr)
|
|
return -EIO;
|
|
|
|
init_device_slot_control(desc, ecomp, desc_ptr);
|
|
|
|
switch (val) {
|
|
/* power = 1 is device_off = 0 and vice versa */
|
|
case 0:
|
|
desc[3] |= 0x10;
|
|
break;
|
|
case 1:
|
|
desc[3] &= 0xef;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
ecomp->power_status = val;
|
|
return ses_set_page2_descriptor(edev, ecomp, desc);
|
|
}
|
|
|
|
static struct enclosure_component_callbacks ses_enclosure_callbacks = {
|
|
.get_fault = ses_get_fault,
|
|
.set_fault = ses_set_fault,
|
|
.get_status = ses_get_status,
|
|
.get_locate = ses_get_locate,
|
|
.set_locate = ses_set_locate,
|
|
.get_power_status = ses_get_power_status,
|
|
.set_power_status = ses_set_power_status,
|
|
.set_active = ses_set_active,
|
|
.show_id = ses_show_id,
|
|
};
|
|
|
|
struct ses_host_edev {
|
|
struct Scsi_Host *shost;
|
|
struct enclosure_device *edev;
|
|
};
|
|
|
|
#if 0
|
|
int ses_match_host(struct enclosure_device *edev, void *data)
|
|
{
|
|
struct ses_host_edev *sed = data;
|
|
struct scsi_device *sdev;
|
|
|
|
if (!scsi_is_sdev_device(edev->edev.parent))
|
|
return 0;
|
|
|
|
sdev = to_scsi_device(edev->edev.parent);
|
|
|
|
if (sdev->host != sed->shost)
|
|
return 0;
|
|
|
|
sed->edev = edev;
|
|
return 1;
|
|
}
|
|
#endif /* 0 */
|
|
|
|
static int ses_process_descriptor(struct enclosure_component *ecomp,
|
|
unsigned char *desc, int max_desc_len)
|
|
{
|
|
int eip = desc[0] & 0x10;
|
|
int invalid = desc[0] & 0x80;
|
|
enum scsi_protocol proto = desc[0] & 0x0f;
|
|
u64 addr = 0;
|
|
int slot = -1;
|
|
struct ses_component *scomp = ecomp->scratch;
|
|
unsigned char *d;
|
|
|
|
if (invalid)
|
|
return 0;
|
|
|
|
switch (proto) {
|
|
case SCSI_PROTOCOL_FCP:
|
|
if (eip) {
|
|
if (max_desc_len <= 7)
|
|
return 1;
|
|
d = desc + 4;
|
|
slot = d[3];
|
|
}
|
|
break;
|
|
case SCSI_PROTOCOL_SAS:
|
|
|
|
if (eip) {
|
|
if (max_desc_len <= 27)
|
|
return 1;
|
|
d = desc + 4;
|
|
slot = d[3];
|
|
d = desc + 8;
|
|
} else {
|
|
if (max_desc_len <= 23)
|
|
return 1;
|
|
d = desc + 4;
|
|
}
|
|
|
|
|
|
/* only take the phy0 addr */
|
|
addr = (u64)d[12] << 56 |
|
|
(u64)d[13] << 48 |
|
|
(u64)d[14] << 40 |
|
|
(u64)d[15] << 32 |
|
|
(u64)d[16] << 24 |
|
|
(u64)d[17] << 16 |
|
|
(u64)d[18] << 8 |
|
|
(u64)d[19];
|
|
break;
|
|
default:
|
|
/* FIXME: Need to add more protocols than just SAS */
|
|
break;
|
|
}
|
|
ecomp->slot = slot;
|
|
scomp->addr = addr;
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct efd {
|
|
u64 addr;
|
|
struct device *dev;
|
|
};
|
|
|
|
static int ses_enclosure_find_by_addr(struct enclosure_device *edev,
|
|
void *data)
|
|
{
|
|
struct efd *efd = data;
|
|
int i;
|
|
struct ses_component *scomp;
|
|
|
|
if (!edev->component[0].scratch)
|
|
return 0;
|
|
|
|
for (i = 0; i < edev->components; i++) {
|
|
scomp = edev->component[i].scratch;
|
|
if (scomp->addr != efd->addr)
|
|
continue;
|
|
|
|
if (enclosure_add_device(edev, i, efd->dev) == 0)
|
|
kobject_uevent(&efd->dev->kobj, KOBJ_CHANGE);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define INIT_ALLOC_SIZE 32
|
|
|
|
static void ses_enclosure_data_process(struct enclosure_device *edev,
|
|
struct scsi_device *sdev,
|
|
int create)
|
|
{
|
|
u32 result;
|
|
unsigned char *buf = NULL, *type_ptr, *desc_ptr, *addl_desc_ptr = NULL;
|
|
int i, j, page7_len, len, components;
|
|
struct ses_device *ses_dev = edev->scratch;
|
|
int types = ses_dev->page1_num_types;
|
|
unsigned char *hdr_buf = kzalloc(INIT_ALLOC_SIZE, GFP_KERNEL);
|
|
|
|
if (!hdr_buf)
|
|
goto simple_populate;
|
|
|
|
/* re-read page 10 */
|
|
if (ses_dev->page10)
|
|
ses_recv_diag(sdev, 10, ses_dev->page10, ses_dev->page10_len);
|
|
/* Page 7 for the descriptors is optional */
|
|
result = ses_recv_diag(sdev, 7, hdr_buf, INIT_ALLOC_SIZE);
|
|
if (result)
|
|
goto simple_populate;
|
|
|
|
page7_len = len = (hdr_buf[2] << 8) + hdr_buf[3] + 4;
|
|
/* add 1 for trailing '\0' we'll use */
|
|
buf = kzalloc(len + 1, GFP_KERNEL);
|
|
if (!buf)
|
|
goto simple_populate;
|
|
result = ses_recv_diag(sdev, 7, buf, len);
|
|
if (result) {
|
|
simple_populate:
|
|
kfree(buf);
|
|
buf = NULL;
|
|
desc_ptr = NULL;
|
|
len = 0;
|
|
page7_len = 0;
|
|
} else {
|
|
desc_ptr = buf + 8;
|
|
len = (desc_ptr[2] << 8) + desc_ptr[3];
|
|
/* skip past overall descriptor */
|
|
desc_ptr += len + 4;
|
|
}
|
|
if (ses_dev->page10 && ses_dev->page10_len > 9)
|
|
addl_desc_ptr = ses_dev->page10 + 8;
|
|
type_ptr = ses_dev->page1_types;
|
|
components = 0;
|
|
for (i = 0; i < types; i++, type_ptr += 4) {
|
|
for (j = 0; j < type_ptr[1]; j++) {
|
|
char *name = NULL;
|
|
struct enclosure_component *ecomp;
|
|
int max_desc_len;
|
|
|
|
if (desc_ptr) {
|
|
if (desc_ptr + 3 >= buf + page7_len) {
|
|
desc_ptr = NULL;
|
|
} else {
|
|
len = (desc_ptr[2] << 8) + desc_ptr[3];
|
|
desc_ptr += 4;
|
|
if (desc_ptr + len > buf + page7_len)
|
|
desc_ptr = NULL;
|
|
else {
|
|
/* Add trailing zero - pushes into
|
|
* reserved space */
|
|
desc_ptr[len] = '\0';
|
|
name = desc_ptr;
|
|
}
|
|
}
|
|
}
|
|
if (type_ptr[0] == ENCLOSURE_COMPONENT_DEVICE ||
|
|
type_ptr[0] == ENCLOSURE_COMPONENT_ARRAY_DEVICE) {
|
|
|
|
if (create)
|
|
ecomp = enclosure_component_alloc(
|
|
edev,
|
|
components++,
|
|
type_ptr[0],
|
|
name);
|
|
else
|
|
ecomp = &edev->component[components++];
|
|
|
|
if (!IS_ERR(ecomp)) {
|
|
if (addl_desc_ptr) {
|
|
max_desc_len = ses_dev->page10_len -
|
|
(addl_desc_ptr - ses_dev->page10);
|
|
if (ses_process_descriptor(ecomp,
|
|
addl_desc_ptr,
|
|
max_desc_len))
|
|
addl_desc_ptr = NULL;
|
|
}
|
|
if (create)
|
|
enclosure_component_register(
|
|
ecomp);
|
|
}
|
|
}
|
|
if (desc_ptr)
|
|
desc_ptr += len;
|
|
|
|
if (addl_desc_ptr &&
|
|
/* only find additional descriptions for specific devices */
|
|
(type_ptr[0] == ENCLOSURE_COMPONENT_DEVICE ||
|
|
type_ptr[0] == ENCLOSURE_COMPONENT_ARRAY_DEVICE ||
|
|
type_ptr[0] == ENCLOSURE_COMPONENT_SAS_EXPANDER ||
|
|
/* these elements are optional */
|
|
type_ptr[0] == ENCLOSURE_COMPONENT_SCSI_TARGET_PORT ||
|
|
type_ptr[0] == ENCLOSURE_COMPONENT_SCSI_INITIATOR_PORT ||
|
|
type_ptr[0] == ENCLOSURE_COMPONENT_CONTROLLER_ELECTRONICS)) {
|
|
addl_desc_ptr += addl_desc_ptr[1] + 2;
|
|
if (addl_desc_ptr + 1 >= ses_dev->page10 + ses_dev->page10_len)
|
|
addl_desc_ptr = NULL;
|
|
}
|
|
}
|
|
}
|
|
kfree(buf);
|
|
kfree(hdr_buf);
|
|
}
|
|
|
|
static void ses_match_to_enclosure(struct enclosure_device *edev,
|
|
struct scsi_device *sdev,
|
|
int refresh)
|
|
{
|
|
struct scsi_device *edev_sdev = to_scsi_device(edev->edev.parent);
|
|
struct efd efd = {
|
|
.addr = 0,
|
|
};
|
|
|
|
if (refresh)
|
|
ses_enclosure_data_process(edev, edev_sdev, 0);
|
|
|
|
if (scsi_is_sas_rphy(sdev->sdev_target->dev.parent))
|
|
efd.addr = sas_get_address(sdev);
|
|
|
|
if (efd.addr) {
|
|
efd.dev = &sdev->sdev_gendev;
|
|
|
|
enclosure_for_each_device(ses_enclosure_find_by_addr, &efd);
|
|
}
|
|
}
|
|
|
|
static int ses_intf_add(struct device *cdev,
|
|
struct class_interface *intf)
|
|
{
|
|
struct scsi_device *sdev = to_scsi_device(cdev->parent);
|
|
struct scsi_device *tmp_sdev;
|
|
unsigned char *buf = NULL, *hdr_buf, *type_ptr, page;
|
|
struct ses_device *ses_dev;
|
|
u32 result;
|
|
int i, types, len, components = 0;
|
|
int err = -ENOMEM;
|
|
int num_enclosures;
|
|
struct enclosure_device *edev;
|
|
struct ses_component *scomp = NULL;
|
|
|
|
if (!scsi_device_enclosure(sdev)) {
|
|
/* not an enclosure, but might be in one */
|
|
struct enclosure_device *prev = NULL;
|
|
|
|
while ((edev = enclosure_find(&sdev->host->shost_gendev, prev)) != NULL) {
|
|
ses_match_to_enclosure(edev, sdev, 1);
|
|
prev = edev;
|
|
}
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* TYPE_ENCLOSURE prints a message in probe */
|
|
if (sdev->type != TYPE_ENCLOSURE)
|
|
sdev_printk(KERN_NOTICE, sdev, "Embedded Enclosure Device\n");
|
|
|
|
ses_dev = kzalloc(sizeof(*ses_dev), GFP_KERNEL);
|
|
hdr_buf = kzalloc(INIT_ALLOC_SIZE, GFP_KERNEL);
|
|
if (!hdr_buf || !ses_dev)
|
|
goto err_init_free;
|
|
|
|
page = 1;
|
|
result = ses_recv_diag(sdev, page, hdr_buf, INIT_ALLOC_SIZE);
|
|
if (result)
|
|
goto recv_failed;
|
|
|
|
len = (hdr_buf[2] << 8) + hdr_buf[3] + 4;
|
|
buf = kzalloc(len, GFP_KERNEL);
|
|
if (!buf)
|
|
goto err_free;
|
|
|
|
result = ses_recv_diag(sdev, page, buf, len);
|
|
if (result)
|
|
goto recv_failed;
|
|
|
|
types = 0;
|
|
|
|
/* we always have one main enclosure and the rest are referred
|
|
* to as secondary subenclosures */
|
|
num_enclosures = buf[1] + 1;
|
|
|
|
/* begin at the enclosure descriptor */
|
|
type_ptr = buf + 8;
|
|
/* skip all the enclosure descriptors */
|
|
for (i = 0; i < num_enclosures && type_ptr < buf + len; i++) {
|
|
types += type_ptr[2];
|
|
type_ptr += type_ptr[3] + 4;
|
|
}
|
|
|
|
ses_dev->page1_types = type_ptr;
|
|
ses_dev->page1_num_types = types;
|
|
|
|
for (i = 0; i < types && type_ptr < buf + len; i++, type_ptr += 4) {
|
|
if (type_ptr[0] == ENCLOSURE_COMPONENT_DEVICE ||
|
|
type_ptr[0] == ENCLOSURE_COMPONENT_ARRAY_DEVICE)
|
|
components += type_ptr[1];
|
|
}
|
|
|
|
if (components == 0) {
|
|
sdev_printk(KERN_WARNING, sdev, "enclosure has no enumerated components\n");
|
|
goto err_free;
|
|
}
|
|
|
|
ses_dev->page1 = buf;
|
|
ses_dev->page1_len = len;
|
|
buf = NULL;
|
|
|
|
page = 2;
|
|
result = ses_recv_diag(sdev, page, hdr_buf, INIT_ALLOC_SIZE);
|
|
if (result)
|
|
goto page2_not_supported;
|
|
|
|
len = (hdr_buf[2] << 8) + hdr_buf[3] + 4;
|
|
buf = kzalloc(len, GFP_KERNEL);
|
|
if (!buf)
|
|
goto err_free;
|
|
|
|
/* make sure getting page 2 actually works */
|
|
result = ses_recv_diag(sdev, 2, buf, len);
|
|
if (result)
|
|
goto recv_failed;
|
|
ses_dev->page2 = buf;
|
|
ses_dev->page2_len = len;
|
|
buf = NULL;
|
|
|
|
/* The additional information page --- allows us
|
|
* to match up the devices */
|
|
page = 10;
|
|
result = ses_recv_diag(sdev, page, hdr_buf, INIT_ALLOC_SIZE);
|
|
if (!result) {
|
|
|
|
len = (hdr_buf[2] << 8) + hdr_buf[3] + 4;
|
|
buf = kzalloc(len, GFP_KERNEL);
|
|
if (!buf)
|
|
goto err_free;
|
|
|
|
result = ses_recv_diag(sdev, page, buf, len);
|
|
if (result)
|
|
goto recv_failed;
|
|
ses_dev->page10 = buf;
|
|
ses_dev->page10_len = len;
|
|
buf = NULL;
|
|
}
|
|
page2_not_supported:
|
|
scomp = kcalloc(components, sizeof(struct ses_component), GFP_KERNEL);
|
|
if (!scomp)
|
|
goto err_free;
|
|
|
|
edev = enclosure_register(cdev->parent, dev_name(&sdev->sdev_gendev),
|
|
components, &ses_enclosure_callbacks);
|
|
if (IS_ERR(edev)) {
|
|
err = PTR_ERR(edev);
|
|
goto err_free;
|
|
}
|
|
|
|
kfree(hdr_buf);
|
|
|
|
edev->scratch = ses_dev;
|
|
for (i = 0; i < components; i++)
|
|
edev->component[i].scratch = scomp + i;
|
|
|
|
ses_enclosure_data_process(edev, sdev, 1);
|
|
|
|
/* see if there are any devices matching before
|
|
* we found the enclosure */
|
|
shost_for_each_device(tmp_sdev, sdev->host) {
|
|
if (tmp_sdev->lun != 0 || scsi_device_enclosure(tmp_sdev))
|
|
continue;
|
|
ses_match_to_enclosure(edev, tmp_sdev, 0);
|
|
}
|
|
|
|
return 0;
|
|
|
|
recv_failed:
|
|
sdev_printk(KERN_ERR, sdev, "Failed to get diagnostic page 0x%x\n",
|
|
page);
|
|
err = -ENODEV;
|
|
err_free:
|
|
kfree(buf);
|
|
kfree(scomp);
|
|
kfree(ses_dev->page10);
|
|
kfree(ses_dev->page2);
|
|
kfree(ses_dev->page1);
|
|
err_init_free:
|
|
kfree(ses_dev);
|
|
kfree(hdr_buf);
|
|
sdev_printk(KERN_ERR, sdev, "Failed to bind enclosure %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
static int ses_remove(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void ses_intf_remove_component(struct scsi_device *sdev)
|
|
{
|
|
struct enclosure_device *edev, *prev = NULL;
|
|
|
|
while ((edev = enclosure_find(&sdev->host->shost_gendev, prev)) != NULL) {
|
|
prev = edev;
|
|
if (!enclosure_remove_device(edev, &sdev->sdev_gendev))
|
|
break;
|
|
}
|
|
if (edev)
|
|
put_device(&edev->edev);
|
|
}
|
|
|
|
static void ses_intf_remove_enclosure(struct scsi_device *sdev)
|
|
{
|
|
struct enclosure_device *edev;
|
|
struct ses_device *ses_dev;
|
|
|
|
/* exact match to this enclosure */
|
|
edev = enclosure_find(&sdev->sdev_gendev, NULL);
|
|
if (!edev)
|
|
return;
|
|
|
|
ses_dev = edev->scratch;
|
|
edev->scratch = NULL;
|
|
|
|
kfree(ses_dev->page10);
|
|
kfree(ses_dev->page1);
|
|
kfree(ses_dev->page2);
|
|
kfree(ses_dev);
|
|
|
|
if (edev->components)
|
|
kfree(edev->component[0].scratch);
|
|
|
|
put_device(&edev->edev);
|
|
enclosure_unregister(edev);
|
|
}
|
|
|
|
static void ses_intf_remove(struct device *cdev,
|
|
struct class_interface *intf)
|
|
{
|
|
struct scsi_device *sdev = to_scsi_device(cdev->parent);
|
|
|
|
if (!scsi_device_enclosure(sdev))
|
|
ses_intf_remove_component(sdev);
|
|
else
|
|
ses_intf_remove_enclosure(sdev);
|
|
}
|
|
|
|
static struct class_interface ses_interface = {
|
|
.add_dev = ses_intf_add,
|
|
.remove_dev = ses_intf_remove,
|
|
};
|
|
|
|
static struct scsi_driver ses_template = {
|
|
.gendrv = {
|
|
.name = "ses",
|
|
.owner = THIS_MODULE,
|
|
.probe = ses_probe,
|
|
.remove = ses_remove,
|
|
},
|
|
};
|
|
|
|
static int __init ses_init(void)
|
|
{
|
|
int err;
|
|
|
|
err = scsi_register_interface(&ses_interface);
|
|
if (err)
|
|
return err;
|
|
|
|
err = scsi_register_driver(&ses_template.gendrv);
|
|
if (err)
|
|
goto out_unreg;
|
|
|
|
return 0;
|
|
|
|
out_unreg:
|
|
scsi_unregister_interface(&ses_interface);
|
|
return err;
|
|
}
|
|
|
|
static void __exit ses_exit(void)
|
|
{
|
|
scsi_unregister_driver(&ses_template.gendrv);
|
|
scsi_unregister_interface(&ses_interface);
|
|
}
|
|
|
|
module_init(ses_init);
|
|
module_exit(ses_exit);
|
|
|
|
MODULE_ALIAS_SCSI_DEVICE(TYPE_ENCLOSURE);
|
|
|
|
MODULE_AUTHOR("James Bottomley");
|
|
MODULE_DESCRIPTION("SCSI Enclosure Services (ses) driver");
|
|
MODULE_LICENSE("GPL v2");
|