mirror of
https://github.com/torvalds/linux.git
synced 2024-12-01 08:31:37 +00:00
556eb8b791
Here is the large set of driver core changes for 6.4-rc1. Once again, a busy development cycle, with lots of changes happening in the driver core in the quest to be able to move "struct bus" and "struct class" into read-only memory, a task now complete with these changes. This will make the future rust interactions with the driver core more "provably correct" as well as providing more obvious lifetime rules for all busses and classes in the kernel. The changes required for this did touch many individual classes and busses as many callbacks were changed to take const * parameters instead. All of these changes have been submitted to the various subsystem maintainers, giving them plenty of time to review, and most of them actually did so. Other than those changes, included in here are a small set of other things: - kobject logging improvements - cacheinfo improvements and updates - obligatory fw_devlink updates and fixes - documentation updates - device property cleanups and const * changes - firwmare loader dependency fixes. All of these have been in linux-next for a while with no reported problems. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCZEp7Sw8cZ3JlZ0Brcm9h aC5jb20ACgkQMUfUDdst+ykitQCfamUHpxGcKOAGuLXMotXNakTEsxgAoIquENm5 LEGadNS38k5fs+73UaxV =7K4B -----END PGP SIGNATURE----- Merge tag 'driver-core-6.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core Pull driver core updates from Greg KH: "Here is the large set of driver core changes for 6.4-rc1. Once again, a busy development cycle, with lots of changes happening in the driver core in the quest to be able to move "struct bus" and "struct class" into read-only memory, a task now complete with these changes. This will make the future rust interactions with the driver core more "provably correct" as well as providing more obvious lifetime rules for all busses and classes in the kernel. The changes required for this did touch many individual classes and busses as many callbacks were changed to take const * parameters instead. All of these changes have been submitted to the various subsystem maintainers, giving them plenty of time to review, and most of them actually did so. Other than those changes, included in here are a small set of other things: - kobject logging improvements - cacheinfo improvements and updates - obligatory fw_devlink updates and fixes - documentation updates - device property cleanups and const * changes - firwmare loader dependency fixes. All of these have been in linux-next for a while with no reported problems" * tag 'driver-core-6.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core: (120 commits) device property: make device_property functions take const device * driver core: update comments in device_rename() driver core: Don't require dynamic_debug for initcall_debug probe timing firmware_loader: rework crypto dependencies firmware_loader: Strip off \n from customized path zram: fix up permission for the hot_add sysfs file cacheinfo: Add use_arch[|_cache]_info field/function arch_topology: Remove early cacheinfo error message if -ENOENT cacheinfo: Check cache properties are present in DT cacheinfo: Check sib_leaf in cache_leaves_are_shared() cacheinfo: Allow early level detection when DT/ACPI info is missing/broken cacheinfo: Add arm64 early level initializer implementation cacheinfo: Add arch specific early level initializer tty: make tty_class a static const structure driver core: class: remove struct class_interface * from callbacks driver core: class: mark the struct class in struct class_interface constant driver core: class: make class_register() take a const * driver core: class: mark class_release() as taking a const * driver core: remove incorrect comment for device_create* MIPS: vpe-cmp: remove module owner pointer from struct class usage. ...
924 lines
21 KiB
C
924 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;
|
|
|
|
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 if (components < edev->components)
|
|
ecomp = &edev->component[components++];
|
|
else
|
|
ecomp = ERR_PTR(-EINVAL);
|
|
|
|
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 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];
|
|
}
|
|
|
|
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:
|
|
if (components > 0) {
|
|
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 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");
|