// SPDX-License-Identifier: GPL-2.0
/*
* Thunderbolt Cactus Ridge driver - switch/port utility functions
*
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
*/
#include <linux/delay.h>
#include <linux/idr.h>
#include <linux/nvmem-provider.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include "tb.h"
/* Switch authorization from userspace is serialized by this lock */
static DEFINE_MUTEX(switch_lock);
/* Switch NVM support */
#define NVM_DEVID 0x05
#define NVM_VERSION 0x08
#define NVM_CSS 0x10
#define NVM_FLASH_SIZE 0x45
#define NVM_MIN_SIZE SZ_32K
#define NVM_MAX_SIZE SZ_512K
static DEFINE_IDA(nvm_ida);
struct nvm_auth_status {
struct list_head list;
uuid_t uuid;
u32 status;
};
/*
* Hold NVM authentication failure status per switch This information
* needs to stay around even when the switch gets power cycled so we
* keep it separately.
*/
static LIST_HEAD(nvm_auth_status_cache);
static DEFINE_MUTEX(nvm_auth_status_lock);
static struct nvm_auth_status *__nvm_get_auth_status(const struct tb_switch *sw)
{
struct nvm_auth_status *st;
list_for_each_entry(st, &nvm_auth_status_cache, list) {
if (uuid_equal(&st->uuid, sw->uuid))
return st;
}
return NULL;
}
static void nvm_get_auth_status(const struct tb_switch *sw, u32 *status)
{
struct nvm_auth_status *st;
mutex_lock(&nvm_auth_status_lock);
st = __nvm_get_auth_status(sw);
mutex_unlock(&nvm_auth_status_lock);
*status = st ? st->status : 0;
}
static void nvm_set_auth_status(const struct tb_switch *sw, u32 status)
{
struct nvm_auth_status *st;
if (WARN_ON(!sw->uuid))
return;
mutex_lock(&nvm_auth_status_lock);
st = __nvm_get_auth_status(sw);
if (!st) {
st = kzalloc(sizeof(*st), GFP_KERNEL);
if (!st)
goto unlock;
memcpy(&st->uuid, sw->uuid, sizeof(st->uuid));
INIT_LIST_HEAD(&st->list);
list_add_tail(&st->list, &nvm_auth_status_cache);
}
st->status = status;
unlock:
mutex_unlock(&nvm_auth_status_lock);
}
static void nvm_clear_auth_status(const struct tb_switch *sw)
{
struct nvm_auth_status *st;
mutex_lock(&nvm_auth_status_lock);
st = __nvm_get_auth_status(sw);
if (st) {
list_del(&st->list);
kfree(st);
}
mutex_unlock(&nvm_auth_status_lock);
}
static int nvm_validate_and_write(struct tb_switch *sw)
{
unsigned int image_size, hdr_size;
const u8 *buf = sw->nvm->buf;
u16 ds_size;
int ret;
if (!buf)
return -EINVAL;
image_size = sw->nvm->buf_data_size;
if (image_size < NVM_MIN_SIZE || image_size > NVM_MAX_SIZE)
return -EINVAL;
/*
* FARB pointer must point inside the image and must at least
* contain parts of the digital section we will be reading here.
*/
hdr_size = (*(u32 *)buf) & 0xffffff;
if (hdr_size + NVM_DEVID + 2 >= image_size)
return -EINVAL;
/* Digital section start should be aligned to 4k page */
if (!IS_ALIGNED(hdr_size, SZ_4K))
return -EINVAL;
/*
* Read digital section size and check that it also fits inside
* the image.
*/
ds_size = *(u16 *)(buf + hdr_size);
if (ds_size >= image_size)
return -EINVAL;
if (!sw->safe_mode) {
u16 device_id;
/*
* Make sure the device ID in the image matches the one
* we read from the switch config space.
*/
device_id = *(u16 *)(buf + hdr_size + NVM_DEVID);
if (device_id != sw->config.device_id)
return -EINVAL;
if (sw->generation < 3) {
/* Write CSS headers first */
ret = dma_port_flash_write(sw->dma_port,
DMA_PORT_CSS_ADDRESS, buf + NVM_CSS,
DMA_PORT_CSS_MAX_SIZE);
if (ret)
return ret;
}
/* Skip headers in the image */
buf += hdr_size;
image_size -= hdr_size;
}
return dma_port_flash_write(sw->dma_port, 0, buf, image_size);
}
static int nvm_authenticate_host(struct tb_switch *sw)
{
int ret;
/*
* Root switch NVM upgrade requires that we disconnect the
* existing paths first (in case it is not in safe mode
* already).
*/
if (!sw->safe_mode) {
ret = tb_domain_disconnect_all_paths(sw->tb);
if (ret)
return ret;
/*
* The host controller goes away pretty soon after this if
* everything goes well so getting timeout is expected.
*/
ret = dma_port_flash_update_auth(sw->dma_port);
return ret == -ETIMEDOUT ? 0 : ret;
}
/*
* From safe mode we can get out by just power cycling the
* switch.
*/
dma_port_power_cycle(sw->dma_port);
return 0;
}
static int nvm_authenticate_device(struct tb_switch *sw)
{
int ret, retries = 10;
ret = dma_port_flash_update_auth(sw->dma_port);
if (ret && ret != -ETIMEDOUT)
|