summaryrefslogtreecommitdiff
path: root/src/vendorcode/google/chromeos/vpd_calibration.c
blob: e5f5b579d1ea92e408ba9141187ea6d06bcad39c (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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/* SPDX-License-Identifier: GPL-2.0-only */

#include <b64_decode.h>
#include <cbmem.h>
#include <console/console.h>
#include <stdlib.h>
#include <string.h>

#include <vendorcode/google/chromeos/chromeos.h>
#include <drivers/vpd/vpd.h>

/*
 * This file provides functions looking in the VPD for WiFi calibration data,
 * and if found, copying the calibration blobs into CBMEM.
 *
 * Per interface calibration data is stored in the VPD in opaque blobs. The
 * keys of the blobs follow one of two possible patterns:
 * "wifi_base64_calibration<N>" or "wifi_calibration<N>", where <N> is the
 * interface number.
 *
 * This function accommodates up to 4 interfaces. All calibration blobs found
 * in the VPD are packed into a single CBMEM entry as describe by the
 * structures below:
 */

/* This structure describes a single calibration data blob */
struct calibration_blob {
	uint32_t blob_size;  /* Total size. rounded up to fall on a 4 byte
				   boundary. */
	uint32_t key_size;   /* Size of the name of this entry, \0 included. */
	uint32_t value_size; /* Size of the value of this entry */
	/* Zero terminated name(key) goes here, immediately followed by value */
};

/*
 * This is the structure of the CBMEM entry containing WiFi calibration blobs.
 * It starts with the total size (header size included) followed by an
 * arbitrary number of concatenated 4 byte aligned calibration blobs.
 */
struct calibration_entry {
	uint32_t size;
	struct calibration_blob entries[0];  /* A varialble size container. */
};


#define MAX_WIFI_INTERFACE_COUNT 4

/*
 * Structure of the cache to keep information about calibration blobs present
 * in the VPD, one cache entry per VPD blob.
 *
 * Maintaing the cache allows to scan the VPD once, determine the CBMEM entry
 * memory requirements, then allocate as much room as necessary and fill it
 * up.
 */
struct vpd_blob_cache_t {
	/* The longest name template must fit with an extra character. */
	char key_name[40];
	void  *value_pointer;
	unsigned blob_size;
	unsigned key_size;
	unsigned value_size;
};

static const char * const templates[] = {
	"wifi_base64_calibrationX",
	"wifi_calibrationX"
};

/*
 * Scan the VPD for WiFi calibration data, checking for all possible key names
 * and caching discovered blobs.
 *
 * Return the sum of sizes of all blobs, as stored in CBMEM.
 */
static size_t fill_up_entries_cache(struct vpd_blob_cache_t *cache,
				    size_t max_entries, size_t *filled_entries)
{
	int i;
	int cbmem_entry_size = 0;
	size_t used_entries = 0;


	for (i = 0;
	     (i < ARRAY_SIZE(templates)) && (used_entries < max_entries);
	     i++) {
		int j;
		const int index_location = strlen(templates[i]) - 1;
		const int key_length = index_location + 2;

		if (key_length > sizeof(cache->key_name))
			continue;

		for (j = 0; j < MAX_WIFI_INTERFACE_COUNT; j++) {
			const void *payload;
			void *decoded_payload;
			int payload_size;
			size_t decoded_size;

			strcpy(cache->key_name, templates[i]);
			cache->key_name[index_location] = j + '0';

			payload = vpd_find(cache->key_name, &payload_size, VPD_RO_THEN_RW);
			if (!payload)
				continue;

			decoded_size = B64_DECODED_SIZE(payload_size);
			decoded_payload = malloc(decoded_size);
			if (!decoded_payload) {
				printk(BIOS_ERR,
				       "%s: failed allocating %zd bytes\n",
				       __func__, decoded_size);
				continue;
			}

			decoded_size = b64_decode(payload, payload_size,
						  decoded_payload);
			if (!decoded_size) {
				free(decoded_payload);
				printk(BIOS_ERR, "%s: failed decoding %s\n",
				       __func__, cache->key_name);
				continue;
			}

			cache->value_pointer = decoded_payload;
			cache->key_size = key_length;
			cache->value_size = decoded_size;
			cache->blob_size =
				ALIGN_UP(sizeof(struct calibration_blob) +
				      cache->key_size +
				      cache->value_size, 4);
			cbmem_entry_size += cache->blob_size;

			used_entries++;
			if (used_entries == max_entries)
				break;

			cache++;
		}
	}

	*filled_entries = used_entries;
	return cbmem_entry_size;
}

void cbmem_add_vpd_calibration_data(void)
{
	size_t cbmem_entry_size, filled_entries;
	struct calibration_entry *cbmem_entry;
	struct calibration_blob *cal_blob;
	int i;
	/*
	 * Allocate one more cache entry than max required, to make sure that
	 * the last entry can be identified by the key size of zero.
	 */
	struct vpd_blob_cache_t vpd_blob_cache[ARRAY_SIZE(templates) *
					       MAX_WIFI_INTERFACE_COUNT];

	cbmem_entry_size = fill_up_entries_cache(vpd_blob_cache,
						 ARRAY_SIZE(vpd_blob_cache),
						 &filled_entries);

	if (!cbmem_entry_size)
		return; /* No calibration data found in the VPD. */

	cbmem_entry_size += sizeof(struct calibration_entry);
	cbmem_entry = cbmem_add(CBMEM_ID_WIFI_CALIBRATION, cbmem_entry_size);
	if (!cbmem_entry) {
		printk(BIOS_ERR, "%s: no room in cbmem to add %zd bytes\n",
		       __func__, cbmem_entry_size);
		return;
	}

	cbmem_entry->size = cbmem_entry_size;

	/* Copy cached data into the CBMEM entry. */
	cal_blob = cbmem_entry->entries;

	for (i = 0; i < filled_entries; i++) {
		/* Use this as a pointer to the current cache entry. */
		struct vpd_blob_cache_t *cache = vpd_blob_cache + i;
		char *pointer;

		cal_blob->blob_size = cache->blob_size;
		cal_blob->key_size = cache->key_size;
		cal_blob->value_size = cache->value_size;

		/* copy the key */
		pointer = (char *)(cal_blob + 1);
		memcpy(pointer, cache->key_name, cache->key_size);

		/* and the value */
		pointer += cache->key_size;
		memcpy(pointer, cache->value_pointer, cache->value_size);
		free(cache->value_pointer);

		printk(BIOS_INFO, "%s: added %s to CBMEM\n",
		       __func__, cache->key_name);

		cal_blob = (struct calibration_blob *)
			((char *)cal_blob + cal_blob->blob_size);
	}
}