Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3264a8d8 authored by Tejun Heo's avatar Tejun Heo Committed by Jeff Garzik
Browse files

libata-acpi: implement _GTF command filtering



Implement _GTF command filtering which can be controlled by
libata.acpi_filter kernel parameter.  Currently SETXFER and LOCK
commands are filtered.

libata configures transfer mode by itself and _GTF SETXFER commands
can potentially disrupt device configuration.  _GTM/_STM mechanism
can't handle hotplugging too well and when _GTF is executed,
controller is in PIO0 rather than the mode _STM configured.

Note that detecting SET MAX LOCK requires looking at the previous
command.  This adds a bit to code complexity.

Signed-off-by: default avatarTejun Heo <htejun@gmail.com>
Signed-off-by: default avatarJeff Garzik <jeff@garzik.org>
parent 0e8634bf
Loading
Loading
Loading
Loading
+115 −44
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@
 * Copyright (C) 2006 Randy Dunlap
 */

#include <linux/module.h>
#include <linux/ata.h>
#include <linux/delay.h>
#include <linux/device.h>
@@ -25,6 +26,18 @@
#include <acpi/acmacros.h>
#include <acpi/actypes.h>

enum {
	ATA_ACPI_FILTER_SETXFER	= 1 << 0,
	ATA_ACPI_FILTER_LOCK	= 1 << 1,

	ATA_ACPI_FILTER_DEFAULT	= ATA_ACPI_FILTER_SETXFER |
				  ATA_ACPI_FILTER_LOCK,
};

static unsigned int ata_acpi_gtf_filter = ATA_ACPI_FILTER_DEFAULT;
module_param_named(acpi_gtf_filter, ata_acpi_gtf_filter, int, 0644);
MODULE_PARM_DESC(acpi_gtf_filter, "filter mask for ACPI _GTF commands, set to filter out (0x1=set xfermode, 0x2=lock/freeze lock)");

#define NO_PORT_MULT		0xffff
#define SATA_ADR(root, pmp)	(((root) << 16) | (pmp))

@@ -465,6 +478,60 @@ int ata_acpi_cbl_80wire(struct ata_port *ap)

EXPORT_SYMBOL_GPL(ata_acpi_cbl_80wire);

static void ata_acpi_gtf_to_tf(struct ata_device *dev,
			       const struct ata_acpi_gtf *gtf,
			       struct ata_taskfile *tf)
{
	ata_tf_init(dev, tf);

	tf->flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE;
	tf->protocol = ATA_PROT_NODATA;
	tf->feature = gtf->tf[0];	/* 0x1f1 */
	tf->nsect   = gtf->tf[1];	/* 0x1f2 */
	tf->lbal    = gtf->tf[2];	/* 0x1f3 */
	tf->lbam    = gtf->tf[3];	/* 0x1f4 */
	tf->lbah    = gtf->tf[4];	/* 0x1f5 */
	tf->device  = gtf->tf[5];	/* 0x1f6 */
	tf->command = gtf->tf[6];	/* 0x1f7 */
}

static int ata_acpi_filter_tf(const struct ata_taskfile *tf,
			      const struct ata_taskfile *ptf)
{
	if (ata_acpi_gtf_filter & ATA_ACPI_FILTER_SETXFER) {
		/* libata doesn't use ACPI to configure transfer mode.
		 * It will only confuse device configuration.  Skip.
		 */
		if (tf->command == ATA_CMD_SET_FEATURES &&
		    tf->feature == SETFEATURES_XFER)
			return 1;
	}

	if (ata_acpi_gtf_filter & ATA_ACPI_FILTER_LOCK) {
		/* BIOS writers, sorry but we don't wanna lock
		 * features unless the user explicitly said so.
		 */

		/* DEVICE CONFIGURATION FREEZE LOCK */
		if (tf->command == ATA_CMD_CONF_OVERLAY &&
		    tf->feature == ATA_DCO_FREEZE_LOCK)
			return 1;

		/* SECURITY FREEZE LOCK */
		if (tf->command == ATA_CMD_SEC_FREEZE_LOCK)
			return 1;

		/* SET MAX LOCK and SET MAX FREEZE LOCK */
		if ((!ptf || ptf->command != ATA_CMD_READ_NATIVE_MAX) &&
		    tf->command == ATA_CMD_SET_MAX &&
		    (tf->feature == ATA_SET_MAX_LOCK ||
		     tf->feature == ATA_SET_MAX_FREEZE_LOCK))
			return 1;
	}

	return 0;
}

