@@ -111,7 +111,7 @@ obj-$(CONFIG_ATA_GENERIC) += ata_generic.o
# Should be last libata driver
obj-$(CONFIG_PATA_LEGACY) += pata_legacy.o
-libata-y := libata-core.o libata-scsi.o libata-eh.o libata-transport.o
+libata-y := libata-core.o libata-scsi.o libata-eh.o libata-transport.o libata-hybrid.o
libata-$(CONFIG_ATA_SFF) += libata-sff.o
libata-$(CONFIG_SATA_PMP) += libata-pmp.o
libata-$(CONFIG_ATA_ACPI) += libata-acpi.o
@@ -72,6 +72,7 @@
#include "libata.h"
#include "libata-transport.h"
+#include "libata-hybrid.h"
/* debounce timing parameters in msecs { interval, duration, timeout } */
const unsigned long sata_deb_timing_normal[] = { 5, 100, 2000 };
@@ -747,7 +748,7 @@ u64 ata_tf_read_block(struct ata_taskfile *tf, struct ata_device *dev)
*/
int ata_build_rw_tf(struct ata_taskfile *tf, struct ata_device *dev,
u64 block, u32 n_block, unsigned int tf_flags,
- unsigned int tag)
+ unsigned int tag, unsigned int ata_hybrid_hint)
{
tf->flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE;
tf->flags |= tf_flags;
@@ -775,6 +776,7 @@ int ata_build_rw_tf(struct ata_taskfile *tf, struct ata_device *dev,
tf->lbah = (block >> 16) & 0xff;
tf->lbam = (block >> 8) & 0xff;
tf->lbal = block & 0xff;
+ tf->auxiliary |= (ata_hybrid_hint << 16);
tf->device = ATA_LBA;
if (tf->flags & ATA_TFLAG_FUA)
@@ -2389,6 +2391,12 @@ int ata_dev_configure(struct ata_device *dev)
}
}
+ if (ata_id_has_hybrid_cap(dev->id)) {
+ dev->hybrid_cap = true;
+ initialize_hybrid_drive(dev);
+ } else
+ dev->hybrid_cap = false;
+
dev->cdb_len = 16;
}
@@ -4494,7 +4502,8 @@ unsigned int ata_dev_set_feature(struct ata_device *dev, u8 enable, u8 feature)
unsigned int err_mask;
/* set up set-features taskfile */
- DPRINTK("set features - SATA features\n");
+ DPRINTK("set features port:%d enable:0x%X feature:0x%X\n",
+ dev->link->ap->print_id, enable, feature);
ata_tf_init(dev, &tf);
tf.command = ATA_CMD_SET_FEATURES;
@@ -47,6 +47,7 @@
#include <linux/libata.h>
#include "libata.h"
+#include "libata-hybrid.h"
enum {
/* speed down verdicts */
@@ -3096,6 +3097,16 @@ static int ata_eh_revalidate_and_attach(struct ata_link *link,
if (ehc->i.flags & ATA_EHI_DID_RESET)
readid_flags |= ATA_READID_POSTRESET;
+ /* enable host hints or self-pinning depending on ehi flag */
+ if (ehc->i.flags & ATA_EHI_SET_HYBRID &&
+ dev->hybrid_cap && !dev->hybrid_en)
+ set_hybrid_enabled(dev, 1, 0);
+ else if (ehc->i.flags & ATA_EHI_SET_HYBRID &&
+ dev->hybrid_cap && dev->hybrid_en)
+ set_hybrid_enabled(dev, 0, 0);
+ pr_info("ehc iflags = 0x%X, hybrid_cap = %d, hybrid_en = %d\n",
+ ehc->i.flags, dev->hybrid_cap, dev->hybrid_en);
+
if ((action & ATA_EH_REVALIDATE) && ata_dev_enabled(dev)) {
WARN_ON(dev->class == ATA_DEV_PMP);
new file mode 100644
@@ -0,0 +1,261 @@
+/*
+ * Copyright(c) 2013-2014 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+/*
+ * Libata Hybrid Information Feature from SATA standard 3.2.
+ *
+ * This file contains the methods and associated data that augment libata to
+ * support hybrid information feature as defined by the SATA standard revision
+ * 3.2. This feature enables the libata to hint the Solid State Hybrid Drives
+ * (SSHDs) that have a small embedded NAND and a large magnetic media. The hint
+ * conveys to the SSHD what data to place on the NAND portion and what to place
+ * on the spindle.An accurate hinting will realize the potential of SSHDs by
+ * giving SSD like performance and a hard disk like capacity at a $5 adder to
+ * the hard drive cost.
+ *
+ * At initialization, initialize_hybrid_drive is invoked that based on
+ * the availability of hybrid feature, loads a translation table for the
+ * solid state hybrid drive (SSHD)--either the default or via the
+ * update_firmware mechanism. This is done once per ata_device because this
+ * translation could be different based on the performance/power
+ * characteristics of the SSHD.
+ *
+ * Hybrid information feature can be enabled or disabled via sysfs. The methods
+ * in this module eventually perform the enable/disable at the ata_device level
+ *
+ * The only method that invoked in the IO path is get_ata_hybrid_hint. This
+ * method references a look-up table to plug in the hybrid hint
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/mm.h>
+#include <linux/spinlock.h>
+#include <linux/blkdev.h>
+#include <linux/delay.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/suspend.h>
+#include <linux/workqueue.h>
+#include <linux/scatterlist.h>
+#include <linux/io.h>
+#include <linux/ioprio.h>
+#include <linux/async.h>
+#include <linux/log2.h>
+#include <linux/slab.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_device.h>
+#include <linux/libata.h>
+#include <asm/byteorder.h>
+#include <linux/cdrom.h>
+#include <linux/ratelimit.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+#include <linux/firmware.h>
+
+#include "libata.h"
+#include "libata-transport.h"
+#include "libata-hybrid.h"
+
+static unsigned int max_priority;
+
+struct hybrid_info_translation {
+ unsigned char signature[5];
+ unsigned char dont_disturb;
+ unsigned char normal;
+ unsigned char will_need;
+ unsigned char dont_need;
+ unsigned char evict;
+};
+
+static struct hybrid_info_translation translation = {
+ {"sshd"}, 0x00, 0x21, 0x21, 0x20, 0x20};
+
+unsigned int get_ata_hybrid_hint (struct ata_queued_cmd *qc)
+{
+ unsigned int ioprio = qc->scsicmd->request->ioprio;
+ unsigned int ata_hybrid_hint = translation.normal;
+
+ if (ioprio_advice_valid(ioprio)) {
+ switch (IOPRIO_ADVICE(ioprio)) {
+ default:
+ break;
+ case IOPRIO_ADV_EVICT:
+ ata_hybrid_hint = translation.evict;
+ break;
+ case IOPRIO_ADV_DONTNEED:
+ ata_hybrid_hint = translation.dont_need;
+ break;
+ case IOPRIO_ADV_NORMAL:
+ ata_hybrid_hint = translation.normal;
+ break;
+ case IOPRIO_ADV_WILLNEED:
+ ata_hybrid_hint = translation.will_need;
+ break;
+ }
+ }
+
+ return ata_hybrid_hint;
+}
+
+unsigned int update_hybrid_info_translation_table(struct ata_device *dev)
+{
+ int i = 0;
+ const struct firmware *fw = NULL;
+ struct hybrid_info_translation *new_translation;
+ unsigned char *data;
+
+ i = request_firmware(&fw, HYBRID_INFO_TABLE_NAME, &dev->tdev);
+ if (i < 0) {
+ pr_debug("%s: request_firmware failed %x\n", __func__, i);
+ release_firmware(fw);
+ return 1;
+ }
+
+ new_translation = (struct hybrid_info_translation *)fw->data;
+
+ if (strncmp(new_translation->signature, "sshd", 4) != 0) {
+ pr_warn("%s: bad firmware signature %x\n", __func__, i);
+ release_firmware(fw);
+ return 1;
+ }
+
+ pr_info("old table={%x, %x, %x, %x, %x}\n",
+ translation.dont_disturb,
+ translation.normal,
+ translation.will_need,
+ translation.dont_need,
+ translation.evict);
+
+ data = (unsigned char *)&new_translation->dont_disturb;
+
+ for (i = 0; i < 5; i++)
+ data[i] = (data[i] & (0x20|max_priority));
+
+ pr_info("new table={%x, %x, %x, %x, %x}\n",
+ new_translation->dont_disturb,
+ new_translation->normal,
+ new_translation->will_need,
+ new_translation->dont_need,
+ new_translation->evict);
+
+ translation = *new_translation;
+ release_firmware(fw);
+
+ return 0;
+}
+
+unsigned int initialize_hybrid_drive(struct ata_device *dev)
+{
+ struct ata_port *ap = dev->link->ap;
+ u8 *inBuff = ap->sector_buf;
+ int k;
+ unsigned int err_mask;
+ int nvmSize;
+
+ err_mask = ata_read_log_page(dev,
+ 0x14,
+ 0,
+ inBuff,
+ 1);
+ if (err_mask)
+ ata_dev_dbg(dev,
+ "failed to get Hybrid Info Log, Emask 0x%x\n",
+ err_mask);
+ else {
+ pr_info("SATA hybrid information log:\n");
+ k = 0;
+ pr_info("Number of Hybrid Descriptors=%d\n",
+ inBuff[k]&0xF);
+ k = 2;
+
+ if (inBuff[k])
+ dev->hybrid_en = true;
+ else
+ dev->hybrid_en = false;
+
+ pr_info("Enabled=%x\n", inBuff[k++]);
+ pr_info("Hybrid Health =%x\n", inBuff[k++]);
+ pr_info("Dirty Low Threshold =%x\n", inBuff[k++]);
+ pr_info("Dirty High Threshold =%x\n", inBuff[k++]);
+ pr_info("Optimal Write Granularity=%x\n", inBuff[k++]);
+
+ max_priority = inBuff[k]&0xF;
+
+ pr_info("Maximum Priority Level=%d\n", inBuff[k++]&0xF);
+ k = 16;
+ nvmSize = (inBuff[k] | (inBuff[k+1]<<8) |
+ (inBuff[k+2]<<16) | (inBuff[k+3]<<24));
+ pr_info("NVM Size=%x(%d GigaBytes)\n",
+ nvmSize, nvmSize/(2*1024*1024));
+
+ for (k = 64; k < 512; k += 16) {
+ if (inBuff[k]) {
+ pr_info("Hybrid Priority=%d\n",
+ inBuff[k]);
+ pr_info("Consumed NVM Size Fraction=%x\n",
+ inBuff[k+1]);
+ pr_info("Consumed Map Res Fraction=%x\n",
+ inBuff[k+2]);
+ pr_info("Consumed Siz Drty Fraction=%x\n",
+ inBuff[k+3]);
+ pr_info("Consumed Map Res Drty Frtn=%x\n",
+ inBuff[k+4]);
+ }
+ }
+ }
+
+
+ /* Keep the feature disabled but update the translation table from the
+ * user space
+ */
+ return update_hybrid_info_translation_table(dev);
+}
+
+unsigned int hybrid_is_enabled(struct ata_device *dev)
+{
+ return dev->hybrid_en;
+}
+
+unsigned int set_hybrid_enabled(struct ata_device *dev, unsigned int value,
+ unsigned int count)
+{
+ if (value == 1) {
+ /*enable the hybrid information feature*/
+ if (!ata_dev_set_feature(dev, 0x10, 0xA))
+ dev->hybrid_en = true;
+ else
+ pr_warn("%s: Failed to set hybrid feature\n",
+ __func__);
+ dev->link->eh_info.flags &= ~ATA_EHI_SET_HYBRID;
+ return 0;
+ } else if (value == 0) {
+ /*disable the hybrid information feature*/
+ if (!ata_dev_set_feature(dev, 0x90, 0xA))
+ dev->hybrid_en = false;
+ else
+ pr_warn("%s: Failed to set hybrid feature\n",
+ __func__);
+
+ dev->link->eh_info.flags &= ~ATA_EHI_SET_HYBRID;
+ return 0;
+ }
+ return -EINVAL;
+}
new file mode 100644
@@ -0,0 +1,14 @@
+#ifndef _LIBATA_HYBRID_H
+#define _LIBATA_HYBRID_H
+
+#define HYBRID_INFO_TABLE_NAME "hybrid_information_table.bin"
+
+unsigned int get_ata_hybrid_hint(struct ata_queued_cmd *qc);
+unsigned int update_hybrid_info_translation_table(struct ata_device *dev);
+unsigned int initialize_hybrid_drive(struct ata_device *dev);
+unsigned int hybrid_is_enabled(struct ata_device *dev);
+unsigned int set_hybrid_enabled(struct ata_device *dev,
+ unsigned int value, unsigned int count);
+#define ata_id_has_hybrid_cap(id) ((id)[ATA_ID_FEATURE_SUPP] & (1 << 9))
+#define ata_id_has_hybrid_en(id) ((id)[ATA_ID_FEATURE_EN] & (1 << 9))
+#endif
@@ -53,6 +53,7 @@
#include "libata.h"
#include "libata-transport.h"
+#include "libata-hybrid.h"
#define ATA_SCSI_RBUF_SIZE 4096
@@ -1170,6 +1171,7 @@ static int ata_scsi_dev_config(struct scsi_device *sdev,
blk_queue_flush_queueable(q, false);
dev->sdev = sdev;
+
return 0;
}
@@ -1721,7 +1723,7 @@ static unsigned int ata_scsi_rw_xlat(struct ata_queued_cmd *qc)
qc->nbytes = n_block * scmd->device->sector_size;
rc = ata_build_rw_tf(&qc->tf, qc->dev, block, n_block, tf_flags,
- qc->tag);
+ qc->tag, get_ata_hybrid_hint(qc));
if (likely(rc == 0))
return 0;
@@ -36,10 +36,11 @@
#include "libata.h"
#include "libata-transport.h"
+#include "libata-hybrid.h"
#define ATA_PORT_ATTRS 3
#define ATA_LINK_ATTRS 3
-#define ATA_DEV_ATTRS 9
+#define ATA_DEV_ATTRS 10
struct scsi_transport_template;
struct scsi_transport_template *ata_scsi_transport_template;
@@ -559,6 +560,47 @@ show_ata_dev_gscr(struct device *dev,
static DEVICE_ATTR(gscr, S_IRUGO, show_ata_dev_gscr, NULL);
+static ssize_t
+show_ata_dev_hybrid(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ata_device *ata_dev = transport_class_to_dev(dev);
+
+ scnprintf(buf, PAGE_SIZE, "%d\n", hybrid_is_enabled(ata_dev));
+ return sizeof(unsigned int);
+}
+
+static ssize_t
+store_ata_dev_hybrid(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct ata_device *ata_dev = transport_class_to_dev(dev);
+ struct ata_port *ata_port = ata_dev->link->ap;
+ unsigned long flags;
+ unsigned int value;
+
+ if (kstrtouint(buf, 0, &value) < 0)
+ return -EINVAL;
+
+ spin_lock_irqsave(ata_port->lock, flags);
+ if (value == 0 || value == 1)
+ ata_dev->link->eh_info.flags |= ATA_EHI_SET_HYBRID;
+ else {
+ pr_warn("invalid hybrid value: use 1 or 0\n");
+ return -EINVAL;
+ }
+
+ pr_info("set dev->flags hybrid: 0x%X\n",
+ ata_dev->link->eh_context.i.flags);
+ ata_port_schedule_eh(ata_port);
+
+ spin_unlock_irqrestore(ata_port->lock, flags);
+ return count;
+}
+
+static DEVICE_ATTR(hybrid, S_IRUGO | S_IWUSR, show_ata_dev_hybrid,
+ store_ata_dev_hybrid);
+
static DECLARE_TRANSPORT_CLASS(ata_dev_class,
"ata_device", NULL, NULL, NULL);
@@ -732,6 +774,7 @@ struct scsi_transport_template *ata_attach_transport(void)
SETUP_DEV_ATTRIBUTE(ering);
SETUP_DEV_ATTRIBUTE(id);
SETUP_DEV_ATTRIBUTE(gscr);
+ SETUP_TEMPLATE(dev_attrs, hybrid, S_IRUGO | S_IWUGO, 1);
BUG_ON(count > ATA_DEV_ATTRS);
i->dev_attrs[count] = NULL;
@@ -66,7 +66,7 @@ extern u64 ata_tf_to_lba48(const struct ata_taskfile *tf);
extern struct ata_queued_cmd *ata_qc_new_init(struct ata_device *dev);
extern int ata_build_rw_tf(struct ata_taskfile *tf, struct ata_device *dev,
u64 block, u32 n_block, unsigned int tf_flags,
- unsigned int tag);
+ unsigned int tag, unsigned int ata_hybrid_hint);
extern u64 ata_tf_read_block(struct ata_taskfile *tf, struct ata_device *dev);
extern unsigned ata_exec_internal(struct ata_device *dev,
struct ata_taskfile *tf, const u8 *cdb,
@@ -80,6 +80,7 @@ enum {
ATA_ID_SATA_CAPABILITY = 76,
ATA_ID_SATA_CAPABILITY_2 = 77,
ATA_ID_FEATURE_SUPP = 78,
+ ATA_ID_FEATURE_EN = 79,
ATA_ID_MAJOR_VER = 80,
ATA_ID_COMMAND_SET_1 = 82,
ATA_ID_COMMAND_SET_2 = 83,
@@ -371,6 +371,7 @@ enum {
ATA_EHI_PRINTINFO = (1 << 18), /* print configuration info */
ATA_EHI_SETMODE = (1 << 19), /* configure transfer mode */
ATA_EHI_POST_SETMODE = (1 << 20), /* revalidating after setmode */
+ ATA_EHI_SET_HYBRID = (1 << 21), /* changing hybrid mode */
ATA_EHI_DID_RESET = ATA_EHI_DID_SOFTRESET | ATA_EHI_DID_HARDRESET,
@@ -716,6 +717,9 @@ struct ata_device {
int spdn_cnt;
/* ering is CLEAR_END, read comment above CLEAR_END */
struct ata_ering ering;
+
+ bool hybrid_cap; /* device capable of hybrid hints */
+ bool hybrid_en; /* host hints enabled on device */
};
/* Fields between ATA_DEVICE_CLEAR_BEGIN and ATA_DEVICE_CLEAR_END are