/****************************************************************************** * * Module Name: amfldio - Aml Field I/O * $Revision: 37 $ * *****************************************************************************/ /* * Copyright (C) 2000, 2001 R. Byron Moore * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "acpi.h" #include "acinterp.h" #include "amlcode.h" #include "acnamesp.h" #include "achware.h" #include "acevents.h" #define _COMPONENT INTERPRETER MODULE_NAME ("amfldio") /******************************************************************************* * * FUNCTION: Acpi_aml_read_field_data * * PARAMETERS: *Obj_desc - Field to be read * *Value - Where to store value * Field_bit_width - Field Width in bits (8, 16, or 32) * * RETURN: Status * * DESCRIPTION: Retrieve the value of the given field * ******************************************************************************/ ACPI_STATUS acpi_aml_read_field_data ( ACPI_OPERAND_OBJECT *obj_desc, u32 field_byte_offset, u32 field_bit_width, u32 *value) { ACPI_STATUS status; ACPI_OPERAND_OBJECT *rgn_desc = NULL; ACPI_PHYSICAL_ADDRESS address; u32 local_value = 0; u32 field_byte_width; /* Obj_desc is validated by callers */ if (obj_desc) { rgn_desc = obj_desc->field.container; } field_byte_width = DIV_8 (field_bit_width); status = acpi_aml_setup_field (obj_desc, rgn_desc, field_bit_width); if (ACPI_FAILURE (status)) { return (status); } /* Setup_field validated Rgn_desc and Field_bit_width */ if (!value) { value = &local_value; /* support reads without saving value */ } /* * Set offset to next multiple of field width, * add region base address and offset within the field */ address = rgn_desc->region.address + (obj_desc->field.offset * field_byte_width) + field_byte_offset; /* Invoke the appropriate Address_space/Op_region handler */ status = acpi_ev_address_space_dispatch (rgn_desc, ADDRESS_SPACE_READ, address, field_bit_width, value); return (status); } /******************************************************************************* * * FUNCTION: Acpi_aml_read_field * * PARAMETERS: *Obj_desc - Field to be read * *Value - Where to store value * Field_bit_width - Field Width in bits (8, 16, or 32) * * RETURN: Status * * DESCRIPTION: Retrieve the value of the given field * ******************************************************************************/ ACPI_STATUS acpi_aml_read_field ( ACPI_OPERAND_OBJECT *obj_desc, void *buffer, u32 buffer_length, u32 byte_length, u32 datum_length, u32 bit_granularity, u32 byte_granularity) { ACPI_STATUS status; u32 this_field_byte_offset; u32 this_field_datum_offset; u32 previous_raw_datum; u32 this_raw_datum = 0; u32 valid_field_bits; u32 mask; u32 merged_datum = 0; /* * Clear the caller's buffer (the whole buffer length as given) * This is very important, especially in the cases where a byte is read, * but the buffer is really a u32 (4 bytes). */ MEMSET (buffer, 0, buffer_length); /* Read the first raw datum to prime the loop */ this_field_byte_offset = 0; this_field_datum_offset= 0; status = acpi_aml_read_field_data (obj_desc, this_field_byte_offset, bit_granularity, &previous_raw_datum); if (ACPI_FAILURE (status)) { goto cleanup; } /* We might actually be done if the request fits in one datum */ if ((datum_length == 1) && ((obj_desc->field.bit_offset + obj_desc->field_unit.length) <= (u16) bit_granularity)) { merged_datum = previous_raw_datum; merged_datum = (merged_datum >> obj_desc->field.bit_offset); valid_field_bits = obj_desc->field_unit.length % bit_granularity; if (valid_field_bits) { mask = (((u32) 1 << valid_field_bits) - (u32) 1); merged_datum &= mask; } /* * Place the Merged_datum into the proper format and return buffer * field */ switch (byte_granularity) { case 1: ((u8 *) buffer) [this_field_datum_offset] = (u8) merged_datum; break; case 2: MOVE_UNALIGNED16_TO_16 (&(((u16 *) buffer)[this_field_datum_offset]), &merged_datum); break; case 4: MOVE_UNALIGNED32_TO_32 (&(((u32 *) buffer)[this_field_datum_offset]), &merged_datum); break; } this_field_byte_offset = 1; this_field_datum_offset = 1; } else { /* We need to get more raw data to complete one or more field data */ while (this_field_datum_offset < datum_length) { /* * If the field is aligned on a byte boundary, we don't want * to perform a final read, since this would potentially read * past the end of the region. * * TBD: [Investigate] It may make more sense to just split the aligned * and non-aligned cases since the aligned case is so very simple, */ if ((obj_desc->field.bit_offset != 0) || ((obj_desc->field.bit_offset == 0) && (this_field_datum_offset < (datum_length -1)))) { /* * Get the next raw datum, it contains some or all bits * of the current field datum */ status = acpi_aml_read_field_data (obj_desc, this_field_byte_offset + byte_granularity, bit_granularity, &this_raw_datum); if (ACPI_FAILURE (status)) { goto cleanup; } /* Before merging the data, make sure the unused bits are clear */ switch (byte_granularity) { case 1: this_raw_datum &= 0x000000FF; previous_raw_datum &= 0x000000FF; break; case 2: this_raw_datum &= 0x0000FFFF; previous_raw_datum &= 0x0000FFFF; break; } } /* * Put together bits of the two raw data to make a complete * field datum */ if (obj_desc->field.bit_offset != 0) { merged_datum = (previous_raw_datum >> obj_desc->field.bit_offset) | (this_raw_datum << (bit_granularity - obj_desc->field.bit_offset)); } else { merged_datum = previous_raw_datum; } /* * Prepare the merged datum for storing into the caller's * buffer. It is possible to have a 32-bit buffer * (Byte_granularity == 4), but a Obj_desc->Field.Length * of 8 or 16, meaning that the upper bytes of merged data * are undesired. This section fixes that. */ switch (obj_desc->field.length) { case 8: merged_datum &= 0x000000FF; break; case 16: merged_datum &= 0x0000FFFF; break; } /* * Now store the datum in the caller's buffer, according to * the data type */ switch (byte_granularity) { case 1: ((u8 *) buffer) [this_field_datum_offset] = (u8) merged_datum; break; case 2: MOVE_UNALIGNED16_TO_16 (&(((u16 *) buffer) [this_field_datum_offset]), &merged_datum); break; case 4: MOVE_UNALIGNED32_TO_32 (&(((u32 *) buffer) [this_field_datum_offset]), &merged_datum); break; } /* * Save the most recent datum since it contains bits of * the *next* field datum */ previous_raw_datum = this_raw_datum; this_field_byte_offset += byte_granularity; this_field_datum_offset++; } /* while */ } cleanup: return (status); } /******************************************************************************* * * FUNCTION: Acpi_aml_write_field_data * * PARAMETERS: *Obj_desc - Field to be set * Value - Value to store * Field_bit_width - Field Width in bits (8, 16, or 32) * * RETURN: Status * * DESCRIPTION: Store the value into the given field * ******************************************************************************/ static ACPI_STATUS acpi_aml_write_field_data ( ACPI_OPERAND_OBJECT *obj_desc, u32 field_byte_offset, u32 field_bit_width, u32 value) { ACPI_STATUS status = AE_OK; ACPI_OPERAND_OBJECT *rgn_desc = NULL; ACPI_PHYSICAL_ADDRESS address; u32 field_byte_width; /* Obj_desc is validated by callers */ if (obj_desc) { rgn_desc = obj_desc->field.container; } field_byte_width = DIV_8 (field_bit_width); status = acpi_aml_setup_field (obj_desc, rgn_desc, field_bit_width); if (ACPI_FAILURE (status)) { return (status); } /* * Set offset to next multiple of field width, * add region base address and offset within the field */ address = rgn_desc->region.address + (obj_desc->field.offset * field_byte_width) + field_byte_offset; /* Invoke the appropriate Address_space/Op_region handler */ status = acpi_ev_address_space_dispatch (rgn_desc, ADDRESS_SPACE_WRITE, address, field_bit_width, &value); return (status); } /***************************************************************************** * * FUNCTION: Acpi_aml_write_field_data_with_update_rule * * PARAMETERS: *Obj_desc - Field to be set * Value - Value to store * Field_bit_width - Field Width in bits (8, 16, or 32) * * RETURN: Status * * DESCRIPTION: Apply the field update rule to a field write * ****************************************************************************/ static ACPI_STATUS acpi_aml_write_field_data_with_update_rule ( ACPI_OPERAND_OBJECT *obj_desc, u32 mask, u32 field_value, u32 this_field_byte_offset, u32 bit_granularity) { ACPI_STATUS status = AE_OK; u32 merged_value; u32 current_value; /* Start with the new bits */ merged_value = field_value; /* Decode the update rule */ switch (obj_desc->field.update_rule) { case UPDATE_PRESERVE: /* Check if update rule needs to be applied (not if mask is all ones) */ if (((1 << bit_granularity) -1) & ~mask) { /* * Read the current contents of the byte/word/dword containing * the field, and merge with the new field value. */ status = acpi_aml_read_field_data (obj_desc, this_field_byte_offset, bit_granularity, ¤t_value); merged_value |= (current_value & ~mask); } break; case UPDATE_WRITE_AS_ONES: /* Set positions outside the field to all ones */ merged_value |= ~mask; break; case UPDATE_WRITE_AS_ZEROS: /* Set positions outside the field to all zeros */ merged_value &= mask; break; default: status = AE_AML_OPERAND_VALUE; } /* Write the merged value */ if (ACPI_SUCCESS (status)) { status = acpi_aml_write_field_data (obj_desc, this_field_byte_offset, bit_granularity, merged_value); } return (status); } /***************************************************************************** * * FUNCTION: Acpi_aml_write_field * * PARAMETERS: *Obj_desc - Field to be set * Value - Value to store * Field_bit_width - Field Width in bits (8, 16, or 32) * * RETURN: Status * * DESCRIPTION: Store the value into the given field * ****************************************************************************/ ACPI_STATUS acpi_aml_write_field ( ACPI_OPERAND_OBJECT *obj_desc, void *buffer, u32 buffer_length, u32 byte_length, u32 datum_length, u32 bit_granularity, u32 byte_granularity) { ACPI_STATUS status; u32 this_field_byte_offset; u32 this_field_datum_offset; u32 mask; u32 merged_datum; u32 previous_raw_datum; u32 this_raw_datum; u32 field_value; u32 valid_field_bits; /* * Break the request into up to three parts: * non-aligned part at start, aligned part in middle, non-aligned part * at end --- Just like an I/O request --- */ this_field_byte_offset = 0; this_field_datum_offset= 0; /* Get a datum */ switch (byte_granularity) { case 1: previous_raw_datum = ((u8 *) buffer) [this_field_datum_offset]; break; case 2: MOVE_UNALIGNED16_TO_32 (&previous_raw_datum, &(((u16 *) buffer) [this_field_datum_offset])); break; case 4: MOVE_UNALIGNED32_TO_32 (&previous_raw_datum, &(((u32 *) buffer) [this_field_datum_offset])); break; default: status = AE_AML_OPERAND_VALUE; goto cleanup; } /* * Write a partial field datum if field does not begin on a datum boundary * * Construct Mask with 1 bits where the field is, 0 bits elsewhere * * 1) Bits above the field */ mask = (((u32)(-1)) << (u32)obj_desc->field.bit_offset); /* 2) Only the bottom 5 bits are valid for a shift operation. */ if ((obj_desc->field.bit_offset + obj_desc->field_unit.length) < 32) { /* Bits above the field */ mask &= (~(((u32)(-1)) << ((u32)obj_desc->field.bit_offset + (u32)obj_desc->field_unit.length))); } /* 3) Shift and mask the value into the field position */ field_value = (previous_raw_datum << obj_desc->field.bit_offset) & mask; status = acpi_aml_write_field_data_with_update_rule (obj_desc, mask, field_value, this_field_byte_offset, bit_granularity); if (ACPI_FAILURE (status)) { goto cleanup; } /* If the field fits within one datum, we are done. */ if ((datum_length == 1) && ((obj_desc->field.bit_offset + obj_desc->field_unit.length) <= (u16) bit_granularity)) { goto cleanup; } /* * We don't need to worry about the update rule for these data, because * all of the bits are part of the field. * * Can't write the last datum, however, because it might contain bits that * are not part of the field -- the update rule must be applied. */ while (this_field_datum_offset < (datum_length - 1)) { this_field_datum_offset++; /* Get the next raw datum, it contains bits of the current field datum... */ switch (byte_granularity) { case 1: this_raw_datum = ((u8 *) buffer) [this_field_datum_offset]; break; case 2: MOVE_UNALIGNED16_TO_32 (&this_raw_datum, &(((u16 *) buffer) [this_field_datum_offset])); break; case 4: MOVE_UNALIGNED32_TO_32 (&this_raw_datum, &(((u32 *) buffer) [this_field_datum_offset])); break; default: status = AE_AML_OPERAND_VALUE; goto cleanup; } /* * Put together bits of the two raw data to make a complete field * datum */ if (obj_desc->field.bit_offset != 0) { merged_datum = (previous_raw_datum >> (bit_granularity - obj_desc->field.bit_offset)) | (this_raw_datum << obj_desc->field.bit_offset); } else { merged_datum = this_raw_datum; } /* Now write the completed datum */ status = acpi_aml_write_field_data (obj_desc, this_field_byte_offset + byte_granularity, bit_granularity, merged_datum); if (ACPI_FAILURE (status)) { goto cleanup; } /* * Save the most recent datum since it contains bits of * the *next* field datum */ previous_raw_datum = this_raw_datum; this_field_byte_offset += byte_granularity; } /* while */ /* Write a partial field datum if field does not end on a datum boundary */ if ((obj_desc->field_unit.length + obj_desc->field_unit.bit_offset) % bit_granularity) { switch (byte_granularity) { case 1: this_raw_datum = ((u8 *) buffer) [this_field_datum_offset]; break; case 2: MOVE_UNALIGNED16_TO_32 (&this_raw_datum, &(((u16 *) buffer) [this_field_datum_offset])); break; case 4: MOVE_UNALIGNED32_TO_32 (&this_raw_datum, &(((u32 *) buffer) [this_field_datum_offset])); break; } /* Construct Mask with 1 bits where the field is, 0 bits elsewhere */ valid_field_bits = ((obj_desc->field_unit.length % bit_granularity) + obj_desc->field.bit_offset); mask = (((u32) 1 << valid_field_bits) - (u32) 1); /* Shift and mask the value into the field position */ field_value = (previous_raw_datum >> (bit_granularity - obj_desc->field.bit_offset)) & mask; status = acpi_aml_write_field_data_with_update_rule (obj_desc, mask, field_value, this_field_byte_offset + byte_granularity, bit_granularity); if (ACPI_FAILURE (status)) { goto cleanup; } } cleanup: return (status); }