fu-provider-uefi.c 9.8 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
 *
 * Licensed under the GNU General Public License Version 2
 *
 * 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
 * (at your option) 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.
 */

#include "config.h"

24
#include <fwupd.h>
25
26
#include <glib-object.h>
#include <gio/gio.h>
27
#include <fwup.h>
28
29
30

#include "fu-cleanup.h"
#include "fu-device.h"
31
#include "fu-pending.h"
32
33
34
35
#include "fu-provider-uefi.h"

static void     fu_provider_uefi_finalize	(GObject	*object);

36
G_DEFINE_TYPE (FuProviderUefi, fu_provider_uefi, FU_TYPE_PROVIDER)
37

38
/**
39
 * fu_provider_uefi_get_name:
40
 **/
41
42
43
44
45
46
47
48
49
50
51
static const gchar *
fu_provider_uefi_get_name (FuProvider *provider)
{
	return "UEFI";
}

/**
 * fu_provider_uefi_find:
 **/
static fwup_resource *
fu_provider_uefi_find (fwup_resource_iter *iter, const gchar *guid_str, GError **error)
52
{
Richard Hughes's avatar
Richard Hughes committed
53
54
	efi_guid_t *guid_raw;
	fwup_resource *re_matched = NULL;
55
	fwup_resource *re = NULL;
Richard Hughes's avatar
Richard Hughes committed
56
	_cleanup_free_ gchar *guid_str_tmp = NULL;
57
58

	/* get the hardware we're referencing */
Richard Hughes's avatar
Richard Hughes committed
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
	guid_str_tmp = g_strdup ("00000000-0000-0000-0000-000000000000");
	while (fwup_resource_iter_next (iter, &re) > 0) {

		/* convert to strings */
		fwup_get_guid (re, &guid_raw);
		if (efi_guid_to_str (guid_raw, &guid_str_tmp) < 0) {
			g_warning ("failed to convert guid to string");
			continue;
		}

		/* FIXME: also match hardware_instance too */
		if (g_strcmp0 (guid_str, guid_str_tmp) == 0) {
			re_matched = re;
			break;
		}
	}

	/* paradoxically, no hardware matched */
	if (re_matched == NULL) {
		g_set_error (error,
79
80
			     FWUPD_ERROR,
			     FWUPD_ERROR_NOT_SUPPORTED,
Richard Hughes's avatar
Richard Hughes committed
81
82
			     "No UEFI firmware matched %s",
			     guid_str);
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
	}

	return re_matched;
}

/**
 * fu_provider_uefi_clear_results:
 **/
static gboolean
fu_provider_uefi_clear_results (FuProvider *provider, FuDevice *device, GError **error)
{
	fwup_resource_iter *iter = NULL;
	fwup_resource *re = NULL;
	gboolean ret = TRUE;

	/* get the hardware we're referencing */
	fwup_resource_iter_create (&iter);
	re = fu_provider_uefi_find (iter, fu_device_get_guid (device), error);
	if (re == NULL) {
		ret = FALSE;
		goto out;
	}
	if (fwup_clear_status (re) < 0) {
		ret = FALSE;
		g_set_error (error,
108
109
			     FWUPD_ERROR,
			     FWUPD_ERROR_INTERNAL,
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
			     "Cannot create clear UEFI status for %s",
			     fu_device_get_guid (device));
		goto out;
	}
out:
	fwup_resource_iter_destroy (&iter);
	return ret;
}

/* only in git master */
#ifndef FWUP_LAST_ATTEMPT_STATUS_SUCCESS
#define FWUP_LAST_ATTEMPT_STATUS_SUCCESS			0x00000000
#define FWUP_LAST_ATTEMPT_STATUS_ERROR_UNSUCCESSFUL		0x00000001
#define FWUP_LAST_ATTEMPT_STATUS_ERROR_INSUFFICIENT_RESOURCES	0x00000002
#define FWUP_LAST_ATTEMPT_STATUS_ERROR_INCORRECT_VERSION	0x00000003
#define FWUP_LAST_ATTEMPT_STATUS_ERROR_INVALID_FORMAT		0x00000004
#define FWUP_LAST_ATTEMPT_STATUS_ERROR_AUTH_ERROR		0x00000005
#define FWUP_LAST_ATTEMPT_STATUS_ERROR_PWR_EVT_AC		0x00000006
#define FWUP_LAST_ATTEMPT_STATUS_ERROR_PWR_EVT_BATT		0x00000007
#endif

/**
 * fu_provider_uefi_last_attempt_status_to_str:
 **/
