xref: /openbmc/linux/drivers/accessibility/speakup/kobjects.c (revision 2067fd92d75b6d9085a43caf050bca5d88c491b8)
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