162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * NVM helpers 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2020, Intel Corporation 662306a36Sopenharmony_ci * Author: Mika Westerberg <mika.westerberg@linux.intel.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/idr.h> 1062306a36Sopenharmony_ci#include <linux/slab.h> 1162306a36Sopenharmony_ci#include <linux/vmalloc.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include "tb.h" 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#define NVM_MIN_SIZE SZ_32K 1662306a36Sopenharmony_ci#define NVM_MAX_SIZE SZ_1M 1762306a36Sopenharmony_ci#define NVM_DATA_DWORDS 16 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/* Intel specific NVM offsets */ 2062306a36Sopenharmony_ci#define INTEL_NVM_DEVID 0x05 2162306a36Sopenharmony_ci#define INTEL_NVM_VERSION 0x08 2262306a36Sopenharmony_ci#define INTEL_NVM_CSS 0x10 2362306a36Sopenharmony_ci#define INTEL_NVM_FLASH_SIZE 0x45 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* ASMedia specific NVM offsets */ 2662306a36Sopenharmony_ci#define ASMEDIA_NVM_DATE 0x1c 2762306a36Sopenharmony_ci#define ASMEDIA_NVM_VERSION 0x28 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistatic DEFINE_IDA(nvm_ida); 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci/** 3262306a36Sopenharmony_ci * struct tb_nvm_vendor_ops - Vendor specific NVM operations 3362306a36Sopenharmony_ci * @read_version: Reads out NVM version from the flash 3462306a36Sopenharmony_ci * @validate: Validates the NVM image before update (optional) 3562306a36Sopenharmony_ci * @write_headers: Writes headers before the rest of the image (optional) 3662306a36Sopenharmony_ci */ 3762306a36Sopenharmony_cistruct tb_nvm_vendor_ops { 3862306a36Sopenharmony_ci int (*read_version)(struct tb_nvm *nvm); 3962306a36Sopenharmony_ci int (*validate)(struct tb_nvm *nvm); 4062306a36Sopenharmony_ci int (*write_headers)(struct tb_nvm *nvm); 4162306a36Sopenharmony_ci}; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci/** 4462306a36Sopenharmony_ci * struct tb_nvm_vendor - Vendor to &struct tb_nvm_vendor_ops mapping 4562306a36Sopenharmony_ci * @vendor: Vendor ID 4662306a36Sopenharmony_ci * @vops: Vendor specific NVM operations 4762306a36Sopenharmony_ci * 4862306a36Sopenharmony_ci * Maps vendor ID to NVM vendor operations. If there is no mapping then 4962306a36Sopenharmony_ci * NVM firmware upgrade is disabled for the device. 5062306a36Sopenharmony_ci */ 5162306a36Sopenharmony_cistruct tb_nvm_vendor { 5262306a36Sopenharmony_ci u16 vendor; 5362306a36Sopenharmony_ci const struct tb_nvm_vendor_ops *vops; 5462306a36Sopenharmony_ci}; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic int intel_switch_nvm_version(struct tb_nvm *nvm) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci struct tb_switch *sw = tb_to_switch(nvm->dev); 5962306a36Sopenharmony_ci u32 val, nvm_size, hdr_size; 6062306a36Sopenharmony_ci int ret; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci /* 6362306a36Sopenharmony_ci * If the switch is in safe-mode the only accessible portion of 6462306a36Sopenharmony_ci * the NVM is the non-active one where userspace is expected to 6562306a36Sopenharmony_ci * write new functional NVM. 6662306a36Sopenharmony_ci */ 6762306a36Sopenharmony_ci if (sw->safe_mode) 6862306a36Sopenharmony_ci return 0; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci ret = tb_switch_nvm_read(sw, INTEL_NVM_FLASH_SIZE, &val, sizeof(val)); 7162306a36Sopenharmony_ci if (ret) 7262306a36Sopenharmony_ci return ret; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci hdr_size = sw->generation < 3 ? SZ_8K : SZ_16K; 7562306a36Sopenharmony_ci nvm_size = (SZ_1M << (val & 7)) / 8; 7662306a36Sopenharmony_ci nvm_size = (nvm_size - hdr_size) / 2; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci ret = tb_switch_nvm_read(sw, INTEL_NVM_VERSION, &val, sizeof(val)); 7962306a36Sopenharmony_ci if (ret) 8062306a36Sopenharmony_ci return ret; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci nvm->major = (val >> 16) & 0xff; 8362306a36Sopenharmony_ci nvm->minor = (val >> 8) & 0xff; 8462306a36Sopenharmony_ci nvm->active_size = nvm_size; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci return 0; 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic int intel_switch_nvm_validate(struct tb_nvm *nvm) 9062306a36Sopenharmony_ci{ 9162306a36Sopenharmony_ci struct tb_switch *sw = tb_to_switch(nvm->dev); 9262306a36Sopenharmony_ci unsigned int image_size, hdr_size; 9362306a36Sopenharmony_ci u16 ds_size, device_id; 9462306a36Sopenharmony_ci u8 *buf = nvm->buf; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci image_size = nvm->buf_data_size; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci /* 9962306a36Sopenharmony_ci * FARB pointer must point inside the image and must at least 10062306a36Sopenharmony_ci * contain parts of the digital section we will be reading here. 10162306a36Sopenharmony_ci */ 10262306a36Sopenharmony_ci hdr_size = (*(u32 *)buf) & 0xffffff; 10362306a36Sopenharmony_ci if (hdr_size + INTEL_NVM_DEVID + 2 >= image_size) 10462306a36Sopenharmony_ci return -EINVAL; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci /* Digital section start should be aligned to 4k page */ 10762306a36Sopenharmony_ci if (!IS_ALIGNED(hdr_size, SZ_4K)) 10862306a36Sopenharmony_ci return -EINVAL; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci /* 11162306a36Sopenharmony_ci * Read digital section size and check that it also fits inside 11262306a36Sopenharmony_ci * the image. 11362306a36Sopenharmony_ci */ 11462306a36Sopenharmony_ci ds_size = *(u16 *)(buf + hdr_size); 11562306a36Sopenharmony_ci if (ds_size >= image_size) 11662306a36Sopenharmony_ci return -EINVAL; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci if (sw->safe_mode) 11962306a36Sopenharmony_ci return 0; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci /* 12262306a36Sopenharmony_ci * Make sure the device ID in the image matches the one 12362306a36Sopenharmony_ci * we read from the switch config space. 12462306a36Sopenharmony_ci */ 12562306a36Sopenharmony_ci device_id = *(u16 *)(buf + hdr_size + INTEL_NVM_DEVID); 12662306a36Sopenharmony_ci if (device_id != sw->config.device_id) 12762306a36Sopenharmony_ci return -EINVAL; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci /* Skip headers in the image */ 13062306a36Sopenharmony_ci nvm->buf_data_start = buf + hdr_size; 13162306a36Sopenharmony_ci nvm->buf_data_size = image_size - hdr_size; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci return 0; 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cistatic int intel_switch_nvm_write_headers(struct tb_nvm *nvm) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci struct tb_switch *sw = tb_to_switch(nvm->dev); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci if (sw->generation < 3) { 14162306a36Sopenharmony_ci int ret; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci /* Write CSS headers first */ 14462306a36Sopenharmony_ci ret = dma_port_flash_write(sw->dma_port, 14562306a36Sopenharmony_ci DMA_PORT_CSS_ADDRESS, nvm->buf + INTEL_NVM_CSS, 14662306a36Sopenharmony_ci DMA_PORT_CSS_MAX_SIZE); 14762306a36Sopenharmony_ci if (ret) 14862306a36Sopenharmony_ci return ret; 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci return 0; 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic const struct tb_nvm_vendor_ops intel_switch_nvm_ops = { 15562306a36Sopenharmony_ci .read_version = intel_switch_nvm_version, 15662306a36Sopenharmony_ci .validate = intel_switch_nvm_validate, 15762306a36Sopenharmony_ci .write_headers = intel_switch_nvm_write_headers, 15862306a36Sopenharmony_ci}; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cistatic int asmedia_switch_nvm_version(struct tb_nvm *nvm) 16162306a36Sopenharmony_ci{ 16262306a36Sopenharmony_ci struct tb_switch *sw = tb_to_switch(nvm->dev); 16362306a36Sopenharmony_ci u32 val; 16462306a36Sopenharmony_ci int ret; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci ret = tb_switch_nvm_read(sw, ASMEDIA_NVM_VERSION, &val, sizeof(val)); 16762306a36Sopenharmony_ci if (ret) 16862306a36Sopenharmony_ci return ret; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci nvm->major = (val << 16) & 0xff0000; 17162306a36Sopenharmony_ci nvm->major |= val & 0x00ff00; 17262306a36Sopenharmony_ci nvm->major |= (val >> 16) & 0x0000ff; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci ret = tb_switch_nvm_read(sw, ASMEDIA_NVM_DATE, &val, sizeof(val)); 17562306a36Sopenharmony_ci if (ret) 17662306a36Sopenharmony_ci return ret; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci nvm->minor = (val << 16) & 0xff0000; 17962306a36Sopenharmony_ci nvm->minor |= val & 0x00ff00; 18062306a36Sopenharmony_ci nvm->minor |= (val >> 16) & 0x0000ff; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci /* ASMedia NVM size is fixed to 512k */ 18362306a36Sopenharmony_ci nvm->active_size = SZ_512K; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci return 0; 18662306a36Sopenharmony_ci} 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_cistatic const struct tb_nvm_vendor_ops asmedia_switch_nvm_ops = { 18962306a36Sopenharmony_ci .read_version = asmedia_switch_nvm_version, 19062306a36Sopenharmony_ci}; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci/* Router vendor NVM support table */ 19362306a36Sopenharmony_cistatic const struct tb_nvm_vendor switch_nvm_vendors[] = { 19462306a36Sopenharmony_ci { 0x174c, &asmedia_switch_nvm_ops }, 19562306a36Sopenharmony_ci { PCI_VENDOR_ID_INTEL, &intel_switch_nvm_ops }, 19662306a36Sopenharmony_ci { 0x8087, &intel_switch_nvm_ops }, 19762306a36Sopenharmony_ci}; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cistatic int intel_retimer_nvm_version(struct tb_nvm *nvm) 20062306a36Sopenharmony_ci{ 20162306a36Sopenharmony_ci struct tb_retimer *rt = tb_to_retimer(nvm->dev); 20262306a36Sopenharmony_ci u32 val, nvm_size; 20362306a36Sopenharmony_ci int ret; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci ret = tb_retimer_nvm_read(rt, INTEL_NVM_VERSION, &val, sizeof(val)); 20662306a36Sopenharmony_ci if (ret) 20762306a36Sopenharmony_ci return ret; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci nvm->major = (val >> 16) & 0xff; 21062306a36Sopenharmony_ci nvm->minor = (val >> 8) & 0xff; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci ret = tb_retimer_nvm_read(rt, INTEL_NVM_FLASH_SIZE, &val, sizeof(val)); 21362306a36Sopenharmony_ci if (ret) 21462306a36Sopenharmony_ci return ret; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci nvm_size = (SZ_1M << (val & 7)) / 8; 21762306a36Sopenharmony_ci nvm_size = (nvm_size - SZ_16K) / 2; 21862306a36Sopenharmony_ci nvm->active_size = nvm_size; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci return 0; 22162306a36Sopenharmony_ci} 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_cistatic int intel_retimer_nvm_validate(struct tb_nvm *nvm) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci struct tb_retimer *rt = tb_to_retimer(nvm->dev); 22662306a36Sopenharmony_ci unsigned int image_size, hdr_size; 22762306a36Sopenharmony_ci u8 *buf = nvm->buf; 22862306a36Sopenharmony_ci u16 ds_size, device; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci image_size = nvm->buf_data_size; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci /* 23362306a36Sopenharmony_ci * FARB pointer must point inside the image and must at least 23462306a36Sopenharmony_ci * contain parts of the digital section we will be reading here. 23562306a36Sopenharmony_ci */ 23662306a36Sopenharmony_ci hdr_size = (*(u32 *)buf) & 0xffffff; 23762306a36Sopenharmony_ci if (hdr_size + INTEL_NVM_DEVID + 2 >= image_size) 23862306a36Sopenharmony_ci return -EINVAL; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci /* Digital section start should be aligned to 4k page */ 24162306a36Sopenharmony_ci if (!IS_ALIGNED(hdr_size, SZ_4K)) 24262306a36Sopenharmony_ci return -EINVAL; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci /* 24562306a36Sopenharmony_ci * Read digital section size and check that it also fits inside 24662306a36Sopenharmony_ci * the image. 24762306a36Sopenharmony_ci */ 24862306a36Sopenharmony_ci ds_size = *(u16 *)(buf + hdr_size); 24962306a36Sopenharmony_ci if (ds_size >= image_size) 25062306a36Sopenharmony_ci return -EINVAL; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci /* 25362306a36Sopenharmony_ci * Make sure the device ID in the image matches the retimer 25462306a36Sopenharmony_ci * hardware. 25562306a36Sopenharmony_ci */ 25662306a36Sopenharmony_ci device = *(u16 *)(buf + hdr_size + INTEL_NVM_DEVID); 25762306a36Sopenharmony_ci if (device != rt->device) 25862306a36Sopenharmony_ci return -EINVAL; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci /* Skip headers in the image */ 26162306a36Sopenharmony_ci nvm->buf_data_start = buf + hdr_size; 26262306a36Sopenharmony_ci nvm->buf_data_size = image_size - hdr_size; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci return 0; 26562306a36Sopenharmony_ci} 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_cistatic const struct tb_nvm_vendor_ops intel_retimer_nvm_ops = { 26862306a36Sopenharmony_ci .read_version = intel_retimer_nvm_version, 26962306a36Sopenharmony_ci .validate = intel_retimer_nvm_validate, 27062306a36Sopenharmony_ci}; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci/* Retimer vendor NVM support table */ 27362306a36Sopenharmony_cistatic const struct tb_nvm_vendor retimer_nvm_vendors[] = { 27462306a36Sopenharmony_ci { 0x8087, &intel_retimer_nvm_ops }, 27562306a36Sopenharmony_ci}; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci/** 27862306a36Sopenharmony_ci * tb_nvm_alloc() - Allocate new NVM structure 27962306a36Sopenharmony_ci * @dev: Device owning the NVM 28062306a36Sopenharmony_ci * 28162306a36Sopenharmony_ci * Allocates new NVM structure with unique @id and returns it. In case 28262306a36Sopenharmony_ci * of error returns ERR_PTR(). Specifically returns %-EOPNOTSUPP if the 28362306a36Sopenharmony_ci * NVM format of the @dev is not known by the kernel. 28462306a36Sopenharmony_ci */ 28562306a36Sopenharmony_cistruct tb_nvm *tb_nvm_alloc(struct device *dev) 28662306a36Sopenharmony_ci{ 28762306a36Sopenharmony_ci const struct tb_nvm_vendor_ops *vops = NULL; 28862306a36Sopenharmony_ci struct tb_nvm *nvm; 28962306a36Sopenharmony_ci int ret, i; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci if (tb_is_switch(dev)) { 29262306a36Sopenharmony_ci const struct tb_switch *sw = tb_to_switch(dev); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(switch_nvm_vendors); i++) { 29562306a36Sopenharmony_ci const struct tb_nvm_vendor *v = &switch_nvm_vendors[i]; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci if (v->vendor == sw->config.vendor_id) { 29862306a36Sopenharmony_ci vops = v->vops; 29962306a36Sopenharmony_ci break; 30062306a36Sopenharmony_ci } 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci if (!vops) { 30462306a36Sopenharmony_ci tb_sw_dbg(sw, "router NVM format of vendor %#x unknown\n", 30562306a36Sopenharmony_ci sw->config.vendor_id); 30662306a36Sopenharmony_ci return ERR_PTR(-EOPNOTSUPP); 30762306a36Sopenharmony_ci } 30862306a36Sopenharmony_ci } else if (tb_is_retimer(dev)) { 30962306a36Sopenharmony_ci const struct tb_retimer *rt = tb_to_retimer(dev); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(retimer_nvm_vendors); i++) { 31262306a36Sopenharmony_ci const struct tb_nvm_vendor *v = &retimer_nvm_vendors[i]; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci if (v->vendor == rt->vendor) { 31562306a36Sopenharmony_ci vops = v->vops; 31662306a36Sopenharmony_ci break; 31762306a36Sopenharmony_ci } 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci if (!vops) { 32162306a36Sopenharmony_ci dev_dbg(dev, "retimer NVM format of vendor %#x unknown\n", 32262306a36Sopenharmony_ci rt->vendor); 32362306a36Sopenharmony_ci return ERR_PTR(-EOPNOTSUPP); 32462306a36Sopenharmony_ci } 32562306a36Sopenharmony_ci } else { 32662306a36Sopenharmony_ci return ERR_PTR(-EOPNOTSUPP); 32762306a36Sopenharmony_ci } 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci nvm = kzalloc(sizeof(*nvm), GFP_KERNEL); 33062306a36Sopenharmony_ci if (!nvm) 33162306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci ret = ida_simple_get(&nvm_ida, 0, 0, GFP_KERNEL); 33462306a36Sopenharmony_ci if (ret < 0) { 33562306a36Sopenharmony_ci kfree(nvm); 33662306a36Sopenharmony_ci return ERR_PTR(ret); 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci nvm->id = ret; 34062306a36Sopenharmony_ci nvm->dev = dev; 34162306a36Sopenharmony_ci nvm->vops = vops; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci return nvm; 34462306a36Sopenharmony_ci} 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci/** 34762306a36Sopenharmony_ci * tb_nvm_read_version() - Read and populate NVM version 34862306a36Sopenharmony_ci * @nvm: NVM structure 34962306a36Sopenharmony_ci * 35062306a36Sopenharmony_ci * Uses vendor specific means to read out and fill in the existing 35162306a36Sopenharmony_ci * active NVM version. Returns %0 in case of success and negative errno 35262306a36Sopenharmony_ci * otherwise. 35362306a36Sopenharmony_ci */ 35462306a36Sopenharmony_ciint tb_nvm_read_version(struct tb_nvm *nvm) 35562306a36Sopenharmony_ci{ 35662306a36Sopenharmony_ci const struct tb_nvm_vendor_ops *vops = nvm->vops; 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci if (vops && vops->read_version) 35962306a36Sopenharmony_ci return vops->read_version(nvm); 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci return -EOPNOTSUPP; 36262306a36Sopenharmony_ci} 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci/** 36562306a36Sopenharmony_ci * tb_nvm_validate() - Validate new NVM image 36662306a36Sopenharmony_ci * @nvm: NVM structure 36762306a36Sopenharmony_ci * 36862306a36Sopenharmony_ci * Runs vendor specific validation over the new NVM image and if all 36962306a36Sopenharmony_ci * checks pass returns %0. As side effect updates @nvm->buf_data_start 37062306a36Sopenharmony_ci * and @nvm->buf_data_size fields to match the actual data to be written 37162306a36Sopenharmony_ci * to the NVM. 37262306a36Sopenharmony_ci * 37362306a36Sopenharmony_ci * If the validation does not pass then returns negative errno. 37462306a36Sopenharmony_ci */ 37562306a36Sopenharmony_ciint tb_nvm_validate(struct tb_nvm *nvm) 37662306a36Sopenharmony_ci{ 37762306a36Sopenharmony_ci const struct tb_nvm_vendor_ops *vops = nvm->vops; 37862306a36Sopenharmony_ci unsigned int image_size; 37962306a36Sopenharmony_ci u8 *buf = nvm->buf; 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci if (!buf) 38262306a36Sopenharmony_ci return -EINVAL; 38362306a36Sopenharmony_ci if (!vops) 38462306a36Sopenharmony_ci return -EOPNOTSUPP; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci /* Just do basic image size checks */ 38762306a36Sopenharmony_ci image_size = nvm->buf_data_size; 38862306a36Sopenharmony_ci if (image_size < NVM_MIN_SIZE || image_size > NVM_MAX_SIZE) 38962306a36Sopenharmony_ci return -EINVAL; 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci /* 39262306a36Sopenharmony_ci * Set the default data start in the buffer. The validate method 39362306a36Sopenharmony_ci * below can change this if needed. 39462306a36Sopenharmony_ci */ 39562306a36Sopenharmony_ci nvm->buf_data_start = buf; 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci return vops->validate ? vops->validate(nvm) : 0; 39862306a36Sopenharmony_ci} 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci/** 40162306a36Sopenharmony_ci * tb_nvm_write_headers() - Write headers before the rest of the image 40262306a36Sopenharmony_ci * @nvm: NVM structure 40362306a36Sopenharmony_ci * 40462306a36Sopenharmony_ci * If the vendor NVM format requires writing headers before the rest of 40562306a36Sopenharmony_ci * the image, this function does that. Can be called even if the device 40662306a36Sopenharmony_ci * does not need this. 40762306a36Sopenharmony_ci * 40862306a36Sopenharmony_ci * Returns %0 in case of success and negative errno otherwise. 40962306a36Sopenharmony_ci */ 41062306a36Sopenharmony_ciint tb_nvm_write_headers(struct tb_nvm *nvm) 41162306a36Sopenharmony_ci{ 41262306a36Sopenharmony_ci const struct tb_nvm_vendor_ops *vops = nvm->vops; 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci return vops->write_headers ? vops->write_headers(nvm) : 0; 41562306a36Sopenharmony_ci} 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci/** 41862306a36Sopenharmony_ci * tb_nvm_add_active() - Adds active NVMem device to NVM 41962306a36Sopenharmony_ci * @nvm: NVM structure 42062306a36Sopenharmony_ci * @reg_read: Pointer to the function to read the NVM (passed directly to the 42162306a36Sopenharmony_ci * NVMem device) 42262306a36Sopenharmony_ci * 42362306a36Sopenharmony_ci * Registers new active NVmem device for @nvm. The @reg_read is called 42462306a36Sopenharmony_ci * directly from NVMem so it must handle possible concurrent access if 42562306a36Sopenharmony_ci * needed. The first parameter passed to @reg_read is @nvm structure. 42662306a36Sopenharmony_ci * Returns %0 in success and negative errno otherwise. 42762306a36Sopenharmony_ci */ 42862306a36Sopenharmony_ciint tb_nvm_add_active(struct tb_nvm *nvm, nvmem_reg_read_t reg_read) 42962306a36Sopenharmony_ci{ 43062306a36Sopenharmony_ci struct nvmem_config config; 43162306a36Sopenharmony_ci struct nvmem_device *nvmem; 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci memset(&config, 0, sizeof(config)); 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci config.name = "nvm_active"; 43662306a36Sopenharmony_ci config.reg_read = reg_read; 43762306a36Sopenharmony_ci config.read_only = true; 43862306a36Sopenharmony_ci config.id = nvm->id; 43962306a36Sopenharmony_ci config.stride = 4; 44062306a36Sopenharmony_ci config.word_size = 4; 44162306a36Sopenharmony_ci config.size = nvm->active_size; 44262306a36Sopenharmony_ci config.dev = nvm->dev; 44362306a36Sopenharmony_ci config.owner = THIS_MODULE; 44462306a36Sopenharmony_ci config.priv = nvm; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci nvmem = nvmem_register(&config); 44762306a36Sopenharmony_ci if (IS_ERR(nvmem)) 44862306a36Sopenharmony_ci return PTR_ERR(nvmem); 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci nvm->active = nvmem; 45162306a36Sopenharmony_ci return 0; 45262306a36Sopenharmony_ci} 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci/** 45562306a36Sopenharmony_ci * tb_nvm_write_buf() - Write data to @nvm buffer 45662306a36Sopenharmony_ci * @nvm: NVM structure 45762306a36Sopenharmony_ci * @offset: Offset where to write the data 45862306a36Sopenharmony_ci * @val: Data buffer to write 45962306a36Sopenharmony_ci * @bytes: Number of bytes to write 46062306a36Sopenharmony_ci * 46162306a36Sopenharmony_ci * Helper function to cache the new NVM image before it is actually 46262306a36Sopenharmony_ci * written to the flash. Copies @bytes from @val to @nvm->buf starting 46362306a36Sopenharmony_ci * from @offset. 46462306a36Sopenharmony_ci */ 46562306a36Sopenharmony_ciint tb_nvm_write_buf(struct tb_nvm *nvm, unsigned int offset, void *val, 46662306a36Sopenharmony_ci size_t bytes) 46762306a36Sopenharmony_ci{ 46862306a36Sopenharmony_ci if (!nvm->buf) { 46962306a36Sopenharmony_ci nvm->buf = vmalloc(NVM_MAX_SIZE); 47062306a36Sopenharmony_ci if (!nvm->buf) 47162306a36Sopenharmony_ci return -ENOMEM; 47262306a36Sopenharmony_ci } 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci nvm->flushed = false; 47562306a36Sopenharmony_ci nvm->buf_data_size = offset + bytes; 47662306a36Sopenharmony_ci memcpy(nvm->buf + offset, val, bytes); 47762306a36Sopenharmony_ci return 0; 47862306a36Sopenharmony_ci} 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci/** 48162306a36Sopenharmony_ci * tb_nvm_add_non_active() - Adds non-active NVMem device to NVM 48262306a36Sopenharmony_ci * @nvm: NVM structure 48362306a36Sopenharmony_ci * @reg_write: Pointer to the function to write the NVM (passed directly 48462306a36Sopenharmony_ci * to the NVMem device) 48562306a36Sopenharmony_ci * 48662306a36Sopenharmony_ci * Registers new non-active NVmem device for @nvm. The @reg_write is called 48762306a36Sopenharmony_ci * directly from NVMem so it must handle possible concurrent access if 48862306a36Sopenharmony_ci * needed. The first parameter passed to @reg_write is @nvm structure. 48962306a36Sopenharmony_ci * The size of the NVMem device is set to %NVM_MAX_SIZE. 49062306a36Sopenharmony_ci * 49162306a36Sopenharmony_ci * Returns %0 in success and negative errno otherwise. 49262306a36Sopenharmony_ci */ 49362306a36Sopenharmony_ciint tb_nvm_add_non_active(struct tb_nvm *nvm, nvmem_reg_write_t reg_write) 49462306a36Sopenharmony_ci{ 49562306a36Sopenharmony_ci struct nvmem_config config; 49662306a36Sopenharmony_ci struct nvmem_device *nvmem; 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci memset(&config, 0, sizeof(config)); 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci config.name = "nvm_non_active"; 50162306a36Sopenharmony_ci config.reg_write = reg_write; 50262306a36Sopenharmony_ci config.root_only = true; 50362306a36Sopenharmony_ci config.id = nvm->id; 50462306a36Sopenharmony_ci config.stride = 4; 50562306a36Sopenharmony_ci config.word_size = 4; 50662306a36Sopenharmony_ci config.size = NVM_MAX_SIZE; 50762306a36Sopenharmony_ci config.dev = nvm->dev; 50862306a36Sopenharmony_ci config.owner = THIS_MODULE; 50962306a36Sopenharmony_ci config.priv = nvm; 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci nvmem = nvmem_register(&config); 51262306a36Sopenharmony_ci if (IS_ERR(nvmem)) 51362306a36Sopenharmony_ci return PTR_ERR(nvmem); 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_ci nvm->non_active = nvmem; 51662306a36Sopenharmony_ci return 0; 51762306a36Sopenharmony_ci} 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci/** 52062306a36Sopenharmony_ci * tb_nvm_free() - Release NVM and its resources 52162306a36Sopenharmony_ci * @nvm: NVM structure to release 52262306a36Sopenharmony_ci * 52362306a36Sopenharmony_ci * Releases NVM and the NVMem devices if they were registered. 52462306a36Sopenharmony_ci */ 52562306a36Sopenharmony_civoid tb_nvm_free(struct tb_nvm *nvm) 52662306a36Sopenharmony_ci{ 52762306a36Sopenharmony_ci if (nvm) { 52862306a36Sopenharmony_ci nvmem_unregister(nvm->non_active); 52962306a36Sopenharmony_ci nvmem_unregister(nvm->active); 53062306a36Sopenharmony_ci vfree(nvm->buf); 53162306a36Sopenharmony_ci ida_simple_remove(&nvm_ida, nvm->id); 53262306a36Sopenharmony_ci } 53362306a36Sopenharmony_ci kfree(nvm); 53462306a36Sopenharmony_ci} 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci/** 53762306a36Sopenharmony_ci * tb_nvm_read_data() - Read data from NVM 53862306a36Sopenharmony_ci * @address: Start address on the flash 53962306a36Sopenharmony_ci * @buf: Buffer where the read data is copied 54062306a36Sopenharmony_ci * @size: Size of the buffer in bytes 54162306a36Sopenharmony_ci * @retries: Number of retries if block read fails 54262306a36Sopenharmony_ci * @read_block: Function that reads block from the flash 54362306a36Sopenharmony_ci * @read_block_data: Data passsed to @read_block 54462306a36Sopenharmony_ci * 54562306a36Sopenharmony_ci * This is a generic function that reads data from NVM or NVM like 54662306a36Sopenharmony_ci * device. 54762306a36Sopenharmony_ci * 54862306a36Sopenharmony_ci * Returns %0 on success and negative errno otherwise. 54962306a36Sopenharmony_ci */ 55062306a36Sopenharmony_ciint tb_nvm_read_data(unsigned int address, void *buf, size_t size, 55162306a36Sopenharmony_ci unsigned int retries, read_block_fn read_block, 55262306a36Sopenharmony_ci void *read_block_data) 55362306a36Sopenharmony_ci{ 55462306a36Sopenharmony_ci do { 55562306a36Sopenharmony_ci unsigned int dwaddress, dwords, offset; 55662306a36Sopenharmony_ci u8 data[NVM_DATA_DWORDS * 4]; 55762306a36Sopenharmony_ci size_t nbytes; 55862306a36Sopenharmony_ci int ret; 55962306a36Sopenharmony_ci 56062306a36Sopenharmony_ci offset = address & 3; 56162306a36Sopenharmony_ci nbytes = min_t(size_t, size + offset, NVM_DATA_DWORDS * 4); 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci dwaddress = address / 4; 56462306a36Sopenharmony_ci dwords = ALIGN(nbytes, 4) / 4; 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci ret = read_block(read_block_data, dwaddress, data, dwords); 56762306a36Sopenharmony_ci if (ret) { 56862306a36Sopenharmony_ci if (ret != -ENODEV && retries--) 56962306a36Sopenharmony_ci continue; 57062306a36Sopenharmony_ci return ret; 57162306a36Sopenharmony_ci } 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci nbytes -= offset; 57462306a36Sopenharmony_ci memcpy(buf, data + offset, nbytes); 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci size -= nbytes; 57762306a36Sopenharmony_ci address += nbytes; 57862306a36Sopenharmony_ci buf += nbytes; 57962306a36Sopenharmony_ci } while (size > 0); 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci return 0; 58262306a36Sopenharmony_ci} 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci/** 58562306a36Sopenharmony_ci * tb_nvm_write_data() - Write data to NVM 58662306a36Sopenharmony_ci * @address: Start address on the flash 58762306a36Sopenharmony_ci * @buf: Buffer where the data is copied from 58862306a36Sopenharmony_ci * @size: Size of the buffer in bytes 58962306a36Sopenharmony_ci * @retries: Number of retries if the block write fails 59062306a36Sopenharmony_ci * @write_block: Function that writes block to the flash 59162306a36Sopenharmony_ci * @write_block_data: Data passwd to @write_block 59262306a36Sopenharmony_ci * 59362306a36Sopenharmony_ci * This is generic function that writes data to NVM or NVM like device. 59462306a36Sopenharmony_ci * 59562306a36Sopenharmony_ci * Returns %0 on success and negative errno otherwise. 59662306a36Sopenharmony_ci */ 59762306a36Sopenharmony_ciint tb_nvm_write_data(unsigned int address, const void *buf, size_t size, 59862306a36Sopenharmony_ci unsigned int retries, write_block_fn write_block, 59962306a36Sopenharmony_ci void *write_block_data) 60062306a36Sopenharmony_ci{ 60162306a36Sopenharmony_ci do { 60262306a36Sopenharmony_ci unsigned int offset, dwaddress; 60362306a36Sopenharmony_ci u8 data[NVM_DATA_DWORDS * 4]; 60462306a36Sopenharmony_ci size_t nbytes; 60562306a36Sopenharmony_ci int ret; 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_ci offset = address & 3; 60862306a36Sopenharmony_ci nbytes = min_t(u32, size + offset, NVM_DATA_DWORDS * 4); 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci memcpy(data + offset, buf, nbytes); 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci dwaddress = address / 4; 61362306a36Sopenharmony_ci ret = write_block(write_block_data, dwaddress, data, nbytes / 4); 61462306a36Sopenharmony_ci if (ret) { 61562306a36Sopenharmony_ci if (ret == -ETIMEDOUT) { 61662306a36Sopenharmony_ci if (retries--) 61762306a36Sopenharmony_ci continue; 61862306a36Sopenharmony_ci ret = -EIO; 61962306a36Sopenharmony_ci } 62062306a36Sopenharmony_ci return ret; 62162306a36Sopenharmony_ci } 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci size -= nbytes; 62462306a36Sopenharmony_ci address += nbytes; 62562306a36Sopenharmony_ci buf += nbytes; 62662306a36Sopenharmony_ci } while (size > 0); 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci return 0; 62962306a36Sopenharmony_ci} 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_civoid tb_nvm_exit(void) 63262306a36Sopenharmony_ci{ 63362306a36Sopenharmony_ci ida_destroy(&nvm_ida); 63462306a36Sopenharmony_ci} 635