mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
893 lines
20 KiB
893 lines
20 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* SCSI Enclosure Services |
|
* |
|
* Copyright (C) 2008 James Bottomley <[email protected]> |
|
*/ |
|
|
|
#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; |
|
|
|
do { |
|
ret = scsi_execute_req(sdev, cmd, DMA_FROM_DEVICE, buf, bufflen, |
|
&sshdr, SES_TIMEOUT, 1, NULL); |
|
} 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; |
|
|
|
do { |
|
result = scsi_execute_req(sdev, cmd, DMA_TO_DEVICE, buf, bufflen, |
|
&sshdr, SES_TIMEOUT, 1, NULL); |
|
} 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 void ses_process_descriptor(struct enclosure_component *ecomp, |
|
unsigned char *desc) |
|
{ |
|
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; |
|
|
|
switch (proto) { |
|
case SCSI_PROTOCOL_FCP: |
|
if (eip) { |
|
d = desc + 4; |
|
slot = d[3]; |
|
} |
|
break; |
|
case SCSI_PROTOCOL_SAS: |
|
if (eip) { |
|
d = desc + 4; |
|
slot = d[3]; |
|
d = desc + 8; |
|
} else |
|
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; |
|
} |
|
|
|
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) |
|
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; |
|
|
|
if (desc_ptr) { |
|
if (desc_ptr >= buf + page7_len) { |
|
desc_ptr = NULL; |
|
} else { |
|
len = (desc_ptr[2] << 8) + desc_ptr[3]; |
|
desc_ptr += 4; |
|
/* 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) |
|
ses_process_descriptor( |
|
ecomp, |
|
addl_desc_ptr); |
|
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; |
|
|
|
} |
|
} |
|
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]; |
|
} |
|
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); |
|
|
|
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");
|
|
|