diff options
author | Joerg Roedel <joerg.roedel@amd.com> | 2009-06-09 04:50:57 -0400 |
---|---|---|
committer | Joerg Roedel <joerg.roedel@amd.com> | 2009-06-09 04:50:57 -0400 |
commit | d2dd01de9924ae24afeba5aa5bc2e08287701df6 (patch) | |
tree | 3021bf496579a48984666355b59df5e44b42dd32 /lib/dma-debug.c | |
parent | 367d04c4ec02dad34d80452e32e3370db7fb6fee (diff) | |
parent | 62a6f465f6572e1f28765c583c12753bb3e23715 (diff) |
Merge commit 'tip/core/iommu' into amd-iommu/fixes
Diffstat (limited to 'lib/dma-debug.c')
-rw-r--r-- | lib/dma-debug.c | 334 |
1 files changed, 308 insertions, 26 deletions
diff --git a/lib/dma-debug.c b/lib/dma-debug.c index cdd205d6bf7c..77053d9ef513 100644 --- a/lib/dma-debug.c +++ b/lib/dma-debug.c | |||
@@ -23,9 +23,11 @@ | |||
23 | #include <linux/dma-debug.h> | 23 | #include <linux/dma-debug.h> |
24 | #include <linux/spinlock.h> | 24 | #include <linux/spinlock.h> |
25 | #include <linux/debugfs.h> | 25 | #include <linux/debugfs.h> |
26 | #include <linux/uaccess.h> | ||
26 | #include <linux/device.h> | 27 | #include <linux/device.h> |
27 | #include <linux/types.h> | 28 | #include <linux/types.h> |
28 | #include <linux/sched.h> | 29 | #include <linux/sched.h> |
30 | #include <linux/ctype.h> | ||
29 | #include <linux/list.h> | 31 | #include <linux/list.h> |
30 | #include <linux/slab.h> | 32 | #include <linux/slab.h> |
31 | 33 | ||
@@ -98,6 +100,16 @@ static struct dentry *show_all_errors_dent __read_mostly; | |||
98 | static struct dentry *show_num_errors_dent __read_mostly; | 100 | static struct dentry *show_num_errors_dent __read_mostly; |
99 | static struct dentry *num_free_entries_dent __read_mostly; | 101 | static struct dentry *num_free_entries_dent __read_mostly; |
100 | static struct dentry *min_free_entries_dent __read_mostly; | 102 | static struct dentry *min_free_entries_dent __read_mostly; |
103 | static struct dentry *filter_dent __read_mostly; | ||
104 | |||
105 | /* per-driver filter related state */ | ||
106 | |||
107 | #define NAME_MAX_LEN 64 | ||
108 | |||
109 | static char current_driver_name[NAME_MAX_LEN] __read_mostly; | ||
110 | static struct device_driver *current_driver __read_mostly; | ||
111 | |||
112 | static DEFINE_RWLOCK(driver_name_lock); | ||
101 | 113 | ||
102 | static const char *type2name[4] = { "single", "page", | 114 | static const char *type2name[4] = { "single", "page", |
103 | "scather-gather", "coherent" }; | 115 | "scather-gather", "coherent" }; |
@@ -105,6 +117,11 @@ static const char *type2name[4] = { "single", "page", | |||
105 | static const char *dir2name[4] = { "DMA_BIDIRECTIONAL", "DMA_TO_DEVICE", | 117 | static const char *dir2name[4] = { "DMA_BIDIRECTIONAL", "DMA_TO_DEVICE", |
106 | "DMA_FROM_DEVICE", "DMA_NONE" }; | 118 | "DMA_FROM_DEVICE", "DMA_NONE" }; |
107 | 119 | ||
120 | /* little merge helper - remove it after the merge window */ | ||
121 | #ifndef BUS_NOTIFY_UNBOUND_DRIVER | ||
122 | #define BUS_NOTIFY_UNBOUND_DRIVER 0x0005 | ||
123 | #endif | ||
124 | |||
108 | /* | 125 | /* |
109 | * The access to some variables in this macro is racy. We can't use atomic_t | 126 | * The access to some variables in this macro is racy. We can't use atomic_t |
110 | * here because all these variables are exported to debugfs. Some of them even | 127 | * here because all these variables are exported to debugfs. Some of them even |
@@ -128,9 +145,48 @@ static inline void dump_entry_trace(struct dma_debug_entry *entry) | |||
128 | #endif | 145 | #endif |
129 | } | 146 | } |
130 | 147 | ||
148 | static bool driver_filter(struct device *dev) | ||
149 | { | ||
150 | /* driver filter off */ | ||
151 | if (likely(!current_driver_name[0])) | ||
152 | return true; | ||
153 | |||
154 | /* driver filter on and initialized */ | ||
155 | if (current_driver && dev->driver == current_driver) | ||
156 | return true; | ||
157 | |||
158 | /* driver filter on but not yet initialized */ | ||
159 | if (!current_driver && current_driver_name[0]) { | ||
160 | struct device_driver *drv = get_driver(dev->driver); | ||
161 | unsigned long flags; | ||
162 | bool ret = false; | ||
163 | |||
164 | if (!drv) | ||
165 | return false; | ||
166 | |||
167 | /* lock to protect against change of current_driver_name */ | ||
168 | read_lock_irqsave(&driver_name_lock, flags); | ||
169 | |||
170 | if (drv->name && | ||
171 | strncmp(current_driver_name, drv->name, | ||
172 | NAME_MAX_LEN-1) == 0) { | ||
173 | current_driver = drv; | ||
174 | ret = true; | ||
175 | } | ||
176 | |||
177 | read_unlock_irqrestore(&driver_name_lock, flags); | ||
178 | put_driver(drv); | ||
179 | |||
180 | return ret; | ||
181 | } | ||
182 | |||
183 | return false; | ||
184 | } | ||
185 | |||
131 | #define err_printk(dev, entry, format, arg...) do { \ | 186 | #define err_printk(dev, entry, format, arg...) do { \ |
132 | error_count += 1; \ | 187 | error_count += 1; \ |
133 | if (show_all_errors || show_num_errors > 0) { \ | 188 | if (driver_filter(dev) && \ |
189 | (show_all_errors || show_num_errors > 0)) { \ | ||
134 | WARN(1, "%s %s: " format, \ | 190 | WARN(1, "%s %s: " format, \ |
135 | dev_driver_string(dev), \ | 191 | dev_driver_string(dev), \ |
136 | dev_name(dev) , ## arg); \ | 192 | dev_name(dev) , ## arg); \ |
@@ -186,15 +242,50 @@ static void put_hash_bucket(struct hash_bucket *bucket, | |||
186 | static struct dma_debug_entry *hash_bucket_find(struct hash_bucket *bucket, | 242 | static struct dma_debug_entry *hash_bucket_find(struct hash_bucket *bucket, |
187 | struct dma_debug_entry *ref) | 243 | struct dma_debug_entry *ref) |
188 | { | 244 | { |
189 | struct dma_debug_entry *entry; | 245 | struct dma_debug_entry *entry, *ret = NULL; |
246 | int matches = 0, match_lvl, last_lvl = 0; | ||
190 | 247 | ||
191 | list_for_each_entry(entry, &bucket->list, list) { | 248 | list_for_each_entry(entry, &bucket->list, list) { |
192 | if ((entry->dev_addr == ref->dev_addr) && | 249 | if ((entry->dev_addr != ref->dev_addr) || |
193 | (entry->dev == ref->dev)) | 250 | (entry->dev != ref->dev)) |
251 | continue; | ||
252 | |||
253 | /* | ||
254 | * Some drivers map the same physical address multiple | ||
255 | * times. Without a hardware IOMMU this results in the | ||
256 | * same device addresses being put into the dma-debug | ||
257 | * hash multiple times too. This can result in false | ||
258 | * positives being reported. Therfore we implement a | ||
259 | * best-fit algorithm here which returns the entry from | ||
260 | * the hash which fits best to the reference value | ||
261 | * instead of the first-fit. | ||
262 | */ | ||
263 | matches += 1; | ||
264 | match_lvl = 0; | ||
265 | entry->size == ref->size ? ++match_lvl : match_lvl; | ||
266 | entry->type == ref->type ? ++match_lvl : match_lvl; | ||
267 | entry->direction == ref->direction ? ++match_lvl : match_lvl; | ||
268 | |||
269 | if (match_lvl == 3) { | ||
270 | /* perfect-fit - return the result */ | ||
194 | return entry; | 271 | return entry; |
272 | } else if (match_lvl > last_lvl) { | ||
273 | /* | ||
274 | * We found an entry that fits better then the | ||
275 | * previous one | ||
276 | */ | ||
277 | last_lvl = match_lvl; | ||
278 | ret = entry; | ||
279 | } | ||
195 | } | 280 | } |
196 | 281 | ||
197 | return NULL; | 282 | /* |
283 | * If we have multiple matches but no perfect-fit, just return | ||
284 | * NULL. | ||
285 | */ | ||
286 | ret = (matches == 1) ? ret : NULL; | ||
287 | |||
288 | return ret; | ||
198 | } | 289 | } |
199 | 290 | ||
200 | /* | 291 | /* |
@@ -407,6 +498,97 @@ out_err: | |||
407 | return -ENOMEM; | 498 | return -ENOMEM; |
408 | } | 499 | } |
409 | 500 | ||
501 | static ssize_t filter_read(struct file *file, char __user *user_buf, | ||
502 | size_t count, loff_t *ppos) | ||
503 | { | ||
504 | unsigned long flags; | ||
505 | char buf[NAME_MAX_LEN + 1]; | ||
506 | int len; | ||
507 | |||
508 | if (!current_driver_name[0]) | ||
509 | return 0; | ||
510 | |||
511 | /* | ||
512 | * We can't copy to userspace directly because current_driver_name can | ||
513 | * only be read under the driver_name_lock with irqs disabled. So | ||
514 | * create a temporary copy first. | ||
515 | */ | ||
516 | read_lock_irqsave(&driver_name_lock, flags); | ||
517 | len = scnprintf(buf, NAME_MAX_LEN + 1, "%s\n", current_driver_name); | ||
518 | read_unlock_irqrestore(&driver_name_lock, flags); | ||
519 | |||
520 | return simple_read_from_buffer(user_buf, count, ppos, buf, len); | ||
521 | } | ||
522 | |||
523 | static ssize_t filter_write(struct file *file, const char __user *userbuf, | ||
524 | size_t count, loff_t *ppos) | ||
525 | { | ||
526 | unsigned long flags; | ||
527 | char buf[NAME_MAX_LEN]; | ||
528 | size_t len = NAME_MAX_LEN - 1; | ||
529 | int i; | ||
530 | |||
531 | /* | ||
532 | * We can't copy from userspace directly. Access to | ||
533 | * current_driver_name is protected with a write_lock with irqs | ||
534 | * disabled. Since copy_from_user can fault and may sleep we | ||
535 | * need to copy to temporary buffer first | ||
536 | */ | ||
537 | len = min(count, len); | ||
538 | if (copy_from_user(buf, userbuf, len)) | ||
539 | return -EFAULT; | ||
540 | |||
541 | buf[len] = 0; | ||
542 | |||
543 | write_lock_irqsave(&driver_name_lock, flags); | ||
544 | |||
545 | /* Now handle the string we got from userspace very carefully. | ||
546 | * The rules are: | ||
547 | * - only use the first token we got | ||
548 | * - token delimiter is everything looking like a space | ||
549 | * character (' ', '\n', '\t' ...) | ||
550 | * | ||
551 | */ | ||
552 | if (!isalnum(buf[0])) { | ||
553 | /* | ||
554 | If the first character userspace gave us is not | ||
555 | * alphanumerical then assume the filter should be | ||
556 | * switched off. | ||
557 | */ | ||
558 | if (current_driver_name[0]) | ||
559 | printk(KERN_INFO "DMA-API: switching off dma-debug " | ||
560 | "driver filter\n"); | ||
561 | current_driver_name[0] = 0; | ||
562 | current_driver = NULL; | ||
563 | goto out_unlock; | ||
564 | } | ||
565 | |||
566 | /* | ||
567 | * Now parse out the first token and use it as the name for the | ||
568 | * driver to filter for. | ||
569 | */ | ||
570 | for (i = 0; i < NAME_MAX_LEN; ++i) { | ||
571 | current_driver_name[i] = buf[i]; | ||
572 | if (isspace(buf[i]) || buf[i] == ' ' || buf[i] == 0) | ||
573 | break; | ||
574 | } | ||
575 | current_driver_name[i] = 0; | ||
576 | current_driver = NULL; | ||
577 | |||
578 | printk(KERN_INFO "DMA-API: enable driver filter for driver [%s]\n", | ||
579 | current_driver_name); | ||
580 | |||
581 | out_unlock: | ||
582 | write_unlock_irqrestore(&driver_name_lock, flags); | ||
583 | |||
584 | return count; | ||
585 | } | ||
586 | |||
587 | const struct file_operations filter_fops = { | ||
588 | .read = filter_read, | ||
589 | .write = filter_write, | ||
590 | }; | ||
591 | |||
410 | static int dma_debug_fs_init(void) | 592 | static int dma_debug_fs_init(void) |
411 | { | 593 | { |
412 | dma_debug_dent = debugfs_create_dir("dma-api", NULL); | 594 | dma_debug_dent = debugfs_create_dir("dma-api", NULL); |
@@ -450,6 +632,11 @@ static int dma_debug_fs_init(void) | |||
450 | if (!min_free_entries_dent) | 632 | if (!min_free_entries_dent) |
451 | goto out_err; | 633 | goto out_err; |
452 | 634 | ||
635 | filter_dent = debugfs_create_file("driver_filter", 0644, | ||
636 | dma_debug_dent, NULL, &filter_fops); | ||
637 | if (!filter_dent) | ||
638 | goto out_err; | ||
639 | |||
453 | return 0; | 640 | return 0; |
454 | 641 | ||
455 | out_err: | 642 | out_err: |
@@ -458,9 +645,60 @@ out_err: | |||
458 | return -ENOMEM; | 645 | return -ENOMEM; |
459 | } | 646 | } |
460 | 647 | ||
648 | static int device_dma_allocations(struct device *dev) | ||
649 | { | ||
650 | struct dma_debug_entry *entry; | ||
651 | unsigned long flags; | ||
652 | int count = 0, i; | ||
653 | |||
654 | for (i = 0; i < HASH_SIZE; ++i) { | ||
655 | spin_lock_irqsave(&dma_entry_hash[i].lock, flags); | ||
656 | list_for_each_entry(entry, &dma_entry_hash[i].list, list) { | ||
657 | if (entry->dev == dev) | ||
658 | count += 1; | ||
659 | } | ||
660 | spin_unlock_irqrestore(&dma_entry_hash[i].lock, flags); | ||
661 | } | ||
662 | |||
663 | return count; | ||
664 | } | ||
665 | |||
666 | static int dma_debug_device_change(struct notifier_block *nb, | ||
667 | unsigned long action, void *data) | ||
668 | { | ||
669 | struct device *dev = data; | ||
670 | int count; | ||
671 | |||
672 | |||
673 | switch (action) { | ||
674 | case BUS_NOTIFY_UNBOUND_DRIVER: | ||
675 | count = device_dma_allocations(dev); | ||
676 | if (count == 0) | ||
677 | break; | ||
678 | err_printk(dev, NULL, "DMA-API: device driver has pending " | ||
679 | "DMA allocations while released from device " | ||
680 | "[count=%d]\n", count); | ||
681 | break; | ||
682 | default: | ||
683 | break; | ||
684 | } | ||
685 | |||
686 | return 0; | ||
687 | } | ||
688 | |||
461 | void dma_debug_add_bus(struct bus_type *bus) | 689 | void dma_debug_add_bus(struct bus_type *bus) |
462 | { | 690 | { |
463 | /* FIXME: register notifier */ | 691 | struct notifier_block *nb; |
692 | |||
693 | nb = kzalloc(sizeof(struct notifier_block), GFP_KERNEL); | ||
694 | if (nb == NULL) { | ||
695 | printk(KERN_ERR "dma_debug_add_bus: out of memory\n"); | ||
696 | return; | ||
697 | } | ||
698 | |||
699 | nb->notifier_call = dma_debug_device_change; | ||
700 | |||
701 | bus_register_notifier(bus, nb); | ||
464 | } | 702 | } |
465 | 703 | ||
466 | /* | 704 | /* |
@@ -783,15 +1021,15 @@ void debug_dma_map_sg(struct device *dev, struct scatterlist *sg, | |||
783 | entry->type = dma_debug_sg; | 1021 | entry->type = dma_debug_sg; |
784 | entry->dev = dev; | 1022 | entry->dev = dev; |
785 | entry->paddr = sg_phys(s); | 1023 | entry->paddr = sg_phys(s); |
786 | entry->size = s->length; | 1024 | entry->size = sg_dma_len(s); |
787 | entry->dev_addr = s->dma_address; | 1025 | entry->dev_addr = sg_dma_address(s); |
788 | entry->direction = direction; | 1026 | entry->direction = direction; |
789 | entry->sg_call_ents = nents; | 1027 | entry->sg_call_ents = nents; |
790 | entry->sg_mapped_ents = mapped_ents; | 1028 | entry->sg_mapped_ents = mapped_ents; |
791 | 1029 | ||
792 | if (!PageHighMem(sg_page(s))) { | 1030 | if (!PageHighMem(sg_page(s))) { |
793 | check_for_stack(dev, sg_virt(s)); | 1031 | check_for_stack(dev, sg_virt(s)); |
794 | check_for_illegal_area(dev, sg_virt(s), s->length); | 1032 | check_for_illegal_area(dev, sg_virt(s), sg_dma_len(s)); |
795 | } | 1033 | } |
796 | 1034 | ||
797 | add_dma_entry(entry); | 1035 | add_dma_entry(entry); |
@@ -799,13 +1037,32 @@ void debug_dma_map_sg(struct device *dev, struct scatterlist *sg, | |||
799 | } | 1037 | } |
800 | EXPORT_SYMBOL(debug_dma_map_sg); | 1038 | EXPORT_SYMBOL(debug_dma_map_sg); |
801 | 1039 | ||
1040 | static int get_nr_mapped_entries(struct device *dev, struct scatterlist *s) | ||
1041 | { | ||
1042 | struct dma_debug_entry *entry; | ||
1043 | struct hash_bucket *bucket; | ||
1044 | unsigned long flags; | ||
1045 | int mapped_ents = 0; | ||
1046 | struct dma_debug_entry ref; | ||
1047 | |||
1048 | ref.dev = dev; | ||
1049 | ref.dev_addr = sg_dma_address(s); | ||
1050 | ref.size = sg_dma_len(s), | ||
1051 | |||
1052 | bucket = get_hash_bucket(&ref, &flags); | ||
1053 | entry = hash_bucket_find(bucket, &ref); | ||
1054 | if (entry) | ||
1055 | mapped_ents = entry->sg_mapped_ents; | ||
1056 | put_hash_bucket(bucket, &flags); | ||
1057 | |||
1058 | return mapped_ents; | ||
1059 | } | ||
1060 | |||
802 | void debug_dma_unmap_sg(struct device *dev, struct scatterlist *sglist, | 1061 | void debug_dma_unmap_sg(struct device *dev, struct scatterlist *sglist, |
803 | int nelems, int dir) | 1062 | int nelems, int dir) |
804 | { | 1063 | { |
805 | struct dma_debug_entry *entry; | ||
806 | struct scatterlist *s; | 1064 | struct scatterlist *s; |
807 | int mapped_ents = 0, i; | 1065 | int mapped_ents = 0, i; |
808 | unsigned long flags; | ||
809 | 1066 | ||
810 | if (unlikely(global_disable)) | 1067 | if (unlikely(global_disable)) |
811 | return; | 1068 | return; |
@@ -816,8 +1073,8 @@ void debug_dma_unmap_sg(struct device *dev, struct scatterlist *sglist, | |||
816 | .type = dma_debug_sg, | 1073 | .type = dma_debug_sg, |
817 | .dev = dev, | 1074 | .dev = dev, |
818 | .paddr = sg_phys(s), | 1075 | .paddr = sg_phys(s), |
819 | .dev_addr = s->dma_address, | 1076 | .dev_addr = sg_dma_address(s), |
820 | .size = s->length, | 1077 | .size = sg_dma_len(s), |
821 | .direction = dir, | 1078 | .direction = dir, |
822 | .sg_call_ents = 0, | 1079 | .sg_call_ents = 0, |
823 | }; | 1080 | }; |
@@ -825,14 +1082,9 @@ void debug_dma_unmap_sg(struct device *dev, struct scatterlist *sglist, | |||
825 | if (mapped_ents && i >= mapped_ents) | 1082 | if (mapped_ents && i >= mapped_ents) |
826 | break; | 1083 | break; |
827 | 1084 | ||
828 | if (mapped_ents == 0) { | 1085 | if (!i) { |
829 | struct hash_bucket *bucket; | ||
830 | ref.sg_call_ents = nelems; | 1086 | ref.sg_call_ents = nelems; |
831 | bucket = get_hash_bucket(&ref, &flags); | 1087 | mapped_ents = get_nr_mapped_entries(dev, s); |
832 | entry = hash_bucket_find(bucket, &ref); | ||
833 | if (entry) | ||
834 | mapped_ents = entry->sg_mapped_ents; | ||
835 | put_hash_bucket(bucket, &flags); | ||
836 | } | 1088 | } |
837 | 1089 | ||
838 | check_unmap(&ref); | 1090 | check_unmap(&ref); |
@@ -934,14 +1186,20 @@ void debug_dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, | |||
934 | int nelems, int direction) | 1186 | int nelems, int direction) |
935 | { | 1187 | { |
936 | struct scatterlist *s; | 1188 | struct scatterlist *s; |
937 | int i; | 1189 | int mapped_ents = 0, i; |
938 | 1190 | ||
939 | if (unlikely(global_disable)) | 1191 | if (unlikely(global_disable)) |
940 | return; | 1192 | return; |
941 | 1193 | ||
942 | for_each_sg(sg, s, nelems, i) { | 1194 | for_each_sg(sg, s, nelems, i) { |
943 | check_sync(dev, s->dma_address, s->dma_length, 0, | 1195 | if (!i) |
944 | direction, true); | 1196 | mapped_ents = get_nr_mapped_entries(dev, s); |
1197 | |||
1198 | if (i >= mapped_ents) | ||
1199 | break; | ||
1200 | |||
1201 | check_sync(dev, sg_dma_address(s), sg_dma_len(s), 0, | ||
1202 | direction, true); | ||
945 | } | 1203 | } |
946 | } | 1204 | } |
947 | EXPORT_SYMBOL(debug_dma_sync_sg_for_cpu); | 1205 | EXPORT_SYMBOL(debug_dma_sync_sg_for_cpu); |
@@ -950,15 +1208,39 @@ void debug_dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, | |||
950 | int nelems, int direction) | 1208 | int nelems, int direction) |
951 | { | 1209 | { |
952 | struct scatterlist *s; | 1210 | struct scatterlist *s; |
953 | int i; | 1211 | int mapped_ents = 0, i; |
954 | 1212 | ||
955 | if (unlikely(global_disable)) | 1213 | if (unlikely(global_disable)) |
956 | return; | 1214 | return; |
957 | 1215 | ||
958 | for_each_sg(sg, s, nelems, i) { | 1216 | for_each_sg(sg, s, nelems, i) { |
959 | check_sync(dev, s->dma_address, s->dma_length, 0, | 1217 | if (!i) |
960 | direction, false); | 1218 | mapped_ents = get_nr_mapped_entries(dev, s); |
1219 | |||
1220 | if (i >= mapped_ents) | ||
1221 | break; | ||
1222 | |||
1223 | check_sync(dev, sg_dma_address(s), sg_dma_len(s), 0, | ||
1224 | direction, false); | ||
961 | } | 1225 | } |
962 | } | 1226 | } |
963 | EXPORT_SYMBOL(debug_dma_sync_sg_for_device); | 1227 | EXPORT_SYMBOL(debug_dma_sync_sg_for_device); |
964 | 1228 | ||
1229 | static int __init dma_debug_driver_setup(char *str) | ||
1230 | { | ||
1231 | int i; | ||
1232 | |||
1233 | for (i = 0; i < NAME_MAX_LEN - 1; ++i, ++str) { | ||
1234 | current_driver_name[i] = *str; | ||
1235 | if (*str == 0) | ||
1236 | break; | ||
1237 | } | ||
1238 | |||
1239 | if (current_driver_name[0]) | ||
1240 | printk(KERN_INFO "DMA-API: enable driver filter for " | ||
1241 | "driver [%s]\n", current_driver_name); | ||
1242 | |||
1243 | |||
1244 | return 1; | ||
1245 | } | ||
1246 | __setup("dma_debug_driver=", dma_debug_driver_setup); | ||