blob: d54a76145b85b9764fc133aac60a649666cb604f [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2018 IBM Corp.
extern "C" {
#include "mboxd.h"
}
#include "config.h"
#include "vpnor/partition.hpp"
#include "vpnor/table.hpp"
#include "xyz/openbmc_project/Common/error.hpp"
#include <assert.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <syslog.h>
#include <unistd.h>
#include <exception>
#include <iostream>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/log.hpp>
#include <stdexcept>
#include <string>
#include "common.h"
#include "vpnor/backend.h"
namespace openpower
{
namespace virtual_pnor
{
namespace fs = std::filesystem;
fs::path Request::getPartitionFilePath(int flags)
{
struct vpnor_data* priv = (struct vpnor_data*)backend->priv;
// Check if partition exists in patch location
auto dst = fs::path(priv->paths.patch_loc) / partition.data.name;
if (fs::is_regular_file(dst))
{
return dst;
}
// Check if partition exists in rw location (default)
dst = fs::path(priv->paths.rw_loc) / partition.data.name;
if (fs::exists(dst))
{
return dst;
}
switch (partition.data.user.data[1] &
(PARTITION_PRESERVED | PARTITION_READONLY))
{
case PARTITION_PRESERVED:
dst = priv->paths.prsv_loc;
break;
case PARTITION_READONLY:
dst = priv->paths.ro_loc;
break;
default:
dst = priv->paths.rw_loc;
}
dst /= partition.data.name;
if (fs::exists(dst))
{
return dst;
}
if (flags == O_RDONLY)
{
dst = fs::path(priv->paths.ro_loc) / partition.data.name;
assert(fs::exists(dst));
return dst;
}
assert(flags == O_RDWR);
auto src = fs::path(priv->paths.ro_loc) / partition.data.name;
assert(fs::exists(src));
MSG_DBG("RWRequest: Didn't find '%s' under '%s', copying from '%s'\n",
partition.data.name, dst.c_str(), src.c_str());
dst = priv->paths.rw_loc;
if (partition.data.user.data[1] & PARTITION_PRESERVED)
{
dst = priv->paths.prsv_loc;
}
dst /= partition.data.name;
fs::copy_file(src, dst);
fs::permissions(dst, fs::perms::owner_write, fs::perm_options::add);
return dst;
}
size_t Request::clamp(size_t len)
{
size_t maxAccess = offset + len;
size_t partSize = partition.data.size << backend->block_size_shift;
return std::min(maxAccess, partSize) - offset;
}
/* Perform reads and writes for an entire buffer's worth of data
*
* Post-condition: All bytes read or written, or an error has occurred
*
* Yields 0 if the entire buffer was processed, otherwise -1
*/
#define fd_process_all(fn, fd, src, len) \
({ \
size_t __len = len; \
ssize_t __accessed = 0; \
do \
{ \
__len -= __accessed; \
__accessed = TEMP_FAILURE_RETRY(fn(fd, src, __len)); \
} while (__len > 0 && __accessed > 0); \
__len ? -1 : 0; \
})
ssize_t Request::read(void* dst, size_t len)
{
len = clamp(len);
fs::path path = getPartitionFilePath(O_RDONLY);
MSG_INFO("Fulfilling read request against %s at offset 0x%zx into %p "
"for %zu\n",
path.c_str(), offset, dst, len);
size_t fileSize = fs::file_size(path);
size_t access_len = 0;
if (offset < fileSize)
{
int fd = ::open(path.c_str(), O_RDONLY);
if (fd == -1)
{
MSG_ERR("Failed to open backing file at '%s': %d\n", path.c_str(),
errno);
throw std::system_error(errno, std::system_category());
}
int rc = lseek(fd, offset, SEEK_SET);
if (rc < 0)
{
int lerrno = errno;
close(fd);
MSG_ERR("Failed to seek to %zu in %s (%zu bytes): %d\n", offset,
path.c_str(), fileSize, lerrno);
throw std::system_error(lerrno, std::system_category());
}
access_len = std::min(len, fileSize - offset);
rc = fd_process_all(::read, fd, dst, access_len);
if (rc < 0)
{
int lerrno = errno;
close(fd);
MSG_ERR(
"Requested %zu bytes but failed to read %zu from %s (%zu) at "
"%zu: %d\n",
len, access_len, path.c_str(), fileSize, offset, lerrno);
throw std::system_error(lerrno, std::system_category());
}
close(fd);
}
/* Set any remaining buffer space to the erased state */
memset((char*)dst + access_len, 0xff, len - access_len);
return len;
}
ssize_t Request::write(void* dst, size_t len)
{
if (len != clamp(len))
{
std::stringstream err;
err << "Request size 0x" << std::hex << len << " from offset 0x"
<< std::hex << offset << " exceeds the partition size 0x"
<< std::hex << (partition.data.size << backend->block_size_shift);
throw OutOfBoundsOffset(err.str());
}
/* Ensure file is at least the size of the maximum access */
fs::path path = getPartitionFilePath(O_RDWR);
MSG_INFO("Fulfilling write request against %s at offset 0x%zx from %p "
"for %zu\n",
path.c_str(), offset, dst, len);
int fd = ::open(path.c_str(), O_RDWR);
if (fd == -1)
{
MSG_ERR("Failed to open backing file at '%s': %d\n", path.c_str(),
errno);
throw std::system_error(errno, std::system_category());
}
int rc = lseek(fd, offset, SEEK_SET);
if (rc < 0)
{
int lerrno = errno;
close(fd);
MSG_ERR("Failed to seek to %zu in %s: %d\n", offset, path.c_str(),
lerrno);
throw std::system_error(lerrno, std::system_category());
}
rc = fd_process_all(::write, fd, dst, len);
if (rc < 0)
{
int lerrno = errno;
close(fd);
MSG_ERR("Failed to write %zu bytes to %s at %zu: %d\n", len,
path.c_str(), offset, lerrno);
throw std::system_error(lerrno, std::system_category());
}
backend_set_bytemap(backend, base + offset, len, FLASH_DIRTY);
close(fd);
return len;
}
} // namespace virtual_pnor
} // namespace openpower