Path: blob/master/drivers/accessibility/speakup/kobjects.c
26282 views
// SPDX-License-Identifier: GPL-2.01/*2* Speakup kobject implementation3*4* Copyright (C) 2009 William Hubbs5*6* This code is based on kobject-example.c, which came with linux 2.6.x.7*8* Copyright (C) 2004-2007 Greg Kroah-Hartman <[email protected]>9* Copyright (C) 2007 Novell Inc.10*11* Released under the GPL version 2 only.12*13*/14#include <linux/slab.h> /* For kmalloc. */15#include <linux/kernel.h>16#include <linux/kobject.h>17#include <linux/string.h>18#include <linux/string_helpers.h>19#include <linux/sysfs.h>20#include <linux/ctype.h>2122#include "speakup.h"23#include "spk_priv.h"2425/*26* This is called when a user reads the characters or chartab sys file.27*/28static ssize_t chars_chartab_show(struct kobject *kobj,29struct kobj_attribute *attr, char *buf)30{31int i;32int len = 0;33char *cp;34char *buf_pointer = buf;35size_t bufsize = PAGE_SIZE;36unsigned long flags;3738spin_lock_irqsave(&speakup_info.spinlock, flags);39*buf_pointer = '\0';40for (i = 0; i < 256; i++) {41if (bufsize <= 1)42break;43if (strcmp("characters", attr->attr.name) == 0) {44len = scnprintf(buf_pointer, bufsize, "%d\t%s\n",45i, spk_characters[i]);46} else { /* show chartab entry */47if (IS_TYPE(i, B_CTL))48cp = "B_CTL";49else if (IS_TYPE(i, WDLM))50cp = "WDLM";51else if (IS_TYPE(i, A_PUNC))52cp = "A_PUNC";53else if (IS_TYPE(i, PUNC))54cp = "PUNC";55else if (IS_TYPE(i, NUM))56cp = "NUM";57else if (IS_TYPE(i, A_CAP))58cp = "A_CAP";59else if (IS_TYPE(i, ALPHA))60cp = "ALPHA";61else if (IS_TYPE(i, B_CAPSYM))62cp = "B_CAPSYM";63else if (IS_TYPE(i, B_SYM))64cp = "B_SYM";65else66cp = "0";67len =68scnprintf(buf_pointer, bufsize, "%d\t%s\n", i, cp);69}70bufsize -= len;71buf_pointer += len;72}73spin_unlock_irqrestore(&speakup_info.spinlock, flags);74return buf_pointer - buf;75}7677/*78* Print informational messages or warnings after updating79* character descriptions or chartab entries.80*/81static void report_char_chartab_status(int reset, int received, int used,82int rejected, int do_characters)83{84static char const *object_type[] = {85"character class entries",86"character descriptions",87};88int len;89char buf[80];9091if (reset) {92pr_info("%s reset to defaults\n", object_type[do_characters]);93} else if (received) {94len = snprintf(buf, sizeof(buf),95" updated %d of %d %s\n",96used, received, object_type[do_characters]);97if (rejected)98snprintf(buf + (len - 1), sizeof(buf) - (len - 1),99" with %d reject%s\n",100rejected, rejected > 1 ? "s" : "");101pr_info("%s", buf);102}103}104105/*106* This is called when a user changes the characters or chartab parameters.107*/108static ssize_t chars_chartab_store(struct kobject *kobj,109struct kobj_attribute *attr,110const char *buf, size_t count)111{112char *cp = (char *)buf;113char *end = cp + count; /* the null at the end of the buffer */114char *linefeed = NULL;115char keyword[MAX_DESC_LEN + 1];116char *outptr = NULL; /* Will hold keyword or desc. */117char *temp = NULL;118char *desc = NULL;119ssize_t retval = count;120unsigned long flags;121unsigned long index = 0;122int charclass = 0;123int received = 0;124int used = 0;125int rejected = 0;126int reset = 0;127int do_characters = !strcmp(attr->attr.name, "characters");128size_t desc_length = 0;129int i;130131spin_lock_irqsave(&speakup_info.spinlock, flags);132while (cp < end) {133while ((cp < end) && (*cp == ' ' || *cp == '\t'))134cp++;135136if (cp == end)137break;138if ((*cp == '\n') || strchr("dDrR", *cp)) {139reset = 1;140break;141}142received++;143144linefeed = strchr(cp, '\n');145if (!linefeed) {146rejected++;147break;148}149150if (!isdigit(*cp)) {151rejected++;152cp = linefeed + 1;153continue;154}155156/*157* Do not replace with kstrtoul:158* here we need temp to be updated159*/160index = simple_strtoul(cp, &temp, 10);161if (index > 255) {162rejected++;163cp = linefeed + 1;164continue;165}166167while ((temp < linefeed) && (*temp == ' ' || *temp == '\t'))168temp++;169170desc_length = linefeed - temp;171if (desc_length > MAX_DESC_LEN) {172rejected++;173cp = linefeed + 1;174continue;175}176if (do_characters) {177desc = kmalloc(desc_length + 1, GFP_ATOMIC);178if (!desc) {179retval = -ENOMEM;180reset = 1; /* just reset on error. */181break;182}183outptr = desc;184} else {185outptr = keyword;186}187188for (i = 0; i < desc_length; i++)189outptr[i] = temp[i];190outptr[desc_length] = '\0';191192if (do_characters) {193if (spk_characters[index] != spk_default_chars[index])194kfree(spk_characters[index]);195spk_characters[index] = desc;196used++;197} else {198charclass = spk_chartab_get_value(keyword);199if (charclass == 0) {200rejected++;201cp = linefeed + 1;202continue;203}204if (charclass != spk_chartab[index]) {205spk_chartab[index] = charclass;206used++;207}208}209cp = linefeed + 1;210}211212if (reset) {213if (do_characters)214spk_reset_default_chars();215else216spk_reset_default_chartab();217}218219spin_unlock_irqrestore(&speakup_info.spinlock, flags);220report_char_chartab_status(reset, received, used, rejected,221do_characters);222return retval;223}224225/*226* This is called when a user reads the keymap parameter.227*/228static ssize_t keymap_show(struct kobject *kobj, struct kobj_attribute *attr,229char *buf)230{231char *cp = buf;232int i;233int n;234int num_keys;235int nstates;236u_char *cp1;237u_char ch;238unsigned long flags;239240spin_lock_irqsave(&speakup_info.spinlock, flags);241cp1 = spk_key_buf + SHIFT_TBL_SIZE;242num_keys = (int)(*cp1);243nstates = (int)cp1[1];244cp += sprintf(cp, "%d, %d, %d,\n", KEY_MAP_VER, num_keys, nstates);245cp1 += 2; /* now pointing at shift states */246/* dump num_keys+1 as first row is shift states + flags,247* each subsequent row is key + states248*/249for (n = 0; n <= num_keys; n++) {250for (i = 0; i <= nstates; i++) {251ch = *cp1++;252cp += sprintf(cp, "%d,", (int)ch);253*cp++ = (i < nstates) ? SPACE : '\n';254}255}256cp += sprintf(cp, "0, %d\n", KEY_MAP_VER);257spin_unlock_irqrestore(&speakup_info.spinlock, flags);258return (int)(cp - buf);259}260261/*262* This is called when a user changes the keymap parameter.263*/264static ssize_t keymap_store(struct kobject *kobj, struct kobj_attribute *attr,265const char *buf, size_t count)266{267int i;268ssize_t ret = count;269char *in_buff = NULL;270char *cp;271u_char *cp1;272unsigned long flags;273274spin_lock_irqsave(&speakup_info.spinlock, flags);275in_buff = kmemdup(buf, count + 1, GFP_ATOMIC);276if (!in_buff) {277spin_unlock_irqrestore(&speakup_info.spinlock, flags);278return -ENOMEM;279}280if (strchr("dDrR", *in_buff)) {281spk_set_key_info(spk_key_defaults, spk_key_buf);282pr_info("keymap set to default values\n");283kfree(in_buff);284spin_unlock_irqrestore(&speakup_info.spinlock, flags);285return count;286}287if (in_buff[count - 1] == '\n')288in_buff[count - 1] = '\0';289cp = in_buff;290cp1 = (u_char *)in_buff;291for (i = 0; i < 3; i++) {292cp = spk_s2uchar(cp, cp1);293cp1++;294}295i = (int)cp1[-2] + 1;296i *= (int)cp1[-1] + 1;297i += 2; /* 0 and last map ver */298if (cp1[-3] != KEY_MAP_VER || cp1[-1] > 10 ||299i + SHIFT_TBL_SIZE + 4 >= sizeof(spk_key_buf)) {300pr_warn("i %d %d %d %d\n", i,301(int)cp1[-3], (int)cp1[-2], (int)cp1[-1]);302kfree(in_buff);303spin_unlock_irqrestore(&speakup_info.spinlock, flags);304return -EINVAL;305}306while (--i >= 0) {307cp = spk_s2uchar(cp, cp1);308cp1++;309if (!(*cp))310break;311}312if (i != 0 || cp1[-1] != KEY_MAP_VER || cp1[-2] != 0) {313ret = -EINVAL;314pr_warn("end %d %d %d %d\n", i,315(int)cp1[-3], (int)cp1[-2], (int)cp1[-1]);316} else {317if (spk_set_key_info(in_buff, spk_key_buf)) {318spk_set_key_info(spk_key_defaults, spk_key_buf);319ret = -EINVAL;320pr_warn("set key failed\n");321}322}323kfree(in_buff);324spin_unlock_irqrestore(&speakup_info.spinlock, flags);325return ret;326}327328/*329* This is called when a user changes the value of the silent parameter.330*/331static ssize_t silent_store(struct kobject *kobj, struct kobj_attribute *attr,332const char *buf, size_t count)333{334int len;335struct vc_data *vc = vc_cons[fg_console].d;336char ch = 0;337char shut;338unsigned long flags;339340len = strlen(buf);341if (len > 0 && len < 3) {342ch = buf[0];343if (ch == '\n')344ch = '0';345}346if (ch < '0' || ch > '7') {347pr_warn("silent value '%c' not in range (0,7)\n", ch);348return -EINVAL;349}350spin_lock_irqsave(&speakup_info.spinlock, flags);351if (ch & 2) {352shut = 1;353spk_do_flush();354} else {355shut = 0;356}357if (ch & 4)358shut |= 0x40;359if (ch & 1)360spk_shut_up |= shut;361else362spk_shut_up &= ~shut;363spin_unlock_irqrestore(&speakup_info.spinlock, flags);364return count;365}366367/*368* This is called when a user reads the synth setting.369*/370static ssize_t synth_show(struct kobject *kobj, struct kobj_attribute *attr,371char *buf)372{373int rv;374375if (!synth)376rv = sprintf(buf, "%s\n", "none");377else378rv = sprintf(buf, "%s\n", synth->name);379return rv;380}381382/*383* This is called when a user requests to change synthesizers.384*/385static ssize_t synth_store(struct kobject *kobj, struct kobj_attribute *attr,386const char *buf, size_t count)387{388int len;389char new_synth_name[10];390391len = strlen(buf);392if (len < 2 || len > 9)393return -EINVAL;394memcpy(new_synth_name, buf, len);395if (new_synth_name[len - 1] == '\n')396len--;397new_synth_name[len] = '\0';398spk_strlwr(new_synth_name);399if (synth && !strcmp(new_synth_name, synth->name)) {400pr_warn("%s already in use\n", new_synth_name);401} else if (synth_init(new_synth_name) != 0) {402pr_warn("failed to init synth %s\n", new_synth_name);403return -ENODEV;404}405return count;406}407408/*409* This is called when text is sent to the synth via the synth_direct file.410*/411static ssize_t synth_direct_store(struct kobject *kobj,412struct kobj_attribute *attr,413const char *buf, size_t count)414{415char *unescaped;416unsigned long flags;417418if (!synth)419return -EPERM;420421unescaped = kstrdup(buf, GFP_KERNEL);422if (!unescaped)423return -ENOMEM;424425string_unescape_any_inplace(unescaped);426427spin_lock_irqsave(&speakup_info.spinlock, flags);428synth_write(unescaped, strlen(unescaped));429spin_unlock_irqrestore(&speakup_info.spinlock, flags);430431kfree(unescaped);432433return count;434}435436/*437* This function is called when a user reads the version.438*/439static ssize_t version_show(struct kobject *kobj, struct kobj_attribute *attr,440char *buf)441{442char *cp;443444cp = buf;445cp += sprintf(cp, "Speakup version %s\n", SPEAKUP_VERSION);446if (synth)447cp += sprintf(cp, "%s synthesizer driver version %s\n",448synth->name, synth->version);449return cp - buf;450}451452/*453* This is called when a user reads the punctuation settings.454*/455static ssize_t punc_show(struct kobject *kobj, struct kobj_attribute *attr,456char *buf)457{458int i;459char *cp = buf;460struct st_var_header *p_header;461struct punc_var_t *var;462struct st_bits_data *pb;463short mask;464unsigned long flags;465466p_header = spk_var_header_by_name(attr->attr.name);467if (!p_header) {468pr_warn("p_header is null, attr->attr.name is %s\n",469attr->attr.name);470return -EINVAL;471}472473var = spk_get_punc_var(p_header->var_id);474if (!var) {475pr_warn("var is null, p_header->var_id is %i\n",476p_header->var_id);477return -EINVAL;478}479480spin_lock_irqsave(&speakup_info.spinlock, flags);481pb = (struct st_bits_data *)&spk_punc_info[var->value];482mask = pb->mask;483for (i = 33; i < 128; i++) {484if (!(spk_chartab[i] & mask))485continue;486*cp++ = (char)i;487}488spin_unlock_irqrestore(&speakup_info.spinlock, flags);489return cp - buf;490}491492/*493* This is called when a user changes the punctuation settings.494*/495static ssize_t punc_store(struct kobject *kobj, struct kobj_attribute *attr,496const char *buf, size_t count)497{498int x;499struct st_var_header *p_header;500struct punc_var_t *var;501char punc_buf[100];502unsigned long flags;503504x = strlen(buf);505if (x < 1 || x > 99)506return -EINVAL;507508p_header = spk_var_header_by_name(attr->attr.name);509if (!p_header) {510pr_warn("p_header is null, attr->attr.name is %s\n",511attr->attr.name);512return -EINVAL;513}514515var = spk_get_punc_var(p_header->var_id);516if (!var) {517pr_warn("var is null, p_header->var_id is %i\n",518p_header->var_id);519return -EINVAL;520}521522memcpy(punc_buf, buf, x);523524while (x && punc_buf[x - 1] == '\n')525x--;526punc_buf[x] = '\0';527528spin_lock_irqsave(&speakup_info.spinlock, flags);529530if (*punc_buf == 'd' || *punc_buf == 'r')531x = spk_set_mask_bits(NULL, var->value, 3);532else533x = spk_set_mask_bits(punc_buf, var->value, 3);534535spin_unlock_irqrestore(&speakup_info.spinlock, flags);536return count;537}538539/*540* This function is called when a user reads one of the variable parameters.541*/542ssize_t spk_var_show(struct kobject *kobj, struct kobj_attribute *attr,543char *buf)544{545int rv = 0;546struct st_var_header *param;547struct var_t *var;548char *cp1;549char *cp;550char ch;551unsigned long flags;552553param = spk_var_header_by_name(attr->attr.name);554if (!param)555return -EINVAL;556557spin_lock_irqsave(&speakup_info.spinlock, flags);558var = (struct var_t *)param->data;559switch (param->var_type) {560case VAR_NUM:561case VAR_TIME:562if (var)563rv = sprintf(buf, "%i\n", var->u.n.value);564else565rv = sprintf(buf, "0\n");566break;567case VAR_STRING:568if (var) {569cp1 = buf;570*cp1++ = '"';571for (cp = (char *)param->p_val; (ch = *cp); cp++) {572if (ch >= ' ' && ch < '~')573*cp1++ = ch;574else575cp1 += sprintf(cp1, "\\x%02x", ch);576}577*cp1++ = '"';578*cp1++ = '\n';579*cp1 = '\0';580rv = cp1 - buf;581} else {582rv = sprintf(buf, "\"\"\n");583}584break;585default:586rv = sprintf(buf, "Bad parameter %s, type %i\n",587param->name, param->var_type);588break;589}590spin_unlock_irqrestore(&speakup_info.spinlock, flags);591return rv;592}593EXPORT_SYMBOL_GPL(spk_var_show);594595/*596* Used to reset either default_pitch or default_vol.597*/598static inline void spk_reset_default_value(char *header_name,599int *synth_default_value, int idx)600{601struct st_var_header *param;602603if (synth && synth_default_value) {604param = spk_var_header_by_name(header_name);605if (param) {606spk_set_num_var(synth_default_value[idx],607param, E_NEW_DEFAULT);608spk_set_num_var(0, param, E_DEFAULT);609pr_info("%s reset to default value\n", param->name);610}611}612}613614/*615* This function is called when a user echos a value to one of the616* variable parameters.617*/618ssize_t spk_var_store(struct kobject *kobj, struct kobj_attribute *attr,619const char *buf, size_t count)620{621struct st_var_header *param;622int ret;623int len;624char *cp;625struct var_t *var_data;626long value;627unsigned long flags;628629param = spk_var_header_by_name(attr->attr.name);630if (!param)631return -EINVAL;632if (!param->data)633return 0;634ret = 0;635cp = (char *)buf;636string_unescape_any_inplace(cp);637638spin_lock_irqsave(&speakup_info.spinlock, flags);639switch (param->var_type) {640case VAR_NUM:641case VAR_TIME:642if (*cp == 'd' || *cp == 'r' || *cp == '\0')643len = E_DEFAULT;644else if (*cp == '+' || *cp == '-')645len = E_INC;646else647len = E_SET;648if (kstrtol(cp, 10, &value) == 0)649ret = spk_set_num_var(value, param, len);650else651pr_warn("overflow or parsing error has occurred");652if (ret == -ERANGE) {653var_data = param->data;654pr_warn("value for %s out of range, expect %d to %d\n",655param->name,656var_data->u.n.low, var_data->u.n.high);657}658659/*660* If voice was just changed, we might need to reset our default661* pitch and volume.662*/663if (param->var_id == VOICE && synth &&664(ret == 0 || ret == -ERESTART)) {665var_data = param->data;666value = var_data->u.n.value;667spk_reset_default_value("pitch", synth->default_pitch,668value);669spk_reset_default_value("vol", synth->default_vol,670value);671}672break;673case VAR_STRING:674len = strlen(cp);675if ((len >= 1) && (cp[len - 1] == '\n'))676--len;677if ((len >= 2) && (cp[0] == '"') && (cp[len - 1] == '"')) {678++cp;679len -= 2;680}681cp[len] = '\0';682ret = spk_set_string_var(cp, param, len);683if (ret == -E2BIG)684pr_warn("value too long for %s\n",685param->name);686break;687default:688pr_warn("%s unknown type %d\n",689param->name, (int)param->var_type);690break;691}692spin_unlock_irqrestore(&speakup_info.spinlock, flags);693694if (ret == -ERESTART)695pr_info("%s reset to default value\n", param->name);696return count;697}698EXPORT_SYMBOL_GPL(spk_var_store);699700/*701* Functions for reading and writing lists of i18n messages. Incomplete.702*/703704static ssize_t message_show_helper(char *buf, enum msg_index_t first,705enum msg_index_t last)706{707size_t bufsize = PAGE_SIZE;708char *buf_pointer = buf;709int printed;710enum msg_index_t cursor;711int index = 0;712*buf_pointer = '\0'; /* buf_pointer always looking at a NUL byte. */713714for (cursor = first; cursor <= last; cursor++, index++) {715if (bufsize <= 1)716break;717printed = scnprintf(buf_pointer, bufsize, "%d\t%s\n",718index, spk_msg_get(cursor));719buf_pointer += printed;720bufsize -= printed;721}722723return buf_pointer - buf;724}725726static void report_msg_status(int reset, int received, int used,727int rejected, char *groupname)728{729int len;730char buf[160];731732if (reset) {733pr_info("i18n messages from group %s reset to defaults\n",734groupname);735} else if (received) {736len = snprintf(buf, sizeof(buf),737" updated %d of %d i18n messages from group %s\n",738used, received, groupname);739if (rejected)740snprintf(buf + (len - 1), sizeof(buf) - (len - 1),741" with %d reject%s\n",742rejected, rejected > 1 ? "s" : "");743pr_info("%s", buf);744}745}746747static ssize_t message_store_helper(const char *buf, size_t count,748struct msg_group_t *group)749{750char *cp = (char *)buf;751char *end = cp + count;752char *linefeed = NULL;753char *temp = NULL;754ssize_t msg_stored = 0;755ssize_t retval = count;756size_t desc_length = 0;757unsigned long index = 0;758int received = 0;759int used = 0;760int rejected = 0;761int reset = 0;762enum msg_index_t firstmessage = group->start;763enum msg_index_t lastmessage = group->end;764enum msg_index_t curmessage;765766while (cp < end) {767while ((cp < end) && (*cp == ' ' || *cp == '\t'))768cp++;769770if (cp == end)771break;772if (strchr("dDrR", *cp)) {773reset = 1;774break;775}776received++;777778linefeed = strchr(cp, '\n');779if (!linefeed) {780rejected++;781break;782}783784if (!isdigit(*cp)) {785rejected++;786cp = linefeed + 1;787continue;788}789790/*791* Do not replace with kstrtoul:792* here we need temp to be updated793*/794index = simple_strtoul(cp, &temp, 10);795796while ((temp < linefeed) && (*temp == ' ' || *temp == '\t'))797temp++;798799desc_length = linefeed - temp;800curmessage = firstmessage + index;801802/*803* Note the check (curmessage < firstmessage). It is not804* redundant. Suppose that the user gave us an index805* equal to ULONG_MAX - 1. If firstmessage > 1, then806* firstmessage + index < firstmessage!807*/808809if ((curmessage < firstmessage) || (curmessage > lastmessage)) {810rejected++;811cp = linefeed + 1;812continue;813}814815msg_stored = spk_msg_set(curmessage, temp, desc_length);816if (msg_stored < 0) {817retval = msg_stored;818if (msg_stored == -ENOMEM)819reset = 1;820break;821}822823used++;824825cp = linefeed + 1;826}827828if (reset)829spk_reset_msg_group(group);830831report_msg_status(reset, received, used, rejected, group->name);832return retval;833}834835static ssize_t message_show(struct kobject *kobj,836struct kobj_attribute *attr, char *buf)837{838ssize_t retval = 0;839struct msg_group_t *group = spk_find_msg_group(attr->attr.name);840unsigned long flags;841842if (WARN_ON(!group))843return -EINVAL;844845spin_lock_irqsave(&speakup_info.spinlock, flags);846retval = message_show_helper(buf, group->start, group->end);847spin_unlock_irqrestore(&speakup_info.spinlock, flags);848return retval;849}850851static ssize_t message_store(struct kobject *kobj, struct kobj_attribute *attr,852const char *buf, size_t count)853{854struct msg_group_t *group = spk_find_msg_group(attr->attr.name);855856if (WARN_ON(!group))857return -EINVAL;858859return message_store_helper(buf, count, group);860}861862/*863* Declare the attributes.864*/865static struct kobj_attribute keymap_attribute =866__ATTR_RW(keymap);867static struct kobj_attribute silent_attribute =868__ATTR_WO(silent);869static struct kobj_attribute synth_attribute =870__ATTR_RW(synth);871static struct kobj_attribute synth_direct_attribute =872__ATTR_WO(synth_direct);873static struct kobj_attribute version_attribute =874__ATTR_RO(version);875876static struct kobj_attribute delimiters_attribute =877__ATTR(delimiters, 0644, punc_show, punc_store);878static struct kobj_attribute ex_num_attribute =879__ATTR(ex_num, 0644, punc_show, punc_store);880static struct kobj_attribute punc_all_attribute =881__ATTR(punc_all, 0644, punc_show, punc_store);882static struct kobj_attribute punc_most_attribute =883__ATTR(punc_most, 0644, punc_show, punc_store);884static struct kobj_attribute punc_some_attribute =885__ATTR(punc_some, 0644, punc_show, punc_store);886static struct kobj_attribute repeats_attribute =887__ATTR(repeats, 0644, punc_show, punc_store);888889static struct kobj_attribute attrib_bleep_attribute =890__ATTR(attrib_bleep, 0644, spk_var_show, spk_var_store);891static struct kobj_attribute bell_pos_attribute =892__ATTR(bell_pos, 0644, spk_var_show, spk_var_store);893static struct kobj_attribute bleep_time_attribute =894__ATTR(bleep_time, 0644, spk_var_show, spk_var_store);895static struct kobj_attribute bleeps_attribute =896__ATTR(bleeps, 0644, spk_var_show, spk_var_store);897static struct kobj_attribute cursor_time_attribute =898__ATTR(cursor_time, 0644, spk_var_show, spk_var_store);899static struct kobj_attribute key_echo_attribute =900__ATTR(key_echo, 0644, spk_var_show, spk_var_store);901static struct kobj_attribute no_interrupt_attribute =902__ATTR(no_interrupt, 0644, spk_var_show, spk_var_store);903static struct kobj_attribute punc_level_attribute =904__ATTR(punc_level, 0644, spk_var_show, spk_var_store);905static struct kobj_attribute reading_punc_attribute =906__ATTR(reading_punc, 0644, spk_var_show, spk_var_store);907static struct kobj_attribute say_control_attribute =908__ATTR(say_control, 0644, spk_var_show, spk_var_store);909static struct kobj_attribute say_word_ctl_attribute =910__ATTR(say_word_ctl, 0644, spk_var_show, spk_var_store);911static struct kobj_attribute spell_delay_attribute =912__ATTR(spell_delay, 0644, spk_var_show, spk_var_store);913static struct kobj_attribute cur_phonetic_attribute =914__ATTR(cur_phonetic, 0644, spk_var_show, spk_var_store);915916/*917* These attributes are i18n related.918*/919static struct kobj_attribute announcements_attribute =920__ATTR(announcements, 0644, message_show, message_store);921static struct kobj_attribute characters_attribute =922__ATTR(characters, 0644, chars_chartab_show,923chars_chartab_store);924static struct kobj_attribute chartab_attribute =925__ATTR(chartab, 0644, chars_chartab_show,926chars_chartab_store);927static struct kobj_attribute ctl_keys_attribute =928__ATTR(ctl_keys, 0644, message_show, message_store);929static struct kobj_attribute colors_attribute =930__ATTR(colors, 0644, message_show, message_store);931static struct kobj_attribute formatted_attribute =932__ATTR(formatted, 0644, message_show, message_store);933static struct kobj_attribute function_names_attribute =934__ATTR(function_names, 0644, message_show, message_store);935static struct kobj_attribute key_names_attribute =936__ATTR(key_names, 0644, message_show, message_store);937static struct kobj_attribute states_attribute =938__ATTR(states, 0644, message_show, message_store);939940/*941* Create groups of attributes so that we can create and destroy them all942* at once.943*/944static struct attribute *main_attrs[] = {945&keymap_attribute.attr,946&silent_attribute.attr,947&synth_attribute.attr,948&synth_direct_attribute.attr,949&version_attribute.attr,950&delimiters_attribute.attr,951&ex_num_attribute.attr,952&punc_all_attribute.attr,953&punc_most_attribute.attr,954&punc_some_attribute.attr,955&repeats_attribute.attr,956&attrib_bleep_attribute.attr,957&bell_pos_attribute.attr,958&bleep_time_attribute.attr,959&bleeps_attribute.attr,960&cursor_time_attribute.attr,961&key_echo_attribute.attr,962&no_interrupt_attribute.attr,963&punc_level_attribute.attr,964&reading_punc_attribute.attr,965&say_control_attribute.attr,966&say_word_ctl_attribute.attr,967&spell_delay_attribute.attr,968&cur_phonetic_attribute.attr,969NULL,970};971972static struct attribute *i18n_attrs[] = {973&announcements_attribute.attr,974&characters_attribute.attr,975&chartab_attribute.attr,976&ctl_keys_attribute.attr,977&colors_attribute.attr,978&formatted_attribute.attr,979&function_names_attribute.attr,980&key_names_attribute.attr,981&states_attribute.attr,982NULL,983};984985/*986* An unnamed attribute group will put all of the attributes directly in987* the kobject directory. If we specify a name, a subdirectory will be988* created for the attributes with the directory being the name of the989* attribute group.990*/991static const struct attribute_group main_attr_group = {992.attrs = main_attrs,993};994995static const struct attribute_group i18n_attr_group = {996.attrs = i18n_attrs,997.name = "i18n",998};9991000static struct kobject *accessibility_kobj;1001struct kobject *speakup_kobj;10021003int speakup_kobj_init(void)1004{1005int retval;10061007/*1008* Create a simple kobject with the name of "accessibility",1009* located under /sys/1010*1011* As this is a simple directory, no uevent will be sent to1012* userspace. That is why this function should not be used for1013* any type of dynamic kobjects, where the name and number are1014* not known ahead of time.1015*/1016accessibility_kobj = kobject_create_and_add("accessibility", NULL);1017if (!accessibility_kobj) {1018retval = -ENOMEM;1019goto out;1020}10211022speakup_kobj = kobject_create_and_add("speakup", accessibility_kobj);1023if (!speakup_kobj) {1024retval = -ENOMEM;1025goto err_acc;1026}10271028/* Create the files associated with this kobject */1029retval = sysfs_create_group(speakup_kobj, &main_attr_group);1030if (retval)1031goto err_speakup;10321033retval = sysfs_create_group(speakup_kobj, &i18n_attr_group);1034if (retval)1035goto err_group;10361037goto out;10381039err_group:1040sysfs_remove_group(speakup_kobj, &main_attr_group);1041err_speakup:1042kobject_put(speakup_kobj);1043err_acc:1044kobject_put(accessibility_kobj);1045out:1046return retval;1047}10481049void speakup_kobj_exit(void)1050{1051sysfs_remove_group(speakup_kobj, &i18n_attr_group);1052sysfs_remove_group(speakup_kobj, &main_attr_group);1053kobject_put(speakup_kobj);1054kobject_put(accessibility_kobj);1055}105610571058