static const gchar *
fu_provider_uefi_last_attempt_status_to_str (guint32 status)
{
	if (status == FWUP_LAST_ATTEMPT_STATUS_SUCCESS)
		return "Success";
	if (status == FWUP_LAST_ATTEMPT_STATUS_ERROR_UNSUCCESSFUL)
		return "Unsuccessful";
	if (status == FWUP_LAST_ATTEMPT_STATUS_ERROR_INSUFFICIENT_RESOURCES)
		return "Insufficient resources";
	if (status == FWUP_LAST_ATTEMPT_STATUS_ERROR_INCORRECT_VERSION)
		return "Incorrect version";
	if (status == FWUP_LAST_ATTEMPT_STATUS_ERROR_INVALID_FORMAT)
		return "Invalid firmware format";
	if (status == FWUP_LAST_ATTEMPT_STATUS_ERROR_AUTH_ERROR)
		return "Authentication signing error";
	if (status == FWUP_LAST_ATTEMPT_STATUS_ERROR_PWR_EVT_AC)
		return "AC power required";
	if (status == FWUP_LAST_ATTEMPT_STATUS_ERROR_PWR_EVT_BATT)
		return "Battery level is too low";
	return NULL;
}

/**
 * fu_provider_uefi_get_results:
 **/
static gboolean
fu_provider_uefi_get_results (FuProvider *provider, FuDevice *device, GError **error)
{
	const gchar *tmp;
	fwup_resource_iter *iter = NULL;
	fwup_resource *re = NULL;
	gboolean ret = TRUE;
	guint32 status = 0;
	guint32 version = 0;
	_cleanup_free_ gchar *version_str = NULL;

	/* get the hardware we're referencing */
	fwup_resource_iter_create (&iter);
	re = fu_provider_uefi_find (iter, fu_device_get_guid (device), error);
	if (re == NULL) {
		ret = FALSE;
		goto out;
	}
	if (fwup_get_last_attempt_info (re, &version, &status, NULL) < 0) {
		ret = FALSE;
		g_set_error (error,
180
181
			     FWUPD_ERROR,
			     FWUPD_ERROR_INTERNAL,
182
183
184
185
186
			     "Cannot get UEFI status for %s",
			     fu_device_get_guid (device));
		goto out;
	}
	version_str = g_strdup_printf ("%u", version);
187
	fu_device_set_metadata (device, FU_DEVICE_KEY_UPDATE_VERSION, version_str);
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
	if (status == FWUP_LAST_ATTEMPT_STATUS_SUCCESS) {
		fu_device_set_metadata (device, FU_DEVICE_KEY_PENDING_STATE,
					fu_pending_state_to_string (FU_PENDING_STATE_SUCCESS));
	} else {
		fu_device_set_metadata (device, FU_DEVICE_KEY_PENDING_STATE,
					fu_pending_state_to_string (FU_PENDING_STATE_FAILED));
		tmp = fu_provider_uefi_last_attempt_status_to_str (status);
		if (tmp != NULL)
			fu_device_set_metadata (device, FU_DEVICE_KEY_PENDING_ERROR, tmp);
	}
out:
	fwup_resource_iter_destroy (&iter);
	return ret;
}

/**
 * fu_provider_uefi_update:
 **/
static gboolean
fu_provider_uefi_update (FuProvider *provider,
			 FuDevice *device,
			 gint fd,
			 FuProviderFlags flags,
			 GError **error)
{
	fwup_resource_iter *iter = NULL;
	fwup_resource *re = NULL;
	gboolean ret = TRUE;
	guint64 hardware_instance = 0;	/* FIXME */

	/* get the hardware we're referencing */
	fwup_resource_iter_create (&iter);
	re = fu_provider_uefi_find (iter, fu_device_get_guid (device), error);
	if (re == NULL) {
		ret = FALSE;
Richard Hughes's avatar
Richard Hughes committed
223
		goto out;
224
225
226
227
	}

	/* perform the update */
	g_debug ("Performing UEFI capsule update");
228
	fu_provider_set_status (provider, FWUPD_STATUS_SCHEDULING);
229
	if (fwup_set_up_update (re, hardware_instance, fd) < 0) {
Richard Hughes's avatar
Richard Hughes committed
230
		ret = FALSE;
231
		g_set_error (error,
232
233
			     FWUPD_ERROR,
			     FWUPD_ERROR_NOT_SUPPORTED,
234
			     "UEFI firmware update failed: %s",
235
			     strerror (errno));
Richard Hughes's avatar
Richard Hughes committed
236
		goto out;
237
	}
Richard Hughes's avatar
Richard Hughes committed
238
239
240
out:
	fwup_resource_iter_destroy (&iter);
	return ret;
241
242
}

243
244
245
/**
 * fu_provider_uefi_coldplug:
 **/
