at_wini/ahci: write cache ioctls

This commit is contained in:
David van Moolenbroek 2010-08-12 14:09:34 +00:00
parent 5998a4b2af
commit 484b2f43d6
5 changed files with 188 additions and 9 deletions

View file

@ -178,6 +178,7 @@ PRIVATE long ahci_sig_timeout = SIG_TIMEOUT;
PRIVATE long ahci_sig_checks = NR_SIG_CHECKS;
PRIVATE long ahci_command_timeout = COMMAND_TIMEOUT;
PRIVATE long ahci_transfer_timeout = TRANSFER_TIMEOUT;
PRIVATE long ahci_flush_timeout = FLUSH_TIMEOUT;
PRIVATE int ahci_map[MAX_DRIVES]; /* device-to-port mapping */
@ -437,6 +438,19 @@ PRIVATE int atapi_id_check(struct port_state *ps, u16_t *buf)
ATA_ID_GCAP_TYPE_SHIFT) == ATAPI_TYPE_CDROM)
ps->flags |= FLAG_READONLY;
if ((buf[ATA_ID_SUP1] & ATA_ID_SUP1_VALID_MASK) == ATA_ID_SUP1_VALID &&
!(ps->flags & FLAG_READONLY)) {
/* Save write cache related capabilities of the device. It is
* possible, although unlikely, that a device has support for
* either of these but not both.
*/
if (buf[ATA_ID_SUP0] & ATA_ID_SUP0_WCACHE)
ps->flags |= FLAG_HAS_WCACHE;
if (buf[ATA_ID_SUP1] & ATA_ID_SUP1_FLUSH)
ps->flags |= FLAG_HAS_FLUSH;
}
return TRUE;
}
@ -486,15 +500,16 @@ PRIVATE int ata_id_check(struct port_state *ps, u16_t *buf)
*/
/* This must be an ATA device; it must not have removable media;
* it must support LBA and DMA; it must support 48-bit addressing.
* it must support LBA and DMA; it must support the FLUSH CACHE
* command; it must support 48-bit addressing.
*/
if ((buf[ATA_ID_GCAP] & (ATA_ID_GCAP_ATA_MASK | ATA_ID_GCAP_REMOVABLE |
ATA_ID_GCAP_INCOMPLETE)) != ATA_ID_GCAP_ATA ||
(buf[ATA_ID_CAP] & (ATA_ID_CAP_LBA | ATA_ID_CAP_DMA)) !=
(ATA_ID_CAP_LBA | ATA_ID_CAP_DMA) ||
(buf[ATA_ID_SUP1] &
(ATA_ID_SUP1_VALID_MASK | ATA_ID_SUP1_LBA48)) !=
(ATA_ID_SUP1_VALID | ATA_ID_SUP1_LBA48)) {
(buf[ATA_ID_SUP1] & (ATA_ID_SUP1_VALID_MASK |
ATA_ID_SUP1_FLUSH | ATA_ID_SUP1_LBA48)) !=
(ATA_ID_SUP1_VALID | ATA_ID_SUP1_FLUSH | ATA_ID_SUP1_LBA48)) {
dprintf(V_ERR, ("%s: unsupported ATA device\n",
ahci_portname(ps)));
@ -527,7 +542,11 @@ PRIVATE int ata_id_check(struct port_state *ps, u16_t *buf)
return FALSE;
}
ps->flags |= FLAG_HAS_MEDIUM;
ps->flags |= FLAG_HAS_MEDIUM | FLAG_HAS_FLUSH;
/* FLUSH CACHE is mandatory for ATA devices; write caches are not. */
if (buf[ATA_ID_SUP0] & ATA_ID_SUP0_WCACHE)
ps->flags |= FLAG_HAS_WCACHE;
return TRUE;
}
@ -566,9 +585,10 @@ PRIVATE int ata_transfer(struct port_state *ps, int cmd, u64_t start_lba,
/*===========================================================================*
* gen_identify *
*===========================================================================*/
PRIVATE void gen_identify(struct port_state *ps, int cmd)
PRIVATE int gen_identify(struct port_state *ps, int cmd, int blocking)
{
/* Identify an ATA or ATAPI device.
/* Identify an ATA or ATAPI device. If the blocking flag is set, block
* until the command has completed; otherwise return immediately.
*/
cmd_fis_t fis;
prd_t prd;
@ -584,10 +604,102 @@ PRIVATE void gen_identify(struct port_state *ps, int cmd)
prd.prd_phys = ps->tmp_phys;
prd.prd_size = ATA_ID_SIZE;
/* Start the command, but do not wait for the result. */
/* Start the command, and possibly wait for the result. */
port_set_cmd(ps, cmd, &fis, NULL /*packet*/, &prd, 1, FALSE /*write*/);
if (blocking)
return port_exec(ps, cmd, ahci_command_timeout);
port_issue(ps, cmd, ahci_command_timeout);
return OK;
}
/*===========================================================================*
* gen_flush_wcache *
*===========================================================================*/
PRIVATE int gen_flush_wcache(struct port_state *ps, int cmd)
{
/* Flush the device's write cache.
*/
cmd_fis_t fis;
/* The FLUSH CACHE command may not be supported by all (writable ATAPI)
* devices.
*/
if (!(ps->flags & FLAG_HAS_FLUSH))
return EINVAL;
/* Use the FLUSH CACHE command for both ATA and ATAPI. We are not
* interested in the disk location of a failure, so there is no reason
* to use the ATA-only FLUSH CACHE EXT command. Either way, the command
* may indeed fail due to a disk error, in which case it should be
* repeated. For now, we shift this responsibility onto the caller.
*/
memset(&fis, 0, sizeof(fis));
fis.cf_cmd = ATA_CMD_FLUSH_CACHE;
/* Start the command, and wait for it to complete or fail.
* The flush command may take longer than regular I/O commands.
*/
port_set_cmd(ps, cmd, &fis, NULL /*packet*/, NULL /*prdt*/, 0,
FALSE /*write*/);
return port_exec(ps, cmd, ahci_flush_timeout);
}
/*===========================================================================*
* gen_get_wcache *
*===========================================================================*/
PRIVATE int gen_get_wcache(struct port_state *ps, int cmd, int *val)
{
/* Retrieve the status of the device's write cache.
*/
int r;
/* Write caches are not mandatory. */
if (!(ps->flags & FLAG_HAS_WCACHE))
return EINVAL;
/* Retrieve information about the device. */
if ((r = gen_identify(ps, cmd, TRUE /*blocking*/)) != OK)
return r;
/* Return the current setting. */
*val = !!(((u16_t *) ps->tmp_base)[ATA_ID_ENA0] & ATA_ID_ENA0_WCACHE);
return OK;
}
/*===========================================================================*
* gen_set_wcache *
*===========================================================================*/
PRIVATE int gen_set_wcache(struct port_state *ps, int cmd, int enable)
{
/* Enable or disable the device's write cache.
*/
cmd_fis_t fis;
clock_t timeout;
/* Write caches are not mandatory. */
if (!(ps->flags & FLAG_HAS_WCACHE))
return EINVAL;
/* Disabling the write cache causes a (blocking) cache flush. Cache
* flushes may take much longer than regular commands.
*/
timeout = enable ? ahci_command_timeout : ahci_flush_timeout;
/* Set up a command. */
memset(&fis, 0, sizeof(fis));
fis.cf_cmd = ATA_CMD_SET_FEATURES;
fis.cf_feat = enable ? ATA_SF_EN_WCACHE : ATA_SF_DI_WCACHE;
/* Start the command, and wait for it to complete or fail. */
port_set_cmd(ps, cmd, &fis, NULL /*packet*/, NULL /*prdt*/, 0,
FALSE /*write*/);
return port_exec(ps, cmd, timeout);
}
/*===========================================================================*
@ -1121,7 +1233,7 @@ PRIVATE void port_sig_check(struct port_state *ps)
ps->state = STATE_WAIT_ID;
ps->reg[AHCI_PORT_IE] = AHCI_PORT_IE_MASK;
gen_identify(ps, 0);
(void) gen_identify(ps, 0, FALSE /*blocking*/);
}
/*===========================================================================*
@ -1799,6 +1911,9 @@ PRIVATE void ahci_stop(void)
for (port = 0; port < hba_state.nr_ports; port++) {
if (port_state[port].state != STATE_NO_PORT) {
if (port_state[port].state == STATE_GOOD_DEV)
(void) gen_flush_wcache(&port_state[port], 0);
port_stop(&port_state[port]);
port_free(&port_state[port]);
@ -1896,6 +2011,7 @@ PRIVATE void ahci_get_params(void)
ahci_get_var("ahci_sig_checks", &ahci_sig_checks, FALSE);
ahci_get_var("ahci_cmd_timeout", &ahci_command_timeout, TRUE);
ahci_get_var("ahci_io_timeout", &ahci_transfer_timeout, TRUE);
ahci_get_var("ahci_flush_timeout", &ahci_flush_timeout, TRUE);
}
/*===========================================================================*
@ -2269,6 +2385,7 @@ PRIVATE int ahci_other(struct driver *UNUSED(dp), message *m)
/* Process any messages not covered by the other calls.
* This function only implements IOCTLs.
*/
int r, val;
if (m->m_type != DEV_IOCTL_S)
return EINVAL;
@ -2290,6 +2407,33 @@ PRIVATE int ahci_other(struct driver *UNUSED(dp), message *m)
return sys_safecopyto(m->IO_ENDPT, (cp_grant_id_t) m->IO_GRANT,
0, (vir_bytes) &current_port->open_count,
sizeof(current_port->open_count), D);
case DIOCFLUSH:
if (current_port->state != STATE_GOOD_DEV)
return EIO;
return gen_flush_wcache(current_port, 0);
case DIOCSETWC:
if (current_port->state != STATE_GOOD_DEV)
return EIO;
if ((r = sys_safecopyfrom(m->IO_ENDPT,
(cp_grant_id_t) m->IO_GRANT, 0, (vir_bytes) &val,
sizeof(val), D)) != OK)
return r;
return gen_set_wcache(current_port, 0, val);
case DIOCGETWC:
if (current_port->state != STATE_GOOD_DEV)
return EIO;
if ((r = gen_get_wcache(current_port, 0, &val)) != OK)
return r;
return sys_safecopyto(m->IO_ENDPT, (cp_grant_id_t) m->IO_GRANT,
0, (vir_bytes) &val, sizeof(val), D);
}
return EINVAL;

