Signed-off-by: Michael Olbrich <m.olbrich@pengutronix.de> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
564 lines
13 KiB
C
564 lines
13 KiB
C
/*
|
|
* efi.c - EFI filesystem mirror driver
|
|
*
|
|
* Copyright (c) 2014 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
|
|
*
|
|
* See file CREDITS for list of people who contributed to this
|
|
* project.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <driver.h>
|
|
#include <init.h>
|
|
#include <malloc.h>
|
|
#include <fs.h>
|
|
#include <string.h>
|
|
#include <command.h>
|
|
#include <errno.h>
|
|
#include <linux/stat.h>
|
|
#include <xfuncs.h>
|
|
#include <fcntl.h>
|
|
#include <wchar.h>
|
|
#include <efi.h>
|
|
#include <mach/efi.h>
|
|
#include <mach/efi-device.h>
|
|
|
|
/* Open modes */
|
|
#define EFI_FILE_MODE_READ 0x0000000000000001
|
|
#define EFI_FILE_MODE_WRITE 0x0000000000000002
|
|
#define EFI_FILE_MODE_CREATE 0x8000000000000000
|
|
|
|
/* File attributes */
|
|
#define EFI_FILE_READ_ONLY 0x0000000000000001
|
|
#define EFI_FILE_HIDDEN 0x0000000000000002
|
|
#define EFI_FILE_SYSTEM 0x0000000000000004
|
|
#define EFI_FILE_RESERVIED 0x0000000000000008
|
|
#define EFI_FILE_DIRECTORY 0x0000000000000010
|
|
#define EFI_FILE_ARCHIVE 0x0000000000000020
|
|
#define EFI_FILE_VALID_ATTR 0x0000000000000037
|
|
|
|
#define EFI_FILE_HANDLE_REVISION 0x00010000
|
|
struct efi_file_handle {
|
|
uint64_t Revision;
|
|
efi_status_t(EFIAPI *open)(struct efi_file_handle *File,
|
|
struct efi_file_handle **NewHandle, s16 *FileName,
|
|
uint64_t OpenMode, uint64_t Attributes);
|
|
efi_status_t(EFIAPI *close)(struct efi_file_handle *File);
|
|
efi_status_t(EFIAPI *delete)(struct efi_file_handle *File);
|
|
efi_status_t(EFIAPI *read)(struct efi_file_handle *File, unsigned long *BufferSize,
|
|
void *Buffer);
|
|
efi_status_t(EFIAPI *write)(struct efi_file_handle *File,
|
|
unsigned long *BufferSize, void *Buffer);
|
|
efi_status_t(EFIAPI *get_position)(struct efi_file_handle *File,
|
|
uint64_t *Position);
|
|
efi_status_t(EFIAPI *set_position)(struct efi_file_handle *File,
|
|
uint64_t Position);
|
|
efi_status_t(EFIAPI *get_info)(struct efi_file_handle *File,
|
|
efi_guid_t *InformationType, unsigned long *BufferSize,
|
|
void *Buffer);
|
|
efi_status_t(EFIAPI *set_info)(struct efi_file_handle *File,
|
|
efi_guid_t *InformationType, unsigned long BufferSize,
|
|
void *Buffer);
|
|
efi_status_t(EFIAPI *flush)(struct efi_file_handle *File);
|
|
};
|
|
|
|
#define EFI_FILE_IO_INTERFACE_REVISION 0x00010000
|
|
|
|
struct efi_file_io_interface {
|
|
uint64_t Revision;
|
|
efi_status_t(EFIAPI *open_volume)(
|
|
struct efi_file_io_interface *This,
|
|
struct efi_file_handle **Root);
|
|
};
|
|
|
|
struct efi_file_info {
|
|
uint64_t Size;
|
|
uint64_t FileSize;
|
|
uint64_t PhysicalSize;
|
|
efi_time_t CreateTime;
|
|
efi_time_t LastAccessTime;
|
|
efi_time_t ModificationTime;
|
|
uint64_t Attribute;
|
|
s16 FileName[1];
|
|
};
|
|
|
|
typedef unsigned short wchar_t;
|
|
|
|
struct efifs_priv {
|
|
struct efi_file_handle *root_dir;
|
|
struct efi_file_io_interface *protocol;
|
|
};
|
|
|
|
struct efifs_file {
|
|
struct efi_file_handle *entry;
|
|
};
|
|
|
|
struct efifs_dir {
|
|
DIR dir;
|
|
struct efi_file_handle *entries;
|
|
};
|
|
|
|
static wchar_t *path_to_efi(const char *path)
|
|
{
|
|
wchar_t *dst;
|
|
wchar_t *ret;
|
|
|
|
if (!*path)
|
|
return xstrdup_char_to_wchar("\\");
|
|
|
|
dst = xstrdup_char_to_wchar(path);
|
|
if (!dst)
|
|
return NULL;
|
|
|
|
ret = dst;
|
|
|
|
while (*dst) {
|
|
if (*dst == '/')
|
|
*dst = '\\';
|
|
dst++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int efifs_create(struct device_d *dev, const char *pathname, mode_t mode)
|
|
{
|
|
struct efifs_priv *priv = dev->priv;
|
|
wchar_t *efi_path = path_to_efi(pathname);
|
|
struct efi_file_handle *entry;
|
|
efi_status_t efiret;
|
|
|
|
efiret = priv->root_dir->open(priv->root_dir, &entry, efi_path,
|
|
EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE,
|
|
0ULL);
|
|
|
|
free(efi_path);
|
|
|
|
if (EFI_ERROR(efiret)) {
|
|
printf("%s %s: %s\n", __func__, pathname, efi_strerror(efiret));
|
|
return -efi_errno(efiret);
|
|
}
|
|
|
|
entry->close(entry);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int efifs_unlink(struct device_d *dev, const char *pathname)
|
|
{
|
|
struct efifs_priv *priv = dev->priv;
|
|
wchar_t *efi_path = path_to_efi(pathname);
|
|
struct efi_file_handle *entry;
|
|
efi_status_t efiret;
|
|
|
|
efiret = priv->root_dir->open(priv->root_dir, &entry, efi_path,
|
|
EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0ULL);
|
|
|
|
free(efi_path);
|
|
|
|
if (EFI_ERROR(efiret))
|
|
return -efi_errno(efiret);
|
|
|
|
efiret = entry->delete(entry);
|
|
if (EFI_ERROR(efiret))
|
|
return -efi_errno(efiret);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int efifs_mkdir(struct device_d *dev, const char *pathname)
|
|
{
|
|
struct efifs_priv *priv = dev->priv;
|
|
wchar_t *efi_path = path_to_efi(pathname);
|
|
struct efi_file_handle *entry;
|
|
efi_status_t efiret;
|
|
|
|
efiret = priv->root_dir->open(priv->root_dir, &entry, efi_path,
|
|
EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE,
|
|
EFI_FILE_DIRECTORY);
|
|
|
|
free(efi_path);
|
|
|
|
if (EFI_ERROR(efiret)) {
|
|
printf("%s %s: %s\n", __func__, pathname, efi_strerror(efiret));
|
|
return -efi_errno(efiret);
|
|
}
|
|
|
|
entry->close(entry);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int efifs_rmdir(struct device_d *dev, const char *pathname)
|
|
{
|
|
return efifs_unlink(dev, pathname);
|
|
}
|
|
|
|
static int efifs_open(struct device_d *dev, FILE *f, const char *filename)
|
|
{
|
|
struct efifs_priv *priv = dev->priv;
|
|
efi_status_t efiret;
|
|
struct efifs_file *ufile;
|
|
wchar_t *efi_path = path_to_efi(filename);
|
|
struct efi_file_info *info;
|
|
unsigned long bufsize = 1024;
|
|
uint64_t efimode = EFI_FILE_MODE_READ;
|
|
int ret;
|
|
|
|
ufile = xzalloc(sizeof(*ufile));
|
|
|
|
if (f->flags & O_ACCMODE)
|
|
efimode |= EFI_FILE_MODE_WRITE;
|
|
|
|
efiret = priv->root_dir->open(priv->root_dir, &ufile->entry, efi_path,
|
|
efimode, 0ULL);
|
|
if (EFI_ERROR(efiret)) {
|
|
pr_err("%s: unable to Open %s: %s\n", __func__,
|
|
filename, efi_strerror(efiret));
|
|
free(ufile);
|
|
return -efi_errno(efiret);
|
|
}
|
|
|
|
free(efi_path);
|
|
|
|
info = xzalloc(1024);
|
|
efiret = ufile->entry->get_info(ufile->entry, &efi_file_info_id, &bufsize, info);
|
|
if (EFI_ERROR(efiret)) {
|
|
pr_err("%s: unable to GetInfo %s: %s\n", __func__,
|
|
filename, efi_strerror(efiret));
|
|
ret = -efi_errno(efiret);
|
|
goto out;
|
|
}
|
|
|
|
f->size = info->FileSize;
|
|
|
|
free(info);
|
|
f->priv = ufile;
|
|
|
|
return 0;
|
|
out:
|
|
free(info);
|
|
free(ufile);
|
|
return ret;
|
|
}
|
|
|
|
static int efifs_close(struct device_d *dev, FILE *f)
|
|
{
|
|
struct efifs_file *ufile = f->priv;
|
|
|
|
ufile->entry->close(ufile->entry);
|
|
|
|
free(ufile);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int efifs_read(struct device_d *_dev, FILE *f, void *buf, size_t insize)
|
|
{
|
|
struct efifs_file *ufile = f->priv;
|
|
efi_status_t efiret;
|
|
unsigned long bufsize = insize;
|
|
|
|
efiret = ufile->entry->read(ufile->entry, &bufsize, buf);
|
|
if (EFI_ERROR(efiret)) {
|
|
return -efi_errno(efiret);
|
|
}
|
|
|
|
return bufsize;
|
|
}
|
|
|
|
static int efifs_write(struct device_d *_dev, FILE *f, const void *buf, size_t insize)
|
|
{
|
|
struct efifs_file *ufile = f->priv;
|
|
efi_status_t efiret;
|
|
unsigned long bufsize = insize;
|
|
|
|
efiret = ufile->entry->write(ufile->entry, &bufsize, (void *)buf);
|
|
if (EFI_ERROR(efiret)) {
|
|
pr_err("%s: unable to write: %s\n", __func__, efi_strerror(efiret));
|
|
return -efi_errno(efiret);
|
|
}
|
|
|
|
return bufsize;
|
|
}
|
|
|
|
static loff_t efifs_lseek(struct device_d *dev, FILE *f, loff_t pos)
|
|
{
|
|
struct efifs_file *ufile = f->priv;
|
|
efi_status_t efiret;
|
|
|
|
f->pos = pos;
|
|
|
|
efiret = ufile->entry->set_position(ufile->entry, pos);
|
|
if (EFI_ERROR(efiret)) {
|
|
return -efi_errno(efiret);
|
|
}
|
|
|
|
return f->pos;
|
|
}
|
|
|
|
static int efifs_truncate(struct device_d *dev, FILE *f, unsigned long size)
|
|
{
|
|
struct efifs_file *ufile = f->priv;
|
|
efi_status_t efiret;
|
|
struct efi_file_info *info;
|
|
unsigned long bufsize = 1024;
|
|
int ret;
|
|
|
|
info = xzalloc(1024);
|
|
|
|
efiret = ufile->entry->get_info(ufile->entry, &efi_file_info_id, &bufsize, info);
|
|
if (EFI_ERROR(efiret)) {
|
|
pr_err("%s: unable to GetInfo: %s\n", __func__, efi_strerror(efiret));
|
|
ret = -efi_errno(efiret);
|
|
goto out;
|
|
}
|
|
|
|
if (size > info->FileSize)
|
|
return 0;
|
|
|
|
info->FileSize = size;
|
|
|
|
efiret = ufile->entry->set_info(ufile->entry, &efi_file_info_id, bufsize, info);
|
|
if (EFI_ERROR(efiret)) {
|
|
pr_err("%s: unable to SetInfo: %s\n", __func__, efi_strerror(efiret));
|
|
ret = -efi_errno(efiret);
|
|
goto out;
|
|
}
|
|
|
|
return 0;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static DIR *efifs_opendir(struct device_d *dev, const char *pathname)
|
|
{
|
|
struct efifs_priv *priv = dev->priv;
|
|
efi_status_t efiret;
|
|
struct efifs_dir *udir;
|
|
wchar_t *efi_path = path_to_efi(pathname);
|
|
|
|
udir = xzalloc(sizeof(*udir));
|
|
|
|
efiret = priv->root_dir->open(priv->root_dir, &udir->entries, efi_path, EFI_FILE_MODE_READ, 0ULL);
|
|
if (EFI_ERROR(efiret)) {
|
|
free(udir);
|
|
return NULL;
|
|
}
|
|
|
|
free(efi_path);
|
|
|
|
return &udir->dir;
|
|
}
|
|
|
|
static struct dirent *efifs_readdir(struct device_d *dev, DIR *dir)
|
|
{
|
|
struct efifs_dir *udir = container_of(dir, struct efifs_dir, dir);
|
|
efi_status_t efiret;
|
|
unsigned long bufsize = 256;
|
|
s16 buf[256];
|
|
struct efi_file_info *f;
|
|
|
|
efiret = udir->entries->read(udir->entries, &bufsize, buf);
|
|
if (EFI_ERROR(efiret) || bufsize == 0)
|
|
return NULL;
|
|
|
|
f = (struct efi_file_info *)buf;
|
|
|
|
strcpy_wchar_to_char(dir->d.d_name, f->FileName);
|
|
|
|
return &dir->d;
|
|
}
|
|
|
|
static int efifs_closedir(struct device_d *dev, DIR *dir)
|
|
{
|
|
struct efifs_dir *udir = container_of(dir, struct efifs_dir, dir);
|
|
|
|
udir->entries->close(udir->entries);
|
|
|
|
free(dir);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int efifs_stat(struct device_d *dev, const char *filename, struct stat *s)
|
|
{
|
|
struct efifs_priv *priv = dev->priv;
|
|
wchar_t *efi_path;
|
|
efi_status_t efiret;
|
|
struct efi_file_handle *entry;
|
|
struct efi_file_info *info;
|
|
unsigned long bufsize = 1024;
|
|
int ret;
|
|
|
|
info = xzalloc(1024);
|
|
|
|
efi_path = path_to_efi(filename);
|
|
|
|
efiret = priv->root_dir->open(priv->root_dir, &entry, efi_path, EFI_FILE_MODE_READ, 0ULL);
|
|
if (EFI_ERROR(efiret)) {
|
|
pr_err("%s: unable to Open %s: %s\n", __func__, filename,
|
|
efi_strerror(efiret));
|
|
ret = -efi_errno(efiret);
|
|
goto out_free;
|
|
}
|
|
|
|
efiret = entry->get_info(entry, &efi_file_info_id, &bufsize, info);
|
|
if (EFI_ERROR(efiret)) {
|
|
pr_err("%s: unable to GetInfo %s: %s\n", __func__, filename,
|
|
efi_strerror(efiret));
|
|
ret = -efi_errno(efiret);
|
|
goto out;
|
|
}
|
|
|
|
s->st_size = info->FileSize;
|
|
s->st_mode = 00555;
|
|
|
|
if (!info->Attribute & EFI_FILE_READ_ONLY)
|
|
s->st_mode |= 00222;
|
|
|
|
if (info->Attribute & EFI_FILE_DIRECTORY)
|
|
s->st_mode |= S_IFDIR;
|
|
else
|
|
s->st_mode |= S_IFREG;
|
|
|
|
ret = 0;
|
|
out:
|
|
entry->close(entry);
|
|
out_free:
|
|
free(efi_path);
|
|
free(info);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int efifs_symlink(struct device_d *dev, const char *pathname,
|
|
const char *newpath)
|
|
{
|
|
return -EROFS;
|
|
}
|
|
|
|
static int efifs_readlink(struct device_d *dev, const char *pathname,
|
|
char *buf, size_t bufsiz)
|
|
{
|
|
return -ENOENT;
|
|
}
|
|
|
|
static int efifs_probe(struct device_d *dev)
|
|
{
|
|
struct fs_device_d *fsdev = dev_to_fs_device(dev);
|
|
struct efifs_priv *priv;
|
|
efi_status_t efiret;
|
|
struct efi_file_handle *file;
|
|
struct device_d *efi = get_device_by_name(fsdev->backingstore);
|
|
struct efi_device *udev = container_of(efi, struct efi_device, dev);
|
|
|
|
priv = xzalloc(sizeof(struct efifs_priv));
|
|
priv->protocol = udev->protocol;
|
|
dev->priv = priv;
|
|
dev->parent = &udev->dev;
|
|
|
|
efiret = priv->protocol->open_volume(priv->protocol, &file);
|
|
if (EFI_ERROR(efiret)) {
|
|
dev_err(dev, "failed to open volume: %s\n", efi_strerror(efiret));
|
|
return -efi_errno(efiret);
|
|
}
|
|
|
|
priv->root_dir = file;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void efifs_remove(struct device_d *dev)
|
|
{
|
|
free(dev->priv);
|
|
}
|
|
|
|
static struct fs_driver_d efifs_driver = {
|
|
.create = efifs_create,
|
|
.unlink = efifs_unlink,
|
|
.open = efifs_open,
|
|
.close = efifs_close,
|
|
.truncate = efifs_truncate,
|
|
.read = efifs_read,
|
|
.write = efifs_write,
|
|
.lseek = efifs_lseek,
|
|
.mkdir = efifs_mkdir,
|
|
.rmdir = efifs_rmdir,
|
|
.opendir = efifs_opendir,
|
|
.readdir = efifs_readdir,
|
|
.closedir = efifs_closedir,
|
|
.stat = efifs_stat,
|
|
.symlink = efifs_symlink,
|
|
.readlink = efifs_readlink,
|
|
.drv = {
|
|
.probe = efifs_probe,
|
|
.remove = efifs_remove,
|
|
.name = "efifs",
|
|
}
|
|
};
|
|
|
|
static int efifs_init(void)
|
|
{
|
|
return register_fs_driver(&efifs_driver);
|
|
}
|
|
|
|
coredevice_initcall(efifs_init);
|
|
|
|
static int index;
|
|
|
|
int efi_fs_probe(struct efi_device *efidev)
|
|
{
|
|
char *path, *device;
|
|
int ret;
|
|
struct efi_file_io_interface *volume;
|
|
|
|
if (efi_loaded_image)
|
|
BS->handle_protocol(efi_loaded_image->device_handle,
|
|
&efi_simple_file_system_protocol_guid, (void*)&volume);
|
|
|
|
if (efidev->protocol == volume)
|
|
path = xstrdup("/boot");
|
|
else
|
|
path = asprintf("/efi%d", index);
|
|
device = asprintf("%s", dev_name(&efidev->dev));
|
|
|
|
ret = make_directory(path);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = mount(device, "efifs", path, NULL);
|
|
if (ret)
|
|
goto out;
|
|
|
|
index++;
|
|
|
|
dev_info(&efidev->dev, "mounted on %s\n", path);
|
|
|
|
ret = 0;
|
|
out:
|
|
free(path);
|
|
free(device);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct efi_driver efi_fs_driver = {
|
|
.driver = {
|
|
.name = "efi-fs",
|
|
},
|
|
.probe = efi_fs_probe,
|
|
.guid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID,
|
|
};
|
|
device_efi_driver(efi_fs_driver);
|