1/* 2 * Thunderbolt Cactus Ridge driver - capabilities lookup 3 * 4 * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> 5 */ 6 7#include <linux/slab.h> 8#include <linux/errno.h> 9 10#include "tb.h" 11 12 13struct tb_cap_any { 14 union { 15 struct tb_cap_basic basic; 16 struct tb_cap_extended_short extended_short; 17 struct tb_cap_extended_long extended_long; 18 }; 19} __packed; 20 21static bool tb_cap_is_basic(struct tb_cap_any *cap) 22{ 23 /* basic.cap is u8. This checks only the lower 8 bit of cap. */ 24 return cap->basic.cap != 5; 25} 26 27static bool tb_cap_is_long(struct tb_cap_any *cap) 28{ 29 return !tb_cap_is_basic(cap) 30 && cap->extended_short.next == 0 31 && cap->extended_short.length == 0; 32} 33 34static enum tb_cap tb_cap(struct tb_cap_any *cap) 35{ 36 if (tb_cap_is_basic(cap)) 37 return cap->basic.cap; 38 else 39 /* extended_short/long have cap at the same offset. */ 40 return cap->extended_short.cap; 41} 42 43static u32 tb_cap_next(struct tb_cap_any *cap, u32 offset) 44{ 45 int next; 46 if (offset == 1) { 47 /* 48 * The first pointer is part of the switch header and always 49 * a simple pointer. 50 */ 51 next = cap->basic.next; 52 } else { 53 /* 54 * Somehow Intel decided to use 3 different types of capability 55 * headers. It is not like anyone could have predicted that 56 * single byte offsets are not enough... 57 */ 58 if (tb_cap_is_basic(cap)) 59 next = cap->basic.next; 60 else if (!tb_cap_is_long(cap)) 61 next = cap->extended_short.next; 62 else 63 next = cap->extended_long.next; 64 } 65 /* 66 * "Hey, we could terminate some capability lists with a null offset 67 * and others with a pointer to the last element." - "Great idea!" 68 */ 69 if (next == offset) 70 return 0; 71 return next; 72} 73 74/** 75 * tb_find_cap() - find a capability 76 * 77 * Return: Returns a positive offset if the capability was found and 0 if not. 78 * Returns an error code on failure. 79 */ 80int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, enum tb_cap cap) 81{ 82 u32 offset = 1; 83 struct tb_cap_any header; 84 int res; 85 int retries = 10; 86 while (retries--) { 87 res = tb_port_read(port, &header, space, offset, 1); 88 if (res) { 89 /* Intel needs some help with linked lists. */ 90 if (space == TB_CFG_PORT && offset == 0xa 91 && port->config.type == TB_TYPE_DP_HDMI_OUT) { 92 offset = 0x39; 93 continue; 94 } 95 return res; 96 } 97 if (offset != 1) { 98 if (tb_cap(&header) == cap) 99 return offset; 100 if (tb_cap_is_long(&header)) { 101 /* tb_cap_extended_long is 2 dwords */ 102 res = tb_port_read(port, &header, space, 103 offset, 2); 104 if (res) 105 return res; 106 } 107 } 108 offset = tb_cap_next(&header, offset); 109 if (!offset) 110 return 0; 111 } 112 tb_port_WARN(port, 113 "run out of retries while looking for cap %#x in config space %d, last offset: %#x\n", 114 cap, space, offset); 115 return -EIO; 116} 117