linux/drivers/thunderbolt/cap.c
<<
>>
Prefs
   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