diff options
Diffstat (limited to 'drivers/ata/libata-sata.c')
-rw-r--r-- | drivers/ata/libata-sata.c | 359 |
1 files changed, 255 insertions, 104 deletions
diff --git a/drivers/ata/libata-sata.c b/drivers/ata/libata-sata.c index 0fb1934875f2..ba300cc0a3a3 100644 --- a/drivers/ata/libata-sata.c +++ b/drivers/ata/libata-sata.c @@ -13,7 +13,7 @@ #include <scsi/scsi_device.h> #include <scsi/scsi_eh.h> #include <linux/libata.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include "libata.h" #include "libata-transport.h" @@ -518,6 +518,86 @@ int sata_set_spd(struct ata_link *link) EXPORT_SYMBOL_GPL(sata_set_spd); /** + * sata_down_spd_limit - adjust SATA spd limit downward + * @link: Link to adjust SATA spd limit for + * @spd_limit: Additional limit + * + * Adjust SATA spd limit of @link downward. Note that this + * function only adjusts the limit. The change must be applied + * using sata_set_spd(). + * + * If @spd_limit is non-zero, the speed is limited to equal to or + * lower than @spd_limit if such speed is supported. If + * @spd_limit is slower than any supported speed, only the lowest + * supported speed is allowed. + * + * LOCKING: + * Inherited from caller. + * + * RETURNS: + * 0 on success, negative errno on failure + */ +int sata_down_spd_limit(struct ata_link *link, u32 spd_limit) +{ + u32 sstatus, spd, mask; + int rc, bit; + + if (!sata_scr_valid(link)) + return -EOPNOTSUPP; + + /* If SCR can be read, use it to determine the current SPD. + * If not, use cached value in link->sata_spd. + */ + rc = sata_scr_read(link, SCR_STATUS, &sstatus); + if (rc == 0 && ata_sstatus_online(sstatus)) + spd = (sstatus >> 4) & 0xf; + else + spd = link->sata_spd; + + mask = link->sata_spd_limit; + if (mask <= 1) + return -EINVAL; + + /* unconditionally mask off the highest bit */ + bit = fls(mask) - 1; + mask &= ~(1 << bit); + + /* + * Mask off all speeds higher than or equal to the current one. At + * this point, if current SPD is not available and we previously + * recorded the link speed from SStatus, the driver has already + * masked off the highest bit so mask should already be 1 or 0. + * Otherwise, we should not force 1.5Gbps on a link where we have + * not previously recorded speed from SStatus. Just return in this + * case. + */ + if (spd > 1) + mask &= (1 << (spd - 1)) - 1; + else if (link->sata_spd) + return -EINVAL; + + /* were we already at the bottom? */ + if (!mask) + return -EINVAL; + + if (spd_limit) { + if (mask & ((1 << spd_limit) - 1)) + mask &= (1 << spd_limit) - 1; + else { + bit = ffs(mask) - 1; + mask = 1 << bit; + } + } + + link->sata_spd_limit = mask; + + ata_link_warn(link, "limiting SATA link speed to %s\n", + sata_spd_string(fls(mask))); + + return 0; +} + +/** * sata_link_hardreset - reset link via SATA phy reset * @link: link to reset * @timing: timing parameters { interval, duration, timeout } in msec @@ -627,6 +707,34 @@ int sata_link_hardreset(struct ata_link *link, const unsigned int *timing, EXPORT_SYMBOL_GPL(sata_link_hardreset); /** + * sata_std_hardreset - COMRESET w/o waiting or classification + * @link: link to reset + * @class: resulting class of attached device + * @deadline: deadline jiffies for the operation + * + * Standard SATA COMRESET w/o waiting or classification. + * + * LOCKING: + * Kernel thread context (may sleep) + * + * RETURNS: + * 0 if link offline, -EAGAIN if link online, -errno on errors. + */ +int sata_std_hardreset(struct ata_link *link, unsigned int *class, + unsigned long deadline) +{ + const unsigned int *timing = sata_ehc_deb_timing(&link->eh_context); + bool online; + int rc; + + rc = sata_link_hardreset(link, timing, deadline, &online, NULL); + if (online) + return -EAGAIN; + return rc; +} +EXPORT_SYMBOL_GPL(sata_std_hardreset); + +/** * ata_qc_complete_multiple - Complete multiple qcs successfully * @ap: port in question * @qc_active: new qc_active mask @@ -818,7 +926,7 @@ static ssize_t ata_scsi_lpm_store(struct device *device, ata_for_each_link(link, ap, EDGE) { ata_for_each_dev(dev, &ap->link, ENABLED) { - if (dev->horkage & ATA_HORKAGE_NOLPM) { + if (dev->quirks & ATA_QUIRK_NOLPM) { count = -EOPNOTSUPP; goto out_unlock; } @@ -848,80 +956,143 @@ DEVICE_ATTR(link_power_management_policy, S_IRUGO | S_IWUSR, ata_scsi_lpm_show, ata_scsi_lpm_store); EXPORT_SYMBOL_GPL(dev_attr_link_power_management_policy); -static ssize_t ata_ncq_prio_supported_show(struct device *device, - struct device_attribute *attr, - char *buf) +/** + * ata_ncq_prio_supported - Check if device supports NCQ Priority + * @ap: ATA port of the target device + * @sdev: SCSI device + * @supported: Address of a boolean to store the result + * + * Helper to check if device supports NCQ Priority feature. + * + * Context: Any context. Takes and releases @ap->lock. + * + * Return: + * * %0 - OK. Status is stored into @supported + * * %-ENODEV - Failed to find the ATA device + */ +int ata_ncq_prio_supported(struct ata_port *ap, struct scsi_device *sdev, + bool *supported) { - struct scsi_device *sdev = to_scsi_device(device); - struct ata_port *ap = ata_shost_to_port(sdev->host); struct ata_device *dev; - bool ncq_prio_supported; + unsigned long flags; int rc = 0; - spin_lock_irq(ap->lock); + spin_lock_irqsave(ap->lock, flags); dev = ata_scsi_find_dev(ap, sdev); if (!dev) rc = -ENODEV; else - ncq_prio_supported = dev->flags & ATA_DFLAG_NCQ_PRIO; - spin_unlock_irq(ap->lock); + *supported = dev->flags & ATA_DFLAG_NCQ_PRIO; + spin_unlock_irqrestore(ap->lock, flags); + + return rc; +} +EXPORT_SYMBOL_GPL(ata_ncq_prio_supported); + +static ssize_t ata_ncq_prio_supported_show(struct device *device, + struct device_attribute *attr, + char *buf) +{ + struct scsi_device *sdev = to_scsi_device(device); + struct ata_port *ap = ata_shost_to_port(sdev->host); + bool supported; + int rc; - return rc ? rc : sysfs_emit(buf, "%u\n", ncq_prio_supported); + rc = ata_ncq_prio_supported(ap, sdev, &supported); + if (rc) + return rc; + + return sysfs_emit(buf, "%d\n", supported); } DEVICE_ATTR(ncq_prio_supported, S_IRUGO, ata_ncq_prio_supported_show, NULL); EXPORT_SYMBOL_GPL(dev_attr_ncq_prio_supported); -static ssize_t ata_ncq_prio_enable_show(struct device *device, - struct device_attribute *attr, - char *buf) +/** + * ata_ncq_prio_enabled - Check if NCQ Priority is enabled + * @ap: ATA port of the target device + * @sdev: SCSI device + * @enabled: Address of a boolean to store the result + * + * Helper to check if NCQ Priority feature is enabled. + * + * Context: Any context. Takes and releases @ap->lock. + * + * Return: + * * %0 - OK. Status is stored into @enabled + * * %-ENODEV - Failed to find the ATA device + */ +int ata_ncq_prio_enabled(struct ata_port *ap, struct scsi_device *sdev, + bool *enabled) { - struct scsi_device *sdev = to_scsi_device(device); - struct ata_port *ap = ata_shost_to_port(sdev->host); struct ata_device *dev; - bool ncq_prio_enable; + unsigned long flags; int rc = 0; - spin_lock_irq(ap->lock); + spin_lock_irqsave(ap->lock, flags); dev = ata_scsi_find_dev(ap, sdev); if (!dev) rc = -ENODEV; else - ncq_prio_enable = dev->flags & ATA_DFLAG_NCQ_PRIO_ENABLED; - spin_unlock_irq(ap->lock); + *enabled = dev->flags & ATA_DFLAG_NCQ_PRIO_ENABLED; + spin_unlock_irqrestore(ap->lock, flags); - return rc ? rc : sysfs_emit(buf, "%u\n", ncq_prio_enable); + return rc; } +EXPORT_SYMBOL_GPL(ata_ncq_prio_enabled); -static ssize_t ata_ncq_prio_enable_store(struct device *device, - struct device_attribute *attr, - const char *buf, size_t len) +static ssize_t ata_ncq_prio_enable_show(struct device *device, + struct device_attribute *attr, + char *buf) { struct scsi_device *sdev = to_scsi_device(device); - struct ata_port *ap; - struct ata_device *dev; - long int input; - int rc = 0; + struct ata_port *ap = ata_shost_to_port(sdev->host); + bool enabled; + int rc; - rc = kstrtol(buf, 10, &input); + rc = ata_ncq_prio_enabled(ap, sdev, &enabled); if (rc) return rc; - if ((input < 0) || (input > 1)) - return -EINVAL; - ap = ata_shost_to_port(sdev->host); - dev = ata_scsi_find_dev(ap, sdev); - if (unlikely(!dev)) - return -ENODEV; + return sysfs_emit(buf, "%d\n", enabled); +} + +/** + * ata_ncq_prio_enable - Enable/disable NCQ Priority + * @ap: ATA port of the target device + * @sdev: SCSI device + * @enable: true - enable NCQ Priority, false - disable NCQ Priority + * + * Helper to enable/disable NCQ Priority feature. + * + * Context: Any context. Takes and releases @ap->lock. + * + * Return: + * * %0 - OK. Status is stored into @enabled + * * %-ENODEV - Failed to find the ATA device + * * %-EINVAL - NCQ Priority is not supported or CDL is enabled + */ +int ata_ncq_prio_enable(struct ata_port *ap, struct scsi_device *sdev, + bool enable) +{ + struct ata_device *dev; + unsigned long flags; + int rc = 0; - spin_lock_irq(ap->lock); + spin_lock_irqsave(ap->lock, flags); + + dev = ata_scsi_find_dev(ap, sdev); + if (!dev) { + rc = -ENODEV; + goto unlock; + } if (!(dev->flags & ATA_DFLAG_NCQ_PRIO)) { rc = -EINVAL; goto unlock; } - if (input) { + if (enable) { if (dev->flags & ATA_DFLAG_CDL_ENABLED) { ata_dev_err(dev, "CDL must be disabled to enable NCQ priority\n"); @@ -934,9 +1105,30 @@ static ssize_t ata_ncq_prio_enable_store(struct device *device, } unlock: - spin_unlock_irq(ap->lock); + spin_unlock_irqrestore(ap->lock, flags); + + return rc; +} +EXPORT_SYMBOL_GPL(ata_ncq_prio_enable); + +static ssize_t ata_ncq_prio_enable_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct scsi_device *sdev = to_scsi_device(device); + struct ata_port *ap = ata_shost_to_port(sdev->host); + bool enable; + int rc; + + rc = kstrtobool(buf, &enable); + if (rc) + return rc; + + rc = ata_ncq_prio_enable(ap, sdev, enable); + if (rc) + return rc; - return rc ? rc : len; + return len; } DEVICE_ATTR(ncq_prio_enable, S_IRUGO | S_IWUSR, @@ -1121,70 +1313,24 @@ int ata_scsi_change_queue_depth(struct scsi_device *sdev, int queue_depth) EXPORT_SYMBOL_GPL(ata_scsi_change_queue_depth); /** - * ata_sas_port_alloc - Allocate port for a SAS attached SATA device - * @host: ATA host container for all SAS ports - * @port_info: Information from low-level host driver - * @shost: SCSI host that the scsi device is attached to - * - * LOCKING: - * PCI/etc. bus probe sem. - * - * RETURNS: - * ata_port pointer on success / NULL on failure. - */ - -struct ata_port *ata_sas_port_alloc(struct ata_host *host, - struct ata_port_info *port_info, - struct Scsi_Host *shost) -{ - struct ata_port *ap; - - ap = ata_port_alloc(host); - if (!ap) - return NULL; - - ap->port_no = 0; - ap->lock = &host->lock; - ap->pio_mask = port_info->pio_mask; - ap->mwdma_mask = port_info->mwdma_mask; - ap->udma_mask = port_info->udma_mask; - ap->flags |= port_info->flags; - ap->ops = port_info->port_ops; - ap->cbl = ATA_CBL_SATA; - ap->print_id = atomic_inc_return(&ata_print_id); - - return ap; -} -EXPORT_SYMBOL_GPL(ata_sas_port_alloc); - -int ata_sas_tport_add(struct device *parent, struct ata_port *ap) -{ - return ata_tport_add(parent, ap); -} -EXPORT_SYMBOL_GPL(ata_sas_tport_add); - -void ata_sas_tport_delete(struct ata_port *ap) -{ - ata_tport_delete(ap); -} -EXPORT_SYMBOL_GPL(ata_sas_tport_delete); - -/** - * ata_sas_slave_configure - Default slave_config routine for libata devices + * ata_sas_sdev_configure - Default sdev_configure routine for libata + * devices * @sdev: SCSI device to configure + * @lim: queue limits * @ap: ATA port to which SCSI device is attached * * RETURNS: * Zero. */ -int ata_sas_slave_configure(struct scsi_device *sdev, struct ata_port *ap) +int ata_sas_sdev_configure(struct scsi_device *sdev, struct queue_limits *lim, + struct ata_port *ap) { ata_scsi_sdev_config(sdev); - return ata_scsi_dev_config(sdev, ap->link.device); + return ata_scsi_dev_config(sdev, lim, ap->link.device); } -EXPORT_SYMBOL_GPL(ata_sas_slave_configure); +EXPORT_SYMBOL_GPL(ata_sas_sdev_configure); /** * ata_sas_queuecmd - Issue SCSI cdb to libata-managed device @@ -1302,7 +1448,7 @@ EXPORT_SYMBOL_GPL(sata_async_notification); static int ata_eh_read_log_10h(struct ata_device *dev, int *tag, struct ata_taskfile *tf) { - u8 *buf = dev->link->ap->sector_buf; + u8 *buf = dev->sector_buf; unsigned int err_mask; u8 csum; int i; @@ -1341,8 +1487,8 @@ static int ata_eh_read_log_10h(struct ata_device *dev, } /** - * ata_eh_read_sense_success_ncq_log - Read the sense data for successful - * NCQ commands log + * ata_eh_get_ncq_success_sense - Read and process the sense data for + * successful NCQ commands log page * @link: ATA link to get sense data for * * Read the sense data for successful NCQ commands log page to obtain @@ -1355,11 +1501,11 @@ static int ata_eh_read_log_10h(struct ata_device *dev, * RETURNS: * 0 on success, -errno otherwise. */ -int ata_eh_read_sense_success_ncq_log(struct ata_link *link) +int ata_eh_get_ncq_success_sense(struct ata_link *link) { struct ata_device *dev = link->device; struct ata_port *ap = dev->link->ap; - u8 *buf = ap->ncq_sense_buf; + u8 *buf = dev->cdl->ncq_sense_log_buf; struct ata_queued_cmd *qc; unsigned int err_mask, tag; u8 *sense, sk = 0, asc = 0, ascq = 0; @@ -1417,17 +1563,14 @@ int ata_eh_read_sense_success_ncq_log(struct ata_link *link) qc->flags |= ATA_QCFLAG_SENSE_VALID; /* - * If we have sense data, call scsi_check_sense() in order to - * set the correct SCSI ML byte (if any). No point in checking - * the return value, since the command has already completed - * successfully. + * No point in checking the return value, since the command has + * already completed successfully. */ - scsi_check_sense(qc->scsicmd); + ata_eh_decide_disposition(qc); } return ret; } -EXPORT_SYMBOL_GPL(ata_eh_read_sense_success_ncq_log); /** * ata_eh_analyze_ncq_error - analyze NCQ error @@ -1538,3 +1681,11 @@ void ata_eh_analyze_ncq_error(struct ata_link *link) ehc->i.err_mask &= ~AC_ERR_DEV; } EXPORT_SYMBOL_GPL(ata_eh_analyze_ncq_error); + +const struct ata_port_operations sata_port_ops = { + .inherits = &ata_base_port_ops, + + .qc_defer = ata_std_qc_defer, + .hardreset = sata_std_hardreset, +}; +EXPORT_SYMBOL_GPL(sata_port_ops); |