summaryrefslogblamecommitdiffstats
path: root/src/interface/efi/efi_snp_hii.c
blob: c02233db0c8c5706af72a12c7b76adc0cb7cb798 (plain) (tree)
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430













































































































































































































































































































































































































































                                                                                
/*
 * Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

FILE_LICENCE ( GPL2_OR_LATER );

#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <ipxe/device.h>
#include <ipxe/netdevice.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/efi_hii.h>
#include <ipxe/efi/efi_snp.h>
#include <ipxe/efi/efi_strings.h>
#include <config/general.h>

/** EFI configuration access protocol GUID */
static EFI_GUID efi_hii_config_access_protocol_guid
	= EFI_HII_CONFIG_ACCESS_PROTOCOL_GUID;

/** EFI HII database protocol */
static EFI_HII_DATABASE_PROTOCOL *efihii;
EFI_REQUIRE_PROTOCOL ( EFI_HII_DATABASE_PROTOCOL, &efihii );

/** Local base GUID used for our EFI SNP formset */
#define EFI_SNP_FORMSET_GUID_BASE					\
	{ 0xc4f84019, 0x6dfd, 0x4a27,					\
	  { 0x9b, 0x94, 0xb7, 0x2e, 0x1f, 0xbc, 0xad, 0xca } }

/** Form identifiers used for our EFI SNP HII */
enum efi_snp_hii_form_id {
	EFI_SNP_FORM = 0x0001,		/**< The only form */
};

/** String identifiers used for our EFI SNP HII */
enum efi_snp_hii_string_id {
	/* Language name */
	EFI_SNP_LANGUAGE_NAME = 0x0001,
	/* Formset */
	EFI_SNP_FORMSET_TITLE, EFI_SNP_FORMSET_HELP,
	/* Product name */
	EFI_SNP_PRODUCT_PROMPT, EFI_SNP_PRODUCT_HELP, EFI_SNP_PRODUCT_TEXT,
	/* Version */
	EFI_SNP_VERSION_PROMPT, EFI_SNP_VERSION_HELP, EFI_SNP_VERSION_TEXT,
	/* Driver */
	EFI_SNP_DRIVER_PROMPT, EFI_SNP_DRIVER_HELP, EFI_SNP_DRIVER_TEXT,
	/* Device */
	EFI_SNP_DEVICE_PROMPT, EFI_SNP_DEVICE_HELP, EFI_SNP_DEVICE_TEXT,
	/* End of list */
	EFI_SNP_MAX_STRING_ID
};

/** EFI SNP formset */
struct efi_snp_formset {
	EFI_HII_PACKAGE_HEADER Header;
	EFI_IFR_FORM_SET_TYPE(2) FormSet;
	EFI_IFR_GUID_CLASS Class;
	EFI_IFR_GUID_SUBCLASS SubClass;
	EFI_IFR_FORM Form;
	EFI_IFR_TEXT ProductText;
	EFI_IFR_TEXT VersionText;
	EFI_IFR_TEXT DriverText;
	EFI_IFR_TEXT DeviceText;
	EFI_IFR_END EndForm;
	EFI_IFR_END EndFormSet;
} __attribute__ (( packed )) efi_snp_formset = {
	.Header = {
		.Length = sizeof ( efi_snp_formset ),
		.Type = EFI_HII_PACKAGE_FORMS,
	},
	.FormSet = EFI_IFR_FORM_SET ( EFI_SNP_FORMSET_GUID_BASE,
				      EFI_SNP_FORMSET_TITLE,
				      EFI_SNP_FORMSET_HELP,
				      typeof ( efi_snp_formset.FormSet ),
				      EFI_HII_PLATFORM_SETUP_FORMSET_GUID,
				      EFI_HII_IBM_UCM_COMPLIANT_FORMSET_GUID ),
	.Class = EFI_IFR_GUID_CLASS ( EFI_NETWORK_DEVICE_CLASS ),
	.SubClass = EFI_IFR_GUID_SUBCLASS ( 0x03 ),
	.Form = EFI_IFR_FORM ( EFI_SNP_FORM, EFI_SNP_FORMSET_TITLE ),
	.ProductText = EFI_IFR_TEXT ( EFI_SNP_PRODUCT_PROMPT,
				      EFI_SNP_PRODUCT_HELP,
				      EFI_SNP_PRODUCT_TEXT ),
	.VersionText = EFI_IFR_TEXT ( EFI_SNP_VERSION_PROMPT,
				      EFI_SNP_VERSION_HELP,
				      EFI_SNP_VERSION_TEXT ),
	.DriverText = EFI_IFR_TEXT ( EFI_SNP_DRIVER_PROMPT,
				     EFI_SNP_DRIVER_HELP,
				     EFI_SNP_DRIVER_TEXT ),
	.DeviceText = EFI_IFR_TEXT ( EFI_SNP_DEVICE_PROMPT,
				     EFI_SNP_DEVICE_HELP,
				     EFI_SNP_DEVICE_TEXT ),
	.EndForm = EFI_IFR_END(),
	.EndFormSet = EFI_IFR_END(),
};

