aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/thunderbolt/nvm.c
blob: 29de6d95c6e7fa8ad62c19c050a1babf34ba00a0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// SPDX-License-Identifier: GPL-2.0
/*
 * NVM helpers
 *
 * Copyright (C) 2020, Intel Corporation
 * Author: Mika Westerberg <mika.westerberg@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);
}