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