6a3018e7cf
Introduce ATA_CAM kernel option, turning ata(4) controller drivers into cam(4) interface modules. When enabled, this options deprecates all ata(4) peripheral drivers (ad, acd, ...) and interfaces and allows cam(4) drivers (ada, cd, ...) and interfaces to be natively used instead. As side effect of this, ata(4) mode setting code was completely rewritten to make controller API more strict and permit above change. While doing this, SATA revision was separated from PATA mode. It allows DMA-incapable SATA devices to operate and makes hw.ata.atapi_dma tunable work again. Also allow ata(4) controller drivers (except some specific or broken ones) to handle larger data transfers. Previous constraint of 64K was artificial and is not really required by PCI ATA BM specification or hardware. Submitted by: nwitehorn (powerpc part)
441 lines
12 KiB
C
441 lines
12 KiB
C
/*-
|
|
* Copyright (c) 1998 - 2008 Søren Schmidt <sos@FreeBSD.org>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer,
|
|
* without modification, immediately at the beginning of the file.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/ata.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/module.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/bio.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/endian.h>
|
|
#include <sys/cdio.h>
|
|
#include <sys/sema.h>
|
|
#include <sys/taskqueue.h>
|
|
#include <vm/uma.h>
|
|
#include <machine/bus.h>
|
|
#include <geom/geom_disk.h>
|
|
#include <dev/ata/ata-all.h>
|
|
#include <dev/ata/atapi-fd.h>
|
|
#include <ata_if.h>
|
|
|
|
|
|
/* prototypes */
|
|
static disk_open_t afd_open;
|
|
static disk_close_t afd_close;
|
|
static disk_strategy_t afd_strategy;
|
|
static disk_ioctl_t afd_ioctl;
|
|
static int afd_sense(device_t);
|
|
static void afd_describe(device_t);
|
|
static void afd_done(struct ata_request *);
|
|
static int afd_prevent_allow(device_t, int);
|
|
static int afd_test_ready(device_t);
|
|
|
|
/* internal vars */
|
|
static MALLOC_DEFINE(M_AFD, "afd_driver", "ATAPI floppy driver buffers");
|
|
|
|
static int
|
|
afd_probe(device_t dev)
|
|
{
|
|
struct ata_device *atadev = device_get_softc(dev);
|
|
if ((atadev->param.config & ATA_PROTO_ATAPI) &&
|
|
(atadev->param.config & ATA_ATAPI_TYPE_MASK) == ATA_ATAPI_TYPE_DIRECT)
|
|
return 0;
|
|
else
|
|
return ENXIO;
|
|
}
|
|
|
|
static int
|
|
afd_attach(device_t dev)
|
|
{
|
|
struct ata_channel *ch = device_get_softc(device_get_parent(dev));
|
|
struct ata_device *atadev = device_get_softc(dev);
|
|
struct afd_softc *fdp;
|
|
|
|
if (!(fdp = malloc(sizeof(struct afd_softc), M_AFD, M_NOWAIT | M_ZERO))) {
|
|
device_printf(dev, "out of memory\n");
|
|
return ENOMEM;
|
|
}
|
|
device_set_ivars(dev, fdp);
|
|
ata_setmode(dev);
|
|
|
|
if (afd_sense(dev)) {
|
|
device_set_ivars(dev, NULL);
|
|
free(fdp, M_AFD);
|
|
return ENXIO;
|
|
}
|
|
atadev->flags |= ATA_D_MEDIA_CHANGED;
|
|
|
|
/* announce we are here */
|
|
afd_describe(dev);
|
|
|
|
/* create the disk device */
|
|
fdp->disk = disk_alloc();
|
|
fdp->disk->d_open = afd_open;
|
|
fdp->disk->d_close = afd_close;
|
|
fdp->disk->d_strategy = afd_strategy;
|
|
fdp->disk->d_ioctl = afd_ioctl;
|
|
fdp->disk->d_name = "afd";
|
|
fdp->disk->d_drv1 = dev;
|
|
fdp->disk->d_maxsize = ch->dma.max_iosize ? ch->dma.max_iosize : DFLTPHYS;
|
|
fdp->disk->d_unit = device_get_unit(dev);
|
|
disk_create(fdp->disk, DISK_VERSION);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
afd_detach(device_t dev)
|
|
{
|
|
struct afd_softc *fdp = device_get_ivars(dev);
|
|
|
|
/* check that we have a valid device to detach */
|
|
if (!device_get_ivars(dev))
|
|
return ENXIO;
|
|
|
|
/* detroy disk from the system so we dont get any further requests */
|
|
disk_destroy(fdp->disk);
|
|
|
|
/* fail requests on the queue and any thats "in flight" for this device */
|
|
ata_fail_requests(dev);
|
|
|
|
/* dont leave anything behind */
|
|
device_set_ivars(dev, NULL);
|
|
free(fdp, M_AFD);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
afd_shutdown(device_t dev)
|
|
{
|
|
struct ata_device *atadev = device_get_softc(dev);
|
|
|
|
if (atadev->param.support.command2 & ATA_SUPPORT_FLUSHCACHE)
|
|
ata_controlcmd(dev, ATA_FLUSHCACHE, 0, 0, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
afd_reinit(device_t dev)
|
|
{
|
|
struct ata_channel *ch = device_get_softc(device_get_parent(dev));
|
|
struct ata_device *atadev = device_get_softc(dev);
|
|
|
|
/* if detach pending, return error */
|
|
if (!(ch->devices & (ATA_ATAPI_MASTER << atadev->unit)))
|
|
return 1;
|
|
|
|
ata_setmode(dev);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
afd_open(struct disk *dp)
|
|
{
|
|
device_t dev = dp->d_drv1;
|
|
struct ata_device *atadev = device_get_softc(dev);
|
|
struct afd_softc *fdp = device_get_ivars(dev);
|
|
|
|
if (!fdp)
|
|
return ENXIO;
|
|
if (!device_is_attached(dev))
|
|
return EBUSY;
|
|
|
|
afd_test_ready(dev);
|
|
afd_prevent_allow(dev, 1);
|
|
|
|
if (afd_sense(dev))
|
|
device_printf(dev, "sense media type failed\n");
|
|
atadev->flags &= ~ATA_D_MEDIA_CHANGED;
|
|
|
|
if (!fdp->mediasize)
|
|
return ENXIO;
|
|
|
|
fdp->disk->d_sectorsize = fdp->sectorsize;
|
|
fdp->disk->d_mediasize = fdp->mediasize;
|
|
fdp->disk->d_fwsectors = fdp->sectors;
|
|
fdp->disk->d_fwheads = fdp->heads;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
afd_close(struct disk *dp)
|
|
{
|
|
device_t dev = dp->d_drv1;
|
|
|
|
afd_prevent_allow(dev, 0);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
afd_strategy(struct bio *bp)
|
|
{
|
|
device_t dev = bp->bio_disk->d_drv1;
|
|
struct ata_device *atadev = device_get_softc(dev);
|
|
struct afd_softc *fdp = device_get_ivars(dev);
|
|
struct ata_request *request;
|
|
u_int16_t count;
|
|
int8_t ccb[16];
|
|
|
|
/* if it's a null transfer, return immediatly. */
|
|
if (bp->bio_bcount == 0) {
|
|
bp->bio_resid = 0;
|
|
biodone(bp);
|
|
return;
|
|
}
|
|
|
|
/* should reject all queued entries if media have changed. */
|
|
if (atadev->flags & ATA_D_MEDIA_CHANGED) {
|
|
biofinish(bp, NULL, EIO);
|
|
return;
|
|
}
|
|
|
|
count = bp->bio_bcount / fdp->sectorsize;
|
|
bp->bio_resid = bp->bio_bcount;
|
|
|
|
bzero(ccb, sizeof(ccb));
|
|
|
|
if (bp->bio_cmd == BIO_READ)
|
|
ccb[0] = ATAPI_READ_BIG;
|
|
else
|
|
ccb[0] = ATAPI_WRITE_BIG;
|
|
|
|
ccb[2] = bp->bio_pblkno >> 24;
|
|
ccb[3] = bp->bio_pblkno >> 16;
|
|
ccb[4] = bp->bio_pblkno >> 8;
|
|
ccb[5] = bp->bio_pblkno;
|
|
ccb[7] = count>>8;
|
|
ccb[8] = count;
|
|
|
|
if (!(request = ata_alloc_request())) {
|
|
biofinish(bp, NULL, ENOMEM);
|
|
return;
|
|
}
|
|
request->dev = dev;
|
|
request->bio = bp;
|
|
bcopy(ccb, request->u.atapi.ccb, 16);
|
|
request->data = bp->bio_data;
|
|
request->bytecount = count * fdp->sectorsize;
|
|
request->transfersize = min(request->bytecount, 65534);
|
|
request->timeout = (ccb[0] == ATAPI_WRITE_BIG) ? 60 : 30;
|
|
request->retries = 2;
|
|
request->callback = afd_done;
|
|
switch (bp->bio_cmd) {
|
|
case BIO_READ:
|
|
request->flags = (ATA_R_ATAPI | ATA_R_READ);
|
|
break;
|
|
case BIO_WRITE:
|
|
request->flags = (ATA_R_ATAPI | ATA_R_WRITE);
|
|
break;
|
|
default:
|
|
device_printf(dev, "unknown BIO operation\n");
|
|
ata_free_request(request);
|
|
biofinish(bp, NULL, EIO);
|
|
return;
|
|
}
|
|
if (atadev->mode >= ATA_DMA)
|
|
request->flags |= ATA_R_DMA;
|
|
request->flags |= ATA_R_ORDERED;
|
|
ata_queue_request(request);
|
|
}
|
|
|
|
static void
|
|
afd_done(struct ata_request *request)
|
|
{
|
|
struct bio *bp = request->bio;
|
|
|
|
/* finish up transfer */
|
|
if ((bp->bio_error = request->result))
|
|
bp->bio_flags |= BIO_ERROR;
|
|
bp->bio_resid = bp->bio_bcount - request->donecount;
|
|
biodone(bp);
|
|
ata_free_request(request);
|
|
}
|
|
|
|
static int
|
|
afd_ioctl(struct disk *disk, u_long cmd, void *data, int flag,struct thread *td)
|
|
{
|
|
return ata_device_ioctl(disk->d_drv1, cmd, data);
|
|
}
|
|
|
|
static int
|
|
afd_sense(device_t dev)
|
|
{
|
|
struct ata_device *atadev = device_get_softc(dev);
|
|
struct afd_softc *fdp = device_get_ivars(dev);
|
|
struct afd_capacity capacity;
|
|
struct afd_capacity_big capacity_big;
|
|
struct afd_capabilities capabilities;
|
|
int8_t ccb1[16] = { ATAPI_READ_CAPACITY, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0 };
|
|
int8_t ccb2[16] = { ATAPI_SERVICE_ACTION_IN, 0x10, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, sizeof(struct afd_capacity_big) & 0xff, 0, 0 };
|
|
int8_t ccb3[16] = { ATAPI_MODE_SENSE_BIG, 0, ATAPI_REWRITEABLE_CAP_PAGE,
|
|
0, 0, 0, 0, sizeof(struct afd_capabilities) >> 8,
|
|
sizeof(struct afd_capabilities) & 0xff,
|
|
0, 0, 0, 0, 0, 0, 0 };
|
|
int timeout = 20;
|
|
int error, count;
|
|
|
|
fdp->mediasize = 0;
|
|
|
|
/* wait for device to get ready */
|
|
while ((error = afd_test_ready(dev)) && timeout--) {
|
|
DELAY(100000);
|
|
}
|
|
if (error == EBUSY)
|
|
return 1;
|
|
|
|
/* The IOMEGA Clik! doesn't support reading the cap page, fake it */
|
|
if (!strncmp(atadev->param.model, "IOMEGA Clik!", 12)) {
|
|
fdp->heads = 1;
|
|
fdp->sectors = 2;
|
|
fdp->mediasize = 39441 * 1024;
|
|
fdp->sectorsize = 512;
|
|
afd_test_ready(dev);
|
|
return 0;
|
|
}
|
|
|
|
/* get drive capacity */
|
|
if (!ata_atapicmd(dev, ccb1, (caddr_t)&capacity,
|
|
sizeof(struct afd_capacity), ATA_R_READ, 30)) {
|
|
fdp->heads = 16;
|
|
fdp->sectors = 63;
|
|
fdp->sectorsize = be32toh(capacity.blocksize);
|
|
fdp->mediasize = (u_int64_t)be32toh(capacity.capacity)*fdp->sectorsize;
|
|
afd_test_ready(dev);
|
|
return 0;
|
|
}
|
|
|
|
/* get drive capacity big */
|
|
if (!ata_atapicmd(dev, ccb2, (caddr_t)&capacity_big,
|
|
sizeof(struct afd_capacity_big),
|
|
ATA_R_READ | ATA_R_QUIET, 30)) {
|
|
fdp->heads = 16;
|
|
fdp->sectors = 63;
|
|
fdp->sectorsize = be32toh(capacity_big.blocksize);
|
|
fdp->mediasize = be64toh(capacity_big.capacity)*fdp->sectorsize;
|
|
afd_test_ready(dev);
|
|
return 0;
|
|
}
|
|
|
|
/* get drive capabilities, some bugridden drives needs this repeated */
|
|
for (count = 0 ; count < 5 ; count++) {
|
|
if (!ata_atapicmd(dev, ccb3, (caddr_t)&capabilities,
|
|
sizeof(struct afd_capabilities), ATA_R_READ, 30) &&
|
|
capabilities.page_code == ATAPI_REWRITEABLE_CAP_PAGE) {
|
|
fdp->heads = capabilities.heads;
|
|
fdp->sectors = capabilities.sectors;
|
|
fdp->sectorsize = be16toh(capabilities.sector_size);
|
|
fdp->mediasize = be16toh(capabilities.cylinders) *
|
|
fdp->heads * fdp->sectors * fdp->sectorsize;
|
|
if (!capabilities.medium_type)
|
|
fdp->mediasize = 0;
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
afd_prevent_allow(device_t dev, int lock)
|
|
{
|
|
struct ata_device *atadev = device_get_softc(dev);
|
|
int8_t ccb[16] = { ATAPI_PREVENT_ALLOW, 0, 0, 0, lock,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
|
|
if (!strncmp(atadev->param.model, "IOMEGA Clik!", 12))
|
|
return 0;
|
|
return ata_atapicmd(dev, ccb, NULL, 0, 0, 30);
|
|
}
|
|
|
|
static int
|
|
afd_test_ready(device_t dev)
|
|
{
|
|
int8_t ccb[16] = { ATAPI_TEST_UNIT_READY, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
|
|
return ata_atapicmd(dev, ccb, NULL, 0, 0, 30);
|
|
}
|
|
|
|
static void
|
|
afd_describe(device_t dev)
|
|
{
|
|
struct ata_channel *ch = device_get_softc(device_get_parent(dev));
|
|
struct ata_device *atadev = device_get_softc(dev);
|
|
struct afd_softc *fdp = device_get_ivars(dev);
|
|
char sizestring[16];
|
|
|
|
if (fdp->mediasize > 1048576 * 5)
|
|
sprintf(sizestring, "%juMB", fdp->mediasize / 1048576);
|
|
else if (fdp->mediasize)
|
|
sprintf(sizestring, "%juKB", fdp->mediasize / 1024);
|
|
else
|
|
strcpy(sizestring, "(no media)");
|
|
|
|
device_printf(dev, "%s <%.40s %.8s> at ata%d-%s %s %s\n",
|
|
sizestring, atadev->param.model, atadev->param.revision,
|
|
device_get_unit(ch->dev), ata_unit2str(atadev),
|
|
ata_mode2str(atadev->mode),
|
|
ata_satarev2str(ATA_GETREV(device_get_parent(dev), atadev->unit)));
|
|
if (bootverbose) {
|
|
device_printf(dev, "%ju sectors [%juC/%dH/%dS]\n",
|
|
fdp->mediasize / fdp->sectorsize,
|
|
fdp->mediasize /(fdp->sectorsize*fdp->sectors*fdp->heads),
|
|
fdp->heads, fdp->sectors);
|
|
}
|
|
}
|
|
|
|
static device_method_t afd_methods[] = {
|
|
/* device interface */
|
|
DEVMETHOD(device_probe, afd_probe),
|
|
DEVMETHOD(device_attach, afd_attach),
|
|
DEVMETHOD(device_detach, afd_detach),
|
|
DEVMETHOD(device_shutdown, afd_shutdown),
|
|
|
|
/* ATA methods */
|
|
DEVMETHOD(ata_reinit, afd_reinit),
|
|
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static driver_t afd_driver = {
|
|
"afd",
|
|
afd_methods,
|
|
0,
|
|
};
|
|
|
|
static devclass_t afd_devclass;
|
|
|
|
DRIVER_MODULE(afd, ata, afd_driver, afd_devclass, NULL, NULL);
|
|
MODULE_VERSION(afd, 1);
|
|
MODULE_DEPEND(afd, ata, 1, 1, 1);
|
|
|