root/drivers/accessibility/speakup/kobjects.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Speakup kobject implementation
 *
 * Copyright (C) 2009 William Hubbs
 *
 * This code is based on kobject-example.c, which came with linux 2.6.x.
 *
 * Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com>
 * Copyright (C) 2007 Novell Inc.
 *
 * Released under the GPL version 2 only.
 *
 */
#include <linux/slab.h>         /* For kmalloc. */
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/string_helpers.h>
#include <linux/sysfs.h>
#include <linux/ctype.h>

#include "speakup.h"
#include "spk_priv.h"

/*
 * This is called when a user reads the characters or chartab sys file.
 */
static ssize_t chars_chartab_show(struct kobject *kobj,
                                  struct kobj_attribute *attr, char *buf)
{
        int i;
        int len = 0;
        char *cp;
        char *buf_pointer = buf;
        size_t bufsize = PAGE_SIZE;
        unsigned long flags;

        spin_lock_irqsave(&speakup_info.spinlock, flags);
        *buf_pointer = '\0';
        for (i = 0; i < 256; i++) {
                if (bufsize <= 1)
                        break;
                if (strcmp("characters", attr->attr.name) == 0) {
                        len = scnprintf(buf_pointer, bufsize, "%d\t%s\n",
                                        i, spk_characters[i]);
                } else {        /* show chartab entry */
                        if (IS_TYPE(i, B_CTL))
                                cp = "B_CTL";
                        else if (IS_TYPE(i, WDLM))
                                cp = "WDLM";
                        else if (IS_TYPE(i, A_PUNC))
                                cp = "A_PUNC";
                        else if (IS_TYPE(i, PUNC))
                                cp = "PUNC";
                        else if (IS_TYPE(i, NUM))
                                cp = "NUM";
                        else if (IS_TYPE(i, A_CAP))
                                cp = "A_CAP";
                        else if (IS_TYPE(i, ALPHA))
                                cp = "ALPHA";
                        else if (IS_TYPE(i, B_CAPSYM))
                                cp = "B_CAPSYM";
                        else if (IS_TYPE(i, B_SYM))
                                cp = "B_SYM";
                        else
                                cp = "0";
                        len =
                            scnprintf(buf_pointer, bufsize, "%d\t%s\n", i, cp);
                }
                bufsize -= len;
                buf_pointer += len;
        }
        spin_unlock_irqrestore(&speakup_info.spinlock, flags);
        return buf_pointer - buf;
}

/*
 * Print informational messages or warnings after updating
 * character descriptions or chartab entries.
 */
static void report_char_chartab_status(int reset, int received, int used,
                                       int rejected, int do_characters)
{
        static char const *object_type[] = {
                "character class entries",
                "character descriptions",
        };
        int len;
        char buf[80];

        if (reset) {
                pr_info("%s reset to defaults\n", object_type[do_characters]);
        } else if (received) {
                len = snprintf(buf, sizeof(buf),
                               " updated %d of %d %s\n",
                               used, received, object_type[do_characters]);
                if (rejected)
                        snprintf(buf + (len - 1), sizeof(buf) - (len - 1),
                                 " with %d reject%s\n",
                                 rejected, rejected > 1 ? "s" : "");
                pr_info("%s", buf);
        }
}

/*
 * This is called when a user changes the characters or chartab parameters.
 */