View file

@ -12,6 +12,7 @@
#define NR_SIG_CHECKS 60 /* maximum number of times to check */
#define COMMAND_TIMEOUT 5000 /* time to wait for non-I/O cmd (ms) */
#define TRANSFER_TIMEOUT 30000 /* time to wait for I/O cmd (ms) */
#define FLUSH_TIMEOUT 60000 /* time to wait for flush cmd (ms) */
/* Time values that are defined by the standards. */
#define SPINUP_DELAY 1 /* time to assert spin-up flag (ms) */
@ -31,7 +32,9 @@
#define ATA_CMD_WRITE_DMA_EXT 0x35 /* WRITE DMA EXT */
#define ATA_CMD_PACKET 0xA0 /* PACKET */
#define ATA_CMD_IDENTIFY_PACKET 0xA1 /* IDENTIFY PACKET DEVICE */
#define ATA_CMD_FLUSH_CACHE 0xE7 /* FLUSH CACHE */
#define ATA_CMD_IDENTIFY 0xEC /* IDENTIFY DEVICE */
#define ATA_CMD_SET_FEATURES 0xEF /* SET FEATURES */
#define ATA_H2D_FEAT 3 /* Features */
#define ATA_FEAT_PACKET_DMA 0x01 /* use DMA */
#define ATA_FEAT_PACKET_DMADIR 0x03 /* DMA is inbound */
@ -69,10 +72,18 @@
#define ATA_ID_DMADIR 62 /* DMADIR */
#define ATA_ID_DMADIR_DMADIR 0x8000 /* DMADIR required */
#define ATA_ID_DMADIR_DMA 0x0400 /* DMA supported (DMADIR) */
#define ATA_ID_SUP0 82 /* Features supported (1/3) */
#define ATA_ID_SUP0_WCACHE 0x0020 /* Write cache supported */
#define ATA_ID_SUP1 83 /* Features supported (2/3) */
#define ATA_ID_SUP1_VALID_MASK 0xC000 /* Word validity mask */
#define ATA_ID_SUP1_VALID 0x4000 /* Word contents are valid */
#define ATA_ID_SUP1_FLUSH 0x1000 /* FLUSH CACHE supported */
#define ATA_ID_SUP1_LBA48 0x0400 /* 48-bit LBA supported */
#define ATA_ID_ENA0 85 /* Features enabled (1/3) */
#define ATA_ID_ENA0_WCACHE 0x0020 /* Write cache enabled */
#define ATA_ID_ENA2 87 /* Features enabled (3/3) */
#define ATA_ID_ENA2_VALID_MASK 0xC000 /* Word validity mask */
#define ATA_ID_ENA2_VALID 0x4000 /* Word contents are valid */
#define ATA_ID_LBA0 100 /* Max. LBA48 address (LSW) */
#define ATA_ID_LBA1 101 /* Max. LBA48 address */
#define ATA_ID_LBA2 102 /* Max. LBA48 address */
@ -84,6 +95,9 @@
#define ATA_ID_LSS0 118 /* Logical sector size (LSW) */
#define ATA_ID_LSS1 119 /* Logical sector size (MSW) */
#define ATA_SF_EN_WCACHE 0x02 /* Enable write cache */
#define ATA_SF_DI_WCACHE 0x82 /* Disable write cache */
/* ATAPI constants. */
#define ATAPI_PACKET_SIZE 16 /* ATAPI packet size */
@ -254,6 +268,8 @@ enum {
#define FLAG_BUSY 0x00000010 /* is an operation ongoing? */
#define FLAG_FAILURE 0x00000020 /* did the operation fail? */
#define FLAG_BARRIER 0x00000040 /* no access until unset */
#define FLAG_HAS_WCACHE 0x00000080 /* is a write cache present? */
#define FLAG_HAS_FLUSH 0x00000100 /* is FLUSH CACHE supported? */
/* Mapping between devices and ports. */
#define NO_PORT -1 /* this device maps to no port */

View file

@ -2297,6 +2297,7 @@ struct driver *dr;
message *m;
{
int r, timeout, prev;
struct command cmd;
if (m->m_type != DEV_IOCTL_S )
return EINVAL;
@ -2353,6 +2354,20 @@ message *m;
return r;
return OK;
} else if (m->REQUEST == DIOCFLUSH) {
if (w_prepare(m->DEVICE) == NULL) return ENXIO;
if (w_wn->state & ATAPI) return EINVAL;
if (!(w_wn->state & INITIALIZED) && w_specify() != OK)
return EIO;
cmd.command = CMD_FLUSH_CACHE;
if (com_simple(&cmd) != OK || !w_waitfor(STATUS_BSY, 0))
return EIO;
return (w_wn->w_status & (STATUS_ERR|STATUS_WF)) ? EIO : OK;
}
return EINVAL;
}

View file

@ -68,6 +68,7 @@
#define CMD_SPECIFY 0x91 /* specify parameters */
#define CMD_READ_DMA 0xC8 /* read data using DMA */
#define CMD_WRITE_DMA 0xCA /* write data using DMA */
#define CMD_FLUSH_CACHE 0xE7 /* flush the write cache */
#define ATA_IDENTIFY 0xEC /* identify drive */
/* #define REG_CTL 0x206 */ /* control register */
#define REG_CTL 0 /* control register */

View file

@ -13,5 +13,8 @@
#define DIOCEJECT _IO ('d', 5)
#define DIOCTIMEOUT _IORW('d', 6, int)
#define DIOCOPENCT _IOR('d', 7, int)
#define DIOCFLUSH _IO ('d', 8)
#define DIOCSETWC _IOW('d', 9, int)
#define DIOCGETWC _IOR('d', 10, int)
#endif /* _S_I_DISK_H */