linux/drivers/thunderbolt/nvm.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * NVM helpers
   4 *
   5 * Copyright (C) 2020, Intel Corporation
   6 * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
   7 */
   8
   9#include <linux/idr.h>
  10#include <linux/slab.h>
  11#include <linux/vmalloc.h>
  12
  13#include "tb.h"
  14
  15static DEFINE_IDA(nvm_ida);
  16
  17/**
  18 * tb_nvm_alloc() - Allocate new NVM structure
  19 * @dev: Device owning the NVM
  20 *
  21 * Allocates new NVM structure with unique @id and returns it. In case
  22 * of error returns ERR_PTR().
  23 */
  24struct tb_nvm *tb_nvm_alloc(struct device *dev)
  25{
  26        struct tb_nvm *nvm;
  27        int ret;
  28
  29        nvm = kzalloc(sizeof(*nvm), GFP_KERNEL);
  30        if (!nvm)
  31                return ERR_PTR(-ENOMEM);
  32
  33        ret = ida_simple_get(&nvm_ida, 0, 0, GFP_KERNEL);
  34        if (ret < 0) {
  35                kfree(nvm);
  36                return ERR_PTR(ret);
  37        }
  38
  39        nvm->id = ret;
  40        nvm->dev = dev;
  41
  42        return nvm;
  43}
  44
  45/**
  46 * tb_nvm_add_active() - Adds active NVMem device to NVM
  47 * @nvm: NVM structure
  48 * @size: Size of the active NVM in bytes
  49 * @reg_read: Pointer to the function to read the NVM (passed directly to the
  50 *            NVMem device)
  51 *
  52 * Registers new active NVmem device for @nvm. The @reg_read is called
  53 * directly from NVMem so it must handle possible concurrent access if
  54 * needed. The first parameter passed to @reg_read is @nvm structure.
  55 * Returns %0 in success and negative errno otherwise.
  56 */
  57int tb_nvm_add_active(struct tb_nvm *nvm, size_t size, nvmem_reg_read_t reg_read)
  58{
  59        struct nvmem_config config;
  60        struct nvmem_device *nvmem;
  61
  62        memset(&config, 0, sizeof(config));
  63
  64        config.name = "nvm_active";
  65        config.reg_read = reg_read;
  66        config.read_only = true;
  67        config.id = nvm->id;
  68        config.stride = 4;
  69        config.word_size = 4;
  70        config.size = size;
  71        config.dev = nvm->dev;
  72        config.owner = THIS_MODULE;
  73        config.priv = nvm;
  74
  75        nvmem = nvmem_register(&config);
  76        if (IS_ERR(nvmem))
  77                return PTR_ERR(nvmem);
  78
  79        nvm->active = nvmem;
  80        return 0;
  81}
  82
  83/**
  84 * tb_nvm_write_buf() - Write data to @nvm buffer
  85 * @nvm: NVM structure
  86 * @offset: Offset where to write the data
  87 * @val: Data buffer to write
  88 * @bytes: Number of bytes to write
  89 *
  90 * Helper function to cache the new NVM image before it is actually
  91 * written to the flash. Copies @bytes from @val to @nvm->buf starting
  92 * from @offset.
  93 */
  94int tb_nvm_write_buf(struct tb_nvm *nvm, unsigned int offset, void *val,
  95                     size_t bytes)
  96{
  97        if (!nvm->buf) {
  98                nvm->buf = vmalloc(NVM_MAX_SIZE);
  99                if (!nvm->buf)
 100                        return -ENOMEM;
 101        }
 102
 103        nvm->flushed = false;
 104        nvm->buf_data_size = offset + bytes;
 105        memcpy(nvm->buf + offset, val, bytes);
 106        return 0;
 107}
 108
 109/**
 110 * tb_nvm_add_non_active() - Adds non-active NVMem device to NVM
 111 * @nvm: NVM structure
 112 * @size: Size of the non-active NVM in bytes
 113 * @reg_write: Pointer to the function to write the NVM (passed directly
 114 *             to the NVMem device)
 115 *
 116 * Registers new non-active NVmem device for @nvm. The @reg_write is called
 117 * directly from NVMem so it must handle possible concurrent access if
 118 * needed. The first parameter passed to @reg_write is @nvm structure.
 119 * Returns %0 in success and negative errno otherwise.
 120 */
 121int tb_nvm_add_non_active(struct tb_nvm *nvm, size_t size,
 122                          nvmem_reg_write_t reg_write)
 123{
 124        struct nvmem_config config;
 125        struct nvmem_device *nvmem;
 126
 127        memset(&config, 0, sizeof(config));
 128
 129        config.name = "nvm_non_active";
 130        config.reg_write = reg_write;
 131        config.root_only = true;
 132        config.id = nvm->id;
 133        config.stride = 4;
 134        config.word_size = 4;
 135        config.size = size;
 136        config.dev = nvm->dev;
 137        config.owner = THIS_MODULE;
 138        config.priv = nvm;
 139
 140        nvmem = nvmem_register(&config);
 141        if (IS_ERR(nvmem))
 142                return PTR_ERR(nvmem);
 143
 144        nvm->non_active = nvmem;
 145        return 0;
 146}
 147
 148/**
 149 * tb_nvm_free() - Release NVM and its resources
 150 * @nvm: NVM structure to release
 151 *
 152 * Releases NVM and the NVMem devices if they were registered.
 153 */
 154void tb_nvm_free(struct tb_nvm *nvm)
 155{
 156        if (nvm) {
 157                if (nvm->non_active)
 158                        nvmem_unregister(nvm->non_active);
 159                if (nvm->active)
 160                        nvmem_unregister(nvm->active);
 161                vfree(nvm->buf);
 162                ida_simple_remove(&nvm_ida, nvm->id);
 163        }
 164        kfree(nvm);
 165}
 166
 167/**
 168 * tb_nvm_read_data() - Read data from NVM
 169 * @address: Start address on the flash
 170 * @buf: Buffer where the read data is copied
 171 * @size: Size of the buffer in bytes
 172 * @retries: Number of retries if block read fails
 173 * @read_block: Function that reads block from the flash
 174 * @read_block_data: Data passsed to @read_block
 175 *
 176 * This is a generic function that reads data from NVM or NVM like
 177 * device.
 178 *
 179 * Returns %0 on success and negative errno otherwise.
 180 */
 181int tb_nvm_read_data(unsigned int address, void *buf, size_t size,
 182                     unsigned int retries, read_block_fn read_block,
 183                     void *read_block_data)
 184{
 185        do {
 186                unsigned int dwaddress, dwords, offset;
 187                u8 data[NVM_DATA_DWORDS * 4];
 188                size_t nbytes;
 189                int ret;
 190
 191                offset = address & 3;
 192                nbytes = min_t(size_t, size + offset, NVM_DATA_DWORDS * 4);
 193
 194                dwaddress = address / 4;
 195                dwords = ALIGN(nbytes, 4) / 4;
 196
 197                ret = read_block(read_block_data, dwaddress, data, dwords);
 198                if (ret) {
 199                        if (ret != -ENODEV && retries--)
 200                                continue;
 201                        return ret;
 202                }
 203
 204                nbytes -= offset;
 205                memcpy(buf, data + offset, nbytes);
 206
 207                size -= nbytes;
 208                address += nbytes;
 209                buf += nbytes;
 210        } while (size > 0);
 211
 212        return 0;
 213}
 214
 215/**
 216 * tb_nvm_write_data() - Write data to NVM
 217 * @address: Start address on the flash
 218 * @buf: Buffer where the data is copied from
 219 * @size: Size of the buffer in bytes
 220 * @retries: Number of retries if the block write fails
 221 * @write_block: Function that writes block to the flash
 222 * @write_block_data: Data passwd to @write_block
 223 *
 224 * This is generic function that writes data to NVM or NVM like device.
 225 *
 226 * Returns %0 on success and negative errno otherwise.
 227 */
 228int tb_nvm_write_data(unsigned int address, const void *buf, size_t size,
 229                      unsigned int retries, write_block_fn write_block,
 230                      void *write_block_data)
 231{
 232        do {
 233                unsigned int offset, dwaddress;
 234                u8 data[NVM_DATA_DWORDS * 4];
 235                size_t nbytes;
 236                int ret;
 237
 238                offset = address & 3;
 239                nbytes = min_t(u32, size + offset, NVM_DATA_DWORDS * 4);
 240
 241                memcpy(data + offset, buf, nbytes);
 242
 243                dwaddress = address / 4;
 244                ret = write_block(write_block_data, dwaddress, data, nbytes / 4);
 245                if (ret) {
 246                        if (ret == -ETIMEDOUT) {
 247                                if (retries--)
 248                                        continue;
 249                                ret = -EIO;
 250                        }
 251                        return ret;
 252                }
 253
 254                size -= nbytes;
 255                address += nbytes;
 256                buf += nbytes;
 257        } while (size > 0);
 258
 259        return 0;
 260}
 261
 262void tb_nvm_exit(void)
 263{
 264        ida_destroy(&nvm_ida);
 265}
 266