static ssize_t chars_chartab_store(struct kobject *kobj,
                                   struct kobj_attribute *attr,
                                   const char *buf, size_t count)
{
        char *cp = (char *)buf;
        char *end = cp + count; /* the null at the end of the buffer */
        char *linefeed = NULL;
        char keyword[MAX_DESC_LEN + 1];
        char *outptr = NULL;    /* Will hold keyword or desc. */
        char *temp = NULL;
        char *desc = NULL;
        ssize_t retval = count;
        unsigned long flags;
        unsigned long index = 0;
        int charclass = 0;
        int received = 0;
        int used = 0;
        int rejected = 0;
        int reset = 0;
        int do_characters = !strcmp(attr->attr.name, "characters");
        size_t desc_length = 0;
        int i;

        spin_lock_irqsave(&speakup_info.spinlock, flags);
        while (cp < end) {
                while ((cp < end) && (*cp == ' ' || *cp == '\t'))
                        cp++;

                if (cp == end)
                        break;
                if ((*cp == '\n') || strchr("dDrR", *cp)) {
                        reset = 1;
                        break;
                }
                received++;

                linefeed = strchr(cp, '\n');
                if (!linefeed) {
                        rejected++;
                        break;
                }

                if (!isdigit(*cp)) {
                        rejected++;
                        cp = linefeed + 1;
                        continue;
                }

                /*
                 * Do not replace with kstrtoul:
                 * here we need temp to be updated
                 */
                index = simple_strtoul(cp, &temp, 10);
                if (index > 255) {
                        rejected++;
                        cp = linefeed + 1;
                        continue;
                }

                while ((temp < linefeed) && (*temp == ' ' || *temp == '\t'))
                        temp++;

                desc_length = linefeed - temp;
                if (desc_length > MAX_DESC_LEN) {
                        rejected++;
                        cp = linefeed + 1;
                        continue;
                }
                if (do_characters) {
                        desc = kmalloc(desc_length + 1, GFP_ATOMIC);
                        if (!desc) {
                                retval = -ENOMEM;
                                reset = 1;      /* just reset on error. */
                                break;
                        }
                        outptr = desc;
                } else {
                        outptr = keyword;
                }

                for (i = 0; i < desc_length; i++)
                        outptr[i] = temp[i];
                outptr[desc_length] = '\0';

                if (do_characters) {
                        if (spk_characters[index] != spk_default_chars[index])
                                kfree(spk_characters[index]);
                        spk_characters[index] = desc;
                        used++;
                } else {
                        charclass = spk_chartab_get_value(keyword);
                        if (charclass == 0) {
                                rejected++;
                                cp = linefeed + 1;
                                continue;
                        }
                        if (charclass != spk_chartab[index]) {
                                spk_chartab[index] = charclass;
                                used++;
                        }
                }
                cp = linefeed + 1;
        }

        if (reset) {
                if (do_characters)
                        spk_reset_default_chars();
                else
                        spk_reset_default_chartab();
        }

        spin_unlock_irqrestore(&speakup_info.spinlock, flags);
        report_char_chartab_status(reset, received, used, rejected,
                                   do_characters);
        return retval;
}

/*
 * This is called when a user reads the keymap parameter.
 */
static ssize_t keymap_show(struct kobject *kobj, struct kobj_attribute *attr,
                           char *buf)
{
        char *cp = buf;
        int i;
        int n;
        int num_keys;
        int nstates;
        u_char *cp1;
        u_char ch;
        unsigned long flags;

        spin_lock_irqsave(&speakup_info.spinlock, flags);
        cp1 = spk_key_buf + SHIFT_TBL_SIZE;
        num_keys = (int)(*cp1);
        nstates = (int)cp1[1];
        cp += sprintf(cp, "%d, %d, %d,\n", KEY_MAP_VER, num_keys, nstates);
        cp1 += 2; /* now pointing at shift states */
        /* dump num_keys+1 as first row is shift states + flags,
         * each subsequent row is key + states
         */
        for (n = 0; n <= num_keys; n++) {
                for (i = 0; i <= nstates; i++) {
                        ch = *cp1++;
                        cp += sprintf(cp, "%d,", (int)ch);
                        *cp++ = (i < nstates) ? SPACE : '\n';
                }
        }
        cp += sprintf(cp, "0, %d\n", KEY_MAP_VER);
        spin_unlock_irqrestore(&speakup_info.spinlock, flags);
        return (int)(cp - buf);
}

/*
 * This is called when a user changes the keymap parameter.
 */