/**
 * ata_acpi_run_tf - send taskfile registers to host controller
 * @dev: target ATA device
@@ -485,13 +552,15 @@ EXPORT_SYMBOL_GPL(ata_acpi_cbl_80wire);
 * EH context.
 *
 * RETURNS:
 * 1 if command is executed successfully.  0 if ignored or rejected,
 * -errno on other errors.
 * 1 if command is executed successfully.  0 if ignored, rejected or
 * filtered out, -errno on other errors.
 */
static int ata_acpi_run_tf(struct ata_device *dev,
			   const struct ata_acpi_gtf *gtf)
			   const struct ata_acpi_gtf *gtf,
			   const struct ata_acpi_gtf *prev_gtf)
{
	struct ata_taskfile tf, rtf;
	struct ata_taskfile *pptf = NULL;
	struct ata_taskfile tf, ptf, rtf;
	unsigned int err_mask;
	const char *level;
	char msg[60];
@@ -502,21 +571,16 @@ static int ata_acpi_run_tf(struct ata_device *dev,
	    && (gtf->tf[6] == 0))
		return 0;

	ata_tf_init(dev, &tf);

	/* convert gtf to tf */
	tf.flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE; /* TBD */
	tf.protocol = ATA_PROT_NODATA;
	tf.feature = gtf->tf[0];	/* 0x1f1 */
	tf.nsect   = gtf->tf[1];	/* 0x1f2 */
	tf.lbal    = gtf->tf[2];	/* 0x1f3 */
	tf.lbam    = gtf->tf[3];	/* 0x1f4 */
	tf.lbah    = gtf->tf[4];	/* 0x1f5 */
	tf.device  = gtf->tf[5];	/* 0x1f6 */
	tf.command = gtf->tf[6];	/* 0x1f7 */
	ata_acpi_gtf_to_tf(dev, gtf, &tf);
	if (prev_gtf) {
		ata_acpi_gtf_to_tf(dev, prev_gtf, &ptf);
		pptf = &ptf;
	}

	if (!ata_acpi_filter_tf(&tf, pptf)) {
		rtf = tf;
	err_mask = ata_exec_internal(dev, &rtf, NULL, DMA_NONE, NULL, 0, 0);
		err_mask = ata_exec_internal(dev, &rtf, NULL,
					     DMA_NONE, NULL, 0, 0);

		switch (err_mask) {
		case 0:
@@ -541,6 +605,11 @@ static int ata_acpi_run_tf(struct ata_device *dev,
			rc = -EIO;
			break;
		}
	} else {
		level = KERN_INFO;
		snprintf(msg, sizeof(msg), "filtered out");
		rc = 0;
	}

	ata_dev_printk(dev, level,
		       "ACPI cmd %02x/%02x:%02x:%02x:%02x:%02x:%02x %s\n",
@@ -566,7 +635,7 @@ static int ata_acpi_run_tf(struct ata_device *dev,
 */
static int ata_acpi_exec_tfs(struct ata_device *dev, int *nr_executed)
{
	struct ata_acpi_gtf *gtf = NULL;
	struct ata_acpi_gtf *gtf = NULL, *pgtf = NULL;
	int gtf_count, i, rc;

	/* get taskfiles */
@@ -576,12 +645,14 @@ static int ata_acpi_exec_tfs(struct ata_device *dev, int *nr_executed)
	gtf_count = rc;

	/* execute them */
	for (i = 0; i < gtf_count; i++) {
		rc = ata_acpi_run_tf(dev, gtf++);
	for (i = 0; i < gtf_count; i++, gtf++) {
		rc = ata_acpi_run_tf(dev, gtf, pgtf);
		if (rc < 0)
			break;
		if (rc)
		if (rc) {
			(*nr_executed)++;
			pgtf = gtf;
		}
	}

	ata_acpi_clear_gtf(dev);