1 | // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 |
2 | /****************************************************************************** |
3 | * |
4 | * Module Name: exfield - AML execution - field_unit read/write |
5 | * |
6 | * Copyright (C) 2000 - 2023, Intel Corp. |
7 | * |
8 | *****************************************************************************/ |
9 | |
10 | #include <acpi/acpi.h> |
11 | #include "accommon.h" |
12 | #include "acdispat.h" |
13 | #include "acinterp.h" |
14 | #include "amlcode.h" |
15 | |
16 | #define _COMPONENT ACPI_EXECUTER |
17 | ACPI_MODULE_NAME("exfield" ) |
18 | |
19 | /* |
20 | * This table maps the various Attrib protocols to the byte transfer |
21 | * length. Used for the generic serial bus. |
22 | */ |
23 | #define ACPI_INVALID_PROTOCOL_ID 0x80 |
24 | #define ACPI_MAX_PROTOCOL_ID 0x0F |
25 | static const u8 acpi_protocol_lengths[] = { |
26 | ACPI_INVALID_PROTOCOL_ID, /* 0 - reserved */ |
27 | ACPI_INVALID_PROTOCOL_ID, /* 1 - reserved */ |
28 | 0x00, /* 2 - ATTRIB_QUICK */ |
29 | ACPI_INVALID_PROTOCOL_ID, /* 3 - reserved */ |
30 | 0x01, /* 4 - ATTRIB_SEND_RECEIVE */ |
31 | ACPI_INVALID_PROTOCOL_ID, /* 5 - reserved */ |
32 | 0x01, /* 6 - ATTRIB_BYTE */ |
33 | ACPI_INVALID_PROTOCOL_ID, /* 7 - reserved */ |
34 | 0x02, /* 8 - ATTRIB_WORD */ |
35 | ACPI_INVALID_PROTOCOL_ID, /* 9 - reserved */ |
36 | 0xFF, /* A - ATTRIB_BLOCK */ |
37 | 0xFF, /* B - ATTRIB_BYTES */ |
38 | 0x02, /* C - ATTRIB_PROCESS_CALL */ |
39 | 0xFF, /* D - ATTRIB_BLOCK_PROCESS_CALL */ |
40 | 0xFF, /* E - ATTRIB_RAW_BYTES */ |
41 | 0xFF /* F - ATTRIB_RAW_PROCESS_BYTES */ |
42 | }; |
43 | |
44 | #define PCC_MASTER_SUBSPACE 3 |
45 | |
46 | /* |
47 | * The following macros determine a given offset is a COMD field. |
48 | * According to the specification, generic subspaces (types 0-2) contains a |
49 | * 2-byte COMD field at offset 4 and master subspaces (type 3) contains a 4-byte |
50 | * COMD field starting at offset 12. |
51 | */ |
52 | #define GENERIC_SUBSPACE_COMMAND(a) (4 == a || a == 5) |
53 | #define MASTER_SUBSPACE_COMMAND(a) (12 <= a && a <= 15) |
54 | |
55 | /******************************************************************************* |
56 | * |
57 | * FUNCTION: acpi_ex_get_protocol_buffer_length |
58 | * |
59 | * PARAMETERS: protocol_id - The type of the protocol indicated by region |
60 | * field access attributes |
61 | * return_length - Where the protocol byte transfer length is |
62 | * returned |
63 | * |
64 | * RETURN: Status and decoded byte transfer length |
65 | * |
66 | * DESCRIPTION: This routine returns the length of the generic_serial_bus |
67 | * protocol bytes |
68 | * |
69 | ******************************************************************************/ |
70 | |
71 | acpi_status |
72 | acpi_ex_get_protocol_buffer_length(u32 protocol_id, u32 *return_length) |
73 | { |
74 | |
75 | if ((protocol_id > ACPI_MAX_PROTOCOL_ID) || |
76 | (acpi_protocol_lengths[protocol_id] == ACPI_INVALID_PROTOCOL_ID)) { |
77 | ACPI_ERROR((AE_INFO, |
78 | "Invalid Field/AccessAs protocol ID: 0x%4.4X" , |
79 | protocol_id)); |
80 | |
81 | return (AE_AML_PROTOCOL); |
82 | } |
83 | |
84 | *return_length = acpi_protocol_lengths[protocol_id]; |
85 | return (AE_OK); |
86 | } |
87 | |
88 | /******************************************************************************* |
89 | * |
90 | * FUNCTION: acpi_ex_read_data_from_field |
91 | * |
92 | * PARAMETERS: walk_state - Current execution state |
93 | * obj_desc - The named field |
94 | * ret_buffer_desc - Where the return data object is stored |
95 | * |
96 | * RETURN: Status |
97 | * |
98 | * DESCRIPTION: Read from a named field. Returns either an Integer or a |
99 | * Buffer, depending on the size of the field and whether if a |
100 | * field is created by the create_field() operator. |
101 | * |
102 | ******************************************************************************/ |
103 | |
104 | acpi_status |
105 | acpi_ex_read_data_from_field(struct acpi_walk_state *walk_state, |
106 | union acpi_operand_object *obj_desc, |
107 | union acpi_operand_object **ret_buffer_desc) |
108 | { |
109 | acpi_status status; |
110 | union acpi_operand_object *buffer_desc; |
111 | void *buffer; |
112 | u32 buffer_length; |
113 | |
114 | ACPI_FUNCTION_TRACE_PTR(ex_read_data_from_field, obj_desc); |
115 | |
116 | /* Parameter validation */ |
117 | |
118 | if (!obj_desc) { |
119 | return_ACPI_STATUS(AE_AML_NO_OPERAND); |
120 | } |
121 | if (!ret_buffer_desc) { |
122 | return_ACPI_STATUS(AE_BAD_PARAMETER); |
123 | } |
124 | |
125 | if (obj_desc->common.type == ACPI_TYPE_BUFFER_FIELD) { |
126 | /* |
127 | * If the buffer_field arguments have not been previously evaluated, |
128 | * evaluate them now and save the results. |
129 | */ |
130 | if (!(obj_desc->common.flags & AOPOBJ_DATA_VALID)) { |
131 | status = acpi_ds_get_buffer_field_arguments(obj_desc); |
132 | if (ACPI_FAILURE(status)) { |
133 | return_ACPI_STATUS(status); |
134 | } |
135 | } |
136 | } else if ((obj_desc->common.type == ACPI_TYPE_LOCAL_REGION_FIELD) && |
137 | (obj_desc->field.region_obj->region.space_id == |
138 | ACPI_ADR_SPACE_SMBUS |
139 | || obj_desc->field.region_obj->region.space_id == |
140 | ACPI_ADR_SPACE_GSBUS |
141 | || obj_desc->field.region_obj->region.space_id == |
142 | ACPI_ADR_SPACE_IPMI |
143 | || obj_desc->field.region_obj->region.space_id == |
144 | ACPI_ADR_SPACE_PLATFORM_RT |
145 | || obj_desc->field.region_obj->region.space_id == |
146 | ACPI_ADR_SPACE_FIXED_HARDWARE)) { |
147 | |
148 | /* SMBus, GSBus, IPMI serial */ |
149 | |
150 | status = acpi_ex_read_serial_bus(obj_desc, return_buffer: ret_buffer_desc); |
151 | return_ACPI_STATUS(status); |
152 | } |
153 | |
154 | /* |
155 | * Allocate a buffer for the contents of the field. |
156 | * |
157 | * If the field is larger than the current integer width, create |
158 | * a BUFFER to hold it. Otherwise, use an INTEGER. This allows |
159 | * the use of arithmetic operators on the returned value if the |
160 | * field size is equal or smaller than an Integer. |
161 | * |
162 | * However, all buffer fields created by create_field operator needs to |
163 | * remain as a buffer to match other AML interpreter implementations. |
164 | * |
165 | * Note: Field.length is in bits. |
166 | */ |
167 | buffer_length = |
168 | (acpi_size)ACPI_ROUND_BITS_UP_TO_BYTES(obj_desc->field.bit_length); |
169 | |
170 | if (buffer_length > acpi_gbl_integer_byte_width || |
171 | (obj_desc->common.type == ACPI_TYPE_BUFFER_FIELD && |
172 | obj_desc->buffer_field.is_create_field)) { |
173 | |
174 | /* Field is too large for an Integer, create a Buffer instead */ |
175 | |
176 | buffer_desc = acpi_ut_create_buffer_object(buffer_size: buffer_length); |
177 | if (!buffer_desc) { |
178 | return_ACPI_STATUS(AE_NO_MEMORY); |
179 | } |
180 | buffer = buffer_desc->buffer.pointer; |
181 | } else { |
182 | /* Field will fit within an Integer (normal case) */ |
183 | |
184 | buffer_desc = acpi_ut_create_integer_object(value: (u64) 0); |
185 | if (!buffer_desc) { |
186 | return_ACPI_STATUS(AE_NO_MEMORY); |
187 | } |
188 | |
189 | buffer_length = acpi_gbl_integer_byte_width; |
190 | buffer = &buffer_desc->integer.value; |
191 | } |
192 | |
193 | if ((obj_desc->common.type == ACPI_TYPE_LOCAL_REGION_FIELD) && |
194 | (obj_desc->field.region_obj->region.space_id == |
195 | ACPI_ADR_SPACE_GPIO)) { |
196 | |
197 | /* General Purpose I/O */ |
198 | |
199 | status = acpi_ex_read_gpio(obj_desc, buffer); |
200 | goto exit; |
201 | } else if ((obj_desc->common.type == ACPI_TYPE_LOCAL_REGION_FIELD) && |
202 | (obj_desc->field.region_obj->region.space_id == |
203 | ACPI_ADR_SPACE_PLATFORM_COMM)) { |
204 | /* |
205 | * Reading from a PCC field unit does not require the handler because |
206 | * it only requires reading from the internal_pcc_buffer. |
207 | */ |
208 | ACPI_DEBUG_PRINT((ACPI_DB_BFIELD, |
209 | "PCC FieldRead bits %u\n" , |
210 | obj_desc->field.bit_length)); |
211 | |
212 | memcpy(buffer, |
213 | obj_desc->field.region_obj->field.internal_pcc_buffer + |
214 | obj_desc->field.base_byte_offset, |
215 | (acpi_size)ACPI_ROUND_BITS_UP_TO_BYTES(obj_desc->field. |
216 | bit_length)); |
217 | |
218 | *ret_buffer_desc = buffer_desc; |
219 | return AE_OK; |
220 | } |
221 | |
222 | ACPI_DEBUG_PRINT((ACPI_DB_BFIELD, |
223 | "FieldRead [TO]: Obj %p, Type %X, Buf %p, ByteLen %X\n" , |
224 | obj_desc, obj_desc->common.type, buffer, |
225 | buffer_length)); |
226 | ACPI_DEBUG_PRINT((ACPI_DB_BFIELD, |
227 | "FieldRead [FROM]: BitLen %X, BitOff %X, ByteOff %X\n" , |
228 | obj_desc->common_field.bit_length, |
229 | obj_desc->common_field.start_field_bit_offset, |
230 | obj_desc->common_field.base_byte_offset)); |
231 | |
232 | /* Lock entire transaction if requested */ |
233 | |
234 | acpi_ex_acquire_global_lock(rule: obj_desc->common_field.field_flags); |
235 | |
236 | /* Read from the field */ |
237 | |
238 | status = acpi_ex_extract_from_field(obj_desc, buffer, buffer_length); |
239 | acpi_ex_release_global_lock(rule: obj_desc->common_field.field_flags); |
240 | |
241 | exit: |
242 | if (ACPI_FAILURE(status)) { |
243 | acpi_ut_remove_reference(object: buffer_desc); |
244 | } else { |
245 | *ret_buffer_desc = buffer_desc; |
246 | } |
247 | |
248 | return_ACPI_STATUS(status); |
249 | } |
250 | |
251 | /******************************************************************************* |
252 | * |
253 | * FUNCTION: acpi_ex_write_data_to_field |
254 | * |
255 | * PARAMETERS: source_desc - Contains data to write |
256 | * obj_desc - The named field |
257 | * result_desc - Where the return value is returned, if any |
258 | * |
259 | * RETURN: Status |
260 | * |
261 | * DESCRIPTION: Write to a named field |
262 | * |
263 | ******************************************************************************/ |
264 | |
265 | acpi_status |
266 | acpi_ex_write_data_to_field(union acpi_operand_object *source_desc, |
267 | union acpi_operand_object *obj_desc, |
268 | union acpi_operand_object **result_desc) |
269 | { |
270 | acpi_status status; |
271 | u32 buffer_length; |
272 | u32 data_length; |
273 | void *buffer; |
274 | |
275 | ACPI_FUNCTION_TRACE_PTR(ex_write_data_to_field, obj_desc); |
276 | |
277 | /* Parameter validation */ |
278 | |
279 | if (!source_desc || !obj_desc) { |
280 | return_ACPI_STATUS(AE_AML_NO_OPERAND); |
281 | } |
282 | |
283 | if (obj_desc->common.type == ACPI_TYPE_BUFFER_FIELD) { |
284 | /* |
285 | * If the buffer_field arguments have not been previously evaluated, |
286 | * evaluate them now and save the results. |
287 | */ |
288 | if (!(obj_desc->common.flags & AOPOBJ_DATA_VALID)) { |
289 | status = acpi_ds_get_buffer_field_arguments(obj_desc); |
290 | if (ACPI_FAILURE(status)) { |
291 | return_ACPI_STATUS(status); |
292 | } |
293 | } |
294 | } else if ((obj_desc->common.type == ACPI_TYPE_LOCAL_REGION_FIELD) && |
295 | (obj_desc->field.region_obj->region.space_id == |
296 | ACPI_ADR_SPACE_GPIO)) { |
297 | |
298 | /* General Purpose I/O */ |
299 | |
300 | status = acpi_ex_write_gpio(source_desc, obj_desc, return_buffer: result_desc); |
301 | return_ACPI_STATUS(status); |
302 | } else if ((obj_desc->common.type == ACPI_TYPE_LOCAL_REGION_FIELD) && |
303 | (obj_desc->field.region_obj->region.space_id == |
304 | ACPI_ADR_SPACE_SMBUS |
305 | || obj_desc->field.region_obj->region.space_id == |
306 | ACPI_ADR_SPACE_GSBUS |
307 | || obj_desc->field.region_obj->region.space_id == |
308 | ACPI_ADR_SPACE_IPMI |
309 | || obj_desc->field.region_obj->region.space_id == |
310 | ACPI_ADR_SPACE_PLATFORM_RT |
311 | || obj_desc->field.region_obj->region.space_id == |
312 | ACPI_ADR_SPACE_FIXED_HARDWARE)) { |
313 | |
314 | /* SMBus, GSBus, IPMI serial */ |
315 | |
316 | status = |
317 | acpi_ex_write_serial_bus(source_desc, obj_desc, |
318 | return_buffer: result_desc); |
319 | return_ACPI_STATUS(status); |
320 | } else if ((obj_desc->common.type == ACPI_TYPE_LOCAL_REGION_FIELD) && |
321 | (obj_desc->field.region_obj->region.space_id == |
322 | ACPI_ADR_SPACE_PLATFORM_COMM)) { |
323 | /* |
324 | * According to the spec a write to the COMD field will invoke the |
325 | * region handler. Otherwise, write to the pcc_internal buffer. This |
326 | * implementation will use the offsets specified rather than the name |
327 | * of the field. This is considered safer because some firmware tools |
328 | * are known to obfiscate named objects. |
329 | */ |
330 | data_length = |
331 | (acpi_size)ACPI_ROUND_BITS_UP_TO_BYTES(obj_desc->field. |
332 | bit_length); |
333 | memcpy(obj_desc->field.region_obj->field.internal_pcc_buffer + |
334 | obj_desc->field.base_byte_offset, |
335 | source_desc->buffer.pointer, data_length); |
336 | |
337 | if (MASTER_SUBSPACE_COMMAND(obj_desc->field.base_byte_offset)) { |
338 | |
339 | /* Perform the write */ |
340 | |
341 | ACPI_DEBUG_PRINT((ACPI_DB_BFIELD, |
342 | "PCC COMD field has been written. Invoking PCC handler now.\n" )); |
343 | |
344 | status = |
345 | acpi_ex_access_region(obj_desc, field_datum_byte_offset: 0, |
346 | value: (u64 *)obj_desc->field. |
347 | region_obj->field. |
348 | internal_pcc_buffer, |
349 | ACPI_WRITE); |
350 | return_ACPI_STATUS(status); |
351 | } |
352 | return (AE_OK); |
353 | } |
354 | |
355 | /* Get a pointer to the data to be written */ |
356 | |
357 | switch (source_desc->common.type) { |
358 | case ACPI_TYPE_INTEGER: |
359 | |
360 | buffer = &source_desc->integer.value; |
361 | buffer_length = sizeof(source_desc->integer.value); |
362 | break; |
363 | |
364 | case ACPI_TYPE_BUFFER: |
365 | |
366 | buffer = source_desc->buffer.pointer; |
367 | buffer_length = source_desc->buffer.length; |
368 | break; |
369 | |
370 | case ACPI_TYPE_STRING: |
371 | |
372 | buffer = source_desc->string.pointer; |
373 | buffer_length = source_desc->string.length; |
374 | break; |
375 | |
376 | default: |
377 | return_ACPI_STATUS(AE_AML_OPERAND_TYPE); |
378 | } |
379 | |
380 | ACPI_DEBUG_PRINT((ACPI_DB_BFIELD, |
381 | "FieldWrite [FROM]: Obj %p (%s:%X), Buf %p, ByteLen %X\n" , |
382 | source_desc, |
383 | acpi_ut_get_type_name(source_desc->common.type), |
384 | source_desc->common.type, buffer, buffer_length)); |
385 | |
386 | ACPI_DEBUG_PRINT((ACPI_DB_BFIELD, |
387 | "FieldWrite [TO]: Obj %p (%s:%X), BitLen %X, BitOff %X, ByteOff %X\n" , |
388 | obj_desc, |
389 | acpi_ut_get_type_name(obj_desc->common.type), |
390 | obj_desc->common.type, |
391 | obj_desc->common_field.bit_length, |
392 | obj_desc->common_field.start_field_bit_offset, |
393 | obj_desc->common_field.base_byte_offset)); |
394 | |
395 | /* Lock entire transaction if requested */ |
396 | |
397 | acpi_ex_acquire_global_lock(rule: obj_desc->common_field.field_flags); |
398 | |
399 | /* Write to the field */ |
400 | |
401 | status = acpi_ex_insert_into_field(obj_desc, buffer, buffer_length); |
402 | acpi_ex_release_global_lock(rule: obj_desc->common_field.field_flags); |
403 | return_ACPI_STATUS(status); |
404 | } |
405 | |