#include "opt_ata.h"
#include <sys/param.h>
#include <sys/bio.h>
#include <sys/buf.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/devicestat.h>
#include <sys/disk.h>
#include <sys/libkern.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/nata.h>
#include <sys/systm.h>
#include <vm/pmap.h>
#include <machine/md_var.h>
#include "ata-all.h"
#include "ata-disk.h"
#include "ata_if.h"
static inline void
biofinish(struct bio *bp, struct bio *x __unused, int error)
{
struct buf *bbp = bp->bio_buf;
bbp->b_flags |= B_ERROR;
bbp->b_error = error;
biodone(bp);
}
static d_open_t ad_open;
static d_close_t ad_close;
static d_ioctl_t ad_ioctl;
static d_strategy_t ad_strategy;
static d_dump_t ad_dump;
static struct dev_ops ad_ops = {
{ "ad", 0, D_DISK },
.d_open = ad_open,
.d_close = ad_close,
.d_read = physread,
.d_write = physwrite,
.d_ioctl = ad_ioctl,
.d_strategy = ad_strategy,
.d_dump = ad_dump,
};
static void ad_init(device_t dev);
static void ad_get_geometry(device_t dev);
static void ad_done(struct ata_request *request);
static void ad_describe(device_t dev);
static int ad_version(u_int16_t version);
static MALLOC_DEFINE(M_AD, "ad_driver", "ATA disk driver");
static int
ad_probe(device_t dev)
{
struct ata_device *atadev = device_get_softc(dev);
if (!(atadev->param.config & ATA_PROTO_ATAPI) ||
(atadev->param.config == ATA_CFA_MAGIC1) ||
(atadev->param.config == ATA_CFA_MAGIC2) ||
(atadev->param.config == ATA_CFA_MAGIC3))
return 0;
else
return ENXIO;
}
static int
ad_attach(device_t dev)
{
struct ata_channel *ch = device_get_softc(device_get_parent(dev));
struct ata_device *atadev = device_get_softc(dev);
struct disk_info info;
struct ad_softc *adp;
cdev_t cdev;
if (device_get_ivars(dev))
return EEXIST;
adp = kmalloc(sizeof(struct ad_softc), M_AD, M_INTWAIT | M_ZERO);
device_set_ivars(dev, adp);
ad_get_geometry(dev);
ad_init(dev);
devstat_add_entry(&adp->stats, "ad", device_get_unit(dev), DEV_BSIZE,
DEVSTAT_NO_ORDERED_TAGS,
DEVSTAT_TYPE_DIRECT | DEVSTAT_TYPE_IF_IDE,
DEVSTAT_PRIORITY_DISK);
cdev = disk_create(device_get_unit(dev), &adp->disk, &ad_ops);
cdev->si_drv1 = dev;
if (ch->dma)
cdev->si_iosize_max = ch->dma->max_iosize;
else
cdev->si_iosize_max = min(MAXPHYS,64*1024);
adp->cdev = cdev;
bzero(&info, sizeof(info));
info.d_media_blksize = DEV_BSIZE;
info.d_media_blocks = adp->total_secs;
info.d_secpertrack = adp->sectors;
info.d_nheads = adp->heads;
info.d_ncylinders = adp->total_secs/(adp->heads*adp->sectors);
info.d_secpercyl = adp->sectors * adp->heads;
info.d_serialno = atadev->param.serial;
device_add_child(dev, "subdisk", device_get_unit(dev));
bus_generic_attach(dev);
ad_describe(dev);
disk_setdiskinfo(&adp->disk, &info);
#if defined(__DragonFly__)
callout_init_mp(&atadev->spindown_timer);
#else
callout_init(&atadev->spindown_timer, 1);
#endif
return 0;
}
static int
ad_detach(device_t dev)
{
struct ad_softc *adp = device_get_ivars(dev);
struct ata_device *atadev = device_get_softc(dev);
device_t *children;
int nchildren, i;
if (!adp)
return ENXIO;
callout_drain(&atadev->spindown_timer);
if (!device_get_children(dev, &children, &nchildren)) {
for (i = 0; i < nchildren; i++)
if (children[i])
device_delete_child(dev, children[i]);
kfree(children, M_TEMP);
}
disk_invalidate(&adp->disk);
disk_destroy(&adp->disk);
ata_fail_requests(dev);
devstat_remove_entry(&adp->stats);
device_set_ivars(dev, NULL);
kfree(adp, M_AD);
return 0;
}
static void
ad_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);
}
static int
ad_reinit(device_t dev)
{
struct ata_channel *ch = device_get_softc(device_get_parent(dev));
struct ata_device *atadev = device_get_softc(dev);
if (((atadev->unit == ATA_MASTER) && !(ch->devices & ATA_ATA_MASTER)) ||
((atadev->unit == ATA_SLAVE) && !(ch->devices & ATA_ATA_SLAVE))) {
return 1;
}
ad_init(dev);
return 0;
}
static void
ad_power_callback(struct ata_request *request)
{
device_printf(request->dev, "drive spun down.\n");
ata_free_request(request);
}
static void
ad_spindown(void *priv)
{
device_t dev = priv;
struct ata_device *atadev = device_get_softc(dev);
struct ata_request *request;
if (!atadev->spindown)
return;
device_printf(dev, "Idle, spin down\n");
atadev->spindown_state = 1;
if (!(request = ata_alloc_request())) {
device_printf(dev, "FAILURE - out of memory in ad_spindown\n");
return;
}
request->dev = dev;
request->flags = ATA_R_CONTROL;
request->timeout = ATA_DEFAULT_TIMEOUT;
request->retries = 1;
request->callback = ad_power_callback;
request->u.ata.command = ATA_STANDBY_IMMEDIATE;
ata_queue_request(request);
}
static int
ad_open(struct dev_open_args *ap)
{
device_t dev = ap->a_head.a_dev->si_drv1;
struct ad_softc *adp = device_get_ivars(dev);
if (!adp || adp->cdev == NULL)
return ENXIO;
if(!device_is_attached(dev))
return EBUSY;
return 0;
}
static int
ad_close(struct dev_close_args *ap)
{
return 0;
}
static int
ad_strategy(struct dev_strategy_args *ap)
{
device_t dev = ap->a_head.a_dev->si_drv1;
struct bio *bp = ap->a_bio;
struct buf *bbp = bp->bio_buf;
struct ata_device *atadev = device_get_softc(dev);
struct ata_request *request;
struct ad_softc *adp = device_get_ivars(dev);
if (atadev->spindown)
callout_reset(&atadev->spindown_timer, hz * atadev->spindown,
ad_spindown, dev);
if (!(request = ata_alloc_request())) {
device_printf(dev, "FAILURE - out of memory in strategy\n");
biofinish(bp, NULL, ENOMEM);
return(0);
}
request->dev = dev;
request->bio = bp;
request->callback = ad_done;
if (atadev->spindown_state) {
device_printf(dev, "request while spun down, starting.\n");
atadev->spindown_state = 0;
request->timeout = MAX(ATA_DEFAULT_TIMEOUT, 31);
} else {
request->timeout = ATA_DEFAULT_TIMEOUT;
}
request->retries = 2;
request->data = bbp->b_data;
request->bytecount = bbp->b_bcount;
request->u.ata.lba = (u_int64_t)(bp->bio_offset >> DEV_BSHIFT);
request->u.ata.count = request->bytecount / DEV_BSIZE;
request->transfersize = min(bbp->b_bcount, atadev->max_iosize);
switch (bbp->b_cmd) {
case BUF_CMD_READ:
request->flags = ATA_R_READ;
if (atadev->mode >= ATA_DMA) {
request->u.ata.command = ATA_READ_DMA;
request->flags |= ATA_R_DMA;
}
else if (request->transfersize > DEV_BSIZE)
request->u.ata.command = ATA_READ_MUL;
else
request->u.ata.command = ATA_READ;
break;
case BUF_CMD_WRITE:
request->flags = ATA_R_WRITE;
if (atadev->mode >= ATA_DMA) {
request->u.ata.command = ATA_WRITE_DMA;
request->flags |= ATA_R_DMA;
}
else if (request->transfersize > DEV_BSIZE)
request->u.ata.command = ATA_WRITE_MUL;
else
request->u.ata.command = ATA_WRITE;
break;
case BUF_CMD_FLUSH:
request->u.ata.lba = 0;
request->u.ata.count = 0;
request->u.ata.feature = 0;
request->bytecount = 0;
request->transfersize = 0;
request->flags = ATA_R_CONTROL;
request->u.ata.command = ATA_FLUSHCACHE;
request->timeout = 30;
break;
default:
device_printf(dev, "FAILURE - unknown BUF operation\n");
ata_free_request(request);
biofinish(bp, NULL, EIO);
return(0);
}
request->flags |= ATA_R_ORDERED;
devstat_start_transaction(&adp->stats);
ata_queue_request(request);
return(0);
}
static void
ad_done(struct ata_request *request)
{
struct ad_softc *adp = device_get_ivars(request->dev);
struct bio *bp = request->bio;
struct buf *bbp = bp->bio_buf;
if ((bbp->b_error = request->result))
bbp->b_flags |= B_ERROR;
bbp->b_resid = bbp->b_bcount - request->donecount;
devstat_end_transaction_buf(&adp->stats, bbp);
biodone(bp);
ata_free_request(request);
}
static int
ad_ioctl(struct dev_ioctl_args *ap)
{
return ata_device_ioctl(ap->a_head.a_dev->si_drv1, ap->a_cmd, ap->a_data);
}
static int
ad_dump(struct dev_dump_args *ap)
{
device_t dev = ap->a_head.a_dev->si_drv1;
struct ata_device *atadev = device_get_softc(dev);
struct ata_request request;
ata_drop_requests(dev);
if (ap->a_length == 0) {
if (atadev->param.support.command2 & ATA_SUPPORT_FLUSHCACHE)
return ata_controlcmd(dev, ATA_FLUSHCACHE, 0, 0, 0);
else
return ENXIO;
}
bzero(&request, sizeof(struct ata_request));
request.dev = dev;
request.data = ap->a_virtual;
request.bytecount = ap->a_length;
request.transfersize = min(request.bytecount, atadev->max_iosize);
request.flags = ATA_R_WRITE;
if (atadev->mode >= ATA_DMA) {
request.u.ata.command = ATA_WRITE_DMA;
request.flags |= ATA_DMA;
} else if (request.transfersize > DEV_BSIZE)
request.u.ata.command = ATA_WRITE_MUL;
else
request.u.ata.command = ATA_WRITE;
request.u.ata.lba = ap->a_offset / DEV_BSIZE;
request.u.ata.count = request.bytecount / DEV_BSIZE;
request.timeout = ATA_DEFAULT_TIMEOUT;
request.retries = 2;
ata_queue_request(&request);
return request.result;
}
static void
ad_init(device_t dev)
{
struct ata_device *atadev = device_get_softc(dev);
ATA_SETMODE(device_get_parent(dev), dev);
if (atadev->param.support.command1 & ATA_SUPPORT_LOOKAHEAD)
ata_controlcmd(dev, ATA_SETFEATURES, ATA_SF_ENAB_RCACHE, 0, 0);
if (atadev->param.support.command1 & ATA_SUPPORT_WRITECACHE) {
if (ata_wc)
ata_controlcmd(dev, ATA_SETFEATURES, ATA_SF_ENAB_WCACHE, 0, 0);
else
ata_controlcmd(dev, ATA_SETFEATURES, ATA_SF_DIS_WCACHE, 0, 0);
}
if (ad_version(atadev->param.version_major)) {
int secsperint = max(1, min(atadev->param.sectors_intr & 0xff, 16));
if (!ata_controlcmd(dev, ATA_SET_MULTI, 0, 0, secsperint))
atadev->max_iosize = secsperint * DEV_BSIZE;
}
else
atadev->max_iosize = DEV_BSIZE;
}
static void
ad_get_geometry(device_t dev)
{
struct ata_device *atadev = device_get_softc(dev);
struct ad_softc *adp = device_get_ivars(dev);
u_int64_t lbasize48;
u_int32_t lbasize;
if ((atadev->param.atavalid & ATA_FLAG_54_58) &&
atadev->param.current_heads && atadev->param.current_sectors) {
adp->heads = atadev->param.current_heads;
adp->sectors = atadev->param.current_sectors;
adp->total_secs = (u_int32_t)atadev->param.current_size_1 |
((u_int32_t)atadev->param.current_size_2 << 16);
}
else {
adp->heads = atadev->param.heads;
adp->sectors = atadev->param.sectors;
adp->total_secs = atadev->param.cylinders * adp->heads * adp->sectors;
}
lbasize = (u_int32_t)atadev->param.lba_size_1 |
((u_int32_t)atadev->param.lba_size_2 << 16);
if (!ad_version(atadev->param.version_major) || !lbasize)
atadev->flags |= ATA_D_USE_CHS;
if (atadev->param.cylinders == 16383 || adp->total_secs < lbasize)
adp->total_secs = lbasize;
lbasize48 = ((u_int64_t)atadev->param.lba_size48_1) |
((u_int64_t)atadev->param.lba_size48_2 << 16) |
((u_int64_t)atadev->param.lba_size48_3 << 32) |
((u_int64_t)atadev->param.lba_size48_4 << 48);
if ((atadev->param.support.command2 & ATA_SUPPORT_ADDRESS48) &&
lbasize48 > ATA_MAX_28BIT_LBA)
adp->total_secs = lbasize48;
}
static void
ad_describe(device_t dev)
{
struct ata_channel *ch = device_get_softc(device_get_parent(dev));
struct ata_device *atadev = device_get_softc(dev);
struct ad_softc *adp = device_get_ivars(dev);
u_int8_t *marker, vendor[64], product[64];
if ((marker = index(atadev->param.model, ' ')) ||
(marker = index(atadev->param.model, '-'))) {
int len = (marker - atadev->param.model);
strncpy(vendor, atadev->param.model, len);
vendor[len++] = 0;
strcat(vendor, " ");
strncpy(product, atadev->param.model + len, 40 - len);
vendor[40 - len] = 0;
}
else {
if (!strncmp(atadev->param.model, "ST", 2))
strcpy(vendor, "Seagate ");
else if (!strncmp(atadev->param.model, "HDS", 3))
strcpy(vendor, "Hitachi ");
else
strcpy(vendor, "");
strncpy(product, atadev->param.model, 40);
}
device_printf(dev, "%juMB <%s%s %.8s> at ata%d-%s %s%s\n",
adp->total_secs / (1048576 / DEV_BSIZE),
vendor, product, atadev->param.revision,
device_get_unit(ch->dev), ata_unit2str(atadev),
(adp->flags & AD_F_TAG_ENABLED) ? "tagged " : "",
ata_mode2str(atadev->mode));
if (bootverbose) {
device_printf(dev, "%ju sectors [%juC/%dH/%dS] "
"%d sectors/interrupt %d depth queue\n", adp->total_secs,
adp->total_secs / (adp->heads * adp->sectors),
adp->heads, adp->sectors, atadev->max_iosize / DEV_BSIZE,
adp->num_tags + 1);
}
}
static int
ad_version(u_int16_t version)
{
int bit;
if (version == 0xffff)
return 0;
for (bit = 15; bit >= 0; bit--)
if (version & (1<<bit))
return bit;
return 0;
}
static device_method_t ad_methods[] = {
DEVMETHOD(device_probe, ad_probe),
DEVMETHOD(device_attach, ad_attach),
DEVMETHOD(device_detach, ad_detach),
DEVMETHOD(device_shutdown, ad_shutdown),
DEVMETHOD(ata_reinit, ad_reinit),
DEVMETHOD_END
};
static driver_t ad_driver = {
"ad",
ad_methods,
0,
};
devclass_t ad_devclass;
DRIVER_MODULE(ad, ata, ad_driver, ad_devclass, NULL, NULL);
MODULE_VERSION(ad, 1);
MODULE_DEPEND(ad, ata, 1, 1, 1);