/**
 * Generate EFI SNP string
 *
 * @v wbuf		Buffer
 * @v swlen		Size of buffer (in wide characters)
 * @v snpdev		SNP device
 * @ret wlen		Length of string (in wide characters)
 */
static int efi_snp_string ( wchar_t *wbuf, ssize_t swlen,
			    enum efi_snp_hii_string_id id,
			    struct efi_snp_device *snpdev ) {
	struct net_device *netdev = snpdev->netdev;
	struct device *dev = netdev->dev;

	switch ( id ) {
	case EFI_SNP_LANGUAGE_NAME:
		return efi_ssnprintf ( wbuf, swlen, "English" );
	case EFI_SNP_FORMSET_TITLE:
		return efi_ssnprintf ( wbuf, swlen, "%s (%s)",
				       ( PRODUCT_NAME[0] ?
					 PRODUCT_NAME : PRODUCT_SHORT_NAME ),
				       netdev_addr ( netdev ) );
	case EFI_SNP_FORMSET_HELP:
		return efi_ssnprintf ( wbuf, swlen,
				       "Configure " PRODUCT_SHORT_NAME );
	case EFI_SNP_PRODUCT_PROMPT:
		return efi_ssnprintf ( wbuf, swlen, "Name" );
	case EFI_SNP_PRODUCT_HELP:
		return efi_ssnprintf ( wbuf, swlen, "Firmware product name" );
	case EFI_SNP_PRODUCT_TEXT:
		return efi_ssnprintf ( wbuf, swlen, "%s",
				       ( PRODUCT_NAME[0] ?
					 PRODUCT_NAME : PRODUCT_SHORT_NAME ) );
	case EFI_SNP_VERSION_PROMPT:
		return efi_ssnprintf ( wbuf, swlen, "Version" );
	case EFI_SNP_VERSION_HELP:
		return efi_ssnprintf ( wbuf, swlen, "Firmware version" );
	case EFI_SNP_VERSION_TEXT:
		return efi_ssnprintf ( wbuf, swlen, VERSION );
	case EFI_SNP_DRIVER_PROMPT:
		return efi_ssnprintf ( wbuf, swlen, "Driver" );
	case EFI_SNP_DRIVER_HELP:
		return efi_ssnprintf ( wbuf, swlen, "Firmware driver" );
	case EFI_SNP_DRIVER_TEXT:
		return efi_ssnprintf ( wbuf, swlen, "%s", dev->driver_name );
	case EFI_SNP_DEVICE_PROMPT:
		return efi_ssnprintf ( wbuf, swlen, "Device" );
	case EFI_SNP_DEVICE_HELP:
		return efi_ssnprintf ( wbuf, swlen, "Hardware device" );
	case EFI_SNP_DEVICE_TEXT:
		return efi_ssnprintf ( wbuf, swlen, "%s", dev->name );
	default:
		assert ( 0 );
		return 0;
	}
}

/**
 * Generate EFI SNP string package
 *
 * @v strings		String package header buffer
 * @v max_len		Buffer length
 * @v snpdev		SNP device
 * @ret len		Length of string package
 */