static ssize_t keymap_store(struct kobject *kobj, struct kobj_attribute *attr,
                            const char *buf, size_t count)
{
        int i;
        ssize_t ret = count;
        char *in_buff = NULL;
        char *cp;
        u_char *cp1;
        unsigned long flags;

        spin_lock_irqsave(&speakup_info.spinlock, flags);
        in_buff = kmemdup(buf, count + 1, GFP_ATOMIC);
        if (!in_buff) {
                spin_unlock_irqrestore(&speakup_info.spinlock, flags);
                return -ENOMEM;
        }
        if (strchr("dDrR", *in_buff)) {
                spk_set_key_info(spk_key_defaults, spk_key_buf);
                pr_info("keymap set to default values\n");
                kfree(in_buff);
                spin_unlock_irqrestore(&speakup_info.spinlock, flags);
                return count;
        }
        if (in_buff[count - 1] == '\n')
                in_buff[count - 1] = '\0';
        cp = in_buff;
        cp1 = (u_char *)in_buff;
        for (i = 0; i < 3; i++) {
                cp = spk_s2uchar(cp, cp1);
                cp1++;
        }
        i = (int)cp1[-2] + 1;
        i *= (int)cp1[-1] + 1;
        i += 2; /* 0 and last map ver */
        if (cp1[-3] != KEY_MAP_VER || cp1[-1] > 10 ||
            i + SHIFT_TBL_SIZE + 4 >= sizeof(spk_key_buf)) {
                pr_warn("i %d %d %d %d\n", i,
                        (int)cp1[-3], (int)cp1[-2], (int)cp1[-1]);
                kfree(in_buff);
                spin_unlock_irqrestore(&speakup_info.spinlock, flags);
                return -EINVAL;
        }
        while (--i >= 0) {
                cp = spk_s2uchar(cp, cp1);
                cp1++;
                if (!(*cp))
                        break;
        }
        if (i != 0 || cp1[-1] != KEY_MAP_VER || cp1[-2] != 0) {
                ret = -EINVAL;
                pr_warn("end %d %d %d %d\n", i,
                        (int)cp1[-3], (int)cp1[-2], (int)cp1[-1]);
        } else {
                if (spk_set_key_info(in_buff, spk_key_buf)) {
                        spk_set_key_info(spk_key_defaults, spk_key_buf);
                        ret = -EINVAL;
                        pr_warn("set key failed\n");
                }
        }
        kfree(in_buff);
        spin_unlock_irqrestore(&speakup_info.spinlock, flags);
        return ret;
}

/*
 * This is called when a user changes the value of the silent parameter.
 */
static ssize_t silent_store(struct kobject *kobj, struct kobj_attribute *attr,
                            const char *buf, size_t count)
{
        int len;
        struct vc_data *vc = vc_cons[fg_console].d;
        char ch = 0;
        char shut;
        unsigned long flags;

        len = strlen(buf);
        if (len > 0 && len < 3) {
                ch = buf[0];
                if (ch == '\n')
                        ch = '0';
        }
        if (ch < '0' || ch > '7') {
                pr_warn("silent value '%c' not in range (0,7)\n", ch);
                return -EINVAL;
        }
        spin_lock_irqsave(&speakup_info.spinlock, flags);
        if (ch & 2) {
                shut = 1;
                spk_do_flush();
        } else {
                shut = 0;
        }
        if (ch & 4)
                shut |= 0x40;
        if (ch & 1)
                spk_shut_up |= shut;
        else
                spk_shut_up &= ~shut;
        spin_unlock_irqrestore(&speakup_info.spinlock, flags);
        return count;
}

/*
 * This is called when a user reads the synth setting.
 */
static ssize_t synth_show(struct kobject *kobj, struct kobj_attribute *attr,
                          char *buf)
{
        int rv;

        if (!synth)
                rv = sprintf(buf, "%s\n", "none");
        else
                rv = sprintf(buf, "%s\n", synth->name);
        return rv;
}

/*
 * This is called when a user requests to change synthesizers.
 */
static ssize_t synth_store(struct kobject *kobj, struct kobj_attribute *attr,
                           const char *buf, size_t count)
{
        int len;
        char new_synth_name[10];

        len = strlen(buf);
        if (len < 2 || len > 9)
                return -EINVAL;
        memcpy(new_synth_name, buf, len);
        if (new_synth_name[len - 1] == '\n')
                len--;
        new_synth_name[len] = '\0';
        spk_strlwr(new_synth_name);
        if (synth && !strcmp(new_synth_name, synth->name)) {
                pr_warn("%s already in use\n", new_synth_name);
        } else if (synth_init(new_synth_name) != 0) {
                pr_warn("failed to init synth %s\n", new_synth_name);
                return -ENODEV;
        }
        return count;
}

