diff options
Diffstat (limited to 'samsung-ipc/devices/herolte/herolte.c')
-rw-r--r-- | samsung-ipc/devices/herolte/herolte.c | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/samsung-ipc/devices/herolte/herolte.c b/samsung-ipc/devices/herolte/herolte.c new file mode 100644 index 0000000..d45b272 --- /dev/null +++ b/samsung-ipc/devices/herolte/herolte.c @@ -0,0 +1,515 @@ +/* + * This file is part of libsamsung-ipc. + * + * Copyright (C) 2013-2014 Paul Kocialkowski <contact@paulk.fr> + * Copyright (C) 2017 Wolfgang Wiedmeyer <wolfgit@wiedmeyer.de> + * Copyright (C) 2020 Tony Garnock-Jones <tonyg@leastfixedpoint.com> + * + * libsamsung-ipc 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. + * + * libsamsung-ipc 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 libsamsung-ipc. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <fcntl.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "ipc.h" +#include "devices/herolte/herolte.h" +#include "modems/xmm626/xmm626.h" +#include "modems/xmm626/xmm626_modem_prj.h" +#include "modems/xmm626/xmm626_kernel_smdk4412.h" + +struct __attribute__((__packed__)) firmware_toc_entry { + char name[12]; + uint32_t offset; /* offset within firmware file/partition */ + uint32_t loadaddr; /* target memory address for this blob */ + uint32_t size; /* size of this blob in bytes */ + uint32_t crc; + uint32_t entryid; +}; + +struct __attribute__((__packed__)) security_req { + uint32_t mode; + uint32_t size_boot; + uint32_t size_main; + uint32_t pad_zero; +}; + +struct __attribute__((__packed__)) modem_firmware { + uint64_t binary; + uint32_t size; + uint32_t m_offset; + uint32_t b_offset; + uint32_t mode; + uint32_t len; +}; + +#define IOCTL_SECURITY_REQ _IO('o', 0x53) + +#define N_TOC_ENTRIES (512 / sizeof(struct firmware_toc_entry)) + +static struct firmware_toc_entry const *find_toc_entry( + char const *name, + struct firmware_toc_entry const *toc) +{ + /* We don't know, yet, details of the TOC format; for now, we assume two things: + 1. reading 512 bytes of TOC is enough, and + 2. the first entry with an empty name field ends the list. */ + + for (int index = 0; index < N_TOC_ENTRIES; index++) { + if (toc[index].name[0] == '\0') + break; + if (strncmp(toc[index].name, name, sizeof(toc[index].name)) == 0) + return &toc[index]; + } + return NULL; +} + +#define MAX_CHUNK_LEN (62 * 1024) /* This is just what cbd uses. + * Perhaps a larger value would also + * work. */ + +static int upload_chunk(struct ipc_client *client, + int device_fd, + int firmware_fd, + struct firmware_toc_entry const *toc, + char const *name, + uint32_t *size) +{ + int rc = -1; + uint8_t *buffer = NULL; + struct modem_firmware header; + struct firmware_toc_entry const *boot_toc_entry; + struct firmware_toc_entry const *current_toc_entry; + uint32_t remaining; + + ipc_client_log(client, "Uploading %s", name); + + boot_toc_entry = find_toc_entry("BOOT", toc); + if (boot_toc_entry == NULL) + goto exit; + + current_toc_entry = find_toc_entry(name, toc); + if (current_toc_entry == NULL) + goto exit; + + if (size != NULL) + *size = current_toc_entry->size; + ipc_client_log(client, " - blob size for %s is %lu", name, current_toc_entry->size); + + buffer = calloc(1, MAX_CHUNK_LEN); + if (buffer == NULL) + goto exit; + + header.binary = (uint64_t) buffer; + header.size = current_toc_entry->size; + header.m_offset = current_toc_entry->loadaddr - boot_toc_entry->loadaddr; + header.b_offset = current_toc_entry->offset; + header.mode = 0; + header.len = 0; + + if (lseek(firmware_fd, header.b_offset, SEEK_SET) < 0) + goto exit; + + remaining = header.size; + while (remaining > 0) { + header.len = remaining < MAX_CHUNK_LEN ? remaining : MAX_CHUNK_LEN; + if (read(firmware_fd, buffer, header.len) != header.len) + goto exit; + if (ioctl(device_fd, IOCTL_DPRAM_SEND_BOOT, &header) < 0) + goto exit; + header.m_offset += header.len; + header.b_offset += header.len; + remaining -= header.len; + } + + rc = 0; + +exit: + if (buffer != NULL) + free(buffer); + return rc; +} + +static int select_secure_mode(struct ipc_client *client, + int boot0_fd, + int secure, + uint32_t size_boot, + uint32_t size_main) +{ + struct security_req req; + + ipc_client_log(client, + "Issuing IOCTL_SECURITY_REQ - setting %s mode", + secure ? "secure" : "insecure"); + + req.mode = secure ? 0 : 2; + req.size_boot = size_boot; + req.size_main = size_main; + req.pad_zero = 0; + + if (ioctl(boot0_fd, IOCTL_SECURITY_REQ, &req) < 0) + return -1; + + return 0; +} + +static char const * const modem_image_devices[] = { + "/dev/disk/by-partlabel/RADIO", /* PostmarketOS */ + "/dev/block/platform/155a0000.ufs/by-name/RADIO", /* LineageOS */ + NULL +}; + +static int open_image_device(struct ipc_client *client) +{ + for (int i = 0; modem_image_devices[i] != NULL; i++) { + char const * const path = modem_image_devices[i]; + int fd; + + ipc_client_log(client, " ... trying device path %s", path); + + fd = open(path, O_RDONLY); + if (fd < 0) { + if (errno == ENOENT) + continue; + return -1; + } + return fd; + } + + ipc_client_log(client, " ... no modem image device found!"); + errno = ENOENT; + return -1; +} + +int herolte_boot(struct ipc_client *client) +{ + struct firmware_toc_entry toc[N_TOC_ENTRIES]; + int imagefd = -1; + int boot0_fd = -1; + int nvfd = -1; + int rc = -1; + uint32_t size_boot; + uint32_t size_main; + + ipc_client_log(client, "Loading firmware TOC"); + + imagefd = open_image_device(client); + if (imagefd < 0) + goto exit; + + if (read(imagefd, &toc[0], sizeof(toc)) != sizeof(toc)) + goto exit; + + ipc_client_log(client, "Loaded firmware TOC"); + + nvfd = open(herolte_nv_data_specs.nv_data_path, O_RDONLY | O_NOCTTY); + if (nvfd < 0) + goto exit; + ipc_client_log(client, "Opened NV data file"); + + boot0_fd = open(XMM626_SEC_MODEM_BOOT0_DEVICE, O_RDWR | O_NOCTTY); + if (boot0_fd < 0) + goto exit; + + ipc_client_log(client, "Resetting modem"); + if (ioctl(boot0_fd, IOCTL_MODEM_RESET, 0) < 0) + goto exit; + + if (select_secure_mode(client, boot0_fd, 0, 0, 0) < 0) + goto exit; + + if (upload_chunk(client, boot0_fd, imagefd, toc, "BOOT", &size_boot) < 0) + goto exit; + + if (upload_chunk(client, boot0_fd, imagefd, toc, "MAIN", &size_main) < 0) + goto exit; + + if (upload_chunk(client, boot0_fd, nvfd, toc, "NV", NULL) < 0) + goto exit; + + if (select_secure_mode(client, boot0_fd, 1, size_boot, size_main) < 0) + goto exit; + + ipc_client_log(client, "Powering on modem"); + if (xmm626_kernel_smdk4412_power(client, boot0_fd, 1) < 0) + goto exit; + + ipc_client_log(client, "Starting modem boot process"); + if (xmm626_kernel_smdk4412_boot_power(client, boot0_fd, 1) < 0) + goto exit; + + ipc_client_log(client, "Kicking off firmware download"); + if (ioctl(boot0_fd, IOCTL_MODEM_DL_START, 0) < 0) + goto exit; + + ipc_client_log(client, "Handshaking with modem"); + /* At this point, cbd engages in a little dance with the + * newly-booted modem, apparently to verify that it is running + * as expected. I don’t know the sources of these magic + * numbers, I just faithfully reproduce them. */ + { + uint32_t buf; + + buf = 0x900d; + if (write(boot0_fd, &buf, sizeof(buf)) != sizeof(buf)) + goto exit; + if (read(boot0_fd, &buf, sizeof(buf)) != sizeof(buf)) + goto exit; + if (buf != 0xa00d) + goto exit; + ipc_client_log(client, "Handshake stage I passed"); + + buf = 0x9f00; + if (write(boot0_fd, &buf, sizeof(buf)) != sizeof(buf)) + goto exit; + if (read(boot0_fd, &buf, sizeof(buf)) != sizeof(buf)) + goto exit; + if (buf != 0xaf00) + goto exit; + ipc_client_log(client, "Handshake stage II passed"); + } + + ipc_client_log(client, "Finishing modem boot process"); + if (xmm626_kernel_smdk4412_boot_power(client, boot0_fd, 0) < 0) + goto exit; + + ipc_client_log(client, "Modem boot complete"); + rc = 0; + + /* Samsung's official daemons continue to read from umts_boot0 + * at this point, but at present we don't have any means of + * doing that within the design of this framework. This is + * probably (?) ok, since I have never seen anything actually + * come out of umts_boot0 after booting is complete! */ + + /* One thing that could be an issue, though, is the kernel's + * use of its function rild_ready(). The phone wants *both* + * umts_ipc0 and umts_rfs0 to be open for it to be fully + * happy. Any upper-level clients of this library will have to + * make sure to open both for things to work. */ + +exit: + if (boot0_fd != -1) + close(boot0_fd); + if (imagefd != -1) + close(imagefd); + if (nvfd != -1) + close(nvfd); + return rc; +} + +int herolte_open(__attribute__((unused)) struct ipc_client *client, void *data, + int type) +{ + struct herolte_transport_data *transport_data; + + if (data == NULL) + return -1; + + transport_data = (struct herolte_transport_data *) data; + + transport_data->fd = xmm626_kernel_smdk4412_open(client, type); + if (transport_data->fd < 0) + return -1; + + return 0; +} + +int herolte_close(__attribute__((unused)) struct ipc_client *client, void *data) +{ + struct herolte_transport_data *transport_data; + + if (data == NULL) + return -1; + + transport_data = (struct herolte_transport_data *) data; + + xmm626_kernel_smdk4412_close(client, transport_data->fd); + transport_data->fd = -1; + + return 0; +} + +int herolte_read(__attribute__((unused)) struct ipc_client *client, void *data, + void *buffer, size_t length) +{ + struct herolte_transport_data *transport_data; + int rc; + + if (data == NULL) + return -1; + + transport_data = (struct herolte_transport_data *) data; + + rc = xmm626_kernel_smdk4412_read(client, transport_data->fd, buffer, + length); + + return rc; +} + +int herolte_write(__attribute__((unused)) struct ipc_client *client, void *data, + const void *buffer, size_t length) +{ + struct herolte_transport_data *transport_data; + int rc; + + if (data == NULL) + return -1; + + transport_data = (struct herolte_transport_data *) data; + + rc = xmm626_kernel_smdk4412_write(client, transport_data->fd, buffer, + length); + + return rc; +} + +int herolte_poll(__attribute__((unused)) struct ipc_client *client, void *data, + struct ipc_poll_fds *fds, struct timeval *timeout) +{ + struct herolte_transport_data *transport_data; + int rc; + + if (data == NULL) + return -1; + + transport_data = (struct herolte_transport_data *) data; + + rc = xmm626_kernel_smdk4412_poll(client, transport_data->fd, fds, + timeout); + + return rc; +} + +int herolte_power_on(__attribute__((unused)) struct ipc_client *client, + __attribute__((unused)) void *data) +{ + return 0; +} + +int herolte_power_off(__attribute__((unused)) struct ipc_client *client, + __attribute__((unused)) void *data) +{ + int fd; + int rc; + + fd = open(XMM626_SEC_MODEM_BOOT0_DEVICE, O_RDWR | O_NOCTTY | + O_NONBLOCK); + if (fd < 0) + return -1; + + rc = xmm626_kernel_smdk4412_power(client, fd, 0); + + close(fd); + + if (rc < 0) + return -1; + + return 0; +} + +int herolte_gprs_activate(__attribute__((unused)) struct ipc_client *client, + __attribute__((unused)) void *data, + __attribute__((unused)) unsigned int cid) +{ + /* TODO: For now, we don't have enough information to + * implement this sensibly, hence this placeholder. */ + return 0; +} + +int herolte_gprs_deactivate(__attribute__((unused)) struct ipc_client *client, + __attribute__((unused)) void *data, + __attribute__((unused)) unsigned int cid) +{ + /* TODO: For now, we don't have enough information to + * implement this sensibly, hence this placeholder. */ + return 0; +} + +int herolte_data_create(__attribute__((unused)) struct ipc_client *client, + void **transport_data, + __attribute__((unused)) void **power_data, + __attribute__((unused)) void **gprs_data) +{ + if (transport_data == NULL) + return -1; + + *transport_data = calloc(1, sizeof(struct herolte_transport_data)); + + return 0; +} + +int herolte_data_destroy(__attribute__((unused)) struct ipc_client *client, + void *transport_data, + __attribute__((unused)) void *power_data, + __attribute__((unused)) void *gprs_data) +{ + if (transport_data == NULL) + return -1; + + free(transport_data); + + return 0; +} + +struct ipc_client_ops herolte_fmt_ops = { + .boot = herolte_boot, + .send = xmm626_kernel_smdk4412_fmt_send, + .recv = xmm626_kernel_smdk4412_fmt_recv, +}; + +struct ipc_client_ops herolte_rfs_ops = { + .boot = NULL, + .send = xmm626_kernel_smdk4412_rfs_send, + .recv = xmm626_kernel_smdk4412_rfs_recv, +}; + +struct ipc_client_handlers herolte_handlers = { + .read = herolte_read, + .write = herolte_write, + .open = herolte_open, + .close = herolte_close, + .poll = herolte_poll, + .transport_data = NULL, + .power_on = herolte_power_on, + .power_off = herolte_power_off, + .power_data = NULL, + .gprs_activate = herolte_gprs_activate, + .gprs_deactivate = herolte_gprs_deactivate, + .gprs_data = NULL, + .data_create = herolte_data_create, + .data_destroy = herolte_data_destroy, +}; + +struct ipc_client_gprs_specs herolte_gprs_specs = { + .gprs_get_iface = xmm626_kernel_smdk4412_gprs_get_iface, + .gprs_get_capabilities = xmm626_kernel_smdk4412_gprs_get_capabilities, +}; + +struct ipc_client_nv_data_specs herolte_nv_data_specs = { + .nv_data_path = XMM626_NV_DATA_PATH, + .nv_data_md5_path = XMM626_NV_DATA_MD5_PATH, + .nv_data_backup_path = XMM626_NV_DATA_BACKUP_PATH, + .nv_data_backup_md5_path = XMM626_NV_DATA_BACKUP_MD5_PATH, + .nv_data_secret = XMM626_NV_DATA_SECRET, + .nv_data_size = XMM626_NV_DATA_SIZE, + .nv_data_chunk_size = XMM626_NV_DATA_CHUNK_SIZE, +}; |