18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Speakup kobject implementation 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2009 William Hubbs 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * This code is based on kobject-example.c, which came with linux 2.6.x. 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com> 108c2ecf20Sopenharmony_ci * Copyright (C) 2007 Novell Inc. 118c2ecf20Sopenharmony_ci * 128c2ecf20Sopenharmony_ci * Released under the GPL version 2 only. 138c2ecf20Sopenharmony_ci * 148c2ecf20Sopenharmony_ci */ 158c2ecf20Sopenharmony_ci#include <linux/slab.h> /* For kmalloc. */ 168c2ecf20Sopenharmony_ci#include <linux/kernel.h> 178c2ecf20Sopenharmony_ci#include <linux/kobject.h> 188c2ecf20Sopenharmony_ci#include <linux/string.h> 198c2ecf20Sopenharmony_ci#include <linux/string_helpers.h> 208c2ecf20Sopenharmony_ci#include <linux/sysfs.h> 218c2ecf20Sopenharmony_ci#include <linux/ctype.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#include "speakup.h" 248c2ecf20Sopenharmony_ci#include "spk_priv.h" 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci/* 278c2ecf20Sopenharmony_ci * This is called when a user reads the characters or chartab sys file. 288c2ecf20Sopenharmony_ci */ 298c2ecf20Sopenharmony_cistatic ssize_t chars_chartab_show(struct kobject *kobj, 308c2ecf20Sopenharmony_ci struct kobj_attribute *attr, char *buf) 318c2ecf20Sopenharmony_ci{ 328c2ecf20Sopenharmony_ci int i; 338c2ecf20Sopenharmony_ci int len = 0; 348c2ecf20Sopenharmony_ci char *cp; 358c2ecf20Sopenharmony_ci char *buf_pointer = buf; 368c2ecf20Sopenharmony_ci size_t bufsize = PAGE_SIZE; 378c2ecf20Sopenharmony_ci unsigned long flags; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 408c2ecf20Sopenharmony_ci *buf_pointer = '\0'; 418c2ecf20Sopenharmony_ci for (i = 0; i < 256; i++) { 428c2ecf20Sopenharmony_ci if (bufsize <= 1) 438c2ecf20Sopenharmony_ci break; 448c2ecf20Sopenharmony_ci if (strcmp("characters", attr->attr.name) == 0) { 458c2ecf20Sopenharmony_ci len = scnprintf(buf_pointer, bufsize, "%d\t%s\n", 468c2ecf20Sopenharmony_ci i, spk_characters[i]); 478c2ecf20Sopenharmony_ci } else { /* show chartab entry */ 488c2ecf20Sopenharmony_ci if (IS_TYPE(i, B_CTL)) 498c2ecf20Sopenharmony_ci cp = "B_CTL"; 508c2ecf20Sopenharmony_ci else if (IS_TYPE(i, WDLM)) 518c2ecf20Sopenharmony_ci cp = "WDLM"; 528c2ecf20Sopenharmony_ci else if (IS_TYPE(i, A_PUNC)) 538c2ecf20Sopenharmony_ci cp = "A_PUNC"; 548c2ecf20Sopenharmony_ci else if (IS_TYPE(i, PUNC)) 558c2ecf20Sopenharmony_ci cp = "PUNC"; 568c2ecf20Sopenharmony_ci else if (IS_TYPE(i, NUM)) 578c2ecf20Sopenharmony_ci cp = "NUM"; 588c2ecf20Sopenharmony_ci else if (IS_TYPE(i, A_CAP)) 598c2ecf20Sopenharmony_ci cp = "A_CAP"; 608c2ecf20Sopenharmony_ci else if (IS_TYPE(i, ALPHA)) 618c2ecf20Sopenharmony_ci cp = "ALPHA"; 628c2ecf20Sopenharmony_ci else if (IS_TYPE(i, B_CAPSYM)) 638c2ecf20Sopenharmony_ci cp = "B_CAPSYM"; 648c2ecf20Sopenharmony_ci else if (IS_TYPE(i, B_SYM)) 658c2ecf20Sopenharmony_ci cp = "B_SYM"; 668c2ecf20Sopenharmony_ci else 678c2ecf20Sopenharmony_ci cp = "0"; 688c2ecf20Sopenharmony_ci len = 698c2ecf20Sopenharmony_ci scnprintf(buf_pointer, bufsize, "%d\t%s\n", i, cp); 708c2ecf20Sopenharmony_ci } 718c2ecf20Sopenharmony_ci bufsize -= len; 728c2ecf20Sopenharmony_ci buf_pointer += len; 738c2ecf20Sopenharmony_ci } 748c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 758c2ecf20Sopenharmony_ci return buf_pointer - buf; 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci/* 798c2ecf20Sopenharmony_ci * Print informational messages or warnings after updating 808c2ecf20Sopenharmony_ci * character descriptions or chartab entries. 818c2ecf20Sopenharmony_ci */ 828c2ecf20Sopenharmony_cistatic void report_char_chartab_status(int reset, int received, int used, 838c2ecf20Sopenharmony_ci int rejected, int do_characters) 848c2ecf20Sopenharmony_ci{ 858c2ecf20Sopenharmony_ci static char const *object_type[] = { 868c2ecf20Sopenharmony_ci "character class entries", 878c2ecf20Sopenharmony_ci "character descriptions", 888c2ecf20Sopenharmony_ci }; 898c2ecf20Sopenharmony_ci int len; 908c2ecf20Sopenharmony_ci char buf[80]; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci if (reset) { 938c2ecf20Sopenharmony_ci pr_info("%s reset to defaults\n", object_type[do_characters]); 948c2ecf20Sopenharmony_ci } else if (received) { 958c2ecf20Sopenharmony_ci len = snprintf(buf, sizeof(buf), 968c2ecf20Sopenharmony_ci " updated %d of %d %s\n", 978c2ecf20Sopenharmony_ci used, received, object_type[do_characters]); 988c2ecf20Sopenharmony_ci if (rejected) 998c2ecf20Sopenharmony_ci snprintf(buf + (len - 1), sizeof(buf) - (len - 1), 1008c2ecf20Sopenharmony_ci " with %d reject%s\n", 1018c2ecf20Sopenharmony_ci rejected, rejected > 1 ? "s" : ""); 1028c2ecf20Sopenharmony_ci pr_info("%s", buf); 1038c2ecf20Sopenharmony_ci } 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci/* 1078c2ecf20Sopenharmony_ci * This is called when a user changes the characters or chartab parameters. 1088c2ecf20Sopenharmony_ci */ 1098c2ecf20Sopenharmony_cistatic ssize_t chars_chartab_store(struct kobject *kobj, 1108c2ecf20Sopenharmony_ci struct kobj_attribute *attr, 1118c2ecf20Sopenharmony_ci const char *buf, size_t count) 1128c2ecf20Sopenharmony_ci{ 1138c2ecf20Sopenharmony_ci char *cp = (char *)buf; 1148c2ecf20Sopenharmony_ci char *end = cp + count; /* the null at the end of the buffer */ 1158c2ecf20Sopenharmony_ci char *linefeed = NULL; 1168c2ecf20Sopenharmony_ci char keyword[MAX_DESC_LEN + 1]; 1178c2ecf20Sopenharmony_ci char *outptr = NULL; /* Will hold keyword or desc. */ 1188c2ecf20Sopenharmony_ci char *temp = NULL; 1198c2ecf20Sopenharmony_ci char *desc = NULL; 1208c2ecf20Sopenharmony_ci ssize_t retval = count; 1218c2ecf20Sopenharmony_ci unsigned long flags; 1228c2ecf20Sopenharmony_ci unsigned long index = 0; 1238c2ecf20Sopenharmony_ci int charclass = 0; 1248c2ecf20Sopenharmony_ci int received = 0; 1258c2ecf20Sopenharmony_ci int used = 0; 1268c2ecf20Sopenharmony_ci int rejected = 0; 1278c2ecf20Sopenharmony_ci int reset = 0; 1288c2ecf20Sopenharmony_ci int do_characters = !strcmp(attr->attr.name, "characters"); 1298c2ecf20Sopenharmony_ci size_t desc_length = 0; 1308c2ecf20Sopenharmony_ci int i; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 1338c2ecf20Sopenharmony_ci while (cp < end) { 1348c2ecf20Sopenharmony_ci while ((cp < end) && (*cp == ' ' || *cp == '\t')) 1358c2ecf20Sopenharmony_ci cp++; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci if (cp == end) 1388c2ecf20Sopenharmony_ci break; 1398c2ecf20Sopenharmony_ci if ((*cp == '\n') || strchr("dDrR", *cp)) { 1408c2ecf20Sopenharmony_ci reset = 1; 1418c2ecf20Sopenharmony_ci break; 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci received++; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci linefeed = strchr(cp, '\n'); 1468c2ecf20Sopenharmony_ci if (!linefeed) { 1478c2ecf20Sopenharmony_ci rejected++; 1488c2ecf20Sopenharmony_ci break; 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci if (!isdigit(*cp)) { 1528c2ecf20Sopenharmony_ci rejected++; 1538c2ecf20Sopenharmony_ci cp = linefeed + 1; 1548c2ecf20Sopenharmony_ci continue; 1558c2ecf20Sopenharmony_ci } 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci /* 1588c2ecf20Sopenharmony_ci * Do not replace with kstrtoul: 1598c2ecf20Sopenharmony_ci * here we need temp to be updated 1608c2ecf20Sopenharmony_ci */ 1618c2ecf20Sopenharmony_ci index = simple_strtoul(cp, &temp, 10); 1628c2ecf20Sopenharmony_ci if (index > 255) { 1638c2ecf20Sopenharmony_ci rejected++; 1648c2ecf20Sopenharmony_ci cp = linefeed + 1; 1658c2ecf20Sopenharmony_ci continue; 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci while ((temp < linefeed) && (*temp == ' ' || *temp == '\t')) 1698c2ecf20Sopenharmony_ci temp++; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci desc_length = linefeed - temp; 1728c2ecf20Sopenharmony_ci if (desc_length > MAX_DESC_LEN) { 1738c2ecf20Sopenharmony_ci rejected++; 1748c2ecf20Sopenharmony_ci cp = linefeed + 1; 1758c2ecf20Sopenharmony_ci continue; 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci if (do_characters) { 1788c2ecf20Sopenharmony_ci desc = kmalloc(desc_length + 1, GFP_ATOMIC); 1798c2ecf20Sopenharmony_ci if (!desc) { 1808c2ecf20Sopenharmony_ci retval = -ENOMEM; 1818c2ecf20Sopenharmony_ci reset = 1; /* just reset on error. */ 1828c2ecf20Sopenharmony_ci break; 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci outptr = desc; 1858c2ecf20Sopenharmony_ci } else { 1868c2ecf20Sopenharmony_ci outptr = keyword; 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci for (i = 0; i < desc_length; i++) 1908c2ecf20Sopenharmony_ci outptr[i] = temp[i]; 1918c2ecf20Sopenharmony_ci outptr[desc_length] = '\0'; 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci if (do_characters) { 1948c2ecf20Sopenharmony_ci if (spk_characters[index] != spk_default_chars[index]) 1958c2ecf20Sopenharmony_ci kfree(spk_characters[index]); 1968c2ecf20Sopenharmony_ci spk_characters[index] = desc; 1978c2ecf20Sopenharmony_ci used++; 1988c2ecf20Sopenharmony_ci } else { 1998c2ecf20Sopenharmony_ci charclass = spk_chartab_get_value(keyword); 2008c2ecf20Sopenharmony_ci if (charclass == 0) { 2018c2ecf20Sopenharmony_ci rejected++; 2028c2ecf20Sopenharmony_ci cp = linefeed + 1; 2038c2ecf20Sopenharmony_ci continue; 2048c2ecf20Sopenharmony_ci } 2058c2ecf20Sopenharmony_ci if (charclass != spk_chartab[index]) { 2068c2ecf20Sopenharmony_ci spk_chartab[index] = charclass; 2078c2ecf20Sopenharmony_ci used++; 2088c2ecf20Sopenharmony_ci } 2098c2ecf20Sopenharmony_ci } 2108c2ecf20Sopenharmony_ci cp = linefeed + 1; 2118c2ecf20Sopenharmony_ci } 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci if (reset) { 2148c2ecf20Sopenharmony_ci if (do_characters) 2158c2ecf20Sopenharmony_ci spk_reset_default_chars(); 2168c2ecf20Sopenharmony_ci else 2178c2ecf20Sopenharmony_ci spk_reset_default_chartab(); 2188c2ecf20Sopenharmony_ci } 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2218c2ecf20Sopenharmony_ci report_char_chartab_status(reset, received, used, rejected, 2228c2ecf20Sopenharmony_ci do_characters); 2238c2ecf20Sopenharmony_ci return retval; 2248c2ecf20Sopenharmony_ci} 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci/* 2278c2ecf20Sopenharmony_ci * This is called when a user reads the keymap parameter. 2288c2ecf20Sopenharmony_ci */ 2298c2ecf20Sopenharmony_cistatic ssize_t keymap_show(struct kobject *kobj, struct kobj_attribute *attr, 2308c2ecf20Sopenharmony_ci char *buf) 2318c2ecf20Sopenharmony_ci{ 2328c2ecf20Sopenharmony_ci char *cp = buf; 2338c2ecf20Sopenharmony_ci int i; 2348c2ecf20Sopenharmony_ci int n; 2358c2ecf20Sopenharmony_ci int num_keys; 2368c2ecf20Sopenharmony_ci int nstates; 2378c2ecf20Sopenharmony_ci u_char *cp1; 2388c2ecf20Sopenharmony_ci u_char ch; 2398c2ecf20Sopenharmony_ci unsigned long flags; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 2428c2ecf20Sopenharmony_ci cp1 = spk_key_buf + SHIFT_TBL_SIZE; 2438c2ecf20Sopenharmony_ci num_keys = (int)(*cp1); 2448c2ecf20Sopenharmony_ci nstates = (int)cp1[1]; 2458c2ecf20Sopenharmony_ci cp += sprintf(cp, "%d, %d, %d,\n", KEY_MAP_VER, num_keys, nstates); 2468c2ecf20Sopenharmony_ci cp1 += 2; /* now pointing at shift states */ 2478c2ecf20Sopenharmony_ci /* dump num_keys+1 as first row is shift states + flags, 2488c2ecf20Sopenharmony_ci * each subsequent row is key + states 2498c2ecf20Sopenharmony_ci */ 2508c2ecf20Sopenharmony_ci for (n = 0; n <= num_keys; n++) { 2518c2ecf20Sopenharmony_ci for (i = 0; i <= nstates; i++) { 2528c2ecf20Sopenharmony_ci ch = *cp1++; 2538c2ecf20Sopenharmony_ci cp += sprintf(cp, "%d,", (int)ch); 2548c2ecf20Sopenharmony_ci *cp++ = (i < nstates) ? SPACE : '\n'; 2558c2ecf20Sopenharmony_ci } 2568c2ecf20Sopenharmony_ci } 2578c2ecf20Sopenharmony_ci cp += sprintf(cp, "0, %d\n", KEY_MAP_VER); 2588c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2598c2ecf20Sopenharmony_ci return (int)(cp - buf); 2608c2ecf20Sopenharmony_ci} 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci/* 2638c2ecf20Sopenharmony_ci * This is called when a user changes the keymap parameter. 2648c2ecf20Sopenharmony_ci */ 2658c2ecf20Sopenharmony_cistatic ssize_t keymap_store(struct kobject *kobj, struct kobj_attribute *attr, 2668c2ecf20Sopenharmony_ci const char *buf, size_t count) 2678c2ecf20Sopenharmony_ci{ 2688c2ecf20Sopenharmony_ci int i; 2698c2ecf20Sopenharmony_ci ssize_t ret = count; 2708c2ecf20Sopenharmony_ci char *in_buff = NULL; 2718c2ecf20Sopenharmony_ci char *cp; 2728c2ecf20Sopenharmony_ci u_char *cp1; 2738c2ecf20Sopenharmony_ci unsigned long flags; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 2768c2ecf20Sopenharmony_ci in_buff = kmemdup(buf, count + 1, GFP_ATOMIC); 2778c2ecf20Sopenharmony_ci if (!in_buff) { 2788c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2798c2ecf20Sopenharmony_ci return -ENOMEM; 2808c2ecf20Sopenharmony_ci } 2818c2ecf20Sopenharmony_ci if (strchr("dDrR", *in_buff)) { 2828c2ecf20Sopenharmony_ci spk_set_key_info(spk_key_defaults, spk_key_buf); 2838c2ecf20Sopenharmony_ci pr_info("keymap set to default values\n"); 2848c2ecf20Sopenharmony_ci kfree(in_buff); 2858c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 2868c2ecf20Sopenharmony_ci return count; 2878c2ecf20Sopenharmony_ci } 2888c2ecf20Sopenharmony_ci if (in_buff[count - 1] == '\n') 2898c2ecf20Sopenharmony_ci in_buff[count - 1] = '\0'; 2908c2ecf20Sopenharmony_ci cp = in_buff; 2918c2ecf20Sopenharmony_ci cp1 = (u_char *)in_buff; 2928c2ecf20Sopenharmony_ci for (i = 0; i < 3; i++) { 2938c2ecf20Sopenharmony_ci cp = spk_s2uchar(cp, cp1); 2948c2ecf20Sopenharmony_ci cp1++; 2958c2ecf20Sopenharmony_ci } 2968c2ecf20Sopenharmony_ci i = (int)cp1[-2] + 1; 2978c2ecf20Sopenharmony_ci i *= (int)cp1[-1] + 1; 2988c2ecf20Sopenharmony_ci i += 2; /* 0 and last map ver */ 2998c2ecf20Sopenharmony_ci if (cp1[-3] != KEY_MAP_VER || cp1[-1] > 10 || 3008c2ecf20Sopenharmony_ci i + SHIFT_TBL_SIZE + 4 >= sizeof(spk_key_buf)) { 3018c2ecf20Sopenharmony_ci pr_warn("i %d %d %d %d\n", i, 3028c2ecf20Sopenharmony_ci (int)cp1[-3], (int)cp1[-2], (int)cp1[-1]); 3038c2ecf20Sopenharmony_ci kfree(in_buff); 3048c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 3058c2ecf20Sopenharmony_ci return -EINVAL; 3068c2ecf20Sopenharmony_ci } 3078c2ecf20Sopenharmony_ci while (--i >= 0) { 3088c2ecf20Sopenharmony_ci cp = spk_s2uchar(cp, cp1); 3098c2ecf20Sopenharmony_ci cp1++; 3108c2ecf20Sopenharmony_ci if (!(*cp)) 3118c2ecf20Sopenharmony_ci break; 3128c2ecf20Sopenharmony_ci } 3138c2ecf20Sopenharmony_ci if (i != 0 || cp1[-1] != KEY_MAP_VER || cp1[-2] != 0) { 3148c2ecf20Sopenharmony_ci ret = -EINVAL; 3158c2ecf20Sopenharmony_ci pr_warn("end %d %d %d %d\n", i, 3168c2ecf20Sopenharmony_ci (int)cp1[-3], (int)cp1[-2], (int)cp1[-1]); 3178c2ecf20Sopenharmony_ci } else { 3188c2ecf20Sopenharmony_ci if (spk_set_key_info(in_buff, spk_key_buf)) { 3198c2ecf20Sopenharmony_ci spk_set_key_info(spk_key_defaults, spk_key_buf); 3208c2ecf20Sopenharmony_ci ret = -EINVAL; 3218c2ecf20Sopenharmony_ci pr_warn("set key failed\n"); 3228c2ecf20Sopenharmony_ci } 3238c2ecf20Sopenharmony_ci } 3248c2ecf20Sopenharmony_ci kfree(in_buff); 3258c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 3268c2ecf20Sopenharmony_ci return ret; 3278c2ecf20Sopenharmony_ci} 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci/* 3308c2ecf20Sopenharmony_ci * This is called when a user changes the value of the silent parameter. 3318c2ecf20Sopenharmony_ci */ 3328c2ecf20Sopenharmony_cistatic ssize_t silent_store(struct kobject *kobj, struct kobj_attribute *attr, 3338c2ecf20Sopenharmony_ci const char *buf, size_t count) 3348c2ecf20Sopenharmony_ci{ 3358c2ecf20Sopenharmony_ci int len; 3368c2ecf20Sopenharmony_ci struct vc_data *vc = vc_cons[fg_console].d; 3378c2ecf20Sopenharmony_ci char ch = 0; 3388c2ecf20Sopenharmony_ci char shut; 3398c2ecf20Sopenharmony_ci unsigned long flags; 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci len = strlen(buf); 3428c2ecf20Sopenharmony_ci if (len > 0 && len < 3) { 3438c2ecf20Sopenharmony_ci ch = buf[0]; 3448c2ecf20Sopenharmony_ci if (ch == '\n') 3458c2ecf20Sopenharmony_ci ch = '0'; 3468c2ecf20Sopenharmony_ci } 3478c2ecf20Sopenharmony_ci if (ch < '0' || ch > '7') { 3488c2ecf20Sopenharmony_ci pr_warn("silent value '%c' not in range (0,7)\n", ch); 3498c2ecf20Sopenharmony_ci return -EINVAL; 3508c2ecf20Sopenharmony_ci } 3518c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 3528c2ecf20Sopenharmony_ci if (ch & 2) { 3538c2ecf20Sopenharmony_ci shut = 1; 3548c2ecf20Sopenharmony_ci spk_do_flush(); 3558c2ecf20Sopenharmony_ci } else { 3568c2ecf20Sopenharmony_ci shut = 0; 3578c2ecf20Sopenharmony_ci } 3588c2ecf20Sopenharmony_ci if (ch & 4) 3598c2ecf20Sopenharmony_ci shut |= 0x40; 3608c2ecf20Sopenharmony_ci if (ch & 1) 3618c2ecf20Sopenharmony_ci spk_shut_up |= shut; 3628c2ecf20Sopenharmony_ci else 3638c2ecf20Sopenharmony_ci spk_shut_up &= ~shut; 3648c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 3658c2ecf20Sopenharmony_ci return count; 3668c2ecf20Sopenharmony_ci} 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_ci/* 3698c2ecf20Sopenharmony_ci * This is called when a user reads the synth setting. 3708c2ecf20Sopenharmony_ci */ 3718c2ecf20Sopenharmony_cistatic ssize_t synth_show(struct kobject *kobj, struct kobj_attribute *attr, 3728c2ecf20Sopenharmony_ci char *buf) 3738c2ecf20Sopenharmony_ci{ 3748c2ecf20Sopenharmony_ci int rv; 3758c2ecf20Sopenharmony_ci 3768c2ecf20Sopenharmony_ci if (!synth) 3778c2ecf20Sopenharmony_ci rv = sprintf(buf, "%s\n", "none"); 3788c2ecf20Sopenharmony_ci else 3798c2ecf20Sopenharmony_ci rv = sprintf(buf, "%s\n", synth->name); 3808c2ecf20Sopenharmony_ci return rv; 3818c2ecf20Sopenharmony_ci} 3828c2ecf20Sopenharmony_ci 3838c2ecf20Sopenharmony_ci/* 3848c2ecf20Sopenharmony_ci * This is called when a user requests to change synthesizers. 3858c2ecf20Sopenharmony_ci */ 3868c2ecf20Sopenharmony_cistatic ssize_t synth_store(struct kobject *kobj, struct kobj_attribute *attr, 3878c2ecf20Sopenharmony_ci const char *buf, size_t count) 3888c2ecf20Sopenharmony_ci{ 3898c2ecf20Sopenharmony_ci int len; 3908c2ecf20Sopenharmony_ci char new_synth_name[10]; 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci len = strlen(buf); 3938c2ecf20Sopenharmony_ci if (len < 2 || len > 9) 3948c2ecf20Sopenharmony_ci return -EINVAL; 3958c2ecf20Sopenharmony_ci memcpy(new_synth_name, buf, len); 3968c2ecf20Sopenharmony_ci if (new_synth_name[len - 1] == '\n') 3978c2ecf20Sopenharmony_ci len--; 3988c2ecf20Sopenharmony_ci new_synth_name[len] = '\0'; 3998c2ecf20Sopenharmony_ci spk_strlwr(new_synth_name); 4008c2ecf20Sopenharmony_ci if (synth && !strcmp(new_synth_name, synth->name)) { 4018c2ecf20Sopenharmony_ci pr_warn("%s already in use\n", new_synth_name); 4028c2ecf20Sopenharmony_ci } else if (synth_init(new_synth_name) != 0) { 4038c2ecf20Sopenharmony_ci pr_warn("failed to init synth %s\n", new_synth_name); 4048c2ecf20Sopenharmony_ci return -ENODEV; 4058c2ecf20Sopenharmony_ci } 4068c2ecf20Sopenharmony_ci return count; 4078c2ecf20Sopenharmony_ci} 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_ci/* 4108c2ecf20Sopenharmony_ci * This is called when text is sent to the synth via the synth_direct file. 4118c2ecf20Sopenharmony_ci */ 4128c2ecf20Sopenharmony_cistatic ssize_t synth_direct_store(struct kobject *kobj, 4138c2ecf20Sopenharmony_ci struct kobj_attribute *attr, 4148c2ecf20Sopenharmony_ci const char *buf, size_t count) 4158c2ecf20Sopenharmony_ci{ 4168c2ecf20Sopenharmony_ci u_char tmp[256]; 4178c2ecf20Sopenharmony_ci int len; 4188c2ecf20Sopenharmony_ci int bytes; 4198c2ecf20Sopenharmony_ci const char *ptr = buf; 4208c2ecf20Sopenharmony_ci unsigned long flags; 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_ci if (!synth) 4238c2ecf20Sopenharmony_ci return -EPERM; 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_ci len = strlen(buf); 4268c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 4278c2ecf20Sopenharmony_ci while (len > 0) { 4288c2ecf20Sopenharmony_ci bytes = min_t(size_t, len, 250); 4298c2ecf20Sopenharmony_ci strncpy(tmp, ptr, bytes); 4308c2ecf20Sopenharmony_ci tmp[bytes] = '\0'; 4318c2ecf20Sopenharmony_ci string_unescape_any_inplace(tmp); 4328c2ecf20Sopenharmony_ci synth_printf("%s", tmp); 4338c2ecf20Sopenharmony_ci ptr += bytes; 4348c2ecf20Sopenharmony_ci len -= bytes; 4358c2ecf20Sopenharmony_ci } 4368c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 4378c2ecf20Sopenharmony_ci return count; 4388c2ecf20Sopenharmony_ci} 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_ci/* 4418c2ecf20Sopenharmony_ci * This function is called when a user reads the version. 4428c2ecf20Sopenharmony_ci */ 4438c2ecf20Sopenharmony_cistatic ssize_t version_show(struct kobject *kobj, struct kobj_attribute *attr, 4448c2ecf20Sopenharmony_ci char *buf) 4458c2ecf20Sopenharmony_ci{ 4468c2ecf20Sopenharmony_ci char *cp; 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci cp = buf; 4498c2ecf20Sopenharmony_ci cp += sprintf(cp, "Speakup version %s\n", SPEAKUP_VERSION); 4508c2ecf20Sopenharmony_ci if (synth) 4518c2ecf20Sopenharmony_ci cp += sprintf(cp, "%s synthesizer driver version %s\n", 4528c2ecf20Sopenharmony_ci synth->name, synth->version); 4538c2ecf20Sopenharmony_ci return cp - buf; 4548c2ecf20Sopenharmony_ci} 4558c2ecf20Sopenharmony_ci 4568c2ecf20Sopenharmony_ci/* 4578c2ecf20Sopenharmony_ci * This is called when a user reads the punctuation settings. 4588c2ecf20Sopenharmony_ci */ 4598c2ecf20Sopenharmony_cistatic ssize_t punc_show(struct kobject *kobj, struct kobj_attribute *attr, 4608c2ecf20Sopenharmony_ci char *buf) 4618c2ecf20Sopenharmony_ci{ 4628c2ecf20Sopenharmony_ci int i; 4638c2ecf20Sopenharmony_ci char *cp = buf; 4648c2ecf20Sopenharmony_ci struct st_var_header *p_header; 4658c2ecf20Sopenharmony_ci struct punc_var_t *var; 4668c2ecf20Sopenharmony_ci struct st_bits_data *pb; 4678c2ecf20Sopenharmony_ci short mask; 4688c2ecf20Sopenharmony_ci unsigned long flags; 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_ci p_header = spk_var_header_by_name(attr->attr.name); 4718c2ecf20Sopenharmony_ci if (!p_header) { 4728c2ecf20Sopenharmony_ci pr_warn("p_header is null, attr->attr.name is %s\n", 4738c2ecf20Sopenharmony_ci attr->attr.name); 4748c2ecf20Sopenharmony_ci return -EINVAL; 4758c2ecf20Sopenharmony_ci } 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_ci var = spk_get_punc_var(p_header->var_id); 4788c2ecf20Sopenharmony_ci if (!var) { 4798c2ecf20Sopenharmony_ci pr_warn("var is null, p_header->var_id is %i\n", 4808c2ecf20Sopenharmony_ci p_header->var_id); 4818c2ecf20Sopenharmony_ci return -EINVAL; 4828c2ecf20Sopenharmony_ci } 4838c2ecf20Sopenharmony_ci 4848c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 4858c2ecf20Sopenharmony_ci pb = (struct st_bits_data *)&spk_punc_info[var->value]; 4868c2ecf20Sopenharmony_ci mask = pb->mask; 4878c2ecf20Sopenharmony_ci for (i = 33; i < 128; i++) { 4888c2ecf20Sopenharmony_ci if (!(spk_chartab[i] & mask)) 4898c2ecf20Sopenharmony_ci continue; 4908c2ecf20Sopenharmony_ci *cp++ = (char)i; 4918c2ecf20Sopenharmony_ci } 4928c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 4938c2ecf20Sopenharmony_ci return cp - buf; 4948c2ecf20Sopenharmony_ci} 4958c2ecf20Sopenharmony_ci 4968c2ecf20Sopenharmony_ci/* 4978c2ecf20Sopenharmony_ci * This is called when a user changes the punctuation settings. 4988c2ecf20Sopenharmony_ci */ 4998c2ecf20Sopenharmony_cistatic ssize_t punc_store(struct kobject *kobj, struct kobj_attribute *attr, 5008c2ecf20Sopenharmony_ci const char *buf, size_t count) 5018c2ecf20Sopenharmony_ci{ 5028c2ecf20Sopenharmony_ci int x; 5038c2ecf20Sopenharmony_ci struct st_var_header *p_header; 5048c2ecf20Sopenharmony_ci struct punc_var_t *var; 5058c2ecf20Sopenharmony_ci char punc_buf[100]; 5068c2ecf20Sopenharmony_ci unsigned long flags; 5078c2ecf20Sopenharmony_ci 5088c2ecf20Sopenharmony_ci x = strlen(buf); 5098c2ecf20Sopenharmony_ci if (x < 1 || x > 99) 5108c2ecf20Sopenharmony_ci return -EINVAL; 5118c2ecf20Sopenharmony_ci 5128c2ecf20Sopenharmony_ci p_header = spk_var_header_by_name(attr->attr.name); 5138c2ecf20Sopenharmony_ci if (!p_header) { 5148c2ecf20Sopenharmony_ci pr_warn("p_header is null, attr->attr.name is %s\n", 5158c2ecf20Sopenharmony_ci attr->attr.name); 5168c2ecf20Sopenharmony_ci return -EINVAL; 5178c2ecf20Sopenharmony_ci } 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_ci var = spk_get_punc_var(p_header->var_id); 5208c2ecf20Sopenharmony_ci if (!var) { 5218c2ecf20Sopenharmony_ci pr_warn("var is null, p_header->var_id is %i\n", 5228c2ecf20Sopenharmony_ci p_header->var_id); 5238c2ecf20Sopenharmony_ci return -EINVAL; 5248c2ecf20Sopenharmony_ci } 5258c2ecf20Sopenharmony_ci 5268c2ecf20Sopenharmony_ci memcpy(punc_buf, buf, x); 5278c2ecf20Sopenharmony_ci 5288c2ecf20Sopenharmony_ci while (x && punc_buf[x - 1] == '\n') 5298c2ecf20Sopenharmony_ci x--; 5308c2ecf20Sopenharmony_ci punc_buf[x] = '\0'; 5318c2ecf20Sopenharmony_ci 5328c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci if (*punc_buf == 'd' || *punc_buf == 'r') 5358c2ecf20Sopenharmony_ci x = spk_set_mask_bits(NULL, var->value, 3); 5368c2ecf20Sopenharmony_ci else 5378c2ecf20Sopenharmony_ci x = spk_set_mask_bits(punc_buf, var->value, 3); 5388c2ecf20Sopenharmony_ci 5398c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 5408c2ecf20Sopenharmony_ci return count; 5418c2ecf20Sopenharmony_ci} 5428c2ecf20Sopenharmony_ci 5438c2ecf20Sopenharmony_ci/* 5448c2ecf20Sopenharmony_ci * This function is called when a user reads one of the variable parameters. 5458c2ecf20Sopenharmony_ci */ 5468c2ecf20Sopenharmony_cissize_t spk_var_show(struct kobject *kobj, struct kobj_attribute *attr, 5478c2ecf20Sopenharmony_ci char *buf) 5488c2ecf20Sopenharmony_ci{ 5498c2ecf20Sopenharmony_ci int rv = 0; 5508c2ecf20Sopenharmony_ci struct st_var_header *param; 5518c2ecf20Sopenharmony_ci struct var_t *var; 5528c2ecf20Sopenharmony_ci char *cp1; 5538c2ecf20Sopenharmony_ci char *cp; 5548c2ecf20Sopenharmony_ci char ch; 5558c2ecf20Sopenharmony_ci unsigned long flags; 5568c2ecf20Sopenharmony_ci 5578c2ecf20Sopenharmony_ci param = spk_var_header_by_name(attr->attr.name); 5588c2ecf20Sopenharmony_ci if (!param) 5598c2ecf20Sopenharmony_ci return -EINVAL; 5608c2ecf20Sopenharmony_ci 5618c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 5628c2ecf20Sopenharmony_ci var = (struct var_t *)param->data; 5638c2ecf20Sopenharmony_ci switch (param->var_type) { 5648c2ecf20Sopenharmony_ci case VAR_NUM: 5658c2ecf20Sopenharmony_ci case VAR_TIME: 5668c2ecf20Sopenharmony_ci if (var) 5678c2ecf20Sopenharmony_ci rv = sprintf(buf, "%i\n", var->u.n.value); 5688c2ecf20Sopenharmony_ci else 5698c2ecf20Sopenharmony_ci rv = sprintf(buf, "0\n"); 5708c2ecf20Sopenharmony_ci break; 5718c2ecf20Sopenharmony_ci case VAR_STRING: 5728c2ecf20Sopenharmony_ci if (var) { 5738c2ecf20Sopenharmony_ci cp1 = buf; 5748c2ecf20Sopenharmony_ci *cp1++ = '"'; 5758c2ecf20Sopenharmony_ci for (cp = (char *)param->p_val; (ch = *cp); cp++) { 5768c2ecf20Sopenharmony_ci if (ch >= ' ' && ch < '~') 5778c2ecf20Sopenharmony_ci *cp1++ = ch; 5788c2ecf20Sopenharmony_ci else 5798c2ecf20Sopenharmony_ci cp1 += sprintf(cp1, "\\x%02x", ch); 5808c2ecf20Sopenharmony_ci } 5818c2ecf20Sopenharmony_ci *cp1++ = '"'; 5828c2ecf20Sopenharmony_ci *cp1++ = '\n'; 5838c2ecf20Sopenharmony_ci *cp1 = '\0'; 5848c2ecf20Sopenharmony_ci rv = cp1 - buf; 5858c2ecf20Sopenharmony_ci } else { 5868c2ecf20Sopenharmony_ci rv = sprintf(buf, "\"\"\n"); 5878c2ecf20Sopenharmony_ci } 5888c2ecf20Sopenharmony_ci break; 5898c2ecf20Sopenharmony_ci default: 5908c2ecf20Sopenharmony_ci rv = sprintf(buf, "Bad parameter %s, type %i\n", 5918c2ecf20Sopenharmony_ci param->name, param->var_type); 5928c2ecf20Sopenharmony_ci break; 5938c2ecf20Sopenharmony_ci } 5948c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 5958c2ecf20Sopenharmony_ci return rv; 5968c2ecf20Sopenharmony_ci} 5978c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(spk_var_show); 5988c2ecf20Sopenharmony_ci 5998c2ecf20Sopenharmony_ci/* 6008c2ecf20Sopenharmony_ci * Used to reset either default_pitch or default_vol. 6018c2ecf20Sopenharmony_ci */ 6028c2ecf20Sopenharmony_cistatic inline void spk_reset_default_value(char *header_name, 6038c2ecf20Sopenharmony_ci int *synth_default_value, int idx) 6048c2ecf20Sopenharmony_ci{ 6058c2ecf20Sopenharmony_ci struct st_var_header *param; 6068c2ecf20Sopenharmony_ci 6078c2ecf20Sopenharmony_ci if (synth && synth_default_value) { 6088c2ecf20Sopenharmony_ci param = spk_var_header_by_name(header_name); 6098c2ecf20Sopenharmony_ci if (param) { 6108c2ecf20Sopenharmony_ci spk_set_num_var(synth_default_value[idx], 6118c2ecf20Sopenharmony_ci param, E_NEW_DEFAULT); 6128c2ecf20Sopenharmony_ci spk_set_num_var(0, param, E_DEFAULT); 6138c2ecf20Sopenharmony_ci pr_info("%s reset to default value\n", param->name); 6148c2ecf20Sopenharmony_ci } 6158c2ecf20Sopenharmony_ci } 6168c2ecf20Sopenharmony_ci} 6178c2ecf20Sopenharmony_ci 6188c2ecf20Sopenharmony_ci/* 6198c2ecf20Sopenharmony_ci * This function is called when a user echos a value to one of the 6208c2ecf20Sopenharmony_ci * variable parameters. 6218c2ecf20Sopenharmony_ci */ 6228c2ecf20Sopenharmony_cissize_t spk_var_store(struct kobject *kobj, struct kobj_attribute *attr, 6238c2ecf20Sopenharmony_ci const char *buf, size_t count) 6248c2ecf20Sopenharmony_ci{ 6258c2ecf20Sopenharmony_ci struct st_var_header *param; 6268c2ecf20Sopenharmony_ci int ret; 6278c2ecf20Sopenharmony_ci int len; 6288c2ecf20Sopenharmony_ci char *cp; 6298c2ecf20Sopenharmony_ci struct var_t *var_data; 6308c2ecf20Sopenharmony_ci long value; 6318c2ecf20Sopenharmony_ci unsigned long flags; 6328c2ecf20Sopenharmony_ci 6338c2ecf20Sopenharmony_ci param = spk_var_header_by_name(attr->attr.name); 6348c2ecf20Sopenharmony_ci if (!param) 6358c2ecf20Sopenharmony_ci return -EINVAL; 6368c2ecf20Sopenharmony_ci if (!param->data) 6378c2ecf20Sopenharmony_ci return 0; 6388c2ecf20Sopenharmony_ci ret = 0; 6398c2ecf20Sopenharmony_ci cp = (char *)buf; 6408c2ecf20Sopenharmony_ci string_unescape_any_inplace(cp); 6418c2ecf20Sopenharmony_ci 6428c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 6438c2ecf20Sopenharmony_ci switch (param->var_type) { 6448c2ecf20Sopenharmony_ci case VAR_NUM: 6458c2ecf20Sopenharmony_ci case VAR_TIME: 6468c2ecf20Sopenharmony_ci if (*cp == 'd' || *cp == 'r' || *cp == '\0') 6478c2ecf20Sopenharmony_ci len = E_DEFAULT; 6488c2ecf20Sopenharmony_ci else if (*cp == '+' || *cp == '-') 6498c2ecf20Sopenharmony_ci len = E_INC; 6508c2ecf20Sopenharmony_ci else 6518c2ecf20Sopenharmony_ci len = E_SET; 6528c2ecf20Sopenharmony_ci if (kstrtol(cp, 10, &value) == 0) 6538c2ecf20Sopenharmony_ci ret = spk_set_num_var(value, param, len); 6548c2ecf20Sopenharmony_ci else 6558c2ecf20Sopenharmony_ci pr_warn("overflow or parsing error has occurred"); 6568c2ecf20Sopenharmony_ci if (ret == -ERANGE) { 6578c2ecf20Sopenharmony_ci var_data = param->data; 6588c2ecf20Sopenharmony_ci pr_warn("value for %s out of range, expect %d to %d\n", 6598c2ecf20Sopenharmony_ci param->name, 6608c2ecf20Sopenharmony_ci var_data->u.n.low, var_data->u.n.high); 6618c2ecf20Sopenharmony_ci } 6628c2ecf20Sopenharmony_ci 6638c2ecf20Sopenharmony_ci /* 6648c2ecf20Sopenharmony_ci * If voice was just changed, we might need to reset our default 6658c2ecf20Sopenharmony_ci * pitch and volume. 6668c2ecf20Sopenharmony_ci */ 6678c2ecf20Sopenharmony_ci if (param->var_id == VOICE && synth && 6688c2ecf20Sopenharmony_ci (ret == 0 || ret == -ERESTART)) { 6698c2ecf20Sopenharmony_ci var_data = param->data; 6708c2ecf20Sopenharmony_ci value = var_data->u.n.value; 6718c2ecf20Sopenharmony_ci spk_reset_default_value("pitch", synth->default_pitch, 6728c2ecf20Sopenharmony_ci value); 6738c2ecf20Sopenharmony_ci spk_reset_default_value("vol", synth->default_vol, 6748c2ecf20Sopenharmony_ci value); 6758c2ecf20Sopenharmony_ci } 6768c2ecf20Sopenharmony_ci break; 6778c2ecf20Sopenharmony_ci case VAR_STRING: 6788c2ecf20Sopenharmony_ci len = strlen(cp); 6798c2ecf20Sopenharmony_ci if ((len >= 1) && (cp[len - 1] == '\n')) 6808c2ecf20Sopenharmony_ci --len; 6818c2ecf20Sopenharmony_ci if ((len >= 2) && (cp[0] == '"') && (cp[len - 1] == '"')) { 6828c2ecf20Sopenharmony_ci ++cp; 6838c2ecf20Sopenharmony_ci len -= 2; 6848c2ecf20Sopenharmony_ci } 6858c2ecf20Sopenharmony_ci cp[len] = '\0'; 6868c2ecf20Sopenharmony_ci ret = spk_set_string_var(cp, param, len); 6878c2ecf20Sopenharmony_ci if (ret == -E2BIG) 6888c2ecf20Sopenharmony_ci pr_warn("value too long for %s\n", 6898c2ecf20Sopenharmony_ci param->name); 6908c2ecf20Sopenharmony_ci break; 6918c2ecf20Sopenharmony_ci default: 6928c2ecf20Sopenharmony_ci pr_warn("%s unknown type %d\n", 6938c2ecf20Sopenharmony_ci param->name, (int)param->var_type); 6948c2ecf20Sopenharmony_ci break; 6958c2ecf20Sopenharmony_ci } 6968c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 6978c2ecf20Sopenharmony_ci 6988c2ecf20Sopenharmony_ci if (ret == -ERESTART) 6998c2ecf20Sopenharmony_ci pr_info("%s reset to default value\n", param->name); 7008c2ecf20Sopenharmony_ci return count; 7018c2ecf20Sopenharmony_ci} 7028c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(spk_var_store); 7038c2ecf20Sopenharmony_ci 7048c2ecf20Sopenharmony_ci/* 7058c2ecf20Sopenharmony_ci * Functions for reading and writing lists of i18n messages. Incomplete. 7068c2ecf20Sopenharmony_ci */ 7078c2ecf20Sopenharmony_ci 7088c2ecf20Sopenharmony_cistatic ssize_t message_show_helper(char *buf, enum msg_index_t first, 7098c2ecf20Sopenharmony_ci enum msg_index_t last) 7108c2ecf20Sopenharmony_ci{ 7118c2ecf20Sopenharmony_ci size_t bufsize = PAGE_SIZE; 7128c2ecf20Sopenharmony_ci char *buf_pointer = buf; 7138c2ecf20Sopenharmony_ci int printed; 7148c2ecf20Sopenharmony_ci enum msg_index_t cursor; 7158c2ecf20Sopenharmony_ci int index = 0; 7168c2ecf20Sopenharmony_ci *buf_pointer = '\0'; /* buf_pointer always looking at a NUL byte. */ 7178c2ecf20Sopenharmony_ci 7188c2ecf20Sopenharmony_ci for (cursor = first; cursor <= last; cursor++, index++) { 7198c2ecf20Sopenharmony_ci if (bufsize <= 1) 7208c2ecf20Sopenharmony_ci break; 7218c2ecf20Sopenharmony_ci printed = scnprintf(buf_pointer, bufsize, "%d\t%s\n", 7228c2ecf20Sopenharmony_ci index, spk_msg_get(cursor)); 7238c2ecf20Sopenharmony_ci buf_pointer += printed; 7248c2ecf20Sopenharmony_ci bufsize -= printed; 7258c2ecf20Sopenharmony_ci } 7268c2ecf20Sopenharmony_ci 7278c2ecf20Sopenharmony_ci return buf_pointer - buf; 7288c2ecf20Sopenharmony_ci} 7298c2ecf20Sopenharmony_ci 7308c2ecf20Sopenharmony_cistatic void report_msg_status(int reset, int received, int used, 7318c2ecf20Sopenharmony_ci int rejected, char *groupname) 7328c2ecf20Sopenharmony_ci{ 7338c2ecf20Sopenharmony_ci int len; 7348c2ecf20Sopenharmony_ci char buf[160]; 7358c2ecf20Sopenharmony_ci 7368c2ecf20Sopenharmony_ci if (reset) { 7378c2ecf20Sopenharmony_ci pr_info("i18n messages from group %s reset to defaults\n", 7388c2ecf20Sopenharmony_ci groupname); 7398c2ecf20Sopenharmony_ci } else if (received) { 7408c2ecf20Sopenharmony_ci len = snprintf(buf, sizeof(buf), 7418c2ecf20Sopenharmony_ci " updated %d of %d i18n messages from group %s\n", 7428c2ecf20Sopenharmony_ci used, received, groupname); 7438c2ecf20Sopenharmony_ci if (rejected) 7448c2ecf20Sopenharmony_ci snprintf(buf + (len - 1), sizeof(buf) - (len - 1), 7458c2ecf20Sopenharmony_ci " with %d reject%s\n", 7468c2ecf20Sopenharmony_ci rejected, rejected > 1 ? "s" : ""); 7478c2ecf20Sopenharmony_ci pr_info("%s", buf); 7488c2ecf20Sopenharmony_ci } 7498c2ecf20Sopenharmony_ci} 7508c2ecf20Sopenharmony_ci 7518c2ecf20Sopenharmony_cistatic ssize_t message_store_helper(const char *buf, size_t count, 7528c2ecf20Sopenharmony_ci struct msg_group_t *group) 7538c2ecf20Sopenharmony_ci{ 7548c2ecf20Sopenharmony_ci char *cp = (char *)buf; 7558c2ecf20Sopenharmony_ci char *end = cp + count; 7568c2ecf20Sopenharmony_ci char *linefeed = NULL; 7578c2ecf20Sopenharmony_ci char *temp = NULL; 7588c2ecf20Sopenharmony_ci ssize_t msg_stored = 0; 7598c2ecf20Sopenharmony_ci ssize_t retval = count; 7608c2ecf20Sopenharmony_ci size_t desc_length = 0; 7618c2ecf20Sopenharmony_ci unsigned long index = 0; 7628c2ecf20Sopenharmony_ci int received = 0; 7638c2ecf20Sopenharmony_ci int used = 0; 7648c2ecf20Sopenharmony_ci int rejected = 0; 7658c2ecf20Sopenharmony_ci int reset = 0; 7668c2ecf20Sopenharmony_ci enum msg_index_t firstmessage = group->start; 7678c2ecf20Sopenharmony_ci enum msg_index_t lastmessage = group->end; 7688c2ecf20Sopenharmony_ci enum msg_index_t curmessage; 7698c2ecf20Sopenharmony_ci 7708c2ecf20Sopenharmony_ci while (cp < end) { 7718c2ecf20Sopenharmony_ci while ((cp < end) && (*cp == ' ' || *cp == '\t')) 7728c2ecf20Sopenharmony_ci cp++; 7738c2ecf20Sopenharmony_ci 7748c2ecf20Sopenharmony_ci if (cp == end) 7758c2ecf20Sopenharmony_ci break; 7768c2ecf20Sopenharmony_ci if (strchr("dDrR", *cp)) { 7778c2ecf20Sopenharmony_ci reset = 1; 7788c2ecf20Sopenharmony_ci break; 7798c2ecf20Sopenharmony_ci } 7808c2ecf20Sopenharmony_ci received++; 7818c2ecf20Sopenharmony_ci 7828c2ecf20Sopenharmony_ci linefeed = strchr(cp, '\n'); 7838c2ecf20Sopenharmony_ci if (!linefeed) { 7848c2ecf20Sopenharmony_ci rejected++; 7858c2ecf20Sopenharmony_ci break; 7868c2ecf20Sopenharmony_ci } 7878c2ecf20Sopenharmony_ci 7888c2ecf20Sopenharmony_ci if (!isdigit(*cp)) { 7898c2ecf20Sopenharmony_ci rejected++; 7908c2ecf20Sopenharmony_ci cp = linefeed + 1; 7918c2ecf20Sopenharmony_ci continue; 7928c2ecf20Sopenharmony_ci } 7938c2ecf20Sopenharmony_ci 7948c2ecf20Sopenharmony_ci /* 7958c2ecf20Sopenharmony_ci * Do not replace with kstrtoul: 7968c2ecf20Sopenharmony_ci * here we need temp to be updated 7978c2ecf20Sopenharmony_ci */ 7988c2ecf20Sopenharmony_ci index = simple_strtoul(cp, &temp, 10); 7998c2ecf20Sopenharmony_ci 8008c2ecf20Sopenharmony_ci while ((temp < linefeed) && (*temp == ' ' || *temp == '\t')) 8018c2ecf20Sopenharmony_ci temp++; 8028c2ecf20Sopenharmony_ci 8038c2ecf20Sopenharmony_ci desc_length = linefeed - temp; 8048c2ecf20Sopenharmony_ci curmessage = firstmessage + index; 8058c2ecf20Sopenharmony_ci 8068c2ecf20Sopenharmony_ci /* 8078c2ecf20Sopenharmony_ci * Note the check (curmessage < firstmessage). It is not 8088c2ecf20Sopenharmony_ci * redundant. Suppose that the user gave us an index 8098c2ecf20Sopenharmony_ci * equal to ULONG_MAX - 1. If firstmessage > 1, then 8108c2ecf20Sopenharmony_ci * firstmessage + index < firstmessage! 8118c2ecf20Sopenharmony_ci */ 8128c2ecf20Sopenharmony_ci 8138c2ecf20Sopenharmony_ci if ((curmessage < firstmessage) || (curmessage > lastmessage)) { 8148c2ecf20Sopenharmony_ci rejected++; 8158c2ecf20Sopenharmony_ci cp = linefeed + 1; 8168c2ecf20Sopenharmony_ci continue; 8178c2ecf20Sopenharmony_ci } 8188c2ecf20Sopenharmony_ci 8198c2ecf20Sopenharmony_ci msg_stored = spk_msg_set(curmessage, temp, desc_length); 8208c2ecf20Sopenharmony_ci if (msg_stored < 0) { 8218c2ecf20Sopenharmony_ci retval = msg_stored; 8228c2ecf20Sopenharmony_ci if (msg_stored == -ENOMEM) 8238c2ecf20Sopenharmony_ci reset = 1; 8248c2ecf20Sopenharmony_ci break; 8258c2ecf20Sopenharmony_ci } 8268c2ecf20Sopenharmony_ci 8278c2ecf20Sopenharmony_ci used++; 8288c2ecf20Sopenharmony_ci 8298c2ecf20Sopenharmony_ci cp = linefeed + 1; 8308c2ecf20Sopenharmony_ci } 8318c2ecf20Sopenharmony_ci 8328c2ecf20Sopenharmony_ci if (reset) 8338c2ecf20Sopenharmony_ci spk_reset_msg_group(group); 8348c2ecf20Sopenharmony_ci 8358c2ecf20Sopenharmony_ci report_msg_status(reset, received, used, rejected, group->name); 8368c2ecf20Sopenharmony_ci return retval; 8378c2ecf20Sopenharmony_ci} 8388c2ecf20Sopenharmony_ci 8398c2ecf20Sopenharmony_cistatic ssize_t message_show(struct kobject *kobj, 8408c2ecf20Sopenharmony_ci struct kobj_attribute *attr, char *buf) 8418c2ecf20Sopenharmony_ci{ 8428c2ecf20Sopenharmony_ci ssize_t retval = 0; 8438c2ecf20Sopenharmony_ci struct msg_group_t *group = spk_find_msg_group(attr->attr.name); 8448c2ecf20Sopenharmony_ci unsigned long flags; 8458c2ecf20Sopenharmony_ci 8468c2ecf20Sopenharmony_ci if (WARN_ON(!group)) 8478c2ecf20Sopenharmony_ci return -EINVAL; 8488c2ecf20Sopenharmony_ci 8498c2ecf20Sopenharmony_ci spin_lock_irqsave(&speakup_info.spinlock, flags); 8508c2ecf20Sopenharmony_ci retval = message_show_helper(buf, group->start, group->end); 8518c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&speakup_info.spinlock, flags); 8528c2ecf20Sopenharmony_ci return retval; 8538c2ecf20Sopenharmony_ci} 8548c2ecf20Sopenharmony_ci 8558c2ecf20Sopenharmony_cistatic ssize_t message_store(struct kobject *kobj, struct kobj_attribute *attr, 8568c2ecf20Sopenharmony_ci const char *buf, size_t count) 8578c2ecf20Sopenharmony_ci{ 8588c2ecf20Sopenharmony_ci struct msg_group_t *group = spk_find_msg_group(attr->attr.name); 8598c2ecf20Sopenharmony_ci 8608c2ecf20Sopenharmony_ci if (WARN_ON(!group)) 8618c2ecf20Sopenharmony_ci return -EINVAL; 8628c2ecf20Sopenharmony_ci 8638c2ecf20Sopenharmony_ci return message_store_helper(buf, count, group); 8648c2ecf20Sopenharmony_ci} 8658c2ecf20Sopenharmony_ci 8668c2ecf20Sopenharmony_ci/* 8678c2ecf20Sopenharmony_ci * Declare the attributes. 8688c2ecf20Sopenharmony_ci */ 8698c2ecf20Sopenharmony_cistatic struct kobj_attribute keymap_attribute = 8708c2ecf20Sopenharmony_ci __ATTR_RW(keymap); 8718c2ecf20Sopenharmony_cistatic struct kobj_attribute silent_attribute = 8728c2ecf20Sopenharmony_ci __ATTR_WO(silent); 8738c2ecf20Sopenharmony_cistatic struct kobj_attribute synth_attribute = 8748c2ecf20Sopenharmony_ci __ATTR_RW(synth); 8758c2ecf20Sopenharmony_cistatic struct kobj_attribute synth_direct_attribute = 8768c2ecf20Sopenharmony_ci __ATTR_WO(synth_direct); 8778c2ecf20Sopenharmony_cistatic struct kobj_attribute version_attribute = 8788c2ecf20Sopenharmony_ci __ATTR_RO(version); 8798c2ecf20Sopenharmony_ci 8808c2ecf20Sopenharmony_cistatic struct kobj_attribute delimiters_attribute = 8818c2ecf20Sopenharmony_ci __ATTR(delimiters, 0644, punc_show, punc_store); 8828c2ecf20Sopenharmony_cistatic struct kobj_attribute ex_num_attribute = 8838c2ecf20Sopenharmony_ci __ATTR(ex_num, 0644, punc_show, punc_store); 8848c2ecf20Sopenharmony_cistatic struct kobj_attribute punc_all_attribute = 8858c2ecf20Sopenharmony_ci __ATTR(punc_all, 0644, punc_show, punc_store); 8868c2ecf20Sopenharmony_cistatic struct kobj_attribute punc_most_attribute = 8878c2ecf20Sopenharmony_ci __ATTR(punc_most, 0644, punc_show, punc_store); 8888c2ecf20Sopenharmony_cistatic struct kobj_attribute punc_some_attribute = 8898c2ecf20Sopenharmony_ci __ATTR(punc_some, 0644, punc_show, punc_store); 8908c2ecf20Sopenharmony_cistatic struct kobj_attribute repeats_attribute = 8918c2ecf20Sopenharmony_ci __ATTR(repeats, 0644, punc_show, punc_store); 8928c2ecf20Sopenharmony_ci 8938c2ecf20Sopenharmony_cistatic struct kobj_attribute attrib_bleep_attribute = 8948c2ecf20Sopenharmony_ci __ATTR(attrib_bleep, 0644, spk_var_show, spk_var_store); 8958c2ecf20Sopenharmony_cistatic struct kobj_attribute bell_pos_attribute = 8968c2ecf20Sopenharmony_ci __ATTR(bell_pos, 0644, spk_var_show, spk_var_store); 8978c2ecf20Sopenharmony_cistatic struct kobj_attribute bleep_time_attribute = 8988c2ecf20Sopenharmony_ci __ATTR(bleep_time, 0644, spk_var_show, spk_var_store); 8998c2ecf20Sopenharmony_cistatic struct kobj_attribute bleeps_attribute = 9008c2ecf20Sopenharmony_ci __ATTR(bleeps, 0644, spk_var_show, spk_var_store); 9018c2ecf20Sopenharmony_cistatic struct kobj_attribute cursor_time_attribute = 9028c2ecf20Sopenharmony_ci __ATTR(cursor_time, 0644, spk_var_show, spk_var_store); 9038c2ecf20Sopenharmony_cistatic struct kobj_attribute key_echo_attribute = 9048c2ecf20Sopenharmony_ci __ATTR(key_echo, 0644, spk_var_show, spk_var_store); 9058c2ecf20Sopenharmony_cistatic struct kobj_attribute no_interrupt_attribute = 9068c2ecf20Sopenharmony_ci __ATTR(no_interrupt, 0644, spk_var_show, spk_var_store); 9078c2ecf20Sopenharmony_cistatic struct kobj_attribute punc_level_attribute = 9088c2ecf20Sopenharmony_ci __ATTR(punc_level, 0644, spk_var_show, spk_var_store); 9098c2ecf20Sopenharmony_cistatic struct kobj_attribute reading_punc_attribute = 9108c2ecf20Sopenharmony_ci __ATTR(reading_punc, 0644, spk_var_show, spk_var_store); 9118c2ecf20Sopenharmony_cistatic struct kobj_attribute say_control_attribute = 9128c2ecf20Sopenharmony_ci __ATTR(say_control, 0644, spk_var_show, spk_var_store); 9138c2ecf20Sopenharmony_cistatic struct kobj_attribute say_word_ctl_attribute = 9148c2ecf20Sopenharmony_ci __ATTR(say_word_ctl, 0644, spk_var_show, spk_var_store); 9158c2ecf20Sopenharmony_cistatic struct kobj_attribute spell_delay_attribute = 9168c2ecf20Sopenharmony_ci __ATTR(spell_delay, 0644, spk_var_show, spk_var_store); 9178c2ecf20Sopenharmony_ci 9188c2ecf20Sopenharmony_ci/* 9198c2ecf20Sopenharmony_ci * These attributes are i18n related. 9208c2ecf20Sopenharmony_ci */ 9218c2ecf20Sopenharmony_cistatic struct kobj_attribute announcements_attribute = 9228c2ecf20Sopenharmony_ci __ATTR(announcements, 0644, message_show, message_store); 9238c2ecf20Sopenharmony_cistatic struct kobj_attribute characters_attribute = 9248c2ecf20Sopenharmony_ci __ATTR(characters, 0644, chars_chartab_show, 9258c2ecf20Sopenharmony_ci chars_chartab_store); 9268c2ecf20Sopenharmony_cistatic struct kobj_attribute chartab_attribute = 9278c2ecf20Sopenharmony_ci __ATTR(chartab, 0644, chars_chartab_show, 9288c2ecf20Sopenharmony_ci chars_chartab_store); 9298c2ecf20Sopenharmony_cistatic struct kobj_attribute ctl_keys_attribute = 9308c2ecf20Sopenharmony_ci __ATTR(ctl_keys, 0644, message_show, message_store); 9318c2ecf20Sopenharmony_cistatic struct kobj_attribute colors_attribute = 9328c2ecf20Sopenharmony_ci __ATTR(colors, 0644, message_show, message_store); 9338c2ecf20Sopenharmony_cistatic struct kobj_attribute formatted_attribute = 9348c2ecf20Sopenharmony_ci __ATTR(formatted, 0644, message_show, message_store); 9358c2ecf20Sopenharmony_cistatic struct kobj_attribute function_names_attribute = 9368c2ecf20Sopenharmony_ci __ATTR(function_names, 0644, message_show, message_store); 9378c2ecf20Sopenharmony_cistatic struct kobj_attribute key_names_attribute = 9388c2ecf20Sopenharmony_ci __ATTR(key_names, 0644, message_show, message_store); 9398c2ecf20Sopenharmony_cistatic struct kobj_attribute states_attribute = 9408c2ecf20Sopenharmony_ci __ATTR(states, 0644, message_show, message_store); 9418c2ecf20Sopenharmony_ci 9428c2ecf20Sopenharmony_ci/* 9438c2ecf20Sopenharmony_ci * Create groups of attributes so that we can create and destroy them all 9448c2ecf20Sopenharmony_ci * at once. 9458c2ecf20Sopenharmony_ci */ 9468c2ecf20Sopenharmony_cistatic struct attribute *main_attrs[] = { 9478c2ecf20Sopenharmony_ci &keymap_attribute.attr, 9488c2ecf20Sopenharmony_ci &silent_attribute.attr, 9498c2ecf20Sopenharmony_ci &synth_attribute.attr, 9508c2ecf20Sopenharmony_ci &synth_direct_attribute.attr, 9518c2ecf20Sopenharmony_ci &version_attribute.attr, 9528c2ecf20Sopenharmony_ci &delimiters_attribute.attr, 9538c2ecf20Sopenharmony_ci &ex_num_attribute.attr, 9548c2ecf20Sopenharmony_ci &punc_all_attribute.attr, 9558c2ecf20Sopenharmony_ci &punc_most_attribute.attr, 9568c2ecf20Sopenharmony_ci &punc_some_attribute.attr, 9578c2ecf20Sopenharmony_ci &repeats_attribute.attr, 9588c2ecf20Sopenharmony_ci &attrib_bleep_attribute.attr, 9598c2ecf20Sopenharmony_ci &bell_pos_attribute.attr, 9608c2ecf20Sopenharmony_ci &bleep_time_attribute.attr, 9618c2ecf20Sopenharmony_ci &bleeps_attribute.attr, 9628c2ecf20Sopenharmony_ci &cursor_time_attribute.attr, 9638c2ecf20Sopenharmony_ci &key_echo_attribute.attr, 9648c2ecf20Sopenharmony_ci &no_interrupt_attribute.attr, 9658c2ecf20Sopenharmony_ci &punc_level_attribute.attr, 9668c2ecf20Sopenharmony_ci &reading_punc_attribute.attr, 9678c2ecf20Sopenharmony_ci &say_control_attribute.attr, 9688c2ecf20Sopenharmony_ci &say_word_ctl_attribute.attr, 9698c2ecf20Sopenharmony_ci &spell_delay_attribute.attr, 9708c2ecf20Sopenharmony_ci NULL, 9718c2ecf20Sopenharmony_ci}; 9728c2ecf20Sopenharmony_ci 9738c2ecf20Sopenharmony_cistatic struct attribute *i18n_attrs[] = { 9748c2ecf20Sopenharmony_ci &announcements_attribute.attr, 9758c2ecf20Sopenharmony_ci &characters_attribute.attr, 9768c2ecf20Sopenharmony_ci &chartab_attribute.attr, 9778c2ecf20Sopenharmony_ci &ctl_keys_attribute.attr, 9788c2ecf20Sopenharmony_ci &colors_attribute.attr, 9798c2ecf20Sopenharmony_ci &formatted_attribute.attr, 9808c2ecf20Sopenharmony_ci &function_names_attribute.attr, 9818c2ecf20Sopenharmony_ci &key_names_attribute.attr, 9828c2ecf20Sopenharmony_ci &states_attribute.attr, 9838c2ecf20Sopenharmony_ci NULL, 9848c2ecf20Sopenharmony_ci}; 9858c2ecf20Sopenharmony_ci 9868c2ecf20Sopenharmony_ci/* 9878c2ecf20Sopenharmony_ci * An unnamed attribute group will put all of the attributes directly in 9888c2ecf20Sopenharmony_ci * the kobject directory. If we specify a name, a subdirectory will be 9898c2ecf20Sopenharmony_ci * created for the attributes with the directory being the name of the 9908c2ecf20Sopenharmony_ci * attribute group. 9918c2ecf20Sopenharmony_ci */ 9928c2ecf20Sopenharmony_cistatic const struct attribute_group main_attr_group = { 9938c2ecf20Sopenharmony_ci .attrs = main_attrs, 9948c2ecf20Sopenharmony_ci}; 9958c2ecf20Sopenharmony_ci 9968c2ecf20Sopenharmony_cistatic const struct attribute_group i18n_attr_group = { 9978c2ecf20Sopenharmony_ci .attrs = i18n_attrs, 9988c2ecf20Sopenharmony_ci .name = "i18n", 9998c2ecf20Sopenharmony_ci}; 10008c2ecf20Sopenharmony_ci 10018c2ecf20Sopenharmony_cistatic struct kobject *accessibility_kobj; 10028c2ecf20Sopenharmony_cistruct kobject *speakup_kobj; 10038c2ecf20Sopenharmony_ci 10048c2ecf20Sopenharmony_ciint speakup_kobj_init(void) 10058c2ecf20Sopenharmony_ci{ 10068c2ecf20Sopenharmony_ci int retval; 10078c2ecf20Sopenharmony_ci 10088c2ecf20Sopenharmony_ci /* 10098c2ecf20Sopenharmony_ci * Create a simple kobject with the name of "accessibility", 10108c2ecf20Sopenharmony_ci * located under /sys/ 10118c2ecf20Sopenharmony_ci * 10128c2ecf20Sopenharmony_ci * As this is a simple directory, no uevent will be sent to 10138c2ecf20Sopenharmony_ci * userspace. That is why this function should not be used for 10148c2ecf20Sopenharmony_ci * any type of dynamic kobjects, where the name and number are 10158c2ecf20Sopenharmony_ci * not known ahead of time. 10168c2ecf20Sopenharmony_ci */ 10178c2ecf20Sopenharmony_ci accessibility_kobj = kobject_create_and_add("accessibility", NULL); 10188c2ecf20Sopenharmony_ci if (!accessibility_kobj) { 10198c2ecf20Sopenharmony_ci retval = -ENOMEM; 10208c2ecf20Sopenharmony_ci goto out; 10218c2ecf20Sopenharmony_ci } 10228c2ecf20Sopenharmony_ci 10238c2ecf20Sopenharmony_ci speakup_kobj = kobject_create_and_add("speakup", accessibility_kobj); 10248c2ecf20Sopenharmony_ci if (!speakup_kobj) { 10258c2ecf20Sopenharmony_ci retval = -ENOMEM; 10268c2ecf20Sopenharmony_ci goto err_acc; 10278c2ecf20Sopenharmony_ci } 10288c2ecf20Sopenharmony_ci 10298c2ecf20Sopenharmony_ci /* Create the files associated with this kobject */ 10308c2ecf20Sopenharmony_ci retval = sysfs_create_group(speakup_kobj, &main_attr_group); 10318c2ecf20Sopenharmony_ci if (retval) 10328c2ecf20Sopenharmony_ci goto err_speakup; 10338c2ecf20Sopenharmony_ci 10348c2ecf20Sopenharmony_ci retval = sysfs_create_group(speakup_kobj, &i18n_attr_group); 10358c2ecf20Sopenharmony_ci if (retval) 10368c2ecf20Sopenharmony_ci goto err_group; 10378c2ecf20Sopenharmony_ci 10388c2ecf20Sopenharmony_ci goto out; 10398c2ecf20Sopenharmony_ci 10408c2ecf20Sopenharmony_cierr_group: 10418c2ecf20Sopenharmony_ci sysfs_remove_group(speakup_kobj, &main_attr_group); 10428c2ecf20Sopenharmony_cierr_speakup: 10438c2ecf20Sopenharmony_ci kobject_put(speakup_kobj); 10448c2ecf20Sopenharmony_cierr_acc: 10458c2ecf20Sopenharmony_ci kobject_put(accessibility_kobj); 10468c2ecf20Sopenharmony_ciout: 10478c2ecf20Sopenharmony_ci return retval; 10488c2ecf20Sopenharmony_ci} 10498c2ecf20Sopenharmony_ci 10508c2ecf20Sopenharmony_civoid speakup_kobj_exit(void) 10518c2ecf20Sopenharmony_ci{ 10528c2ecf20Sopenharmony_ci sysfs_remove_group(speakup_kobj, &i18n_attr_group); 10538c2ecf20Sopenharmony_ci sysfs_remove_group(speakup_kobj, &main_attr_group); 10548c2ecf20Sopenharmony_ci kobject_put(speakup_kobj); 10558c2ecf20Sopenharmony_ci kobject_put(accessibility_kobj); 10568c2ecf20Sopenharmony_ci} 1057