2a842acab1
Currently we use nornal Linux errno values in the block layer, and while we accept any error a few have overloaded magic meanings. This patch instead introduces a new blk_status_t value that holds block layer specific status codes and explicitly explains their meaning. Helpers to convert from and to the previous special meanings are provided for now, but I suspect we want to get rid of them in the long run - those drivers that have a errno input (e.g. networking) usually get errnos that don't know about the special block layer overloads, and similarly returning them to userspace will usually return somethings that strictly speaking isn't correct for file system operations, but that's left as an exercise for later. For now the set of errors is a very limited set that closely corresponds to the previous overloaded errno values, but there is some low hanging fruite to improve it. blk_status_t (ab)uses the sparse __bitwise annotations to allow for sparse typechecking, so that we can easily catch places passing the wrong values. Signed-off-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Jens Axboe <axboe@fb.com>
678 lines
17 KiB
C
678 lines
17 KiB
C
/*
|
|
* Copyright (C) 2000-2002 Michael Cornwell <cornwell@acm.org>
|
|
* Copyright (C) 2000-2002 Andre Hedrick <andre@linux-ide.org>
|
|
* Copyright (C) 2001-2002 Klaus Smolin
|
|
* IBM Storage Technology Division
|
|
* Copyright (C) 2003-2004, 2007 Bartlomiej Zolnierkiewicz
|
|
*
|
|
* The big the bad and the ugly.
|
|
*/
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/string.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/export.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/hdreg.h>
|
|
#include <linux/ide.h>
|
|
#include <linux/nmi.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <asm/io.h>
|
|
|
|
void ide_tf_readback(ide_drive_t *drive, struct ide_cmd *cmd)
|
|
{
|
|
ide_hwif_t *hwif = drive->hwif;
|
|
const struct ide_tp_ops *tp_ops = hwif->tp_ops;
|
|
|
|
/* Be sure we're looking at the low order bytes */
|
|
tp_ops->write_devctl(hwif, ATA_DEVCTL_OBS);
|
|
|
|
tp_ops->tf_read(drive, &cmd->tf, cmd->valid.in.tf);
|
|
|
|
if (cmd->tf_flags & IDE_TFLAG_LBA48) {
|
|
tp_ops->write_devctl(hwif, ATA_HOB | ATA_DEVCTL_OBS);
|
|
|
|
tp_ops->tf_read(drive, &cmd->hob, cmd->valid.in.hob);
|
|
}
|
|
}
|
|
|
|
void ide_tf_dump(const char *s, struct ide_cmd *cmd)
|
|
{
|
|
#ifdef DEBUG
|
|
printk("%s: tf: feat 0x%02x nsect 0x%02x lbal 0x%02x "
|
|
"lbam 0x%02x lbah 0x%02x dev 0x%02x cmd 0x%02x\n",
|
|
s, cmd->tf.feature, cmd->tf.nsect,
|
|
cmd->tf.lbal, cmd->tf.lbam, cmd->tf.lbah,
|
|
cmd->tf.device, cmd->tf.command);
|
|
printk("%s: hob: nsect 0x%02x lbal 0x%02x lbam 0x%02x lbah 0x%02x\n",
|
|
s, cmd->hob.nsect, cmd->hob.lbal, cmd->hob.lbam, cmd->hob.lbah);
|
|
#endif
|
|
}
|
|
|
|
int taskfile_lib_get_identify(ide_drive_t *drive, u8 *buf)
|
|
{
|
|
struct ide_cmd cmd;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.tf.nsect = 0x01;
|
|
if (drive->media == ide_disk)
|
|
cmd.tf.command = ATA_CMD_ID_ATA;
|
|
else
|
|
cmd.tf.command = ATA_CMD_ID_ATAPI;
|
|
cmd.valid.out.tf = IDE_VALID_OUT_TF | IDE_VALID_DEVICE;
|
|
cmd.valid.in.tf = IDE_VALID_IN_TF | IDE_VALID_DEVICE;
|
|
cmd.protocol = ATA_PROT_PIO;
|
|
|
|
return ide_raw_taskfile(drive, &cmd, buf, 1);
|
|
}
|
|
|
|
static ide_startstop_t task_no_data_intr(ide_drive_t *);
|
|
static ide_startstop_t pre_task_out_intr(ide_drive_t *, struct ide_cmd *);
|
|
static ide_startstop_t task_pio_intr(ide_drive_t *);
|
|
|
|
ide_startstop_t do_rw_taskfile(ide_drive_t *drive, struct ide_cmd *orig_cmd)
|
|
{
|
|
ide_hwif_t *hwif = drive->hwif;
|
|
struct ide_cmd *cmd = &hwif->cmd;
|
|
struct ide_taskfile *tf = &cmd->tf;
|
|
ide_handler_t *handler = NULL;
|
|
const struct ide_tp_ops *tp_ops = hwif->tp_ops;
|
|
const struct ide_dma_ops *dma_ops = hwif->dma_ops;
|
|
|
|
if (orig_cmd->protocol == ATA_PROT_PIO &&
|
|
(orig_cmd->tf_flags & IDE_TFLAG_MULTI_PIO) &&
|
|
drive->mult_count == 0) {
|
|
pr_err("%s: multimode not set!\n", drive->name);
|
|
return ide_stopped;
|
|
}
|
|
|
|
if (orig_cmd->ftf_flags & IDE_FTFLAG_FLAGGED)
|
|
orig_cmd->ftf_flags |= IDE_FTFLAG_SET_IN_FLAGS;
|
|
|
|
memcpy(cmd, orig_cmd, sizeof(*cmd));
|
|
|
|
if ((cmd->tf_flags & IDE_TFLAG_DMA_PIO_FALLBACK) == 0) {
|
|
ide_tf_dump(drive->name, cmd);
|
|
tp_ops->write_devctl(hwif, ATA_DEVCTL_OBS);
|
|
|
|
if (cmd->ftf_flags & IDE_FTFLAG_OUT_DATA) {
|
|
u8 data[2] = { cmd->tf.data, cmd->hob.data };
|
|
|
|
tp_ops->output_data(drive, cmd, data, 2);
|
|
}
|
|
|
|
if (cmd->valid.out.tf & IDE_VALID_DEVICE) {
|
|
u8 HIHI = (cmd->tf_flags & IDE_TFLAG_LBA48) ?
|
|
0xE0 : 0xEF;
|
|
|
|
if (!(cmd->ftf_flags & IDE_FTFLAG_FLAGGED))
|
|
cmd->tf.device &= HIHI;
|
|
cmd->tf.device |= drive->select;
|
|
}
|
|
|
|
tp_ops->tf_load(drive, &cmd->hob, cmd->valid.out.hob);
|
|
tp_ops->tf_load(drive, &cmd->tf, cmd->valid.out.tf);
|
|
}
|
|
|
|
switch (cmd->protocol) {
|
|
case ATA_PROT_PIO:
|
|
if (cmd->tf_flags & IDE_TFLAG_WRITE) {
|
|
tp_ops->exec_command(hwif, tf->command);
|
|
ndelay(400); /* FIXME */
|
|
return pre_task_out_intr(drive, cmd);
|
|
}
|
|
handler = task_pio_intr;
|
|
/* fall-through */
|
|
case ATA_PROT_NODATA:
|
|
if (handler == NULL)
|
|
handler = task_no_data_intr;
|
|
ide_execute_command(drive, cmd, handler, WAIT_WORSTCASE);
|
|
return ide_started;
|
|
case ATA_PROT_DMA:
|
|
if (ide_dma_prepare(drive, cmd))
|
|
return ide_stopped;
|
|
hwif->expiry = dma_ops->dma_timer_expiry;
|
|
ide_execute_command(drive, cmd, ide_dma_intr, 2 * WAIT_CMD);
|
|
dma_ops->dma_start(drive);
|
|
default:
|
|
return ide_started;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(do_rw_taskfile);
|
|
|
|
static ide_startstop_t task_no_data_intr(ide_drive_t *drive)
|
|
{
|
|
ide_hwif_t *hwif = drive->hwif;
|
|
struct ide_cmd *cmd = &hwif->cmd;
|
|
struct ide_taskfile *tf = &cmd->tf;
|
|
int custom = (cmd->tf_flags & IDE_TFLAG_CUSTOM_HANDLER) ? 1 : 0;
|
|
int retries = (custom && tf->command == ATA_CMD_INIT_DEV_PARAMS) ? 5 : 1;
|
|
u8 stat;
|
|
|
|
local_irq_enable_in_hardirq();
|
|
|
|
while (1) {
|
|
stat = hwif->tp_ops->read_status(hwif);
|
|
if ((stat & ATA_BUSY) == 0 || retries-- == 0)
|
|
break;
|
|
udelay(10);
|
|
};
|
|
|
|
if (!OK_STAT(stat, ATA_DRDY, BAD_STAT)) {
|
|
if (custom && tf->command == ATA_CMD_SET_MULTI) {
|
|
drive->mult_req = drive->mult_count = 0;
|
|
drive->special_flags |= IDE_SFLAG_RECALIBRATE;
|
|
(void)ide_dump_status(drive, __func__, stat);
|
|
return ide_stopped;
|
|
} else if (custom && tf->command == ATA_CMD_INIT_DEV_PARAMS) {
|
|
if ((stat & (ATA_ERR | ATA_DRQ)) == 0) {
|
|
ide_set_handler(drive, &task_no_data_intr,
|
|
WAIT_WORSTCASE);
|
|
return ide_started;
|
|
}
|
|
}
|
|
return ide_error(drive, "task_no_data_intr", stat);
|
|
}
|
|
|
|
if (custom && tf->command == ATA_CMD_SET_MULTI)
|
|
drive->mult_count = drive->mult_req;
|
|
|
|
if (custom == 0 || tf->command == ATA_CMD_IDLEIMMEDIATE ||
|
|
tf->command == ATA_CMD_CHK_POWER) {
|
|
struct request *rq = hwif->rq;
|
|
|
|
if (ata_pm_request(rq))
|
|
ide_complete_pm_rq(drive, rq);
|
|
else
|
|
ide_finish_cmd(drive, cmd, stat);
|
|
}
|
|
|
|
return ide_stopped;
|
|
}
|
|
|
|
static u8 wait_drive_not_busy(ide_drive_t *drive)
|
|
{
|
|
ide_hwif_t *hwif = drive->hwif;
|
|
int retries;
|
|
u8 stat;
|
|
|
|
/*
|
|
* Last sector was transferred, wait until device is ready. This can
|
|
* take up to 6 ms on some ATAPI devices, so we will wait max 10 ms.
|
|
*/
|
|
for (retries = 0; retries < 1000; retries++) {
|
|
stat = hwif->tp_ops->read_status(hwif);
|
|
|
|
if (stat & ATA_BUSY)
|
|
udelay(10);
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (stat & ATA_BUSY)
|
|
pr_err("%s: drive still BUSY!\n", drive->name);
|
|
|
|
return stat;
|
|
}
|
|
|
|
void ide_pio_bytes(ide_drive_t *drive, struct ide_cmd *cmd,
|
|
unsigned int write, unsigned int len)
|
|
{
|
|
ide_hwif_t *hwif = drive->hwif;
|
|
struct scatterlist *sg = hwif->sg_table;
|
|
struct scatterlist *cursg = cmd->cursg;
|
|
unsigned long uninitialized_var(flags);
|
|
struct page *page;
|
|
unsigned int offset;
|
|
u8 *buf;
|
|
|
|
cursg = cmd->cursg;
|
|
if (cursg == NULL)
|
|
cursg = cmd->cursg = sg;
|
|
|
|
while (len) {
|
|
unsigned nr_bytes = min(len, cursg->length - cmd->cursg_ofs);
|
|
int page_is_high;
|
|
|
|
page = sg_page(cursg);
|
|
offset = cursg->offset + cmd->cursg_ofs;
|
|
|
|
/* get the current page and offset */
|
|
page = nth_page(page, (offset >> PAGE_SHIFT));
|
|
offset %= PAGE_SIZE;
|
|
|
|
nr_bytes = min_t(unsigned, nr_bytes, (PAGE_SIZE - offset));
|
|
|
|
page_is_high = PageHighMem(page);
|
|
if (page_is_high)
|
|
local_irq_save(flags);
|
|
|
|
buf = kmap_atomic(page) + offset;
|
|
|
|
cmd->nleft -= nr_bytes;
|
|
cmd->cursg_ofs += nr_bytes;
|
|
|
|
if (cmd->cursg_ofs == cursg->length) {
|
|
cursg = cmd->cursg = sg_next(cmd->cursg);
|
|
cmd->cursg_ofs = 0;
|
|
}
|
|
|
|
/* do the actual data transfer */
|
|
if (write)
|
|
hwif->tp_ops->output_data(drive, cmd, buf, nr_bytes);
|
|
else
|
|
hwif->tp_ops->input_data(drive, cmd, buf, nr_bytes);
|
|
|
|
kunmap_atomic(buf);
|
|
|
|
if (page_is_high)
|
|
local_irq_restore(flags);
|
|
|
|
len -= nr_bytes;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(ide_pio_bytes);
|
|
|
|
static void ide_pio_datablock(ide_drive_t *drive, struct ide_cmd *cmd,
|
|
unsigned int write)
|
|
{
|
|
unsigned int nr_bytes;
|
|
|
|
u8 saved_io_32bit = drive->io_32bit;
|
|
|
|
if (cmd->tf_flags & IDE_TFLAG_FS)
|
|
scsi_req(cmd->rq)->result = 0;
|
|
|
|
if (cmd->tf_flags & IDE_TFLAG_IO_16BIT)
|
|
drive->io_32bit = 0;
|
|
|
|
touch_softlockup_watchdog();
|
|
|
|
if (cmd->tf_flags & IDE_TFLAG_MULTI_PIO)
|
|
nr_bytes = min_t(unsigned, cmd->nleft, drive->mult_count << 9);
|
|
else
|
|
nr_bytes = SECTOR_SIZE;
|
|
|
|
ide_pio_bytes(drive, cmd, write, nr_bytes);
|
|
|
|
drive->io_32bit = saved_io_32bit;
|
|
}
|
|
|
|
static void ide_error_cmd(ide_drive_t *drive, struct ide_cmd *cmd)
|
|
{
|
|
if (cmd->tf_flags & IDE_TFLAG_FS) {
|
|
int nr_bytes = cmd->nbytes - cmd->nleft;
|
|
|
|
if (cmd->protocol == ATA_PROT_PIO &&
|
|
((cmd->tf_flags & IDE_TFLAG_WRITE) || cmd->nleft == 0)) {
|
|
if (cmd->tf_flags & IDE_TFLAG_MULTI_PIO)
|
|
nr_bytes -= drive->mult_count << 9;
|
|
else
|
|
nr_bytes -= SECTOR_SIZE;
|
|
}
|
|
|
|
if (nr_bytes > 0)
|
|
ide_complete_rq(drive, BLK_STS_OK, nr_bytes);
|
|
}
|
|
}
|
|
|
|
void ide_finish_cmd(ide_drive_t *drive, struct ide_cmd *cmd, u8 stat)
|
|
{
|
|
struct request *rq = drive->hwif->rq;
|
|
u8 err = ide_read_error(drive), nsect = cmd->tf.nsect;
|
|
u8 set_xfer = !!(cmd->tf_flags & IDE_TFLAG_SET_XFER);
|
|
|
|
ide_complete_cmd(drive, cmd, stat, err);
|
|
scsi_req(rq)->result = err;
|
|
|
|
if (err == 0 && set_xfer) {
|
|
ide_set_xfer_rate(drive, nsect);
|
|
ide_driveid_update(drive);
|
|
}
|
|
|
|
ide_complete_rq(drive, err ? BLK_STS_IOERR : BLK_STS_OK, blk_rq_bytes(rq));
|
|
}
|
|
|
|
/*
|
|
* Handler for command with PIO data phase.
|
|
*/
|
|
static ide_startstop_t task_pio_intr(ide_drive_t *drive)
|
|
{
|
|
ide_hwif_t *hwif = drive->hwif;
|
|
struct ide_cmd *cmd = &drive->hwif->cmd;
|
|
u8 stat = hwif->tp_ops->read_status(hwif);
|
|
u8 write = !!(cmd->tf_flags & IDE_TFLAG_WRITE);
|
|
|
|
if (write == 0) {
|
|
/* Error? */
|
|
if (stat & ATA_ERR)
|
|
goto out_err;
|
|
|
|
/* Didn't want any data? Odd. */
|
|
if ((stat & ATA_DRQ) == 0) {
|
|
/* Command all done? */
|
|
if (OK_STAT(stat, ATA_DRDY, ATA_BUSY))
|
|
goto out_end;
|
|
|
|
/* Assume it was a spurious irq */
|
|
goto out_wait;
|
|
}
|
|
} else {
|
|
if (!OK_STAT(stat, DRIVE_READY, drive->bad_wstat))
|
|
goto out_err;
|
|
|
|
/* Deal with unexpected ATA data phase. */
|
|
if (((stat & ATA_DRQ) == 0) ^ (cmd->nleft == 0))
|
|
goto out_err;
|
|
}
|
|
|
|
if (write && cmd->nleft == 0)
|
|
goto out_end;
|
|
|
|
/* Still data left to transfer. */
|
|
ide_pio_datablock(drive, cmd, write);
|
|
|
|
/* Are we done? Check status and finish transfer. */
|
|
if (write == 0 && cmd->nleft == 0) {
|
|
stat = wait_drive_not_busy(drive);
|
|
if (!OK_STAT(stat, 0, BAD_STAT))
|
|
goto out_err;
|
|
|
|
goto out_end;
|
|
}
|
|
out_wait:
|
|
/* Still data left to transfer. */
|
|
ide_set_handler(drive, &task_pio_intr, WAIT_WORSTCASE);
|
|
return ide_started;
|
|
out_end:
|
|
if ((cmd->tf_flags & IDE_TFLAG_FS) == 0)
|
|
ide_finish_cmd(drive, cmd, stat);
|
|
else
|
|
ide_complete_rq(drive, BLK_STS_OK, blk_rq_sectors(cmd->rq) << 9);
|
|
return ide_stopped;
|
|
out_err:
|
|
ide_error_cmd(drive, cmd);
|
|
return ide_error(drive, __func__, stat);
|
|
}
|
|
|
|
static ide_startstop_t pre_task_out_intr(ide_drive_t *drive,
|
|
struct ide_cmd *cmd)
|
|
{
|
|
ide_startstop_t startstop;
|
|
|
|
if (ide_wait_stat(&startstop, drive, ATA_DRQ,
|
|
drive->bad_wstat, WAIT_DRQ)) {
|
|
pr_err("%s: no DRQ after issuing %sWRITE%s\n", drive->name,
|
|
(cmd->tf_flags & IDE_TFLAG_MULTI_PIO) ? "MULT" : "",
|
|
(drive->dev_flags & IDE_DFLAG_LBA48) ? "_EXT" : "");
|
|
return startstop;
|
|
}
|
|
|
|
if ((drive->dev_flags & IDE_DFLAG_UNMASK) == 0)
|
|
local_irq_disable();
|
|
|
|
ide_set_handler(drive, &task_pio_intr, WAIT_WORSTCASE);
|
|
|
|
ide_pio_datablock(drive, cmd, 1);
|
|
|
|
return ide_started;
|
|
}
|
|
|
|
int ide_raw_taskfile(ide_drive_t *drive, struct ide_cmd *cmd, u8 *buf,
|
|
u16 nsect)
|
|
{
|
|
struct request *rq;
|
|
int error;
|
|
|
|
rq = blk_get_request(drive->queue,
|
|
(cmd->tf_flags & IDE_TFLAG_WRITE) ?
|
|
REQ_OP_DRV_OUT : REQ_OP_DRV_IN, __GFP_RECLAIM);
|
|
scsi_req_init(rq);
|
|
ide_req(rq)->type = ATA_PRIV_TASKFILE;
|
|
|
|
/*
|
|
* (ks) We transfer currently only whole sectors.
|
|
* This is suffient for now. But, it would be great,
|
|
* if we would find a solution to transfer any size.
|
|
* To support special commands like READ LONG.
|
|
*/
|
|
if (nsect) {
|
|
error = blk_rq_map_kern(drive->queue, rq, buf,
|
|
nsect * SECTOR_SIZE, __GFP_RECLAIM);
|
|
if (error)
|
|
goto put_req;
|
|
}
|
|
|
|
rq->special = cmd;
|
|
cmd->rq = rq;
|
|
|
|
blk_execute_rq(drive->queue, NULL, rq, 0);
|
|
error = scsi_req(rq)->result ? -EIO : 0;
|
|
put_req:
|
|
blk_put_request(rq);
|
|
return error;
|
|
}
|
|
EXPORT_SYMBOL(ide_raw_taskfile);
|
|
|
|
int ide_no_data_taskfile(ide_drive_t *drive, struct ide_cmd *cmd)
|
|
{
|
|
cmd->protocol = ATA_PROT_NODATA;
|
|
|
|
return ide_raw_taskfile(drive, cmd, NULL, 0);
|
|
}
|
|
EXPORT_SYMBOL_GPL(ide_no_data_taskfile);
|
|
|
|
#ifdef CONFIG_IDE_TASK_IOCTL
|
|
int ide_taskfile_ioctl(ide_drive_t *drive, unsigned long arg)
|
|
{
|
|
ide_task_request_t *req_task;
|
|
struct ide_cmd cmd;
|
|
u8 *outbuf = NULL;
|
|
u8 *inbuf = NULL;
|
|
u8 *data_buf = NULL;
|
|
int err = 0;
|
|
int tasksize = sizeof(struct ide_task_request_s);
|
|
unsigned int taskin = 0;
|
|
unsigned int taskout = 0;
|
|
u16 nsect = 0;
|
|
char __user *buf = (char __user *)arg;
|
|
|
|
req_task = memdup_user(buf, tasksize);
|
|
if (IS_ERR(req_task))
|
|
return PTR_ERR(req_task);
|
|
|
|
taskout = req_task->out_size;
|
|
taskin = req_task->in_size;
|
|
|
|
if (taskin > 65536 || taskout > 65536) {
|
|
err = -EINVAL;
|
|
goto abort;
|
|
}
|
|
|
|
if (taskout) {
|
|
int outtotal = tasksize;
|
|
outbuf = kzalloc(taskout, GFP_KERNEL);
|
|
if (outbuf == NULL) {
|
|
err = -ENOMEM;
|
|
goto abort;
|
|
}
|
|
if (copy_from_user(outbuf, buf + outtotal, taskout)) {
|
|
err = -EFAULT;
|
|
goto abort;
|
|
}
|
|
}
|
|
|
|
if (taskin) {
|
|
int intotal = tasksize + taskout;
|
|
inbuf = kzalloc(taskin, GFP_KERNEL);
|
|
if (inbuf == NULL) {
|
|
err = -ENOMEM;
|
|
goto abort;
|
|
}
|
|
if (copy_from_user(inbuf, buf + intotal, taskin)) {
|
|
err = -EFAULT;
|
|
goto abort;
|
|
}
|
|
}
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
|
|
memcpy(&cmd.hob, req_task->hob_ports, HDIO_DRIVE_HOB_HDR_SIZE - 2);
|
|
memcpy(&cmd.tf, req_task->io_ports, HDIO_DRIVE_TASK_HDR_SIZE);
|
|
|
|
cmd.valid.out.tf = IDE_VALID_DEVICE;
|
|
cmd.valid.in.tf = IDE_VALID_DEVICE | IDE_VALID_IN_TF;
|
|
cmd.tf_flags = IDE_TFLAG_IO_16BIT;
|
|
|
|
if (drive->dev_flags & IDE_DFLAG_LBA48) {
|
|
cmd.tf_flags |= IDE_TFLAG_LBA48;
|
|
cmd.valid.in.hob = IDE_VALID_IN_HOB;
|
|
}
|
|
|
|
if (req_task->out_flags.all) {
|
|
cmd.ftf_flags |= IDE_FTFLAG_FLAGGED;
|
|
|
|
if (req_task->out_flags.b.data)
|
|
cmd.ftf_flags |= IDE_FTFLAG_OUT_DATA;
|
|
|
|
if (req_task->out_flags.b.nsector_hob)
|
|
cmd.valid.out.hob |= IDE_VALID_NSECT;
|
|
if (req_task->out_flags.b.sector_hob)
|
|
cmd.valid.out.hob |= IDE_VALID_LBAL;
|
|
if (req_task->out_flags.b.lcyl_hob)
|
|
cmd.valid.out.hob |= IDE_VALID_LBAM;
|
|
if (req_task->out_flags.b.hcyl_hob)
|
|
cmd.valid.out.hob |= IDE_VALID_LBAH;
|
|
|
|
if (req_task->out_flags.b.error_feature)
|
|
cmd.valid.out.tf |= IDE_VALID_FEATURE;
|
|
if (req_task->out_flags.b.nsector)
|
|
cmd.valid.out.tf |= IDE_VALID_NSECT;
|
|
if (req_task->out_flags.b.sector)
|
|
cmd.valid.out.tf |= IDE_VALID_LBAL;
|
|
if (req_task->out_flags.b.lcyl)
|
|
cmd.valid.out.tf |= IDE_VALID_LBAM;
|
|
if (req_task->out_flags.b.hcyl)
|
|
cmd.valid.out.tf |= IDE_VALID_LBAH;
|
|
} else {
|
|
cmd.valid.out.tf |= IDE_VALID_OUT_TF;
|
|
if (cmd.tf_flags & IDE_TFLAG_LBA48)
|
|
cmd.valid.out.hob |= IDE_VALID_OUT_HOB;
|
|
}
|
|
|
|
if (req_task->in_flags.b.data)
|
|
cmd.ftf_flags |= IDE_FTFLAG_IN_DATA;
|
|
|
|
if (req_task->req_cmd == IDE_DRIVE_TASK_RAW_WRITE) {
|
|
/* fixup data phase if needed */
|
|
if (req_task->data_phase == TASKFILE_IN_DMAQ ||
|
|
req_task->data_phase == TASKFILE_IN_DMA)
|
|
cmd.tf_flags |= IDE_TFLAG_WRITE;
|
|
}
|
|
|
|
cmd.protocol = ATA_PROT_DMA;
|
|
|
|
switch (req_task->data_phase) {
|
|
case TASKFILE_MULTI_OUT:
|
|
if (!drive->mult_count) {
|
|
/* (hs): give up if multcount is not set */
|
|
pr_err("%s: %s Multimode Write multcount is not set\n",
|
|
drive->name, __func__);
|
|
err = -EPERM;
|
|
goto abort;
|
|
}
|
|
cmd.tf_flags |= IDE_TFLAG_MULTI_PIO;
|
|
/* fall through */
|
|
case TASKFILE_OUT:
|
|
cmd.protocol = ATA_PROT_PIO;
|
|
/* fall through */
|
|
case TASKFILE_OUT_DMAQ:
|
|
case TASKFILE_OUT_DMA:
|
|
cmd.tf_flags |= IDE_TFLAG_WRITE;
|
|
nsect = taskout / SECTOR_SIZE;
|
|
data_buf = outbuf;
|
|
break;
|
|
case TASKFILE_MULTI_IN:
|
|
if (!drive->mult_count) {
|
|
/* (hs): give up if multcount is not set */
|
|
pr_err("%s: %s Multimode Read multcount is not set\n",
|
|
drive->name, __func__);
|
|
err = -EPERM;
|
|
goto abort;
|
|
}
|
|
cmd.tf_flags |= IDE_TFLAG_MULTI_PIO;
|
|
/* fall through */
|
|
case TASKFILE_IN:
|
|
cmd.protocol = ATA_PROT_PIO;
|
|
/* fall through */
|
|
case TASKFILE_IN_DMAQ:
|
|
case TASKFILE_IN_DMA:
|
|
nsect = taskin / SECTOR_SIZE;
|
|
data_buf = inbuf;
|
|
break;
|
|
case TASKFILE_NO_DATA:
|
|
cmd.protocol = ATA_PROT_NODATA;
|
|
break;
|
|
default:
|
|
err = -EFAULT;
|
|
goto abort;
|
|
}
|
|
|
|
if (req_task->req_cmd == IDE_DRIVE_TASK_NO_DATA)
|
|
nsect = 0;
|
|
else if (!nsect) {
|
|
nsect = (cmd.hob.nsect << 8) | cmd.tf.nsect;
|
|
|
|
if (!nsect) {
|
|
pr_err("%s: in/out command without data\n",
|
|
drive->name);
|
|
err = -EFAULT;
|
|
goto abort;
|
|
}
|
|
}
|
|
|
|
err = ide_raw_taskfile(drive, &cmd, data_buf, nsect);
|
|
|
|
memcpy(req_task->hob_ports, &cmd.hob, HDIO_DRIVE_HOB_HDR_SIZE - 2);
|
|
memcpy(req_task->io_ports, &cmd.tf, HDIO_DRIVE_TASK_HDR_SIZE);
|
|
|
|
if ((cmd.ftf_flags & IDE_FTFLAG_SET_IN_FLAGS) &&
|
|
req_task->in_flags.all == 0) {
|
|
req_task->in_flags.all = IDE_TASKFILE_STD_IN_FLAGS;
|
|
if (drive->dev_flags & IDE_DFLAG_LBA48)
|
|
req_task->in_flags.all |= (IDE_HOB_STD_IN_FLAGS << 8);
|
|
}
|
|
|
|
if (copy_to_user(buf, req_task, tasksize)) {
|
|
err = -EFAULT;
|
|
goto abort;
|
|
}
|
|
if (taskout) {
|
|
int outtotal = tasksize;
|
|
if (copy_to_user(buf + outtotal, outbuf, taskout)) {
|
|
err = -EFAULT;
|
|
goto abort;
|
|
}
|
|
}
|
|
if (taskin) {
|
|
int intotal = tasksize + taskout;
|
|
if (copy_to_user(buf + intotal, inbuf, taskin)) {
|
|
err = -EFAULT;
|
|
goto abort;
|
|
}
|
|
}
|
|
abort:
|
|
kfree(req_task);
|
|
kfree(outbuf);
|
|
kfree(inbuf);
|
|
|
|
return err;
|
|
}
|
|
#endif
|