/*
 * This is called when text is sent to the synth via the synth_direct file.
 */
static ssize_t synth_direct_store(struct kobject *kobj,
                                  struct kobj_attribute *attr,
                                  const char *buf, size_t count)
{
        char *unescaped;
        unsigned long flags;

        if (!synth)
                return -EPERM;

        unescaped = kstrdup(buf, GFP_KERNEL);
        if (!unescaped)
                return -ENOMEM;

        string_unescape_any_inplace(unescaped);

        spin_lock_irqsave(&speakup_info.spinlock, flags);
        synth_write(unescaped, strlen(unescaped));
        spin_unlock_irqrestore(&speakup_info.spinlock, flags);

        kfree(unescaped);

        return count;
}

/*
 * This function is called when a user reads the version.
 */
static ssize_t version_show(struct kobject *kobj, struct kobj_attribute *attr,
                            char *buf)
{
        char *cp;

        cp = buf;
        cp += sprintf(cp, "Speakup version %s\n", SPEAKUP_VERSION);
        if (synth)
                cp += sprintf(cp, "%s synthesizer driver version %s\n",
                synth->name, synth->version);
        return cp - buf;
}

/*
 * This is called when a user reads the punctuation settings.
 */
static ssize_t punc_show(struct kobject *kobj, struct kobj_attribute *attr,
                         char *buf)
{
        int i;
        char *cp = buf;
        struct st_var_header *p_header;
        struct punc_var_t *var;
        struct st_bits_data *pb;
        short mask;
        unsigned long flags;

        p_header = spk_var_header_by_name(attr->attr.name);
        if (!p_header) {
                pr_warn("p_header is null, attr->attr.name is %s\n",
                        attr->attr.name);
                return -EINVAL;
        }

        var = spk_get_punc_var(p_header->var_id);
        if (!var) {
                pr_warn("var is null, p_header->var_id is %i\n",
                        p_header->var_id);
                return -EINVAL;
        }

        spin_lock_irqsave(&speakup_info.spinlock, flags);
        pb = (struct st_bits_data *)&spk_punc_info[var->value];
        mask = pb->mask;
        for (i = 33; i < 128; i++) {
                if (!(spk_chartab[i] & mask))
                        continue;
                *cp++ = (char)i;
        }
        spin_unlock_irqrestore(&speakup_info.spinlock, flags);
        return cp - buf;
}

/*
 * This is called when a user changes the punctuation settings.
 */
static ssize_t punc_store(struct kobject *kobj, struct kobj_attribute *attr,
                          const char *buf, size_t count)
{
        int x;
        struct st_var_header *p_header;
        struct punc_var_t *var;
        char punc_buf[100];
        unsigned long flags;

        x = strlen(buf);
        if (x < 1 || x > 99)
                return -EINVAL;

        p_header = spk_var_header_by_name(attr->attr.name);
        if (!p_header) {
                pr_warn("p_header is null, attr->attr.name is %s\n",
                        attr->attr.name);
                return -EINVAL;
        }

        var = spk_get_punc_var(p_header->var_id);
        if (!var) {
                pr_warn("var is null, p_header->var_id is %i\n",
                        p_header->var_id);
                return -EINVAL;
        }

        memcpy(punc_buf, buf, x);

        while (x && punc_buf[x - 1] == '\n')
                x--;
        punc_buf[x] = '\0';

        spin_lock_irqsave(&speakup_info.spinlock, flags);

        if (*punc_buf == 'd' || *punc_buf == 'r')
                x = spk_set_mask_bits(NULL, var->value, 3);
        else
                x = spk_set_mask_bits(punc_buf, var->value, 3);

        spin_unlock_irqrestore(&speakup_info.spinlock, flags);
        return count;
}

/*
 * This function is called when a user reads one of the variable parameters.
 */
