From 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 21 Feb 2023 18:24:12 -0800 Subject: Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next Pull networking updates from Jakub Kicinski: "Core: - Add dedicated kmem_cache for typical/small skb->head, avoid having to access struct page at kfree time, and improve memory use. - Introduce sysctl to set default RPS configuration for new netdevs. - Define Netlink protocol specification format which can be used to describe messages used by each family and auto-generate parsers. Add tools for generating kernel data structures and uAPI headers. - Expose all net/core sysctls inside netns. - Remove 4s sleep in netpoll if carrier is instantly detected on boot. - Add configurable limit of MDB entries per port, and port-vlan. - Continue populating drop reasons throughout the stack. - Retire a handful of legacy Qdiscs and classifiers. Protocols: - Support IPv4 big TCP (TSO frames larger than 64kB). - Add IP_LOCAL_PORT_RANGE socket option, to control local port range on socket by socket basis. - Track and report in procfs number of MPTCP sockets used. - Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path manager. - IPv6: don't check net.ipv6.route.max_size and rely on garbage collection to free memory (similarly to IPv4). - Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986). - ICMP: add per-rate limit counters. - Add support for user scanning requests in ieee802154. - Remove static WEP support. - Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate reporting. - WiFi 7 EHT channel puncturing support (client & AP). BPF: - Add a rbtree data structure following the "next-gen data structure" precedent set by recently added linked list, that is, by using kfunc + kptr instead of adding a new BPF map type. - Expose XDP hints via kfuncs with initial support for RX hash and timestamp metadata. - Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to better support decap on GRE tunnel devices not operating in collect metadata. - Improve x86 JIT's codegen for PROBE_MEM runtime error checks. - Remove the need for trace_printk_lock for bpf_trace_printk and bpf_trace_vprintk helpers. - Extend libbpf's bpf_tracing.h support for tracing arguments of kprobes/uprobes and syscall as a special case. - Significantly reduce the search time for module symbols by livepatch and BPF. - Enable cpumasks to be used as kptrs, which is useful for tracing programs tracking which tasks end up running on which CPUs in different time intervals. - Add support for BPF trampoline on s390x and riscv64. - Add capability to export the XDP features supported by the NIC. - Add __bpf_kfunc tag for marking kernel functions as kfuncs. - Add cgroup.memory=nobpf kernel parameter option to disable BPF memory accounting for container environments. Netfilter: - Remove the CLUSTERIP target. It has been marked as obsolete for years, and we still have WARN splats wrt races of the out-of-band /proc interface installed by this target. - Add 'destroy' commands to nf_tables. They are identical to the existing 'delete' commands, but do not return an error if the referenced object (set, chain, rule...) did not exist. Driver API: - Improve cpumask_local_spread() locality to help NICs set the right IRQ affinity on AMD platforms. - Separate C22 and C45 MDIO bus transactions more clearly. - Introduce new DCB table to control DSCP rewrite on egress. - Support configuration of Physical Layer Collision Avoidance (PLCA) Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of shared medium Ethernet. - Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing preemption of low priority frames by high priority frames. - Add support for controlling MACSec offload using netlink SET. - Rework devlink instance refcounts to allow registration and de-registration under the instance lock. Split the code into multiple files, drop some of the unnecessarily granular locks and factor out common parts of netlink operation handling. - Add TX frame aggregation parameters (for USB drivers). - Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning messages with notifications for debug. - Allow offloading of UDP NEW connections via act_ct. - Add support for per action HW stats in TC. - Support hardware miss to TC action (continue processing in SW from a specific point in the action chain). - Warn if old Wireless Extension user space interface is used with modern cfg80211/mac80211 drivers. Do not support Wireless Extensions for Wi-Fi 7 devices at all. Everyone should switch to using nl80211 interface instead. - Improve the CAN bit timing configuration. Use extack to return error messages directly to user space, update the SJW handling, including the definition of a new default value that will benefit CAN-FD controllers, by increasing their oscillator tolerance. New hardware / drivers: - Ethernet: - nVidia BlueField-3 support (control traffic driver) - Ethernet support for imx93 SoCs - Motorcomm yt8531 gigabit Ethernet PHY - onsemi NCN26000 10BASE-T1S PHY (with support for PLCA) - Microchip LAN8841 PHY (incl. cable diagnostics and PTP) - Amlogic gxl MDIO mux - WiFi: - RealTek RTL8188EU (rtl8xxxu) - Qualcomm Wi-Fi 7 devices (ath12k) - CAN: - Renesas R-Car V4H Drivers: - Bluetooth: - Set Per Platform Antenna Gain (PPAG) for Intel controllers. - Ethernet NICs: - Intel (1G, igc): - support TSN / Qbv / packet scheduling features of i226 model - Intel (100G, ice): - use GNSS subsystem instead of TTY - multi-buffer XDP support - extend support for GPIO pins to E823 devices - nVidia/Mellanox: - update the shared buffer configuration on PFC commands - implement PTP adjphase function for HW offset control - TC support for Geneve and GRE with VF tunnel offload - more efficient crypto key management method - multi-port eswitch support - Netronome/Corigine: - add DCB IEEE support - support IPsec offloading for NFP3800 - Freescale/NXP (enetc): - support XDP_REDIRECT for XDP non-linear buffers - improve reconfig, avoid link flap and waiting for idle - support MAC Merge layer - Other NICs: - sfc/ef100: add basic devlink support for ef100 - ionic: rx_push mode operation (writing descriptors via MMIO) - bnxt: use the auxiliary bus abstraction for RDMA - r8169: disable ASPM and reset bus in case of tx timeout - cpsw: support QSGMII mode for J721e CPSW9G - cpts: support pulse-per-second output - ngbe: add an mdio bus driver - usbnet: optimize usbnet_bh() by avoiding unnecessary queuing - r8152: handle devices with FW with NCM support - amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation - virtio-net: support multi buffer XDP - virtio/vsock: replace virtio_vsock_pkt with sk_buff - tsnep: XDP support - Ethernet high-speed switches: - nVidia/Mellanox (mlxsw): - add support for latency TLV (in FW control messages) - Microchip (sparx5): - separate explicit and implicit traffic forwarding rules, make the implicit rules always active - add support for egress DSCP rewrite - IS0 VCAP support (Ingress Classification) - IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS etc.) - ES2 VCAP support (Egress Access Control) - support for Per-Stream Filtering and Policing (802.1Q, 8.6.5.1) - Ethernet embedded switches: - Marvell (mv88e6xxx): - add MAB (port auth) offload support - enable PTP receive for mv88e6390 - NXP (ocelot): - support MAC Merge layer - support for the the vsc7512 internal copper phys - Microchip: - lan9303: convert to PHYLINK - lan966x: support TC flower filter statistics - lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x - lan937x: support Credit Based Shaper configuration - ksz9477: support Energy Efficient Ethernet - other: - qca8k: convert to regmap read/write API, use bulk operations - rswitch: Improve TX timestamp accuracy - Intel WiFi (iwlwifi): - EHT (Wi-Fi 7) rate reporting - STEP equalizer support: transfer some STEP (connection to radio on platforms with integrated wifi) related parameters from the BIOS to the firmware. - Qualcomm 802.11ax WiFi (ath11k): - IPQ5018 support - Fine Timing Measurement (FTM) responder role support - channel 177 support - MediaTek WiFi (mt76): - per-PHY LED support - mt7996: EHT (Wi-Fi 7) support - Wireless Ethernet Dispatch (WED) reset support - switch to using page pool allocator - RealTek WiFi (rtw89): - support new version of Bluetooth co-existance - Mobile: - rmnet: support TX aggregation" * tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits) page_pool: add a comment explaining the fragment counter usage net: ethtool: fix __ethtool_dev_mm_supported() implementation ethtool: pse-pd: Fix double word in comments xsk: add linux/vmalloc.h to xsk.c sefltests: netdevsim: wait for devlink instance after netns removal selftest: fib_tests: Always cleanup before exit net/mlx5e: Align IPsec ASO result memory to be as required by hardware net/mlx5e: TC, Set CT miss to the specific ct action instance net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG net/mlx5: Refactor tc miss handling to a single function net/mlx5: Kconfig: Make tc offload depend on tc skb extension net/sched: flower: Support hardware miss to tc action net/sched: flower: Move filter handle initialization earlier net/sched: cls_api: Support hardware miss to tc action net/sched: Rename user cookie and act cookie sfc: fix builds without CONFIG_RTC_LIB sfc: clean up some inconsistent indentings net/mlx4_en: Introduce flexible array to silence overflow warning net: lan966x: Fix possible deadlock inside PTP net/ulp: Remove redundant ->clone() test in inet_clone_ulp(). ... --- drivers/accessibility/speakup/kobjects.c | 1059 ++++++++++++++++++++++++++++++ 1 file changed, 1059 insertions(+) create mode 100644 drivers/accessibility/speakup/kobjects.c (limited to 'drivers/accessibility/speakup/kobjects.c') diff --git a/drivers/accessibility/speakup/kobjects.c b/drivers/accessibility/speakup/kobjects.c new file mode 100644 index 000000000..a7522d409 --- /dev/null +++ b/drivers/accessibility/speakup/kobjects.c @@ -0,0 +1,1059 @@ +// 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 + * Copyright (C) 2007 Novell Inc. + * + * Released under the GPL version 2 only. + * + */ +#include /* For kmalloc. */ +#include +#include +#include +#include +#include +#include + +#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) +{ + u_char tmp[256]; + int len; + int bytes; + const char *ptr = buf; + unsigned long flags; + + if (!synth) + return -EPERM; + + len = strlen(buf); + spin_lock_irqsave(&speakup_info.spinlock, flags); + while (len > 0) { + bytes = min_t(size_t, len, 250); + strncpy(tmp, ptr, bytes); + tmp[bytes] = '\0'; + string_unescape_any_inplace(tmp); + synth_printf("%s", tmp); + ptr += bytes; + len -= bytes; + } + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + 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); +} -- cgit v1.2.3