Add support for ATA command pass-through to SCSI devices.

This commit is contained in:
mlelstv 2019-05-30 21:32:08 +00:00
parent 5a254e06b6
commit c2d06938d9

View File

@ -1,4 +1,4 @@
/* $NetBSD: atactl.c,v 1.82 2019/03/03 04:51:57 mrg Exp $ */
/* $NetBSD: atactl.c,v 1.83 2019/05/30 21:32:08 mlelstv Exp $ */
/*-
* Copyright (c) 1998, 2019 The NetBSD Foundation, Inc.
@ -35,7 +35,7 @@
#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: atactl.c,v 1.82 2019/03/03 04:51:57 mrg Exp $");
__RCSID("$NetBSD: atactl.c,v 1.83 2019/05/30 21:32:08 mlelstv Exp $");
#endif
@ -54,6 +54,9 @@ __RCSID("$NetBSD: atactl.c,v 1.82 2019/03/03 04:51:57 mrg Exp $");
#include <dev/ata/atareg.h>
#include <sys/ataio.h>
#include <dev/scsipi/scsi_spc.h>
#include <sys/scsiio.h>
struct ata_smart_error {
struct {
uint8_t device_control;
@ -90,6 +93,53 @@ struct ata_smart_errorlog {
uint8_t checksum;
} __packed;
#define SCSI_ATA_PASS_THROUGH_16 0x85
struct scsi_ata_pass_through_16 {
uint8_t opcode;
uint8_t byte2;
#define SATL_NODATA 0x06
#define SATL_PIO_IN 0x08
#define SATL_PIO_OUT 0x0a
#define SATL_EXTEND 0x01
uint8_t byte3;
#define SATL_CKCOND 0x20
#define SATL_READ 0x08
#define SATL_BLOCKS 0x04
#define SATL_LEN(x) ((x) & 0x03)
uint8_t features[2];
uint8_t sector_count[2];
uint8_t lba[6];
uint8_t device;
uint8_t ata_cmd;
uint8_t control;
} __packed;
#define SCSI_ATA_PASS_THROUGH_12 0xa1
struct scsi_ata_pass_through_12 {
uint8_t opcode;
uint8_t byte2;
uint8_t byte3;
uint8_t features[1];
uint8_t sector_count[1];
uint8_t lba[3];
uint8_t device;
uint8_t ata_cmd;
uint8_t reserved;
uint8_t control;
} __packed;
struct scsi_ata_return_descriptor {
uint8_t descr;
#define SCSI_ATA_RETURN_DESCRIPTOR 9
uint8_t additional_length;
uint8_t extend;
uint8_t error;
uint8_t sector_count[2];
uint8_t lba[6];
uint8_t device;
uint8_t status;
} __packed;
struct command {
const char *cmd_name;
const char *arg_names;
@ -103,6 +153,8 @@ struct bitinfo {
__dead static void usage(void);
static void ata_command(struct atareq *);
static int satl_command(struct atareq *, int);
static const uint8_t *satl_return_desc(const uint8_t *, size_t, uint8_t);
static void print_bitinfo(const char *, const char *, u_int,
const struct bitinfo *);
static void print_bitinfo2(const char *, const char *, u_int, u_int,
@ -119,6 +171,7 @@ static void fillataparams(void);
static int is_smart(void);
static int fd; /* file descriptor for device */
static int use_satl; /* tunnel through SATL */
static const char *dvname; /* device name */
static char dvname_store[MAXPATHLEN]; /* for opendisk(3) */
static const char *cmdname; /* command user issued */
@ -531,10 +584,25 @@ ata_command(struct atareq *req)
{
int error;
error = ioctl(fd, ATAIOCCOMMAND, req);
if (error == -1)
err(1, "ATAIOCCOMMAND failed");
switch (use_satl) {
case 0:
error = ioctl(fd, ATAIOCCOMMAND, req);
if (error == 0)
break;
if (errno != ENOTTY)
err(1, "ATAIOCCOMMAND failed");
use_satl = 1;
/* FALLTHROUGH */
case 1:
error = satl_command(req, 16);
if (error == 0)
return;
use_satl = 2;
/* FALLTHROUGH */
case 2:
(void) satl_command(req, 12);
return;
}
switch (req->retsts) {
@ -561,6 +629,191 @@ ata_command(struct atareq *req)
}
}
/*
* Wrapper that calls SCIOCCOMMAND for a tunneled ATA command
*/
static int
satl_command(struct atareq *req, int cmdlen)
{
scsireq_t sreq;
int error;
union {
struct scsi_ata_pass_through_12 cmd12;
struct scsi_ata_pass_through_16 cmd16;
} c;
uint8_t b2, b3;
const uint8_t *desc;
b2 = SATL_NODATA;
if (req->datalen > 0) {
if (req->flags & ATACMD_READ)
b2 = SATL_PIO_IN;
else
b2 = SATL_PIO_OUT;
}
b3 = SATL_BLOCKS;
if (req->datalen > 0) {
b3 |= 2; /* sector count holds count */
} else {
b3 |= SATL_CKCOND;
}
if (req->datalen == 0 || req->flags & ATACMD_READ)
b3 |= SATL_READ;
switch (cmdlen) {
case 16:
c.cmd16.opcode = SCSI_ATA_PASS_THROUGH_16;
c.cmd16.byte2 = b2;
c.cmd16.byte3 = b3;
c.cmd16.features[0] = 0;
c.cmd16.features[1] = req->features;
c.cmd16.sector_count[0] = 0;
c.cmd16.sector_count[1] = req->sec_count;
c.cmd16.lba[0] = 0;
c.cmd16.lba[1] = req->sec_num;
c.cmd16.lba[2] = 0;
c.cmd16.lba[3] = req->cylinder;
c.cmd16.lba[4] = 0;
c.cmd16.lba[5] = req->cylinder >> 8;
c.cmd16.device = 0;
c.cmd16.ata_cmd = req->command;
c.cmd16.control = 0;
break;
case 12:
c.cmd12.opcode = SCSI_ATA_PASS_THROUGH_12;
c.cmd12.byte2 = b2;
c.cmd12.byte3 = b3;
c.cmd12.features[0] = req->features;
c.cmd12.sector_count[0] = req->sec_count;
c.cmd12.lba[0] = req->sec_num;
c.cmd12.lba[1] = req->cylinder;
c.cmd12.lba[2] = req->cylinder >> 8;
c.cmd12.device = 0;
c.cmd12.reserved = 0;
c.cmd12.ata_cmd = req->command;
c.cmd12.control = 0;
break;
default:
fprintf(stderr, "ATA command with bad length\n");
exit(1);
}
memset(&sreq, 0, sizeof(sreq));
memcpy(sreq.cmd, &c, cmdlen);
sreq.cmdlen = cmdlen;
sreq.databuf = req->databuf;
sreq.datalen = req->datalen;
sreq.senselen = sizeof(sreq.sense);
sreq.timeout = req->timeout;
if (sreq.datalen > 0) {
if (req->flags & ATACMD_READ)
sreq.flags |= SCCMD_READ;
if (req->flags & ATACMD_WRITE)
sreq.flags |= SCCMD_WRITE;
}
error = ioctl(fd, SCIOCCOMMAND, &sreq);
if (error == -1)
err(1, "SCIOCCOMMAND failed");
req->datalen = sreq.datalen_used;
req->retsts = ATACMD_OK;
req->error = 0;
switch (sreq.retsts) {
case SCCMD_OK:
return 0;
case SCCMD_TIMEOUT:
fprintf(stderr, "SATL command timed out\n");
exit(1);
case SCCMD_BUSY:
fprintf(stderr, "SATL command returned busy\n");
exit(1);
case SCCMD_SENSE:
desc = NULL;
switch (SSD_RCODE(sreq.sense[0])) {
case 0x00:
return 0;
case 0x70:
if (sreq.sense[2] == SKEY_NO_SENSE)
return 0;
if (sreq.sense[2] == SKEY_ILLEGAL_REQUEST)
return 1;
break;
case 0x72:
case 0x73:
desc = satl_return_desc(sreq.sense, sreq.senselen_used,
SCSI_ATA_RETURN_DESCRIPTOR);
break;
default:
break;
}
if (desc && desc[1] >= 12) {
req->sec_count = desc[5];
req->sec_num = desc[7];
req->head = (desc[12] & 0xf0) |
((desc[7] >> 24) & 0x0f);
req->cylinder = desc[11] << 8 | desc[9];
req->retsts = desc[13];
req->error = desc[3];
return 0;
}
fprintf(stderr, "SATL command error: rcode %02x key %u\n",
SSD_RCODE(sreq.sense[0]),
SSD_SENSE_KEY(sreq.sense[2]));
if (desc) {
int i, n;
n = desc[1]+2;
printf("ATA Return Descriptor:");
for (i=0; i<n; ++i)
printf(" %02x",desc[i]);
printf("\n");
}
exit(1);
default:
fprintf(stderr, "SCSIIOCCOMMAND returned unknown result code "
"%d\n", sreq.retsts);
exit(1);
}
}
static const uint8_t *
satl_return_desc(const uint8_t *sense, size_t len, uint8_t type)
{
const uint8_t *p, *endp;
size_t l, extra;
if (len < 8)
return NULL;
extra = sense[7];
len -= 8;
if (extra < len)
len = extra;
if (len < 2)
return NULL;
switch (sense[0]) {
case 0x72:
case 0x73:
p = &sense[8];
endp = &p[len-1];
while (p < endp) {
if (p[0] == type)
return p;
l = p[1];
p += l + 2;
}
break;
}
return NULL;
}
/*
* Print out strings associated with particular bitmasks
*/