// 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 <linux/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;
struct scsi_failure failure_defs[] = {
{
.sense = UNIT_ATTENTION,
.asc = 0x29,
.ascq = SCMD_FAILURE_ASCQ_ANY,
.allowed = SES_RETRIES,
.result = SAM_STAT_CHECK_CONDITION,
},
{
.sense = NOT_READY,
.asc = SCMD_FAILURE_ASC_ANY,
.ascq = SCMD_FAILURE_ASCQ_ANY,
.allowed = SES_RETRIES,
.result = SAM_STAT_CHECK_CONDITION,
},
{}
};
struct scsi_failures failures = {
.failure_definitions = failure_defs,
};
const struct scsi_exec_args exec_args = {
.failures = &failures,
};
ret = scsi_execute_cmd(sdev, cmd, REQ_OP_DRV_IN, buf, bufflen,
SES_TIMEOUT, 1, &exec_args);
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_failure failure_defs[] = {
{
.sense = UNIT_ATTENTION,
.asc = 0x29,
.ascq = SCMD_FAILURE_ASCQ_ANY,
.allowed = SES_RETRIES,
.result = SAM_STAT_CHECK_CONDITION,
},
{
.sense = NOT_READY,
.asc = SCMD_FAILURE_ASC_ANY,
.ascq = SCMD_FAILURE_ASCQ_ANY,
.allowed = SES_RETRIES,
.result = SAM_STAT_CHECK_CONDITION,
},
{}
};
struct scsi_failures failures = {
.failure_definitions = failure_defs,
};
const struct scsi_exec_args exec_args = {
.failures = &failures