ssize_t spk_var_show(struct kobject *kobj, struct kobj_attribute *attr,
                     char *buf)
{
        int rv = 0;
        struct st_var_header *param;
        struct var_t *var;
        char *cp1;
        char *cp;
        char ch;
        unsigned long flags;

        param = spk_var_header_by_name(attr->attr.name);
        if (!param)
                return -EINVAL;

        spin_lock_irqsave(&speakup_info.spinlock, flags);
        var = (struct var_t *)param->data;
        switch (param->var_type) {
        case VAR_NUM:
        case VAR_TIME:
                if (var)
                        rv = sprintf(buf, "%i\n", var->u.n.value);
                else
                        rv = sprintf(buf, "0\n");
                break;
        case VAR_STRING:
                if (var) {
                        cp1 = buf;
                        *cp1++ = '"';
                        for (cp = (char *)param->p_val; (ch = *cp); cp++) {
                                if (ch >= ' ' && ch < '~')
                                        *cp1++ = ch;
                                else
                                        cp1 += sprintf(cp1, "\\x%02x", ch);
                        }
                        *cp1++ = '"';
                        *cp1++ = '\n';
                        *cp1 = '\0';
                        rv = cp1 - buf;
                } else {
                        rv = sprintf(buf, "\"\"\n");
                }
                break;
        default:
                rv = sprintf(buf, "Bad parameter  %s, type %i\n",
                             param->name, param->var_type);
                break;
        }
        spin_unlock_irqrestore(&speakup_info.spinlock, flags);
        return rv;
}
EXPORT_SYMBOL_GPL(spk_var_show);

/*
 * Used to reset either default_pitch or default_vol.
 */
static inline void spk_reset_default_value(char *header_name,
                                           int *synth_default_value, int idx)
{
        struct st_var_header *param;

        if (synth && synth_default_value) {
                param = spk_var_header_by_name(header_name);
                if (param)  {
                        spk_set_num_var(synth_default_value[idx],
                                        param, E_NEW_DEFAULT);
                        spk_set_num_var(0, param, E_DEFAULT);
                        pr_info("%s reset to default value\n", param->name);
                }
        }
}

/*
 * This function is called when a user echos a value to one of the
 * variable parameters.
 */
ssize_t spk_var_store(struct kobject *kobj, struct kobj_attribute *attr,
                      const char *buf, size_t count)
{
        struct st_var_header *param;
        int ret;
        int len;
        char *cp;
        struct var_t *var_data;
        long value;
        unsigned long flags;

        param = spk_var_header_by_name(attr->attr.name);
        if (!param)
                return -EINVAL;
        if (!param->data)
                return 0;
        ret = 0;
        cp = (char *)buf;
        string_unescape_any_inplace(cp);

        spin_lock_irqsave(&speakup_info.spinlock, flags);
        switch (param->var_type) {
        case VAR_NUM:
        case VAR_TIME:
                if (*cp == 'd' || *cp == 'r' || *cp == '\0')
                        len = E_DEFAULT;
                else if (*cp == '+' || *cp == '-')
                        len = E_INC;
                else
                        len = E_SET;
                if (kstrtol(cp, 10, &value) == 0)
                        ret = spk_set_num_var(value, param, len);
                else
                        pr_warn("overflow or parsing error has occurred");
                if (ret == -ERANGE) {
                        var_data = param->data;
                        pr_warn("value for %s out of range, expect %d to %d\n",
                                param->name,
                                var_data->u.n.low, var_data->u.n.high);
                }

               /*
                * If voice was just changed, we might need to reset our default
                * pitch and volume.
                */
                if (param->var_id == VOICE && synth &&
                    (ret == 0 || ret == -ERESTART)) {
                        var_data = param->data;
                        value = var_data->u.n.value;
                        spk_reset_default_value("pitch", synth->default_pitch,
                                                value);
                        spk_reset_default_value("vol", synth->default_vol,
                                                value);
                }
                break;
        case VAR_STRING:
                len = strlen(cp);
                if ((len >= 1) && (cp[len - 1] == '\n'))
                        --len;
                if ((len >= 2) && (cp[0] == '"') && (cp[len - 1] == '"')) {
                        ++cp;
                        len -= 2;
                }
                cp[len] = '\0';
                ret = spk_set_string_var(cp, param, len);
                if (ret == -E2BIG)
                        pr_warn("value too long for %s\n",
                                param->name);
                break;
        default:
                pr_warn("%s unknown type %d\n",
                        param->name, (int)param->var_type);
        break;
        }
        spin_unlock_irqrestore(&speakup_info.spinlock, flags);

        if (ret == -ERESTART)
                pr_info("%s reset to default value\n", param->name);
        return count;
}
EXPORT_SYMBOL_GPL(spk_var_store);

