1
2
3
4
5
6
7
8
9
10#include <linux/err.h>
11#include <linux/slab.h>
12#include <linux/string.h>
13#include <linux/uuid.h>
14#include <linux/thunderbolt.h>
15
16struct tb_property_entry {
17 u32 key_hi;
18 u32 key_lo;
19 u16 length;
20 u8 reserved;
21 u8 type;
22 u32 value;
23};
24
25struct tb_property_rootdir_entry {
26 u32 magic;
27 u32 length;
28 struct tb_property_entry entries[];
29};
30
31struct tb_property_dir_entry {
32 u32 uuid[4];
33 struct tb_property_entry entries[];
34};
35
36#define TB_PROPERTY_ROOTDIR_MAGIC 0x55584401
37
38static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,
39 size_t block_len, unsigned int dir_offset, size_t dir_len,
40 bool is_root);
41
42static inline void parse_dwdata(void *dst, const void *src, size_t dwords)
43{
44 be32_to_cpu_array(dst, src, dwords);
45}
46
47static inline void format_dwdata(void *dst, const void *src, size_t dwords)
48{
49 cpu_to_be32_array(dst, src, dwords);
50}
51
52static bool tb_property_entry_valid(const struct tb_property_entry *entry,
53 size_t block_len)
54{
55 switch (entry->type) {
56 case TB_PROPERTY_TYPE_DIRECTORY:
57 case TB_PROPERTY_TYPE_DATA:
58 case TB_PROPERTY_TYPE_TEXT:
59 if (entry->length > block_len)
60 return false;
61 if (entry->value + entry->length > block_len)
62 return false;
63 break;
64
65 case TB_PROPERTY_TYPE_VALUE:
66 if (entry->length != 1)
67 return false;
68 break;
69 }
70
71 return true;
72}
73
74static bool tb_property_key_valid(const char *key)
75{
76 return key && strlen(key) <= TB_PROPERTY_KEY_SIZE;
77}
78
79static struct tb_property *
80tb_property_alloc(const char *key, enum tb_property_type type)
81{
82 struct tb_property *property;
83
84 property = kzalloc(sizeof(*property), GFP_KERNEL);
85 if (!property)
86 return NULL;
87
88 strcpy(property->key, key);
89 property->type = type;
90 INIT_LIST_HEAD(&property->list);
91
92 return property;
93}
94
95static struct tb_property *tb_property_parse(const u32 *block, size_t block_len,
96 const struct tb_property_entry *entry)
97{
98 char key[TB_PROPERTY_KEY_SIZE + 1];
99 struct tb_property *property;
100 struct tb_property_dir *dir;
101
102 if (!tb_property_entry_valid(entry, block_len))
103 return NULL;
104
105 parse_dwdata(key, entry, 2);
106 key[TB_PROPERTY_KEY_SIZE] = '\0';
107
108 property = tb_property_alloc(key, entry->type);
109 if (!property)
110 return NULL;
111
112 property->length = entry->length;
113
114 switch (property->type) {
115 case TB_PROPERTY_TYPE_DIRECTORY:
116 dir = __tb_property_parse_dir(block, block_len, entry->value,
117 entry->length, false);
118 if (!dir) {
119 kfree(property);
120 return NULL;
121 }
122 property->value.dir = dir;
123 break;
124
125 case TB_PROPERTY_TYPE_DATA:
126 property->value.data = kcalloc(property->length, sizeof(u32),
127 GFP_KERNEL);
128 if (!property->value.data) {
129 kfree(property);
130 return NULL;
131 }
132 parse_dwdata(property->value.data, block + entry->value,
133 entry->length);
134 break;
135
136 case TB_PROPERTY_TYPE_TEXT:
137 property->value.text = kcalloc(property->length, sizeof(u32),
138 GFP_KERNEL);
139 if (!property->value.text) {
140 kfree(property);
141 return NULL;
142 }
143 parse_dwdata(property->value.text, block + entry->value,
144 entry->length);
145
146 property->value.text[property->length * 4 - 1] = '\0';
147 break;
148
149 case TB_PROPERTY_TYPE_VALUE:
150 property->value.immediate = entry->value;
151 break;
152
153 default:
154 property->type = TB_PROPERTY_TYPE_UNKNOWN;
155 break;
156 }
157
158 return property;
159}
160
161static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,
162 size_t block_len, unsigned int dir_offset, size_t dir_len, bool is_root)
163{
164 const struct tb_property_entry *entries;
165 size_t i, content_len, nentries;
166 unsigned int content_offset;
167 struct tb_property_dir *dir;
168
169 dir = kzalloc(sizeof(*dir), GFP_KERNEL);
170 if (!dir)
171 return NULL;
172
173 if (is_root) {
174 content_offset = dir_offset + 2;
175 content_len = dir_len;
176 } else {
177 dir->uuid = kmemdup(&block[dir_offset], sizeof(*dir->uuid),
178 GFP_KERNEL);
179 if (!dir->uuid) {
180 tb_property_free_dir(dir);
181 return NULL;
182 }
183 content_offset = dir_offset + 4;
184 content_len = dir_len - 4;
185 }
186
187 entries = (const struct tb_property_entry *)&block[content_offset];
188 nentries = content_len / (sizeof(*entries) / 4);
189
190 INIT_LIST_HEAD(&dir->properties);
191
192 for (i = 0; i < nentries; i++) {
193 struct tb_property *property;
194
195 property = tb_property_parse(block, block_len, &entries[i]);
196 if (!property) {
197 tb_property_free_dir(dir);
198 return NULL;
199 }
200
201 list_add_tail(&property->list, &dir->properties);
202 }
203
204 return dir;
205}
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220struct tb_property_dir *tb_property_parse_dir(const u32 *block,
221 size_t block_len)
222{
223 const struct tb_property_rootdir_entry *rootdir =
224 (const struct tb_property_rootdir_entry *)block;
225
226 if (rootdir->magic != TB_PROPERTY_ROOTDIR_MAGIC)
227 return NULL;
228 if (rootdir->length > block_len)
229 return NULL;
230
231 return __tb_property_parse_dir(block, block_len, 0, rootdir->length,
232 true);
233}
234
235
236
237
238
239
240
241
242struct tb_property_dir *tb_property_create_dir(const uuid_t *uuid)
243{
244 struct tb_property_dir *dir;
245
246 dir = kzalloc(sizeof(*dir), GFP_KERNEL);
247 if (!dir)
248 return NULL;
249
250 INIT_LIST_HEAD(&dir->properties);
251 if (uuid) {
252 dir->uuid = kmemdup(uuid, sizeof(*dir->uuid), GFP_KERNEL);
253 if (!dir->uuid) {
254 kfree(dir);
255 return NULL;
256 }
257 }
258
259 return dir;
260}
261EXPORT_SYMBOL_GPL(tb_property_create_dir);
262
263static void tb_property_free(struct tb_property *property)
264{
265 switch (property->type) {
266 case TB_PROPERTY_TYPE_DIRECTORY:
267 tb_property_free_dir(property->value.dir);
268 break;
269
270 case TB_PROPERTY_TYPE_DATA:
271 kfree(property->value.data);
272 break;
273
274 case TB_PROPERTY_TYPE_TEXT:
275 kfree(property->value.text);
276 break;
277
278 default:
279 break;
280 }
281
282 kfree(property);
283}
284
285
286
287
288
289
290
291
292
293void tb_property_free_dir(struct tb_property_dir *dir)
294{
295 struct tb_property *property, *tmp;
296
297 if (!dir)
298 return;
299
300 list_for_each_entry_safe(property, tmp, &dir->properties, list) {
301 list_del(&property->list);
302 tb_property_free(property);
303 }
304 kfree(dir->uuid);
305 kfree(dir);
306}
307EXPORT_SYMBOL_GPL(tb_property_free_dir);
308
309static size_t tb_property_dir_length(const struct tb_property_dir *dir,
310 bool recurse, size_t *data_len)
311{
312 const struct tb_property *property;
313 size_t len = 0;
314
315 if (dir->uuid)
316 len += sizeof(*dir->uuid) / 4;
317 else
318 len += sizeof(struct tb_property_rootdir_entry) / 4;
319
320 list_for_each_entry(property, &dir->properties, list) {
321 len += sizeof(struct tb_property_entry) / 4;
322
323 switch (property->type) {
324 case TB_PROPERTY_TYPE_DIRECTORY:
325 if (recurse) {
326 len += tb_property_dir_length(
327 property->value.dir, recurse, data_len);
328 }
329
330 if (data_len)
331 *data_len += 1;
332 break;
333
334 case TB_PROPERTY_TYPE_DATA:
335 case TB_PROPERTY_TYPE_TEXT:
336 if (data_len)
337 *data_len += property->length;
338 break;
339
340 default:
341 break;
342 }
343 }
344
345 return len;
346}
347
348static ssize_t __tb_property_format_dir(const struct tb_property_dir *dir,
349 u32 *block, unsigned int start_offset, size_t block_len)
350{
351 unsigned int data_offset, dir_end;
352 const struct tb_property *property;
353 struct tb_property_entry *entry;
354 size_t dir_len, data_len = 0;
355 int ret;
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402 dir_len = tb_property_dir_length(dir, false, &data_len);
403 data_offset = start_offset + dir_len;
404 dir_end = start_offset + data_len + dir_len;
405
406 if (data_offset > dir_end)
407 return -EINVAL;
408 if (dir_end > block_len)
409 return -EINVAL;
410
411
412 if (dir->uuid) {
413 struct tb_property_dir_entry *pe;
414
415 pe = (struct tb_property_dir_entry *)&block[start_offset];
416 memcpy(pe->uuid, dir->uuid, sizeof(pe->uuid));
417 entry = pe->entries;
418 } else {
419 struct tb_property_rootdir_entry *re;
420
421 re = (struct tb_property_rootdir_entry *)&block[start_offset];
422 re->magic = TB_PROPERTY_ROOTDIR_MAGIC;
423 re->length = dir_len - sizeof(*re) / 4;
424 entry = re->entries;
425 }
426
427 list_for_each_entry(property, &dir->properties, list) {
428 const struct tb_property_dir *child;
429
430 format_dwdata(entry, property->key, 2);
431 entry->type = property->type;
432
433 switch (property->type) {
434 case TB_PROPERTY_TYPE_DIRECTORY:
435 child = property->value.dir;
436 ret = __tb_property_format_dir(child, block, dir_end,
437 block_len);
438 if (ret < 0)
439 return ret;
440 entry->length = tb_property_dir_length(child, false,
441 NULL);
442 entry->value = dir_end;
443 dir_end = ret;
444 break;
445
446 case TB_PROPERTY_TYPE_DATA:
447 format_dwdata(&block[data_offset], property->value.data,
448 property->length);
449 entry->length = property->length;
450 entry->value = data_offset;
451 data_offset += entry->length;
452 break;
453
454 case TB_PROPERTY_TYPE_TEXT:
455 format_dwdata(&block[data_offset], property->value.text,
456 property->length);
457 entry->length = property->length;
458 entry->value = data_offset;
459 data_offset += entry->length;
460 break;
461
462 case TB_PROPERTY_TYPE_VALUE:
463 entry->length = property->length;
464 entry->value = property->value.immediate;
465 break;
466
467 default:
468 break;
469 }
470
471 entry++;
472 }
473
474 return dir_end;
475}
476
477
478
479
480
481
482
483
484
485
486
487
488ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block,
489 size_t block_len)
490{
491 ssize_t ret;
492
493 if (!block) {
494 size_t dir_len, data_len = 0;
495
496 dir_len = tb_property_dir_length(dir, true, &data_len);
497 return dir_len + data_len;
498 }
499
500 ret = __tb_property_format_dir(dir, block, 0, block_len);
501 return ret < 0 ? ret : 0;
502}
503
504
505
506
507
508
509
510
511
512struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir)
513{
514 struct tb_property *property, *p = NULL;
515 struct tb_property_dir *d;
516
517 if (!dir)
518 return NULL;
519
520 d = tb_property_create_dir(dir->uuid);
521 if (!d)
522 return NULL;
523
524 list_for_each_entry(property, &dir->properties, list) {
525 struct tb_property *p;
526
527 p = tb_property_alloc(property->key, property->type);
528 if (!p)
529 goto err_free;
530
531 p->length = property->length;
532
533 switch (property->type) {
534 case TB_PROPERTY_TYPE_DIRECTORY:
535 p->value.dir = tb_property_copy_dir(property->value.dir);
536 if (!p->value.dir)
537 goto err_free;
538 break;
539
540 case TB_PROPERTY_TYPE_DATA:
541 p->value.data = kmemdup(property->value.data,
542 property->length * 4,
543 GFP_KERNEL);
544 if (!p->value.data)
545 goto err_free;
546 break;
547
548 case TB_PROPERTY_TYPE_TEXT:
549 p->value.text = kzalloc(p->length * 4, GFP_KERNEL);
550 if (!p->value.text)
551 goto err_free;
552 strcpy(p->value.text, property->value.text);
553 break;
554
555 case TB_PROPERTY_TYPE_VALUE:
556 p->value.immediate = property->value.immediate;
557 break;
558
559 default:
560 break;
561 }
562
563 list_add_tail(&p->list, &d->properties);
564 }
565
566 return d;
567
568err_free:
569 kfree(p);
570 tb_property_free_dir(d);
571
572 return NULL;
573}
574
575
576
577
578
579
580
581int tb_property_add_immediate(struct tb_property_dir *parent, const char *key,
582 u32 value)
583{
584 struct tb_property *property;
585
586 if (!tb_property_key_valid(key))
587 return -EINVAL;
588
589 property = tb_property_alloc(key, TB_PROPERTY_TYPE_VALUE);
590 if (!property)
591 return -ENOMEM;
592
593 property->length = 1;
594 property->value.immediate = value;
595
596 list_add_tail(&property->list, &parent->properties);
597 return 0;
598}
599EXPORT_SYMBOL_GPL(tb_property_add_immediate);
600
601
602
603
604
605
606
607
608
609
610int tb_property_add_data(struct tb_property_dir *parent, const char *key,
611 const void *buf, size_t buflen)
612{
613
614 size_t size = round_up(buflen, 4);
615 struct tb_property *property;
616
617 if (!tb_property_key_valid(key))
618 return -EINVAL;
619
620 property = tb_property_alloc(key, TB_PROPERTY_TYPE_DATA);
621 if (!property)
622 return -ENOMEM;
623
624 property->length = size / 4;
625 property->value.data = kzalloc(size, GFP_KERNEL);
626 if (!property->value.data) {
627 kfree(property);
628 return -ENOMEM;
629 }
630
631 memcpy(property->value.data, buf, buflen);
632
633 list_add_tail(&property->list, &parent->properties);
634 return 0;
635}
636EXPORT_SYMBOL_GPL(tb_property_add_data);
637
638
639
640
641
642
643
644
645
646int tb_property_add_text(struct tb_property_dir *parent, const char *key,
647 const char *text)
648{
649
650 size_t size = round_up(strlen(text) + 1, 4);
651 struct tb_property *property;
652
653 if (!tb_property_key_valid(key))
654 return -EINVAL;
655
656 property = tb_property_alloc(key, TB_PROPERTY_TYPE_TEXT);
657 if (!property)
658 return -ENOMEM;
659
660 property->length = size / 4;
661 property->value.text = kzalloc(size, GFP_KERNEL);
662 if (!property->value.text) {
663 kfree(property);
664 return -ENOMEM;
665 }
666
667 strcpy(property->value.text, text);
668
669 list_add_tail(&property->list, &parent->properties);
670 return 0;
671}
672EXPORT_SYMBOL_GPL(tb_property_add_text);
673
674
675
676
677
678
679
680int tb_property_add_dir(struct tb_property_dir *parent, const char *key,
681 struct tb_property_dir *dir)
682{
683 struct tb_property *property;
684
685 if (!tb_property_key_valid(key))
686 return -EINVAL;
687
688 property = tb_property_alloc(key, TB_PROPERTY_TYPE_DIRECTORY);
689 if (!property)
690 return -ENOMEM;
691
692 property->value.dir = dir;
693
694 list_add_tail(&property->list, &parent->properties);
695 return 0;
696}
697EXPORT_SYMBOL_GPL(tb_property_add_dir);
698
699
700
701
702
703
704
705
706void tb_property_remove(struct tb_property *property)
707{
708 list_del(&property->list);
709 kfree(property);
710}
711EXPORT_SYMBOL_GPL(tb_property_remove);
712
713
714
715
716
717
718
719
720
721
722struct tb_property *tb_property_find(struct tb_property_dir *dir,
723 const char *key, enum tb_property_type type)
724{
725 struct tb_property *property;
726
727 list_for_each_entry(property, &dir->properties, list) {
728 if (property->type == type && !strcmp(property->key, key))
729 return property;
730 }
731
732 return NULL;
733}
734EXPORT_SYMBOL_GPL(tb_property_find);
735
736
737
738
739
740
741struct tb_property *tb_property_get_next(struct tb_property_dir *dir,
742 struct tb_property *prev)
743{
744 if (prev) {
745 if (list_is_last(&prev->list, &dir->properties))
746 return NULL;
747 return list_next_entry(prev, list);
748 }
749 return list_first_entry_or_null(&dir->properties, struct tb_property,
750 list);
751}
752EXPORT_SYMBOL_GPL(tb_property_get_next);
753