// SPDX-License-Identifier: GPL-2.0+ /* Copyright (C) 2004 AVM GmbH */ #include #include #include #include #include #include #include #include /*--- #include ---*/ #include #if defined(CONFIG_PROC_FS) #include #include #endif /*--- #if defined(CONFIG_PROC_FS) ---*/ #include #include #include #include #include "local.h" #ifdef CONFIG_SYSCTL #if !defined(CONFIG_TFFS_ENV) #error "no CONFIG_TFFS_ENV defined" #endif /*--- #if !defined(CONFIG_TFFS_ENV) ---*/ #define DEV_ADAM2 6 #define DEV_ADAM2_ENV 1 #define ADAM2_ENV_STR_SIZE FLASH_ENV_ENTRY_SIZE /*--- 256 Bytes ---*/ #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) /* Evaluates to zero, because memcpy always succeeds */ #define sysctl_copy_from_user(dst, src, len) (memcpy(dst, src, len), 0) #define sysctl_copy_to_user(dst, src, len) (memcpy(dst, src, len), 0) #else #define sysctl_copy_from_user(dst, src, len) copy_from_user(dst, src, len) #define sysctl_copy_to_user(dst, src, len) copy_to_user(dst, src, len) #endif static char urlader_env_buf[ADAM2_ENV_STR_SIZE*TFFS_MAX_ENV_ENTRY]; static DEFINE_MUTEX(urlader_env_buf_lock); static void urlader_env_buf_invalidate(void) { urlader_env_buf[0] = '\0'; } static int urlader_env_buf_is_valid(void) { return urlader_env_buf[0] != '\0'; } /** */ static int avm_urlader_getenv_proc(struct ctl_table *ctl, int write, void *buffer, size_t *lenp, loff_t *ppos) { char tmp_buf[ADAM2_ENV_STR_SIZE]; char *val, len; char *default_val = NULL; int free_val; /*--- pr_info("[avm_urlader_getenv_proc]: ctl->ctl_name=%u, write=%u *lenp=%u *ppos=%u\n", ---*/ /*--- (int)ctl->ctl_name, (int)write, (int)*lenp, (int)*ppos); ---*/ if (write) { pr_err("write not supported\n"); return -EFAULT; } if (!*lenp || *ppos) { *lenp = 0; return 0; } switch ((int)ctl->extra1) { case FLASH_FS_ANNEX: default_val = "B"; break; case FLASH_FS_FIRMWARE_VERSION: default_val = ""; break; default: pr_err("%s: id %x not supported\n", __func__, (int)ctl->extra1); return -EFAULT; } free_val = 1; val = avm_urlader_env_get_value_by_id((int)ctl->extra1); if (IS_ERR_OR_NULL(val)) { if (default_val) { val = default_val; } else { val = ""; } free_val = 0; } /* Scan thru the flash, looking for each possible variable index */ len = strnlen(val, ADAM2_ENV_STR_SIZE - 1); /*--- pr_info("[avm_urlader_getenv_proc] val=%s len=%u\n", val, len); ---*/ memcpy(tmp_buf, val, len + 1); if (free_val) kfree(val); if (len > *lenp) len = *lenp; if (len) { if (sysctl_copy_to_user(buffer, tmp_buf, len)) { return -EFAULT; } } *lenp = len; *ppos += len; /*--- pr_info("[avm_urlader_getenv_proc] *lenp=%u *ppos\n", *lenp, (int)*ppos); ---*/ return 0; } /** */ #if defined(CONFIG_ARCH_PUMA5) || defined(CONFIG_MACH_PUMA6) || defined(CONFIG_ARCH_GEN3) || \ defined(CONFIG_MACH_PUMA7) static int avm_urlader_get_config(struct ctl_table *ctl, int write, void *buffer, size_t *lenp, loff_t *ppos) { char tmp_buf[ADAM2_ENV_STR_SIZE]; int ret; if (!*lenp || (*ppos && !write)) { *lenp = 0; return 0; } if (write) { size_t len = *lenp; if (len >= ADAM2_ENV_STR_SIZE) return -EOVERFLOW; if (sysctl_copy_from_user(tmp_buf, buffer, len)) return -EFAULT; tmp_buf[len] = '\0'; ret = copy_wlan_dect_config2user(tmp_buf, len + 1); if (ret) return ret; *ppos = len; *lenp = len; /*--- lenp darf nicht kleiner sein als bei Uebergabe, sonst werden endlos aufgerufen ---*/ return 0; } else { char *mybuffer = kmalloc(*lenp, GFP_KERNEL); if (mybuffer) { if (!test_wlan_dect_config(mybuffer, lenp)) { if (sysctl_copy_to_user(buffer, mybuffer, *lenp)) { kfree(mybuffer); return -EFAULT; } *ppos = *lenp; kfree(mybuffer); return 0; } kfree(mybuffer); } else { pr_err("[%s] kmalloc failed\n", __func__); } } return -EFAULT; } #endif /*--- #if defined(CONFIG_ARCH_PUMA5) || defined(CONFIG_MACH_PUMA6) || defined(CONFIG_ARCH_GEN3)---*/ static int update_env_buf(char *env_buffer) { char *var, *val; int i, result = 0; size_t len, total = 0; if (urlader_env_buf_is_valid()) return result; /* Scan through the flash, looking for each possible variable index */ for (i = 0; i < TFFS_MAX_ENV_ENTRY; i++) { var = avm_urlader_env_get_variable(i); if (var == NULL) continue; val = __avm_urlader_env_get_value(var); result = PTR_ERR_OR_ZERO(val); if (result) { kfree(var); if (result == -ENOENT) { result = 0; continue; } if (result == -EINTR) result = -ERESTARTSYS; goto err_out; } len = snprintf(env_buffer + total, sizeof(urlader_env_buf) - total, "%s\t%s\n", var, val); kfree(var); kfree(val); if (len + total >= sizeof(urlader_env_buf)) { pr_err("urlader_env_buf too small. Truncating output.\n"); break; } total += len; } err_out: if (result) urlader_env_buf_invalidate(); return result; } /** */ static int avm_urlader_setenv_sysctl(struct ctl_table *ctl, int write, void *buffer, size_t *lenp, loff_t *ppos) { char *ptr; char tmp_buf[2 * ADAM2_ENV_STR_SIZE]; int result = 0; result = mutex_lock_killable(&urlader_env_buf_lock); if (result) return result; if (!write) { result = update_env_buf(urlader_env_buf); if (!result) result = proc_dostring(ctl, write, buffer, lenp, ppos); mutex_unlock(&urlader_env_buf_lock); return result; } if (ctl->extra1 != (void *)DEV_ADAM2_ENV) { result = -ENODEV; goto err_out; } if (*lenp >= ADAM2_ENV_STR_SIZE) { result = -EINVAL; goto err_out; } if (sysctl_copy_from_user(tmp_buf, buffer, *lenp)) { result = -EFAULT; goto err_out; } tmp_buf[*lenp] = '\0'; if (tmp_buf[*lenp - 1] == '\n') { tmp_buf[*lenp - 1] = '\0'; } *ppos = *lenp; /*--- printk(KERN_ERR "{%s}\n", __func__); ---*/ /*--- dump_stack(); ---*/ ptr = strpbrk(tmp_buf, " \t"); if (!ptr) { /* We have no way to distinguish between unsetting a * variable and setting a non-value variable. * * Consequently, we will treat any variable * without a value as an unset request. */ avm_urlader_env_unset_variable(tmp_buf); } else { /* Set the variable-value pair in flash */ *ptr++ = '\0'; result = avm_urlader_env_set_variable(tmp_buf, ptr); if (result != 0) { pr_notice("Defragging the environment variable region.\n"); result = avm_urlader_env_defrag(); if (result != 0) { pr_warn("Defragging the environment variable region failed.\n"); goto err_out; } result = avm_urlader_env_set_variable(tmp_buf, ptr); if (result != 0) { pr_warn("Failed to write %s to environment variable region.\n", tmp_buf); goto err_out; } } } urlader_env_buf_invalidate(); err_out: mutex_unlock(&urlader_env_buf_lock); return result; } /** */ static int avm_urlader_reset_status_proc(struct ctl_table *ctl, int write, void *buffer, size_t *lenp, loff_t *ppos) { char tmp_buf[ADAM2_ENV_STR_SIZE] __maybe_unused; const char *reason; int result; result = 0; if (!*lenp || (*ppos && !write)) { *lenp = 0; goto err_out; } if (write) { if (*lenp >= ADAM2_ENV_STR_SIZE) { result = -EFAULT; goto err_out; } if (sysctl_copy_from_user(tmp_buf, buffer, *lenp)) { result = -EFAULT; goto err_out; } tmp_buf[*lenp] = '\0'; if (tmp_buf[*lenp - 1] == '\n') { tmp_buf[*lenp - 1] = '\0'; } *ppos = *lenp; result = avm_sysctl_set_reset_status(tmp_buf); } else { reason = avm_sysctl_reset_status(); *lenp = strlen(reason); *ppos = *lenp; if (sysctl_copy_to_user(buffer, reason, *lenp)) { result = -EFAULT; } } err_out: return result; } /** */ struct ctl_table avm_urlader_env_table3[] = { { .extra1 = (void *)DEV_ADAM2_ENV, .procname = "environment", .data = urlader_env_buf, .maxlen = sizeof(urlader_env_buf), .mode = 0644, .child = NULL, .proc_handler = &avm_urlader_setenv_sysctl }, { .extra1 = (void *)FLASH_FS_FIRMWARE_VERSION, .procname = "firmware_version", .data = NULL, .maxlen = ADAM2_ENV_STR_SIZE, .mode = 0444, .child = NULL, .proc_handler = &avm_urlader_getenv_proc }, { .extra1 = (void *)FLASH_FS_ANNEX, .procname = "annex", .data = NULL, .maxlen = ADAM2_ENV_STR_SIZE, .mode = 0444, .child = NULL, .proc_handler = &avm_urlader_getenv_proc }, { .procname = "reboot_status", .data = NULL, .maxlen = ADAM2_ENV_STR_SIZE, .mode = 0644, .child = NULL, .proc_handler = &avm_urlader_reset_status_proc }, #if defined(CONFIG_ARCH_PUMA5) || defined(CONFIG_MACH_PUMA6) || defined(CONFIG_ARCH_GEN3) || \ defined(CONFIG_MACH_PUMA7) { .procname = "config", .data = NULL, .maxlen = 0, .mode = 0644, .child = NULL, .proc_handler = &avm_urlader_get_config }, #endif { } }; struct ctl_table avm_urlader_root_table3[] = { { .extra1 = (void *)DEV_ADAM2_ENV, .procname = "urlader", .data = NULL, .maxlen = 0, .mode = 0555, .child = avm_urlader_env_table3 }, { } }; static struct ctl_table_header *avm_urlader_sysctl_header; /** */ static void avm_urlader_env_sysctl_register(void) { static int initialized; if (initialized != 0) { return; } avm_urlader_sysctl_header = register_sysctl_table(avm_urlader_root_table3); //avm_urlader_root_table->child->de->owner = THIS_MODULE; // PORT_ERROR_32: who or what is ->de->owner initialized = 1; } #if defined(MODULE) static void avm_urlader_env_sysctl_unregister(void) { unregister_sysctl_table(avm_urlader_sysctl_header); } #endif /*--- #if defined(MODULE) ---*/ int avm_urlader_env_init(void) { avm_urlader_env_sysctl_register(); pr_info("Adam2 environment variables API installed.\n"); return 0; } #if defined(MODULE) void avm_urlader_env_exit(void) { avm_urlader_env_sysctl_unregister(); } #endif /*--- #if defined(MODULE) ---*/ #endif /* CONFIG_SYSCTL */ /** */ #ifdef CONFIG_PROC_FS static struct tffs_cdev *tffsptr; static struct proc_dir_entry *tffs_proc; unsigned int tffs_info_value; /** */ static int tffsproc_write(struct file *file, const char *buffer, size_t count, loff_t *ppos) { char page[80]; char *p; int request = -1; unsigned int id; struct tffs_core_handle *handle; int result; handle = NULL; result = 0; if (IS_ERR_OR_NULL(tffsptr)) { result = -ENODEV; goto err_out; } if (count > 0 && count <= sizeof(page)) { if (copy_from_user(&page[0], buffer, count)) result = -EFAULT; page[count - 1] = '\0'; } else { result = -EINVAL; } if (result != 0) { goto err_out; } p = skip_spaces(&page[0]); if (strstarts(p, "info")) { request = _TFFS_INFO; } else if (strstarts(p, "index")) { request = _TFFS_REINDEX; } else if (strstarts(p, "cleanup")) { request = _TFFS_CLEANUP; } else if (strstarts(p, "clear_id")) { request = _TFFS_CLEAR_ID; } else if (strstarts(p, "werkseinstellung")) { request = _TFFS_WERKSEINSTELLUNG; } else { pr_err("/proc/tffs: request not recognized\n"); result = -EINVAL; goto err_out; } switch (request) { case _TFFS_INFO: case _TFFS_REINDEX: case _TFFS_CLEANUP: case _TFFS_WERKSEINSTELLUNG: id = 0; break; case _TFFS_CLEAR_ID: p += strlen("clear_id"); p = skip_spaces(p); result = kstrtouint(p, 0, &id); if (result != 0 || id == 0 || id > FLASH_FS_ID_LAST) { pr_err("/proc/tffs: clear id request: invalid id %s\n", p); result = -EINVAL; } break; default: pr_err("/proc/tffs: command not recognized\n"); result = -EINVAL; } if (result != 0) { goto err_out; } handle = TFFS3_Open(id, tffs3_mode_write); if (IS_ERR_OR_NULL(handle)) { result = (handle == NULL) ? -ENOMEM : PTR_ERR(handle); goto err_out; } switch (request) { case _TFFS_INFO: result = TFFS3_Info(handle, &tffs_info_value); pr_info("/proc/tffs: info request: %s\n", result ? "failed" : "success"); break; case _TFFS_REINDEX: result = TFFS3_Create_Index(); pr_info("/proc/tffs: index request: %s\n", result ? "failed" : "success"); break; case _TFFS_CLEANUP: result = TFFS3_Cleanup(handle); pr_info("/proc/tffs: cleanup request: %s\n", result ? "failed" : "success"); break; case _TFFS_WERKSEINSTELLUNG: result = TFFS3_Werkseinstellungen(handle); pr_info("/proc/tffs: werkseinstellungen request: %s\n", result ? "failed" : "success"); break; case _TFFS_CLEAR_ID: result = TFFS3_Clear(handle); pr_info("/proc/tffs: clear id 0x%x request: %s\n", id, result ? "failed" : "success"); break; } err_out: if (!IS_ERR_OR_NULL(handle)) { TFFS3_Close(handle); } return result == 0 ? count : result; } /** */ static int tffsproc_show(struct seq_file *seq, void *data) { seq_puts(seq, "TFFS\n"); #if defined(CONFIG_TFFS2) || defined(CONFIG_TFFS_DEV_LEGACY) seq_printf(seq, "mount=mtd%u\n", tffs_mtd[0]); seq_printf(seq, "mount=mtd%u\n", tffs_mtd[1]); #endif seq_printf(seq, "request=%u\n", tffsptr->request_count); if (tffs_info_value) { seq_printf(seq, "fill=%u\n", tffs_info_value); } if (tffsptr->pending_events) { seq_puts(seq, "event pending\n"); } #if defined(CONFIG_TFFS_EXCLUSIVE) seq_puts(seq, "mode: read/write: exclusive\n"); #else /*--- #if defined(CONFIG_TFFS_EXCLUSIVE) ---*/ seq_puts(seq, "mode: read/write: shared\n"); #endif /*--- #else ---*/ /*--- #if defined(CONFIG_TFFS_EXCLUSIVE) ---*/ seq_printf(seq, "thread state=%s\n", tffsptr->thread_state == tffs3_thread_state_off ? "off" : tffsptr->thread_state == tffs3_thread_state_init ? "init" : tffsptr->thread_state == tffs3_thread_state_idle ? "idle" : tffsptr->thread_state == tffs3_thread_state_process ? "process" : tffsptr->thread_state == tffs3_thread_state_down ? "down" : "unknown"); return 0; } /** */ static int tffsproc_open(struct inode *inode, struct file *file) { int result; if (IS_ERR_OR_NULL(tffsptr)) { result = -ENODEV; } else { result = single_open(file, tffsproc_show, NULL); } return result; } static const PROC_COMPAT_FOPS(tffsproc_fops, tffsproc_open, seq_read, tffsproc_write, seq_lseek, single_release); void tffs_proc_init(struct tffs_cdev *tffs) { tffsptr = tffs; if (!IS_ERR_OR_NULL(tffsptr)) { tffs_proc = proc_create("tffs", 0, 0, &tffsproc_fops); } } void tffs_proc_remove(struct tffs_cdev *tffs) { if (!IS_ERR_OR_NULL(tffs_proc)) { remove_proc_entry("tffs", 0); } } #endif /*--- #ifdef CONFIG_PROC_FS ---*/