/*
 * Functions for reading and writing lists of i18n messages.  Incomplete.
 */

static ssize_t message_show_helper(char *buf, enum msg_index_t first,
                                   enum msg_index_t last)
{
        size_t bufsize = PAGE_SIZE;
        char *buf_pointer = buf;
        int printed;
        enum msg_index_t cursor;
        int index = 0;
        *buf_pointer = '\0'; /* buf_pointer always looking at a NUL byte. */

        for (cursor = first; cursor <= last; cursor++, index++) {
                if (bufsize <= 1)
                        break;
                printed = scnprintf(buf_pointer, bufsize, "%d\t%s\n",
                                    index, spk_msg_get(cursor));
                buf_pointer += printed;
                bufsize -= printed;
        }

        return buf_pointer - buf;
}

static void report_msg_status(int reset, int received, int used,
                              int rejected, char *groupname)
{
        int len;
        char buf[160];

        if (reset) {
                pr_info("i18n messages from group %s reset to defaults\n",
                        groupname);
        } else if (received) {
                len = snprintf(buf, sizeof(buf),
                               " updated %d of %d i18n messages from group %s\n",
                                       used, received, groupname);
                if (rejected)
                        snprintf(buf + (len - 1), sizeof(buf) - (len - 1),
                                 " with %d reject%s\n",
                                 rejected, rejected > 1 ? "s" : "");
                pr_info("%s", buf);
        }
}

static ssize_t message_store_helper(const char *buf, size_t count,
                                    struct msg_group_t *group)
{
        char *cp = (char *)buf;
        char *end = cp + count;
        char *linefeed = NULL;
        char *temp = NULL;
        ssize_t msg_stored = 0;
        ssize_t retval = count;
        size_t desc_length = 0;
        unsigned long index = 0;
        int received = 0;
        int used = 0;
        int rejected = 0;
        int reset = 0;
        enum msg_index_t firstmessage = group->start;
        enum msg_index_t lastmessage = group->end;
        enum msg_index_t curmessage;

        while (cp < end) {
                while ((cp < end) && (*cp == ' ' || *cp == '\t'))
                        cp++;

                if (cp == end)
                        break;
                if (strchr("dDrR", *cp)) {
                        reset = 1;
                        break;
                }
                received++;

                linefeed = strchr(cp, '\n');
                if (!linefeed) {
                        rejected++;
                        break;
                }

                if (!isdigit(*cp)) {
                        rejected++;
                        cp = linefeed + 1;
                        continue;
                }

                /*
                 * Do not replace with kstrtoul:
                 * here we need temp to be updated
                 */
                index = simple_strtoul(cp, &temp, 10);

                while ((temp < linefeed) && (*temp == ' ' || *temp == '\t'))
                        temp++;

                desc_length = linefeed - temp;
                curmessage = firstmessage + index;

                /*
                 * Note the check (curmessage < firstmessage).  It is not
                 * redundant.  Suppose that the user gave us an index
                 * equal to ULONG_MAX - 1.  If firstmessage > 1, then
                 * firstmessage + index < firstmessage!
                 */

                if ((curmessage < firstmessage) || (curmessage > lastmessage)) {
                        rejected++;
                        cp = linefeed + 1;
                        continue;
                }

                msg_stored = spk_msg_set(curmessage, temp, desc_length);
                if (msg_stored < 0) {
                        retval = msg_stored;
                        if (msg_stored == -ENOMEM)
                                reset = 1;
                        break;
                }

                used++;

                cp = linefeed + 1;
        }

        if (reset)
                spk_reset_msg_group(group);

        report_msg_status(reset, received, used, rejected, group->name);
        return retval;
}

