398 lines
9.3 KiB
C
398 lines
9.3 KiB
C
/* Copyright 2013-2014 IBM Corp.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
* implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <limits.h>
|
|
|
|
#include <ccan/container_of/container_of.h>
|
|
|
|
#include <mtd/mtd-abi.h>
|
|
|
|
#include "libflash.h"
|
|
#include "libflash/file.h"
|
|
#include "blocklevel.h"
|
|
|
|
struct file_data {
|
|
int fd;
|
|
char *name;
|
|
char *path;
|
|
struct blocklevel_device bl;
|
|
};
|
|
|
|
static int file_release(struct blocklevel_device *bl)
|
|
{
|
|
struct file_data *file_data = container_of(bl, struct file_data, bl);
|
|
close(file_data->fd);
|
|
file_data->fd = -1;
|
|
return 0;
|
|
}
|
|
|
|
static int file_reacquire(struct blocklevel_device *bl)
|
|
{
|
|
struct file_data *file_data = container_of(bl, struct file_data, bl);
|
|
int fd;
|
|
|
|
fd = open(file_data->path, O_RDWR);
|
|
if (fd == -1)
|
|
return FLASH_ERR_PARM_ERROR;
|
|
file_data->fd = fd;
|
|
return 0;
|
|
}
|
|
|
|
static int file_read(struct blocklevel_device *bl, uint64_t pos, void *buf, uint64_t len)
|
|
{
|
|
struct file_data *file_data = container_of(bl, struct file_data, bl);
|
|
int rc, count = 0;
|
|
|
|
rc = lseek(file_data->fd, pos, SEEK_SET);
|
|
/* errno should remain set */
|
|
if (rc != pos)
|
|
return FLASH_ERR_PARM_ERROR;
|
|
|
|
while (count < len) {
|
|
rc = read(file_data->fd, buf, len - count);
|
|
/* errno should remain set */
|
|
if (rc == -1 || rc == 0)
|
|
return FLASH_ERR_BAD_READ;
|
|
|
|
buf += rc;
|
|
count += rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int file_write(struct blocklevel_device *bl, uint64_t dst, const void *src,
|
|
uint64_t len)
|
|
{
|
|
struct file_data *file_data = container_of(bl, struct file_data, bl);
|
|
int rc, count = 0;
|
|
|
|
rc = lseek(file_data->fd, dst, SEEK_SET);
|
|
/* errno should remain set */
|
|
if (rc != dst)
|
|
return FLASH_ERR_PARM_ERROR;
|
|
|
|
while (count < len) {
|
|
rc = write(file_data->fd, src, len - count);
|
|
/* errno should remain set */
|
|
if (rc == -1)
|
|
return FLASH_ERR_VERIFY_FAILURE;
|
|
|
|
src += rc;
|
|
count += rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Due to to the fact these interfaces are ultimately supposed to deal with
|
|
* flash, an erase function must be implemented even when the flash images
|
|
* are backed by regular files.
|
|
* Also, erasing flash leaves all the bits set to 1. This may be expected
|
|
* by higher level functions so this function should also emulate that
|
|
*/
|
|
static int file_erase(struct blocklevel_device *bl, uint64_t dst, uint64_t len)
|
|
{
|
|
static char buf[4096];
|
|
int i = 0;
|
|
int rc;
|
|
|
|
memset(buf, ~0, sizeof(buf));
|
|
|
|
while (len - i > 0) {
|
|
rc = file_write(bl, dst + i, buf, len - i > sizeof(buf) ? sizeof(buf) : len - i);
|
|
if (rc)
|
|
return rc;
|
|
i += (len - i > sizeof(buf)) ? sizeof(buf) : len - i;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mtd_erase(struct blocklevel_device *bl, uint64_t dst, uint64_t len)
|
|
{
|
|
struct file_data *file_data = container_of(bl, struct file_data, bl);
|
|
int err;
|
|
|
|
FL_DBG("%s: dst: 0x%" PRIx64 ", len: 0x%" PRIx64 "\n", __func__, dst, len);
|
|
|
|
/*
|
|
* Some kernels that pflash supports do not know about the 64bit
|
|
* version of the ioctl() therefore we'll just use the 32bit (which
|
|
* should always be supported...) unless we MUST use the 64bit and
|
|
* then lets just hope the kernel knows how to deal with it. If it
|
|
* is unsupported the ioctl() will fail and we'll report that -
|
|
* there is no other option.
|
|
*
|
|
* Furthermore, even very recent MTD layers and drivers aren't
|
|
* particularly good at not blocking in the kernel. This creates
|
|
* unexpected behaviour in userspace tools using these functions.
|
|
* In the absence of significant work inside the kernel, we'll just
|
|
* split stuff up here for convenience.
|
|
* We can assume everything is aligned here.
|
|
*/
|
|
while (len) {
|
|
if (dst > UINT_MAX || len > UINT_MAX) {
|
|
struct erase_info_user64 erase_info = {
|
|
.start = dst,
|
|
.length = file_data->bl.erase_mask + 1
|
|
};
|
|
|
|
if (ioctl(file_data->fd, MEMERASE64, &erase_info) == -1) {
|
|
err = errno;
|
|
if (err == 25) /* Kernel doesn't do 64bit MTD erase ioctl() */
|
|
FL_DBG("Attempted a 64bit erase on a kernel which doesn't support it\n");
|
|
FL_ERR("%s: IOCTL to kernel failed! %s\n", __func__, strerror(err));
|
|
errno = err;
|
|
return FLASH_ERR_PARM_ERROR;
|
|
}
|
|
} else {
|
|
struct erase_info_user erase_info = {
|
|
.start = dst,
|
|
.length = file_data->bl.erase_mask + 1
|
|
};
|
|
if (ioctl(file_data->fd, MEMERASE, &erase_info) == -1) {
|
|
err = errno;
|
|
FL_ERR("%s: IOCTL to kernel failed! %s\n", __func__, strerror(err));
|
|
errno = err;
|
|
return FLASH_ERR_PARM_ERROR;
|
|
}
|
|
}
|
|
dst += file_data->bl.erase_mask + 1;
|
|
len -= file_data->bl.erase_mask + 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int get_info_name(struct file_data *file_data, char **name)
|
|
{
|
|
char *path, *lpath;
|
|
int len;
|
|
struct stat st;
|
|
|
|
if (asprintf(&path, "/proc/self/fd/%d", file_data->fd) == -1)
|
|
return FLASH_ERR_MALLOC_FAILED;
|
|
|
|
if (lstat(path, &st)) {
|
|
free(path);
|
|
return FLASH_ERR_PARM_ERROR;
|
|
}
|
|
|
|
lpath = malloc(st.st_size + 1);
|
|
if (!lpath) {
|
|
free(path);
|
|
return FLASH_ERR_MALLOC_FAILED;
|
|
}
|
|
|
|
len = readlink(path, lpath, st.st_size +1);
|
|
if (len == -1) {
|
|
free(path);
|
|
free(lpath);
|
|
return FLASH_ERR_PARM_ERROR;
|
|
}
|
|
lpath[len] = '\0';
|
|
|
|
*name = lpath;
|
|
|
|
free(path);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int mtd_get_info(struct blocklevel_device *bl, const char **name,
|
|
uint64_t *total_size, uint32_t *erase_granule)
|
|
{
|
|
struct file_data *file_data = container_of(bl, struct file_data, bl);
|
|
struct mtd_info_user mtd_info;
|
|
int rc;
|
|
|
|
rc = ioctl(file_data->fd, MEMGETINFO, &mtd_info);
|
|
if (rc == -1)
|
|
return FLASH_ERR_BAD_READ;
|
|
|
|
if (total_size)
|
|
*total_size = mtd_info.size;
|
|
|
|
if (erase_granule)
|
|
*erase_granule = mtd_info.erasesize;
|
|
|
|
if (name) {
|
|
rc = get_info_name(file_data, &(file_data->name));
|
|
if (rc)
|
|
return rc;
|
|
*name = file_data->name;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int file_get_info(struct blocklevel_device *bl, const char **name,
|
|
uint64_t *total_size, uint32_t *erase_granule)
|
|
{
|
|
struct file_data *file_data = container_of(bl, struct file_data, bl);
|
|
struct stat st;
|
|
int rc;
|
|
|
|
if (fstat(file_data->fd, &st))
|
|
return FLASH_ERR_PARM_ERROR;
|
|
|
|
if (total_size)
|
|
*total_size = st.st_size;
|
|
|
|
if (erase_granule)
|
|
*erase_granule = 1;
|
|
|
|
if (name) {
|
|
rc = get_info_name(file_data, &(file_data->name));
|
|
if (rc)
|
|
return rc;
|
|
*name = file_data->name;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int file_init(int fd, struct blocklevel_device **bl)
|
|
{
|
|
struct file_data *file_data;
|
|
struct stat sbuf;
|
|
|
|
if (!bl)
|
|
return FLASH_ERR_PARM_ERROR;
|
|
|
|
*bl = NULL;
|
|
|
|
file_data = calloc(1, sizeof(struct file_data));
|
|
if (!file_data)
|
|
return FLASH_ERR_MALLOC_FAILED;
|
|
|
|
file_data->fd = fd;
|
|
file_data->bl.reacquire = &file_reacquire;
|
|
file_data->bl.release = &file_release;
|
|
file_data->bl.read = &file_read;
|
|
file_data->bl.write = &file_write;
|
|
file_data->bl.erase = &file_erase;
|
|
file_data->bl.get_info = &file_get_info;
|
|
file_data->bl.erase_mask = 0;
|
|
|
|
/*
|
|
* If the blocklevel_device is only inited with file_init() then keep
|
|
* alive is assumed, as fd will change otherwise and this may break
|
|
* callers assumptions.
|
|
*/
|
|
file_data->bl.keep_alive = 1;
|
|
|
|
/*
|
|
* Unfortunately not all file descriptors are created equal...
|
|
* Here we check to see if the file descriptor is to an MTD device, in
|
|
* which case we have to erase and get the size of it differently.
|
|
*/
|
|
if (fstat(file_data->fd, &sbuf) == -1)
|
|
goto out;
|
|
|
|
/* Won't be able to handle other than MTD devices for now */
|
|
if (S_ISCHR(sbuf.st_mode)) {
|
|
file_data->bl.erase = &mtd_erase;
|
|
file_data->bl.get_info = &mtd_get_info;
|
|
file_data->bl.flags = WRITE_NEED_ERASE;
|
|
mtd_get_info(&file_data->bl, NULL, NULL, &(file_data->bl.erase_mask));
|
|
file_data->bl.erase_mask--;
|
|
} else if (!S_ISREG(sbuf.st_mode)) {
|
|
/* If not a char device or a regular file something went wrong */
|
|
goto out;
|
|
}
|
|
|
|
*bl = &(file_data->bl);
|
|
return 0;
|
|
out:
|
|
free(file_data);
|
|
return FLASH_ERR_PARM_ERROR;
|
|
}
|
|
|
|
int file_init_path(const char *path, int *r_fd, bool keep_alive,
|
|
struct blocklevel_device **bl)
|
|
{
|
|
int fd, rc;
|
|
char *path_ptr = NULL;
|
|
struct file_data *file_data;
|
|
|
|
if (!path || !bl)
|
|
return FLASH_ERR_PARM_ERROR;
|
|
|
|
fd = open(path, O_RDWR);
|
|
if (fd == -1)
|
|
return FLASH_ERR_PARM_ERROR;
|
|
|
|
/*
|
|
* strdup() first so don't have to deal with malloc failure after
|
|
* file_init()
|
|
*/
|
|
path_ptr = strdup(path);
|
|
if (!path_ptr) {
|
|
rc = FLASH_ERR_MALLOC_FAILED;
|
|
goto out;
|
|
}
|
|
|
|
rc = file_init(fd, bl);
|
|
if (rc)
|
|
goto out;
|
|
|
|
file_data = container_of(*bl, struct file_data, bl);
|
|
file_data->bl.keep_alive = keep_alive;
|
|
file_data->path = path_ptr;
|
|
|
|
if (r_fd)
|
|
*r_fd = fd;
|
|
|
|
return rc;
|
|
out:
|
|
free(path_ptr);
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
void file_exit(struct blocklevel_device *bl)
|
|
{
|
|
struct file_data *file_data;
|
|
if (bl) {
|
|
free(bl->ecc_prot.prot);
|
|
file_data = container_of(bl, struct file_data, bl);
|
|
free(file_data->name);
|
|
free(file_data->path);
|
|
free(file_data);
|
|
}
|
|
}
|
|
|
|
void file_exit_close(struct blocklevel_device *bl)
|
|
{
|
|
struct file_data *file_data;
|
|
if (bl) {
|
|
file_data = container_of(bl, struct file_data, bl);
|
|
close(file_data->fd);
|
|
file_exit(bl);
|
|
}
|
|
}
|