Merge tag 'cgroup-for-6.1' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup
[linux-block.git] / drivers / thunderbolt / nvm.c
index b3f31038937864993e2d861b07dbddb627d8b3ab..3dd5f81bd629355ab2274848b02bb08b29d8941b 100644 (file)
 
 #include "tb.h"
 
+/* Intel specific NVM offsets */
+#define INTEL_NVM_DEVID                        0x05
+#define INTEL_NVM_VERSION              0x08
+#define INTEL_NVM_CSS                  0x10
+#define INTEL_NVM_FLASH_SIZE           0x45
+
+/* ASMedia specific NVM offsets */
+#define ASMEDIA_NVM_DATE               0x1c
+#define ASMEDIA_NVM_VERSION            0x28
+
 static DEFINE_IDA(nvm_ida);
 
+/**
+ * struct tb_nvm_vendor_ops - Vendor specific NVM operations
+ * @read_version: Reads out NVM version from the flash
+ * @validate: Validates the NVM image before update (optional)
+ * @write_headers: Writes headers before the rest of the image (optional)
+ */
+struct tb_nvm_vendor_ops {
+       int (*read_version)(struct tb_nvm *nvm);
+       int (*validate)(struct tb_nvm *nvm);
+       int (*write_headers)(struct tb_nvm *nvm);
+};
+
+/**
+ * struct tb_nvm_vendor - Vendor to &struct tb_nvm_vendor_ops mapping
+ * @vendor: Vendor ID
+ * @vops: Vendor specific NVM operations
+ *
+ * Maps vendor ID to NVM vendor operations. If there is no mapping then
+ * NVM firmware upgrade is disabled for the device.
+ */
+struct tb_nvm_vendor {
+       u16 vendor;
+       const struct tb_nvm_vendor_ops *vops;
+};
+
+static int intel_switch_nvm_version(struct tb_nvm *nvm)
+{
+       struct tb_switch *sw = tb_to_switch(nvm->dev);
+       u32 val, nvm_size, hdr_size;
+       int ret;
+
+       /*
+        * If the switch is in safe-mode the only accessible portion of
+        * the NVM is the non-active one where userspace is expected to
+        * write new functional NVM.
+        */
+       if (sw->safe_mode)
+               return 0;
+
+       ret = tb_switch_nvm_read(sw, INTEL_NVM_FLASH_SIZE, &val, sizeof(val));
+       if (ret)
+               return ret;
+
+       hdr_size = sw->generation < 3 ? SZ_8K : SZ_16K;
+       nvm_size = (SZ_1M << (val & 7)) / 8;
+       nvm_size = (nvm_size - hdr_size) / 2;
+
+       ret = tb_switch_nvm_read(sw, INTEL_NVM_VERSION, &val, sizeof(val));
+       if (ret)
+               return ret;
+
+       nvm->major = (val >> 16) & 0xff;
+       nvm->minor = (val >> 8) & 0xff;
+       nvm->active_size = nvm_size;
+
+       return 0;
+}
+
+static int intel_switch_nvm_validate(struct tb_nvm *nvm)
+{
+       struct tb_switch *sw = tb_to_switch(nvm->dev);
+       unsigned int image_size, hdr_size;
+       u16 ds_size, device_id;
+       u8 *buf = nvm->buf;
+
+       image_size = nvm->buf_data_size;
+
+       /*
+        * 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 + INTEL_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)
+               return 0;
+
+       /*
+        * Make sure the device ID in the image matches the one
+        * we read from the switch config space.
+        */
+       device_id = *(u16 *)(buf + hdr_size + INTEL_NVM_DEVID);
+       if (device_id != sw->config.device_id)
+               return -EINVAL;
+
+       /* Skip headers in the image */
+       nvm->buf_data_start = buf + hdr_size;
+       nvm->buf_data_size = image_size - hdr_size;
+
+       return 0;
+}
+
+static int intel_switch_nvm_write_headers(struct tb_nvm *nvm)
+{
+       struct tb_switch *sw = tb_to_switch(nvm->dev);
+
+       if (sw->generation < 3) {
+               int ret;
+
+               /* Write CSS headers first */
+               ret = dma_port_flash_write(sw->dma_port,
+                       DMA_PORT_CSS_ADDRESS, nvm->buf + INTEL_NVM_CSS,
+                       DMA_PORT_CSS_MAX_SIZE);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static const struct tb_nvm_vendor_ops intel_switch_nvm_ops = {
+       .read_version = intel_switch_nvm_version,
+       .validate = intel_switch_nvm_validate,
+       .write_headers = intel_switch_nvm_write_headers,
+};
+
+static int asmedia_switch_nvm_version(struct tb_nvm *nvm)
+{
+       struct tb_switch *sw = tb_to_switch(nvm->dev);
+       u32 val;
+       int ret;
+
+       ret = tb_switch_nvm_read(sw, ASMEDIA_NVM_VERSION, &val, sizeof(val));
+       if (ret)
+               return ret;
+
+       nvm->major = (val << 16) & 0xff0000;
+       nvm->major |= val & 0x00ff00;
+       nvm->major |= (val >> 16) & 0x0000ff;
+
+       ret = tb_switch_nvm_read(sw, ASMEDIA_NVM_DATE, &val, sizeof(val));
+       if (ret)
+               return ret;
+
+       nvm->minor = (val << 16) & 0xff0000;
+       nvm->minor |= val & 0x00ff00;
+       nvm->minor |= (val >> 16) & 0x0000ff;
+
+       /* ASMedia NVM size is fixed to 512k */
+       nvm->active_size = SZ_512K;
+
+       return 0;
+}
+
+static const struct tb_nvm_vendor_ops asmedia_switch_nvm_ops = {
+       .read_version = asmedia_switch_nvm_version,
+};
+
+/* Router vendor NVM support table */
+static const struct tb_nvm_vendor switch_nvm_vendors[] = {
+       { 0x174c, &asmedia_switch_nvm_ops },
+       { PCI_VENDOR_ID_INTEL, &intel_switch_nvm_ops },
+       { 0x8087, &intel_switch_nvm_ops },
+};
+
+static int intel_retimer_nvm_version(struct tb_nvm *nvm)
+{
+       struct tb_retimer *rt = tb_to_retimer(nvm->dev);
+       u32 val, nvm_size;
+       int ret;
+
+       ret = tb_retimer_nvm_read(rt, INTEL_NVM_VERSION, &val, sizeof(val));
+       if (ret)
+               return ret;
+
+       nvm->major = (val >> 16) & 0xff;
+       nvm->minor = (val >> 8) & 0xff;
+
+       ret = tb_retimer_nvm_read(rt, INTEL_NVM_FLASH_SIZE, &val, sizeof(val));
+       if (ret)
+               return ret;
+
+       nvm_size = (SZ_1M << (val & 7)) / 8;
+       nvm_size = (nvm_size - SZ_16K) / 2;
+       nvm->active_size = nvm_size;
+
+       return 0;
+}
+
+static int intel_retimer_nvm_validate(struct tb_nvm *nvm)
+{
+       struct tb_retimer *rt = tb_to_retimer(nvm->dev);
+       unsigned int image_size, hdr_size;
+       u8 *buf = nvm->buf;
+       u16 ds_size, device;
+
+       image_size = nvm->buf_data_size;
+
+       /*
+        * 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 + INTEL_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;
+
+       /*
+        * Make sure the device ID in the image matches the retimer
+        * hardware.
+        */
+       device = *(u16 *)(buf + hdr_size + INTEL_NVM_DEVID);
+       if (device != rt->device)
+               return -EINVAL;
+
+       /* Skip headers in the image */
+       nvm->buf_data_start = buf + hdr_size;
+       nvm->buf_data_size = image_size - hdr_size;
+
+       return 0;
+}
+
+static const struct tb_nvm_vendor_ops intel_retimer_nvm_ops = {
+       .read_version = intel_retimer_nvm_version,
+       .validate = intel_retimer_nvm_validate,
+};
+
+/* Retimer vendor NVM support table */
+static const struct tb_nvm_vendor retimer_nvm_vendors[] = {
+       { 0x8087, &intel_retimer_nvm_ops },
+};
+
 /**
  * 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().
+ * of error returns ERR_PTR(). Specifically returns %-EOPNOTSUPP if the
+ * NVM format of the @dev is not known by the kernel.
  */
 struct tb_nvm *tb_nvm_alloc(struct device *dev)
 {
+       const struct tb_nvm_vendor_ops *vops = NULL;
        struct tb_nvm *nvm;
-       int ret;
+       int ret, i;
+
+       if (tb_is_switch(dev)) {
+               const struct tb_switch *sw = tb_to_switch(dev);
+
+               for (i = 0; i < ARRAY_SIZE(switch_nvm_vendors); i++) {
+                       const struct tb_nvm_vendor *v = &switch_nvm_vendors[i];
+
+                       if (v->vendor == sw->config.vendor_id) {
+                               vops = v->vops;
+                               break;
+                       }
+               }
+
+               if (!vops) {
+                       tb_sw_dbg(sw, "router NVM format of vendor %#x unknown\n",
+                                 sw->config.vendor_id);
+                       return ERR_PTR(-EOPNOTSUPP);
+               }
+       } else if (tb_is_retimer(dev)) {
+               const struct tb_retimer *rt = tb_to_retimer(dev);
+
+               for (i = 0; i < ARRAY_SIZE(retimer_nvm_vendors); i++) {
+                       const struct tb_nvm_vendor *v = &retimer_nvm_vendors[i];
+
+                       if (v->vendor == rt->vendor) {
+                               vops = v->vops;
+                               break;
+                       }
+               }
+
+               if (!vops) {
+                       dev_dbg(dev, "retimer NVM format of vendor %#x unknown\n",
+                               rt->vendor);
+                       return ERR_PTR(-EOPNOTSUPP);
+               }
+       } else {
+               return ERR_PTR(-EOPNOTSUPP);
+       }
 
        nvm = kzalloc(sizeof(*nvm), GFP_KERNEL);
        if (!nvm)
@@ -38,14 +334,85 @@ struct tb_nvm *tb_nvm_alloc(struct device *dev)
 
        nvm->id = ret;
        nvm->dev = dev;
+       nvm->vops = vops;
 
        return nvm;
 }
 
+/**
+ * tb_nvm_read_version() - Read and populate NVM version
+ * @nvm: NVM structure
+ *
+ * Uses vendor specific means to read out and fill in the existing
+ * active NVM version. Returns %0 in case of success and negative errno
+ * otherwise.
+ */
+int tb_nvm_read_version(struct tb_nvm *nvm)
+{
+       const struct tb_nvm_vendor_ops *vops = nvm->vops;
+
+       if (vops && vops->read_version)
+               return vops->read_version(nvm);
+
+       return -EOPNOTSUPP;
+}
+
+/**
+ * tb_nvm_validate() - Validate new NVM image
+ * @nvm: NVM structure
+ *
+ * Runs vendor specific validation over the new NVM image and if all
+ * checks pass returns %0. As side effect updates @nvm->buf_data_start
+ * and @nvm->buf_data_size fields to match the actual data to be written
+ * to the NVM.
+ *
+ * If the validation does not pass then returns negative errno.
+ */
+int tb_nvm_validate(struct tb_nvm *nvm)
+{
+       const struct tb_nvm_vendor_ops *vops = nvm->vops;
+       unsigned int image_size;
+       u8 *buf = nvm->buf;
+
+       if (!buf)
+               return -EINVAL;
+       if (!vops)
+               return -EOPNOTSUPP;
+
+       /* Just do basic image size checks */
+       image_size = nvm->buf_data_size;
+       if (image_size < NVM_MIN_SIZE || image_size > NVM_MAX_SIZE)
+               return -EINVAL;
+
+       /*
+        * Set the default data start in the buffer. The validate method
+        * below can change this if needed.
+        */
+       nvm->buf_data_start = buf;
+
+       return vops->validate ? vops->validate(nvm) : 0;
+}
+
+/**
+ * tb_nvm_write_headers() - Write headers before the rest of the image
+ * @nvm: NVM structure
+ *
+ * If the vendor NVM format requires writing headers before the rest of
+ * the image, this function does that. Can be called even if the device
+ * does not need this.
+ *
+ * Returns %0 in case of success and negative errno otherwise.
+ */
+int tb_nvm_write_headers(struct tb_nvm *nvm)
+{
+       const struct tb_nvm_vendor_ops *vops = nvm->vops;
+
+       return vops->write_headers ? vops->write_headers(nvm) : 0;
+}
+
 /**
  * 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)
  *
@@ -54,7 +421,7 @@ struct tb_nvm *tb_nvm_alloc(struct device *dev)
  * 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)
+int tb_nvm_add_active(struct tb_nvm *nvm, nvmem_reg_read_t reg_read)
 {
        struct nvmem_config config;
        struct nvmem_device *nvmem;
@@ -67,7 +434,7 @@ int tb_nvm_add_active(struct tb_nvm *nvm, size_t size, nvmem_reg_read_t reg_read
        config.id = nvm->id;
        config.stride = 4;
        config.word_size = 4;
-       config.size = size;
+       config.size = nvm->active_size;
        config.dev = nvm->dev;
        config.owner = THIS_MODULE;
        config.priv = nvm;
@@ -109,17 +476,17 @@ int tb_nvm_write_buf(struct tb_nvm *nvm, unsigned int offset, void *val,
 /**
  * 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.
+ * The size of the NVMem device is set to %NVM_MAX_SIZE.
+ *
  * 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)
+int tb_nvm_add_non_active(struct tb_nvm *nvm, nvmem_reg_write_t reg_write)
 {
        struct nvmem_config config;
        struct nvmem_device *nvmem;
@@ -132,7 +499,7 @@ int tb_nvm_add_non_active(struct tb_nvm *nvm, size_t size,
        config.id = nvm->id;
        config.stride = 4;
        config.word_size = 4;
-       config.size = size;
+       config.size = NVM_MAX_SIZE;
        config.dev = nvm->dev;
        config.owner = THIS_MODULE;
        config.priv = nvm;