[PATCH 1/8][SRU][G] thunderbolt: Add support for separating the flush to SPI and authenticate
Koba Ko
koba.ko at canonical.com
Thu Sep 10 03:45:19 UTC 2020
From: Mario Limonciello <mario.limonciello at dell.com>
BugLink: https://bugs.launchpad.net/bugs/1895073
This allows userspace to have a shorter period of time that the device
is unusable and to call it at a more convenient time.
For example flushing the image may happen while the user is using the
machine and authenticating/rebooting may happen while logging out.
Signed-off-by: Mario Limonciello <mario.limonciello at dell.com>
Signed-off-by: Mika Westerberg <mika.westerberg at linux.intel.com>
(cherry picked from commit 4b794f8066e84818c172c81024f1d61071f14710)
Signed-off-by: Koba Ko <koba.ko at canonical.com>
---
.../ABI/testing/sysfs-bus-thunderbolt | 11 +-
drivers/thunderbolt/nvm.c | 170 ++++++++++++++++++
drivers/thunderbolt/switch.c | 42 +++--
drivers/thunderbolt/tb.h | 7 +-
4 files changed, 212 insertions(+), 18 deletions(-)
create mode 100644 drivers/thunderbolt/nvm.c
diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt b/Documentation/ABI/testing/sysfs-bus-thunderbolt
index 82e80de78dd0..7216d71ec242 100644
--- a/Documentation/ABI/testing/sysfs-bus-thunderbolt
+++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt
@@ -178,11 +178,18 @@ KernelVersion: 4.13
Contact: thunderbolt-software at lists.01.org
Description: When new NVM image is written to the non-active NVM
area (through non_activeX NVMem device), the
- authentication procedure is started by writing 1 to
- this file. If everything goes well, the device is
+ authentication procedure is started by writing to
+ this file.
+ If everything goes well, the device is
restarted with the new NVM firmware. If the image
verification fails an error code is returned instead.
+ This file will accept writing values "1" or "2"
+ - Writing "1" will flush the image to the storage
+ area and authenticate the image in one action.
+ - Writing "2" will run some basic validation on the image
+ and flush it to the storage area.
+
When read holds status of the last authentication
operation if an error occurred during the process. This
is directly the status value from the DMA configuration
diff --git a/drivers/thunderbolt/nvm.c b/drivers/thunderbolt/nvm.c
new file mode 100644
index 000000000000..29de6d95c6e7
--- /dev/null
+++ b/drivers/thunderbolt/nvm.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * NVM helpers
+ *
+ * Copyright (C) 2020, Intel Corporation
+ * Author: Mika Westerberg <mika.westerberg at linux.intel.com>
+ */
+
+#include <linux/idr.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+
+#include "tb.h"
+
+static DEFINE_IDA(nvm_ida);
+
+/**
+ * tb_nvm_alloc() - Allocate new NVM structure
+ * @dev: Device owning the NVM
+ *
+ * Allocates new NVM structure with unique @id and returns it. In case
+ * of error returns ERR_PTR().
+ */
+struct tb_nvm *tb_nvm_alloc(struct device *dev)
+{
+ struct tb_nvm *nvm;
+ int ret;
+
+ nvm = kzalloc(sizeof(*nvm), GFP_KERNEL);
+ if (!nvm)
+ return ERR_PTR(-ENOMEM);
+
+ ret = ida_simple_get(&nvm_ida, 0, 0, GFP_KERNEL);
+ if (ret < 0) {
+ kfree(nvm);
+ return ERR_PTR(ret);
+ }
+
+ nvm->id = ret;
+ nvm->dev = dev;
+
+ return nvm;
+}
+
+/**
+ * tb_nvm_add_active() - Adds active NVMem device to NVM
+ * @nvm: NVM structure
+ * @size: Size of the active NVM in bytes
+ * @reg_read: Pointer to the function to read the NVM (passed directly to the
+ * NVMem device)
+ *
+ * Registers new active NVmem device for @nvm. The @reg_read is called
+ * directly from NVMem so it must handle possible concurrent access if
+ * needed. The first parameter passed to @reg_read is @nvm structure.
+ * Returns %0 in success and negative errno otherwise.
+ */
+int tb_nvm_add_active(struct tb_nvm *nvm, size_t size, nvmem_reg_read_t reg_read)
+{
+ struct nvmem_config config;
+ struct nvmem_device *nvmem;
+
+ memset(&config, 0, sizeof(config));
+
+ config.name = "nvm_active";
+ config.reg_read = reg_read;
+ config.read_only = true;
+ config.id = nvm->id;
+ config.stride = 4;
+ config.word_size = 4;
+ config.size = size;
+ config.dev = nvm->dev;
+ config.owner = THIS_MODULE;
+ config.priv = nvm;
+
+ nvmem = nvmem_register(&config);
+ if (IS_ERR(nvmem))
+ return PTR_ERR(nvmem);
+
+ nvm->active = nvmem;
+ return 0;
+}
+
+/**
+ * tb_nvm_write_buf() - Write data to @nvm buffer
+ * @nvm: NVM structure
+ * @offset: Offset where to write the data
+ * @val: Data buffer to write
+ * @bytes: Number of bytes to write
+ *
+ * Helper function to cache the new NVM image before it is actually
+ * written to the flash. Copies @bytes from @val to @nvm->buf starting
+ * from @offset.
+ */
+int tb_nvm_write_buf(struct tb_nvm *nvm, unsigned int offset, void *val,
+ size_t bytes)
+{
+ if (!nvm->buf) {
+ nvm->buf = vmalloc(NVM_MAX_SIZE);
+ if (!nvm->buf)
+ return -ENOMEM;
+ }
+
+ nvm->flushed = false;
+ nvm->buf_data_size = offset + bytes;
+ memcpy(nvm->buf + offset, val, bytes);
+ return 0;
+}
+
+/**
+ * tb_nvm_add_non_active() - Adds non-active NVMem device to NVM
+ * @nvm: NVM structure
+ * @size: Size of the non-active NVM in bytes
+ * @reg_write: Pointer to the function to write the NVM (passed directly
+ * to the NVMem device)
+ *
+ * Registers new non-active NVmem device for @nvm. The @reg_write is called
+ * directly from NVMem so it must handle possible concurrent access if
+ * needed. The first parameter passed to @reg_write is @nvm structure.
+ * Returns %0 in success and negative errno otherwise.
+ */
+int tb_nvm_add_non_active(struct tb_nvm *nvm, size_t size,
+ nvmem_reg_write_t reg_write)
+{
+ struct nvmem_config config;
+ struct nvmem_device *nvmem;
+
+ memset(&config, 0, sizeof(config));
+
+ config.name = "nvm_non_active";
+ config.reg_write = reg_write;
+ config.root_only = true;
+ config.id = nvm->id;
+ config.stride = 4;
+ config.word_size = 4;
+ config.size = size;
+ config.dev = nvm->dev;
+ config.owner = THIS_MODULE;
+ config.priv = nvm;
+
+ nvmem = nvmem_register(&config);
+ if (IS_ERR(nvmem))
+ return PTR_ERR(nvmem);
+
+ nvm->non_active = nvmem;
+ return 0;
+}
+
+/**
+ * tb_nvm_free() - Release NVM and its resources
+ * @nvm: NVM structure to release
+ *
+ * Releases NVM and the NVMem devices if they were registered.
+ */
+void tb_nvm_free(struct tb_nvm *nvm)
+{
+ if (nvm) {
+ if (nvm->non_active)
+ nvmem_unregister(nvm->non_active);
+ if (nvm->active)
+ nvmem_unregister(nvm->active);
+ vfree(nvm->buf);
+ ida_simple_remove(&nvm_ida, nvm->id);
+ }
+ kfree(nvm);
+}
+
+void tb_nvm_exit(void)
+{
+ ida_destroy(&nvm_ida);
+}
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index d7d60cd9226f..0de54b150527 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -35,6 +35,11 @@ struct nvm_auth_status {
u32 status;
};
+enum nvm_write_ops {
+ WRITE_AND_AUTHENTICATE = 1,
+ WRITE_ONLY = 2,
+};
+
/*
* Hold NVM authentication failure status per switch This information
* needs to stay around even when the switch gets power cycled so we
@@ -164,8 +169,12 @@ static int nvm_validate_and_write(struct tb_switch *sw)
}
if (tb_switch_is_usb4(sw))
- return usb4_switch_nvm_write(sw, 0, buf, image_size);
- return dma_port_flash_write(sw->dma_port, 0, buf, image_size);
+ ret = usb4_switch_nvm_write(sw, 0, buf, image_size);
+ else
+ ret = dma_port_flash_write(sw->dma_port, 0, buf, image_size);
+ if (!ret)
+ sw->nvm->flushed = true;
+ return ret;
}
static int nvm_authenticate_host_dma_port(struct tb_switch *sw)
@@ -1536,7 +1545,7 @@ static ssize_t nvm_authenticate_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct tb_switch *sw = tb_to_switch(dev);
- bool val;
+ int val;
int ret;
pm_runtime_get_sync(&sw->dev);
@@ -1552,25 +1561,28 @@ static ssize_t nvm_authenticate_store(struct device *dev,
goto exit_unlock;
}
- ret = kstrtobool(buf, &val);
+ ret = kstrtoint(buf, 10, &val);
if (ret)
goto exit_unlock;
/* Always clear the authentication status */
nvm_clear_auth_status(sw);
- if (val) {
- if (!sw->nvm->buf) {
- ret = -EINVAL;
- goto exit_unlock;
- }
-
- ret = nvm_validate_and_write(sw);
- if (ret)
- goto exit_unlock;
+ if (val > 0) {
+ if (!sw->nvm->flushed) {
+ if (!sw->nvm->buf) {
+ ret = -EINVAL;
+ goto exit_unlock;
+ }
- sw->nvm->authenticating = true;
- ret = nvm_authenticate(sw);
+ ret = nvm_validate_and_write(sw);
+ if (ret || val == WRITE_ONLY)
+ goto exit_unlock;
+ }
+ if (val == WRITE_AND_AUTHENTICATE) {
+ sw->nvm->authenticating = true;
+ ret = nvm_authenticate(sw);
+ }
}
exit_unlock:
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 2eb2bcd3cca3..065eb65b2695 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -29,7 +29,11 @@
* the actual NVM flash device
* @buf_data_size: Number of bytes actually consumed by the new NVM
* image
- * @authenticating: The switch is authenticating the new NVM
+ * @authenticating: The device is authenticating the new NVM
+ * @flushed: The image has been flushed to the storage area
+ *
+ * The user of this structure needs to handle serialization of possible
+ * concurrent access.
*/
struct tb_switch_nvm {
u8 major;
@@ -40,6 +44,7 @@ struct tb_switch_nvm {
void *buf;
size_t buf_data_size;
bool authenticating;
+ bool flushed;
};
#define TB_SWITCH_KEY_SIZE 32
--
2.17.1
More information about the kernel-team
mailing list