static int efi_snp_strings ( EFI_HII_STRING_PACKAGE_HDR *strings,
			     size_t max_len, struct efi_snp_device *snpdev ) {
	static const char language[] = "en-us";
	void *buf = strings;
	ssize_t remaining = max_len;
	size_t hdrsize;
	EFI_HII_SIBT_STRING_UCS2_BLOCK *string;
	ssize_t wremaining;
	size_t string_wlen;
	unsigned int id;
	EFI_HII_STRING_BLOCK *end;
	size_t len;

	/* Calculate header size */
	hdrsize = ( offsetof ( typeof ( *strings ), Language ) +
		    sizeof ( language ) );
	buf += hdrsize;
	remaining -= hdrsize;

	/* Fill in strings */
	for ( id = 1 ; id < EFI_SNP_MAX_STRING_ID ; id++ ) {
		string = buf;
		if ( remaining >= ( ( ssize_t ) sizeof ( string->Header ) ) )
			string->Header.BlockType = EFI_HII_SIBT_STRING_UCS2;
		buf += offsetof ( typeof ( *string ), StringText );
		remaining -= offsetof ( typeof ( *string ), StringText );
		wremaining = ( remaining /
			       ( ( ssize_t ) sizeof ( string->StringText[0] )));
		assert ( ! ( ( remaining <= 0 ) && ( wremaining > 0 ) ) );
		string_wlen = efi_snp_string ( string->StringText, wremaining,
					       id, snpdev );
		buf += ( ( string_wlen + 1 /* wNUL */ ) *
			 sizeof ( string->StringText[0] ) );
		remaining -= ( ( string_wlen + 1 /* wNUL */ ) *
			       sizeof ( string->StringText[0] ) );
	}

	/* Fill in end marker */
	end = buf;
	if ( remaining >= ( ( ssize_t ) sizeof ( *end ) ) )
		end->BlockType = EFI_HII_SIBT_END;
	buf += sizeof ( *end );
	remaining -= sizeof ( *end );

	/* Calculate overall length */
	len = ( max_len - remaining );

	/* Fill in string package header */
	if ( strings ) {
		memset ( strings, 0, sizeof ( *strings ) );
		strings->Header.Length = len;
		strings->Header.Type = EFI_HII_PACKAGE_STRINGS;
		strings->HdrSize = hdrsize;
		strings->StringInfoOffset = hdrsize;
		strings->LanguageName = EFI_SNP_LANGUAGE_NAME;
		memcpy ( strings->Language, language, sizeof ( language ) );
	}

	return len;
}

/**
 * Generate EFI SNP package list
 *
 * @v snpdev		SNP device
 * @ret package_list	Package list, or NULL on error
 *
 * The package list is allocated using malloc(), and must eventually
 * be freed by the caller.
 */
static EFI_HII_PACKAGE_LIST_HEADER *
efi_snp_package_list ( struct efi_snp_device *snpdev ) {
	size_t strings_len = efi_snp_strings ( NULL, 0, snpdev );
	struct {
		EFI_HII_PACKAGE_LIST_HEADER header;
		struct efi_snp_formset formset;
		union {
			EFI_HII_STRING_PACKAGE_HDR strings;
			uint8_t pad[strings_len];
		} __attribute__ (( packed )) strings;
		EFI_HII_PACKAGE_HEADER end;
	} __attribute__ (( packed )) *package_list;

	/* Allocate package list */
	package_list = zalloc ( sizeof ( *package_list ) );
	if ( ! package_list )
		return NULL;

	/* Create a unique GUID for this package list and formset */
	efi_snp_formset.FormSet.FormSet.Guid.Data1++;

	/* Populate package list */
	memcpy ( &package_list->header.PackageListGuid,
		 &efi_snp_formset.FormSet.FormSet.Guid,
		 sizeof ( package_list->header.PackageListGuid ) );
	package_list->header.PackageLength = sizeof ( *package_list );
	memcpy ( &package_list->formset, &efi_snp_formset,
		 sizeof ( package_list->formset ) );
	efi_snp_strings ( &package_list->strings.strings,
			  sizeof ( package_list->strings ), snpdev );
	package_list->end.Length = sizeof ( package_list->end );
	package_list->end.Type = EFI_HII_PACKAGE_END;

	return &package_list->header;
}

/**
 * Fetch configuration
 *
 * @v hii		HII configuration access protocol
 * @v request		Configuration to fetch
 * @ret progress	Progress made through configuration to fetch
 * @ret results		Query results
 * @ret efirc		EFI status code
 */
static EFI_STATUS EFIAPI
efi_snp_hii_extract_config ( const EFI_HII_CONFIG_ACCESS_PROTOCOL *hii,
			     EFI_STRING request, EFI_STRING *progress,
			     EFI_STRING *results __unused ) {
	struct efi_snp_device *snpdev =
		container_of ( hii, struct efi_snp_device, hii );

	DBGC ( snpdev, "SNPDEV %p ExtractConfig \"%ls\"\n", snpdev, request );

	*progress = request;
	return EFI_INVALID_PARAMETER;
}

/**
 * Store configuration
 *
 * @v hii		HII configuration access protocol
 * @v config		Configuration to store
 * @ret progress	Progress made through configuration to store
 * @ret efirc		EFI status code
 */
