1*2067fd92SSamuel Thibault // SPDX-License-Identifier: GPL-2.0 2*2067fd92SSamuel Thibault /* 3*2067fd92SSamuel Thibault * Speakup kobject implementation 4*2067fd92SSamuel Thibault * 5*2067fd92SSamuel Thibault * Copyright (C) 2009 William Hubbs 6*2067fd92SSamuel Thibault * 7*2067fd92SSamuel Thibault * This code is based on kobject-example.c, which came with linux 2.6.x. 8*2067fd92SSamuel Thibault * 9*2067fd92SSamuel Thibault * Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com> 10*2067fd92SSamuel Thibault * Copyright (C) 2007 Novell Inc. 11*2067fd92SSamuel Thibault * 12*2067fd92SSamuel Thibault * Released under the GPL version 2 only. 13*2067fd92SSamuel Thibault * 14*2067fd92SSamuel Thibault */ 15*2067fd92SSamuel Thibault #include <linux/slab.h> /* For kmalloc. */ 16*2067fd92SSamuel Thibault #include <linux/kernel.h> 17*2067fd92SSamuel Thibault #include <linux/kobject.h> 18*2067fd92SSamuel Thibault #include <linux/string.h> 19*2067fd92SSamuel Thibault #include <linux/string_helpers.h> 20*2067fd92SSamuel Thibault #include <linux/sysfs.h> 21*2067fd92SSamuel Thibault #include <linux/ctype.h> 22*2067fd92SSamuel Thibault 23*2067fd92SSamuel Thibault #include "speakup.h" 24*2067fd92SSamuel Thibault #include "spk_priv.h" 25*2067fd92SSamuel Thibault 26*2067fd92SSamuel Thibault /* 27*2067fd92SSamuel Thibault * This is called when a user reads the characters or chartab sys file. 28*2067fd92SSamuel Thibault */ 29*2067fd92SSamuel Thibault static ssize_t chars_chartab_show(struct kobject *kobj, 30*2067fd92SSamuel Thibault struct kobj_attribute *attr, char *buf) 31*2067fd92SSamuel Thibault { 32*2067fd92SSamuel Thibault int i; 33*2067fd92SSamuel Thibault int len = 0; 34*2067fd92SSamuel Thibault char *cp; 35*2067fd92SSamuel Thibault char *buf_pointer = buf; 36*2067fd92SSamuel Thibault size_t bufsize = PAGE_SIZE; 37*2067fd92SSamuel Thibault unsigned long flags; 38*2067fd92SSamuel Thibault 39*2067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 40*2067fd92SSamuel Thibault *buf_pointer = '\0'; 41*2067fd92SSamuel Thibault for (i = 0; i < 256; i++) { 42*2067fd92SSamuel Thibault if (bufsize <= 1) 43*2067fd92SSamuel Thibault break; 44*2067fd92SSamuel Thibault if (strcmp("characters", attr->attr.name) == 0) { 45*2067fd92SSamuel Thibault len = scnprintf(buf_pointer, bufsize, "%d\t%s\n", 46*2067fd92SSamuel Thibault i, spk_characters[i]); 47*2067fd92SSamuel Thibault } else { /* show chartab entry */ 48*2067fd92SSamuel Thibault if (IS_TYPE(i, B_CTL)) 49*2067fd92SSamuel Thibault cp = "B_CTL"; 50*2067fd92SSamuel Thibault else if (IS_TYPE(i, WDLM)) 51*2067fd92SSamuel Thibault cp = "WDLM"; 52*2067fd92SSamuel Thibault else if (IS_TYPE(i, A_PUNC)) 53*2067fd92SSamuel Thibault cp = "A_PUNC"; 54*2067fd92SSamuel Thibault else if (IS_TYPE(i, PUNC)) 55*2067fd92SSamuel Thibault cp = "PUNC"; 56*2067fd92SSamuel Thibault else if (IS_TYPE(i, NUM)) 57*2067fd92SSamuel Thibault cp = "NUM"; 58*2067fd92SSamuel Thibault else if (IS_TYPE(i, A_CAP)) 59*2067fd92SSamuel Thibault cp = "A_CAP"; 60*2067fd92SSamuel Thibault else if (IS_TYPE(i, ALPHA)) 61*2067fd92SSamuel Thibault cp = "ALPHA"; 62*2067fd92SSamuel Thibault else if (IS_TYPE(i, B_CAPSYM)) 63*2067fd92SSamuel Thibault cp = "B_CAPSYM"; 64*2067fd92SSamuel Thibault else if (IS_TYPE(i, B_SYM)) 65*2067fd92SSamuel Thibault cp = "B_SYM"; 66*2067fd92SSamuel Thibault else 67*2067fd92SSamuel Thibault cp = "0"; 68*2067fd92SSamuel Thibault len = 69*2067fd92SSamuel Thibault scnprintf(buf_pointer, bufsize, "%d\t%s\n", i, cp); 70*2067fd92SSamuel Thibault } 71*2067fd92SSamuel Thibault bufsize -= len; 72*2067fd92SSamuel Thibault buf_pointer += len; 73*2067fd92SSamuel Thibault } 74*2067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 75*2067fd92SSamuel Thibault return buf_pointer - buf; 76*2067fd92SSamuel Thibault } 77*2067fd92SSamuel Thibault 78*2067fd92SSamuel Thibault /* 79*2067fd92SSamuel Thibault * Print informational messages or warnings after updating 80*2067fd92SSamuel Thibault * character descriptions or chartab entries. 81*2067fd92SSamuel Thibault */ 82*2067fd92SSamuel Thibault static void report_char_chartab_status(int reset, int received, int used, 83*2067fd92SSamuel Thibault int rejected, int do_characters) 84*2067fd92SSamuel Thibault { 85*2067fd92SSamuel Thibault static char const *object_type[] = { 86*2067fd92SSamuel Thibault "character class entries", 87*2067fd92SSamuel Thibault "character descriptions", 88*2067fd92SSamuel Thibault }; 89*2067fd92SSamuel Thibault int len; 90*2067fd92SSamuel Thibault char buf[80]; 91*2067fd92SSamuel Thibault 92*2067fd92SSamuel Thibault if (reset) { 93*2067fd92SSamuel Thibault pr_info("%s reset to defaults\n", object_type[do_characters]); 94*2067fd92SSamuel Thibault } else if (received) { 95*2067fd92SSamuel Thibault len = snprintf(buf, sizeof(buf), 96*2067fd92SSamuel Thibault " updated %d of %d %s\n", 97*2067fd92SSamuel Thibault used, received, object_type[do_characters]); 98*2067fd92SSamuel Thibault if (rejected) 99*2067fd92SSamuel Thibault snprintf(buf + (len - 1), sizeof(buf) - (len - 1), 100*2067fd92SSamuel Thibault " with %d reject%s\n", 101*2067fd92SSamuel Thibault rejected, rejected > 1 ? "s" : ""); 102*2067fd92SSamuel Thibault pr_info("%s", buf); 103*2067fd92SSamuel Thibault } 104*2067fd92SSamuel Thibault } 105*2067fd92SSamuel Thibault 106*2067fd92SSamuel Thibault /* 107*2067fd92SSamuel Thibault * This is called when a user changes the characters or chartab parameters. 108*2067fd92SSamuel Thibault */ 109*2067fd92SSamuel Thibault static ssize_t chars_chartab_store(struct kobject *kobj, 110*2067fd92SSamuel Thibault struct kobj_attribute *attr, 111*2067fd92SSamuel Thibault const char *buf, size_t count) 112*2067fd92SSamuel Thibault { 113*2067fd92SSamuel Thibault char *cp = (char *)buf; 114*2067fd92SSamuel Thibault char *end = cp + count; /* the null at the end of the buffer */ 115*2067fd92SSamuel Thibault char *linefeed = NULL; 116*2067fd92SSamuel Thibault char keyword[MAX_DESC_LEN + 1]; 117*2067fd92SSamuel Thibault char *outptr = NULL; /* Will hold keyword or desc. */ 118*2067fd92SSamuel Thibault char *temp = NULL; 119*2067fd92SSamuel Thibault char *desc = NULL; 120*2067fd92SSamuel Thibault ssize_t retval = count; 121*2067fd92SSamuel Thibault unsigned long flags; 122*2067fd92SSamuel Thibault unsigned long index = 0; 123*2067fd92SSamuel Thibault int charclass = 0; 124*2067fd92SSamuel Thibault int received = 0; 125*2067fd92SSamuel Thibault int used = 0; 126*2067fd92SSamuel Thibault int rejected = 0; 127*2067fd92SSamuel Thibault int reset = 0; 128*2067fd92SSamuel Thibault int do_characters = !strcmp(attr->attr.name, "characters"); 129*2067fd92SSamuel Thibault size_t desc_length = 0; 130*2067fd92SSamuel Thibault int i; 131*2067fd92SSamuel Thibault 132*2067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 133*2067fd92SSamuel Thibault while (cp < end) { 134*2067fd92SSamuel Thibault while ((cp < end) && (*cp == ' ' || *cp == '\t')) 135*2067fd92SSamuel Thibault cp++; 136*2067fd92SSamuel Thibault 137*2067fd92SSamuel Thibault if (cp == end) 138*2067fd92SSamuel Thibault break; 139*2067fd92SSamuel Thibault if ((*cp == '\n') || strchr("dDrR", *cp)) { 140*2067fd92SSamuel Thibault reset = 1; 141*2067fd92SSamuel Thibault break; 142*2067fd92SSamuel Thibault } 143*2067fd92SSamuel Thibault received++; 144*2067fd92SSamuel Thibault 145*2067fd92SSamuel Thibault linefeed = strchr(cp, '\n'); 146*2067fd92SSamuel Thibault if (!linefeed) { 147*2067fd92SSamuel Thibault rejected++; 148*2067fd92SSamuel Thibault break; 149*2067fd92SSamuel Thibault } 150*2067fd92SSamuel Thibault 151*2067fd92SSamuel Thibault if (!isdigit(*cp)) { 152*2067fd92SSamuel Thibault rejected++; 153*2067fd92SSamuel Thibault cp = linefeed + 1; 154*2067fd92SSamuel Thibault continue; 155*2067fd92SSamuel Thibault } 156*2067fd92SSamuel Thibault 157*2067fd92SSamuel Thibault /* 158*2067fd92SSamuel Thibault * Do not replace with kstrtoul: 159*2067fd92SSamuel Thibault * here we need temp to be updated 160*2067fd92SSamuel Thibault */ 161*2067fd92SSamuel Thibault index = simple_strtoul(cp, &temp, 10); 162*2067fd92SSamuel Thibault if (index > 255) { 163*2067fd92SSamuel Thibault rejected++; 164*2067fd92SSamuel Thibault cp = linefeed + 1; 165*2067fd92SSamuel Thibault continue; 166*2067fd92SSamuel Thibault } 167*2067fd92SSamuel Thibault 168*2067fd92SSamuel Thibault while ((temp < linefeed) && (*temp == ' ' || *temp == '\t')) 169*2067fd92SSamuel Thibault temp++; 170*2067fd92SSamuel Thibault 171*2067fd92SSamuel Thibault desc_length = linefeed - temp; 172*2067fd92SSamuel Thibault if (desc_length > MAX_DESC_LEN) { 173*2067fd92SSamuel Thibault rejected++; 174*2067fd92SSamuel Thibault cp = linefeed + 1; 175*2067fd92SSamuel Thibault continue; 176*2067fd92SSamuel Thibault } 177*2067fd92SSamuel Thibault if (do_characters) { 178*2067fd92SSamuel Thibault desc = kmalloc(desc_length + 1, GFP_ATOMIC); 179*2067fd92SSamuel Thibault if (!desc) { 180*2067fd92SSamuel Thibault retval = -ENOMEM; 181*2067fd92SSamuel Thibault reset = 1; /* just reset on error. */ 182*2067fd92SSamuel Thibault break; 183*2067fd92SSamuel Thibault } 184*2067fd92SSamuel Thibault outptr = desc; 185*2067fd92SSamuel Thibault } else { 186*2067fd92SSamuel Thibault outptr = keyword; 187*2067fd92SSamuel Thibault } 188*2067fd92SSamuel Thibault 189*2067fd92SSamuel Thibault for (i = 0; i < desc_length; i++) 190*2067fd92SSamuel Thibault outptr[i] = temp[i]; 191*2067fd92SSamuel Thibault outptr[desc_length] = '\0'; 192*2067fd92SSamuel Thibault 193*2067fd92SSamuel Thibault if (do_characters) { 194*2067fd92SSamuel Thibault if (spk_characters[index] != spk_default_chars[index]) 195*2067fd92SSamuel Thibault kfree(spk_characters[index]); 196*2067fd92SSamuel Thibault spk_characters[index] = desc; 197*2067fd92SSamuel Thibault used++; 198*2067fd92SSamuel Thibault } else { 199*2067fd92SSamuel Thibault charclass = spk_chartab_get_value(keyword); 200*2067fd92SSamuel Thibault if (charclass == 0) { 201*2067fd92SSamuel Thibault rejected++; 202*2067fd92SSamuel Thibault cp = linefeed + 1; 203*2067fd92SSamuel Thibault continue; 204*2067fd92SSamuel Thibault } 205*2067fd92SSamuel Thibault if (charclass != spk_chartab[index]) { 206*2067fd92SSamuel Thibault spk_chartab[index] = charclass; 207*2067fd92SSamuel Thibault used++; 208*2067fd92SSamuel Thibault } 209*2067fd92SSamuel Thibault } 210*2067fd92SSamuel Thibault cp = linefeed + 1; 211*2067fd92SSamuel Thibault } 212*2067fd92SSamuel Thibault 213*2067fd92SSamuel Thibault if (reset) { 214*2067fd92SSamuel Thibault if (do_characters) 215*2067fd92SSamuel Thibault spk_reset_default_chars(); 216*2067fd92SSamuel Thibault else 217*2067fd92SSamuel Thibault spk_reset_default_chartab(); 218*2067fd92SSamuel Thibault } 219*2067fd92SSamuel Thibault 220*2067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 221*2067fd92SSamuel Thibault report_char_chartab_status(reset, received, used, rejected, 222*2067fd92SSamuel Thibault do_characters); 223*2067fd92SSamuel Thibault return retval; 224*2067fd92SSamuel Thibault } 225*2067fd92SSamuel Thibault 226*2067fd92SSamuel Thibault /* 227*2067fd92SSamuel Thibault * This is called when a user reads the keymap parameter. 228*2067fd92SSamuel Thibault */ 229*2067fd92SSamuel Thibault static ssize_t keymap_show(struct kobject *kobj, struct kobj_attribute *attr, 230*2067fd92SSamuel Thibault char *buf) 231*2067fd92SSamuel Thibault { 232*2067fd92SSamuel Thibault char *cp = buf; 233*2067fd92SSamuel Thibault int i; 234*2067fd92SSamuel Thibault int n; 235*2067fd92SSamuel Thibault int num_keys; 236*2067fd92SSamuel Thibault int nstates; 237*2067fd92SSamuel Thibault u_char *cp1; 238*2067fd92SSamuel Thibault u_char ch; 239*2067fd92SSamuel Thibault unsigned long flags; 240*2067fd92SSamuel Thibault 241*2067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 242*2067fd92SSamuel Thibault cp1 = spk_key_buf + SHIFT_TBL_SIZE; 243*2067fd92SSamuel Thibault num_keys = (int)(*cp1); 244*2067fd92SSamuel Thibault nstates = (int)cp1[1]; 245*2067fd92SSamuel Thibault cp += sprintf(cp, "%d, %d, %d,\n", KEY_MAP_VER, num_keys, nstates); 246*2067fd92SSamuel Thibault cp1 += 2; /* now pointing at shift states */ 247*2067fd92SSamuel Thibault /* dump num_keys+1 as first row is shift states + flags, 248*2067fd92SSamuel Thibault * each subsequent row is key + states 249*2067fd92SSamuel Thibault */ 250*2067fd92SSamuel Thibault for (n = 0; n <= num_keys; n++) { 251*2067fd92SSamuel Thibault for (i = 0; i <= nstates; i++) { 252*2067fd92SSamuel Thibault ch = *cp1++; 253*2067fd92SSamuel Thibault cp += sprintf(cp, "%d,", (int)ch); 254*2067fd92SSamuel Thibault *cp++ = (i < nstates) ? SPACE : '\n'; 255*2067fd92SSamuel Thibault } 256*2067fd92SSamuel Thibault } 257*2067fd92SSamuel Thibault cp += sprintf(cp, "0, %d\n", KEY_MAP_VER); 258*2067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 259*2067fd92SSamuel Thibault return (int)(cp - buf); 260*2067fd92SSamuel Thibault } 261*2067fd92SSamuel Thibault 262*2067fd92SSamuel Thibault /* 263*2067fd92SSamuel Thibault * This is called when a user changes the keymap parameter. 264*2067fd92SSamuel Thibault */ 265*2067fd92SSamuel Thibault static ssize_t keymap_store(struct kobject *kobj, struct kobj_attribute *attr, 266*2067fd92SSamuel Thibault const char *buf, size_t count) 267*2067fd92SSamuel Thibault { 268*2067fd92SSamuel Thibault int i; 269*2067fd92SSamuel Thibault ssize_t ret = count; 270*2067fd92SSamuel Thibault char *in_buff = NULL; 271*2067fd92SSamuel Thibault char *cp; 272*2067fd92SSamuel Thibault u_char *cp1; 273*2067fd92SSamuel Thibault unsigned long flags; 274*2067fd92SSamuel Thibault 275*2067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 276*2067fd92SSamuel Thibault in_buff = kmemdup(buf, count + 1, GFP_ATOMIC); 277*2067fd92SSamuel Thibault if (!in_buff) { 278*2067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 279*2067fd92SSamuel Thibault return -ENOMEM; 280*2067fd92SSamuel Thibault } 281*2067fd92SSamuel Thibault if (strchr("dDrR", *in_buff)) { 282*2067fd92SSamuel Thibault spk_set_key_info(spk_key_defaults, spk_key_buf); 283*2067fd92SSamuel Thibault pr_info("keymap set to default values\n"); 284*2067fd92SSamuel Thibault kfree(in_buff); 285*2067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 286*2067fd92SSamuel Thibault return count; 287*2067fd92SSamuel Thibault } 288*2067fd92SSamuel Thibault if (in_buff[count - 1] == '\n') 289*2067fd92SSamuel Thibault in_buff[count - 1] = '\0'; 290*2067fd92SSamuel Thibault cp = in_buff; 291*2067fd92SSamuel Thibault cp1 = (u_char *)in_buff; 292*2067fd92SSamuel Thibault for (i = 0; i < 3; i++) { 293*2067fd92SSamuel Thibault cp = spk_s2uchar(cp, cp1); 294*2067fd92SSamuel Thibault cp1++; 295*2067fd92SSamuel Thibault } 296*2067fd92SSamuel Thibault i = (int)cp1[-2] + 1; 297*2067fd92SSamuel Thibault i *= (int)cp1[-1] + 1; 298*2067fd92SSamuel Thibault i += 2; /* 0 and last map ver */ 299*2067fd92SSamuel Thibault if (cp1[-3] != KEY_MAP_VER || cp1[-1] > 10 || 300*2067fd92SSamuel Thibault i + SHIFT_TBL_SIZE + 4 >= sizeof(spk_key_buf)) { 301*2067fd92SSamuel Thibault pr_warn("i %d %d %d %d\n", i, 302*2067fd92SSamuel Thibault (int)cp1[-3], (int)cp1[-2], (int)cp1[-1]); 303*2067fd92SSamuel Thibault kfree(in_buff); 304*2067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 305*2067fd92SSamuel Thibault return -EINVAL; 306*2067fd92SSamuel Thibault } 307*2067fd92SSamuel Thibault while (--i >= 0) { 308*2067fd92SSamuel Thibault cp = spk_s2uchar(cp, cp1); 309*2067fd92SSamuel Thibault cp1++; 310*2067fd92SSamuel Thibault if (!(*cp)) 311*2067fd92SSamuel Thibault break; 312*2067fd92SSamuel Thibault } 313*2067fd92SSamuel Thibault if (i != 0 || cp1[-1] != KEY_MAP_VER || cp1[-2] != 0) { 314*2067fd92SSamuel Thibault ret = -EINVAL; 315*2067fd92SSamuel Thibault pr_warn("end %d %d %d %d\n", i, 316*2067fd92SSamuel Thibault (int)cp1[-3], (int)cp1[-2], (int)cp1[-1]); 317*2067fd92SSamuel Thibault } else { 318*2067fd92SSamuel Thibault if (spk_set_key_info(in_buff, spk_key_buf)) { 319*2067fd92SSamuel Thibault spk_set_key_info(spk_key_defaults, spk_key_buf); 320*2067fd92SSamuel Thibault ret = -EINVAL; 321*2067fd92SSamuel Thibault pr_warn("set key failed\n"); 322*2067fd92SSamuel Thibault } 323*2067fd92SSamuel Thibault } 324*2067fd92SSamuel Thibault kfree(in_buff); 325*2067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 326*2067fd92SSamuel Thibault return ret; 327*2067fd92SSamuel Thibault } 328*2067fd92SSamuel Thibault 329*2067fd92SSamuel Thibault /* 330*2067fd92SSamuel Thibault * This is called when a user changes the value of the silent parameter. 331*2067fd92SSamuel Thibault */ 332*2067fd92SSamuel Thibault static ssize_t silent_store(struct kobject *kobj, struct kobj_attribute *attr, 333*2067fd92SSamuel Thibault const char *buf, size_t count) 334*2067fd92SSamuel Thibault { 335*2067fd92SSamuel Thibault int len; 336*2067fd92SSamuel Thibault struct vc_data *vc = vc_cons[fg_console].d; 337*2067fd92SSamuel Thibault char ch = 0; 338*2067fd92SSamuel Thibault char shut; 339*2067fd92SSamuel Thibault unsigned long flags; 340*2067fd92SSamuel Thibault 341*2067fd92SSamuel Thibault len = strlen(buf); 342*2067fd92SSamuel Thibault if (len > 0 && len < 3) { 343*2067fd92SSamuel Thibault ch = buf[0]; 344*2067fd92SSamuel Thibault if (ch == '\n') 345*2067fd92SSamuel Thibault ch = '0'; 346*2067fd92SSamuel Thibault } 347*2067fd92SSamuel Thibault if (ch < '0' || ch > '7') { 348*2067fd92SSamuel Thibault pr_warn("silent value '%c' not in range (0,7)\n", ch); 349*2067fd92SSamuel Thibault return -EINVAL; 350*2067fd92SSamuel Thibault } 351*2067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 352*2067fd92SSamuel Thibault if (ch & 2) { 353*2067fd92SSamuel Thibault shut = 1; 354*2067fd92SSamuel Thibault spk_do_flush(); 355*2067fd92SSamuel Thibault } else { 356*2067fd92SSamuel Thibault shut = 0; 357*2067fd92SSamuel Thibault } 358*2067fd92SSamuel Thibault if (ch & 4) 359*2067fd92SSamuel Thibault shut |= 0x40; 360*2067fd92SSamuel Thibault if (ch & 1) 361*2067fd92SSamuel Thibault spk_shut_up |= shut; 362*2067fd92SSamuel Thibault else 363*2067fd92SSamuel Thibault spk_shut_up &= ~shut; 364*2067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 365*2067fd92SSamuel Thibault return count; 366*2067fd92SSamuel Thibault } 367*2067fd92SSamuel Thibault 368*2067fd92SSamuel Thibault /* 369*2067fd92SSamuel Thibault * This is called when a user reads the synth setting. 370*2067fd92SSamuel Thibault */ 371*2067fd92SSamuel Thibault static ssize_t synth_show(struct kobject *kobj, struct kobj_attribute *attr, 372*2067fd92SSamuel Thibault char *buf) 373*2067fd92SSamuel Thibault { 374*2067fd92SSamuel Thibault int rv; 375*2067fd92SSamuel Thibault 376*2067fd92SSamuel Thibault if (!synth) 377*2067fd92SSamuel Thibault rv = sprintf(buf, "%s\n", "none"); 378*2067fd92SSamuel Thibault else 379*2067fd92SSamuel Thibault rv = sprintf(buf, "%s\n", synth->name); 380*2067fd92SSamuel Thibault return rv; 381*2067fd92SSamuel Thibault } 382*2067fd92SSamuel Thibault 383*2067fd92SSamuel Thibault /* 384*2067fd92SSamuel Thibault * This is called when a user requests to change synthesizers. 385*2067fd92SSamuel Thibault */ 386*2067fd92SSamuel Thibault static ssize_t synth_store(struct kobject *kobj, struct kobj_attribute *attr, 387*2067fd92SSamuel Thibault const char *buf, size_t count) 388*2067fd92SSamuel Thibault { 389*2067fd92SSamuel Thibault int len; 390*2067fd92SSamuel Thibault char new_synth_name[10]; 391*2067fd92SSamuel Thibault 392*2067fd92SSamuel Thibault len = strlen(buf); 393*2067fd92SSamuel Thibault if (len < 2 || len > 9) 394*2067fd92SSamuel Thibault return -EINVAL; 395*2067fd92SSamuel Thibault memcpy(new_synth_name, buf, len); 396*2067fd92SSamuel Thibault if (new_synth_name[len - 1] == '\n') 397*2067fd92SSamuel Thibault len--; 398*2067fd92SSamuel Thibault new_synth_name[len] = '\0'; 399*2067fd92SSamuel Thibault spk_strlwr(new_synth_name); 400*2067fd92SSamuel Thibault if (synth && !strcmp(new_synth_name, synth->name)) { 401*2067fd92SSamuel Thibault pr_warn("%s already in use\n", new_synth_name); 402*2067fd92SSamuel Thibault } else if (synth_init(new_synth_name) != 0) { 403*2067fd92SSamuel Thibault pr_warn("failed to init synth %s\n", new_synth_name); 404*2067fd92SSamuel Thibault return -ENODEV; 405*2067fd92SSamuel Thibault } 406*2067fd92SSamuel Thibault return count; 407*2067fd92SSamuel Thibault } 408*2067fd92SSamuel Thibault 409*2067fd92SSamuel Thibault /* 410*2067fd92SSamuel Thibault * This is called when text is sent to the synth via the synth_direct file. 411*2067fd92SSamuel Thibault */ 412*2067fd92SSamuel Thibault static ssize_t synth_direct_store(struct kobject *kobj, 413*2067fd92SSamuel Thibault struct kobj_attribute *attr, 414*2067fd92SSamuel Thibault const char *buf, size_t count) 415*2067fd92SSamuel Thibault { 416*2067fd92SSamuel Thibault u_char tmp[256]; 417*2067fd92SSamuel Thibault int len; 418*2067fd92SSamuel Thibault int bytes; 419*2067fd92SSamuel Thibault const char *ptr = buf; 420*2067fd92SSamuel Thibault unsigned long flags; 421*2067fd92SSamuel Thibault 422*2067fd92SSamuel Thibault if (!synth) 423*2067fd92SSamuel Thibault return -EPERM; 424*2067fd92SSamuel Thibault 425*2067fd92SSamuel Thibault len = strlen(buf); 426*2067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 427*2067fd92SSamuel Thibault while (len > 0) { 428*2067fd92SSamuel Thibault bytes = min_t(size_t, len, 250); 429*2067fd92SSamuel Thibault strncpy(tmp, ptr, bytes); 430*2067fd92SSamuel Thibault tmp[bytes] = '\0'; 431*2067fd92SSamuel Thibault string_unescape_any_inplace(tmp); 432*2067fd92SSamuel Thibault synth_printf("%s", tmp); 433*2067fd92SSamuel Thibault ptr += bytes; 434*2067fd92SSamuel Thibault len -= bytes; 435*2067fd92SSamuel Thibault } 436*2067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 437*2067fd92SSamuel Thibault return count; 438*2067fd92SSamuel Thibault } 439*2067fd92SSamuel Thibault 440*2067fd92SSamuel Thibault /* 441*2067fd92SSamuel Thibault * This function is called when a user reads the version. 442*2067fd92SSamuel Thibault */ 443*2067fd92SSamuel Thibault static ssize_t version_show(struct kobject *kobj, struct kobj_attribute *attr, 444*2067fd92SSamuel Thibault char *buf) 445*2067fd92SSamuel Thibault { 446*2067fd92SSamuel Thibault char *cp; 447*2067fd92SSamuel Thibault 448*2067fd92SSamuel Thibault cp = buf; 449*2067fd92SSamuel Thibault cp += sprintf(cp, "Speakup version %s\n", SPEAKUP_VERSION); 450*2067fd92SSamuel Thibault if (synth) 451*2067fd92SSamuel Thibault cp += sprintf(cp, "%s synthesizer driver version %s\n", 452*2067fd92SSamuel Thibault synth->name, synth->version); 453*2067fd92SSamuel Thibault return cp - buf; 454*2067fd92SSamuel Thibault } 455*2067fd92SSamuel Thibault 456*2067fd92SSamuel Thibault /* 457*2067fd92SSamuel Thibault * This is called when a user reads the punctuation settings. 458*2067fd92SSamuel Thibault */ 459*2067fd92SSamuel Thibault static ssize_t punc_show(struct kobject *kobj, struct kobj_attribute *attr, 460*2067fd92SSamuel Thibault char *buf) 461*2067fd92SSamuel Thibault { 462*2067fd92SSamuel Thibault int i; 463*2067fd92SSamuel Thibault char *cp = buf; 464*2067fd92SSamuel Thibault struct st_var_header *p_header; 465*2067fd92SSamuel Thibault struct punc_var_t *var; 466*2067fd92SSamuel Thibault struct st_bits_data *pb; 467*2067fd92SSamuel Thibault short mask; 468*2067fd92SSamuel Thibault unsigned long flags; 469*2067fd92SSamuel Thibault 470*2067fd92SSamuel Thibault p_header = spk_var_header_by_name(attr->attr.name); 471*2067fd92SSamuel Thibault if (!p_header) { 472*2067fd92SSamuel Thibault pr_warn("p_header is null, attr->attr.name is %s\n", 473*2067fd92SSamuel Thibault attr->attr.name); 474*2067fd92SSamuel Thibault return -EINVAL; 475*2067fd92SSamuel Thibault } 476*2067fd92SSamuel Thibault 477*2067fd92SSamuel Thibault var = spk_get_punc_var(p_header->var_id); 478*2067fd92SSamuel Thibault if (!var) { 479*2067fd92SSamuel Thibault pr_warn("var is null, p_header->var_id is %i\n", 480*2067fd92SSamuel Thibault p_header->var_id); 481*2067fd92SSamuel Thibault return -EINVAL; 482*2067fd92SSamuel Thibault } 483*2067fd92SSamuel Thibault 484*2067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 485*2067fd92SSamuel Thibault pb = (struct st_bits_data *)&spk_punc_info[var->value]; 486*2067fd92SSamuel Thibault mask = pb->mask; 487*2067fd92SSamuel Thibault for (i = 33; i < 128; i++) { 488*2067fd92SSamuel Thibault if (!(spk_chartab[i] & mask)) 489*2067fd92SSamuel Thibault continue; 490*2067fd92SSamuel Thibault *cp++ = (char)i; 491*2067fd92SSamuel Thibault } 492*2067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 493*2067fd92SSamuel Thibault return cp - buf; 494*2067fd92SSamuel Thibault } 495*2067fd92SSamuel Thibault 496*2067fd92SSamuel Thibault /* 497*2067fd92SSamuel Thibault * This is called when a user changes the punctuation settings. 498*2067fd92SSamuel Thibault */ 499*2067fd92SSamuel Thibault static ssize_t punc_store(struct kobject *kobj, struct kobj_attribute *attr, 500*2067fd92SSamuel Thibault const char *buf, size_t count) 501*2067fd92SSamuel Thibault { 502*2067fd92SSamuel Thibault int x; 503*2067fd92SSamuel Thibault struct st_var_header *p_header; 504*2067fd92SSamuel Thibault struct punc_var_t *var; 505*2067fd92SSamuel Thibault char punc_buf[100]; 506*2067fd92SSamuel Thibault unsigned long flags; 507*2067fd92SSamuel Thibault 508*2067fd92SSamuel Thibault x = strlen(buf); 509*2067fd92SSamuel Thibault if (x < 1 || x > 99) 510*2067fd92SSamuel Thibault return -EINVAL; 511*2067fd92SSamuel Thibault 512*2067fd92SSamuel Thibault p_header = spk_var_header_by_name(attr->attr.name); 513*2067fd92SSamuel Thibault if (!p_header) { 514*2067fd92SSamuel Thibault pr_warn("p_header is null, attr->attr.name is %s\n", 515*2067fd92SSamuel Thibault attr->attr.name); 516*2067fd92SSamuel Thibault return -EINVAL; 517*2067fd92SSamuel Thibault } 518*2067fd92SSamuel Thibault 519*2067fd92SSamuel Thibault var = spk_get_punc_var(p_header->var_id); 520*2067fd92SSamuel Thibault if (!var) { 521*2067fd92SSamuel Thibault pr_warn("var is null, p_header->var_id is %i\n", 522*2067fd92SSamuel Thibault p_header->var_id); 523*2067fd92SSamuel Thibault return -EINVAL; 524*2067fd92SSamuel Thibault } 525*2067fd92SSamuel Thibault 526*2067fd92SSamuel Thibault memcpy(punc_buf, buf, x); 527*2067fd92SSamuel Thibault 528*2067fd92SSamuel Thibault while (x && punc_buf[x - 1] == '\n') 529*2067fd92SSamuel Thibault x--; 530*2067fd92SSamuel Thibault punc_buf[x] = '\0'; 531*2067fd92SSamuel Thibault 532*2067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 533*2067fd92SSamuel Thibault 534*2067fd92SSamuel Thibault if (*punc_buf == 'd' || *punc_buf == 'r') 535*2067fd92SSamuel Thibault x = spk_set_mask_bits(NULL, var->value, 3); 536*2067fd92SSamuel Thibault else 537*2067fd92SSamuel Thibault x = spk_set_mask_bits(punc_buf, var->value, 3); 538*2067fd92SSamuel Thibault 539*2067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 540*2067fd92SSamuel Thibault return count; 541*2067fd92SSamuel Thibault } 542*2067fd92SSamuel Thibault 543*2067fd92SSamuel Thibault /* 544*2067fd92SSamuel Thibault * This function is called when a user reads one of the variable parameters. 545*2067fd92SSamuel Thibault */ 546*2067fd92SSamuel Thibault ssize_t spk_var_show(struct kobject *kobj, struct kobj_attribute *attr, 547*2067fd92SSamuel Thibault char *buf) 548*2067fd92SSamuel Thibault { 549*2067fd92SSamuel Thibault int rv = 0; 550*2067fd92SSamuel Thibault struct st_var_header *param; 551*2067fd92SSamuel Thibault struct var_t *var; 552*2067fd92SSamuel Thibault char *cp1; 553*2067fd92SSamuel Thibault char *cp; 554*2067fd92SSamuel Thibault char ch; 555*2067fd92SSamuel Thibault unsigned long flags; 556*2067fd92SSamuel Thibault 557*2067fd92SSamuel Thibault param = spk_var_header_by_name(attr->attr.name); 558*2067fd92SSamuel Thibault if (!param) 559*2067fd92SSamuel Thibault return -EINVAL; 560*2067fd92SSamuel Thibault 561*2067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 562*2067fd92SSamuel Thibault var = (struct var_t *)param->data; 563*2067fd92SSamuel Thibault switch (param->var_type) { 564*2067fd92SSamuel Thibault case VAR_NUM: 565*2067fd92SSamuel Thibault case VAR_TIME: 566*2067fd92SSamuel Thibault if (var) 567*2067fd92SSamuel Thibault rv = sprintf(buf, "%i\n", var->u.n.value); 568*2067fd92SSamuel Thibault else 569*2067fd92SSamuel Thibault rv = sprintf(buf, "0\n"); 570*2067fd92SSamuel Thibault break; 571*2067fd92SSamuel Thibault case VAR_STRING: 572*2067fd92SSamuel Thibault if (var) { 573*2067fd92SSamuel Thibault cp1 = buf; 574*2067fd92SSamuel Thibault *cp1++ = '"'; 575*2067fd92SSamuel Thibault for (cp = (char *)param->p_val; (ch = *cp); cp++) { 576*2067fd92SSamuel Thibault if (ch >= ' ' && ch < '~') 577*2067fd92SSamuel Thibault *cp1++ = ch; 578*2067fd92SSamuel Thibault else 579*2067fd92SSamuel Thibault cp1 += sprintf(cp1, "\\x%02x", ch); 580*2067fd92SSamuel Thibault } 581*2067fd92SSamuel Thibault *cp1++ = '"'; 582*2067fd92SSamuel Thibault *cp1++ = '\n'; 583*2067fd92SSamuel Thibault *cp1 = '\0'; 584*2067fd92SSamuel Thibault rv = cp1 - buf; 585*2067fd92SSamuel Thibault } else { 586*2067fd92SSamuel Thibault rv = sprintf(buf, "\"\"\n"); 587*2067fd92SSamuel Thibault } 588*2067fd92SSamuel Thibault break; 589*2067fd92SSamuel Thibault default: 590*2067fd92SSamuel Thibault rv = sprintf(buf, "Bad parameter %s, type %i\n", 591*2067fd92SSamuel Thibault param->name, param->var_type); 592*2067fd92SSamuel Thibault break; 593*2067fd92SSamuel Thibault } 594*2067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 595*2067fd92SSamuel Thibault return rv; 596*2067fd92SSamuel Thibault } 597*2067fd92SSamuel Thibault EXPORT_SYMBOL_GPL(spk_var_show); 598*2067fd92SSamuel Thibault 599*2067fd92SSamuel Thibault /* 600*2067fd92SSamuel Thibault * Used to reset either default_pitch or default_vol. 601*2067fd92SSamuel Thibault */ 602*2067fd92SSamuel Thibault static inline void spk_reset_default_value(char *header_name, 603*2067fd92SSamuel Thibault int *synth_default_value, int idx) 604*2067fd92SSamuel Thibault { 605*2067fd92SSamuel Thibault struct st_var_header *param; 606*2067fd92SSamuel Thibault 607*2067fd92SSamuel Thibault if (synth && synth_default_value) { 608*2067fd92SSamuel Thibault param = spk_var_header_by_name(header_name); 609*2067fd92SSamuel Thibault if (param) { 610*2067fd92SSamuel Thibault spk_set_num_var(synth_default_value[idx], 611*2067fd92SSamuel Thibault param, E_NEW_DEFAULT); 612*2067fd92SSamuel Thibault spk_set_num_var(0, param, E_DEFAULT); 613*2067fd92SSamuel Thibault pr_info("%s reset to default value\n", param->name); 614*2067fd92SSamuel Thibault } 615*2067fd92SSamuel Thibault } 616*2067fd92SSamuel Thibault } 617*2067fd92SSamuel Thibault 618*2067fd92SSamuel Thibault /* 619*2067fd92SSamuel Thibault * This function is called when a user echos a value to one of the 620*2067fd92SSamuel Thibault * variable parameters. 621*2067fd92SSamuel Thibault */ 622*2067fd92SSamuel Thibault ssize_t spk_var_store(struct kobject *kobj, struct kobj_attribute *attr, 623*2067fd92SSamuel Thibault const char *buf, size_t count) 624*2067fd92SSamuel Thibault { 625*2067fd92SSamuel Thibault struct st_var_header *param; 626*2067fd92SSamuel Thibault int ret; 627*2067fd92SSamuel Thibault int len; 628*2067fd92SSamuel Thibault char *cp; 629*2067fd92SSamuel Thibault struct var_t *var_data; 630*2067fd92SSamuel Thibault long value; 631*2067fd92SSamuel Thibault unsigned long flags; 632*2067fd92SSamuel Thibault 633*2067fd92SSamuel Thibault param = spk_var_header_by_name(attr->attr.name); 634*2067fd92SSamuel Thibault if (!param) 635*2067fd92SSamuel Thibault return -EINVAL; 636*2067fd92SSamuel Thibault if (!param->data) 637*2067fd92SSamuel Thibault return 0; 638*2067fd92SSamuel Thibault ret = 0; 639*2067fd92SSamuel Thibault cp = (char *)buf; 640*2067fd92SSamuel Thibault string_unescape_any_inplace(cp); 641*2067fd92SSamuel Thibault 642*2067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 643*2067fd92SSamuel Thibault switch (param->var_type) { 644*2067fd92SSamuel Thibault case VAR_NUM: 645*2067fd92SSamuel Thibault case VAR_TIME: 646*2067fd92SSamuel Thibault if (*cp == 'd' || *cp == 'r' || *cp == '\0') 647*2067fd92SSamuel Thibault len = E_DEFAULT; 648*2067fd92SSamuel Thibault else if (*cp == '+' || *cp == '-') 649*2067fd92SSamuel Thibault len = E_INC; 650*2067fd92SSamuel Thibault else 651*2067fd92SSamuel Thibault len = E_SET; 652*2067fd92SSamuel Thibault if (kstrtol(cp, 10, &value) == 0) 653*2067fd92SSamuel Thibault ret = spk_set_num_var(value, param, len); 654*2067fd92SSamuel Thibault else 655*2067fd92SSamuel Thibault pr_warn("overflow or parsing error has occurred"); 656*2067fd92SSamuel Thibault if (ret == -ERANGE) { 657*2067fd92SSamuel Thibault var_data = param->data; 658*2067fd92SSamuel Thibault pr_warn("value for %s out of range, expect %d to %d\n", 659*2067fd92SSamuel Thibault param->name, 660*2067fd92SSamuel Thibault var_data->u.n.low, var_data->u.n.high); 661*2067fd92SSamuel Thibault } 662*2067fd92SSamuel Thibault 663*2067fd92SSamuel Thibault /* 664*2067fd92SSamuel Thibault * If voice was just changed, we might need to reset our default 665*2067fd92SSamuel Thibault * pitch and volume. 666*2067fd92SSamuel Thibault */ 667*2067fd92SSamuel Thibault if (param->var_id == VOICE && synth && 668*2067fd92SSamuel Thibault (ret == 0 || ret == -ERESTART)) { 669*2067fd92SSamuel Thibault var_data = param->data; 670*2067fd92SSamuel Thibault value = var_data->u.n.value; 671*2067fd92SSamuel Thibault spk_reset_default_value("pitch", synth->default_pitch, 672*2067fd92SSamuel Thibault value); 673*2067fd92SSamuel Thibault spk_reset_default_value("vol", synth->default_vol, 674*2067fd92SSamuel Thibault value); 675*2067fd92SSamuel Thibault } 676*2067fd92SSamuel Thibault break; 677*2067fd92SSamuel Thibault case VAR_STRING: 678*2067fd92SSamuel Thibault len = strlen(cp); 679*2067fd92SSamuel Thibault if ((len >= 1) && (cp[len - 1] == '\n')) 680*2067fd92SSamuel Thibault --len; 681*2067fd92SSamuel Thibault if ((len >= 2) && (cp[0] == '"') && (cp[len - 1] == '"')) { 682*2067fd92SSamuel Thibault ++cp; 683*2067fd92SSamuel Thibault len -= 2; 684*2067fd92SSamuel Thibault } 685*2067fd92SSamuel Thibault cp[len] = '\0'; 686*2067fd92SSamuel Thibault ret = spk_set_string_var(cp, param, len); 687*2067fd92SSamuel Thibault if (ret == -E2BIG) 688*2067fd92SSamuel Thibault pr_warn("value too long for %s\n", 689*2067fd92SSamuel Thibault param->name); 690*2067fd92SSamuel Thibault break; 691*2067fd92SSamuel Thibault default: 692*2067fd92SSamuel Thibault pr_warn("%s unknown type %d\n", 693*2067fd92SSamuel Thibault param->name, (int)param->var_type); 694*2067fd92SSamuel Thibault break; 695*2067fd92SSamuel Thibault } 696*2067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 697*2067fd92SSamuel Thibault 698*2067fd92SSamuel Thibault if (ret == -ERESTART) 699*2067fd92SSamuel Thibault pr_info("%s reset to default value\n", param->name); 700*2067fd92SSamuel Thibault return count; 701*2067fd92SSamuel Thibault } 702*2067fd92SSamuel Thibault EXPORT_SYMBOL_GPL(spk_var_store); 703*2067fd92SSamuel Thibault 704*2067fd92SSamuel Thibault /* 705*2067fd92SSamuel Thibault * Functions for reading and writing lists of i18n messages. Incomplete. 706*2067fd92SSamuel Thibault */ 707*2067fd92SSamuel Thibault 708*2067fd92SSamuel Thibault static ssize_t message_show_helper(char *buf, enum msg_index_t first, 709*2067fd92SSamuel Thibault enum msg_index_t last) 710*2067fd92SSamuel Thibault { 711*2067fd92SSamuel Thibault size_t bufsize = PAGE_SIZE; 712*2067fd92SSamuel Thibault char *buf_pointer = buf; 713*2067fd92SSamuel Thibault int printed; 714*2067fd92SSamuel Thibault enum msg_index_t cursor; 715*2067fd92SSamuel Thibault int index = 0; 716*2067fd92SSamuel Thibault *buf_pointer = '\0'; /* buf_pointer always looking at a NUL byte. */ 717*2067fd92SSamuel Thibault 718*2067fd92SSamuel Thibault for (cursor = first; cursor <= last; cursor++, index++) { 719*2067fd92SSamuel Thibault if (bufsize <= 1) 720*2067fd92SSamuel Thibault break; 721*2067fd92SSamuel Thibault printed = scnprintf(buf_pointer, bufsize, "%d\t%s\n", 722*2067fd92SSamuel Thibault index, spk_msg_get(cursor)); 723*2067fd92SSamuel Thibault buf_pointer += printed; 724*2067fd92SSamuel Thibault bufsize -= printed; 725*2067fd92SSamuel Thibault } 726*2067fd92SSamuel Thibault 727*2067fd92SSamuel Thibault return buf_pointer - buf; 728*2067fd92SSamuel Thibault } 729*2067fd92SSamuel Thibault 730*2067fd92SSamuel Thibault static void report_msg_status(int reset, int received, int used, 731*2067fd92SSamuel Thibault int rejected, char *groupname) 732*2067fd92SSamuel Thibault { 733*2067fd92SSamuel Thibault int len; 734*2067fd92SSamuel Thibault char buf[160]; 735*2067fd92SSamuel Thibault 736*2067fd92SSamuel Thibault if (reset) { 737*2067fd92SSamuel Thibault pr_info("i18n messages from group %s reset to defaults\n", 738*2067fd92SSamuel Thibault groupname); 739*2067fd92SSamuel Thibault } else if (received) { 740*2067fd92SSamuel Thibault len = snprintf(buf, sizeof(buf), 741*2067fd92SSamuel Thibault " updated %d of %d i18n messages from group %s\n", 742*2067fd92SSamuel Thibault used, received, groupname); 743*2067fd92SSamuel Thibault if (rejected) 744*2067fd92SSamuel Thibault snprintf(buf + (len - 1), sizeof(buf) - (len - 1), 745*2067fd92SSamuel Thibault " with %d reject%s\n", 746*2067fd92SSamuel Thibault rejected, rejected > 1 ? "s" : ""); 747*2067fd92SSamuel Thibault pr_info("%s", buf); 748*2067fd92SSamuel Thibault } 749*2067fd92SSamuel Thibault } 750*2067fd92SSamuel Thibault 751*2067fd92SSamuel Thibault static ssize_t message_store_helper(const char *buf, size_t count, 752*2067fd92SSamuel Thibault struct msg_group_t *group) 753*2067fd92SSamuel Thibault { 754*2067fd92SSamuel Thibault char *cp = (char *)buf; 755*2067fd92SSamuel Thibault char *end = cp + count; 756*2067fd92SSamuel Thibault char *linefeed = NULL; 757*2067fd92SSamuel Thibault char *temp = NULL; 758*2067fd92SSamuel Thibault ssize_t msg_stored = 0; 759*2067fd92SSamuel Thibault ssize_t retval = count; 760*2067fd92SSamuel Thibault size_t desc_length = 0; 761*2067fd92SSamuel Thibault unsigned long index = 0; 762*2067fd92SSamuel Thibault int received = 0; 763*2067fd92SSamuel Thibault int used = 0; 764*2067fd92SSamuel Thibault int rejected = 0; 765*2067fd92SSamuel Thibault int reset = 0; 766*2067fd92SSamuel Thibault enum msg_index_t firstmessage = group->start; 767*2067fd92SSamuel Thibault enum msg_index_t lastmessage = group->end; 768*2067fd92SSamuel Thibault enum msg_index_t curmessage; 769*2067fd92SSamuel Thibault 770*2067fd92SSamuel Thibault while (cp < end) { 771*2067fd92SSamuel Thibault while ((cp < end) && (*cp == ' ' || *cp == '\t')) 772*2067fd92SSamuel Thibault cp++; 773*2067fd92SSamuel Thibault 774*2067fd92SSamuel Thibault if (cp == end) 775*2067fd92SSamuel Thibault break; 776*2067fd92SSamuel Thibault if (strchr("dDrR", *cp)) { 777*2067fd92SSamuel Thibault reset = 1; 778*2067fd92SSamuel Thibault break; 779*2067fd92SSamuel Thibault } 780*2067fd92SSamuel Thibault received++; 781*2067fd92SSamuel Thibault 782*2067fd92SSamuel Thibault linefeed = strchr(cp, '\n'); 783*2067fd92SSamuel Thibault if (!linefeed) { 784*2067fd92SSamuel Thibault rejected++; 785*2067fd92SSamuel Thibault break; 786*2067fd92SSamuel Thibault } 787*2067fd92SSamuel Thibault 788*2067fd92SSamuel Thibault if (!isdigit(*cp)) { 789*2067fd92SSamuel Thibault rejected++; 790*2067fd92SSamuel Thibault cp = linefeed + 1; 791*2067fd92SSamuel Thibault continue; 792*2067fd92SSamuel Thibault } 793*2067fd92SSamuel Thibault 794*2067fd92SSamuel Thibault /* 795*2067fd92SSamuel Thibault * Do not replace with kstrtoul: 796*2067fd92SSamuel Thibault * here we need temp to be updated 797*2067fd92SSamuel Thibault */ 798*2067fd92SSamuel Thibault index = simple_strtoul(cp, &temp, 10); 799*2067fd92SSamuel Thibault 800*2067fd92SSamuel Thibault while ((temp < linefeed) && (*temp == ' ' || *temp == '\t')) 801*2067fd92SSamuel Thibault temp++; 802*2067fd92SSamuel Thibault 803*2067fd92SSamuel Thibault desc_length = linefeed - temp; 804*2067fd92SSamuel Thibault curmessage = firstmessage + index; 805*2067fd92SSamuel Thibault 806*2067fd92SSamuel Thibault /* 807*2067fd92SSamuel Thibault * Note the check (curmessage < firstmessage). It is not 808*2067fd92SSamuel Thibault * redundant. Suppose that the user gave us an index 809*2067fd92SSamuel Thibault * equal to ULONG_MAX - 1. If firstmessage > 1, then 810*2067fd92SSamuel Thibault * firstmessage + index < firstmessage! 811*2067fd92SSamuel Thibault */ 812*2067fd92SSamuel Thibault 813*2067fd92SSamuel Thibault if ((curmessage < firstmessage) || (curmessage > lastmessage)) { 814*2067fd92SSamuel Thibault rejected++; 815*2067fd92SSamuel Thibault cp = linefeed + 1; 816*2067fd92SSamuel Thibault continue; 817*2067fd92SSamuel Thibault } 818*2067fd92SSamuel Thibault 819*2067fd92SSamuel Thibault msg_stored = spk_msg_set(curmessage, temp, desc_length); 820*2067fd92SSamuel Thibault if (msg_stored < 0) { 821*2067fd92SSamuel Thibault retval = msg_stored; 822*2067fd92SSamuel Thibault if (msg_stored == -ENOMEM) 823*2067fd92SSamuel Thibault reset = 1; 824*2067fd92SSamuel Thibault break; 825*2067fd92SSamuel Thibault } 826*2067fd92SSamuel Thibault 827*2067fd92SSamuel Thibault used++; 828*2067fd92SSamuel Thibault 829*2067fd92SSamuel Thibault cp = linefeed + 1; 830*2067fd92SSamuel Thibault } 831*2067fd92SSamuel Thibault 832*2067fd92SSamuel Thibault if (reset) 833*2067fd92SSamuel Thibault spk_reset_msg_group(group); 834*2067fd92SSamuel Thibault 835*2067fd92SSamuel Thibault report_msg_status(reset, received, used, rejected, group->name); 836*2067fd92SSamuel Thibault return retval; 837*2067fd92SSamuel Thibault } 838*2067fd92SSamuel Thibault 839*2067fd92SSamuel Thibault static ssize_t message_show(struct kobject *kobj, 840*2067fd92SSamuel Thibault struct kobj_attribute *attr, char *buf) 841*2067fd92SSamuel Thibault { 842*2067fd92SSamuel Thibault ssize_t retval = 0; 843*2067fd92SSamuel Thibault struct msg_group_t *group = spk_find_msg_group(attr->attr.name); 844*2067fd92SSamuel Thibault unsigned long flags; 845*2067fd92SSamuel Thibault 846*2067fd92SSamuel Thibault if (WARN_ON(!group)) 847*2067fd92SSamuel Thibault return -EINVAL; 848*2067fd92SSamuel Thibault 849*2067fd92SSamuel Thibault spin_lock_irqsave(&speakup_info.spinlock, flags); 850*2067fd92SSamuel Thibault retval = message_show_helper(buf, group->start, group->end); 851*2067fd92SSamuel Thibault spin_unlock_irqrestore(&speakup_info.spinlock, flags); 852*2067fd92SSamuel Thibault return retval; 853*2067fd92SSamuel Thibault } 854*2067fd92SSamuel Thibault 855*2067fd92SSamuel Thibault static ssize_t message_store(struct kobject *kobj, struct kobj_attribute *attr, 856*2067fd92SSamuel Thibault const char *buf, size_t count) 857*2067fd92SSamuel Thibault { 858*2067fd92SSamuel Thibault struct msg_group_t *group = spk_find_msg_group(attr->attr.name); 859*2067fd92SSamuel Thibault 860*2067fd92SSamuel Thibault if (WARN_ON(!group)) 861*2067fd92SSamuel Thibault return -EINVAL; 862*2067fd92SSamuel Thibault 863*2067fd92SSamuel Thibault return message_store_helper(buf, count, group); 864*2067fd92SSamuel Thibault } 865*2067fd92SSamuel Thibault 866*2067fd92SSamuel Thibault /* 867*2067fd92SSamuel Thibault * Declare the attributes. 868*2067fd92SSamuel Thibault */ 869*2067fd92SSamuel Thibault static struct kobj_attribute keymap_attribute = 870*2067fd92SSamuel Thibault __ATTR_RW(keymap); 871*2067fd92SSamuel Thibault static struct kobj_attribute silent_attribute = 872*2067fd92SSamuel Thibault __ATTR_WO(silent); 873*2067fd92SSamuel Thibault static struct kobj_attribute synth_attribute = 874*2067fd92SSamuel Thibault __ATTR_RW(synth); 875*2067fd92SSamuel Thibault static struct kobj_attribute synth_direct_attribute = 876*2067fd92SSamuel Thibault __ATTR_WO(synth_direct); 877*2067fd92SSamuel Thibault static struct kobj_attribute version_attribute = 878*2067fd92SSamuel Thibault __ATTR_RO(version); 879*2067fd92SSamuel Thibault 880*2067fd92SSamuel Thibault static struct kobj_attribute delimiters_attribute = 881*2067fd92SSamuel Thibault __ATTR(delimiters, 0644, punc_show, punc_store); 882*2067fd92SSamuel Thibault static struct kobj_attribute ex_num_attribute = 883*2067fd92SSamuel Thibault __ATTR(ex_num, 0644, punc_show, punc_store); 884*2067fd92SSamuel Thibault static struct kobj_attribute punc_all_attribute = 885*2067fd92SSamuel Thibault __ATTR(punc_all, 0644, punc_show, punc_store); 886*2067fd92SSamuel Thibault static struct kobj_attribute punc_most_attribute = 887*2067fd92SSamuel Thibault __ATTR(punc_most, 0644, punc_show, punc_store); 888*2067fd92SSamuel Thibault static struct kobj_attribute punc_some_attribute = 889*2067fd92SSamuel Thibault __ATTR(punc_some, 0644, punc_show, punc_store); 890*2067fd92SSamuel Thibault static struct kobj_attribute repeats_attribute = 891*2067fd92SSamuel Thibault __ATTR(repeats, 0644, punc_show, punc_store); 892*2067fd92SSamuel Thibault 893*2067fd92SSamuel Thibault static struct kobj_attribute attrib_bleep_attribute = 894*2067fd92SSamuel Thibault __ATTR(attrib_bleep, 0644, spk_var_show, spk_var_store); 895*2067fd92SSamuel Thibault static struct kobj_attribute bell_pos_attribute = 896*2067fd92SSamuel Thibault __ATTR(bell_pos, 0644, spk_var_show, spk_var_store); 897*2067fd92SSamuel Thibault static struct kobj_attribute bleep_time_attribute = 898*2067fd92SSamuel Thibault __ATTR(bleep_time, 0644, spk_var_show, spk_var_store); 899*2067fd92SSamuel Thibault static struct kobj_attribute bleeps_attribute = 900*2067fd92SSamuel Thibault __ATTR(bleeps, 0644, spk_var_show, spk_var_store); 901*2067fd92SSamuel Thibault static struct kobj_attribute cursor_time_attribute = 902*2067fd92SSamuel Thibault __ATTR(cursor_time, 0644, spk_var_show, spk_var_store); 903*2067fd92SSamuel Thibault static struct kobj_attribute key_echo_attribute = 904*2067fd92SSamuel Thibault __ATTR(key_echo, 0644, spk_var_show, spk_var_store); 905*2067fd92SSamuel Thibault static struct kobj_attribute no_interrupt_attribute = 906*2067fd92SSamuel Thibault __ATTR(no_interrupt, 0644, spk_var_show, spk_var_store); 907*2067fd92SSamuel Thibault static struct kobj_attribute punc_level_attribute = 908*2067fd92SSamuel Thibault __ATTR(punc_level, 0644, spk_var_show, spk_var_store); 909*2067fd92SSamuel Thibault static struct kobj_attribute reading_punc_attribute = 910*2067fd92SSamuel Thibault __ATTR(reading_punc, 0644, spk_var_show, spk_var_store); 911*2067fd92SSamuel Thibault static struct kobj_attribute say_control_attribute = 912*2067fd92SSamuel Thibault __ATTR(say_control, 0644, spk_var_show, spk_var_store); 913*2067fd92SSamuel Thibault static struct kobj_attribute say_word_ctl_attribute = 914*2067fd92SSamuel Thibault __ATTR(say_word_ctl, 0644, spk_var_show, spk_var_store); 915*2067fd92SSamuel Thibault static struct kobj_attribute spell_delay_attribute = 916*2067fd92SSamuel Thibault __ATTR(spell_delay, 0644, spk_var_show, spk_var_store); 917*2067fd92SSamuel Thibault 918*2067fd92SSamuel Thibault /* 919*2067fd92SSamuel Thibault * These attributes are i18n related. 920*2067fd92SSamuel Thibault */ 921*2067fd92SSamuel Thibault static struct kobj_attribute announcements_attribute = 922*2067fd92SSamuel Thibault __ATTR(announcements, 0644, message_show, message_store); 923*2067fd92SSamuel Thibault static struct kobj_attribute characters_attribute = 924*2067fd92SSamuel Thibault __ATTR(characters, 0644, chars_chartab_show, 925*2067fd92SSamuel Thibault chars_chartab_store); 926*2067fd92SSamuel Thibault static struct kobj_attribute chartab_attribute = 927*2067fd92SSamuel Thibault __ATTR(chartab, 0644, chars_chartab_show, 928*2067fd92SSamuel Thibault chars_chartab_store); 929*2067fd92SSamuel Thibault static struct kobj_attribute ctl_keys_attribute = 930*2067fd92SSamuel Thibault __ATTR(ctl_keys, 0644, message_show, message_store); 931*2067fd92SSamuel Thibault static struct kobj_attribute colors_attribute = 932*2067fd92SSamuel Thibault __ATTR(colors, 0644, message_show, message_store); 933*2067fd92SSamuel Thibault static struct kobj_attribute formatted_attribute = 934*2067fd92SSamuel Thibault __ATTR(formatted, 0644, message_show, message_store); 935*2067fd92SSamuel Thibault static struct kobj_attribute function_names_attribute = 936*2067fd92SSamuel Thibault __ATTR(function_names, 0644, message_show, message_store); 937*2067fd92SSamuel Thibault static struct kobj_attribute key_names_attribute = 938*2067fd92SSamuel Thibault __ATTR(key_names, 0644, message_show, message_store); 939*2067fd92SSamuel Thibault static struct kobj_attribute states_attribute = 940*2067fd92SSamuel Thibault __ATTR(states, 0644, message_show, message_store); 941*2067fd92SSamuel Thibault 942*2067fd92SSamuel Thibault /* 943*2067fd92SSamuel Thibault * Create groups of attributes so that we can create and destroy them all 944*2067fd92SSamuel Thibault * at once. 945*2067fd92SSamuel Thibault */ 946*2067fd92SSamuel Thibault static struct attribute *main_attrs[] = { 947*2067fd92SSamuel Thibault &keymap_attribute.attr, 948*2067fd92SSamuel Thibault &silent_attribute.attr, 949*2067fd92SSamuel Thibault &synth_attribute.attr, 950*2067fd92SSamuel Thibault &synth_direct_attribute.attr, 951*2067fd92SSamuel Thibault &version_attribute.attr, 952*2067fd92SSamuel Thibault &delimiters_attribute.attr, 953*2067fd92SSamuel Thibault &ex_num_attribute.attr, 954*2067fd92SSamuel Thibault &punc_all_attribute.attr, 955*2067fd92SSamuel Thibault &punc_most_attribute.attr, 956*2067fd92SSamuel Thibault &punc_some_attribute.attr, 957*2067fd92SSamuel Thibault &repeats_attribute.attr, 958*2067fd92SSamuel Thibault &attrib_bleep_attribute.attr, 959*2067fd92SSamuel Thibault &bell_pos_attribute.attr, 960*2067fd92SSamuel Thibault &bleep_time_attribute.attr, 961*2067fd92SSamuel Thibault &bleeps_attribute.attr, 962*2067fd92SSamuel Thibault &cursor_time_attribute.attr, 963*2067fd92SSamuel Thibault &key_echo_attribute.attr, 964*2067fd92SSamuel Thibault &no_interrupt_attribute.attr, 965*2067fd92SSamuel Thibault &punc_level_attribute.attr, 966*2067fd92SSamuel Thibault &reading_punc_attribute.attr, 967*2067fd92SSamuel Thibault &say_control_attribute.attr, 968*2067fd92SSamuel Thibault &say_word_ctl_attribute.attr, 969*2067fd92SSamuel Thibault &spell_delay_attribute.attr, 970*2067fd92SSamuel Thibault NULL, 971*2067fd92SSamuel Thibault }; 972*2067fd92SSamuel Thibault 973*2067fd92SSamuel Thibault static struct attribute *i18n_attrs[] = { 974*2067fd92SSamuel Thibault &announcements_attribute.attr, 975*2067fd92SSamuel Thibault &characters_attribute.attr, 976*2067fd92SSamuel Thibault &chartab_attribute.attr, 977*2067fd92SSamuel Thibault &ctl_keys_attribute.attr, 978*2067fd92SSamuel Thibault &colors_attribute.attr, 979*2067fd92SSamuel Thibault &formatted_attribute.attr, 980*2067fd92SSamuel Thibault &function_names_attribute.attr, 981*2067fd92SSamuel Thibault &key_names_attribute.attr, 982*2067fd92SSamuel Thibault &states_attribute.attr, 983*2067fd92SSamuel Thibault NULL, 984*2067fd92SSamuel Thibault }; 985*2067fd92SSamuel Thibault 986*2067fd92SSamuel Thibault /* 987*2067fd92SSamuel Thibault * An unnamed attribute group will put all of the attributes directly in 988*2067fd92SSamuel Thibault * the kobject directory. If we specify a name, a subdirectory will be 989*2067fd92SSamuel Thibault * created for the attributes with the directory being the name of the 990*2067fd92SSamuel Thibault * attribute group. 991*2067fd92SSamuel Thibault */ 992*2067fd92SSamuel Thibault static const struct attribute_group main_attr_group = { 993*2067fd92SSamuel Thibault .attrs = main_attrs, 994*2067fd92SSamuel Thibault }; 995*2067fd92SSamuel Thibault 996*2067fd92SSamuel Thibault static const struct attribute_group i18n_attr_group = { 997*2067fd92SSamuel Thibault .attrs = i18n_attrs, 998*2067fd92SSamuel Thibault .name = "i18n", 999*2067fd92SSamuel Thibault }; 1000*2067fd92SSamuel Thibault 1001*2067fd92SSamuel Thibault static struct kobject *accessibility_kobj; 1002*2067fd92SSamuel Thibault struct kobject *speakup_kobj; 1003*2067fd92SSamuel Thibault 1004*2067fd92SSamuel Thibault int speakup_kobj_init(void) 1005*2067fd92SSamuel Thibault { 1006*2067fd92SSamuel Thibault int retval; 1007*2067fd92SSamuel Thibault 1008*2067fd92SSamuel Thibault /* 1009*2067fd92SSamuel Thibault * Create a simple kobject with the name of "accessibility", 1010*2067fd92SSamuel Thibault * located under /sys/ 1011*2067fd92SSamuel Thibault * 1012*2067fd92SSamuel Thibault * As this is a simple directory, no uevent will be sent to 1013*2067fd92SSamuel Thibault * userspace. That is why this function should not be used for 1014*2067fd92SSamuel Thibault * any type of dynamic kobjects, where the name and number are 1015*2067fd92SSamuel Thibault * not known ahead of time. 1016*2067fd92SSamuel Thibault */ 1017*2067fd92SSamuel Thibault accessibility_kobj = kobject_create_and_add("accessibility", NULL); 1018*2067fd92SSamuel Thibault if (!accessibility_kobj) { 1019*2067fd92SSamuel Thibault retval = -ENOMEM; 1020*2067fd92SSamuel Thibault goto out; 1021*2067fd92SSamuel Thibault } 1022*2067fd92SSamuel Thibault 1023*2067fd92SSamuel Thibault speakup_kobj = kobject_create_and_add("speakup", accessibility_kobj); 1024*2067fd92SSamuel Thibault if (!speakup_kobj) { 1025*2067fd92SSamuel Thibault retval = -ENOMEM; 1026*2067fd92SSamuel Thibault goto err_acc; 1027*2067fd92SSamuel Thibault } 1028*2067fd92SSamuel Thibault 1029*2067fd92SSamuel Thibault /* Create the files associated with this kobject */ 1030*2067fd92SSamuel Thibault retval = sysfs_create_group(speakup_kobj, &main_attr_group); 1031*2067fd92SSamuel Thibault if (retval) 1032*2067fd92SSamuel Thibault goto err_speakup; 1033*2067fd92SSamuel Thibault 1034*2067fd92SSamuel Thibault retval = sysfs_create_group(speakup_kobj, &i18n_attr_group); 1035*2067fd92SSamuel Thibault if (retval) 1036*2067fd92SSamuel Thibault goto err_group; 1037*2067fd92SSamuel Thibault 1038*2067fd92SSamuel Thibault goto out; 1039*2067fd92SSamuel Thibault 1040*2067fd92SSamuel Thibault err_group: 1041*2067fd92SSamuel Thibault sysfs_remove_group(speakup_kobj, &main_attr_group); 1042*2067fd92SSamuel Thibault err_speakup: 1043*2067fd92SSamuel Thibault kobject_put(speakup_kobj); 1044*2067fd92SSamuel Thibault err_acc: 1045*2067fd92SSamuel Thibault kobject_put(accessibility_kobj); 1046*2067fd92SSamuel Thibault out: 1047*2067fd92SSamuel Thibault return retval; 1048*2067fd92SSamuel Thibault } 1049*2067fd92SSamuel Thibault 1050*2067fd92SSamuel Thibault void speakup_kobj_exit(void) 1051*2067fd92SSamuel Thibault { 1052*2067fd92SSamuel Thibault sysfs_remove_group(speakup_kobj, &i18n_attr_group); 1053*2067fd92SSamuel Thibault sysfs_remove_group(speakup_kobj, &main_attr_group); 1054*2067fd92SSamuel Thibault kobject_put(speakup_kobj); 1055*2067fd92SSamuel Thibault kobject_put(accessibility_kobj); 1056*2067fd92SSamuel Thibault } 1057