v1.1.0: Complete kernel modules, fix WebUI bugs
Kernel modules fully implemented for kernel 6.6/Tensor G5: - rc_wifi_mon: kprobes kallsyms, bcmdhd iovar monitor/promisc/allmulti, sysfs status at /sys/kernel/rc_wifi_mon/, clean unpatch on unload - rc_shannon_cmd: ioctl interface (AT_CMD, GET_URC, SET_TIMEOUT, GET_STATUS, FLUSH), URC ring buffer (64 entries), modem probe on init - rc_diag_bridge: HDLC decode with CRC-16 validation, FTM ioctl, EFS read/write/stat/unlink, version query, subsystem dispatch - rc_ioctl.h: shared userspace header for all ioctl definitions - All modules handle class_create() API change in kernel 6.4+ WebUI fixes: - Fix malformed WiFi firmware JSON output - Add vonr/vt/apn/nradv to carrier config read endpoint - Fix carrier toggle state loading in frontend - Fix redundant replace in kmod toggle logic Makefile: single-module build (MOD=), make package target uninstall.sh: unload kernel modules before cleanup
This commit is contained in:
@@ -16,6 +16,11 @@
|
||||
* - FTM commands (subsys 0x4B, subsys_id 11)
|
||||
* - Log mask / message mask configuration
|
||||
* - Raw DIAG passthrough for advanced use
|
||||
*
|
||||
* Note: This module is for devices with Qualcomm basebands.
|
||||
* On Tensor G5 with Shannon 5400, use rc_shannon_cmd instead.
|
||||
*
|
||||
* Target: kernel 6.6
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
@@ -30,6 +35,8 @@
|
||||
#include <linux/wait.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("RadioControl");
|
||||
@@ -37,29 +44,58 @@ MODULE_DESCRIPTION("Qualcomm DIAG protocol bridge for NV/EFS/FTM access");
|
||||
MODULE_VERSION("1.0");
|
||||
|
||||
#define DEVICE_NAME "rc_diag"
|
||||
#define CLASS_NAME "radiocontrol"
|
||||
#define CLASS_NAME "radiocontrol_diag"
|
||||
#define DIAG_BUF_SIZE 8192
|
||||
|
||||
/* DIAG command codes */
|
||||
#define DIAG_NV_READ_F 0x26
|
||||
#define DIAG_NV_WRITE_F 0x27
|
||||
#define DIAG_STATUS_F 0x0C
|
||||
#define DIAG_VERNO_F 0x00
|
||||
#define DIAG_SUBSYS_CMD_F 0x4B
|
||||
#define DIAG_LOG_CONFIG_F 0x73
|
||||
#define DIAG_MSG_CONFIG_F 0x7D
|
||||
#define DIAG_EXT_BUILD_ID_F 0x7C
|
||||
|
||||
/* DIAG subsystem IDs */
|
||||
#define DIAG_SUBSYS_FTM 11
|
||||
#define DIAG_SUBSYS_EFS2 19
|
||||
#define DIAG_SUBSYS_PARAMS 37
|
||||
#define DIAG_SUBSYS_DIAG 18
|
||||
|
||||
/* EFS2 sub-commands (within DIAG_SUBSYS_EFS2) */
|
||||
#define EFS2_DIAG_OPEN 0x01
|
||||
#define EFS2_DIAG_CLOSE 0x02
|
||||
#define EFS2_DIAG_READ 0x03
|
||||
#define EFS2_DIAG_WRITE 0x04
|
||||
#define EFS2_DIAG_MKDIR 0x06
|
||||
#define EFS2_DIAG_OPENDIR 0x08
|
||||
#define EFS2_DIAG_READDIR 0x09
|
||||
#define EFS2_DIAG_CLOSEDIR 0x0A
|
||||
#define EFS2_DIAG_STAT 0x0D
|
||||
#define EFS2_DIAG_UNLINK 0x10
|
||||
|
||||
/* FTM sub-commands */
|
||||
#define FTM_SET_MODE 0x00
|
||||
#define FTM_SET_CHAN 0x03
|
||||
#define FTM_SET_TX_ON 0x06
|
||||
#define FTM_SET_TX_OFF 0x07
|
||||
#define FTM_SET_PA_RANGE 0x08
|
||||
#define FTM_SET_PDM 0x09
|
||||
#define FTM_GET_RSSI 0x26
|
||||
#define FTM_GET_RX_LEVEL 0x44
|
||||
|
||||
/* IOCTL commands */
|
||||
#define RC_DIAG_MAGIC 'D'
|
||||
#define RC_DIAG_NV_READ _IOWR(RC_DIAG_MAGIC, 1, struct rc_nv_item)
|
||||
#define RC_DIAG_NV_WRITE _IOW(RC_DIAG_MAGIC, 2, struct rc_nv_item)
|
||||
#define RC_DIAG_RAW_CMD _IOWR(RC_DIAG_MAGIC, 3, struct rc_diag_raw)
|
||||
#define RC_DIAG_FTM_CMD _IOWR(RC_DIAG_MAGIC, 4, struct rc_diag_raw)
|
||||
#define RC_DIAG_FTM_CMD _IOWR(RC_DIAG_MAGIC, 4, struct rc_ftm_cmd)
|
||||
#define RC_DIAG_EFS_READ _IOWR(RC_DIAG_MAGIC, 5, struct rc_efs_op)
|
||||
#define RC_DIAG_EFS_WRITE _IOW(RC_DIAG_MAGIC, 6, struct rc_efs_op)
|
||||
#define RC_DIAG_EFS_STAT _IOWR(RC_DIAG_MAGIC, 7, struct rc_efs_op)
|
||||
#define RC_DIAG_EFS_UNLINK _IOW(RC_DIAG_MAGIC, 8, struct rc_efs_op)
|
||||
#define RC_DIAG_GET_VERSION _IOR(RC_DIAG_MAGIC, 9, struct rc_diag_version)
|
||||
|
||||
/* NV item structure */
|
||||
struct rc_nv_item {
|
||||
@@ -69,7 +105,7 @@ struct rc_nv_item {
|
||||
uint32_t data_len;
|
||||
};
|
||||
|
||||
/* Raw DIAG command */
|
||||
/* Raw DIAG command — allocated on heap due to size */
|
||||
struct rc_diag_raw {
|
||||
uint8_t cmd[DIAG_BUF_SIZE];
|
||||
uint32_t cmd_len;
|
||||
@@ -77,12 +113,34 @@ struct rc_diag_raw {
|
||||
uint32_t resp_len;
|
||||
};
|
||||
|
||||
/* FTM command */
|
||||
struct rc_ftm_cmd {
|
||||
uint16_t cmd_id;
|
||||
uint16_t data_len;
|
||||
uint8_t data[512];
|
||||
uint16_t status;
|
||||
uint8_t resp[512];
|
||||
uint16_t resp_len;
|
||||
};
|
||||
|
||||
/* EFS operation */
|
||||
struct rc_efs_op {
|
||||
char path[256];
|
||||
uint8_t data[4096];
|
||||
uint32_t data_len;
|
||||
int32_t status;
|
||||
uint32_t mode; /* file mode for open/mkdir */
|
||||
uint32_t offset; /* read/write offset */
|
||||
};
|
||||
|
||||
/* Version info */
|
||||
struct rc_diag_version {
|
||||
char comp_date[12];
|
||||
char comp_time[8];
|
||||
char rel_date[12];
|
||||
char rel_time[8];
|
||||
char model[32];
|
||||
uint8_t mob_sw_rev;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -93,8 +151,8 @@ struct rc_efs_op {
|
||||
* Escape: 0x7E -> 0x7D 0x5E, 0x7D -> 0x7D 0x5D
|
||||
*/
|
||||
|
||||
#define HDLC_FLAG 0x7E
|
||||
#define HDLC_ESC 0x7D
|
||||
#define HDLC_FLAG 0x7E
|
||||
#define HDLC_ESC 0x7D
|
||||
#define HDLC_ESC_MASK 0x20
|
||||
|
||||
static const uint16_t crc16_table[256] = {
|
||||
@@ -135,6 +193,7 @@ static const uint16_t crc16_table[256] = {
|
||||
static uint16_t crc16_calc(const uint8_t *buf, int len)
|
||||
{
|
||||
uint16_t crc = 0xFFFF;
|
||||
|
||||
while (len--)
|
||||
crc = crc16_table[(crc ^ *buf++) & 0xFF] ^ (crc >> 8);
|
||||
return ~crc & 0xFFFF;
|
||||
@@ -148,6 +207,9 @@ static int hdlc_encode(const uint8_t *src, int src_len,
|
||||
int pos = 0;
|
||||
int i;
|
||||
|
||||
if (dst_size < src_len * 2 + 6)
|
||||
return -ENOMEM;
|
||||
|
||||
crc = crc16_calc(src, src_len);
|
||||
|
||||
dst[pos++] = HDLC_FLAG;
|
||||
@@ -164,6 +226,7 @@ static int hdlc_encode(const uint8_t *src, int src_len,
|
||||
/* Append CRC (little-endian) with escaping */
|
||||
for (i = 0; i < 2 && pos < dst_size - 2; i++) {
|
||||
uint8_t b = (crc >> (i * 8)) & 0xFF;
|
||||
|
||||
if (b == HDLC_FLAG || b == HDLC_ESC) {
|
||||
dst[pos++] = HDLC_ESC;
|
||||
dst[pos++] = b ^ HDLC_ESC_MASK;
|
||||
@@ -176,6 +239,67 @@ static int hdlc_encode(const uint8_t *src, int src_len,
|
||||
return pos;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode an HDLC-framed DIAG response.
|
||||
* Strips framing bytes, unescapes, validates CRC.
|
||||
* Returns payload length (without CRC) or negative error.
|
||||
*/
|
||||
static int hdlc_decode(const uint8_t *src, int src_len,
|
||||
uint8_t *dst, int dst_size)
|
||||
{
|
||||
int pos = 0;
|
||||
int i;
|
||||
int start = -1, end = -1;
|
||||
uint16_t crc_recv, crc_calc;
|
||||
|
||||
/* Find the HDLC frame boundaries */
|
||||
for (i = 0; i < src_len; i++) {
|
||||
if (src[i] == HDLC_FLAG) {
|
||||
if (start < 0) {
|
||||
start = i;
|
||||
} else {
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (start < 0 || end < 0 || end <= start + 1)
|
||||
return -EINVAL;
|
||||
|
||||
/* Unescape the payload between the flags */
|
||||
for (i = start + 1; i < end && pos < dst_size; i++) {
|
||||
if (src[i] == HDLC_ESC) {
|
||||
i++;
|
||||
if (i >= end)
|
||||
return -EINVAL;
|
||||
dst[pos++] = src[i] ^ HDLC_ESC_MASK;
|
||||
} else if (src[i] == HDLC_FLAG) {
|
||||
break;
|
||||
} else {
|
||||
dst[pos++] = src[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Need at least 2 bytes for CRC + 1 byte payload */
|
||||
if (pos < 3)
|
||||
return -EINVAL;
|
||||
|
||||
/* Last 2 bytes are CRC-16 (little-endian) */
|
||||
crc_recv = dst[pos - 2] | (dst[pos - 1] << 8);
|
||||
pos -= 2;
|
||||
|
||||
/* Validate CRC */
|
||||
crc_calc = crc16_calc(dst, pos);
|
||||
if (crc_calc != crc_recv) {
|
||||
pr_debug("rc_diag: CRC mismatch: calculated 0x%04x, "
|
||||
"received 0x%04x\n", crc_calc, crc_recv);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
static int major;
|
||||
static struct class *rc_class;
|
||||
static struct cdev rc_cdev;
|
||||
@@ -185,50 +309,134 @@ static DEFINE_MUTEX(diag_mutex);
|
||||
|
||||
static struct file *open_diag_device(void)
|
||||
{
|
||||
static const char *diag_paths[] = {
|
||||
"/dev/diag",
|
||||
"/dev/diag_mdm",
|
||||
NULL
|
||||
};
|
||||
struct file *f;
|
||||
int i;
|
||||
|
||||
f = filp_open("/dev/diag", O_RDWR | O_NONBLOCK, 0);
|
||||
if (!IS_ERR(f))
|
||||
return f;
|
||||
for (i = 0; diag_paths[i]; i++) {
|
||||
f = filp_open(diag_paths[i], O_RDWR | O_NONBLOCK, 0);
|
||||
if (!IS_ERR(f)) {
|
||||
pr_info("rc_diag: opened %s\n", diag_paths[i]);
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
pr_info("rc_diag: /dev/diag not available (%ld)\n", PTR_ERR(f));
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a raw DIAG command (pre-framing) and receive decoded response.
|
||||
*/
|
||||
static int send_diag_cmd(const uint8_t *cmd, int cmd_len,
|
||||
uint8_t *resp, int resp_size)
|
||||
{
|
||||
uint8_t hdlc_buf[DIAG_BUF_SIZE * 2];
|
||||
uint8_t *hdlc_tx;
|
||||
uint8_t *hdlc_rx;
|
||||
loff_t pos = 0;
|
||||
ssize_t written, bytes_read;
|
||||
int hdlc_len;
|
||||
int timeout_ms = 2000;
|
||||
int timeout_ms = 3000;
|
||||
int elapsed = 0;
|
||||
int decoded_len;
|
||||
|
||||
if (!diag_filp || IS_ERR(diag_filp))
|
||||
return -ENODEV;
|
||||
|
||||
hdlc_tx = kmalloc(DIAG_BUF_SIZE * 2, GFP_KERNEL);
|
||||
hdlc_rx = kmalloc(DIAG_BUF_SIZE * 2, GFP_KERNEL);
|
||||
if (!hdlc_tx || !hdlc_rx) {
|
||||
kfree(hdlc_tx);
|
||||
kfree(hdlc_rx);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* HDLC encode the command */
|
||||
hdlc_len = hdlc_encode(cmd, cmd_len, hdlc_buf, sizeof(hdlc_buf));
|
||||
hdlc_len = hdlc_encode(cmd, cmd_len, hdlc_tx, DIAG_BUF_SIZE * 2);
|
||||
if (hdlc_len < 0) {
|
||||
kfree(hdlc_tx);
|
||||
kfree(hdlc_rx);
|
||||
return hdlc_len;
|
||||
}
|
||||
|
||||
/* Send to DIAG */
|
||||
written = kernel_write(diag_filp, hdlc_buf, hdlc_len, &pos);
|
||||
if (written < 0)
|
||||
return written;
|
||||
written = kernel_write(diag_filp, hdlc_tx, hdlc_len, &pos);
|
||||
kfree(hdlc_tx);
|
||||
|
||||
/* Read response */
|
||||
if (written < 0) {
|
||||
kfree(hdlc_rx);
|
||||
return written;
|
||||
}
|
||||
|
||||
/* Read response with timeout */
|
||||
pos = 0;
|
||||
while (elapsed < timeout_ms) {
|
||||
bytes_read = kernel_read(diag_filp, resp, resp_size, &pos);
|
||||
if (bytes_read > 0)
|
||||
return bytes_read;
|
||||
bytes_read = kernel_read(diag_filp, hdlc_rx,
|
||||
DIAG_BUF_SIZE * 2, &pos);
|
||||
if (bytes_read > 0) {
|
||||
/* Decode HDLC frame */
|
||||
decoded_len = hdlc_decode(hdlc_rx, bytes_read,
|
||||
resp, resp_size);
|
||||
kfree(hdlc_rx);
|
||||
|
||||
if (decoded_len < 0) {
|
||||
pr_debug("rc_diag: HDLC decode failed: %d, "
|
||||
"returning raw (%zd bytes)\n",
|
||||
decoded_len, bytes_read);
|
||||
/*
|
||||
* Some DIAG drivers return pre-decoded data.
|
||||
* Fall back to raw copy.
|
||||
*/
|
||||
if (bytes_read <= resp_size) {
|
||||
memcpy(resp, hdlc_rx, bytes_read);
|
||||
return bytes_read;
|
||||
}
|
||||
return decoded_len;
|
||||
}
|
||||
return decoded_len;
|
||||
}
|
||||
msleep(20);
|
||||
elapsed += 20;
|
||||
}
|
||||
|
||||
kfree(hdlc_rx);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build and send a subsystem command.
|
||||
* Format: [0x4B] [subsys_id] [sub_cmd LE16] [payload...]
|
||||
*/
|
||||
static int send_subsys_cmd(uint8_t subsys_id, uint16_t sub_cmd,
|
||||
const uint8_t *payload, int payload_len,
|
||||
uint8_t *resp, int resp_size)
|
||||
{
|
||||
uint8_t *cmd;
|
||||
int cmd_len = 4 + payload_len;
|
||||
int ret;
|
||||
|
||||
cmd = kmalloc(cmd_len, GFP_KERNEL);
|
||||
if (!cmd)
|
||||
return -ENOMEM;
|
||||
|
||||
cmd[0] = DIAG_SUBSYS_CMD_F;
|
||||
cmd[1] = subsys_id;
|
||||
cmd[2] = sub_cmd & 0xFF;
|
||||
cmd[3] = (sub_cmd >> 8) & 0xFF;
|
||||
if (payload_len > 0)
|
||||
memcpy(cmd + 4, payload, payload_len);
|
||||
|
||||
ret = send_diag_cmd(cmd, cmd_len, resp, resp_size);
|
||||
kfree(cmd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* IOCTL handlers
|
||||
*/
|
||||
static long rc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int ret = 0;
|
||||
@@ -253,11 +461,21 @@ static long rc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
|
||||
ret = send_diag_cmd(diag_cmd, 3, diag_resp, sizeof(diag_resp));
|
||||
if (ret > 0) {
|
||||
/* Response: [0x26] [NV_ID] [status] [data...] */
|
||||
nv.status = diag_resp[3] | (diag_resp[4] << 8);
|
||||
nv.data_len = ret > 133 ? 128 : ret - 5;
|
||||
if (nv.data_len > 0)
|
||||
memcpy(nv.data, diag_resp + 5, nv.data_len);
|
||||
/*
|
||||
* Response format:
|
||||
* [0x26] [NV_ID LE 16] [status LE 16] [data...]
|
||||
* Status: 0=OK, 5=inactive, 6=bad_security
|
||||
*/
|
||||
if (ret < 5) {
|
||||
nv.status = 0xFFFF;
|
||||
nv.data_len = 0;
|
||||
} else {
|
||||
nv.status = diag_resp[3] | (diag_resp[4] << 8);
|
||||
nv.data_len = (ret > 133) ? 128 : ret - 5;
|
||||
if (nv.data_len > 0)
|
||||
memcpy(nv.data, diag_resp + 5,
|
||||
nv.data_len);
|
||||
}
|
||||
if (copy_to_user((void __user *)arg, &nv, sizeof(nv)))
|
||||
ret = -EFAULT;
|
||||
else
|
||||
@@ -290,7 +508,10 @@ static long rc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
ret = send_diag_cmd(diag_cmd, 3 + nv.data_len,
|
||||
diag_resp, sizeof(diag_resp));
|
||||
if (ret > 0) {
|
||||
nv.status = diag_resp[3] | (diag_resp[4] << 8);
|
||||
if (ret >= 5)
|
||||
nv.status = diag_resp[3] | (diag_resp[4] << 8);
|
||||
else
|
||||
nv.status = 0xFFFF;
|
||||
if (copy_to_user((void __user *)arg, &nv, sizeof(nv)))
|
||||
ret = -EFAULT;
|
||||
else
|
||||
@@ -301,8 +522,12 @@ static long rc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
|
||||
case RC_DIAG_RAW_CMD: {
|
||||
struct rc_diag_raw *raw;
|
||||
|
||||
raw = kmalloc(sizeof(*raw), GFP_KERNEL);
|
||||
if (!raw) { ret = -ENOMEM; break; }
|
||||
if (!raw) {
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
|
||||
if (copy_from_user(raw, (void __user *)arg, sizeof(*raw))) {
|
||||
kfree(raw);
|
||||
@@ -310,11 +535,18 @@ static long rc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
break;
|
||||
}
|
||||
|
||||
if (raw->cmd_len == 0 || raw->cmd_len > DIAG_BUF_SIZE) {
|
||||
kfree(raw);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = send_diag_cmd(raw->cmd, raw->cmd_len,
|
||||
raw->resp, sizeof(raw->resp));
|
||||
if (ret > 0) {
|
||||
raw->resp_len = ret;
|
||||
if (copy_to_user((void __user *)arg, raw, sizeof(*raw)))
|
||||
if (copy_to_user((void __user *)arg, raw,
|
||||
sizeof(*raw)))
|
||||
ret = -EFAULT;
|
||||
else
|
||||
ret = 0;
|
||||
@@ -323,6 +555,393 @@ static long rc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
break;
|
||||
}
|
||||
|
||||
case RC_DIAG_FTM_CMD: {
|
||||
struct rc_ftm_cmd ftm;
|
||||
uint8_t payload[520];
|
||||
uint8_t resp[256];
|
||||
int payload_len;
|
||||
|
||||
if (copy_from_user(&ftm, (void __user *)arg, sizeof(ftm))) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ftm.data_len > sizeof(ftm.data)) {
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* FTM payload format (within subsys command):
|
||||
* [cmd_id LE16] [data_len LE16] [data...]
|
||||
*/
|
||||
payload[0] = ftm.cmd_id & 0xFF;
|
||||
payload[1] = (ftm.cmd_id >> 8) & 0xFF;
|
||||
payload[2] = ftm.data_len & 0xFF;
|
||||
payload[3] = (ftm.data_len >> 8) & 0xFF;
|
||||
if (ftm.data_len > 0)
|
||||
memcpy(payload + 4, ftm.data, ftm.data_len);
|
||||
payload_len = 4 + ftm.data_len;
|
||||
|
||||
ret = send_subsys_cmd(DIAG_SUBSYS_FTM, ftm.cmd_id,
|
||||
payload, payload_len,
|
||||
resp, sizeof(resp));
|
||||
if (ret > 0) {
|
||||
/*
|
||||
* FTM response:
|
||||
* [0x4B] [subsys_id] [cmd LE16] [status LE16] [data...]
|
||||
*/
|
||||
if (ret >= 6)
|
||||
ftm.status = resp[4] | (resp[5] << 8);
|
||||
else
|
||||
ftm.status = 0xFFFF;
|
||||
|
||||
ftm.resp_len = (ret > 6) ? ret - 6 : 0;
|
||||
if (ftm.resp_len > sizeof(ftm.resp))
|
||||
ftm.resp_len = sizeof(ftm.resp);
|
||||
if (ftm.resp_len > 0)
|
||||
memcpy(ftm.resp, resp + 6, ftm.resp_len);
|
||||
|
||||
if (copy_to_user((void __user *)arg, &ftm,
|
||||
sizeof(ftm)))
|
||||
ret = -EFAULT;
|
||||
else
|
||||
ret = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RC_DIAG_EFS_READ: {
|
||||
struct rc_efs_op *efs;
|
||||
uint8_t open_payload[264];
|
||||
uint8_t read_payload[12];
|
||||
uint8_t resp[4224];
|
||||
int path_len;
|
||||
int32_t fd;
|
||||
|
||||
efs = kmalloc(sizeof(*efs), GFP_KERNEL);
|
||||
if (!efs) {
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
|
||||
if (copy_from_user(efs, (void __user *)arg, sizeof(*efs))) {
|
||||
kfree(efs);
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
efs->path[sizeof(efs->path) - 1] = '\0';
|
||||
path_len = strlen(efs->path);
|
||||
|
||||
/*
|
||||
* Step 1: EFS2_DIAG_OPEN
|
||||
* Payload: [oflag LE32] [mode LE32] [path (null-terminated)]
|
||||
*/
|
||||
open_payload[0] = 0x00; /* O_RDONLY */
|
||||
open_payload[1] = 0x00;
|
||||
open_payload[2] = 0x00;
|
||||
open_payload[3] = 0x00;
|
||||
open_payload[4] = 0x00; /* mode (ignored for read) */
|
||||
open_payload[5] = 0x00;
|
||||
open_payload[6] = 0x00;
|
||||
open_payload[7] = 0x00;
|
||||
memcpy(open_payload + 8, efs->path, path_len + 1);
|
||||
|
||||
ret = send_subsys_cmd(DIAG_SUBSYS_EFS2, EFS2_DIAG_OPEN,
|
||||
open_payload, 8 + path_len + 1,
|
||||
resp, sizeof(resp));
|
||||
if (ret < 8) {
|
||||
efs->status = -EIO;
|
||||
goto efs_read_out;
|
||||
}
|
||||
|
||||
/* Response: [hdr 4B] [fd LE32] [errno LE32] */
|
||||
fd = resp[4] | (resp[5] << 8) |
|
||||
(resp[6] << 16) | (resp[7] << 24);
|
||||
|
||||
if (fd < 0) {
|
||||
efs->status = fd;
|
||||
goto efs_read_out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Step 2: EFS2_DIAG_READ
|
||||
* Payload: [fd LE32] [nbytes LE32] [offset LE32]
|
||||
*/
|
||||
read_payload[0] = fd & 0xFF;
|
||||
read_payload[1] = (fd >> 8) & 0xFF;
|
||||
read_payload[2] = (fd >> 16) & 0xFF;
|
||||
read_payload[3] = (fd >> 24) & 0xFF;
|
||||
read_payload[4] = 0x00; /* nbytes = 4096 */
|
||||
read_payload[5] = 0x10;
|
||||
read_payload[6] = 0x00;
|
||||
read_payload[7] = 0x00;
|
||||
read_payload[8] = efs->offset & 0xFF;
|
||||
read_payload[9] = (efs->offset >> 8) & 0xFF;
|
||||
read_payload[10] = (efs->offset >> 16) & 0xFF;
|
||||
read_payload[11] = (efs->offset >> 24) & 0xFF;
|
||||
|
||||
ret = send_subsys_cmd(DIAG_SUBSYS_EFS2, EFS2_DIAG_READ,
|
||||
read_payload, 12, resp, sizeof(resp));
|
||||
if (ret > 12) {
|
||||
/*
|
||||
* Response: [hdr 4B] [fd LE32] [offset LE32]
|
||||
* [bytes_read LE32] [errno LE32] [data...]
|
||||
*/
|
||||
int32_t bytes_read = resp[12] | (resp[13] << 8) |
|
||||
(resp[14] << 16) | (resp[15] << 24);
|
||||
int32_t efs_errno = resp[16] | (resp[17] << 8) |
|
||||
(resp[18] << 16) | (resp[19] << 24);
|
||||
|
||||
if (bytes_read > 0 && efs_errno == 0) {
|
||||
efs->data_len = min_t(uint32_t, bytes_read,
|
||||
sizeof(efs->data));
|
||||
memcpy(efs->data, resp + 20, efs->data_len);
|
||||
efs->status = 0;
|
||||
} else {
|
||||
efs->data_len = 0;
|
||||
efs->status = efs_errno ? -efs_errno : -EIO;
|
||||
}
|
||||
} else {
|
||||
efs->status = -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* Step 3: EFS2_DIAG_CLOSE
|
||||
* Payload: [fd LE32]
|
||||
*/
|
||||
send_subsys_cmd(DIAG_SUBSYS_EFS2, EFS2_DIAG_CLOSE,
|
||||
read_payload, 4, resp, sizeof(resp));
|
||||
|
||||
efs_read_out:
|
||||
if (copy_to_user((void __user *)arg, efs, sizeof(*efs)))
|
||||
ret = -EFAULT;
|
||||
else
|
||||
ret = 0;
|
||||
kfree(efs);
|
||||
break;
|
||||
}
|
||||
|
||||
case RC_DIAG_EFS_WRITE: {
|
||||
struct rc_efs_op *efs;
|
||||
uint8_t open_payload[264];
|
||||
uint8_t write_payload[4108];
|
||||
uint8_t resp[64];
|
||||
int path_len;
|
||||
int32_t fd;
|
||||
|
||||
efs = kmalloc(sizeof(*efs), GFP_KERNEL);
|
||||
if (!efs) {
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
|
||||
if (copy_from_user(efs, (void __user *)arg, sizeof(*efs))) {
|
||||
kfree(efs);
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
efs->path[sizeof(efs->path) - 1] = '\0';
|
||||
path_len = strlen(efs->path);
|
||||
|
||||
if (efs->data_len > sizeof(efs->data)) {
|
||||
kfree(efs);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Step 1: EFS2_DIAG_OPEN (O_WRONLY | O_CREAT | O_TRUNC)
|
||||
* oflag = 0x0601
|
||||
*/
|
||||
open_payload[0] = 0x01;
|
||||
open_payload[1] = 0x06;
|
||||
open_payload[2] = 0x00;
|
||||
open_payload[3] = 0x00;
|
||||
/* mode: use provided or default 0644 */
|
||||
open_payload[4] = (efs->mode ? efs->mode : 0644) & 0xFF;
|
||||
open_payload[5] = ((efs->mode ? efs->mode : 0644) >> 8) & 0xFF;
|
||||
open_payload[6] = 0x00;
|
||||
open_payload[7] = 0x00;
|
||||
memcpy(open_payload + 8, efs->path, path_len + 1);
|
||||
|
||||
ret = send_subsys_cmd(DIAG_SUBSYS_EFS2, EFS2_DIAG_OPEN,
|
||||
open_payload, 8 + path_len + 1,
|
||||
resp, sizeof(resp));
|
||||
if (ret < 8) {
|
||||
efs->status = -EIO;
|
||||
goto efs_write_out;
|
||||
}
|
||||
|
||||
fd = resp[4] | (resp[5] << 8) |
|
||||
(resp[6] << 16) | (resp[7] << 24);
|
||||
if (fd < 0) {
|
||||
efs->status = fd;
|
||||
goto efs_write_out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Step 2: EFS2_DIAG_WRITE
|
||||
* Payload: [fd LE32] [offset LE32] [data...]
|
||||
*/
|
||||
write_payload[0] = fd & 0xFF;
|
||||
write_payload[1] = (fd >> 8) & 0xFF;
|
||||
write_payload[2] = (fd >> 16) & 0xFF;
|
||||
write_payload[3] = (fd >> 24) & 0xFF;
|
||||
write_payload[4] = efs->offset & 0xFF;
|
||||
write_payload[5] = (efs->offset >> 8) & 0xFF;
|
||||
write_payload[6] = (efs->offset >> 16) & 0xFF;
|
||||
write_payload[7] = (efs->offset >> 24) & 0xFF;
|
||||
memcpy(write_payload + 8, efs->data, efs->data_len);
|
||||
|
||||
ret = send_subsys_cmd(DIAG_SUBSYS_EFS2, EFS2_DIAG_WRITE,
|
||||
write_payload, 8 + efs->data_len,
|
||||
resp, sizeof(resp));
|
||||
if (ret > 8) {
|
||||
int32_t efs_errno = resp[8] | (resp[9] << 8) |
|
||||
(resp[10] << 16) | (resp[11] << 24);
|
||||
efs->status = efs_errno ? -efs_errno : 0;
|
||||
} else {
|
||||
efs->status = -EIO;
|
||||
}
|
||||
|
||||
/* Step 3: Close */
|
||||
send_subsys_cmd(DIAG_SUBSYS_EFS2, EFS2_DIAG_CLOSE,
|
||||
write_payload, 4, resp, sizeof(resp));
|
||||
|
||||
efs_write_out:
|
||||
if (copy_to_user((void __user *)arg, efs, sizeof(*efs)))
|
||||
ret = -EFAULT;
|
||||
else
|
||||
ret = 0;
|
||||
kfree(efs);
|
||||
break;
|
||||
}
|
||||
|
||||
case RC_DIAG_EFS_STAT: {
|
||||
struct rc_efs_op *efs;
|
||||
uint8_t payload[264];
|
||||
uint8_t resp[64];
|
||||
int path_len;
|
||||
|
||||
efs = kmalloc(sizeof(*efs), GFP_KERNEL);
|
||||
if (!efs) {
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
|
||||
if (copy_from_user(efs, (void __user *)arg, sizeof(*efs))) {
|
||||
kfree(efs);
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
efs->path[sizeof(efs->path) - 1] = '\0';
|
||||
path_len = strlen(efs->path);
|
||||
memcpy(payload, efs->path, path_len + 1);
|
||||
|
||||
ret = send_subsys_cmd(DIAG_SUBSYS_EFS2, EFS2_DIAG_STAT,
|
||||
payload, path_len + 1,
|
||||
resp, sizeof(resp));
|
||||
if (ret > 4) {
|
||||
int32_t efs_errno = resp[4] | (resp[5] << 8) |
|
||||
(resp[6] << 16) | (resp[7] << 24);
|
||||
efs->status = efs_errno ? -efs_errno : 0;
|
||||
|
||||
if (efs->status == 0 && ret >= 16) {
|
||||
/* mode at offset 8, size at offset 12 */
|
||||
efs->mode = resp[8] | (resp[9] << 8) |
|
||||
(resp[10] << 16) | (resp[11] << 24);
|
||||
efs->data_len = resp[12] | (resp[13] << 8) |
|
||||
(resp[14] << 16) | (resp[15] << 24);
|
||||
}
|
||||
} else {
|
||||
efs->status = -EIO;
|
||||
}
|
||||
|
||||
if (copy_to_user((void __user *)arg, efs, sizeof(*efs)))
|
||||
ret = -EFAULT;
|
||||
else
|
||||
ret = 0;
|
||||
kfree(efs);
|
||||
break;
|
||||
}
|
||||
|
||||
case RC_DIAG_EFS_UNLINK: {
|
||||
struct rc_efs_op *efs;
|
||||
uint8_t payload[264];
|
||||
uint8_t resp[32];
|
||||
int path_len;
|
||||
|
||||
efs = kmalloc(sizeof(*efs), GFP_KERNEL);
|
||||
if (!efs) {
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
|
||||
if (copy_from_user(efs, (void __user *)arg, sizeof(*efs))) {
|
||||
kfree(efs);
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
efs->path[sizeof(efs->path) - 1] = '\0';
|
||||
path_len = strlen(efs->path);
|
||||
memcpy(payload, efs->path, path_len + 1);
|
||||
|
||||
ret = send_subsys_cmd(DIAG_SUBSYS_EFS2, EFS2_DIAG_UNLINK,
|
||||
payload, path_len + 1,
|
||||
resp, sizeof(resp));
|
||||
if (ret > 4) {
|
||||
int32_t efs_errno = resp[4] | (resp[5] << 8) |
|
||||
(resp[6] << 16) | (resp[7] << 24);
|
||||
efs->status = efs_errno ? -efs_errno : 0;
|
||||
} else {
|
||||
efs->status = -EIO;
|
||||
}
|
||||
|
||||
if (copy_to_user((void __user *)arg, efs, sizeof(*efs)))
|
||||
ret = -EFAULT;
|
||||
else
|
||||
ret = 0;
|
||||
kfree(efs);
|
||||
break;
|
||||
}
|
||||
|
||||
case RC_DIAG_GET_VERSION: {
|
||||
struct rc_diag_version ver;
|
||||
uint8_t diag_cmd = DIAG_VERNO_F;
|
||||
uint8_t resp[128];
|
||||
|
||||
memset(&ver, 0, sizeof(ver));
|
||||
|
||||
ret = send_diag_cmd(&diag_cmd, 1, resp, sizeof(resp));
|
||||
if (ret > 0) {
|
||||
/*
|
||||
* Version response:
|
||||
* [0x00] [comp_date 11B] [comp_time 8B]
|
||||
* [rel_date 11B] [rel_time 8B] [model...]
|
||||
*/
|
||||
if (ret >= 40) {
|
||||
memcpy(ver.comp_date, resp + 1, 11);
|
||||
memcpy(ver.comp_time, resp + 12, 8);
|
||||
memcpy(ver.rel_date, resp + 20, 11);
|
||||
memcpy(ver.rel_time, resp + 31, 8);
|
||||
}
|
||||
if (ret >= 42)
|
||||
ver.mob_sw_rev = resp[39];
|
||||
|
||||
if (copy_to_user((void __user *)arg, &ver,
|
||||
sizeof(ver)))
|
||||
ret = -EFAULT;
|
||||
else
|
||||
ret = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
ret = -ENOTTY;
|
||||
}
|
||||
@@ -334,7 +953,7 @@ static long rc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
static const struct file_operations rc_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = rc_ioctl,
|
||||
.compat_ioctl = rc_ioctl,
|
||||
.compat_ioctl = compat_ptr_ioctl,
|
||||
};
|
||||
|
||||
static int __init rc_diag_init(void)
|
||||
@@ -356,30 +975,43 @@ static int __init rc_diag_init(void)
|
||||
return ret;
|
||||
major = MAJOR(dev);
|
||||
|
||||
rc_class = class_create(THIS_MODULE, CLASS_NAME "_diag");
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
|
||||
rc_class = class_create(CLASS_NAME);
|
||||
#else
|
||||
rc_class = class_create(THIS_MODULE, CLASS_NAME);
|
||||
#endif
|
||||
if (IS_ERR(rc_class)) {
|
||||
ret = PTR_ERR(rc_class);
|
||||
goto err;
|
||||
}
|
||||
|
||||
cdev_init(&rc_cdev, &rc_fops);
|
||||
cdev_add(&rc_cdev, MKDEV(major, 0), 1);
|
||||
rc_cdev.owner = THIS_MODULE;
|
||||
|
||||
ret = cdev_add(&rc_cdev, MKDEV(major, 0), 1);
|
||||
if (ret < 0)
|
||||
goto err_cdev;
|
||||
|
||||
rc_device = device_create(rc_class, NULL, MKDEV(major, 0),
|
||||
NULL, DEVICE_NAME);
|
||||
if (IS_ERR(rc_device)) {
|
||||
ret = PTR_ERR(rc_device);
|
||||
goto err2;
|
||||
goto err_device;
|
||||
}
|
||||
|
||||
pr_info("rc_diag: /dev/%s created\n", DEVICE_NAME);
|
||||
pr_info("rc_diag: /dev/%s created (major %d)%s\n",
|
||||
DEVICE_NAME, major,
|
||||
diag_filp ? "" : " [inactive — no Qualcomm modem]");
|
||||
return 0;
|
||||
|
||||
err2:
|
||||
err_device:
|
||||
cdev_del(&rc_cdev);
|
||||
err_cdev:
|
||||
class_destroy(rc_class);
|
||||
err:
|
||||
unregister_chrdev_region(MKDEV(major, 0), 1);
|
||||
if (diag_filp)
|
||||
filp_close(diag_filp, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user