1 // SPDX-License-Identifier: GPL-2.0-only
2 //
3 // KUnit test for the Cirrus common amplifier library.
4 //
5 // Copyright (C) 2024 Cirrus Logic, Inc. and
6 // Cirrus Logic International Semiconductor Ltd.
7
8 #include <kunit/resource.h>
9 #include <kunit/test.h>
10 #include <kunit/test-bug.h>
11 #include <kunit/static_stub.h>
12 #include <linux/device/faux.h>
13 #include <linux/firmware/cirrus/cs_dsp.h>
14 #include <linux/firmware/cirrus/wmfw.h>
15 #include <linux/gpio/driver.h>
16 #include <linux/list.h>
17 #include <linux/module.h>
18 #include <linux/overflow.h>
19 #include <linux/platform_device.h>
20 #include <linux/random.h>
21 #include <sound/cs-amp-lib.h>
22
23 #define LENOVO_SPEAKER_ID_EFI_NAME L"SdwSpeaker"
24 #define LENOVO_SPEAKER_ID_EFI_GUID \
25 EFI_GUID(0x48df970e, 0xe27f, 0x460a, 0xb5, 0x86, 0x77, 0x19, 0x80, 0x1d, 0x92, 0x82)
26
27 #define HP_SPEAKER_ID_EFI_NAME L"HPSpeakerID"
28 #define HP_SPEAKER_ID_EFI_GUID \
29 EFI_GUID(0xc49593a4, 0xd099, 0x419b, 0xa2, 0xc3, 0x67, 0xe9, 0x80, 0xe6, 0x1d, 0x1e)
30
31 KUNIT_DEFINE_ACTION_WRAPPER(faux_device_destroy_wrapper, faux_device_destroy,
32 struct faux_device *)
33
34 struct cs_amp_lib_test_priv {
35 struct faux_device *amp_dev;
36
37 struct cirrus_amp_efi_data *cal_blob;
38 struct list_head ctl_write_list;
39 };
40
41 struct cs_amp_lib_test_ctl_write_entry {
42 struct list_head list;
43 unsigned int value;
44 char name[16];
45 };
46
47 struct cs_amp_lib_test_param {
48 int num_amps;
49 int amp_index;
50 };
51
cs_amp_lib_test_init_dummy_cal_blob(struct kunit * test,int num_amps)52 static void cs_amp_lib_test_init_dummy_cal_blob(struct kunit *test, int num_amps)
53 {
54 struct cs_amp_lib_test_priv *priv = test->priv;
55 unsigned int blob_size;
56 int i;
57
58 blob_size = struct_size(priv->cal_blob, data, num_amps);
59
60 priv->cal_blob = kunit_kzalloc(test, blob_size, GFP_KERNEL);
61 KUNIT_ASSERT_NOT_NULL(test, priv->cal_blob);
62
63 priv->cal_blob->size = blob_size;
64 priv->cal_blob->count = num_amps;
65
66 get_random_bytes(priv->cal_blob->data, flex_array_size(priv->cal_blob, data, num_amps));
67
68 /* Ensure all timestamps are non-zero to mark the entry valid. */
69 for (i = 0; i < num_amps; i++)
70 priv->cal_blob->data[i].calTime[0] |= 1;
71
72 /* Ensure that all UIDs are non-zero and unique. */
73 for (i = 0; i < num_amps; i++)
74 *(u8 *)&priv->cal_blob->data[i].calTarget[0] = i + 1;
75 }
76
cs_amp_lib_test_get_target_uid(struct kunit * test)77 static u64 cs_amp_lib_test_get_target_uid(struct kunit *test)
78 {
79 struct cs_amp_lib_test_priv *priv = test->priv;
80 const struct cs_amp_lib_test_param *param = test->param_value;
81 u64 uid;
82
83 uid = priv->cal_blob->data[param->amp_index].calTarget[1];
84 uid <<= 32;
85 uid |= priv->cal_blob->data[param->amp_index].calTarget[0];
86
87 return uid;
88 }
89
90 /* Redirected get_efi_variable to simulate that the file is too short */
cs_amp_lib_test_get_efi_variable_nohead(efi_char16_t * name,efi_guid_t * guid,unsigned long * size,void * buf)91 static efi_status_t cs_amp_lib_test_get_efi_variable_nohead(efi_char16_t *name,
92 efi_guid_t *guid,
93 unsigned long *size,
94 void *buf)
95 {
96 if (!buf) {
97 *size = offsetof(struct cirrus_amp_efi_data, data) - 1;
98 return EFI_BUFFER_TOO_SMALL;
99 }
100
101 return EFI_NOT_FOUND;
102 }
103
104 /* Should return -EOVERFLOW if the header is larger than the EFI data */
cs_amp_lib_test_cal_data_too_short_test(struct kunit * test)105 static void cs_amp_lib_test_cal_data_too_short_test(struct kunit *test)
106 {
107 struct cs_amp_lib_test_priv *priv = test->priv;
108 struct cirrus_amp_cal_data result_data;
109 int ret;
110
111 /* Redirect calls to get EFI data */
112 kunit_activate_static_stub(test,
113 cs_amp_test_hooks->get_efi_variable,
114 cs_amp_lib_test_get_efi_variable_nohead);
115
116 ret = cs_amp_get_efi_calibration_data(&priv->amp_dev->dev, 0, 0, &result_data);
117 KUNIT_EXPECT_EQ(test, ret, -EOVERFLOW);
118
119 kunit_deactivate_static_stub(test, cs_amp_test_hooks->get_efi_variable);
120 }
121
122 /* Redirected get_efi_variable to simulate that the count is larger than the file */
cs_amp_lib_test_get_efi_variable_bad_count(efi_char16_t * name,efi_guid_t * guid,unsigned long * size,void * buf)123 static efi_status_t cs_amp_lib_test_get_efi_variable_bad_count(efi_char16_t *name,
124 efi_guid_t *guid,
125 unsigned long *size,
126 void *buf)
127 {
128 struct kunit *test = kunit_get_current_test();
129 struct cs_amp_lib_test_priv *priv = test->priv;
130
131 if (!buf) {
132 /*
133 * Return a size that is shorter than required for the
134 * declared number of entries.
135 */
136 *size = priv->cal_blob->size - 1;
137 return EFI_BUFFER_TOO_SMALL;
138 }
139
140 memcpy(buf, priv->cal_blob, priv->cal_blob->size - 1);
141
142 return EFI_SUCCESS;
143 }
144
145 /* Should return -EOVERFLOW if the entry count is larger than the EFI data */
cs_amp_lib_test_cal_count_too_big_test(struct kunit * test)146 static void cs_amp_lib_test_cal_count_too_big_test(struct kunit *test)
147 {
148 struct cs_amp_lib_test_priv *priv = test->priv;
149 struct cirrus_amp_cal_data result_data;
150 int ret;
151
152 cs_amp_lib_test_init_dummy_cal_blob(test, 8);
153
154 /* Redirect calls to get EFI data */
155 kunit_activate_static_stub(test,
156 cs_amp_test_hooks->get_efi_variable,
157 cs_amp_lib_test_get_efi_variable_bad_count);
158
159 ret = cs_amp_get_efi_calibration_data(&priv->amp_dev->dev, 0, 0, &result_data);
160 KUNIT_EXPECT_EQ(test, ret, -EOVERFLOW);
161
162 kunit_deactivate_static_stub(test, cs_amp_test_hooks->get_efi_variable);
163 }
164
165 /* Redirected get_efi_variable to simulate that the variable not found */
cs_amp_lib_test_get_efi_variable_none(efi_char16_t * name,efi_guid_t * guid,unsigned long * size,void * buf)166 static efi_status_t cs_amp_lib_test_get_efi_variable_none(efi_char16_t *name,
167 efi_guid_t *guid,
168 unsigned long *size,
169 void *buf)
170 {
171 return EFI_NOT_FOUND;
172 }
173
174 /* If EFI doesn't contain a cal data variable the result should be -ENOENT */
cs_amp_lib_test_no_cal_data_test(struct kunit * test)175 static void cs_amp_lib_test_no_cal_data_test(struct kunit *test)
176 {
177 struct cs_amp_lib_test_priv *priv = test->priv;
178 struct cirrus_amp_cal_data result_data;
179 int ret;
180
181 /* Redirect calls to get EFI data */
182 kunit_activate_static_stub(test,
183 cs_amp_test_hooks->get_efi_variable,
184 cs_amp_lib_test_get_efi_variable_none);
185
186 ret = cs_amp_get_efi_calibration_data(&priv->amp_dev->dev, 0, 0, &result_data);
187 KUNIT_EXPECT_EQ(test, ret, -ENOENT);
188
189 kunit_deactivate_static_stub(test, cs_amp_test_hooks->get_efi_variable);
190 }
191
192 /* Redirected get_efi_variable to simulate reading a cal data blob */
cs_amp_lib_test_get_efi_variable(efi_char16_t * name,efi_guid_t * guid,unsigned long * size,void * buf)193 static efi_status_t cs_amp_lib_test_get_efi_variable(efi_char16_t *name,
194 efi_guid_t *guid,
195 unsigned long *size,
196 void *buf)
197 {
198 static const efi_char16_t expected_name[] = L"CirrusSmartAmpCalibrationData";
199 static const efi_guid_t expected_guid =
200 EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3);
201 struct kunit *test = kunit_get_current_test();
202 struct cs_amp_lib_test_priv *priv = test->priv;
203
204 KUNIT_EXPECT_NOT_ERR_OR_NULL(test, name);
205 KUNIT_EXPECT_NOT_ERR_OR_NULL(test, guid);
206 KUNIT_EXPECT_NOT_ERR_OR_NULL(test, size);
207
208 if (memcmp(name, expected_name, sizeof(expected_name)) ||
209 efi_guidcmp(*guid, expected_guid))
210 return -EFI_NOT_FOUND;
211
212 if (!buf) {
213 *size = priv->cal_blob->size;
214 return EFI_BUFFER_TOO_SMALL;
215 }
216
217 KUNIT_ASSERT_GE_MSG(test, ksize(buf), priv->cal_blob->size, "Buffer to small");
218
219 memcpy(buf, priv->cal_blob, priv->cal_blob->size);
220
221 return EFI_SUCCESS;
222 }
223
cs_amp_lib_test_get_hp_cal_efi_variable(efi_char16_t * name,efi_guid_t * guid,unsigned long * size,void * buf)224 static efi_status_t cs_amp_lib_test_get_hp_cal_efi_variable(efi_char16_t *name,
225 efi_guid_t *guid,
226 unsigned long *size,
227 void *buf)
228 {
229 static const efi_char16_t expected_name[] = L"SmartAmpCalibrationData";
230 static const efi_guid_t expected_guid =
231 EFI_GUID(0x53559579, 0x8753, 0x4f5c, 0x91, 0x30, 0xe8, 0x2a, 0xcf, 0xb8, 0xd8, 0x93);
232 struct kunit *test = kunit_get_current_test();
233 struct cs_amp_lib_test_priv *priv = test->priv;
234
235 KUNIT_EXPECT_NOT_ERR_OR_NULL(test, name);
236 KUNIT_EXPECT_NOT_ERR_OR_NULL(test, guid);
237 KUNIT_EXPECT_NOT_ERR_OR_NULL(test, size);
238
239 if (memcmp(name, expected_name, sizeof(expected_name)) ||
240 efi_guidcmp(*guid, expected_guid))
241 return -EFI_NOT_FOUND;
242
243 if (!buf) {
244 *size = priv->cal_blob->size;
245 return EFI_BUFFER_TOO_SMALL;
246 }
247
248 KUNIT_ASSERT_GE_MSG(test, ksize(buf), priv->cal_blob->size, "Buffer to small");
249
250 memcpy(buf, priv->cal_blob, priv->cal_blob->size);
251
252 return EFI_SUCCESS;
253 }
254
255 /* Get cal data block from HP variable. */
cs_amp_lib_test_get_hp_efi_cal(struct kunit * test)256 static void cs_amp_lib_test_get_hp_efi_cal(struct kunit *test)
257 {
258 struct cs_amp_lib_test_priv *priv = test->priv;
259 struct cirrus_amp_cal_data result_data;
260 int ret;
261
262 cs_amp_lib_test_init_dummy_cal_blob(test, 2);
263
264 kunit_activate_static_stub(test,
265 cs_amp_test_hooks->get_efi_variable,
266 cs_amp_lib_test_get_hp_cal_efi_variable);
267
268 ret = cs_amp_get_efi_calibration_data(&priv->amp_dev->dev, 0, 0, &result_data);
269 KUNIT_EXPECT_EQ(test, ret, 0);
270
271 KUNIT_EXPECT_MEMEQ(test, &result_data, &priv->cal_blob->data[0], sizeof(result_data));
272 }
273
274 /* Get cal data block for a given amp, matched by target UID. */
cs_amp_lib_test_get_efi_cal_by_uid_test(struct kunit * test)275 static void cs_amp_lib_test_get_efi_cal_by_uid_test(struct kunit *test)
276 {
277 struct cs_amp_lib_test_priv *priv = test->priv;
278 const struct cs_amp_lib_test_param *param = test->param_value;
279 struct cirrus_amp_cal_data result_data;
280 u64 target_uid;
281 int ret;
282
283 cs_amp_lib_test_init_dummy_cal_blob(test, param->num_amps);
284
285 /* Redirect calls to get EFI data */
286 kunit_activate_static_stub(test,
287 cs_amp_test_hooks->get_efi_variable,
288 cs_amp_lib_test_get_efi_variable);
289
290 target_uid = cs_amp_lib_test_get_target_uid(test);
291 ret = cs_amp_get_efi_calibration_data(&priv->amp_dev->dev, target_uid, -1, &result_data);
292 KUNIT_EXPECT_EQ(test, ret, 0);
293
294 kunit_deactivate_static_stub(test, cs_amp_test_hooks->get_efi_variable);
295
296 KUNIT_EXPECT_EQ(test, result_data.calTarget[0], target_uid & 0xFFFFFFFFULL);
297 KUNIT_EXPECT_EQ(test, result_data.calTarget[1], target_uid >> 32);
298 KUNIT_EXPECT_EQ(test, result_data.calTime[0],
299 priv->cal_blob->data[param->amp_index].calTime[0]);
300 KUNIT_EXPECT_EQ(test, result_data.calTime[1],
301 priv->cal_blob->data[param->amp_index].calTime[1]);
302 KUNIT_EXPECT_EQ(test, result_data.calAmbient,
303 priv->cal_blob->data[param->amp_index].calAmbient);
304 KUNIT_EXPECT_EQ(test, result_data.calStatus,
305 priv->cal_blob->data[param->amp_index].calStatus);
306 KUNIT_EXPECT_EQ(test, result_data.calR,
307 priv->cal_blob->data[param->amp_index].calR);
308 }
309
310 /* Get cal data block for a given amp index without checking target UID. */
cs_amp_lib_test_get_efi_cal_by_index_unchecked_test(struct kunit * test)311 static void cs_amp_lib_test_get_efi_cal_by_index_unchecked_test(struct kunit *test)
312 {
313 struct cs_amp_lib_test_priv *priv = test->priv;
314 const struct cs_amp_lib_test_param *param = test->param_value;
315 struct cirrus_amp_cal_data result_data;
316 int ret;
317
318 cs_amp_lib_test_init_dummy_cal_blob(test, param->num_amps);
319
320 /* Redirect calls to get EFI data */
321 kunit_activate_static_stub(test,
322 cs_amp_test_hooks->get_efi_variable,
323 cs_amp_lib_test_get_efi_variable);
324
325 ret = cs_amp_get_efi_calibration_data(&priv->amp_dev->dev, 0,
326 param->amp_index, &result_data);
327 KUNIT_EXPECT_EQ(test, ret, 0);
328
329 kunit_deactivate_static_stub(test, cs_amp_test_hooks->get_efi_variable);
330
331 KUNIT_EXPECT_EQ(test, result_data.calTime[0],
332 priv->cal_blob->data[param->amp_index].calTime[0]);
333 KUNIT_EXPECT_EQ(test, result_data.calTime[1],
334 priv->cal_blob->data[param->amp_index].calTime[1]);
335 KUNIT_EXPECT_EQ(test, result_data.calAmbient,
336 priv->cal_blob->data[param->amp_index].calAmbient);
337 KUNIT_EXPECT_EQ(test, result_data.calStatus,
338 priv->cal_blob->data[param->amp_index].calStatus);
339 KUNIT_EXPECT_EQ(test, result_data.calR,
340 priv->cal_blob->data[param->amp_index].calR);
341 }
342
343 /* Get cal data block for a given amp index with checked target UID. */
cs_amp_lib_test_get_efi_cal_by_index_checked_test(struct kunit * test)344 static void cs_amp_lib_test_get_efi_cal_by_index_checked_test(struct kunit *test)
345 {
346 struct cs_amp_lib_test_priv *priv = test->priv;
347 const struct cs_amp_lib_test_param *param = test->param_value;
348 struct cirrus_amp_cal_data result_data;
349 u64 target_uid;
350 int ret;
351
352 cs_amp_lib_test_init_dummy_cal_blob(test, param->num_amps);
353
354 /* Redirect calls to get EFI data */
355 kunit_activate_static_stub(test,
356 cs_amp_test_hooks->get_efi_variable,
357 cs_amp_lib_test_get_efi_variable);
358
359 target_uid = cs_amp_lib_test_get_target_uid(test);
360 ret = cs_amp_get_efi_calibration_data(&priv->amp_dev->dev, target_uid,
361 param->amp_index, &result_data);
362 KUNIT_EXPECT_EQ(test, ret, 0);
363
364 kunit_deactivate_static_stub(test, cs_amp_test_hooks->get_efi_variable);
365
366 KUNIT_EXPECT_EQ(test, result_data.calTime[0],
367 priv->cal_blob->data[param->amp_index].calTime[0]);
368 KUNIT_EXPECT_EQ(test, result_data.calTime[1],
369 priv->cal_blob->data[param->amp_index].calTime[1]);
370 KUNIT_EXPECT_EQ(test, result_data.calAmbient,
371 priv->cal_blob->data[param->amp_index].calAmbient);
372 KUNIT_EXPECT_EQ(test, result_data.calStatus,
373 priv->cal_blob->data[param->amp_index].calStatus);
374 KUNIT_EXPECT_EQ(test, result_data.calR,
375 priv->cal_blob->data[param->amp_index].calR);
376 }
377
378 /*
379 * Get cal data block for a given amp index with checked target UID.
380 * The UID does not match so the result should be -ENOENT.
381 */
cs_amp_lib_test_get_efi_cal_by_index_uid_mismatch_test(struct kunit * test)382 static void cs_amp_lib_test_get_efi_cal_by_index_uid_mismatch_test(struct kunit *test)
383 {
384 struct cs_amp_lib_test_priv *priv = test->priv;
385 const struct cs_amp_lib_test_param *param = test->param_value;
386 struct cirrus_amp_cal_data result_data;
387 u64 target_uid;
388 int ret;
389
390 cs_amp_lib_test_init_dummy_cal_blob(test, param->num_amps);
391
392 /* Redirect calls to get EFI data */
393 kunit_activate_static_stub(test,
394 cs_amp_test_hooks->get_efi_variable,
395 cs_amp_lib_test_get_efi_variable);
396
397 /* Get a target UID that won't match the entry */
398 target_uid = ~cs_amp_lib_test_get_target_uid(test);
399 ret = cs_amp_get_efi_calibration_data(&priv->amp_dev->dev, target_uid,
400 param->amp_index, &result_data);
401 KUNIT_EXPECT_EQ(test, ret, -ENOENT);
402
403 kunit_deactivate_static_stub(test, cs_amp_test_hooks->get_efi_variable);
404 }
405
406 /*
407 * Get cal data block for a given amp, where the cal data does not
408 * specify calTarget so the lookup falls back to using the index
409 */
cs_amp_lib_test_get_efi_cal_by_index_fallback_test(struct kunit * test)410 static void cs_amp_lib_test_get_efi_cal_by_index_fallback_test(struct kunit *test)
411 {
412 struct cs_amp_lib_test_priv *priv = test->priv;
413 const struct cs_amp_lib_test_param *param = test->param_value;
414 struct cirrus_amp_cal_data result_data;
415 static const u64 bad_target_uid = 0xBADCA100BABABABAULL;
416 int i, ret;
417
418 cs_amp_lib_test_init_dummy_cal_blob(test, param->num_amps);
419
420 /* Make all the target values zero so they are ignored */
421 for (i = 0; i < priv->cal_blob->count; ++i) {
422 priv->cal_blob->data[i].calTarget[0] = 0;
423 priv->cal_blob->data[i].calTarget[1] = 0;
424 }
425
426 /* Redirect calls to get EFI data */
427 kunit_activate_static_stub(test,
428 cs_amp_test_hooks->get_efi_variable,
429 cs_amp_lib_test_get_efi_variable);
430
431 ret = cs_amp_get_efi_calibration_data(&priv->amp_dev->dev, bad_target_uid,
432 param->amp_index, &result_data);
433 KUNIT_EXPECT_EQ(test, ret, 0);
434
435 kunit_deactivate_static_stub(test, cs_amp_test_hooks->get_efi_variable);
436
437 KUNIT_EXPECT_EQ(test, result_data.calTime[0],
438 priv->cal_blob->data[param->amp_index].calTime[0]);
439 KUNIT_EXPECT_EQ(test, result_data.calTime[1],
440 priv->cal_blob->data[param->amp_index].calTime[1]);
441 KUNIT_EXPECT_EQ(test, result_data.calAmbient,
442 priv->cal_blob->data[param->amp_index].calAmbient);
443 KUNIT_EXPECT_EQ(test, result_data.calStatus,
444 priv->cal_blob->data[param->amp_index].calStatus);
445 KUNIT_EXPECT_EQ(test, result_data.calR,
446 priv->cal_blob->data[param->amp_index].calR);
447 }
448
449 /*
450 * If the target UID isn't present in the cal data, and there isn't an
451 * index to fall back do, the result should be -ENOENT.
452 */
cs_amp_lib_test_get_efi_cal_uid_not_found_noindex_test(struct kunit * test)453 static void cs_amp_lib_test_get_efi_cal_uid_not_found_noindex_test(struct kunit *test)
454 {
455 struct cs_amp_lib_test_priv *priv = test->priv;
456 struct cirrus_amp_cal_data result_data;
457 static const u64 bad_target_uid = 0xBADCA100BABABABAULL;
458 int i, ret;
459
460 cs_amp_lib_test_init_dummy_cal_blob(test, 8);
461
462 /* Make all the target values != bad_target_uid */
463 for (i = 0; i < priv->cal_blob->count; ++i) {
464 priv->cal_blob->data[i].calTarget[0] &= ~(bad_target_uid & 0xFFFFFFFFULL);
465 priv->cal_blob->data[i].calTarget[1] &= ~(bad_target_uid >> 32);
466 }
467
468 /* Redirect calls to get EFI data */
469 kunit_activate_static_stub(test,
470 cs_amp_test_hooks->get_efi_variable,
471 cs_amp_lib_test_get_efi_variable);
472
473 ret = cs_amp_get_efi_calibration_data(&priv->amp_dev->dev, bad_target_uid, -1,
474 &result_data);
475 KUNIT_EXPECT_EQ(test, ret, -ENOENT);
476
477 kunit_deactivate_static_stub(test, cs_amp_test_hooks->get_efi_variable);
478 }
479
480 /*
481 * If the target UID isn't present in the cal data, and the index is
482 * out of range, the result should be -ENOENT.
483 */
cs_amp_lib_test_get_efi_cal_uid_not_found_index_not_found_test(struct kunit * test)484 static void cs_amp_lib_test_get_efi_cal_uid_not_found_index_not_found_test(struct kunit *test)
485 {
486 struct cs_amp_lib_test_priv *priv = test->priv;
487 struct cirrus_amp_cal_data result_data;
488 static const u64 bad_target_uid = 0xBADCA100BABABABAULL;
489 int i, ret;
490
491 cs_amp_lib_test_init_dummy_cal_blob(test, 8);
492
493 /* Make all the target values != bad_target_uid */
494 for (i = 0; i < priv->cal_blob->count; ++i) {
495 priv->cal_blob->data[i].calTarget[0] &= ~(bad_target_uid & 0xFFFFFFFFULL);
496 priv->cal_blob->data[i].calTarget[1] &= ~(bad_target_uid >> 32);
497 }
498
499 /* Redirect calls to get EFI data */
500 kunit_activate_static_stub(test,
501 cs_amp_test_hooks->get_efi_variable,
502 cs_amp_lib_test_get_efi_variable);
503
504 ret = cs_amp_get_efi_calibration_data(&priv->amp_dev->dev, bad_target_uid, 99,
505 &result_data);
506 KUNIT_EXPECT_EQ(test, ret, -ENOENT);
507
508 kunit_deactivate_static_stub(test, cs_amp_test_hooks->get_efi_variable);
509 }
510
511 /*
512 * If the target UID isn't given, and the index is out of range, the
513 * result should be -ENOENT.
514 */
cs_amp_lib_test_get_efi_cal_no_uid_index_not_found_test(struct kunit * test)515 static void cs_amp_lib_test_get_efi_cal_no_uid_index_not_found_test(struct kunit *test)
516 {
517 struct cs_amp_lib_test_priv *priv = test->priv;
518 struct cirrus_amp_cal_data result_data;
519 int ret;
520
521 cs_amp_lib_test_init_dummy_cal_blob(test, 8);
522
523 /* Redirect calls to get EFI data */
524 kunit_activate_static_stub(test,
525 cs_amp_test_hooks->get_efi_variable,
526 cs_amp_lib_test_get_efi_variable);
527
528 ret = cs_amp_get_efi_calibration_data(&priv->amp_dev->dev, 0, 99, &result_data);
529 KUNIT_EXPECT_EQ(test, ret, -ENOENT);
530
531 kunit_deactivate_static_stub(test, cs_amp_test_hooks->get_efi_variable);
532 }
533
534 /* If neither the target UID or the index is given the result should be -ENOENT. */
cs_amp_lib_test_get_efi_cal_no_uid_no_index_test(struct kunit * test)535 static void cs_amp_lib_test_get_efi_cal_no_uid_no_index_test(struct kunit *test)
536 {
537 struct cs_amp_lib_test_priv *priv = test->priv;
538 struct cirrus_amp_cal_data result_data;
539 int ret;
540
541 cs_amp_lib_test_init_dummy_cal_blob(test, 8);
542
543 /* Redirect calls to get EFI data */
544 kunit_activate_static_stub(test,
545 cs_amp_test_hooks->get_efi_variable,
546 cs_amp_lib_test_get_efi_variable);
547
548 ret = cs_amp_get_efi_calibration_data(&priv->amp_dev->dev, 0, -1, &result_data);
549 KUNIT_EXPECT_EQ(test, ret, -ENOENT);
550
551 kunit_deactivate_static_stub(test, cs_amp_test_hooks->get_efi_variable);
552 }
553
554 /*
555 * If the UID is passed as 0 this must not match an entry with an
556 * unpopulated calTarget
557 */
cs_amp_lib_test_get_efi_cal_zero_not_matched_test(struct kunit * test)558 static void cs_amp_lib_test_get_efi_cal_zero_not_matched_test(struct kunit *test)
559 {
560 struct cs_amp_lib_test_priv *priv = test->priv;
561 struct cirrus_amp_cal_data result_data;
562 int i, ret;
563
564 cs_amp_lib_test_init_dummy_cal_blob(test, 8);
565
566 /* Make all the target values zero so they are ignored */
567 for (i = 0; i < priv->cal_blob->count; ++i) {
568 priv->cal_blob->data[i].calTarget[0] = 0;
569 priv->cal_blob->data[i].calTarget[1] = 0;
570 }
571
572 /* Redirect calls to get EFI data */
573 kunit_activate_static_stub(test,
574 cs_amp_test_hooks->get_efi_variable,
575 cs_amp_lib_test_get_efi_variable);
576
577 ret = cs_amp_get_efi_calibration_data(&priv->amp_dev->dev, 0, -1, &result_data);
578 KUNIT_EXPECT_EQ(test, ret, -ENOENT);
579
580 kunit_deactivate_static_stub(test, cs_amp_test_hooks->get_efi_variable);
581 }
582
583 /*
584 * If an entry has a timestamp of 0 it should be ignored even if it has
585 * a matching target UID.
586 */
cs_amp_lib_test_get_efi_cal_empty_entry_test(struct kunit * test)587 static void cs_amp_lib_test_get_efi_cal_empty_entry_test(struct kunit *test)
588 {
589 struct cs_amp_lib_test_priv *priv = test->priv;
590 struct cirrus_amp_cal_data result_data;
591 u64 uid;
592
593 cs_amp_lib_test_init_dummy_cal_blob(test, 8);
594
595 /* Mark the 3rd entry invalid by zeroing calTime */
596 priv->cal_blob->data[2].calTime[0] = 0;
597 priv->cal_blob->data[2].calTime[1] = 0;
598
599 /* Get the UID value of the 3rd entry */
600 uid = priv->cal_blob->data[2].calTarget[1];
601 uid <<= 32;
602 uid |= priv->cal_blob->data[2].calTarget[0];
603
604 /* Redirect calls to get EFI data */
605 kunit_activate_static_stub(test,
606 cs_amp_test_hooks->get_efi_variable,
607 cs_amp_lib_test_get_efi_variable);
608
609 /* Lookup by UID should not find it */
610 KUNIT_EXPECT_EQ(test,
611 cs_amp_get_efi_calibration_data(&priv->amp_dev->dev,
612 uid, -1,
613 &result_data),
614 -ENOENT);
615
616 /* Get by index should ignore it */
617 KUNIT_EXPECT_EQ(test,
618 cs_amp_get_efi_calibration_data(&priv->amp_dev->dev,
619 0, 2,
620 &result_data),
621 -ENOENT);
622
623 kunit_deactivate_static_stub(test, cs_amp_test_hooks->get_efi_variable);
624 }
625
626 static const struct cirrus_amp_cal_controls cs_amp_lib_test_calibration_controls = {
627 .alg_id = 0x9f210,
628 .mem_region = WMFW_ADSP2_YM,
629 .ambient = "CAL_AMBIENT",
630 .calr = "CAL_R",
631 .status = "CAL_STATUS",
632 .checksum = "CAL_CHECKSUM",
633 };
634
cs_amp_lib_test_write_cal_coeff(struct cs_dsp * dsp,const struct cirrus_amp_cal_controls * controls,const char * ctl_name,u32 val)635 static int cs_amp_lib_test_write_cal_coeff(struct cs_dsp *dsp,
636 const struct cirrus_amp_cal_controls *controls,
637 const char *ctl_name, u32 val)
638 {
639 struct kunit *test = kunit_get_current_test();
640 struct cs_amp_lib_test_priv *priv = test->priv;
641 struct cs_amp_lib_test_ctl_write_entry *entry;
642
643 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctl_name);
644 KUNIT_EXPECT_PTR_EQ(test, controls, &cs_amp_lib_test_calibration_controls);
645
646 entry = kunit_kzalloc(test, sizeof(*entry), GFP_KERNEL);
647 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, entry);
648
649 INIT_LIST_HEAD(&entry->list);
650 strscpy(entry->name, ctl_name, sizeof(entry->name));
651 entry->value = val;
652
653 list_add_tail(&entry->list, &priv->ctl_write_list);
654
655 return 0;
656 }
657
cs_amp_lib_test_write_cal_data_test(struct kunit * test)658 static void cs_amp_lib_test_write_cal_data_test(struct kunit *test)
659 {
660 struct cs_amp_lib_test_priv *priv = test->priv;
661 struct cs_amp_lib_test_ctl_write_entry *entry;
662 struct cirrus_amp_cal_data data;
663 struct cs_dsp *dsp;
664 int ret;
665
666 dsp = kunit_kzalloc(test, sizeof(*dsp), GFP_KERNEL);
667 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dsp);
668 dsp->dev = &priv->amp_dev->dev;
669
670 get_random_bytes(&data, sizeof(data));
671
672 /* Redirect calls to write firmware controls */
673 kunit_activate_static_stub(test,
674 cs_amp_test_hooks->write_cal_coeff,
675 cs_amp_lib_test_write_cal_coeff);
676
677 ret = cs_amp_write_cal_coeffs(dsp, &cs_amp_lib_test_calibration_controls, &data);
678 KUNIT_EXPECT_EQ(test, ret, 0);
679
680 kunit_deactivate_static_stub(test, cs_amp_test_hooks->write_cal_coeff);
681
682 KUNIT_EXPECT_EQ(test, list_count_nodes(&priv->ctl_write_list), 4);
683
684 /* Checksum control must be written last */
685 entry = list_last_entry(&priv->ctl_write_list, typeof(*entry), list);
686 KUNIT_EXPECT_STREQ(test, entry->name, cs_amp_lib_test_calibration_controls.checksum);
687 KUNIT_EXPECT_EQ(test, entry->value, data.calR + 1);
688 list_del(&entry->list);
689
690 entry = list_first_entry(&priv->ctl_write_list, typeof(*entry), list);
691 KUNIT_EXPECT_STREQ(test, entry->name, cs_amp_lib_test_calibration_controls.ambient);
692 KUNIT_EXPECT_EQ(test, entry->value, data.calAmbient);
693 list_del(&entry->list);
694
695 entry = list_first_entry(&priv->ctl_write_list, typeof(*entry), list);
696 KUNIT_EXPECT_STREQ(test, entry->name, cs_amp_lib_test_calibration_controls.calr);
697 KUNIT_EXPECT_EQ(test, entry->value, data.calR);
698 list_del(&entry->list);
699
700 entry = list_first_entry(&priv->ctl_write_list, typeof(*entry), list);
701 KUNIT_EXPECT_STREQ(test, entry->name, cs_amp_lib_test_calibration_controls.status);
702 KUNIT_EXPECT_EQ(test, entry->value, data.calStatus);
703 }
704
cs_amp_lib_test_spkid_lenovo_not_present(struct kunit * test)705 static void cs_amp_lib_test_spkid_lenovo_not_present(struct kunit *test)
706 {
707 struct cs_amp_lib_test_priv *priv = test->priv;
708 struct device *dev = &priv->amp_dev->dev;
709
710 kunit_activate_static_stub(test,
711 cs_amp_test_hooks->get_efi_variable,
712 cs_amp_lib_test_get_efi_variable_none);
713
714 KUNIT_EXPECT_EQ(test, -ENOENT, cs_amp_get_vendor_spkid(dev));
715 }
716
cs_amp_lib_test_get_efi_variable_lenovo_d0(efi_char16_t * name,efi_guid_t * guid,unsigned long * size,void * buf)717 static efi_status_t cs_amp_lib_test_get_efi_variable_lenovo_d0(efi_char16_t *name,
718 efi_guid_t *guid,
719 unsigned long *size,
720 void *buf)
721 {
722 struct kunit *test = kunit_get_current_test();
723
724 if (efi_guidcmp(*guid, LENOVO_SPEAKER_ID_EFI_GUID) ||
725 memcmp(name, LENOVO_SPEAKER_ID_EFI_NAME, sizeof(LENOVO_SPEAKER_ID_EFI_NAME)))
726 return EFI_NOT_FOUND;
727
728 KUNIT_ASSERT_EQ(test, *size, 1);
729 *size = 1;
730 *(u8 *)buf = 0xd0;
731
732 return EFI_SUCCESS;
733 }
734
cs_amp_lib_test_get_efi_variable_lenovo_d1(efi_char16_t * name,efi_guid_t * guid,unsigned long * size,void * buf)735 static efi_status_t cs_amp_lib_test_get_efi_variable_lenovo_d1(efi_char16_t *name,
736 efi_guid_t *guid,
737 unsigned long *size,
738 void *buf)
739 {
740 struct kunit *test = kunit_get_current_test();
741
742 if (efi_guidcmp(*guid, LENOVO_SPEAKER_ID_EFI_GUID) ||
743 memcmp(name, LENOVO_SPEAKER_ID_EFI_NAME, sizeof(LENOVO_SPEAKER_ID_EFI_NAME)))
744 return EFI_NOT_FOUND;
745
746 KUNIT_ASSERT_EQ(test, *size, 1);
747 *size = 1;
748 *(u8 *)buf = 0xd1;
749
750 return EFI_SUCCESS;
751 }
752
cs_amp_lib_test_get_efi_variable_lenovo_00(efi_char16_t * name,efi_guid_t * guid,unsigned long * size,void * buf)753 static efi_status_t cs_amp_lib_test_get_efi_variable_lenovo_00(efi_char16_t *name,
754 efi_guid_t *guid,
755 unsigned long *size,
756 void *buf)
757 {
758 struct kunit *test = kunit_get_current_test();
759
760 KUNIT_ASSERT_EQ(test, 0, efi_guidcmp(*guid, LENOVO_SPEAKER_ID_EFI_GUID));
761 KUNIT_ASSERT_EQ(test, *size, 1);
762 *size = 1;
763 *(u8 *)buf = 0;
764
765 return EFI_SUCCESS;
766 }
767
cs_amp_lib_test_spkid_lenovo_d0(struct kunit * test)768 static void cs_amp_lib_test_spkid_lenovo_d0(struct kunit *test)
769 {
770 struct cs_amp_lib_test_priv *priv = test->priv;
771 struct device *dev = &priv->amp_dev->dev;
772
773 kunit_activate_static_stub(test,
774 cs_amp_test_hooks->get_efi_variable,
775 cs_amp_lib_test_get_efi_variable_lenovo_d0);
776
777 KUNIT_EXPECT_EQ(test, 0, cs_amp_get_vendor_spkid(dev));
778 }
779
cs_amp_lib_test_spkid_lenovo_d1(struct kunit * test)780 static void cs_amp_lib_test_spkid_lenovo_d1(struct kunit *test)
781 {
782 struct cs_amp_lib_test_priv *priv = test->priv;
783 struct device *dev = &priv->amp_dev->dev;
784
785 kunit_activate_static_stub(test,
786 cs_amp_test_hooks->get_efi_variable,
787 cs_amp_lib_test_get_efi_variable_lenovo_d1);
788
789 KUNIT_EXPECT_EQ(test, 1, cs_amp_get_vendor_spkid(dev));
790 }
791
cs_amp_lib_test_spkid_lenovo_illegal(struct kunit * test)792 static void cs_amp_lib_test_spkid_lenovo_illegal(struct kunit *test)
793 {
794 struct cs_amp_lib_test_priv *priv = test->priv;
795 struct device *dev = &priv->amp_dev->dev;
796
797 kunit_activate_static_stub(test,
798 cs_amp_test_hooks->get_efi_variable,
799 cs_amp_lib_test_get_efi_variable_lenovo_00);
800
801 KUNIT_EXPECT_LT(test, cs_amp_get_vendor_spkid(dev), 0);
802 }
803
cs_amp_lib_test_get_efi_variable_buf_too_small(efi_char16_t * name,efi_guid_t * guid,unsigned long * size,void * buf)804 static efi_status_t cs_amp_lib_test_get_efi_variable_buf_too_small(efi_char16_t *name,
805 efi_guid_t *guid,
806 unsigned long *size,
807 void *buf)
808 {
809 return EFI_BUFFER_TOO_SMALL;
810 }
811
cs_amp_lib_test_spkid_lenovo_oversize(struct kunit * test)812 static void cs_amp_lib_test_spkid_lenovo_oversize(struct kunit *test)
813 {
814 struct cs_amp_lib_test_priv *priv = test->priv;
815 struct device *dev = &priv->amp_dev->dev;
816
817 kunit_activate_static_stub(test,
818 cs_amp_test_hooks->get_efi_variable,
819 cs_amp_lib_test_get_efi_variable_buf_too_small);
820
821 KUNIT_EXPECT_LT(test, cs_amp_get_vendor_spkid(dev), 0);
822 }
823
cs_amp_lib_test_get_efi_variable_hp_30(efi_char16_t * name,efi_guid_t * guid,unsigned long * size,void * buf)824 static efi_status_t cs_amp_lib_test_get_efi_variable_hp_30(efi_char16_t *name,
825 efi_guid_t *guid,
826 unsigned long *size,
827 void *buf)
828 {
829 struct kunit *test = kunit_get_current_test();
830
831 if (efi_guidcmp(*guid, HP_SPEAKER_ID_EFI_GUID) ||
832 memcmp(name, HP_SPEAKER_ID_EFI_NAME, sizeof(HP_SPEAKER_ID_EFI_NAME)))
833 return EFI_NOT_FOUND;
834
835 KUNIT_ASSERT_EQ(test, *size, 1);
836 *size = 1;
837 *(u8 *)buf = 0x30;
838
839 return EFI_SUCCESS;
840 }
841
cs_amp_lib_test_get_efi_variable_hp_31(efi_char16_t * name,efi_guid_t * guid,unsigned long * size,void * buf)842 static efi_status_t cs_amp_lib_test_get_efi_variable_hp_31(efi_char16_t *name,
843 efi_guid_t *guid,
844 unsigned long *size,
845 void *buf)
846 {
847 struct kunit *test = kunit_get_current_test();
848
849 if (efi_guidcmp(*guid, HP_SPEAKER_ID_EFI_GUID) ||
850 memcmp(name, HP_SPEAKER_ID_EFI_NAME, sizeof(HP_SPEAKER_ID_EFI_NAME)))
851 return EFI_NOT_FOUND;
852
853 KUNIT_ASSERT_EQ(test, *size, 1);
854 *size = 1;
855 *(u8 *)buf = 0x31;
856
857 return EFI_SUCCESS;
858 }
859
cs_amp_lib_test_spkid_hp_30(struct kunit * test)860 static void cs_amp_lib_test_spkid_hp_30(struct kunit *test)
861 {
862 struct cs_amp_lib_test_priv *priv = test->priv;
863 struct device *dev = &priv->amp_dev->dev;
864
865 kunit_activate_static_stub(test,
866 cs_amp_test_hooks->get_efi_variable,
867 cs_amp_lib_test_get_efi_variable_hp_30);
868
869 KUNIT_EXPECT_EQ(test, 0, cs_amp_get_vendor_spkid(dev));
870 }
871
cs_amp_lib_test_spkid_hp_31(struct kunit * test)872 static void cs_amp_lib_test_spkid_hp_31(struct kunit *test)
873 {
874 struct cs_amp_lib_test_priv *priv = test->priv;
875 struct device *dev = &priv->amp_dev->dev;
876
877 kunit_activate_static_stub(test,
878 cs_amp_test_hooks->get_efi_variable,
879 cs_amp_lib_test_get_efi_variable_hp_31);
880
881 KUNIT_EXPECT_EQ(test, 1, cs_amp_get_vendor_spkid(dev));
882 }
883
cs_amp_lib_test_case_init(struct kunit * test)884 static int cs_amp_lib_test_case_init(struct kunit *test)
885 {
886 struct cs_amp_lib_test_priv *priv;
887
888 KUNIT_ASSERT_NOT_NULL(test, cs_amp_test_hooks);
889
890 priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
891 if (!priv)
892 return -ENOMEM;
893
894 test->priv = priv;
895 INIT_LIST_HEAD(&priv->ctl_write_list);
896
897 /* Create dummy amp driver dev */
898 priv->amp_dev = faux_device_create("cs_amp_lib_test_drv", NULL, NULL);
899 KUNIT_ASSERT_NOT_NULL(test, priv->amp_dev);
900 KUNIT_ASSERT_EQ(test, 0,
901 kunit_add_action_or_reset(test,
902 faux_device_destroy_wrapper,
903 priv->amp_dev));
904
905 return 0;
906 }
907
908 static const struct cs_amp_lib_test_param cs_amp_lib_test_get_cal_param_cases[] = {
909 { .num_amps = 2, .amp_index = 0 },
910 { .num_amps = 2, .amp_index = 1 },
911
912 { .num_amps = 3, .amp_index = 0 },
913 { .num_amps = 3, .amp_index = 1 },
914 { .num_amps = 3, .amp_index = 2 },
915
916 { .num_amps = 4, .amp_index = 0 },
917 { .num_amps = 4, .amp_index = 1 },
918 { .num_amps = 4, .amp_index = 2 },
919 { .num_amps = 4, .amp_index = 3 },
920
921 { .num_amps = 5, .amp_index = 0 },
922 { .num_amps = 5, .amp_index = 1 },
923 { .num_amps = 5, .amp_index = 2 },
924 { .num_amps = 5, .amp_index = 3 },
925 { .num_amps = 5, .amp_index = 4 },
926
927 { .num_amps = 6, .amp_index = 0 },
928 { .num_amps = 6, .amp_index = 1 },
929 { .num_amps = 6, .amp_index = 2 },
930 { .num_amps = 6, .amp_index = 3 },
931 { .num_amps = 6, .amp_index = 4 },
932 { .num_amps = 6, .amp_index = 5 },
933
934 { .num_amps = 8, .amp_index = 0 },
935 { .num_amps = 8, .amp_index = 1 },
936 { .num_amps = 8, .amp_index = 2 },
937 { .num_amps = 8, .amp_index = 3 },
938 { .num_amps = 8, .amp_index = 4 },
939 { .num_amps = 8, .amp_index = 5 },
940 { .num_amps = 8, .amp_index = 6 },
941 { .num_amps = 8, .amp_index = 7 },
942 };
943
cs_amp_lib_test_get_cal_param_desc(const struct cs_amp_lib_test_param * param,char * desc)944 static void cs_amp_lib_test_get_cal_param_desc(const struct cs_amp_lib_test_param *param,
945 char *desc)
946 {
947 snprintf(desc, KUNIT_PARAM_DESC_SIZE, "num_amps:%d amp_index:%d",
948 param->num_amps, param->amp_index);
949 }
950
951 KUNIT_ARRAY_PARAM(cs_amp_lib_test_get_cal, cs_amp_lib_test_get_cal_param_cases,
952 cs_amp_lib_test_get_cal_param_desc);
953
954 static struct kunit_case cs_amp_lib_test_cases[] = {
955 /* Tests for getting calibration data from EFI */
956 KUNIT_CASE(cs_amp_lib_test_cal_data_too_short_test),
957 KUNIT_CASE(cs_amp_lib_test_cal_count_too_big_test),
958 KUNIT_CASE(cs_amp_lib_test_no_cal_data_test),
959 KUNIT_CASE(cs_amp_lib_test_get_efi_cal_uid_not_found_noindex_test),
960 KUNIT_CASE(cs_amp_lib_test_get_efi_cal_uid_not_found_index_not_found_test),
961 KUNIT_CASE(cs_amp_lib_test_get_efi_cal_no_uid_index_not_found_test),
962 KUNIT_CASE(cs_amp_lib_test_get_efi_cal_no_uid_no_index_test),
963 KUNIT_CASE(cs_amp_lib_test_get_efi_cal_zero_not_matched_test),
964 KUNIT_CASE(cs_amp_lib_test_get_hp_efi_cal),
965 KUNIT_CASE_PARAM(cs_amp_lib_test_get_efi_cal_by_uid_test,
966 cs_amp_lib_test_get_cal_gen_params),
967 KUNIT_CASE_PARAM(cs_amp_lib_test_get_efi_cal_by_index_unchecked_test,
968 cs_amp_lib_test_get_cal_gen_params),
969 KUNIT_CASE_PARAM(cs_amp_lib_test_get_efi_cal_by_index_checked_test,
970 cs_amp_lib_test_get_cal_gen_params),
971 KUNIT_CASE_PARAM(cs_amp_lib_test_get_efi_cal_by_index_uid_mismatch_test,
972 cs_amp_lib_test_get_cal_gen_params),
973 KUNIT_CASE_PARAM(cs_amp_lib_test_get_efi_cal_by_index_fallback_test,
974 cs_amp_lib_test_get_cal_gen_params),
975 KUNIT_CASE(cs_amp_lib_test_get_efi_cal_empty_entry_test),
976
977 /* Tests for writing calibration data */
978 KUNIT_CASE(cs_amp_lib_test_write_cal_data_test),
979
980 /* Test cases for speaker ID */
981 KUNIT_CASE(cs_amp_lib_test_spkid_lenovo_not_present),
982 KUNIT_CASE(cs_amp_lib_test_spkid_lenovo_d0),
983 KUNIT_CASE(cs_amp_lib_test_spkid_lenovo_d1),
984 KUNIT_CASE(cs_amp_lib_test_spkid_lenovo_illegal),
985 KUNIT_CASE(cs_amp_lib_test_spkid_lenovo_oversize),
986 KUNIT_CASE(cs_amp_lib_test_spkid_hp_30),
987 KUNIT_CASE(cs_amp_lib_test_spkid_hp_31),
988
989 { } /* terminator */
990 };
991
992 static struct kunit_suite cs_amp_lib_test_suite = {
993 .name = "snd-soc-cs-amp-lib-test",
994 .init = cs_amp_lib_test_case_init,
995 .test_cases = cs_amp_lib_test_cases,
996 };
997
998 kunit_test_suite(cs_amp_lib_test_suite);
999
1000 MODULE_IMPORT_NS("SND_SOC_CS_AMP_LIB");
1001 MODULE_DESCRIPTION("KUnit test for Cirrus Logic amplifier library");
1002 MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
1003 MODULE_LICENSE("GPL");
1004