linux/drivers/thunderbolt/cap.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Thunderbolt driver - capabilities lookup
   4 *
   5 * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
   6 * Copyright (C) 2018, Intel Corporation
   7 */
   8
   9#include <linux/slab.h>
  10#include <linux/errno.h>
  11
  12#include "tb.h"
  13
  14#define CAP_OFFSET_MAX          0xff
  15#define VSE_CAP_OFFSET_MAX      0xffff
  16#define TMU_ACCESS_EN           BIT(20)
  17
  18static int tb_port_enable_tmu(struct tb_port *port, bool enable)
  19{
  20        struct tb_switch *sw = port->sw;
  21        u32 value, offset;
  22        int ret;
  23
  24        /*
  25         * Legacy devices need to have TMU access enabled before port
  26         * space can be fully accessed.
  27         */
  28        if (tb_switch_is_light_ridge(sw))
  29                offset = 0x26;
  30        else if (tb_switch_is_eagle_ridge(sw))
  31                offset = 0x2a;
  32        else
  33                return 0;
  34
  35        ret = tb_sw_read(sw, &value, TB_CFG_SWITCH, offset, 1);
  36        if (ret)
  37                return ret;
  38
  39        if (enable)
  40                value |= TMU_ACCESS_EN;
  41        else
  42                value &= ~TMU_ACCESS_EN;
  43
  44        return tb_sw_write(sw, &value, TB_CFG_SWITCH, offset, 1);
  45}
  46
  47static void tb_port_dummy_read(struct tb_port *port)
  48{
  49        /*
  50         * When reading from next capability pointer location in port
  51         * config space the read data is not cleared on LR. To avoid
  52         * reading stale data on next read perform one dummy read after
  53         * port capabilities are walked.
  54         */
  55        if (tb_switch_is_light_ridge(port->sw)) {
  56                u32 dummy;
  57
  58                tb_port_read(port, &dummy, TB_CFG_PORT, 0, 1);
  59        }
  60}
  61
  62/**
  63 * tb_port_next_cap() - Return next capability in the linked list
  64 * @port: Port to find the capability for
  65 * @offset: Previous capability offset (%0 for start)
  66 *
  67 * Returns dword offset of the next capability in port config space
  68 * capability list and returns it. Passing %0 returns the first entry in
  69 * the capability list. If no next capability is found returns %0. In case
  70 * of failure returns negative errno.
  71 */
  72int tb_port_next_cap(struct tb_port *port, unsigned int offset)
  73{
  74        struct tb_cap_any header;
  75        int ret;
  76
  77        if (!offset)
  78                return port->config.first_cap_offset;
  79
  80        ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1);
  81        if (ret)
  82                return ret;
  83
  84        return header.basic.next;
  85}
  86
  87static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
  88{
  89        int offset = 0;
  90
  91        do {
  92                struct tb_cap_any header;
  93                int ret;
  94
  95                offset = tb_port_next_cap(port, offset);
  96                if (offset < 0)
  97                        return offset;
  98
  99                ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1);
 100                if (ret)
 101                        return ret;
 102
 103                if (header.basic.cap == cap)
 104                        return offset;
 105        } while (offset > 0);
 106
 107        return -ENOENT;
 108}
 109
 110/**
 111 * tb_port_find_cap() - Find port capability
 112 * @port: Port to find the capability for
 113 * @cap: Capability to look
 114 *
 115 * Returns offset to start of capability or %-ENOENT if no such
 116 * capability was found. Negative errno is returned if there was an
 117 * error.
 118 */
 119int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
 120{
 121        int ret;
 122
 123        ret = tb_port_enable_tmu(port, true);
 124        if (ret)
 125                return ret;
 126
 127        ret = __tb_port_find_cap(port, cap);
 128
 129        tb_port_dummy_read(port);
 130        tb_port_enable_tmu(port, false);
 131
 132        return ret;
 133}
 134
 135/**
 136 * tb_switch_next_cap() - Return next capability in the linked list
 137 * @sw: Switch to find the capability for
 138 * @offset: Previous capability offset (%0 for start)
 139 *
 140 * Finds dword offset of the next capability in router config space
 141 * capability list and returns it. Passing %0 returns the first entry in
 142 * the capability list. If no next capability is found returns %0. In case
 143 * of failure returns negative errno.
 144 */
 145int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset)
 146{
 147        struct tb_cap_any header;
 148        int ret;
 149
 150        if (!offset)
 151                return sw->config.first_cap_offset;
 152
 153        ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2);
 154        if (ret)
 155                return ret;
 156
 157        switch (header.basic.cap) {
 158        case TB_SWITCH_CAP_TMU:
 159                ret = header.basic.next;
 160                break;
 161
 162        case TB_SWITCH_CAP_VSE:
 163                if (!header.extended_short.length)
 164                        ret = header.extended_long.next;
 165                else
 166                        ret = header.extended_short.next;
 167                break;
 168
 169        default:
 170                tb_sw_dbg(sw, "unknown capability %#x at %#x\n",
 171                          header.basic.cap, offset);
 172                ret = -EINVAL;
 173                break;
 174        }
 175
 176        return ret >= VSE_CAP_OFFSET_MAX ? 0 : ret;
 177}
 178
 179/**
 180 * tb_switch_find_cap() - Find switch capability
 181 * @sw: Switch to find the capability for
 182 * @cap: Capability to look
 183 *
 184 * Returns offset to start of capability or %-ENOENT if no such
 185 * capability was found. Negative errno is returned if there was an
 186 * error.
 187 */
 188int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
 189{
 190        int offset = 0;
 191
 192        do {
 193                struct tb_cap_any header;
 194                int ret;
 195
 196                offset = tb_switch_next_cap(sw, offset);
 197                if (offset < 0)
 198                        return offset;
 199
 200                ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
 201                if (ret)
 202                        return ret;
 203
 204                if (header.basic.cap == cap)
 205                        return offset;
 206        } while (offset);
 207
 208        return -ENOENT;
 209}
 210
 211/**
 212 * tb_switch_find_vse_cap() - Find switch vendor specific capability
 213 * @sw: Switch to find the capability for
 214 * @vsec: Vendor specific capability to look
 215 *
 216 * Functions enumerates vendor specific capabilities (VSEC) of a switch
 217 * and returns offset when capability matching @vsec is found. If no
 218 * such capability is found returns %-ENOENT. In case of error returns
 219 * negative errno.
 220 */
 221int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec)
 222{
 223        int offset = 0;
 224
 225        do {
 226                struct tb_cap_any header;
 227                int ret;
 228
 229                offset = tb_switch_next_cap(sw, offset);
 230                if (offset < 0)
 231                        return offset;
 232
 233                ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
 234                if (ret)
 235                        return ret;
 236
 237                if (header.extended_short.cap == TB_SWITCH_CAP_VSE &&
 238                    header.extended_short.vsec_id == vsec)
 239                        return offset;
 240        } while (offset);
 241
 242        return -ENOENT;
 243}
 244