ACPI methods for SATA
Subsystems (3)
cover ACPI methods for SATA
This is my current work-in-progress for comments on ACPI methods for SATA (and PATA later on, unless a patch from Shaohua Li on the mailing list handles PATA).
Quick status:
Working:
- find devices in namespace
- evaluate _SDD
- evaluate _GTF
- simulate writing taskfile to device
TODO:
- write taskfile to device in a generic, non-controller-specific way
- add more usage events for _SDD and _GTF (SATA) per
ACPI 3.0 spec, section 9.9.3.3, page 288.
---
~Randy
RFC 1/5 ata_acpi: Makefile + Kconfig
From: Randy Dunlap <[email protected]>
Add ata_acpi in Makefile and Kconfig.
drivers/scsi/Kconfig | 5 +++++ drivers/scsi/Makefile | 1 + 2 files changed, 6 insertions(+)
drivers/scsi/Makefile +1 -0
@@ -138,6 +138,7 @@ obj-$(CONFIG_SCSI_SATA_NV) += libata.o s obj-$(CONFIG_SCSI_SATA_ULI) += libata.o sata_uli.o obj-$(CONFIG_SCSI_SATA_MV) += libata.o sata_mv.o obj-$(CONFIG_SCSI_PDC_ADMA) += libata.o pdc_adma.o +obj-$(CONFIG_SCSI_SATA_ACPI) += libata.o ata_acpi.o obj-$(CONFIG_ARM) += arm/
drivers/scsi/Kconfig +5 -0
@@ -598,6 +598,11 @@ config SCSI_SATA_INTEL_COMBINED depends on IDE=y && !BLK_DEV_IDE_SATA && (SCSI_SATA_AHCI || SCSI_ATA_PIIX) default y +config SCSI_SATA_ACPI + bool + depends on SCSI_SATA && ACPI + default y + config SCSI_BUSLOGIC tristate "BusLogic SCSI support" depends on (PCI || ISA || MCA) && SCSI && ISA_DMA_API
---
RFC 2/5 ata_acpi: libata.h fields
In reply to Randy DunlapFrom: Randy Dunlap <[email protected]>
Add ACPI data pointer/length holders.
include/linux/libata.h | 6 ++++++ 1 files changed, 6 insertions(+)
include/linux/libata.h +6 -0
@@ -33,6 +33,7 @@#include <asm/io.h> #include <linux/ata.h> #include <linux/workqueue.h> +#include <acpi/acpi.h> /* * compile-time options
@@ -310,6 +311,11 @@ struct ata_device {u16 cylinders;/* Number of cylinders */ u16 heads;/* Number of heads */ u16 sectors;/* Number of sectors per track */ + +/* ACPI objects info */ +acpi_handle obj_handle; +acpi_size gtf_length; +u8 *gtf_address; };struct ata_port {
---
Re: [RFC 2/5] ata_acpi: libata.h fields
Why do this in the libata layer rather than the block or device layers?
Re: [RFC 2/5] ata_acpi: libata.h fields
Quoted 2 lines · On Wed, 23 Nov 2005 16:38:22 -0800 Edward Falk wrote:
Why do this in the libata layer rather than the block or device layers? -
Hi,
Thanks for your question.
There is some device-to-ACPI table associations that are needed. I don't see how the block layer would be appropriate for that. Could it be?
Also, calls must be made to each SATA device driver to perform a raw taskfile write.
What do you mean by the device layer? Maybe the ops function entries in each SATA driver? If so, it looks like I may need to add the raw taskfile write as an entry there, but I haven't really studied that very much yet.
Do you have some other (different) suggestions for doing this?
---
~Randy
RFC 3/5 ata_acpi: invoke ACPI support functions
In reply to Randy DunlapFrom: Randy Dunlap <[email protected]>
Add calls to ACPI methods for SATA drives.
drivers/scsi/libata-core.c | 4 ++++ drivers/scsi/libata.h | 7 +++++++ 2 files changed, 11 insertions(+)
drivers/scsi/libata-core.c +4 -0
@@ -1345,6 +1345,8 @@ void ata_dev_config(struct ata_port *ap,if (ap->ops->dev_config) ap->ops->dev_config (ap, &ap->device[i]); + +do_drive_SDD (ap, i); }/**
@@ -1784,6 +1786,8 @@ static void ata_set_mode(struct ata_portif (ap->flags &ATA_FLAG_PORT_DISABLED )return ; +do_drive_update_taskfiles (ap); +if (ap->ops->post_set_mode) ap->ops->post_set_mode (ap);
drivers/scsi/libata.h +7 -0
@@ -53,6 +53,13 @@ extern int ata_task_ioctl(struct scsi_deextern int ata_cmd_ioctl (struct scsi_device *scsidev,void __user *arg); +/* ata_acpi.c */ +extern int do_drive_SDD (struct ata_port *ap,unsigned int ix); +extern int do_drive_get_GTF (struct ata_port *ap,struct ata_device *atadev); +extern int do_drive_set_taskfiles (struct ata_port *ap,struct ata_device *atadev); +extern int do_drive_update_taskfiles (struct ata_port *ap); + +/* libata-scsi.c */ extern void ata_scsi_scan_host (struct ata_port *ap);extern int ata_scsi_error (struct Scsi_Host *host);
---
RFC 4/5 ata_acpi: kernel-doc
In reply to Randy DunlapFrom: Randy Dunlap <[email protected]>
Add ata_acpi.c to libata kernel-doc template file.
Documentation/DocBook/libata.tmpl | 6 ++++++ 1 files changed, 6 insertions(+)
Documentation/DocBook/libata.tmpl +6 -0
@@ -787,6 +787,12 @@ and other resources, etc. !Idrivers/scsi/libata-scsi.c </chapter> + <chapter id="libataAcpi"> + <title>libata ACPI interfaces/methods</title> +!Edrivers/scsi/ata_acpi.c +!Idrivers/scsi/ata_acpi.c + </chapter> + <chapter id="ataExceptions"> <title>ATA errors & exceptions</title>
---
RFC 5/5 ata_acpi: add ACPI support functions
In reply to Randy DunlapFrom: Randy Dunlap <[email protected]>
Add support for ACPI methods to SATA suspend/resume.
drivers/scsi/ata_acpi.c | 499 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 499 insertions(+)
drivers/scsi/ata_acpi.c new file +499 -0
@@ -0,0 +1,499 @@ +/* +* ata-acpi.c +* Provides ACPI support for PATA/SATA. +* +* Copyright (C) 2005 Intel Corp. +* Copyright (C) 2005 Randy Dunlap +*/ + +#include <linux/ata.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <acpi/acpi.h> +#include "scsi.h" +#include <linux/libata.h> +#include <linux/pci.h> + +#include <acpi/acpi_bus.h> +#include <acpi/acnames.h> +#include <acpi/acnamesp.h> +#include <acpi/acparser.h> +#include <acpi/acexcep.h> +#include <acpi/acmacros.h> +#include <acpi/actypes.h> + +#define SATA_ROOT_PORT (x) (((x) >> 16) & 0xffff) +#define SATA_PORT_NUMBER (x) ((x) & 0xffff)/* or NO_PORT_MULT */ +#define NO_PORT_MULT 0xffff +#define SATA_ADR_RSVD 0xffffffff + +#define REGS_PER_GTF 7 +struct taskfile_array { +u8 tfa[REGS_PER_GTF ];/* regs. 0x1f1 - 0x1f7 */ +}; + +#define DEBUGGING 1 +/* note: adds function name and KERN_DEBUG */ +#ifdef DEBUGGING +#define DEBPRINT(fmt, args...) \ + printk(KERN_DEBUG "%s: " fmt, __FUNCTION__, ## args) +#else +#define DEBPRINT(fmt, args...) do {} while (0) +#endif /* DEBUGGING */ + +static u8 *acpi_path_name (acpi_handle handle) +{ +acpi_status status; +static u8 path_name[ACPI_PATHNAME_MAX ]; +struct acpi_buffer ret_buf = {ACPI_PATHNAME_MAX , path_name }; + +memset (path_name,0 ,sizeof (path_name)); + status =acpi_get_name (handle,ACPI_FULL_PATHNAME , &ret_buf); + +if (ACPI_FAILURE (status)) +return NULL ; + +return path_name; +} + +/** +* pci_acpi_get_dev_handle - finds acpi_handle and pci device.function +* @dev: device to locate +* @handle: returned acpi_handle for @dev +* @pcidevfn: return PCI device.func for @dev +* +* Returns 0 on success, <0 on error. +*/ +static int pci_acpi_get_dev_handle (struct device *dev,acpi_handle *handle, +acpi_integer *pcidevfn) +{ +struct pci_dev *pci_dev; +acpi_integer addr; + + pci_dev =to_pci_dev (dev); +/* Please refer to the ACPI spec for the syntax of _ADR. */ + addr = (PCI_SLOT (pci_dev->devfn) <<16 ) |PCI_FUNC (pci_dev->devfn); + *pcidevfn = addr; + *handle =acpi_get_child (DEVICE_ACPI_HANDLE (dev->parent), addr); +DEBPRINT ("pcidevfn: 0x%llx\n" , + (unsigned long long )*pcidevfn); +if (!*handle) +return -ENODEV ; +return 0 ; +} + +struct walk_info {/* can be trimmed some */ +struct device *dev; +struct acpi_device *adev; +acpi_handle handle; +acpi_integer pcidevfn; +unsigned int drivenum; +acpi_handle obj_handle; +struct ata_port *ataport; +struct ata_device *atadev; +u32 sata_adr; +int status; +char basepath[ACPI_PATHNAME_MAX ]; +int basepath_len; +}; + +static acpi_status get_devices (acpi_handle handle, +u32 level,void *context,void **return_value) +{ +acpi_status status; +struct walk_info *winfo = context; +u8 *path_name =acpi_path_name (handle); +struct acpi_buffer buffer; +struct acpi_device_info *dinfo; + + buffer.length =ACPI_ALLOCATE_LOCAL_BUFFER ; + buffer.pointer =NULL ; + status =acpi_get_object_info (handle, &buffer); + +if (ACPI_SUCCESS (status)) { + dinfo = buffer.pointer; + +/* find full device path name for pcidevfn */ +if (dinfo && (dinfo->valid &ACPI_VALID_ADR ) && + dinfo->address == winfo->pcidevfn) { +DEBPRINT (":%s: matches pcidevfn (0x%llx)\n" , + path_name, winfo->pcidevfn); +strlcpy (winfo->basepath, path_name, +sizeof (winfo->basepath)); + winfo->basepath_len =strlen (path_name); + goto out; + } + +/* if basepath is not yet known, ignore this object */ +if (!winfo->basepath_len) + goto out; + +/* if this object is in scope of basepath, maybe use it */ +if (strncmp (path_name, winfo->basepath, + winfo->basepath_len) ==0 ) { +if (!(dinfo->valid &ACPI_VALID_ADR )) + goto out; +DEBPRINT ("GOT ONE: " +"(%s) root_port = 0x%llx, port_num = 0x%llx\n" , + path_name, +SATA_ROOT_PORT (dinfo->address), +SATA_PORT_NUMBER (dinfo->address)); +/* heuristics: */ +if (SATA_PORT_NUMBER (dinfo->address) !=NO_PORT_MULT ) +DEBPRINT ("warning: don't know how to handle SATA port multiplier\n" ); +if (SATA_ROOT_PORT (dinfo->address) == + winfo->ataport->port_no && +SATA_PORT_NUMBER (dinfo->address) ==NO_PORT_MULT ) { +DEBPRINT ("THIS ^^^^^ is the requested SATA drive (handle = 0x%p)\n" , + handle); + winfo->sata_adr = dinfo->address; + winfo->obj_handle = handle; + } + } +out: +ACPI_MEM_FREE (dinfo); + } + +return status; +} + +/* Get the SATA drive _ADR object. */ +static int get_sata_adr (struct device *dev,acpi_handle handle, +acpi_integer pcidevfn,unsigned int drive, +struct ata_port *ap, +struct ata_device *atadev,u32 *dev_adr) +{ +acpi_status status; +struct walk_info *winfo; +int err = -ENOMEM ; + + winfo =kzalloc (sizeof (struct walk_info ),GFP_KERNEL ); +if (!winfo) + goto out; + + winfo->dev = dev; + winfo->atadev = atadev; + winfo->ataport = ap; +if (acpi_bus_get_device (handle, &winfo->adev) <0 ) +DEBPRINT ("acpi_bus_get_device failed\n" ); + winfo->handle = handle; + winfo->pcidevfn = pcidevfn; + winfo->drivenum = drive; + + status =acpi_get_devices (NULL , get_devices, winfo,NULL ); +if (ACPI_FAILURE (status)) + err = -ENODEV ; +else { + *dev_adr = winfo->sata_adr; + atadev->obj_handle = winfo->obj_handle; + err =0 ; + } +kfree (winfo); +out: +return err; +} + +/** +* do_drive_SDD - send Identify data to a drive +* @ap: the ata_port for the drive +* @ix: drive index +* +* Must be after Identify (Packet) Device -- uses its data. +*/ +int do_drive_SDD (struct ata_port *ap,unsigned int ix) +{ +// struct acpi_device_walk_info *info = +// (struct acpi_device_walk_info *)context; +// struct acpi_parameter_info pinfo; +acpi_handle handle; +acpi_integer pcidevfn; +int err = -ENODEV ; +struct device *dev = ap->host_set->dev; +struct ata_device *atadev = &ap->device[ix]; +u32 dev_adr; +acpi_status status; +struct acpi_object_list input; +union acpi_object in_params[1 ]; + +printk (KERN_DEBUG +"%s: ap->id: %d, ix = %d, port#: %d, hard_port#: %d\n" , + __FUNCTION__, ap->id, ix, ap->port_no, ap->hard_port_no); + +/* Don't continue if not a SATA device. */ +if (!ata_id_is_sata (atadev->id)) + goto out; + +/* Don't continue if device has no _ADR method. +* _SDD is intended for known motherboard devices. */ + err =pci_acpi_get_dev_handle (dev, &handle, &pcidevfn); +if (err <0 ) + goto out; +/* Get this drive's _ADR info. */ + dev_adr =SATA_ADR_RSVD ; + err =get_sata_adr (dev, handle, pcidevfn, ix, ap, atadev, &dev_adr); +if (err <0 || dev_adr ==SATA_ADR_RSVD || atadev->obj_handle ==NULL ) + goto out; + +/* Give the drive Identify data to the drive via the _SDD method */ +/* _SDD: set up input parameters */ + input.count =1 ; + input.pointer = in_params; + in_params[0 ].type =ACPI_TYPE_BUFFER ; + in_params[0 ].buffer.length =sizeof (atadev->id); + in_params[0 ].buffer.pointer = (u8 *)atadev->id; +/* Output buffer: _SDD has no output */ + +/* It's OK for _SDD to be missing too. */ + status =acpi_evaluate_object (atadev->obj_handle,"_SDD" , &input,NULL ); + err =ACPI_FAILURE (status) ? -EIO :0 ; +if (err <0 ) { +printk (KERN_DEBUG +"ata%u(%u): %s _SDD error: status = 0x%x\n" , + ap->id, ap->device->devno, __FUNCTION__, status); + } +out: +return err; +} +EXPORT_SYMBOL_GPL (do_drive_SDD); + +/** +* do_drive_get_GTF - get the drive bootup default taskfile settings +* @ap: the ata_port for the drive +* @atadev: target ata_device +* +* This applies to both PATA and SATA drives. +* +* The _GTF method has no input parameters. +* It returns a variable number of register set values (registers +* hex 1F1..1F7, taskfiles). +* The <variable number> is not known in advance, so have ACPI-CA +* allocate the buffer as needed and return it, then free it later. +* +*/ +int do_drive_get_GTF (struct ata_port *ap,struct ata_device *atadev) +{ +// struct acpi_device_walk_info *info = +// (struct acpi_device_walk_info *)context; +// struct acpi_parameter_info pinfo; +acpi_status status; +acpi_handle handle; +acpi_integer pcidevfn; +struct acpi_buffer output; +union acpi_object *out_obj; +struct device *dev = ap->host_set->dev; +int err = -ENODEV ; + +if (!ata_dev_present (atadev))/* or port disabled ? */ + goto out; + +/* Don't continue if device has no _ADR method. +* _GTF is intended for known motherboard devices. */ + err =pci_acpi_get_dev_handle (dev, &handle, &pcidevfn); +if (err <0 ) + goto out; + +/* Setting up output buffer */ + output.length =ACPI_ALLOCATE_BUFFER ; + output.pointer =NULL ;/* ACPI-CA sets this; save/free it later */ + +/* _GTF has no input parameters */ + err = -EIO ; + status =acpi_evaluate_object (atadev->obj_handle,"_GTF" , +NULL , &output); +if (ACPI_FAILURE (status)) { +ACPI_MEM_FREE (output.pointer); +printk (KERN_DEBUG +"%s: Run _GTF error: status = 0x%x\n" , + __FUNCTION__, status); + goto out; + } + +if (!output.length || !output.pointer) { +printk (KERN_DEBUG +"%s: Run _GTF: length or ptr is NULL (0x%llx, 0x%p)\n" , + __FUNCTION__, + (unsigned long long )output.length, output.pointer); +ACPI_MEM_FREE (output.pointer); + goto out; + } + + out_obj = output.pointer; +if (out_obj->type !=ACPI_TYPE_BUFFER ) { +ACPI_MEM_FREE (output.pointer); +printk (KERN_DEBUG "%s: Run _GTF: error: " +"expected object type of ACPI_TYPE_BUFFER, got 0x%x\n" , + __FUNCTION__, out_obj->type); + err = -ENOENT ; + goto out; + } + + atadev->gtf_length = out_obj->buffer.length; + atadev->gtf_address = out_obj->buffer.pointer; + err =0 ; +out: +return err; +} +EXPORT_SYMBOL_GPL (do_drive_get_GTF); + +/** +* taskfile_load_pio - send taskfile registers to host controller +* @ap: Port to which output is sent +* @gtf: raw ATA taskfile register set (0x1f1 - 0x1f7) +* +* Outputs ATA taskfile to standard ATA host controller. +* +* LOCKING: TBD: +* Inherited from caller. +*/ +static void taskfile_load_pio (struct ata_port *ap, +const struct taskfile_array *gtf) +{ +struct ata_ioports *ioaddr = &ap->ioaddr; + +outb (gtf->tfa[0 ], ioaddr->feature_addr); +outb (gtf->tfa[1 ], ioaddr->nsect_addr); +outb (gtf->tfa[2 ], ioaddr->lbal_addr); +outb (gtf->tfa[3 ], ioaddr->lbam_addr); +outb (gtf->tfa[4 ], ioaddr->lbah_addr); +outb (gtf->tfa[5 ], ioaddr->device_addr); +outb (gtf->tfa[6 ], ioaddr->command_addr); + +ata_wait_idle (ap); +} + +/** +* taskfile_load_mmio - send taskfile registers to host controller +* @ap: Port to which output is sent +* @gtf: raw ATA taskfile register set (0x1f1 - 0x1f7) +* +* Outputs ATA taskfile to standard ATA host controller using MMIO. +* +* LOCKING: TBD: +* Inherited from caller. +*/ +static void taskfile_load_mmio (struct ata_port *ap, +const struct taskfile_array *gtf) +{ +struct ata_ioports *ioaddr = &ap->ioaddr; + +writeb (gtf->tfa[0 ], (void __iomem *) ioaddr->feature_addr); +writeb (gtf->tfa[1 ], (void __iomem *) ioaddr->nsect_addr); +writeb (gtf->tfa[2 ], (void __iomem *) ioaddr->lbal_addr); +writeb (gtf->tfa[3 ], (void __iomem *) ioaddr->lbam_addr); +writeb (gtf->tfa[4 ], (void __iomem *) ioaddr->lbah_addr); +writeb (gtf->tfa[5 ], (void __iomem *) ioaddr->device_addr); +writeb (gtf->tfa[6 ], (void __iomem *) ioaddr->command_addr); + +ata_wait_idle (ap); +} + + +/** +* taskfile_load_raw - send taskfile registers to host controller +* @ap: Port to which output is sent +* @gtf: raw ATA taskfile register set (0x1f1 - 0x1f7) +* +* Outputs ATA taskfile to standard ATA host controller using MMIO +* or PIO as indicated by the ATA_FLAG_MMIO flag. +* Writes the control, feature, nsect, lbal, lbam, and lbah registers. +* Optionally (ATA_TFLAG_LBA48) writes hob_feature, hob_nsect, +* hob_lbal, hob_lbam, and hob_lbah. +* +* This function waits for idle (!BUSY and !DRQ) after writing +* registers. If the control register has a new value, this +* function also waits for idle after writing control and before +* writing the remaining registers. +* +* LOCKING: TBD: +* Inherited from caller. +*/ +static void taskfile_load_raw (struct ata_port *ap, +const struct taskfile_array *gtf) +{ +DEBPRINT ("(0x1f1-1f7): hex: %02x %02x %02x %02x %02x %02x %02x\n" , + gtf->tfa[0 ], gtf->tfa[1 ], gtf->tfa[2 ], + gtf->tfa[3 ], gtf->tfa[4 ], gtf->tfa[5 ], gtf->tfa[6 ]); + + +#if 0 +if (ap->flags &ATA_FLAG_MMIO ) +taskfile_load_mmio (ap, gtf); +else +taskfile_load_pio (ap, gtf); +#endif +} + +/** +* do_drive_set_taskfiles - write the drive taskfile settings from _GTF +* @ap: the ata_port for the drive +* @atadev: target ata_device +* +* This applies to both PATA and SATA drives. +* +* Write {atadev->gtf_address, length atadev->gtf_length} in groups of +* REGS_PER_GTF bytes. +* +*/ +int do_drive_set_taskfiles (struct ata_port *ap,struct ata_device *atadev) +{ +// struct acpi_device_walk_info *info = +// (struct acpi_device_walk_info *)context; +// struct acpi_parameter_info pinfo; +// struct device *dev = ap->host_set->dev; +int err = -ENODEV ; +int gtf_count = + atadev->gtf_length /REGS_PER_GTF ; +int ix; +struct taskfile_array *gtf; + +if (!ata_dev_present (atadev))/* or port disabled ? */ + goto out; +if (!gtf_count)/* shouldn't be here */ + goto out; + +DEBPRINT ("total GTF bytes = %lld (0x%llx), gtf_count = %d\n" , + (unsigned long long )atadev->gtf_length, + (unsigned long long )atadev->gtf_length, gtf_count); +if (atadev->gtf_length %REGS_PER_GTF ) { +printk (KERN_ERR "%s: unexpected GTF length (%zd)\n" , + __FUNCTION__, atadev->gtf_length); + goto out; + } + +for (ix =0 ; ix < gtf_count; ix++) { + gtf = (struct taskfile_array *) + (atadev->gtf_address + ix *REGS_PER_GTF ); + +/* TBD: writing the taskfile array must have a more +* generic interface than this.... */ +/* send all TaskFile registers (0x1f1-0x1f7) *in*that*order* */ +taskfile_load_raw (ap, gtf); + } + + err =0 ; +out: +return err; +} +EXPORT_SYMBOL_GPL (do_drive_set_taskfiles); + +/** +* do_drive_update_taskfiles - get then write drive taskfile settings +* @ap: the ata_port for the drive +* +* This applies to both PATA and SATA drives. +*/ +int do_drive_update_taskfiles (struct ata_port *ap) +{ +int ix; +int ret; + +for (ix =0 ; ix <ATA_MAX_DEVICES ; ix++) { + ret =do_drive_get_GTF (ap, &ap->device[ix]); +if (ret >=0 ) + ret =do_drive_set_taskfiles (ap, &ap->device[ix]); + } + +return ret; +} +EXPORT_SYMBOL_GPL (do_drive_update_taskfiles);
---