diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /drivers/accessibility/speakup/kobjects.c | |
download | linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip |
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
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().
...
Diffstat (limited to 'drivers/accessibility/speakup/kobjects.c')
-rw-r--r-- | drivers/accessibility/speakup/kobjects.c | 1059 |
1 files changed, 1059 insertions, 0 deletions
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 <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) +{ + 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); +} |