Add unmapped I/O support to ata(4) driver.

Main problem there was PIO mode support, that required KVA mapping.
Handle that case using recently added pmap_quick_enter_page(9) KPI,
mapping data pages to KVA one at a time.
This commit is contained in:
mav 2015-08-07 14:38:26 +00:00
parent 01feda9a99
commit 5817e54da8
2 changed files with 165 additions and 68 deletions

View File

@ -1074,7 +1074,7 @@ ataaction(struct cam_sim *sim, union ccb *ccb)
cpi->version_num = 1; /* XXX??? */ cpi->version_num = 1; /* XXX??? */
cpi->hba_inquiry = PI_SDTR_ABLE; cpi->hba_inquiry = PI_SDTR_ABLE;
cpi->target_sprt = 0; cpi->target_sprt = 0;
cpi->hba_misc = PIM_SEQSCAN; cpi->hba_misc = PIM_SEQSCAN | PIM_UNMAPPED;
cpi->hba_eng_cnt = 0; cpi->hba_eng_cnt = 0;
if (ch->flags & ATA_NO_SLAVE) if (ch->flags & ATA_NO_SLAVE)
cpi->max_target = 0; cpi->max_target = 0;

View File

@ -32,6 +32,7 @@ __FBSDID("$FreeBSD$");
#include <sys/kernel.h> #include <sys/kernel.h>
#include <sys/endian.h> #include <sys/endian.h>
#include <sys/ata.h> #include <sys/ata.h>
#include <sys/bio.h>
#include <sys/conf.h> #include <sys/conf.h>
#include <sys/ctype.h> #include <sys/ctype.h>
#include <sys/bus.h> #include <sys/bus.h>
@ -44,6 +45,12 @@ __FBSDID("$FreeBSD$");
#include <dev/ata/ata-pci.h> #include <dev/ata/ata-pci.h>
#include <ata_if.h> #include <ata_if.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <cam/cam.h>
#include <cam/cam_ccb.h>
/* prototypes */ /* prototypes */
static int ata_generic_status(device_t dev); static int ata_generic_status(device_t dev);
static int ata_wait(struct ata_channel *ch, int unit, u_int8_t); static int ata_wait(struct ata_channel *ch, int unit, u_int8_t);
@ -811,86 +818,176 @@ ata_tf_write(struct ata_request *request)
static void static void
ata_pio_read(struct ata_request *request, int length) ata_pio_read(struct ata_request *request, int length)
{ {
struct ata_channel *ch = device_get_softc(request->parent); struct ata_channel *ch = device_get_softc(request->parent);
uint8_t *addr; struct bio *bio;
int size = min(request->transfersize, length); uint8_t *addr;
int resid; vm_offset_t page;
uint8_t buf[2] __aligned(sizeof(int16_t)); int todo, done, off, moff, resid, size, i;
#ifndef __NO_STRICT_ALIGNMENT uint8_t buf[2] __aligned(2);
int i;
#endif
addr = (uint8_t *)request->data + request->donecount; todo = min(request->transfersize, length);
if (__predict_false(ch->flags & ATA_USE_16BIT || page = done = resid = 0;
(size % sizeof(int32_t)) || ((uintptr_t)addr % sizeof(int32_t)))) { while (done < todo) {
size = todo - done;
/* Prepare data address and limit size (if not sequential). */
off = request->donecount + done;
if ((request->flags & ATA_R_DATA_IN_CCB) == 0 ||
(request->ccb->ccb_h.flags & CAM_DATA_MASK) == CAM_DATA_VADDR) {
addr = (uint8_t *)request->data + off;
} else if ((request->ccb->ccb_h.flags & CAM_DATA_MASK) == CAM_DATA_BIO) {
bio = (struct bio *)request->data;
if ((bio->bio_flags & BIO_UNMAPPED) == 0) {
addr = (uint8_t *)bio->bio_data + off;
} else {
moff = bio->bio_ma_offset + off;
page = pmap_quick_enter_page(
bio->bio_ma[moff / PAGE_SIZE]);
moff %= PAGE_SIZE;
size = min(size, PAGE_SIZE - moff);
addr = (void *)(page + moff);
}
} else
panic("ata_pio_read: Unsupported CAM data type %x\n",
(request->ccb->ccb_h.flags & CAM_DATA_MASK));
/* We may have extra byte already red but not stored. */
if (resid) {
addr[0] = buf[1];
addr++;
done++;
size--;
}
/* Process main part of data. */
resid = size % 2;
if (__predict_false((ch->flags & ATA_USE_16BIT) ||
(size % 4) != 0 || ((uintptr_t)addr % 4) != 0)) {
#ifndef __NO_STRICT_ALIGNMENT #ifndef __NO_STRICT_ALIGNMENT
if (__predict_false((uintptr_t)addr % sizeof(int16_t))) { if (__predict_false((uintptr_t)addr % 2)) {
for (i = 0, resid = size & ~1; resid > 0; resid -= for (i = 0; i + 1 < size; i += 2) {
sizeof(int16_t)) { *(uint16_t *)&buf =
*(uint16_t *)&buf = ATA_IDX_INW_STRM(ch, ATA_DATA); ATA_IDX_INW_STRM(ch, ATA_DATA);
addr[i++] = buf[0]; addr[i] = buf[0];
addr[i++] = buf[1]; addr[i + 1] = buf[1];
} }
} else } else
#endif #endif
ATA_IDX_INSW_STRM(ch, ATA_DATA, (void*)addr, size / ATA_IDX_INSW_STRM(ch, ATA_DATA, (void*)addr,
sizeof(int16_t)); size / 2);
if (size & 1) {
*(uint16_t *)&buf = ATA_IDX_INW_STRM(ch, ATA_DATA); /* If we have extra byte of data, leave it for later. */
(addr + (size & ~1))[0] = buf[0]; if (resid) {
*(uint16_t *)&buf =
ATA_IDX_INW_STRM(ch, ATA_DATA);
addr[size - 1] = buf[0];
}
} else
ATA_IDX_INSL_STRM(ch, ATA_DATA, (void*)addr, size / 4);
if (page) {
pmap_quick_remove_page(page);
page = 0;
}
done += size;
} }
} else
ATA_IDX_INSL_STRM(ch, ATA_DATA, (void*)addr, size / sizeof(int32_t));
if (request->transfersize < length) { if (length > done) {
device_printf(request->parent, "WARNING - %s read data overrun %d>%d\n", device_printf(request->parent,
ata_cmd2str(request), length, request->transfersize); "WARNING - %s read data overrun %d > %d\n",
for (resid = request->transfersize + (size & 1); resid < length; ata_cmd2str(request), length, done);
resid += sizeof(int16_t)) for (i = done + resid; i < length; i += 2)
ATA_IDX_INW(ch, ATA_DATA); ATA_IDX_INW(ch, ATA_DATA);
} }
} }
static void static void
ata_pio_write(struct ata_request *request, int length) ata_pio_write(struct ata_request *request, int length)
{ {
struct ata_channel *ch = device_get_softc(request->parent); struct ata_channel *ch = device_get_softc(request->parent);
uint8_t *addr; struct bio *bio;
int size = min(request->transfersize, length); uint8_t *addr;
int resid; vm_offset_t page;
uint8_t buf[2] __aligned(sizeof(int16_t)); int todo, done, off, moff, resid, size, i;
#ifndef __NO_STRICT_ALIGNMENT uint8_t buf[2] __aligned(2);
int i;
#endif
size = min(request->transfersize, length); todo = min(request->transfersize, length);
addr = (uint8_t *)request->data + request->donecount; page = done = resid = 0;
if (__predict_false(ch->flags & ATA_USE_16BIT || while (done < todo) {
(size % sizeof(int32_t)) || ((uintptr_t)addr % sizeof(int32_t)))) { size = todo - done;
/* Prepare data address and limit size (if not sequential). */
off = request->donecount + done;
if ((request->flags & ATA_R_DATA_IN_CCB) == 0 ||
(request->ccb->ccb_h.flags & CAM_DATA_MASK) == CAM_DATA_VADDR) {
addr = (uint8_t *)request->data + off;
} else if ((request->ccb->ccb_h.flags & CAM_DATA_MASK) == CAM_DATA_BIO) {
bio = (struct bio *)request->data;
if ((bio->bio_flags & BIO_UNMAPPED) == 0) {
addr = (uint8_t *)bio->bio_data + off;
} else {
moff = bio->bio_ma_offset + off;
page = pmap_quick_enter_page(
bio->bio_ma[moff / PAGE_SIZE]);
moff %= PAGE_SIZE;
size = min(size, PAGE_SIZE - moff);
addr = (void *)(page + moff);
}
} else
panic("ata_pio_write: Unsupported CAM data type %x\n",
(request->ccb->ccb_h.flags & CAM_DATA_MASK));
/* We may have extra byte to be written first. */
if (resid) {
buf[1] = addr[0];
ATA_IDX_OUTW_STRM(ch, ATA_DATA, *(uint16_t *)&buf);
addr++;
done++;
size--;
}
/* Process main part of data. */
resid = size % 2;
if (__predict_false((ch->flags & ATA_USE_16BIT) ||
(size % 4) != 0 || ((uintptr_t)addr % 4) != 0)) {
#ifndef __NO_STRICT_ALIGNMENT #ifndef __NO_STRICT_ALIGNMENT
if (__predict_false((uintptr_t)addr % sizeof(int16_t))) { if (__predict_false((uintptr_t)addr % 2)) {
for (i = 0, resid = size & ~1; resid > 0; resid -= for (i = 0; i + 1 < size; i += 2) {
sizeof(int16_t)) { buf[0] = addr[i];
buf[0] = addr[i++]; buf[1] = addr[i + 1];
buf[1] = addr[i++]; ATA_IDX_OUTW_STRM(ch, ATA_DATA,
ATA_IDX_OUTW_STRM(ch, ATA_DATA, *(uint16_t *)&buf); *(uint16_t *)&buf);
} }
} else } else
#endif #endif
ATA_IDX_OUTSW_STRM(ch, ATA_DATA, (void*)addr, size / ATA_IDX_OUTSW_STRM(ch, ATA_DATA, (void*)addr,
sizeof(int16_t)); size / 2);
if (size & 1) {
buf[0] = (addr + (size & ~1))[0]; /* If we have extra byte of data, save it for later. */
ATA_IDX_OUTW_STRM(ch, ATA_DATA, *(uint16_t *)&buf); if (resid)
buf[0] = addr[size - 1];
} else
ATA_IDX_OUTSL_STRM(ch, ATA_DATA,
(void*)addr, size / sizeof(int32_t));
if (page) {
pmap_quick_remove_page(page);
page = 0;
}
done += size;
} }
} else
ATA_IDX_OUTSL_STRM(ch, ATA_DATA, (void*)addr, size / sizeof(int32_t));
if (request->transfersize < length) { /* We may have extra byte of data to be written. Pad it with zero. */
device_printf(request->parent, "WARNING - %s write data underrun %d>%d\n", if (resid) {
ata_cmd2str(request), length, request->transfersize); buf[1] = 0;
for (resid = request->transfersize + (size & 1); resid < length; ATA_IDX_OUTW_STRM(ch, ATA_DATA, *(uint16_t *)&buf);
resid += sizeof(int16_t)) }
ATA_IDX_OUTW(ch, ATA_DATA, 0);
} if (length > done) {
device_printf(request->parent,
"WARNING - %s write data underrun %d > %d\n",
ata_cmd2str(request), length, done);
for (i = done + resid; i < length; i += 2)
ATA_IDX_OUTW(ch, ATA_DATA, 0);
}
} }