static ssize_t message_show(struct kobject *kobj,
                            struct kobj_attribute *attr, char *buf)
{
        ssize_t retval = 0;
        struct msg_group_t *group = spk_find_msg_group(attr->attr.name);
        unsigned long flags;

        if (WARN_ON(!group))
                return -EINVAL;

        spin_lock_irqsave(&speakup_info.spinlock, flags);
        retval = message_show_helper(buf, group->start, group->end);
        spin_unlock_irqrestore(&speakup_info.spinlock, flags);
        return retval;
}

static ssize_t message_store(struct kobject *kobj, struct kobj_attribute *attr,
                             const char *buf, size_t count)
{
        struct msg_group_t *group = spk_find_msg_group(attr->attr.name);

        if (WARN_ON(!group))
                return -EINVAL;

        return message_store_helper(buf, count, group);
}

/*
 * Declare the attributes.
 */
static struct kobj_attribute keymap_attribute =
        __ATTR_RW(keymap);
static struct kobj_attribute silent_attribute =
        __ATTR_WO(silent);
static struct kobj_attribute synth_attribute =
        __ATTR_RW(synth);
static struct kobj_attribute synth_direct_attribute =
        __ATTR_WO(synth_direct);
static struct kobj_attribute version_attribute =
        __ATTR_RO(version);

static struct kobj_attribute delimiters_attribute =
        __ATTR(delimiters, 0644, punc_show, punc_store);
static struct kobj_attribute ex_num_attribute =
        __ATTR(ex_num, 0644, punc_show, punc_store);
static struct kobj_attribute punc_all_attribute =
        __ATTR(punc_all, 0644, punc_show, punc_store);
static struct kobj_attribute punc_most_attribute =
        __ATTR(punc_most, 0644, punc_show, punc_store);
static struct kobj_attribute punc_some_attribute =
        __ATTR(punc_some, 0644, punc_show, punc_store);
static struct kobj_attribute repeats_attribute =
        __ATTR(repeats, 0644, punc_show, punc_store);

static struct kobj_attribute attrib_bleep_attribute =
        __ATTR(attrib_bleep, 0644, spk_var_show, spk_var_store);
static struct kobj_attribute bell_pos_attribute =
        __ATTR(bell_pos, 0644, spk_var_show, spk_var_store);
static struct kobj_attribute bleep_time_attribute =
        __ATTR(bleep_time, 0644, spk_var_show, spk_var_store);
static struct kobj_attribute bleeps_attribute =
        __ATTR(bleeps, 0644, spk_var_show, spk_var_store);
static struct kobj_attribute cursor_time_attribute =
        __ATTR(cursor_time, 0644, spk_var_show, spk_var_store);
static struct kobj_attribute key_echo_attribute =
        __ATTR(key_echo, 0644, spk_var_show, spk_var_store);
static struct kobj_attribute no_interrupt_attribute =
        __ATTR(no_interrupt, 0644, spk_var_show, spk_var_store);
static struct kobj_attribute punc_level_attribute =
        __ATTR(punc_level, 0644, spk_var_show, spk_var_store);
static struct kobj_attribute reading_punc_attribute =
        __ATTR(reading_punc, 0644, spk_var_show, spk_var_store);
static struct kobj_attribute say_control_attribute =
        __ATTR(say_control, 0644, spk_var_show, spk_var_store);
static struct kobj_attribute say_word_ctl_attribute =
        __ATTR(say_word_ctl, 0644, spk_var_show, spk_var_store);
static struct kobj_attribute spell_delay_attribute =
        __ATTR(spell_delay, 0644, spk_var_show, spk_var_store);
static struct kobj_attribute cur_phonetic_attribute =
        __ATTR(cur_phonetic, 0644, spk_var_show, spk_var_store);

/*
 * These attributes are i18n related.
 */
static struct kobj_attribute announcements_attribute =
        __ATTR(announcements, 0644, message_show, message_store);
static struct kobj_attribute characters_attribute =
        __ATTR(characters, 0644, chars_chartab_show,
               chars_chartab_store);
static struct kobj_attribute chartab_attribute =
        __ATTR(chartab, 0644, chars_chartab_show,
               chars_chartab_store);
