summaryrefslogtreecommitdiffstats
path: root/drivers/accessibility/speakup/varhandlers.c
diff options
context:
space:
mode:
authorSteven Rostedt <rostedt@goodmis.org>2026-09-02 09:55:01 -0400
committerSteven Rostedt <rostedt@goodmis.org>2026-09-02 11:08:21 -0400
commit4617721c502b2ddaa4e324e86da4997edf738fa5 (patch)
tree350aa83b47da0b574a814795392b20b0bed83cbc /drivers/accessibility/speakup/varhandlers.c
downloadlinux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz
linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since it can be declared outside of the ftrace.c code, it calls ftrace_ops_init() on the ops in every global function. The issue is that if two tasks call it on the same ops at the same time it is possible to have the initialization of one corrupt the initialization of the other call. Create a ops_mutex to use to synchronize every initialization of the ftrace_ops. The mutex is taken within checking the ftrace_ops flag that states it was initializied but the flag is checked again after the mutex has been taken. Checking first outside the mutex allows it to shortcut having to take the mutex. But then the check needs to be done again after the mute is taken in case of races. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock") Reported-by: sashiko-bot@kernel.org Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/ Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'drivers/accessibility/speakup/varhandlers.c')
-rw-r--r--drivers/accessibility/speakup/varhandlers.c345
1 files changed, 345 insertions, 0 deletions
diff --git a/drivers/accessibility/speakup/varhandlers.c b/drivers/accessibility/speakup/varhandlers.c
new file mode 100644
index 000000000..462f8d879
--- /dev/null
+++ b/drivers/accessibility/speakup/varhandlers.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ctype.h>
+#include "spk_types.h"
+#include "spk_priv.h"
+#include "speakup.h"
+
+static struct st_var_header var_headers[] = {
+ { "version", VERSION, VAR_PROC, NULL, NULL },
+ { "synth_name", SYNTH, VAR_PROC, NULL, NULL },
+ { "keymap", KEYMAP, VAR_PROC, NULL, NULL },
+ { "silent", SILENT, VAR_PROC, NULL, NULL },
+ { "punc_some", PUNC_SOME, VAR_PROC, NULL, NULL },
+ { "punc_most", PUNC_MOST, VAR_PROC, NULL, NULL },
+ { "punc_all", PUNC_ALL, VAR_PROC, NULL, NULL },
+ { "delimiters", DELIM, VAR_PROC, NULL, NULL },
+ { "repeats", REPEATS, VAR_PROC, NULL, NULL },
+ { "ex_num", EXNUMBER, VAR_PROC, NULL, NULL },
+ { "characters", CHARS, VAR_PROC, NULL, NULL },
+ { "synth_direct", SYNTH_DIRECT, VAR_PROC, NULL, NULL },
+ { "caps_start", CAPS_START, VAR_STRING, spk_str_caps_start, NULL },
+ { "caps_stop", CAPS_STOP, VAR_STRING, spk_str_caps_stop, NULL },
+ { "delay_time", DELAY, VAR_TIME, NULL, NULL },
+ { "trigger_time", TRIGGER, VAR_TIME, NULL, NULL },
+ { "jiffy_delta", JIFFY, VAR_TIME, NULL, NULL },
+ { "full_time", FULL, VAR_TIME, NULL, NULL },
+ { "flush_time", FLUSH, VAR_TIME, NULL, NULL },
+ { "spell_delay", SPELL_DELAY, VAR_NUM, &spk_spell_delay, NULL },
+ { "bleeps", BLEEPS, VAR_NUM, &spk_bleeps, NULL },
+ { "attrib_bleep", ATTRIB_BLEEP, VAR_NUM, &spk_attrib_bleep, NULL },
+ { "bleep_time", BLEEP_TIME, VAR_TIME, &spk_bleep_time, NULL },
+ { "cursor_time", CURSOR_TIME, VAR_TIME, NULL, NULL },
+ { "punc_level", PUNC_LEVEL, VAR_NUM, &spk_punc_level, NULL },
+ { "reading_punc", READING_PUNC, VAR_NUM, &spk_reading_punc, NULL },
+ { "say_control", SAY_CONTROL, VAR_NUM, &spk_say_ctrl, NULL },
+ { "say_word_ctl", SAY_WORD_CTL, VAR_NUM, &spk_say_word_ctl, NULL },
+ { "no_interrupt", NO_INTERRUPT, VAR_NUM, &spk_no_intr, NULL },
+ { "key_echo", KEY_ECHO, VAR_NUM, &spk_key_echo, NULL },
+ { "bell_pos", BELL_POS, VAR_NUM, &spk_bell_pos, NULL },
+ { "rate", RATE, VAR_NUM, NULL, NULL },
+ { "pitch", PITCH, VAR_NUM, NULL, NULL },
+ { "inflection", INFLECTION, VAR_NUM, NULL, NULL },
+ { "vol", VOL, VAR_NUM, NULL, NULL },
+ { "tone", TONE, VAR_NUM, NULL, NULL },
+ { "punct", PUNCT, VAR_NUM, NULL, NULL },
+ { "voice", VOICE, VAR_NUM, NULL, NULL },
+ { "freq", FREQUENCY, VAR_NUM, NULL, NULL },
+ { "lang", LANG, VAR_NUM, NULL, NULL },
+ { "chartab", CHARTAB, VAR_PROC, NULL, NULL },
+ { "direct", DIRECT, VAR_NUM, NULL, NULL },
+ { "pause", PAUSE, VAR_STRING, spk_str_pause, NULL },
+ { "cur_phonetic", CUR_PHONETIC, VAR_NUM, &spk_cur_phonetic, NULL },
+};
+
+static struct st_var_header *var_ptrs[MAXVARS] = { NULL, NULL, NULL };
+
+static struct punc_var_t punc_vars[] = {
+ { PUNC_SOME, 1 },
+ { PUNC_MOST, 2 },
+ { PUNC_ALL, 3 },
+ { DELIM, 4 },
+ { REPEATS, 5 },
+ { EXNUMBER, 6 },
+ { -1, -1 },
+};
+
+int spk_chartab_get_value(char *keyword)
+{
+ int value = 0;
+
+ if (!strcmp(keyword, "ALPHA"))
+ value = ALPHA;
+ else if (!strcmp(keyword, "B_CTL"))
+ value = B_CTL;
+ else if (!strcmp(keyword, "WDLM"))
+ value = WDLM;
+ else if (!strcmp(keyword, "A_PUNC"))
+ value = A_PUNC;
+ else if (!strcmp(keyword, "PUNC"))
+ value = PUNC;
+ else if (!strcmp(keyword, "NUM"))
+ value = NUM;
+ else if (!strcmp(keyword, "A_CAP"))
+ value = A_CAP;
+ else if (!strcmp(keyword, "B_CAPSYM"))
+ value = B_CAPSYM;
+ else if (!strcmp(keyword, "B_SYM"))
+ value = B_SYM;
+ return value;
+}
+
+void speakup_register_var(struct var_t *var)
+{
+ static char nothing[2] = "\0";
+ int i;
+ struct st_var_header *p_header;
+
+ BUG_ON(!var || var->var_id < 0 || var->var_id >= MAXVARS);
+ if (!var_ptrs[0]) {
+ for (i = 0; i < MAXVARS; i++) {
+ p_header = &var_headers[i];
+ var_ptrs[p_header->var_id] = p_header;
+ p_header->data = NULL;
+ }
+ }
+ p_header = var_ptrs[var->var_id];
+ if (p_header->data)
+ return;
+ p_header->data = var;
+ switch (p_header->var_type) {
+ case VAR_STRING:
+ spk_set_string_var(nothing, p_header, 0);
+ break;
+ case VAR_NUM:
+ case VAR_TIME:
+ spk_set_num_var(0, p_header, E_DEFAULT);
+ break;
+ default:
+ break;
+ }
+}
+
+void speakup_unregister_var(enum var_id_t var_id)
+{
+ struct st_var_header *p_header;
+
+ BUG_ON(var_id < 0 || var_id >= MAXVARS);
+ p_header = var_ptrs[var_id];
+ p_header->data = NULL;
+}
+
+struct st_var_header *spk_get_var_header(enum var_id_t var_id)
+{
+ struct st_var_header *p_header;
+
+ if (var_id < 0 || var_id >= MAXVARS)
+ return NULL;
+ p_header = var_ptrs[var_id];
+ if (!p_header->data)
+ return NULL;
+ return p_header;
+}
+EXPORT_SYMBOL_GPL(spk_get_var_header);
+
+struct st_var_header *spk_var_header_by_name(const char *name)
+{
+ int i;
+
+ if (!name)
+ return NULL;
+
+ for (i = 0; i < MAXVARS; i++) {
+ if (strcmp(name, var_ptrs[i]->name) == 0)
+ return var_ptrs[i];
+ }
+ return NULL;
+}
+
+struct var_t *spk_get_var(enum var_id_t var_id)
+{
+ BUG_ON(var_id < 0 || var_id >= MAXVARS);
+ BUG_ON(!var_ptrs[var_id]);
+ return var_ptrs[var_id]->data;
+}
+EXPORT_SYMBOL_GPL(spk_get_var);
+
+struct punc_var_t *spk_get_punc_var(enum var_id_t var_id)
+{
+ struct punc_var_t *rv = NULL;
+ struct punc_var_t *where;
+
+ where = punc_vars;
+ while ((where->var_id != -1) && (!rv)) {
+ if (where->var_id == var_id)
+ rv = where;
+ else
+ where++;
+ }
+ return rv;
+}
+
+/* handlers for setting vars */
+int spk_set_num_var(int input, struct st_var_header *var, int how)
+{
+ int val;
+ int *p_val = var->p_val;
+ char buf[32];
+ char *cp;
+ struct var_t *var_data = var->data;
+
+ if (!var_data)
+ return -ENODATA;
+
+ val = var_data->u.n.value;
+ switch (how) {
+ case E_NEW_DEFAULT:
+ if (input < var_data->u.n.low || input > var_data->u.n.high)
+ return -ERANGE;
+ var_data->u.n.default_val = input;
+ return 0;
+ case E_DEFAULT:
+ val = var_data->u.n.default_val;
+ break;
+ case E_SET:
+ val = input;
+ break;
+ case E_INC:
+ val += input;
+ break;
+ case E_DEC:
+ val -= input;
+ break;
+ }
+
+ if (val < var_data->u.n.low || val > var_data->u.n.high)
+ return -ERANGE;
+
+ var_data->u.n.value = val;
+ if (var->var_type == VAR_TIME && p_val) {
+ *p_val = msecs_to_jiffies(val);
+ return 0;
+ }
+ if (p_val)
+ *p_val = val;
+ if (var->var_id == PUNC_LEVEL) {
+ spk_punc_mask = spk_punc_masks[val];
+ }
+ if (var_data->u.n.multiplier != 0)
+ val *= var_data->u.n.multiplier;
+ val += var_data->u.n.offset;
+
+ if (!synth)
+ return 0;
+ if (synth->synth_adjust && synth->synth_adjust(synth, var))
+ return 0;
+ if (var->var_id < FIRST_SYNTH_VAR)
+ return 0;
+
+ if (!var_data->u.n.synth_fmt)
+ return 0;
+ if (var->var_id == PITCH)
+ cp = spk_pitch_buff;
+ else
+ cp = buf;
+ if (!var_data->u.n.out_str)
+ sprintf(cp, var_data->u.n.synth_fmt, (int)val);
+ else
+ sprintf(cp, var_data->u.n.synth_fmt,
+ var_data->u.n.out_str[val]);
+ synth_printf("%s", cp);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spk_set_num_var);
+
+int spk_set_string_var(const char *page, struct st_var_header *var, int len)
+{
+ struct var_t *var_data = var->data;
+
+ if (!var_data)
+ return -ENODATA;
+ if (len > MAXVARLEN)
+ return -E2BIG;
+ if (!len) {
+ if (!var_data->u.s.default_val)
+ return 0;
+ if (!var->p_val)
+ var->p_val = var_data->u.s.default_val;
+ if (var->p_val != var_data->u.s.default_val)
+ strcpy((char *)var->p_val, var_data->u.s.default_val);
+ return -ERESTART;
+ } else if (var->p_val) {
+ strcpy((char *)var->p_val, page);
+ } else {
+ return -E2BIG;
+ }
+ return 0;
+}
+
+/*
+ * spk_set_mask_bits sets or clears the punc/delim/repeat bits,
+ * if input is null uses the defaults.
+ * values for how: 0 clears bits of chars supplied,
+ * 1 clears allk, 2 sets bits for chars
+ */
+int spk_set_mask_bits(const char *input, const int which, const int how)
+{
+ u_char *cp;
+ short mask = spk_punc_info[which].mask;
+
+ if (how & 1) {
+ for (cp = (u_char *)spk_punc_info[3].value; *cp; cp++)
+ spk_chartab[*cp] &= ~mask;
+ }
+ cp = (u_char *)input;
+ if (!cp) {
+ cp = spk_punc_info[which].value;
+ } else {
+ for (; *cp; cp++) {
+ if (*cp < SPACE)
+ break;
+ if (mask < PUNC) {
+ if (!(spk_chartab[*cp] & PUNC))
+ break;
+ } else if (spk_chartab[*cp] & B_NUM) {
+ break;
+ }
+ }
+ if (*cp)
+ return -EINVAL;
+ cp = (u_char *)input;
+ }
+ if (how & 2) {
+ for (; *cp; cp++)
+ if (*cp > SPACE)
+ spk_chartab[*cp] |= mask;
+ } else {
+ for (; *cp; cp++)
+ if (*cp > SPACE)
+ spk_chartab[*cp] &= ~mask;
+ }
+ return 0;
+}
+
+char *spk_strlwr(char *s)
+{
+ char *p;
+
+ if (!s)
+ return NULL;
+
+ for (p = s; *p; p++)
+ *p = tolower(*p);
+ return s;
+}
+
+char *spk_s2uchar(char *start, char *dest)
+{
+ int val;
+
+ /* Do not replace with kstrtoul: here we need start to be updated */
+ val = simple_strtoul(skip_spaces(start), &start, 10);
+ if (*start == ',')
+ start++;
+ *dest = (u_char)val;
+ return start;
+}