static EFI_STATUS EFIAPI
efi_snp_hii_route_config ( const EFI_HII_CONFIG_ACCESS_PROTOCOL *hii,
			   EFI_STRING config, EFI_STRING *progress ) {
	struct efi_snp_device *snpdev =
		container_of ( hii, struct efi_snp_device, hii );

	DBGC ( snpdev, "SNPDEV %p RouteConfig \"%ls\"\n", snpdev, config );

	*progress = config;
	return EFI_INVALID_PARAMETER;
}

/**
 * Handle form actions
 *
 * @v hii		HII configuration access protocol
 * @v action		Form browser action
 * @v question_id	Question ID
 * @v type		Type of value
 * @v value		Value
 * @ret action_request	Action requested by driver
 * @ret efirc		EFI status code
 */
static EFI_STATUS EFIAPI
efi_snp_hii_callback ( const EFI_HII_CONFIG_ACCESS_PROTOCOL *hii,
		       EFI_BROWSER_ACTION action __unused,
		       EFI_QUESTION_ID question_id __unused,
		       UINT8 type __unused, EFI_IFR_TYPE_VALUE *value __unused,
		       EFI_BROWSER_ACTION_REQUEST *action_request __unused ) {
	struct efi_snp_device *snpdev =
		container_of ( hii, struct efi_snp_device, hii );

	DBGC ( snpdev, "SNPDEV %p Callback\n", snpdev );
	return EFI_UNSUPPORTED;
}

/** HII configuration access protocol */
static EFI_HII_CONFIG_ACCESS_PROTOCOL efi_snp_device_hii = {
	.ExtractConfig	= efi_snp_hii_extract_config,
	.RouteConfig	= efi_snp_hii_route_config,
	.Callback	= efi_snp_hii_callback,
};

/**
 * Install HII protocol and packages for SNP device
 *
 * @v snpdev		SNP device
 * @ret rc		Return status code
 */
int efi_snp_hii_install ( struct efi_snp_device *snpdev ) {
	EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
	int efirc;
	int rc;

	/* Initialise HII protocol */
	memcpy ( &snpdev->hii, &efi_snp_device_hii, sizeof ( snpdev->hii ) );

	/* Create HII package list */
	snpdev->package_list = efi_snp_package_list ( snpdev );
	if ( ! snpdev->package_list ) {
		DBGC ( snpdev, "SNPDEV %p could not create HII package list\n",
		       snpdev );
		rc = -ENOMEM;
		goto err_build_package_list;
	}

	/* Add HII packages */
	if ( ( efirc = efihii->NewPackageList ( efihii, snpdev->package_list,
						snpdev->handle,
						&snpdev->hii_handle ) ) != 0 ) {
		DBGC ( snpdev, "SNPDEV %p could not add HII packages: %s\n",
		       snpdev, efi_strerror ( efirc ) );
		rc = EFIRC_TO_RC ( efirc );
		goto err_new_package_list;
	}

	/* Install HII protocol */
	if ( ( efirc = bs->InstallMultipleProtocolInterfaces (
			 &snpdev->handle,
			 &efi_hii_config_access_protocol_guid, &snpdev->hii,
			 NULL ) ) != 0 ) {
		DBGC ( snpdev, "SNPDEV %p could not install HII protocol: %s\n",
		       snpdev, efi_strerror ( efirc ) );
		rc = EFIRC_TO_RC ( efirc );
		goto err_install_protocol;
	}

	return 0;

	bs->UninstallMultipleProtocolInterfaces (
			snpdev->handle,
			&efi_hii_config_access_protocol_guid, &snpdev->hii,
			NULL );
 err_install_protocol:
	efihii->RemovePackageList ( efihii, snpdev->hii_handle );
 err_new_package_list:
	free ( snpdev->package_list );
	snpdev->package_list = NULL;
 err_build_package_list:
	return rc;
}

/**
 * Uninstall HII protocol and package for SNP device
 *
 * @v snpdev		SNP device
 */
void efi_snp_hii_uninstall ( struct efi_snp_device *snpdev ) {
	EFI_BOOT_SERVICES *bs = efi_systab->BootServices;

	bs->UninstallMultipleProtocolInterfaces (
			snpdev->handle,
			&efi_hii_config_access_protocol_guid, &snpdev->hii,
			NULL );
	efihii->RemovePackageList ( efihii, snpdev->hii_handle );
	free ( snpdev->package_list );
	snpdev->package_list = NULL;
}