246
247
static gboolean
fu_provider_uefi_coldplug (FuProvider *provider, GError **error)
248
{
249
	fwup_resource_iter *iter = NULL;
Richard Hughes's avatar
Richard Hughes committed
250
251
252
	fwup_resource *re;
	_cleanup_free_ gchar *guid = NULL;
	_cleanup_object_unref_ FuDevice *dev = NULL;
253
254
255
256

	/* not supported */
	if (!fwup_supported ()) {
		g_set_error_literal (error,
257
258
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED,
259
260
261
262
				     "UEFI firmware updating not supported");
		return FALSE;
	}

Richard Hughes's avatar
Richard Hughes committed
263
	/* this can fail if we have no permissions */
264
265
	if (fwup_resource_iter_create (&iter) < 0) {
		g_set_error_literal (error,
266
267
				     FWUPD_ERROR,
				     FWUPD_ERROR_INTERNAL,
268
269
270
271
				     "Cannot create fwup iter");
		return FALSE;
	}

Richard Hughes's avatar
Richard Hughes committed
272
273
274
275
276
277
	/* add each device */
	guid = g_strdup ("00000000-0000-0000-0000-000000000000");
	while (fwup_resource_iter_next (iter, &re) > 0) {
		efi_guid_t *guid_raw;
		guint32 version_raw;
		guint64 hardware_instance = 0;	/* FIXME */
278
279
280
281
282
		_cleanup_free_ gchar *id = NULL;
		_cleanup_free_ gchar *version = NULL;
		_cleanup_free_ gchar *version_lowest = NULL;

		/* convert to strings */
Richard Hughes's avatar
Richard Hughes committed
283
284
		fwup_get_guid (re, &guid_raw);
		if (efi_guid_to_str (guid_raw, &guid) < 0) {
285
286
287
			g_warning ("failed to convert guid to string");
			continue;
		}
Richard Hughes's avatar
Richard Hughes committed
288
289
		fwup_get_fw_version(re, &version_raw);
		version = g_strdup_printf ("%" G_GUINT32_FORMAT, version_raw);
290
		id = g_strdup_printf ("UEFI-%s-dev%" G_GUINT64_FORMAT,
Richard Hughes's avatar
Richard Hughes committed
291
				      guid, hardware_instance);
292

293
294
		dev = fu_device_new ();
		fu_device_set_id (dev, id);
295
		fu_device_set_guid (dev, guid);
296
		fu_device_set_metadata (dev, FU_DEVICE_KEY_VERSION, version);
Richard Hughes's avatar
Richard Hughes committed
297
298
		fwup_get_lowest_supported_fw_version (re, &version_raw);
		if (version_raw != 0) {
299
			version_lowest = g_strdup_printf ("%" G_GUINT32_FORMAT,
Richard Hughes's avatar
Richard Hughes committed
300
							  version_raw);
301
302
303
			fu_device_set_metadata (dev, FU_DEVICE_KEY_VERSION_LOWEST,
						version_lowest);
		}
304
305
		fu_device_add_flag (dev, FU_DEVICE_FLAG_INTERNAL);
		fu_device_add_flag (dev, FU_DEVICE_FLAG_ALLOW_OFFLINE);
306
		fu_provider_device_add (provider, dev);
307
308
	}
	fwup_resource_iter_destroy (&iter);
309
	return TRUE;
310
311
312
313
314
315
316
317
}

/**
 * fu_provider_uefi_class_init:
 **/
static void
fu_provider_uefi_class_init (FuProviderUefiClass *klass)
{
318
	FuProviderClass *provider_class = FU_PROVIDER_CLASS (klass);
319
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
320

321
	provider_class->get_name = fu_provider_uefi_get_name;
322
	provider_class->coldplug = fu_provider_uefi_coldplug;
323
	provider_class->update_offline = fu_provider_uefi_update;
324
325
	provider_class->clear_results = fu_provider_uefi_clear_results;
	provider_class->get_results = fu_provider_uefi_get_results;
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
	object_class->finalize = fu_provider_uefi_finalize;
}

/**
 * fu_provider_uefi_init:
 **/
static void
fu_provider_uefi_init (FuProviderUefi *provider_uefi)
{
}

/**
 * fu_provider_uefi_finalize:
 **/
static void
fu_provider_uefi_finalize (GObject *object)
{
	G_OBJECT_CLASS (fu_provider_uefi_parent_class)->finalize (object);
}

/**
 * fu_provider_uefi_new:
 **/
349
FuProvider *
350
351
fu_provider_uefi_new (void)
{
352
353
354
	FuProviderUefi *provider;
	provider = g_object_new (FU_TYPE_PROVIDER_UEFI, NULL);
	return FU_PROVIDER (provider);
355
}