static struct kobj_attribute ctl_keys_attribute =
        __ATTR(ctl_keys, 0644, message_show, message_store);
static struct kobj_attribute colors_attribute =
        __ATTR(colors, 0644, message_show, message_store);
static struct kobj_attribute formatted_attribute =
        __ATTR(formatted, 0644, message_show, message_store);
static struct kobj_attribute function_names_attribute =
        __ATTR(function_names, 0644, message_show, message_store);
static struct kobj_attribute key_names_attribute =
        __ATTR(key_names, 0644, message_show, message_store);
static struct kobj_attribute states_attribute =
        __ATTR(states, 0644, message_show, message_store);

/*
 * Create groups of attributes so that we can create and destroy them all
 * at once.
 */
static struct attribute *main_attrs[] = {
        &keymap_attribute.attr,
        &silent_attribute.attr,
        &synth_attribute.attr,
        &synth_direct_attribute.attr,
        &version_attribute.attr,
        &delimiters_attribute.attr,
        &ex_num_attribute.attr,
        &punc_all_attribute.attr,
        &punc_most_attribute.attr,
        &punc_some_attribute.attr,
        &repeats_attribute.attr,
        &attrib_bleep_attribute.attr,
        &bell_pos_attribute.attr,
        &bleep_time_attribute.attr,
        &bleeps_attribute.attr,
        &cursor_time_attribute.attr,
        &key_echo_attribute.attr,
        &no_interrupt_attribute.attr,
        &punc_level_attribute.attr,
        &reading_punc_attribute.attr,
        &say_control_attribute.attr,
        &say_word_ctl_attribute.attr,
        &spell_delay_attribute.attr,
        &cur_phonetic_attribute.attr,
        NULL,
};

static struct attribute *i18n_attrs[] = {
        &announcements_attribute.attr,
        &characters_attribute.attr,
        &chartab_attribute.attr,
        &ctl_keys_attribute.attr,
        &colors_attribute.attr,
        &formatted_attribute.attr,
        &function_names_attribute.attr,
        &key_names_attribute.attr,
        &states_attribute.attr,
        NULL,
};

/*
 * An unnamed attribute group will put all of the attributes directly in
 * the kobject directory.  If we specify a name, a subdirectory will be
 * created for the attributes with the directory being the name of the
 * attribute group.
 */
static const struct attribute_group main_attr_group = {
        .attrs = main_attrs,
};

static const struct attribute_group i18n_attr_group = {
        .attrs = i18n_attrs,
        .name = "i18n",
};

static struct kobject *accessibility_kobj;
struct kobject *speakup_kobj;

int speakup_kobj_init(void)
{
        int retval;

        /*
         * Create a simple kobject with the name of "accessibility",
         * located under /sys/
         *
         * As this is a simple directory, no uevent will be sent to
         * userspace.  That is why this function should not be used for
         * any type of dynamic kobjects, where the name and number are
         * not known ahead of time.
         */
        accessibility_kobj = kobject_create_and_add("accessibility", NULL);
        if (!accessibility_kobj) {
                retval = -ENOMEM;
                goto out;
        }

        speakup_kobj = kobject_create_and_add("speakup", accessibility_kobj);
        if (!speakup_kobj) {
                retval = -ENOMEM;
                goto err_acc;
        }

        /* Create the files associated with this kobject */
        retval = sysfs_create_group(speakup_kobj, &main_attr_group);
        if (retval)
                goto err_speakup;

        retval = sysfs_create_group(speakup_kobj, &i18n_attr_group);
        if (retval)
                goto err_group;

        goto out;

err_group:
        sysfs_remove_group(speakup_kobj, &main_attr_group);
err_speakup:
        kobject_put(speakup_kobj);
err_acc:
        kobject_put(accessibility_kobj);
out:
        return retval;
}

void speakup_kobj_exit(void)
{
        sysfs_remove_group(speakup_kobj, &i18n_attr_group);
        sysfs_remove_group(speakup_kobj, &main_attr_group);
        kobject_put(speakup_kobj);
        kobject_put(accessibility_kobj);
}