#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/pool.h>
#include <scsi/scsi_all.h>
#include <scsi/scsi_disk.h>
#include <scsi/scsiconf.h>
#include <dev/ata/atascsi.h>
#include <dev/ata/pmreg.h>
struct atascsi_port;
struct atascsi {
struct device *as_dev;
void *as_cookie;
struct atascsi_host_port **as_host_ports;
const struct atascsi_methods *as_methods;
struct scsi_adapter as_switch;
struct scsibus_softc *as_scsibus;
int as_capability;
int as_ncqdepth;
};
struct atascsi_host_port {
struct scsi_iopool ahp_iopool;
struct atascsi *ahp_as;
int ahp_port;
int ahp_nports;
struct atascsi_port **ahp_ports;
};
struct atascsi_port {
struct ata_identify ap_identify;
struct atascsi_host_port *ap_host_port;
struct atascsi *ap_as;
int ap_pmp_port;
int ap_type;
int ap_ncqdepth;
int ap_features;
#define ATA_PORT_F_NCQ 0x1
#define ATA_PORT_F_TRIM 0x2
};
void atascsi_cmd(struct scsi_xfer *);
int atascsi_probe(struct scsi_link *);
void atascsi_free(struct scsi_link *);
const struct scsi_adapter atascsi_switch = {
atascsi_cmd, NULL, atascsi_probe, atascsi_free, NULL
};
void ata_swapcopy(void *, void *, size_t);
void atascsi_disk_cmd(struct scsi_xfer *);
void atascsi_disk_cmd_done(struct ata_xfer *);
void atascsi_disk_inq(struct scsi_xfer *);
void atascsi_disk_inquiry(struct scsi_xfer *);
void atascsi_disk_vpd_supported(struct scsi_xfer *);
void atascsi_disk_vpd_serial(struct scsi_xfer *);
void atascsi_disk_vpd_ident(struct scsi_xfer *);
void atascsi_disk_vpd_ata(struct scsi_xfer *);
void atascsi_disk_vpd_limits(struct scsi_xfer *);
void atascsi_disk_vpd_info(struct scsi_xfer *);
void atascsi_disk_vpd_thin(struct scsi_xfer *);
void atascsi_disk_write_same_16(struct scsi_xfer *);
void atascsi_disk_write_same_16_done(struct ata_xfer *);
void atascsi_disk_unmap(struct scsi_xfer *);
void atascsi_disk_unmap_task(void *);
void atascsi_disk_unmap_done(struct ata_xfer *);
void atascsi_disk_capacity(struct scsi_xfer *);
void atascsi_disk_capacity16(struct scsi_xfer *);
void atascsi_disk_sync(struct scsi_xfer *);
void atascsi_disk_sync_done(struct ata_xfer *);
void atascsi_disk_sense(struct scsi_xfer *);
void atascsi_disk_start_stop(struct scsi_xfer *);
void atascsi_disk_start_stop_done(struct ata_xfer *);
void atascsi_atapi_cmd(struct scsi_xfer *);
void atascsi_atapi_cmd_done(struct ata_xfer *);
void atascsi_pmp_cmd(struct scsi_xfer *);
void atascsi_pmp_sense(struct scsi_xfer *xs);
void atascsi_pmp_inq(struct scsi_xfer *xs);
void atascsi_passthru_12(struct scsi_xfer *);
void atascsi_passthru_16(struct scsi_xfer *);
int atascsi_passthru_map(struct scsi_xfer *, u_int8_t, u_int8_t);
void atascsi_passthru_done(struct ata_xfer *);
void atascsi_done(struct scsi_xfer *, int);
void ata_exec(struct atascsi *, struct ata_xfer *);
void ata_polled_complete(struct ata_xfer *);
int ata_polled(struct ata_xfer *);
u_int64_t ata_identify_blocks(struct ata_identify *);
u_int ata_identify_blocksize(struct ata_identify *);
u_int ata_identify_block_l2p_exp(struct ata_identify *);
u_int ata_identify_block_logical_align(struct ata_identify *);
void *atascsi_io_get(void *);
void atascsi_io_put(void *, void *);
struct atascsi_port * atascsi_lookup_port(struct scsi_link *);
int atascsi_port_identify(struct atascsi_port *,
struct ata_identify *);
int atascsi_port_set_features(struct atascsi_port *, int, int);
struct atascsi *
atascsi_attach(struct device *self, struct atascsi_attach_args *aaa)
{
struct scsibus_attach_args saa;
struct atascsi *as;
as = malloc(sizeof(*as), M_DEVBUF, M_WAITOK | M_ZERO);
as->as_dev = self;
as->as_cookie = aaa->aaa_cookie;
as->as_methods = aaa->aaa_methods;
as->as_capability = aaa->aaa_capability;
as->as_ncqdepth = aaa->aaa_ncmds;
as->as_switch = atascsi_switch;
if (aaa->aaa_minphys != NULL)
as->as_switch.dev_minphys = aaa->aaa_minphys;
as->as_host_ports = mallocarray(aaa->aaa_nports,
sizeof(struct atascsi_host_port *), M_DEVBUF, M_WAITOK | M_ZERO);
saa.saa_adapter = &as->as_switch;
saa.saa_adapter_softc = as;
saa.saa_adapter_buswidth = aaa->aaa_nports;
saa.saa_luns = SATA_PMP_MAX_PORTS;
saa.saa_adapter_target = SDEV_NO_ADAPTER_TARGET;
saa.saa_openings = 1;
saa.saa_pool = NULL;
saa.saa_quirks = saa.saa_flags = 0;
saa.saa_wwpn = saa.saa_wwnn = 0;
as->as_scsibus = (struct scsibus_softc *)config_found(self, &saa,
scsiprint);
return (as);
}
int
atascsi_detach(struct atascsi *as, int flags)
{
int rv;
rv = config_detach((struct device *)as->as_scsibus, flags);
if (rv != 0)
return (rv);
free(as->as_host_ports, M_DEVBUF, 0);
free(as, M_DEVBUF, sizeof(*as));
return (0);
}
struct atascsi_port *
atascsi_lookup_port(struct scsi_link *link)
{
struct atascsi *as = link->bus->sb_adapter_softc;
struct atascsi_host_port *ahp;
if (link->target >= link->bus->sb_adapter_buswidth)
return (NULL);
ahp = as->as_host_ports[link->target];
if (link->lun >= ahp->ahp_nports)
return (NULL);
return (ahp->ahp_ports[link->lun]);
}
int
atascsi_probe(struct scsi_link *link)
{
struct atascsi *as = link->bus->sb_adapter_softc;
struct atascsi_host_port *ahp;
struct atascsi_port *ap;
struct ata_xfer *xa;
struct ata_identify *identify;
int port, type, qdepth;
int rv;
u_int16_t cmdset;
u_int16_t validinfo, ultradma;
int i, xfermode = -1;
port = link->target;
if (port >= link->bus->sb_adapter_buswidth)
return (ENXIO);
if (link->lun > 0) {
if (link->lun >= as->as_host_ports[port]->ahp_nports)
return (ENXIO);
}
type = as->as_methods->ata_probe(as->as_cookie, port, link->lun);
switch (type) {
case ATA_PORT_T_DISK:
break;
case ATA_PORT_T_ATAPI:
link->flags |= SDEV_ATAPI;
break;
case ATA_PORT_T_PM:
if (link->lun != 0) {
printf("%s.%d.%d: Port multipliers cannot be nested\n",
as->as_dev->dv_xname, port, link->lun);
rv = ENODEV;
goto unsupported;
}
break;
default:
rv = ENODEV;
goto unsupported;
}
ap = malloc(sizeof(*ap), M_DEVBUF, M_WAITOK | M_ZERO);
ap->ap_as = as;
if (link->lun == 0) {
ahp = malloc(sizeof(*ahp), M_DEVBUF, M_WAITOK | M_ZERO);
ahp->ahp_as = as;
ahp->ahp_port = port;
scsi_iopool_init(&ahp->ahp_iopool, ahp, atascsi_io_get,
atascsi_io_put);
as->as_host_ports[port] = ahp;
if (type == ATA_PORT_T_PM) {
ahp->ahp_nports = SATA_PMP_MAX_PORTS;
ap->ap_pmp_port = SATA_PMP_CONTROL_PORT;
} else {
ahp->ahp_nports = 1;
ap->ap_pmp_port = 0;
}
ahp->ahp_ports = mallocarray(ahp->ahp_nports,
sizeof(struct atascsi_port *), M_DEVBUF, M_WAITOK | M_ZERO);
} else {
ahp = as->as_host_ports[port];
ap->ap_pmp_port = link->lun - 1;
}
ap->ap_host_port = ahp;
ap->ap_type = type;
link->pool = &ahp->ahp_iopool;
if (type != ATA_PORT_T_PM) {
identify = dma_alloc(sizeof(*identify), PR_WAITOK | PR_ZERO);
int count = (link->lun > 0) ? 6 : 2;
while (count--) {
rv = atascsi_port_identify(ap, identify);
if (rv == 0) {
ap->ap_identify = *identify;
break;
}
if (count > 0)
delay(5000000);
}
dma_free(identify, sizeof(*identify));
if (rv != 0) {
goto error;
}
}
ahp->ahp_ports[link->lun] = ap;
if (type != ATA_PORT_T_DISK)
return (0);
validinfo = letoh16(ap->ap_identify.validinfo);
if (ISSET(validinfo, ATA_ID_VALIDINFO_ULTRADMA)) {
ultradma = letoh16(ap->ap_identify.ultradma);
for (i = 7; i >= 0; i--) {
if (ultradma & (1 << i)) {
xfermode = ATA_SF_XFERMODE_UDMA | i;
break;
}
}
}
if (xfermode != -1)
(void)atascsi_port_set_features(ap, ATA_SF_XFERMODE, xfermode);
if (as->as_capability & ASAA_CAP_NCQ &&
ISSET(letoh16(ap->ap_identify.satacap), ATA_SATACAP_NCQ) &&
(link->lun == 0 || as->as_capability & ASAA_CAP_PMP_NCQ)) {
ap->ap_ncqdepth = ATA_QDEPTH(letoh16(ap->ap_identify.qdepth));
qdepth = MIN(ap->ap_ncqdepth, as->as_ncqdepth);
if (ISSET(as->as_capability, ASAA_CAP_NEEDS_RESERVED))
qdepth--;
if (qdepth > 1) {
SET(ap->ap_features, ATA_PORT_F_NCQ);
link->openings = qdepth;
if (link->lun == 0) {
while (qdepth--) {
xa = scsi_io_get(&ahp->ahp_iopool, SCSI_NOSLEEP);
if (xa->tag < link->openings) {
xa->state = ATA_S_COMPLETE;
scsi_io_put(&ahp->ahp_iopool, xa);
}
}
}
}
}
if (ISSET(letoh16(ap->ap_identify.data_set_mgmt),
ATA_ID_DATA_SET_MGMT_TRIM))
SET(ap->ap_features, ATA_PORT_F_TRIM);
cmdset = letoh16(ap->ap_identify.cmdset82);
if (ISSET(cmdset, ATA_IDENTIFY_WRITECACHE)) {
(void)atascsi_port_set_features(ap, ATA_SF_WRITECACHE_EN, 0);
}
if (ISSET(cmdset, ATA_IDENTIFY_LOOKAHEAD)) {
(void)atascsi_port_set_features(ap, ATA_SF_LOOKAHEAD_EN, 0);
}
xa = scsi_io_get(&ahp->ahp_iopool, SCSI_NOSLEEP);
if (xa == NULL)
panic("no free xfers on a new port");
xa->fis->command = ATA_C_SEC_FREEZE_LOCK;
xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
xa->flags = ATA_F_POLL;
xa->timeout = 1000;
xa->complete = ata_polled_complete;
xa->pmp_port = ap->ap_pmp_port;
xa->atascsi_private = &ahp->ahp_iopool;
ata_exec(as, xa);
ata_polled(xa);
return (0);
error:
free(ap, M_DEVBUF, sizeof(*ap));
unsupported:
as->as_methods->ata_free(as->as_cookie, port, link->lun);
return (rv);
}
void
atascsi_free(struct scsi_link *link)
{
struct atascsi *as = link->bus->sb_adapter_softc;
struct atascsi_host_port *ahp;
struct atascsi_port *ap;
int port;
port = link->target;
if (port >= link->bus->sb_adapter_buswidth)
return;
ahp = as->as_host_ports[port];
if (ahp == NULL)
return;
if (link->lun >= ahp->ahp_nports)
return;
ap = ahp->ahp_ports[link->lun];
free(ap, M_DEVBUF, sizeof(*ap));
ahp->ahp_ports[link->lun] = NULL;
as->as_methods->ata_free(as->as_cookie, port, link->lun);
if (link->lun == ahp->ahp_nports - 1) {
free(ahp, M_DEVBUF, sizeof(*ahp));
as->as_host_ports[port] = NULL;
}
}
void
atascsi_cmd(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi_port *ap;
ap = atascsi_lookup_port(link);
if (ap == NULL) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
switch (ap->ap_type) {
case ATA_PORT_T_DISK:
atascsi_disk_cmd(xs);
break;
case ATA_PORT_T_ATAPI:
atascsi_atapi_cmd(xs);
break;
case ATA_PORT_T_PM:
atascsi_pmp_cmd(xs);
break;
case ATA_PORT_T_NONE:
default:
atascsi_done(xs, XS_DRIVER_STUFFUP);
break;
}
}
void
atascsi_disk_cmd(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi *as = link->bus->sb_adapter_softc;
struct atascsi_port *ap;
struct ata_xfer *xa = xs->io;
int flags = 0;
struct ata_fis_h2d *fis;
u_int64_t lba;
u_int32_t sector_count;
ap = atascsi_lookup_port(link);
switch (xs->cmd.opcode) {
case READ_COMMAND:
case READ_10:
case READ_12:
case READ_16:
flags = ATA_F_READ;
break;
case WRITE_COMMAND:
case WRITE_10:
case WRITE_12:
case WRITE_16:
flags = ATA_F_WRITE;
break;
case WRITE_SAME_16:
atascsi_disk_write_same_16(xs);
return;
case UNMAP:
atascsi_disk_unmap(xs);
return;
case SYNCHRONIZE_CACHE:
atascsi_disk_sync(xs);
return;
case REQUEST_SENSE:
atascsi_disk_sense(xs);
return;
case INQUIRY:
atascsi_disk_inq(xs);
return;
case READ_CAPACITY:
atascsi_disk_capacity(xs);
return;
case READ_CAPACITY_16:
atascsi_disk_capacity16(xs);
return;
case ATA_PASSTHRU_12:
atascsi_passthru_12(xs);
return;
case ATA_PASSTHRU_16:
atascsi_passthru_16(xs);
return;
case START_STOP:
atascsi_disk_start_stop(xs);
return;
case TEST_UNIT_READY:
case PREVENT_ALLOW:
atascsi_done(xs, XS_NOERROR);
return;
default:
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
xa->flags = flags;
scsi_cmd_rw_decode(&xs->cmd, &lba, §or_count);
if ((lba >> 48) != 0 || (sector_count >> 16) != 0) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
fis = xa->fis;
fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
fis->lba_low = lba & 0xff;
fis->lba_mid = (lba >> 8) & 0xff;
fis->lba_high = (lba >> 16) & 0xff;
if (ISSET(ap->ap_features, ATA_PORT_F_NCQ) &&
(xa->tag < ap->ap_ncqdepth) &&
!(xs->flags & SCSI_POLL)) {
xa->flags |= ATA_F_NCQ;
fis->command = (xa->flags & ATA_F_WRITE) ?
ATA_C_WRITE_FPDMA : ATA_C_READ_FPDMA;
fis->device = ATA_H2D_DEVICE_LBA;
fis->lba_low_exp = (lba >> 24) & 0xff;
fis->lba_mid_exp = (lba >> 32) & 0xff;
fis->lba_high_exp = (lba >> 40) & 0xff;
fis->sector_count = xa->tag << 3;
fis->features = sector_count & 0xff;
fis->features_exp = (sector_count >> 8) & 0xff;
} else if (sector_count > 0x100 || lba > 0xfffffff) {
fis->command = (xa->flags & ATA_F_WRITE) ?
ATA_C_WRITEDMA_EXT : ATA_C_READDMA_EXT;
fis->device = ATA_H2D_DEVICE_LBA;
fis->lba_low_exp = (lba >> 24) & 0xff;
fis->lba_mid_exp = (lba >> 32) & 0xff;
fis->lba_high_exp = (lba >> 40) & 0xff;
fis->sector_count = sector_count & 0xff;
fis->sector_count_exp = (sector_count >> 8) & 0xff;
} else {
fis->command = (xa->flags & ATA_F_WRITE) ?
ATA_C_WRITEDMA : ATA_C_READDMA;
fis->device = ATA_H2D_DEVICE_LBA | ((lba >> 24) & 0x0f);
fis->sector_count = sector_count & 0xff;
}
xa->data = xs->data;
xa->datalen = xs->datalen;
xa->complete = atascsi_disk_cmd_done;
xa->timeout = xs->timeout;
xa->pmp_port = ap->ap_pmp_port;
xa->atascsi_private = xs;
if (xs->flags & SCSI_POLL)
xa->flags |= ATA_F_POLL;
ata_exec(as, xa);
}
void
atascsi_disk_cmd_done(struct ata_xfer *xa)
{
struct scsi_xfer *xs = xa->atascsi_private;
switch (xa->state) {
case ATA_S_COMPLETE:
xs->error = XS_NOERROR;
break;
case ATA_S_ERROR:
xs->error = XS_DRIVER_STUFFUP;
break;
case ATA_S_TIMEOUT:
xs->error = XS_TIMEOUT;
break;
default:
panic("atascsi_disk_cmd_done: unexpected ata_xfer state (%d)",
xa->state);
}
xs->resid = xa->resid;
scsi_done(xs);
}
void
atascsi_disk_inq(struct scsi_xfer *xs)
{
struct scsi_inquiry *inq = (struct scsi_inquiry *)&xs->cmd;
if (xs->cmdlen != sizeof(*inq)) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
if (ISSET(inq->flags, SI_EVPD)) {
switch (inq->pagecode) {
case SI_PG_SUPPORTED:
atascsi_disk_vpd_supported(xs);
break;
case SI_PG_SERIAL:
atascsi_disk_vpd_serial(xs);
break;
case SI_PG_DEVID:
atascsi_disk_vpd_ident(xs);
break;
case SI_PG_ATA:
atascsi_disk_vpd_ata(xs);
break;
case SI_PG_DISK_LIMITS:
atascsi_disk_vpd_limits(xs);
break;
case SI_PG_DISK_INFO:
atascsi_disk_vpd_info(xs);
break;
case SI_PG_DISK_THIN:
atascsi_disk_vpd_thin(xs);
break;
default:
atascsi_done(xs, XS_DRIVER_STUFFUP);
break;
}
} else
atascsi_disk_inquiry(xs);
}
void
atascsi_disk_inquiry(struct scsi_xfer *xs)
{
struct scsi_inquiry_data inq;
struct scsi_link *link = xs->sc_link;
struct atascsi_port *ap;
ap = atascsi_lookup_port(link);
bzero(&inq, sizeof(inq));
inq.device = T_DIRECT;
inq.version = SCSI_REV_SPC3;
inq.response_format = SID_SCSI2_RESPONSE;
inq.additional_length = SID_SCSI2_ALEN;
inq.flags |= SID_CmdQue;
bcopy("ATA ", inq.vendor, sizeof(inq.vendor));
ata_swapcopy(ap->ap_identify.model, inq.product,
sizeof(inq.product));
ata_swapcopy(ap->ap_identify.firmware, inq.revision,
sizeof(inq.revision));
scsi_copy_internal_data(xs, &inq, sizeof(inq));
atascsi_done(xs, XS_NOERROR);
}
void
atascsi_disk_vpd_supported(struct scsi_xfer *xs)
{
struct {
struct scsi_vpd_hdr hdr;
u_int8_t list[7];
} pg;
struct scsi_link *link = xs->sc_link;
struct atascsi_port *ap;
int fat;
ap = atascsi_lookup_port(link);
fat = ISSET(ap->ap_features, ATA_PORT_F_TRIM) ? 0 : 1;
bzero(&pg, sizeof(pg));
pg.hdr.device = T_DIRECT;
pg.hdr.page_code = SI_PG_SUPPORTED;
_lto2b(sizeof(pg.list) - fat, pg.hdr.page_length);
pg.list[0] = SI_PG_SUPPORTED;
pg.list[1] = SI_PG_SERIAL;
pg.list[2] = SI_PG_DEVID;
pg.list[3] = SI_PG_ATA;
pg.list[4] = SI_PG_DISK_LIMITS;
pg.list[5] = SI_PG_DISK_INFO;
pg.list[6] = SI_PG_DISK_THIN;
bcopy(&pg, xs->data, MIN(sizeof(pg) - fat, xs->datalen));
atascsi_done(xs, XS_NOERROR);
}
void
atascsi_disk_vpd_serial(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi_port *ap;
struct scsi_vpd_serial pg;
ap = atascsi_lookup_port(link);
bzero(&pg, sizeof(pg));
pg.hdr.device = T_DIRECT;
pg.hdr.page_code = SI_PG_SERIAL;
_lto2b(sizeof(ap->ap_identify.serial), pg.hdr.page_length);
ata_swapcopy(ap->ap_identify.serial, pg.serial,
sizeof(ap->ap_identify.serial));
bcopy(&pg, xs->data, MIN(sizeof(pg), xs->datalen));
atascsi_done(xs, XS_NOERROR);
}
void
atascsi_disk_vpd_ident(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi_port *ap;
struct {
struct scsi_vpd_hdr hdr;
struct scsi_vpd_devid_hdr devid_hdr;
u_int8_t devid[68];
} pg;
u_int8_t *p;
size_t pg_len;
ap = atascsi_lookup_port(link);
bzero(&pg, sizeof(pg));
if (letoh16(ap->ap_identify.features87) & ATA_ID_F87_WWN) {
pg_len = 8;
pg.devid_hdr.pi_code = VPD_DEVID_CODE_BINARY;
pg.devid_hdr.flags = VPD_DEVID_ASSOC_LU | VPD_DEVID_TYPE_NAA;
ata_swapcopy(&ap->ap_identify.naa_ieee_oui, pg.devid, pg_len);
} else {
pg_len = 68;
pg.devid_hdr.pi_code = VPD_DEVID_CODE_ASCII;
pg.devid_hdr.flags = VPD_DEVID_ASSOC_LU | VPD_DEVID_TYPE_T10;
p = pg.devid;
bcopy("ATA ", p, 8);
p += 8;
ata_swapcopy(ap->ap_identify.model, p,
sizeof(ap->ap_identify.model));
p += sizeof(ap->ap_identify.model);
ata_swapcopy(ap->ap_identify.serial, p,
sizeof(ap->ap_identify.serial));
}
pg.devid_hdr.len = pg_len;
pg_len += sizeof(pg.devid_hdr);
pg.hdr.device = T_DIRECT;
pg.hdr.page_code = SI_PG_DEVID;
_lto2b(pg_len, pg.hdr.page_length);
pg_len += sizeof(pg.hdr);
bcopy(&pg, xs->data, MIN(pg_len, xs->datalen));
atascsi_done(xs, XS_NOERROR);
}
void
atascsi_disk_vpd_ata(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi_port *ap;
struct scsi_vpd_ata pg;
ap = atascsi_lookup_port(link);
bzero(&pg, sizeof(pg));
pg.hdr.device = T_DIRECT;
pg.hdr.page_code = SI_PG_ATA;
_lto2b(sizeof(pg) - sizeof(pg.hdr), pg.hdr.page_length);
memset(pg.sat_vendor, ' ', sizeof(pg.sat_vendor));
memcpy(pg.sat_vendor, "OpenBSD",
MIN(strlen("OpenBSD"), sizeof(pg.sat_vendor)));
memset(pg.sat_product, ' ', sizeof(pg.sat_product));
memcpy(pg.sat_product, "atascsi",
MIN(strlen("atascsi"), sizeof(pg.sat_product)));
memset(pg.sat_revision, ' ', sizeof(pg.sat_revision));
memcpy(pg.sat_revision, osrelease,
MIN(strlen(osrelease), sizeof(pg.sat_revision)));
switch (ap->ap_type) {
case ATA_PORT_T_DISK:
pg.command_code = VPD_ATA_COMMAND_CODE_ATA;
break;
case ATA_PORT_T_ATAPI:
pg.command_code = VPD_ATA_COMMAND_CODE_ATAPI;
break;
}
memcpy(pg.identify, &ap->ap_identify, sizeof(pg.identify));
bcopy(&pg, xs->data, MIN(sizeof(pg), xs->datalen));
atascsi_done(xs, XS_NOERROR);
}
void
atascsi_disk_vpd_limits(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi_port *ap;
struct scsi_vpd_disk_limits pg;
ap = atascsi_lookup_port(link);
bzero(&pg, sizeof(pg));
pg.hdr.device = T_DIRECT;
pg.hdr.page_code = SI_PG_DISK_LIMITS;
_lto2b(SI_PG_DISK_LIMITS_LEN_THIN, pg.hdr.page_length);
_lto2b(1 << ata_identify_block_l2p_exp(&ap->ap_identify),
pg.optimal_xfer_granularity);
if (ISSET(ap->ap_features, ATA_PORT_F_TRIM)) {
_lto4b(ATA_DSM_TRIM_MAX_LEN, pg.max_unmap_lba_count);
_lto4b(512 / 8, pg.max_unmap_desc_count);
}
bcopy(&pg, xs->data, MIN(sizeof(pg), xs->datalen));
atascsi_done(xs, XS_NOERROR);
}
void
atascsi_disk_vpd_info(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi_port *ap;
struct scsi_vpd_disk_info pg;
ap = atascsi_lookup_port(link);
bzero(&pg, sizeof(pg));
pg.hdr.device = T_DIRECT;
pg.hdr.page_code = SI_PG_DISK_INFO;
_lto2b(sizeof(pg) - sizeof(pg.hdr), pg.hdr.page_length);
_lto2b(letoh16(ap->ap_identify.rpm), pg.rpm);
pg.form_factor = letoh16(ap->ap_identify.form) & ATA_ID_FORM_MASK;
bcopy(&pg, xs->data, MIN(sizeof(pg), xs->datalen));
atascsi_done(xs, XS_NOERROR);
}
void
atascsi_disk_vpd_thin(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi_port *ap;
struct scsi_vpd_disk_thin pg;
ap = atascsi_lookup_port(link);
if (!ISSET(ap->ap_features, ATA_PORT_F_TRIM)) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
bzero(&pg, sizeof(pg));
pg.hdr.device = T_DIRECT;
pg.hdr.page_code = SI_PG_DISK_THIN;
_lto2b(sizeof(pg) - sizeof(pg.hdr), pg.hdr.page_length);
pg.flags = VPD_DISK_THIN_TPU | VPD_DISK_THIN_TPWS;
bcopy(&pg, xs->data, MIN(sizeof(pg), xs->datalen));
atascsi_done(xs, XS_NOERROR);
}
void
atascsi_disk_write_same_16(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi *as = link->bus->sb_adapter_softc;
struct atascsi_port *ap;
struct scsi_write_same_16 *cdb;
struct ata_xfer *xa = xs->io;
struct ata_fis_h2d *fis;
u_int64_t lba;
u_int32_t length;
u_int64_t desc;
if (xs->cmdlen != sizeof(*cdb)) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
ap = atascsi_lookup_port(link);
cdb = (struct scsi_write_same_16 *)&xs->cmd;
if (!ISSET(cdb->flags, WRITE_SAME_F_UNMAP) ||
!ISSET(ap->ap_features, ATA_PORT_F_TRIM)) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
if (xs->datalen < 512) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
lba = _8btol(cdb->lba);
length = _4btol(cdb->length);
if (length > ATA_DSM_TRIM_MAX_LEN) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
xa->data = xs->data;
xa->datalen = 512;
xa->flags = ATA_F_WRITE;
xa->pmp_port = ap->ap_pmp_port;
if (xs->flags & SCSI_POLL)
xa->flags |= ATA_F_POLL;
xa->complete = atascsi_disk_write_same_16_done;
xa->atascsi_private = xs;
xa->timeout = (xs->timeout < 45000) ? 45000 : xs->timeout;
memset(xa->data, 0, xa->datalen);
desc = htole64(ATA_DSM_TRIM_DESC(lba, length));
memcpy(xa->data, &desc, sizeof(desc));
fis = xa->fis;
fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
fis->command = ATA_C_DSM;
fis->features = ATA_DSM_TRIM;
fis->sector_count = 1;
ata_exec(as, xa);
}
void
atascsi_disk_write_same_16_done(struct ata_xfer *xa)
{
struct scsi_xfer *xs = xa->atascsi_private;
switch (xa->state) {
case ATA_S_COMPLETE:
xs->error = XS_NOERROR;
break;
case ATA_S_ERROR:
xs->error = XS_DRIVER_STUFFUP;
break;
case ATA_S_TIMEOUT:
xs->error = XS_TIMEOUT;
break;
default:
panic("atascsi_disk_write_same_16_done: "
"unexpected ata_xfer state (%d)", xa->state);
}
scsi_done(xs);
}
void
atascsi_disk_unmap(struct scsi_xfer *xs)
{
struct ata_xfer *xa = xs->io;
struct scsi_unmap *cdb;
struct scsi_unmap_data *unmap;
u_int len;
if (ISSET(xs->flags, SCSI_POLL) || xs->cmdlen != sizeof(*cdb))
atascsi_done(xs, XS_DRIVER_STUFFUP);
cdb = (struct scsi_unmap *)&xs->cmd;
len = _2btol(cdb->list_len);
if (xs->datalen != len || len < sizeof(*unmap)) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
unmap = (struct scsi_unmap_data *)xs->data;
if (_2btol(unmap->data_length) != len) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
len = _2btol(unmap->desc_length);
if (len != xs->datalen - sizeof(*unmap)) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
if (len < sizeof(struct scsi_unmap_desc)) {
atascsi_done(xs, XS_NOERROR);
}
if (len > sizeof(struct scsi_unmap_desc) * 64) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
if (ISSET(xs->flags, SCSI_NOSLEEP)) {
task_set(&xa->task, atascsi_disk_unmap_task, xs);
task_add(systq, &xa->task);
} else {
atascsi_disk_unmap_task(xs);
}
}
void
atascsi_disk_unmap_task(void *xxs)
{
struct scsi_xfer *xs = xxs;
struct scsi_link *link = xs->sc_link;
struct atascsi *as = link->bus->sb_adapter_softc;
struct atascsi_port *ap;
struct ata_xfer *xa = xs->io;
struct ata_fis_h2d *fis;
struct scsi_unmap_data *unmap;
struct scsi_unmap_desc *descs, *d;
u_int64_t *trims;
u_int len, i;
trims = dma_alloc(512, PR_WAITOK | PR_ZERO);
ap = atascsi_lookup_port(link);
unmap = (struct scsi_unmap_data *)xs->data;
descs = (struct scsi_unmap_desc *)(unmap + 1);
len = _2btol(unmap->desc_length) / sizeof(*d);
for (i = 0; i < len; i++) {
d = &descs[i];
if (_4btol(d->logical_blocks) > ATA_DSM_TRIM_MAX_LEN)
goto fail;
trims[i] = htole64(ATA_DSM_TRIM_DESC(_8btol(d->logical_addr),
_4btol(d->logical_blocks)));
}
xa->data = trims;
xa->datalen = 512;
xa->flags = ATA_F_WRITE;
xa->pmp_port = ap->ap_pmp_port;
xa->complete = atascsi_disk_unmap_done;
xa->atascsi_private = xs;
xa->timeout = (xs->timeout < 45000) ? 45000 : xs->timeout;
fis = xa->fis;
fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
fis->command = ATA_C_DSM;
fis->features = ATA_DSM_TRIM;
fis->sector_count = 1;
ata_exec(as, xa);
return;
fail:
dma_free(xa->data, 512);
atascsi_done(xs, XS_DRIVER_STUFFUP);
}
void
atascsi_disk_unmap_done(struct ata_xfer *xa)
{
struct scsi_xfer *xs = xa->atascsi_private;
dma_free(xa->data, 512);
switch (xa->state) {
case ATA_S_COMPLETE:
xs->error = XS_NOERROR;
break;
case ATA_S_ERROR:
xs->error = XS_DRIVER_STUFFUP;
break;
case ATA_S_TIMEOUT:
xs->error = XS_TIMEOUT;
break;
default:
panic("atascsi_disk_unmap_done: "
"unexpected ata_xfer state (%d)", xa->state);
}
scsi_done(xs);
}
void
atascsi_disk_sync(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi *as = link->bus->sb_adapter_softc;
struct atascsi_port *ap;
struct ata_xfer *xa = xs->io;
if (xs->cmdlen != sizeof(struct scsi_synchronize_cache)) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
ap = atascsi_lookup_port(link);
xa->datalen = 0;
xa->flags = ATA_F_READ;
xa->complete = atascsi_disk_sync_done;
xa->timeout = (xs->timeout < 45000) ? 45000 : xs->timeout;
xa->atascsi_private = xs;
xa->pmp_port = ap->ap_pmp_port;
if (xs->flags & SCSI_POLL)
xa->flags |= ATA_F_POLL;
xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
xa->fis->command = ATA_C_FLUSH_CACHE;
xa->fis->device = 0;
ata_exec(as, xa);
}
void
atascsi_disk_sync_done(struct ata_xfer *xa)
{
struct scsi_xfer *xs = xa->atascsi_private;
switch (xa->state) {
case ATA_S_COMPLETE:
xs->error = XS_NOERROR;
break;
case ATA_S_ERROR:
case ATA_S_TIMEOUT:
printf("atascsi_disk_sync_done: %s\n",
xa->state == ATA_S_TIMEOUT ? "timeout" : "error");
xs->error = (xa->state == ATA_S_TIMEOUT ? XS_TIMEOUT :
XS_DRIVER_STUFFUP);
break;
default:
panic("atascsi_disk_sync_done: unexpected ata_xfer state (%d)",
xa->state);
}
scsi_done(xs);
}
u_int64_t
ata_identify_blocks(struct ata_identify *id)
{
u_int64_t blocks = 0;
int i;
if (letoh16(id->cmdset83) & 0x0400) {
for (i = 3; i >= 0; --i) {
blocks <<= 16;
blocks += letoh16(id->addrsecxt[i]);
}
} else {
blocks = letoh16(id->addrsec[1]);
blocks <<= 16;
blocks += letoh16(id->addrsec[0]);
}
return (blocks - 1);
}
u_int
ata_identify_blocksize(struct ata_identify *id)
{
u_int blocksize = 512;
u_int16_t p2l_sect = letoh16(id->p2l_sect);
if ((p2l_sect & ATA_ID_P2L_SECT_MASK) == ATA_ID_P2L_SECT_VALID &&
ISSET(p2l_sect, ATA_ID_P2L_SECT_SIZESET)) {
blocksize = letoh16(id->words_lsec[1]);
blocksize <<= 16;
blocksize += letoh16(id->words_lsec[0]);
blocksize <<= 1;
}
return (blocksize);
}
u_int
ata_identify_block_l2p_exp(struct ata_identify *id)
{
u_int exponent = 0;
u_int16_t p2l_sect = letoh16(id->p2l_sect);
if ((p2l_sect & ATA_ID_P2L_SECT_MASK) == ATA_ID_P2L_SECT_VALID &&
ISSET(p2l_sect, ATA_ID_P2L_SECT_SET)) {
exponent = (p2l_sect & ATA_ID_P2L_SECT_SIZE);
}
return (exponent);
}
u_int
ata_identify_block_logical_align(struct ata_identify *id)
{
u_int align = 0;
u_int16_t p2l_sect = letoh16(id->p2l_sect);
u_int16_t logical_align = letoh16(id->logical_align);
if ((p2l_sect & ATA_ID_P2L_SECT_MASK) == ATA_ID_P2L_SECT_VALID &&
ISSET(p2l_sect, ATA_ID_P2L_SECT_SET) &&
(logical_align & ATA_ID_LALIGN_MASK) == ATA_ID_LALIGN_VALID)
align = logical_align & ATA_ID_LALIGN;
return (align);
}
void
atascsi_disk_capacity(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi_port *ap;
struct scsi_read_cap_data rcd;
u_int64_t capacity;
ap = atascsi_lookup_port(link);
if (xs->cmdlen != sizeof(struct scsi_read_capacity)) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
bzero(&rcd, sizeof(rcd));
capacity = ata_identify_blocks(&ap->ap_identify);
if (capacity > 0xffffffff)
capacity = 0xffffffff;
_lto4b(capacity, rcd.addr);
_lto4b(ata_identify_blocksize(&ap->ap_identify), rcd.length);
bcopy(&rcd, xs->data, MIN(sizeof(rcd), xs->datalen));
atascsi_done(xs, XS_NOERROR);
}
void
atascsi_disk_capacity16(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi_port *ap;
struct scsi_read_cap_data_16 rcd;
u_int align;
u_int16_t lowest_aligned = 0;
ap = atascsi_lookup_port(link);
if (xs->cmdlen != sizeof(struct scsi_read_capacity_16)) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
bzero(&rcd, sizeof(rcd));
_lto8b(ata_identify_blocks(&ap->ap_identify), rcd.addr);
_lto4b(ata_identify_blocksize(&ap->ap_identify), rcd.length);
rcd.logical_per_phys = ata_identify_block_l2p_exp(&ap->ap_identify);
align = ata_identify_block_logical_align(&ap->ap_identify);
if (align > 0)
lowest_aligned = (1 << rcd.logical_per_phys) - align;
if (ISSET(ap->ap_features, ATA_PORT_F_TRIM)) {
SET(lowest_aligned, READ_CAP_16_TPE);
if (ISSET(letoh16(ap->ap_identify.add_support),
ATA_ID_ADD_SUPPORT_DRT))
SET(lowest_aligned, READ_CAP_16_TPRZ);
}
_lto2b(lowest_aligned, rcd.lowest_aligned);
bcopy(&rcd, xs->data, MIN(sizeof(rcd), xs->datalen));
atascsi_done(xs, XS_NOERROR);
}
int
atascsi_passthru_map(struct scsi_xfer *xs, u_int8_t count_proto, u_int8_t flags)
{
struct ata_xfer *xa = xs->io;
xa->data = xs->data;
xa->datalen = xs->datalen;
xa->timeout = xs->timeout;
xa->flags = 0;
if (xs->flags & SCSI_DATA_IN)
xa->flags |= ATA_F_READ;
if (xs->flags & SCSI_DATA_OUT)
xa->flags |= ATA_F_WRITE;
if (xs->flags & SCSI_POLL)
xa->flags |= ATA_F_POLL;
switch (count_proto & ATA_PASSTHRU_PROTO_MASK) {
case ATA_PASSTHRU_PROTO_NON_DATA:
case ATA_PASSTHRU_PROTO_PIO_DATAIN:
case ATA_PASSTHRU_PROTO_PIO_DATAOUT:
xa->flags |= ATA_F_PIO;
break;
default:
return (1);
}
xa->atascsi_private = xs;
xa->complete = atascsi_passthru_done;
return (0);
}
void
atascsi_passthru_12(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi *as = link->bus->sb_adapter_softc;
struct atascsi_port *ap;
struct ata_xfer *xa = xs->io;
struct scsi_ata_passthru_12 *cdb;
struct ata_fis_h2d *fis;
if (xs->cmdlen != sizeof(*cdb)) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
cdb = (struct scsi_ata_passthru_12 *)&xs->cmd;
if (atascsi_passthru_map(xs, cdb->count_proto, cdb->flags) != 0) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
ap = atascsi_lookup_port(link);
fis = xa->fis;
fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
fis->command = cdb->command;
fis->features = cdb->features;
fis->lba_low = cdb->lba_low;
fis->lba_mid = cdb->lba_mid;
fis->lba_high = cdb->lba_high;
fis->device = cdb->device;
fis->sector_count = cdb->sector_count;
xa->pmp_port = ap->ap_pmp_port;
ata_exec(as, xa);
}
void
atascsi_passthru_16(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi *as = link->bus->sb_adapter_softc;
struct atascsi_port *ap;
struct ata_xfer *xa = xs->io;
struct scsi_ata_passthru_16 *cdb;
struct ata_fis_h2d *fis;
if (xs->cmdlen != sizeof(*cdb)) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
cdb = (struct scsi_ata_passthru_16 *)&xs->cmd;
if (atascsi_passthru_map(xs, cdb->count_proto, cdb->flags) != 0) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
ap = atascsi_lookup_port(link);
fis = xa->fis;
fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
fis->command = cdb->command;
fis->features = cdb->features[1];
fis->lba_low = cdb->lba_low[1];
fis->lba_mid = cdb->lba_mid[1];
fis->lba_high = cdb->lba_high[1];
fis->device = cdb->device;
fis->lba_low_exp = cdb->lba_low[0];
fis->lba_mid_exp = cdb->lba_mid[0];
fis->lba_high_exp = cdb->lba_high[0];
fis->features_exp = cdb->features[0];
fis->sector_count = cdb->sector_count[1];
fis->sector_count_exp = cdb->sector_count[0];
xa->pmp_port = ap->ap_pmp_port;
ata_exec(as, xa);
}
void
atascsi_passthru_done(struct ata_xfer *xa)
{
struct scsi_xfer *xs = xa->atascsi_private;
switch (xa->state) {
case ATA_S_COMPLETE:
xs->error = XS_NOERROR;
break;
case ATA_S_ERROR:
xs->error = XS_DRIVER_STUFFUP;
break;
case ATA_S_TIMEOUT:
printf("atascsi_passthru_done, timeout\n");
xs->error = XS_TIMEOUT;
break;
default:
panic("atascsi_atapi_cmd_done: unexpected ata_xfer state (%d)",
xa->state);
}
xs->resid = xa->resid;
scsi_done(xs);
}
void
atascsi_disk_sense(struct scsi_xfer *xs)
{
struct scsi_sense_data *sd = (struct scsi_sense_data *)xs->data;
bzero(xs->data, xs->datalen);
sd->error_code = SSD_ERRCODE_CURRENT;
sd->flags = SKEY_NO_SENSE;
atascsi_done(xs, XS_NOERROR);
}
void
atascsi_disk_start_stop(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi *as = link->bus->sb_adapter_softc;
struct atascsi_port *ap;
struct ata_xfer *xa = xs->io;
struct scsi_start_stop *ss = (struct scsi_start_stop *)&xs->cmd;
if (xs->cmdlen != sizeof(*ss)) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
if (ss->how != SSS_STOP) {
atascsi_done(xs, XS_NOERROR);
return;
}
ap = atascsi_lookup_port(link);
xa->datalen = 0;
xa->flags = ATA_F_READ;
xa->complete = atascsi_disk_start_stop_done;
xa->timeout = (xs->timeout < 45000) ? 45000 : xs->timeout;
xa->pmp_port = ap->ap_pmp_port;
xa->atascsi_private = xs;
if (xs->flags & SCSI_POLL)
xa->flags |= ATA_F_POLL;
xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
xa->fis->command = ATA_C_FLUSH_CACHE;
xa->fis->device = 0;
ata_exec(as, xa);
}
void
atascsi_disk_start_stop_done(struct ata_xfer *xa)
{
struct scsi_xfer *xs = xa->atascsi_private;
struct scsi_link *link = xs->sc_link;
struct atascsi *as = link->bus->sb_adapter_softc;
struct atascsi_port *ap;
switch (xa->state) {
case ATA_S_COMPLETE:
break;
case ATA_S_ERROR:
case ATA_S_TIMEOUT:
xs->error = (xa->state == ATA_S_TIMEOUT ? XS_TIMEOUT :
XS_DRIVER_STUFFUP);
xs->resid = xa->resid;
scsi_done(xs);
return;
default:
panic("atascsi_disk_start_stop_done: unexpected ata_xfer state (%d)",
xa->state);
}
ap = atascsi_lookup_port(link);
xa->datalen = 0;
xa->flags = ATA_F_READ;
xa->state = ATA_S_SETUP;
xa->complete = atascsi_disk_cmd_done;
xa->timeout = (xs->timeout < 45000) ? 45000 : xs->timeout;
xa->pmp_port = ap->ap_pmp_port;
xa->atascsi_private = xs;
if (xs->flags & SCSI_POLL)
xa->flags |= ATA_F_POLL;
xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
xa->fis->command = ATA_C_STANDBY_IMMED;
xa->fis->device = 0;
ata_exec(as, xa);
}
void
atascsi_atapi_cmd(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct atascsi *as = link->bus->sb_adapter_softc;
struct atascsi_port *ap;
struct ata_xfer *xa = xs->io;
struct ata_fis_h2d *fis;
switch (xs->flags & (SCSI_DATA_IN | SCSI_DATA_OUT)) {
case SCSI_DATA_IN:
xa->flags = ATA_F_PACKET | ATA_F_READ;
break;
case SCSI_DATA_OUT:
xa->flags = ATA_F_PACKET | ATA_F_WRITE;
break;
default:
xa->flags = ATA_F_PACKET;
}
xa->flags |= ATA_F_GET_RFIS;
ap = atascsi_lookup_port(link);
xa->data = xs->data;
xa->datalen = xs->datalen;
xa->complete = atascsi_atapi_cmd_done;
xa->timeout = xs->timeout;
xa->pmp_port = ap->ap_pmp_port;
xa->atascsi_private = xs;
if (xs->flags & SCSI_POLL)
xa->flags |= ATA_F_POLL;
fis = xa->fis;
fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
fis->command = ATA_C_PACKET;
fis->device = 0;
fis->sector_count = xa->tag << 3;
fis->features = ATA_H2D_FEATURES_DMA | ((xa->flags & ATA_F_WRITE) ?
ATA_H2D_FEATURES_DIR_WRITE : ATA_H2D_FEATURES_DIR_READ);
fis->lba_mid = 0x00;
fis->lba_high = 0x20;
memcpy(xa->packetcmd, &xs->cmd, xs->cmdlen);
ata_exec(as, xa);
}
void
atascsi_atapi_cmd_done(struct ata_xfer *xa)
{
struct scsi_xfer *xs = xa->atascsi_private;
struct scsi_sense_data *sd = &xs->sense;
switch (xa->state) {
case ATA_S_COMPLETE:
xs->error = XS_NOERROR;
break;
case ATA_S_ERROR:
sd->error_code = SSD_ERRCODE_CURRENT;
sd->flags = (xa->rfis.error & 0xf0) >> 4;
if (xa->rfis.error & 0x04)
sd->flags = SKEY_ILLEGAL_REQUEST;
if (xa->rfis.error & 0x02)
sd->flags |= SSD_EOM;
if (xa->rfis.error & 0x01)
sd->flags |= SSD_ILI;
xs->error = XS_SENSE;
break;
case ATA_S_TIMEOUT:
printf("atascsi_atapi_cmd_done, timeout\n");
xs->error = XS_TIMEOUT;
break;
default:
panic("atascsi_atapi_cmd_done: unexpected ata_xfer state (%d)",
xa->state);
}
xs->resid = xa->resid;
scsi_done(xs);
}
void
atascsi_pmp_cmd(struct scsi_xfer *xs)
{
switch (xs->cmd.opcode) {
case REQUEST_SENSE:
atascsi_pmp_sense(xs);
return;
case INQUIRY:
atascsi_pmp_inq(xs);
return;
case TEST_UNIT_READY:
case PREVENT_ALLOW:
atascsi_done(xs, XS_NOERROR);
return;
default:
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
}
void
atascsi_pmp_sense(struct scsi_xfer *xs)
{
struct scsi_sense_data *sd = (struct scsi_sense_data *)xs->data;
bzero(xs->data, xs->datalen);
sd->error_code = SSD_ERRCODE_CURRENT;
sd->flags = SKEY_NO_SENSE;
atascsi_done(xs, XS_NOERROR);
}
void
atascsi_pmp_inq(struct scsi_xfer *xs)
{
struct scsi_inquiry_data inq;
struct scsi_inquiry *in_inq = (struct scsi_inquiry *)&xs->cmd;
if (ISSET(in_inq->flags, SI_EVPD)) {
atascsi_done(xs, XS_DRIVER_STUFFUP);
return;
}
bzero(&inq, sizeof(inq));
inq.device = 0x1E;
inq.version = SCSI_REV_SPC3;
inq.response_format = SID_SCSI2_RESPONSE;
inq.additional_length = SID_SCSI2_ALEN;
inq.flags |= SID_CmdQue;
bcopy("ATA ", inq.vendor, sizeof(inq.vendor));
bcopy("Port Multiplier", inq.product, sizeof(inq.product));
bcopy(" ", inq.revision, sizeof(inq.revision));
scsi_copy_internal_data(xs, &inq, sizeof(inq));
atascsi_done(xs, XS_NOERROR);
}
void
atascsi_done(struct scsi_xfer *xs, int error)
{
xs->error = error;
scsi_done(xs);
}
void
ata_exec(struct atascsi *as, struct ata_xfer *xa)
{
as->as_methods->ata_cmd(xa);
}
void *
atascsi_io_get(void *cookie)
{
struct atascsi_host_port *ahp = cookie;
struct atascsi *as = ahp->ahp_as;
struct ata_xfer *xa;
xa = as->as_methods->ata_get_xfer(as->as_cookie, ahp->ahp_port);
if (xa != NULL)
xa->fis->type = ATA_FIS_TYPE_H2D;
return (xa);
}
void
atascsi_io_put(void *cookie, void *io)
{
struct atascsi_host_port *ahp = cookie;
struct atascsi *as = ahp->ahp_as;
struct ata_xfer *xa = io;
xa->state = ATA_S_COMPLETE;
as->as_methods->ata_put_xfer(xa);
}
void
ata_polled_complete(struct ata_xfer *xa)
{
}
int
ata_polled(struct ata_xfer *xa)
{
int rv;
if (!ISSET(xa->flags, ATA_F_DONE))
panic("ata_polled: xa isn't complete");
switch (xa->state) {
case ATA_S_COMPLETE:
rv = 0;
break;
case ATA_S_ERROR:
case ATA_S_TIMEOUT:
rv = EIO;
break;
default:
panic("ata_polled: xa state (%d)",
xa->state);
}
scsi_io_put(xa->atascsi_private, xa);
return (rv);
}
void
ata_complete(struct ata_xfer *xa)
{
SET(xa->flags, ATA_F_DONE);
xa->complete(xa);
}
void
ata_swapcopy(void *src, void *dst, size_t len)
{
u_int16_t *s = src, *d = dst;
int i;
len /= 2;
for (i = 0; i < len; i++)
d[i] = swap16(s[i]);
}
int
atascsi_port_identify(struct atascsi_port *ap, struct ata_identify *identify)
{
struct atascsi *as = ap->ap_as;
struct atascsi_host_port *ahp = ap->ap_host_port;
struct ata_xfer *xa;
xa = scsi_io_get(&ahp->ahp_iopool, SCSI_NOSLEEP);
if (xa == NULL)
panic("no free xfers on a new port");
xa->pmp_port = ap->ap_pmp_port;
xa->data = identify;
xa->datalen = sizeof(*identify);
xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
xa->fis->command = (ap->ap_type == ATA_PORT_T_DISK) ?
ATA_C_IDENTIFY : ATA_C_IDENTIFY_PACKET;
xa->fis->device = 0;
xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL;
xa->timeout = 1000;
xa->complete = ata_polled_complete;
xa->atascsi_private = &ahp->ahp_iopool;
ata_exec(as, xa);
return (ata_polled(xa));
}
int
atascsi_port_set_features(struct atascsi_port *ap, int subcommand, int arg)
{
struct atascsi *as = ap->ap_as;
struct atascsi_host_port *ahp = ap->ap_host_port;
struct ata_xfer *xa;
xa = scsi_io_get(&ahp->ahp_iopool, SCSI_NOSLEEP);
if (xa == NULL)
panic("no free xfers on a new port");
xa->fis->command = ATA_C_SET_FEATURES;
xa->fis->features = subcommand;
xa->fis->sector_count = arg;
xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port;
xa->flags = ATA_F_POLL;
xa->timeout = 1000;
xa->complete = ata_polled_complete;
xa->pmp_port = ap->ap_pmp_port;
xa->atascsi_private = &ahp->ahp_iopool;
ata_exec(as, xa);
return (ata_polled(xa));
}