Fix /dev/lvd attach

This commit is contained in:
Roman Tarasov committed 2026-03-14 23:18:19 +03:00
1 parent aff2fc0e6c
commit 2db6b49439
3 files changed
+124 -74

No files matched your search

+26 -16
View File
@@ -9,36 +9,46 @@
#define UFS_ATTACH_USE_MDCTL 0
// --- LVD Definitions ---
// - ioctl: ATTACH=0xC0286D00, DETACH=0xC0286D01, ATTACH2 path=0xC0286D09.
// - single-image path uses raw option flags 0x8/0x9 -> normalized 0x14/0x1C.
// - DownloadData/LWFS path (imgtype=7) uses normalized options 0x16/0x1E.
// - image_type values accepted by validator: 0..0xC (13 values total).
// this code uses image_type=7 for UFS (DownloadData-like), 0 for generic path.
// - layer source_type observed: 1=file, 2=char/block-like source (/dev/sbram0).
// - layer entry flag bit0 is "no bitmap file specified".
// Kernel exposes base attach (V0), extended attach (V1/Attach2) and detach.
// This code only builds V0 layer descriptors, so it uses the base attach path.
// Wrapper-side raw attach presets:
// - 0x8/0x9 -> single-image/save-data family (normalized 0x14/0x1C)
// - 0xC/0xD -> download-data/LWFS family (normalized 0x16/0x1E)
// raw bit3 (0x8) sets the DONT_MAP_BM-style mode, raw bit2 (0x4) switches to
// the DD/LWFS family, and raw bit0 (0x1) selects the alternate preset inside a
// family. Firmware pairs bit0 with read-only mount variants, but this is not a
// standalone LVD readonly bit by itself.
// image_type values accepted by validator: 0..0xC (13 values total).
// layer source_type observed: 1=file, 2=device/special source (/dev/sbram0, char/block).
// layer descriptor flag bit0 is "no bitmap file specified".
#define LVD_CTRL_PATH "/dev/lvdctl"
#define MD_CTRL_PATH "/dev/mdctl"
#define SCE_LVD_IOC_ATTACH 0xC0286D00
#define SCE_LVD_IOC_ATTACH_V0 0xC0286D00
#define SCE_LVD_IOC_ATTACH_V1 0xC0286D09
#define SCE_LVD_IOC_DETACH 0xC0286D01
#define LVD_ATTACH_IO_VERSION 1
#define LVD_ATTACH_OPTION_FLAGS_DEFAULT 0x9
#define LVD_ATTACH_OPTION_FLAGS_RW 0x8
#define LVD_ATTACH_OPTION_NORM_DD_RO 0x1E
#define LVD_ATTACH_OPTION_NORM_DD_RW 0x16
#define LVD_ATTACH_IO_VERSION_V0 0u
#define LVD_ATTACH_IO_VERSION_V1 1u
#define LVD_ATTACH_RAW_FLAGS_SINGLE_RO 0x9
#define LVD_ATTACH_RAW_FLAGS_SINGLE_RW 0x8
#define LVD_ATTACH_RAW_FLAGS_DD_RO 0xD
#define LVD_ATTACH_RAW_FLAGS_DD_RW 0xC
#
#define LVD_SECTOR_SIZE_EXFAT 512u
#define LVD_SECTOR_SIZE_UFS 4096u
#define LVD_SECTOR_SIZE_PFS 32768u
#define LVD_SECTOR_SIZE_PFS 4096u
#define LVD_SECONDARY_UNIT_SINGLE_IMAGE 0x10000u
#define MD_SECTOR_SIZE_EXFAT 512u
#define MD_SECTOR_SIZE_UFS 512u
// Raw option bits are normalized by sceFsLvdAttachCommon before validation:
// raw:0x1->norm:0x08, raw:0x2->norm:0x80, raw:0x4->norm:0x02, raw:0x8->norm:0x10.
// The normalized masks are then checked against validator constraints (0x82/0x92).
#define LVD_ATTACH_IMAGE_TYPE 0
#define LVD_ATTACH_IMAGE_TYPE_SINGLE 0
#define LVD_ATTACH_IMAGE_TYPE_UFS_DOWNLOAD_DATA 7
#define LVD_ATTACH_IMAGE_TYPE_PFS_SAVE_DATA 0 // also works 5
#define LVD_ATTACH_IMAGE_TYPE_PFS_SAVE_DATA 5
#define LVD_ATTACH_LAYER_COUNT 1
#define LVD_ATTACH_LAYER_ARRAY_SIZE 3
#define LVD_ENTRY_TYPE_FILE 1
#define LVD_ENTRY_TYPE_SPECIAL 2
#define LVD_ENTRY_FLAG_NO_BITMAP 0x1
#define LVD_NODE_WAIT_US 100000
#define LVD_NODE_WAIT_RETRIES 100
+16 -18
View File
@@ -29,14 +29,12 @@ typedef struct {
} devpfs_mount_profile_t;
typedef struct {
// Source object class (observed: 1=file, 2=device-like source).
// Source object class (observed: 1=file, 2=device-like/special source).
uint16_t source_type;
// Layer behavior flags (observed bit0 = no bitmap file path).
uint8_t entry_flags;
// Layer descriptor flags (observed bit0 = no bitmap file path).
uint16_t flags;
// Must be zero.
uint8_t reserved0;
// Must be zero.
uint32_t reserved1;
uint32_t reserved0;
// Backing file or device path.
const char *path;
// Data start offset in backing object (bytes).
@@ -49,28 +47,28 @@ typedef struct {
uint64_t bitmap_offset;
// Bitmap size (bytes), 0 when bitmap is unused.
uint64_t bitmap_size;
} lvd_kernel_layer_t;
} lvd_ioctl_layer_v0_t;
typedef struct {
// Protocol version for /dev/lvdctl ioctl payload (valid <=1).
// Protocol version for /dev/lvdctl ioctl payload (0 = V0/base attach).
uint32_t io_version;
// Input: usually -1 for auto-assign. Output: created lvd unit id.
int32_t device_id;
// Sector-like size fields used by LVD attach request validation.
// In refs these are populated from statfs and clamped to <= 4096.
uint32_t sector_size_0;
uint32_t sector_size_1;
// Encoded option length derived from option flags (0x14 for 0x8, 0x1C for 0x9).
uint16_t option_len;
// LVD image type id (validator accepts 0..0xC; this code uses 0).
// User-visible sector size exported by /dev/lvdN.
uint32_t sector_size;
// Secondary unit/granularity validated against sector_size.
uint32_t secondary_unit;
// Normalized attach flags produced from wrapper raw options.
uint16_t flags;
// LVD image type id (validator accepts 0..0xC).
uint16_t image_type;
// Number of valid entries in layers_ptr.
uint32_t layer_count;
// Total exported virtual size (bytes).
uint64_t device_size;
// Pointer to layer array in user payload.
lvd_kernel_layer_t *layers_ptr;
} lvd_ioctl_attach_t;
// Pointer to V0 layer array in user payload.
lvd_ioctl_layer_v0_t *layers_ptr;
} lvd_ioctl_attach_v0_t;
typedef struct {
// Must be zero.
+82 -40
View File
@@ -23,7 +23,7 @@ static bool wait_for_dev_node_state(const char *devname, bool should_exist) {
return false;
}
static uint32_t get_lvd_sector_size(image_fs_type_t fs_type) {
static uint32_t get_lvd_sector_size_fallback(image_fs_type_t fs_type) {
const runtime_config_t *cfg = runtime_config();
switch (fs_type) {
case IMAGE_FS_UFS:
@@ -36,6 +36,31 @@ static uint32_t get_lvd_sector_size(image_fs_type_t fs_type) {
}
}
static uint32_t get_lvd_sector_size(const char *path, image_fs_type_t fs_type) {
uint32_t fallback = get_lvd_sector_size_fallback(fs_type);
if (!path)
return fallback;
struct statfs sfs;
if (statfs(path, &sfs) != 0)
return fallback;
uint64_t fs_cluster_size = (uint64_t)sfs.f_bsize;
if (fs_cluster_size == 0)
fs_cluster_size = (uint64_t)sfs.f_iosize;
if (fs_cluster_size == 0 || fs_cluster_size >= (uint64_t)fallback)
return fallback;
return (uint32_t)fs_cluster_size;
}
static uint32_t get_lvd_secondary_unit(const char *path,
image_fs_type_t fs_type) {
if (fs_type == IMAGE_FS_EXFAT)
return LVD_SECONDARY_UNIT_SINGLE_IMAGE;
return get_lvd_sector_size(path, fs_type);
}
static uint32_t get_md_sector_size(image_fs_type_t fs_type) {
const runtime_config_t *cfg = runtime_config();
switch (fs_type) {
@@ -54,14 +79,14 @@ static unsigned int get_md_attach_options(bool mount_read_only) {
return options;
}
static uint16_t get_lvd_attach_option(image_fs_type_t fs_type,
bool mount_read_only) {
if (fs_type == IMAGE_FS_UFS) {
return mount_read_only ? LVD_ATTACH_OPTION_NORM_DD_RO
: LVD_ATTACH_OPTION_NORM_DD_RW;
static uint16_t get_lvd_attach_raw_flags(image_fs_type_t fs_type,
bool mount_read_only) {
if (fs_type == IMAGE_FS_UFS) {
return mount_read_only ? LVD_ATTACH_RAW_FLAGS_DD_RO
: LVD_ATTACH_RAW_FLAGS_DD_RW;
}
return mount_read_only ? LVD_ATTACH_OPTION_FLAGS_DEFAULT
: LVD_ATTACH_OPTION_FLAGS_RW;
return mount_read_only ? LVD_ATTACH_RAW_FLAGS_SINGLE_RO
: LVD_ATTACH_RAW_FLAGS_SINGLE_RW;
}
static unsigned int get_nmount_flags(image_fs_type_t fs_type,
@@ -77,15 +102,38 @@ static unsigned int get_nmount_flags(image_fs_type_t fs_type,
return mount_read_only ? MNT_RDONLY : 0;
}
static uint16_t lvd_option_len_from_flags(uint16_t options) {
if ((options & 0x800Eu) != 0u) {
uint32_t raw = (uint32_t)options;
static uint16_t normalize_lvd_raw_flags(uint16_t raw_flags) {
if ((raw_flags & 0x800Eu) != 0u) {
uint32_t raw = (uint32_t)raw_flags;
uint32_t len = (raw & 0xFFFF8000u) + ((raw & 2u) << 6) +
(8u * (raw & 1u)) + (2u * ((raw >> 2) & 1u)) +
(2u * (raw & 8u)) + 4u;
return (uint16_t)len;
}
return (uint16_t)(8u * ((uint32_t)options & 1u) + 4u);
return (uint16_t)(8u * ((uint32_t)raw_flags & 1u) + 4u);
}
static uint16_t get_lvd_image_type(image_fs_type_t fs_type) {
if (fs_type == IMAGE_FS_UFS)
return LVD_ATTACH_IMAGE_TYPE_UFS_DOWNLOAD_DATA;
if (fs_type == IMAGE_FS_PFS)
return LVD_ATTACH_IMAGE_TYPE_PFS_SAVE_DATA;
return LVD_ATTACH_IMAGE_TYPE_SINGLE;
}
static uint16_t get_lvd_source_type(const char *path) {
if (!path)
return LVD_ENTRY_TYPE_FILE;
if (strncmp(path, "/dev/sbram0", strlen("/dev/sbram0")) == 0)
return LVD_ENTRY_TYPE_SPECIAL;
struct stat st;
if (stat(path, &st) == 0) {
if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode))
return LVD_ENTRY_TYPE_SPECIAL;
}
return LVD_ENTRY_TYPE_FILE;
}
// --- Image Path and Naming Helpers ---
@@ -271,49 +319,42 @@ static bool attach_lvd_backend(const char *file_path, image_fs_type_t fs_type,
return false;
}
lvd_kernel_layer_t *layers =
calloc(LVD_ATTACH_LAYER_ARRAY_SIZE, sizeof(*layers));
if (!layers) {
log_debug(" [IMG] calloc layers failed");
close(lvd_fd);
return false;
}
layers[0].source_type = LVD_ENTRY_TYPE_FILE;
layers[0].entry_flags = LVD_ENTRY_FLAG_NO_BITMAP;
lvd_ioctl_layer_v0_t layers[LVD_ATTACH_LAYER_COUNT];
memset(layers, 0, sizeof(layers));
layers[0].source_type = get_lvd_source_type(file_path);
layers[0].flags = LVD_ENTRY_FLAG_NO_BITMAP;
layers[0].path = file_path;
layers[0].offset = 0;
layers[0].size = (uint64_t)file_size;
lvd_ioctl_attach_t req;
uint32_t sector_size = get_lvd_sector_size(file_path, fs_type);
uint32_t secondary_unit = get_lvd_secondary_unit(file_path, fs_type);
uint16_t raw_flags = get_lvd_attach_raw_flags(fs_type, mount_read_only);
uint16_t normalized_flags = normalize_lvd_raw_flags(raw_flags);
lvd_ioctl_attach_v0_t req;
memset(&req, 0, sizeof(req));
req.io_version = LVD_ATTACH_IO_VERSION;
req.image_type = (fs_type == IMAGE_FS_UFS)
? LVD_ATTACH_IMAGE_TYPE_UFS_DOWNLOAD_DATA
: (fs_type == IMAGE_FS_PFS)
? LVD_ATTACH_IMAGE_TYPE_PFS_SAVE_DATA
: LVD_ATTACH_IMAGE_TYPE;
req.io_version = LVD_ATTACH_IO_VERSION_V0;
req.image_type = get_lvd_image_type(fs_type);
req.layer_count = LVD_ATTACH_LAYER_COUNT;
req.device_size = (uint64_t)file_size;
req.layers_ptr = layers;
req.sector_size_0 = get_lvd_sector_size(fs_type);
req.sector_size_1 = req.sector_size_0;
uint16_t attach_option = get_lvd_attach_option(fs_type, mount_read_only);
req.option_len = (fs_type == IMAGE_FS_UFS) ? attach_option
: lvd_option_len_from_flags(attach_option);
req.sector_size = sector_size;
req.secondary_unit = secondary_unit;
req.flags = normalized_flags;
req.device_id = -1;
int last_errno = 0;
log_debug(" [IMG][%s] attach try: ver=%u sec=%u options=0x%x len=0x%x",
log_debug(" [IMG][%s] attach try: ver=%u sec=%u sec2=%u raw=0x%x "
"flags=0x%x img=%u",
attach_backend_name(ATTACH_BACKEND_LVD), req.io_version,
req.sector_size_0, attach_option, req.option_len);
int ret = ioctl(lvd_fd, SCE_LVD_IOC_ATTACH, &req);
req.sector_size, req.secondary_unit, raw_flags, req.flags,
req.image_type);
int ret = ioctl(lvd_fd, SCE_LVD_IOC_ATTACH_V0, &req);
if (ret != 0)
last_errno = errno;
close(lvd_fd);
int unit_id = req.device_id;
free(layers);
if (ret != 0) {
errno = last_errno;
@@ -580,7 +621,8 @@ static bool validate_mounted_image(const char *file_path, image_fs_type_t fs_typ
uint32_t min_device_sector =
(attach_backend == ATTACH_BACKEND_MD) ? get_md_sector_size(fs_type)
: get_lvd_sector_size(fs_type);
: get_lvd_sector_size(file_path,
fs_type);
uint64_t fs_block_size = (uint64_t)mounted_sfs.f_bsize;
if (fs_block_size >= (uint64_t)min_device_sector)
return true;